diff --git a/lld/test/wasm/lto/signature-mismatch.ll b/lld/test/wasm/lto/signature-mismatch.ll --- a/lld/test/wasm/lto/signature-mismatch.ll +++ b/lld/test/wasm/lto/signature-mismatch.ll @@ -1,6 +1,6 @@ ; RUN: llc -filetype=obj -o %t.o %s ; RUN: llvm-as %S/Inputs/archive.ll -o %t1.o -; RUN: not wasm-ld --fatal-warnings %t.o %t1.o -o %t.wasm 2>&1 | FileCheck %s +; RUN: wasm-ld %t.o %t1.o -o %t.wasm 2>&1 | FileCheck %s ; Test that functions defined in bitcode correctly report signature ; mistmaches with existing undefined sybmols in normal objects. @@ -15,6 +15,6 @@ ret void } -; CHECK: error: function signature mismatch: f +; CHECK: warning: function signature mismatch: f ; CHECK: >>> defined as (i32) -> void in {{.*}}signature-mismatch.ll.tmp1.o ; CHECK: >>> defined as () -> void in lto.tmp diff --git a/lld/wasm/InputChunks.h b/lld/wasm/InputChunks.h --- a/lld/wasm/InputChunks.h +++ b/lld/wasm/InputChunks.h @@ -192,7 +192,10 @@ void setBody(ArrayRef body_) { body = body_; } protected: - ArrayRef data() const override { return body; } + ArrayRef data() const override { + assert(!body.empty()); + return body; + } StringRef name; StringRef debugName; diff --git a/lld/wasm/MarkLive.cpp b/lld/wasm/MarkLive.cpp --- a/lld/wasm/MarkLive.cpp +++ b/lld/wasm/MarkLive.cpp @@ -102,6 +102,15 @@ enqueue(WasmSym::initMemory); mark(); + + // For adaptor functions, if that adaptor is live then the target of that + // adaptor needs to be too. Normally relocations take care of this but + // the adaptors are synthetic and don't carry relocation information. + for (auto pair : symtab->adaptorFunctions) + if (pair.first->isLive()) + enqueue(pair.second); + + mark(); } void MarkLive::mark() { diff --git a/lld/wasm/SymbolTable.h b/lld/wasm/SymbolTable.h --- a/lld/wasm/SymbolTable.h +++ b/lld/wasm/SymbolTable.h @@ -81,6 +81,7 @@ void handleSymbolVariants(); void handleWeakUndefines(); + void createAdaptorFunctions(); std::vector objectFiles; std::vector sharedFiles; @@ -88,14 +89,16 @@ std::vector syntheticFunctions; std::vector syntheticGlobals; + std::vector> adaptorFunctions; + private: std::pair insert(StringRef name, const InputFile *file); std::pair insertName(StringRef name); bool getFunctionVariant(Symbol* sym, const WasmSignature *sig, const InputFile *file, Symbol **out); - InputFunction *replaceWithUnreachable(Symbol *sym, const WasmSignature &sig, - StringRef debugName); + InputFunction *replaceWithUnreachable(Symbol *sym, StringRef debugName); + bool replaceWithAdaptorFunction(FunctionSymbol *sym, FunctionSymbol *target); // Maps symbol names to index into the symVector. -1 means that symbols // is to not yet in the vector but it should have tracing enabled if it is diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp --- a/lld/wasm/SymbolTable.cpp +++ b/lld/wasm/SymbolTable.cpp @@ -585,14 +585,97 @@ 0x00 /* opcode unreachable */, 0x0b /* opcode end */ }; +// Replace the given symbol body with an adaptor function which adds or removes +// arguments in order to call a function with a different signature. +// This can only handle adding or removing arguments, if any of the argument +// types don't match the adaptor creation will fail. +// See handleSymbolVariants add replaceWithUnreachable. +bool SymbolTable::replaceWithAdaptorFunction(FunctionSymbol *sym, + FunctionSymbol *target) { + LLVM_DEBUG(dbgs() << "replaceWithAdaptorFunction: " << sym->getName() << "\n"); + const WasmSignature &srcSig = *sym->getSignature(); + const WasmSignature &targetSig = *target->getSignature(); + + if (srcSig.Returns != targetSig.Returns) { + LLVM_DEBUG(dbgs() << "return type mismatch\n"); + return false; + } + + for (size_t i = 0; i < srcSig.Params.size() && i < targetSig.Params.size(); + i++) { + if (srcSig.Params[i] != targetSig.Params[i]) { + LLVM_DEBUG(dbgs() << "param type mismatch\n"); + return false; + } + } + + StringRef name = saver.save("adaptor:" + sym->getName()); + auto *func = make(*sym->getSignature(), name, name); + syntheticFunctions.emplace_back(func); + auto defined = replaceSymbol(sym, sym->getName(), sym->getFlags(), nullptr, + func); + adaptorFunctions.emplace_back(defined, target); + return true; +} + +void SymbolTable::createAdaptorFunctions() { + for (auto pair : adaptorFunctions) { + DefinedFunction* sym = pair.first; + FunctionSymbol* target = pair.second; + const WasmSignature &srcSig = *sym->getSignature(); + const WasmSignature &targetSig = *target->getSignature(); + std::string bodyContent; + { + raw_string_ostream os(bodyContent); + writeUleb128(os, 0, "num locals"); + for (size_t i = 0; i < targetSig.Params.size(); i++) { + if (i < srcSig.Params.size()) { + writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get"); + writeSleb128(os, i, "param index"); + } else { + switch (targetSig.Params[i]) { + case ValType::I32: + writeU8(os, WASM_OPCODE_I32_CONST, "i32.const"); + break; + case ValType::I64: + writeU8(os, WASM_OPCODE_I32_CONST, "i64.const"); + break; + case ValType::F32: + writeU8(os, WASM_OPCODE_I32_CONST, "f32.const"); + break; + case ValType::F64: + writeU8(os, WASM_OPCODE_I32_CONST, "f64.const"); + break; + default: + llvm_unreachable("unhandled param type"); + } + writeSleb128(os, 0, "zero"); + } + } + writeU8(os, WASM_OPCODE_CALL, "CALL"); + writeUleb128(os, target->getFunctionIndex(), "function index"); + writeU8(os, WASM_OPCODE_END, "END"); + } + + std::string functionBody; + { + raw_string_ostream os(functionBody); + writeUleb128(os, bodyContent.size(), "function size"); + os << bodyContent; + } + ArrayRef body = arrayRefFromStringRef(saver.save(functionBody)); + cast(sym->function)->setBody(body); + } +} + // Replace the given symbol body with an unreachable function. // This is used by handleWeakUndefines in order to generate a callable // equivalent of an undefined function and also handleSymbolVariants for // undefined functions that don't match the signature of the definition. InputFunction *SymbolTable::replaceWithUnreachable(Symbol *sym, - const WasmSignature &sig, StringRef debugName) { - auto *func = make(sig, sym->getName(), debugName); + auto *func = + make(*sym->getSignature(), sym->getName(), debugName); func->setBody(unreachableFn); syntheticFunctions.emplace_back(func); replaceSymbol(sym, sym->getName(), sym->getFlags(), nullptr, @@ -609,8 +692,7 @@ if (!sym->isUndefWeak()) continue; - const WasmSignature *sig = sym->getSignature(); - if (!sig) { + if (!sym->getSignature()) { // It is possible for undefined functions not to have a signature (eg. if // added via "--undefined"), but weak undefined ones do have a signature. // Lazy symbols may not be functions and therefore Sig can still be null @@ -622,7 +704,7 @@ // Add a synthetic dummy for weak undefined functions. These dummies will // be GC'd if not used as the target of any "call" instructions. StringRef debugName = saver.save("undefined:" + toString(*sym)); - InputFunction* func = replaceWithUnreachable(sym, *sig, debugName); + InputFunction* func = replaceWithUnreachable(sym, debugName); // 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); @@ -645,8 +727,29 @@ warn(msg); } -// Remove any variant symbols that were created due to function signature +// Resolve any "variant" symbols that were created due to function signature // mismatches. +// These variants are created when different compilation units disagree about +// the signature of a function. Normally this represents a bug in the program +// but there are several use cases that rely on the linker being able to +// successfully link such programs. Two motivating examples: +// +// 1) When _start (crt1.o) calls main we want to be able to support both +// 0-argument, 2-argument and 3-argument forms. +// 2) When cmake tests for the existence of a function it compiles a tiny +// program containing a reference the function, but does not use the +// functions correct signature. +// +// When we encounter mismatches we take two approaches to resolving them: +// +// 1) Attempt to create a wrapper function that either drops arguments or +// adds extern arguments containing zeros. This only works if the return +// types match that the argument that they both share also match. +// 2) If we fail to create the wrapper then we create a dummy function with the +// correct signature that will trap at runtime. +// +// In either case we issue a warning since the resulting program is unlikely to +// be correct. void SymbolTable::handleSymbolVariants() { for (auto pair : symVariants) { // Push the initial symbol onto the list of variants. @@ -686,8 +789,13 @@ if (symbol != defined) { auto *f = cast(symbol); reportFunctionSignatureMismatch(symName, f, defined, false); - StringRef debugName = saver.save("unreachable:" + toString(*f)); - replaceWithUnreachable(f, *f->signature, debugName); + // First attempt to replace the callee with an adaptor function. If + // that fails fall back to creating a dummy function that will trap at + // runtime. + if (!replaceWithAdaptorFunction(f, defined)) { + StringRef debugName = saver.save("unreachable:" + toString(*f)); + replaceWithUnreachable(f, debugName); + } } } } diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -1006,6 +1006,8 @@ assignIndexes(); log("-- calculateInitFunctions"); calculateInitFunctions(); + log("-- createAdaptorFunctions"); + symtab->createAdaptorFunctions(); if (!config->relocatable) { // Create linker synthesized functions