Index: test/wasm/Inputs/comdat1.ll =================================================================== --- /dev/null +++ test/wasm/Inputs/comdat1.ll @@ -0,0 +1,11 @@ +$inlineFn = comdat any +@constantData = weak_odr constant [3 x i8] c"abc", comdat($inlineFn) +define linkonce_odr i32 @inlineFn() comdat { +entry: + ret i32 ptrtoint ([3 x i8]* @constantData to i32) +} + +define i32 @callInline1() { +entry: + ret i32 ptrtoint (i32 ()* @inlineFn to i32) +} Index: test/wasm/Inputs/comdat2.ll =================================================================== --- /dev/null +++ test/wasm/Inputs/comdat2.ll @@ -0,0 +1,11 @@ +$inlineFn = comdat any +@constantData = weak_odr constant [3 x i8] c"abc", comdat($inlineFn) +define linkonce_odr i32 @inlineFn() comdat { +entry: + ret i32 ptrtoint ([3 x i8]* @constantData to i32) +} + +define i32 @callInline2() { +entry: + ret i32 ptrtoint (i32 ()* @inlineFn to i32) +} Index: test/wasm/comdats.ll =================================================================== --- /dev/null +++ test/wasm/comdats.ll @@ -0,0 +1,67 @@ +; RUN: llc -filetype=obj -mtriple=wasm32-unknown-uknown-wasm %p/Inputs/comdat1.ll -o %t1.o +; RUN: llc -filetype=obj -mtriple=wasm32-unknown-uknown-wasm %p/Inputs/comdat2.ll -o %t2.o +; RUN: llc -filetype=obj -mtriple=wasm32-unknown-uknown-wasm %s -o %t.o +; RUN: lld -flavor wasm -o %t.wasm %t.o %t1.o %t2.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +declare i32 @inlineFn() + +define void @_start() local_unnamed_addr { +entry: + %call = call i32 @inlineFn() + ret void +} + +; CHECK: - Type: GLOBAL +; CHECK-NEXT: Globals: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Type: I32 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 66576 +; CHECK-NEXT: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: inlineFn +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: - Name: callInline1 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 2 +; CHECK-NEXT: - Name: callInline2 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 3 +; CHECK-NEXT: - Type: ELEM +; CHECK-NEXT: Segments: +; CHECK-NEXT: - Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1 +; CHECK-NEXT: Functions: [ 1 ] +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Locals: +; CHECK-NEXT: Body: 1081808080001A0B +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Locals: +; CHECK-NEXT: Body: 4180888080000B +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Locals: +; CHECK-NEXT: Body: 4181808080000B +; CHECK-NEXT: - Index: 3 +; CHECK-NEXT: Locals: +; CHECK-NEXT: Body: 4181808080000B +; CHECK-NEXT: - Type: DATA +; CHECK-NEXT: Segments: +; CHECK-NEXT: - SectionOffset: 7 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1024 +; CHECK-NEXT: Content: '616263' Index: test/wasm/relocatable.ll =================================================================== --- test/wasm/relocatable.ll +++ test/wasm/relocatable.ll @@ -17,6 +17,13 @@ @func_addr2 = hidden global i32()* @foo_import, align 4 @data_addr1 = hidden global i64* @data_import, align 8 +$func_comdat = comdat any +@data_comdat = weak_odr constant [3 x i8] c"abc", comdat($func_comdat) +define linkonce_odr i32 @func_comdat() comdat { +entry: + ret i32 ptrtoint ([3 x i8]* @data_comdat to i32) +} + ; CHECK: --- !WASM ; CHECK-NEXT: FileHeader: ; CHECK-NEXT: Version: 0x00000001 @@ -49,7 +56,7 @@ ; CHECK-NEXT: GlobalType: I32 ; CHECK-NEXT: GlobalMutable: false ; CHECK-NEXT: - Type: FUNCTION -; CHECK-NEXT: FunctionTypes: [ 0, 2 ] +; CHECK-NEXT: FunctionTypes: [ 0, 2, 2 ] ; CHECK-NEXT: - Type: TABLE ; CHECK-NEXT: Tables: ; CHECK-NEXT: - ElemType: ANYFUNC @@ -73,18 +80,24 @@ ; CHECK-NEXT: Mutable: false ; CHECK-NEXT: InitExpr: ; CHECK-NEXT: Opcode: I32_CONST -; CHECK-NEXT: Value: 8 +; CHECK-NEXT: Value: 20 ; CHECK-NEXT: - Index: 3 ; CHECK-NEXT: Type: I32 ; CHECK-NEXT: Mutable: false ; CHECK-NEXT: InitExpr: ; CHECK-NEXT: Opcode: I32_CONST -; CHECK-NEXT: Value: 12 +; CHECK-NEXT: Value: 8 ; CHECK-NEXT: - Index: 4 ; CHECK-NEXT: Type: I32 ; CHECK-NEXT: Mutable: false ; CHECK-NEXT: InitExpr: ; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 12 +; CHECK-NEXT: - Index: 5 +; CHECK-NEXT: Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST ; CHECK-NEXT: Value: 16 ; CHECK-NEXT: - Type: EXPORT ; CHECK-NEXT: Exports: @@ -94,18 +107,24 @@ ; CHECK-NEXT: - Name: my_func ; CHECK-NEXT: Kind: FUNCTION ; CHECK-NEXT: Index: 3 +; CHECK-NEXT: - Name: func_comdat +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 4 ; CHECK-NEXT: - Name: hello_str ; CHECK-NEXT: Kind: GLOBAL ; CHECK-NEXT: Index: 1 -; CHECK-NEXT: - Name: func_addr1 +; CHECK-NEXT: - Name: data_comdat ; CHECK-NEXT: Kind: GLOBAL ; CHECK-NEXT: Index: 2 -; CHECK-NEXT: - Name: func_addr2 +; CHECK-NEXT: - Name: func_addr1 ; CHECK-NEXT: Kind: GLOBAL ; CHECK-NEXT: Index: 3 -; CHECK-NEXT: - Name: data_addr1 +; CHECK-NEXT: - Name: func_addr2 ; CHECK-NEXT: Kind: GLOBAL ; CHECK-NEXT: Index: 4 +; CHECK-NEXT: - Name: data_addr1 +; CHECK-NEXT: Kind: GLOBAL +; CHECK-NEXT: Index: 5 ; CHECK-NEXT: - Type: ELEM ; CHECK-NEXT: Segments: ; CHECK-NEXT: - Offset: @@ -123,6 +142,9 @@ ; CHECK-NEXT: - Type: R_WEBASSEMBLY_FUNCTION_INDEX_LEB ; CHECK-NEXT: Index: 1 ; CHECK-NEXT: Offset: 0x00000013 +; CHECK-NEXT: - Type: R_WEBASSEMBLY_MEMORY_ADDR_SLEB +; CHECK-NEXT: Index: 2 +; CHECK-NEXT: Offset: 0x0000001F ; CHECK-NEXT: Functions: ; CHECK-NEXT: - Index: 2 ; CHECK-NEXT: Locals: @@ -130,6 +152,9 @@ ; CHECK-NEXT: - Index: 3 ; CHECK-NEXT: Locals: ; CHECK-NEXT: Body: 1081808080001A41010B +; CHECK-NEXT: - Index: 4 +; CHECK-NEXT: Locals: +; CHECK-NEXT: Body: 4194808080000B ; CHECK-NEXT: - Type: DATA ; CHECK-NEXT: Relocations: ; CHECK-NEXT: - Type: R_WEBASSEMBLY_TABLE_INDEX_I32 @@ -166,26 +191,43 @@ ; CHECK-NEXT: Opcode: I32_CONST ; CHECK-NEXT: Value: 16 ; CHECK-NEXT: Content: '00000000' +; CHECK-NEXT: - SectionOffset: 45 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 20 +; CHECK-NEXT: Content: '616263' ; CHECK-NEXT: - Type: CUSTOM ; CHECK-NEXT: Name: linking -; CHECK-NEXT: DataSize: 20 +; CHECK-NEXT: DataSize: 23 ; CHECK-NEXT: SegmentInfo: ; CHECK-NEXT: - Index: 0 ; CHECK-NEXT: Name: .rodata.hello_str ; CHECK-NEXT: Alignment: 1 -; CHECK-NEXT: Flags: [ ] +; CHECK-NEXT: Flags: [ ] ; CHECK-NEXT: - Index: 1 ; CHECK-NEXT: Name: .data.func_addr1 ; CHECK-NEXT: Alignment: 4 -; CHECK-NEXT: Flags: [ ] +; CHECK-NEXT: Flags: [ ] ; CHECK-NEXT: - Index: 2 ; CHECK-NEXT: Name: .data.func_addr2 ; CHECK-NEXT: Alignment: 4 -; CHECK-NEXT: Flags: [ ] +; CHECK-NEXT: Flags: [ ] ; CHECK-NEXT: - Index: 3 ; CHECK-NEXT: Name: .data.data_addr1 ; CHECK-NEXT: Alignment: 8 -; CHECK-NEXT: Flags: [ ] +; CHECK-NEXT: Flags: [ ] +; CHECK-NEXT: - Index: 4 +; CHECK-NEXT: Name: .rodata.data_comdat +; CHECK-NEXT: Alignment: 1 +; CHECK-NEXT: Flags: [ ] +; CHECK-NEXT: Comdats: +; CHECK-NEXT: - Name: func_comdat +; CHECK-NEXT: Entries: +; CHECK-NEXT: - Kind: FUNCTION +; CHECK-NEXT: Index: 4 +; CHECK-NEXT: - Kind: DATA +; CHECK-NEXT: Index: 4 ; CHECK-NEXT: - Type: CUSTOM ; CHECK-NEXT: Name: name ; CHECK-NEXT: FunctionNames: Index: wasm/InputChunks.h =================================================================== --- wasm/InputChunks.h +++ wasm/InputChunks.h @@ -34,7 +34,9 @@ class InputChunk { public: + virtual const uint8_t *getData() const = 0; virtual uint32_t getSize() const = 0; + virtual StringRef getComdat() const = 0; void copyRelocations(const WasmSection &Section); @@ -47,13 +49,13 @@ uint32_t getOutputOffset() const { return OutputOffset; } + bool Discarded = false; std::vector OutRelocations; protected: InputChunk(const ObjFile &F) : File(F) {} virtual ~InputChunk() = default; void calcRelocations(); - virtual const uint8_t *getData() const = 0; virtual uint32_t getInputSectionOffset() const = 0; std::vector Relocations; @@ -89,6 +91,8 @@ return Segment.Data.Content.data(); } uint32_t getSize() const override { return Segment.Data.Content.size(); } + StringRef getComdat() const override { return Segment.Data.Comdat; } + uint32_t getAlignment() const { return Segment.Data.Alignment; } uint32_t startVA() const { return Segment.Data.Offset.Value.Int32; } uint32_t endVA() const { return startVA() + getSize(); } @@ -112,10 +116,11 @@ const ObjFile &F) : InputChunk(F), Signature(S), Function(Func) {} - uint32_t getSize() const override { return Function.Size; } const uint8_t *getData() const override { return File.CodeSection->Content.data() + getInputSectionOffset(); } + uint32_t getSize() const override { return Function.Size; } + StringRef getComdat() const override { return Function.Comdat; } uint32_t getOutputIndex() const { return OutputIndex.getValue(); }; bool hasOutputIndex() const { return OutputIndex.hasValue(); }; Index: wasm/InputChunks.cpp =================================================================== --- wasm/InputChunks.cpp +++ wasm/InputChunks.cpp @@ -1,4 +1,4 @@ -//===- InputSegment.cpp ---------------------------------------------------===// +//===- InputChunks.cpp ----------------------------------------------------===// // // The LLVM Linker // Index: wasm/InputFiles.h =================================================================== --- wasm/InputFiles.h +++ wasm/InputFiles.h @@ -33,6 +33,7 @@ namespace lld { namespace wasm { +class InputChunk; class InputFunction; class InputSegment; @@ -122,6 +123,7 @@ const WasmSignature *getFunctionSig(const WasmSymbol &Sym) const; uint32_t getGlobalValue(const WasmSymbol &Sym) const; InputFunction *getFunction(const WasmSymbol &Sym) const; + bool isExcludedByComdat(InputChunk *Chunk) const; // List of all symbols referenced or defined by this file. std::vector Symbols; Index: wasm/InputFiles.cpp =================================================================== --- wasm/InputFiles.cpp +++ wasm/InputFiles.cpp @@ -170,6 +170,11 @@ return Functions[FunctionIndex]; } +bool ObjFile::isExcludedByComdat(InputChunk *Chunk) const { + StringRef Comdat = Chunk->getComdat(); + return !Comdat.empty() && Symtab->findComdat(Comdat) != this; +} + void ObjFile::initializeSymbols() { Symbols.reserve(WasmObj->getNumberOfSymbols()); @@ -184,8 +189,16 @@ } } - FunctionSymbols.resize(NumFunctionImports + WasmObj->functions().size()); - GlobalSymbols.resize(NumGlobalImports + WasmObj->globals().size()); + ArrayRef Funcs = WasmObj->functions(); + ArrayRef FuncTypes = WasmObj->functionTypes(); + ArrayRef Types = WasmObj->types(); + ArrayRef Globals = WasmObj->globals(); + + for (const auto &C : WasmObj->comdats()) + Symtab->addComdat(C, this); + + FunctionSymbols.resize(NumFunctionImports + Funcs.size()); + GlobalSymbols.resize(NumGlobalImports + Globals.size()); for (const WasmSegment &S : WasmObj->dataSegments()) { InputSegment *Seg = make(S, *this); @@ -193,9 +206,6 @@ Segments.emplace_back(Seg); } - ArrayRef Funcs = WasmObj->functions(); - ArrayRef FuncTypes = WasmObj->functionTypes(); - ArrayRef Types = WasmObj->types(); for (size_t I = 0; I < Funcs.size(); ++I) { const WasmFunction &Func = Funcs[I]; const WasmSignature &Sig = Types[FuncTypes[I]]; @@ -210,21 +220,35 @@ const WasmSymbol &WasmSym = WasmObj->getWasmSymbol(Sym.getRawDataRefImpl()); Symbol *S; switch (WasmSym.Type) { + case WasmSymbol::SymbolType::FUNCTION_EXPORT: { + InputFunction *Function = getFunction(WasmSym); + if (!isExcludedByComdat(Function)) { + S = createDefined(WasmSym, Symbol::Kind::DefinedFunctionKind, nullptr, + Function); + break; + } else { + Function->Discarded = true; + LLVM_FALLTHROUGH; // Exclude function, and add the symbol as undefined + } + } case WasmSymbol::SymbolType::FUNCTION_IMPORT: S = createUndefined(WasmSym, Symbol::Kind::UndefinedFunctionKind, getFunctionSig(WasmSym)); break; + case WasmSymbol::SymbolType::GLOBAL_EXPORT: { + InputSegment *Segment = getSegment(WasmSym); + if (!isExcludedByComdat(Segment)) { + S = createDefined(WasmSym, Symbol::Kind::DefinedGlobalKind, + Segment, nullptr, getGlobalValue(WasmSym)); + break; + } else { + Segment->Discarded = true; + LLVM_FALLTHROUGH; // Exclude global, and add the symbol as undefined + } + } case WasmSymbol::SymbolType::GLOBAL_IMPORT: S = createUndefined(WasmSym, Symbol::Kind::UndefinedGlobalKind); break; - case WasmSymbol::SymbolType::GLOBAL_EXPORT: - S = createDefined(WasmSym, Symbol::Kind::DefinedGlobalKind, - getSegment(WasmSym), nullptr, getGlobalValue(WasmSym)); - break; - case WasmSymbol::SymbolType::FUNCTION_EXPORT: - S = createDefined(WasmSym, Symbol::Kind::DefinedFunctionKind, nullptr, - getFunction(WasmSym)); - break; case WasmSymbol::SymbolType::DEBUG_FUNCTION_NAME: // These are for debugging only, no need to create linker symbols for them continue; Index: wasm/SymbolTable.h =================================================================== --- wasm/SymbolTable.h +++ wasm/SymbolTable.h @@ -57,11 +57,15 @@ Symbol *addDefinedGlobal(StringRef Name); void addLazy(ArchiveFile *F, const Archive::Symbol *Sym); + bool addComdat(StringRef Name, ObjFile *); + ObjFile *findComdat(StringRef Name) const; + private: std::pair insert(StringRef Name); llvm::DenseMap SymMap; std::vector SymVector; + llvm::DenseMap ComdatMap; }; extern SymbolTable *Symtab; Index: wasm/SymbolTable.cpp =================================================================== --- wasm/SymbolTable.cpp +++ wasm/SymbolTable.cpp @@ -221,3 +221,19 @@ F->addMember(Sym); } } + +bool SymbolTable::addComdat(StringRef Name, ObjFile *F) { + DEBUG(dbgs() << "addComdat: " << Name << "\n"); + ObjFile *&File = ComdatMap[CachedHashStringRef(Name)]; + if (File) { + DEBUG(dbgs() << "COMDAT already defined\n"); + return false; + } + File = F; + return true; +} + +ObjFile *SymbolTable::findComdat(StringRef Name) const { + auto It = ComdatMap.find(CachedHashStringRef(Name)); + return It == ComdatMap.end() ? nullptr : It->second; +} Index: wasm/Writer.cpp =================================================================== --- wasm/Writer.cpp +++ wasm/Writer.cpp @@ -24,6 +24,7 @@ #include "llvm/Support/LEB128.h" #include +#include #define DEBUG_TYPE "lld" @@ -430,6 +431,42 @@ SubSection.finalizeContents(); SubSection.writeToStream(OS); } + + struct ComdatEntry { unsigned Kind; uint32_t Index; }; + std::map> Comdats; + uint32_t NumFunctionImports = ImportedFunctions.size(); + + for (uint32_t I = 0; I < DefinedFunctions.size(); ++I) { + StringRef Comdat = DefinedFunctions[I]->getComdat(); + if (!Comdat.empty()) + Comdats[Comdat].emplace_back( + ComdatEntry{WASM_COMDAT_FUNCTION, I + NumFunctionImports}); + } + for (uint32_t I = 0; I < Segments.size(); ++I) { + const auto &InputSegments = Segments[I]->InputSegments; + StringRef Comdat = InputSegments.empty() ? StringRef() + : InputSegments[0]->getComdat(); + for (const InputSegment *IS : InputSegments) + assert(IS->getComdat() == Comdat); + if (!Comdat.empty()) + Comdats[Comdat].emplace_back(ComdatEntry{WASM_COMDAT_DATA, I}); + } + + if (!Comdats.empty()) { + SubSection SubSection(WASM_COMDAT_INFO); + writeUleb128(SubSection.getStream(), Comdats.size(), "num comdats"); + for (const auto &C : Comdats) { + writeStr(SubSection.getStream(), C.first, "comdat name"); + writeUleb128(SubSection.getStream(), 0, "comdat flags"); // flags for future use + writeUleb128(SubSection.getStream(), C.second.size(), "num entries"); + for (const ComdatEntry &Entry : C.second) { + writeUleb128(SubSection.getStream(), Entry.Kind, "entry kind"); + writeUleb128(SubSection.getStream(), Entry.Index, "entry index"); + } + } + SubSection.finalizeContents(); + SubSection.writeToStream(OS); + } } // Create the custom "name" section containing debug symbol names. @@ -629,6 +666,8 @@ for (ObjFile *File : Symtab->ObjectFiles) { DEBUG(dbgs() << "Functions: " << File->getName() << "\n"); for (InputFunction *Func : File->Functions) { + if (Func->Discarded) + continue; DefinedFunctions.emplace_back(Func); Func->setOutputIndex(FunctionIndex++); } @@ -664,6 +703,8 @@ void Writer::createOutputSegments() { for (ObjFile *File : Symtab->ObjectFiles) { for (InputSegment *Segment : File->Segments) { + if (Segment->Discarded) + continue; StringRef Name = getOutputDataSegmentName(Segment->getName()); OutputSegment *&S = SegmentMap[Name]; if (S == nullptr) {