diff --git a/lld/test/wasm/weak-undefined-pic.s b/lld/test/wasm/weak-undefined-pic.s new file mode 100644 --- /dev/null +++ b/lld/test/wasm/weak-undefined-pic.s @@ -0,0 +1,90 @@ +# Checks handling of undefined weak external functions. When the +# static linker decides they are undefined, check GOT relocations +# resolve to zero (i.e. a global that contains zero.). +# +# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s +# RUN: wasm-ld %t.o -o %t1.wasm +# RUN: obj2yaml %t1.wasm | FileCheck %s +# +# With `--unresolved-symbols=ignore-all` the behaviour should be the same +# as the default.> +# +# RUN: wasm-ld --unresolved-symbols=ignore-all %t.o -o %t2.wasm +# RUN: obj2yaml %t2.wasm | FileCheck %s + +.globl get_foo_addr +get_foo_addr: + .functype get_foo_addr () -> (i32) + global.get foo@GOT + end_function + +.globl _start +_start: + .functype _start () -> () + call get_foo_addr + end_function + +.weak foo +.functype foo () -> (i32) + +# Verify that we do not generate dynamnic relocations for the GOT entry. + +# CHECK-NOT: __wasm_apply_relocs + +# Verify that we do not generate an import for foo + +# CHECK-NOT: - Type: IMPORT + +# 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: 66560 +# Global 'undefined_weak:foo' representing the GOT entry for foo +# Unlike other internal GOT entries that need to be mutable this one +# is immutable and not updated by `__wasm_apply_relocs` +# CHECK-NEXT: - Index: 1 +# CHECK-NEXT: Type: I32 +# CHECK-NEXT: Mutable: false +# CHECK-NEXT: InitExpr: +# CHECK-NEXT: Opcode: I32_CONST +# CHECK-NEXT: Value: 0 + +# CHECK: - Type: CUSTOM +# CHECK-NEXT: Name: name +# CHECK-NEXT: FunctionNames: +# CHECK-NEXT: - Index: 0 +# CHECK-NEXT: Name: 'undefined_weak:foo' +# CHECK-NEXT: - Index: 1 +# CHECK-NEXT: Name: get_foo_addr +# CHECK-NEXT: - Index: 2 +# CHECK-NEXT: Name: _start +# CHECK-NEXT: GlobalNames: +# CHECK-NEXT: - Index: 0 +# CHECK-NEXT: Name: __stack_pointer +# CHECK-NEXT: - Index: 1 +# CHECK-NEXT: Name: 'undefined_weak:foo' + +# With `-pie` or `-shared` the resolution should is defered to the dynamic +# linker and the function address should be imported as GOT.func.foo. +# +# RUN: wasm-ld --experimental-pic -pie %t.o -o %t3.wasm +# RUN: obj2yaml %t3.wasm | FileCheck %s --check-prefix=IMPORT + +# IMPORT: - Type: IMPORT +# IMPORT: - Module: GOT.func +# IMPORT-NEXT: Field: foo +# IMPORT-NEXT: Kind: GLOBAL +# IMPORT-NEXT: GlobalType: I32 +# IMPORT-NEXT: GlobalMutable: true + +# IMPORT: GlobalNames: +# IMPORT-NEXT: - Index: 0 +# IMPORT-NEXT: Name: __memory_base +# IMPORT-NEXT: - Index: 1 +# IMPORT-NEXT: Name: __table_base +# IMPORT-NEXT: - Index: 2 +# IMPORT-NEXT: Name: foo diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -973,7 +973,7 @@ warn(Twine("symbol exported via --export not found: ") + arg->getValue()); } - if (!config->relocatable) { + if (!config->relocatable && !config->isPic) { // Add synthetic dummies for weak undefined functions. Must happen // after LTO otherwise functions may not yet have signatures. symtab->handleWeakUndefines(); diff --git a/lld/wasm/MarkLive.cpp b/lld/wasm/MarkLive.cpp --- a/lld/wasm/MarkLive.cpp +++ b/lld/wasm/MarkLive.cpp @@ -142,7 +142,7 @@ reloc.Type == R_WASM_TABLE_INDEX_I32 || reloc.Type == R_WASM_TABLE_INDEX_I64) { auto *funcSym = cast(sym); - if (funcSym->hasTableIndex() && funcSym->getTableIndex() == 0) + if (funcSym->isStub) continue; } diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp --- a/lld/wasm/Relocations.cpp +++ b/lld/wasm/Relocations.cpp @@ -62,7 +62,9 @@ << "ignoring undefined symbol: " + toString(*sym) + "\n"); f->stubFunction = symtab->createUndefinedStub(*f->getSignature()); f->stubFunction->markLive(); - f->setTableIndex(0); + // Mark the function itself as a stub which prevents it from being + // assigned a table entry. + f->isStub = true; } } break; diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp --- a/lld/wasm/SymbolTable.cpp +++ b/lld/wasm/SymbolTable.cpp @@ -673,9 +673,9 @@ // to be exported outside the object file. replaceSymbol(sym, debugName, WASM_SYMBOL_BINDING_LOCAL, nullptr, func); - // 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). - func->setTableIndex(0); + // Ensure the stub function doesn't get a table entry. Its address + // should always compare equal to the null pointer. + sym->isStub = true; return func; } diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h --- a/lld/wasm/Symbols.h +++ b/lld/wasm/Symbols.h @@ -124,7 +124,7 @@ Symbol(StringRef name, Kind k, uint32_t flags, InputFile *f) : name(name), file(f), symbolKind(k), referenced(!config->gcSections), requiresGOT(false), isUsedInRegularObj(false), forceExport(false), - canInline(false), traced(false), flags(flags) {} + canInline(false), traced(false), isStub(false), flags(flags) {} StringRef name; InputFile *file; @@ -157,6 +157,11 @@ // True if this symbol is specified by --trace-symbol option. bool traced : 1; + // True if this symbol is a linker-synthesized stub function (traps when + // called) and should otherwise be treated as missing/undefined. See + // SymbolTable::replaceWithUndefined. + bool isStub : 1; + uint32_t flags; }; diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp --- a/lld/wasm/SyntheticSections.cpp +++ b/lld/wasm/SyntheticSections.cpp @@ -303,6 +303,8 @@ writeU8(os, opcode_ptr_const, "CONST"); writeSleb128(os, d->getVirtualAddress(), "offset"); } else if (auto *f = dyn_cast(sym)) { + if (f->isStub) + continue; // Get __table_base writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); writeUleb128(os, WasmSym::tableBase->getGlobalIndex(), "__table_base"); @@ -329,12 +331,16 @@ // TODO(wvo): when do these need I64_CONST? for (const Symbol *sym : internalGotSymbols) { WasmGlobal global; - global.Type = {WASM_TYPE_I32, config->isPic}; + // In the case of dynamic linking, internal GOT entries + // need to be mutable since they get updated to the correct + // runtime value during `__wasm_apply_relocs`. + bool mutable_ = config->isPic & !sym->isStub; + global.Type = {WASM_TYPE_I32, mutable_}; global.InitExpr.Opcode = WASM_OPCODE_I32_CONST; if (auto *d = dyn_cast(sym)) global.InitExpr.Value.Int32 = d->getVirtualAddress(); else if (auto *f = dyn_cast(sym)) - global.InitExpr.Value.Int32 = f->getTableIndex(); + global.InitExpr.Value.Int32 = f->isStub ? 0 : f->getTableIndex(); else { assert(isa(sym)); global.InitExpr.Value.Int32 = 0; @@ -375,7 +381,10 @@ } void ElemSection::addEntry(FunctionSymbol *sym) { - if (sym->hasTableIndex()) + // Don't add stub functions to the wasm table. The address of all stub + // functions should be zero and they should they don't appear in the table. + // They only exist so that the calls to missing functions can validate. + if (sym->hasTableIndex() || sym->isStub) return; sym->setTableIndex(config->tableBase + indirectFunctions.size()); indirectFunctions.emplace_back(sym);