Index: test/wasm/undefined-weak-call.ll =================================================================== --- /dev/null +++ test/wasm/undefined-weak-call.ll @@ -0,0 +1,91 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: wasm-ld --check-signatures --no-entry %t.o -o %t.wasm +; RUN: obj2yaml %t.wasm | FileCheck %s + +; Check that calling an undefined weak function generates an appropriate stub +; that will fail at runtime with "unreachable". + +target triple = "wasm32-unknown-unknown-wasm" + +declare extern_weak void @weakFunc() local_unnamed_addr +define void @callWeakFunc() { + call void @weakFunc() + ret void +} + +; CHECK: --- !WASM +; CHECK-NEXT: FileHeader: +; CHECK-NEXT: Version: 0x00000001 +; CHECK-NEXT: Sections: +; CHECK-NEXT: - Type: TYPE +; CHECK-NEXT: Signatures: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: ReturnType: NORESULT +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Type: FUNCTION +; CHECK-NEXT: FunctionTypes: [ 0, 0, 0 ] +; CHECK-NEXT: - Type: TABLE +; CHECK-NEXT: Tables: +; CHECK-NEXT: - ElemType: ANYFUNC +; CHECK-NEXT: Limits: +; CHECK-NEXT: Flags: [ HAS_MAX ] +; CHECK-NEXT: Initial: 0x00000001 +; CHECK-NEXT: Maximum: 0x00000001 +; CHECK-NEXT: - Type: MEMORY +; CHECK-NEXT: Memories: +; CHECK-NEXT: - Initial: 0x00000002 +; CHECK-NEXT: - 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: 66560 +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 66560 +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1024 +; CHECK-NEXT: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: __heap_base +; CHECK-NEXT: Kind: GLOBAL +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: - Name: __data_end +; CHECK-NEXT: Kind: GLOBAL +; CHECK-NEXT: Index: 2 +; CHECK-NEXT: - Name: callWeakFunc +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Locals: +; CHECK-NEXT: Body: 1081808080000B +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Locals: +; CHECK-NEXT: Body: 000B +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Locals: +; CHECK-NEXT: Body: 0B +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: name +; CHECK-NEXT: FunctionNames: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: callWeakFunc +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: __undefined +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Name: __wasm_call_ctors +; CHECK-NEXT: ... Index: test/wasm/undefined-weak-getglobal.test =================================================================== --- /dev/null +++ test/wasm/undefined-weak-getglobal.test @@ -0,0 +1,48 @@ +# RUN: yaml2obj %s > %t.o +# RUN: not lld -flavor wasm --no-entry %t.o -o %t.wasm 2>&1 | FileCheck %s + +# Check linking fails when the input file contains weak symbols, and relocations +# which reference those symbols and cannot be resolved. + +# CHECK: {{.*}}.o: undefined weak global symbol: weakGlobal + +--- !WASM +FileHeader: + Version: 0x00000001 +Sections: + - Type: TYPE + Signatures: + - Index: 0 + ReturnType: NORESULT + ParamTypes: + - Type: IMPORT + Imports: + - Module: env + Field: weakGlobal + Kind: GLOBAL + GlobalType: I32 + GlobalMutable: true + - Type: FUNCTION + FunctionTypes: [ 0 ] + - Type: CODE + Relocations: + - Type: R_WEBASSEMBLY_GLOBAL_INDEX_LEB + Index: 0 + Offset: 0x00000004 + Functions: + - Index: 0 + Locals: + Body: 2380808080001A0F0B + - Type: CUSTOM + Name: linking + SymbolTable: + - Index: 0 + Kind: GLOBAL + Name: weakGlobal + Flags: [ UNDEFINED, BINDING_WEAK ] + Global: 0 + - Index: 1 + Kind: FUNCTION + Name: getWeakGlobal + Flags: [ ] + Function: 0 Index: wasm/Driver.cpp =================================================================== --- wasm/Driver.cpp +++ wasm/Driver.cpp @@ -323,6 +323,10 @@ for (InputFile *F : Files) Symtab->addFile(F); + // Add synthetic dummies for weak undefined functions. + if (!Config->Relocatable) + Symtab->handleWeakUndefines(); + // Make sure we have resolved all symbols. if (!Config->Relocatable && !Config->AllowUndefined) { Symtab->reportRemainingUndefines(); Index: wasm/InputFiles.cpp =================================================================== --- wasm/InputFiles.cpp +++ wasm/InputFiles.cpp @@ -65,14 +65,8 @@ uint32_t ObjFile::calcNewValue(const WasmRelocation &Reloc) const { switch (Reloc.Type) { case R_WEBASSEMBLY_TABLE_INDEX_I32: - case R_WEBASSEMBLY_TABLE_INDEX_SLEB: { - // The null case is possible, if you take the address of a weak function - // that's simply not supplied. - FunctionSymbol *Sym = getFunctionSymbol(Reloc.Index); - if (Sym->hasTableIndex()) - return Sym->getTableIndex(); - return 0; - } + case R_WEBASSEMBLY_TABLE_INDEX_SLEB: + return getFunctionSymbol(Reloc.Index)->getTableIndex(); case R_WEBASSEMBLY_MEMORY_ADDR_SLEB: case R_WEBASSEMBLY_MEMORY_ADDR_I32: case R_WEBASSEMBLY_MEMORY_ADDR_LEB: Index: wasm/MarkLive.cpp =================================================================== --- wasm/MarkLive.cpp +++ wasm/MarkLive.cpp @@ -73,8 +73,16 @@ InputChunk *C = Q.pop_back_val(); for (const WasmRelocation Reloc : C->getRelocations()) { - if (Reloc.Type != R_WEBASSEMBLY_TYPE_INDEX_LEB) - Enqueue(C->File->getSymbol(Reloc.Index)); + if (Reloc.Type == R_WEBASSEMBLY_TYPE_INDEX_LEB) + continue; + Symbol *Sym = C->File->getSymbol(Reloc.Index); + // Don't mark functions live if we're taking the address of a synthetic + // function which already has a table index. + if ((Reloc.Type == R_WEBASSEMBLY_TABLE_INDEX_SLEB || + Reloc.Type == R_WEBASSEMBLY_TABLE_INDEX_I32) && + cast(Sym)->hasTableIndex()) + continue; + Enqueue(Sym); } } Index: wasm/SymbolTable.h =================================================================== --- wasm/SymbolTable.h +++ wasm/SymbolTable.h @@ -42,11 +42,15 @@ std::vector ObjectFiles; + void handleWeakUndefines(); void reportRemainingUndefines(); ArrayRef getSymbols() const { return SymVector; } Symbol *find(StringRef Name); + ArrayRef getWeakUndefinedStubs() const + { return WeakUndefinedStubs; } + Symbol *addDefinedFunction(StringRef Name, uint32_t Flags, InputFile *File, InputFunction *Function); Symbol *addDefinedData(StringRef Name, uint32_t Flags, InputFile *File, @@ -77,6 +81,7 @@ llvm::DenseMap SymMap; std::vector SymVector; + std::vector WeakUndefinedStubs; llvm::DenseMap Comdats; }; Index: wasm/SymbolTable.cpp =================================================================== --- wasm/SymbolTable.cpp +++ wasm/SymbolTable.cpp @@ -33,6 +33,48 @@ ObjectFiles.push_back(F); } +static const uint8_t UNREACHABLE_FN[] = { + 0x03 /* ULEB length */, 0x00 /* ULEB num locals */, + 0x00 /* opcode unreachable */, 0x0b /* opcode end */ +}; + +void SymbolTable::handleWeakUndefines() { + DenseMap UndefinedFunctions; + for (Symbol *Sym : SymVector) { + if (!Sym->isUndefined() || !Sym->isWeak() || isa(Sym)) + continue; + + // Report an error for weak undefined globals. Even though weak, we can't + // allow these to remain undefined, since any code that references them + // can't be fixed up by simple patching/relocations. + if (isa(Sym)) { + error(toString(Sym->getFile()) + ": undefined weak global symbol: " + + toString(*Sym)); + continue; + } + + // It is possible for undefined functions not to have a signature (eg. if + // added via "--undefined"), but weak undefined ones do have a signature. + const WasmSignature &Sig = *cast(Sym)->getFunctionType(); + + // Add a synthetic dummy for weak undefined functions. These dummies will + // be GC'd if not used as the target of any "call" instructions. + InputFunction *&Function = UndefinedFunctions[Sig]; + if (!Function) { + Function = make(Sig, UNREACHABLE_FN, "__undefined"); + WeakUndefinedStubs.emplace_back(Function); + } + // Hide our dummy to prevent export + uint32_t Flags = (Sym->getFlags() & ~WASM_SYMBOL_VISIBILITY_MASK) | + WASM_SYMBOL_VISIBILITY_HIDDEN; + auto *DefSym = replaceSymbol(Sym, Sym->getName(), Flags, + nullptr, Function); + // Ensure it compares equal to the null pointer, and so that table relocs + // don't pull in the stub body (only call-operand relocs should do that). + DefSym->setTableIndex(0); + } +} + void SymbolTable::reportRemainingUndefines() { SetVector Undefs; for (Symbol *Sym : SymVector) { Index: wasm/Symbols.h =================================================================== --- wasm/Symbols.h +++ wasm/Symbols.h @@ -62,6 +62,7 @@ bool isLocal() const; bool isWeak() const; bool isHidden() const; + uint32_t getFlags() const { return Flags; } // Returns the symbol name. StringRef getName() const { return Name; } Index: wasm/Writer.cpp =================================================================== --- wasm/Writer.cpp +++ wasm/Writer.cpp @@ -724,15 +724,19 @@ void Writer::assignIndexes() { uint32_t FunctionIndex = NumImportedFunctions + InputFunctions.size(); + auto AddDefinedFunction = [&](InputFunction *Func) { + if (!Func->Live) + return; + InputFunctions.emplace_back(Func); + Func->setOutputIndex(FunctionIndex++); + }; for (ObjFile *File : Symtab->ObjectFiles) { DEBUG(dbgs() << "Functions: " << File->getName() << "\n"); - for (InputFunction *Func : File->Functions) { - if (!Func->Live) - continue; - InputFunctions.emplace_back(Func); - Func->setOutputIndex(FunctionIndex++); - } + for (InputFunction *Func : File->Functions) + AddDefinedFunction(Func); } + for (InputFunction *Func : Symtab->getWeakUndefinedStubs()) + AddDefinedFunction(Func); uint32_t TableIndex = kInitialTableOffset; auto HandleRelocs = [&](InputChunk *Chunk) {