Index: test/wasm/signature-mismatch.ll =================================================================== --- /dev/null +++ test/wasm/signature-mismatch.ll @@ -0,0 +1,19 @@ +; RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o +; RUN: llc -filetype=obj %s -o %t.main.o +; RUN: not lld -flavor wasm --check-signatures -o %t.wasm %t.main.o %t.ret32.o 2>&1 | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +; Function Attrs: nounwind +define hidden void @_start() local_unnamed_addr #0 { +entry: + %call = tail call i32 @ret32(i32 1, i64 2, i32 3) #2 + ret void +} + +declare i32 @ret32(i32, i64, i32) local_unnamed_addr #1 + +; CHECK: error: function signature mismatch: ret32 +; CHECK-NEXT: >>> defined as (I32, I64, I32) -> I32 in {{.*}}.main.o +; CHECK-NEXT: >>> defined as (F32) -> I32 in {{.*}}.ret32.o Index: wasm/Config.h =================================================================== --- wasm/Config.h +++ wasm/Config.h @@ -24,6 +24,7 @@ struct Configuration { bool AllowUndefined; + bool CheckSignatures; bool Demangle; bool EmitRelocs; bool ImportMemory; Index: wasm/Driver.cpp =================================================================== --- wasm/Driver.cpp +++ wasm/Driver.cpp @@ -153,9 +153,10 @@ // Inject a new undefined symbol into the link. This will cause the link to // fail unless this symbol can be found. -static void addSyntheticUndefinedFunction(StringRef Name) { +static void addSyntheticUndefinedFunction(StringRef Name, + const WasmSignature *Type) { log("injecting undefined func: " + Name); - Symtab->addUndefinedFunction(Name); + Symtab->addUndefinedFunction(Name, Type); } static void printHelp(const char *Argv0) { @@ -243,6 +244,8 @@ } Config->AllowUndefined = Args.hasArg(OPT_allow_undefined); + Config->CheckSignatures = + Args.hasFlag(OPT_check_signatures, OPT_no_check_signatures, false); Config->EmitRelocs = Args.hasArg(OPT_emit_relocs); Config->Entry = Args.getLastArgValue(OPT_entry); Config->ImportMemory = Args.hasArg(OPT_import_memory); @@ -278,7 +281,8 @@ if (!Config->Relocatable) { if (Config->Entry.empty()) Config->Entry = "_start"; - addSyntheticUndefinedFunction(Config->Entry); + static WasmSignature Signature = { {}, WASM_TYPE_NORESULT }; + addSyntheticUndefinedFunction(Config->Entry, &Signature); addSyntheticGlobal("__stack_pointer", 0); } Index: wasm/Options.td =================================================================== --- wasm/Options.td +++ wasm/Options.td @@ -29,11 +29,15 @@ def no_color_diagnostics: F<"no-color-diagnostics">, HelpText<"Do not use colors in diagnostics">; +def no_check_signatures: F<"no-check-signatures">, HelpText<"Don't check function signatures">; + def o: JoinedOrSeparate<["-"], "o">, MetaVarName<"">, HelpText<"Path to file to write output">; def threads: F<"threads">, HelpText<"Run the linker multi-threaded">; +def check_signatures: F<"check-signatures">, HelpText<"Check function signatures">; + def v: Flag<["-"], "v">, HelpText<"Display the version number">; def version: F<"version">, HelpText<"Display the version number and exit">; Index: wasm/SymbolTable.h =================================================================== --- wasm/SymbolTable.h +++ wasm/SymbolTable.h @@ -18,6 +18,7 @@ #include "llvm/Support/raw_ostream.h" using llvm::object::WasmSymbol; +using llvm::wasm::WasmSignature; namespace lld { namespace wasm { @@ -51,7 +52,7 @@ Symbol *addDefined(InputFile *F, const WasmSymbol *Sym, const InputSegment *Segment = nullptr); Symbol *addUndefined(InputFile *F, const WasmSymbol *Sym); - Symbol *addUndefinedFunction(StringRef Name); + Symbol *addUndefinedFunction(StringRef Name, const WasmSignature *Type); Symbol *addDefinedGlobal(StringRef Name); void addLazy(ArchiveFile *F, const Archive::Symbol *Sym); Index: wasm/SymbolTable.cpp =================================================================== --- wasm/SymbolTable.cpp +++ wasm/SymbolTable.cpp @@ -11,6 +11,7 @@ #include "Config.h" #include "Strings.h" +#include "WriterUtils.h" #include "lld/Common/ErrorHandler.h" #include "lld/Common/Memory.h" @@ -76,20 +77,59 @@ toString(NewFile)); } -static void checkSymbolTypes(Symbol *Existing, InputFile *F, - const WasmSymbol *New) { - if (Existing->isLazy()) +// Get the signature for a given function symbol, either by looking +// it up in function sections (for defined functions), of the imports section +// (for imported functions). +static const WasmSignature *getFunctionSig(const ObjFile &Obj, + const WasmSymbol &Sym) { + DEBUG(dbgs() << "getFunctionSig: " << Sym.Name << "\n"); + const WasmObjectFile *WasmObj = Obj.getWasmObj(); + uint32_t FunctionType; + if (Obj.isImportedFunction(Sym.ElementIndex)) { + const WasmImport &Import = WasmObj->imports()[Sym.ImportIndex]; + FunctionType = Import.SigIndex; + } else { + uint32_t FuntionIndex = Sym.ElementIndex - Obj.NumFunctionImports(); + FunctionType = WasmObj->functionTypes()[FuntionIndex]; + } + return &WasmObj->types()[FunctionType]; +} + +// Check the type of new symbol matches that of the symbol is replacing. +// For functions this can also involve verifying that the signatures match. +static void checkSymbolTypes(const Symbol &Existing, const InputFile &F, + const WasmSymbol &New, + const WasmSignature *NewSig) { + if (Existing.isLazy()) + return; + + bool NewIsFunction = New.Type == WasmSymbol::SymbolType::FUNCTION_EXPORT || + New.Type == WasmSymbol::SymbolType::FUNCTION_IMPORT; + + // First check the symbol types match (i.e. either both are function + // symbols or both are data symbols). + if (Existing.isFunction() != NewIsFunction) { + error("symbol type mismatch: " + New.Name + "\n>>> defined as " + + (Existing.isFunction() ? "Function" : "Global") + " in " + + toString(Existing.getFile()) + "\n>>> defined as " + + (NewIsFunction ? "Function" : "Global") + " in " + F.getName()); + return; + } + + // For function symbols, optionally check the function signature matches too. + if (!NewIsFunction || !Config->CheckSignatures) return; - bool NewIsFunction = New->Type == WasmSymbol::SymbolType::FUNCTION_EXPORT || - New->Type == WasmSymbol::SymbolType::FUNCTION_IMPORT; - if (Existing->isFunction() == NewIsFunction) + DEBUG(dbgs() << "checkSymbolTypes: " << New.Name << "\n"); + assert(NewSig); + + const WasmSignature &OldSig = Existing.getFunctionType(); + if (*NewSig == OldSig) return; - error("symbol type mismatch: " + New->Name + "\n>>> defined as " + - (Existing->isFunction() ? "Function" : "Global") + " in " + - toString(Existing->getFile()) + "\n>>> defined as " + - (NewIsFunction ? "Function" : "Global") + " in " + F->getName()); + error("function signature mismatch: " + New.Name + "\n>>> defined as " + + toString(OldSig) + " in " + toString(Existing.getFile()) + + "\n>>> defined as " + toString(*NewSig) + " in " + F.getName()); } Symbol *SymbolTable::addDefinedGlobal(StringRef Name) { @@ -110,19 +150,27 @@ Symbol *S; bool WasInserted; Symbol::Kind Kind = Symbol::DefinedFunctionKind; + const WasmSignature *NewSig = nullptr; if (Sym->Type == WasmSymbol::SymbolType::GLOBAL_EXPORT) Kind = Symbol::DefinedGlobalKind; + else + NewSig = getFunctionSig(*cast(F), *Sym); std::tie(S, WasInserted) = insert(Sym->Name); if (WasInserted) { - S->update(Kind, F, Sym, Segment); + S->update(Kind, F, Sym, Segment, NewSig); + } else if (S->isLazy()) { + // The existing symbol is lazy. Replace it without checking types since + // lazy symbols don't have any type information. + DEBUG(dbgs() << "replacing existing lazy symbol: " << Sym->Name << "\n"); + S->update(Kind, F, Sym, Segment, NewSig); } else if (!S->isDefined()) { // The existing symbol table entry is undefined. The new symbol replaces - // it + // it, after checkign the type matches DEBUG(dbgs() << "resolving existing undefined symbol: " << Sym->Name << "\n"); - checkSymbolTypes(S, F, Sym); - S->update(Kind, F, Sym, Segment); + checkSymbolTypes(*S, *F, *Sym, NewSig); + S->update(Kind, F, Sym, Segment, NewSig); } else if (Sym->isWeak()) { // the new symbol is weak we can ignore it DEBUG(dbgs() << "existing symbol takes precensence\n"); @@ -130,7 +178,8 @@ // the new symbol is not weak and the existing symbol is, so we replace // it DEBUG(dbgs() << "replacing existing weak symbol\n"); - S->update(Kind, F, Sym, Segment); + checkSymbolTypes(*S, *F, *Sym, NewSig); + S->update(Kind, F, Sym, Segment, NewSig); } else { // niether symbol is week. They conflict. reportDuplicate(S, F); @@ -138,14 +187,16 @@ return S; } -Symbol *SymbolTable::addUndefinedFunction(StringRef Name) { +Symbol *SymbolTable::addUndefinedFunction(StringRef Name, + const WasmSignature *Type) { Symbol *S; bool WasInserted; std::tie(S, WasInserted) = insert(Name); - if (WasInserted) - S->update(Symbol::UndefinedFunctionKind); - else if (!S->isFunction()) + if (WasInserted) { + S->update(Symbol::UndefinedFunctionKind, nullptr, nullptr, nullptr, Type); + } else if (!S->isFunction()) { error("symbol type mismatch: " + Name); + } return S; } @@ -154,18 +205,21 @@ Symbol *S; bool WasInserted; Symbol::Kind Kind = Symbol::UndefinedFunctionKind; + const WasmSignature *NewSig = nullptr; if (Sym->Type == WasmSymbol::SymbolType::GLOBAL_IMPORT) Kind = Symbol::UndefinedGlobalKind; + else + NewSig = getFunctionSig(*cast(F), *Sym); std::tie(S, WasInserted) = insert(Sym->Name); if (WasInserted) { - S->update(Kind, F, Sym); + S->update(Kind, F, Sym, nullptr, NewSig); } else if (S->isLazy()) { DEBUG(dbgs() << "resolved by existing lazy\n"); auto *AF = cast(S->getFile()); AF->addMember(&S->getArchiveSymbol()); } else if (S->isDefined()) { DEBUG(dbgs() << "resolved by existing\n"); - checkSymbolTypes(S, F, Sym); + checkSymbolTypes(*S, *F, *Sym, NewSig); } return S; } Index: wasm/Symbols.h =================================================================== --- wasm/Symbols.h +++ wasm/Symbols.h @@ -16,6 +16,7 @@ using llvm::object::Archive; using llvm::object::WasmSymbol; +using llvm::wasm::WasmSignature; using llvm::wasm::WasmImport; using llvm::wasm::WasmExport; @@ -40,7 +41,7 @@ }; Symbol(StringRef Name, bool IsLocal) - : WrittenToSymtab(0), WrittenToNameSec(0), Name(Name), IsLocal(IsLocal) {} + : WrittenToSymtab(0), WrittenToNameSec(0), IsLocal(IsLocal), Name(Name) {} Kind getKind() const { return SymbolKind; } @@ -66,7 +67,8 @@ uint32_t getGlobalIndex() const; uint32_t getFunctionIndex() const; - uint32_t getFunctionTypeIndex() const; + + const WasmSignature &getFunctionType() const; uint32_t getOutputIndex() const; // Returns the virtual address of a defined global. @@ -81,7 +83,8 @@ void setOutputIndex(uint32_t Index); void update(Kind K, InputFile *F = nullptr, const WasmSymbol *Sym = nullptr, - const InputSegment *Segment = nullptr); + const InputSegment *Segment = nullptr, + const WasmSignature *Sig = nullptr); void setArchiveSymbol(const Archive::Symbol &Sym) { ArchiveSymbol = Sym; } const Archive::Symbol &getArchiveSymbol() { return ArchiveSymbol; } @@ -92,14 +95,16 @@ unsigned WrittenToNameSec : 1; protected: + unsigned IsLocal : 1; + StringRef Name; - bool IsLocal; Archive::Symbol ArchiveSymbol = {nullptr, 0, 0}; Kind SymbolKind = InvalidKind; InputFile *File = nullptr; const WasmSymbol *Sym = nullptr; const InputSegment *Segment = nullptr; llvm::Optional OutputIndex; + const WasmSignature* FunctionType; }; } // namespace wasm Index: wasm/Symbols.cpp =================================================================== --- wasm/Symbols.cpp +++ wasm/Symbols.cpp @@ -31,19 +31,9 @@ return Sym->ElementIndex; } -uint32_t Symbol::getFunctionTypeIndex() const { - assert(Sym->isFunction()); - ObjFile *Obj = cast(File); - if (Obj->isImportedFunction(Sym->ElementIndex)) { - const WasmImport &Import = Obj->getWasmObj()->imports()[Sym->ImportIndex]; - DEBUG(dbgs() << "getFunctionTypeIndex: import: " << Sym->ImportIndex - << " -> " << Import.SigIndex << "\n"); - return Import.SigIndex; - } - DEBUG(dbgs() << "getFunctionTypeIndex: non import: " << Sym->ElementIndex - << "\n"); - uint32_t FuntionIndex = Sym->ElementIndex - Obj->NumFunctionImports(); - return Obj->getWasmObj()->functionTypes()[FuntionIndex]; +const WasmSignature &Symbol::getFunctionType() const { + assert(FunctionType != nullptr); + return *FunctionType; } uint32_t Symbol::getVirtualAddress() const { @@ -74,11 +64,12 @@ } void Symbol::update(Kind K, InputFile *F, const WasmSymbol *WasmSym, - const InputSegment *Seg) { + const InputSegment *Seg, const WasmSignature *Sig) { SymbolKind = K; File = F; Sym = WasmSym; Segment = Seg; + FunctionType = Sig; } bool Symbol::isWeak() const { return Sym && Sym->isWeak(); } Index: wasm/Writer.cpp =================================================================== --- wasm/Writer.cpp +++ wasm/Writer.cpp @@ -35,11 +35,6 @@ namespace { -// Needed for WasmSignatureDenseMapInfo -bool operator==(const WasmSignature &LHS, const WasmSignature &RHS) { - return LHS.ReturnType == RHS.ReturnType && LHS.ParamTypes == RHS.ParamTypes; -} - // Traits for using WasmSignature in a DenseMap. struct WasmSignatureDenseMapInfo { static WasmSignature getEmptyKey() { @@ -72,6 +67,7 @@ private: void openFile(); + uint32_t getTypeIndex(const WasmSignature &Sig); void assignSymbolIndexes(); void calculateImports(); void calculateOffsets(); @@ -158,8 +154,8 @@ Import.Module = "env"; Import.Field = Sym->getName(); Import.Kind = WASM_EXTERNAL_FUNCTION; - auto *Obj = cast(Sym->getFile()); - Import.SigIndex = Obj->relocateTypeIndex(Sym->getFunctionTypeIndex()); + assert(TypeIndices.count(Sym->getFunctionType()) > 0); + Import.SigIndex = TypeIndices.lookup(Sym->getFunctionType()); writeImport(OS, Import); } @@ -179,9 +175,6 @@ Import.Field = Sym->getName(); Import.Kind = WASM_EXTERNAL_GLOBAL; Import.Global.Mutable = false; - assert(isa(Sym->getFile())); - // TODO(sbc): Set type of this import - // ObjFile* Obj = dyn_cast(Sym->getFile()); Import.Global.Type = WASM_TYPE_I32; // Sym->getGlobalType(); writeImport(OS, Import); } @@ -634,17 +627,18 @@ } } +uint32_t Writer::getTypeIndex(const WasmSignature &Sig) { + auto Pair = TypeIndices.insert(std::make_pair(Sig, Types.size())); + if (Pair.second) + Types.push_back(&Sig); + return Pair.first->second; +} + void Writer::calculateTypes() { for (ObjFile *File : Symtab->ObjectFiles) { File->TypeMap.reserve(File->getWasmObj()->types().size()); - for (const WasmSignature &Sig : File->getWasmObj()->types()) { - auto Pair = TypeIndices.insert(std::make_pair(Sig, Types.size())); - if (Pair.second) - Types.push_back(&Sig); - - // Now we map the input files index to the index in the linked output - File->TypeMap.push_back(Pair.first->second); - } + for (const WasmSignature &Sig : File->getWasmObj()->types()) + File->TypeMap.push_back(getTypeIndex(Sig)); } } Index: wasm/WriterUtils.h =================================================================== --- wasm/WriterUtils.h +++ wasm/WriterUtils.h @@ -16,6 +16,17 @@ using llvm::raw_ostream; +// Needed for WasmSignatureDenseMapInfo +inline bool operator==(const llvm::wasm::WasmSignature &LHS, + const llvm::wasm::WasmSignature &RHS) { + return LHS.ReturnType == RHS.ReturnType && LHS.ParamTypes == RHS.ParamTypes; +} + +inline bool operator!=(const llvm::wasm::WasmSignature &LHS, + const llvm::wasm::WasmSignature &RHS) { + return !(LHS == RHS); +} + namespace lld { namespace wasm { @@ -58,6 +69,10 @@ void writeReloc(raw_ostream &OS, const OutputRelocation &Reloc); } // namespace wasm + +std::string toString(const llvm::wasm::ValType Type); +std::string toString(const llvm::wasm::WasmSignature &Sig); + } // namespace lld #endif // LLD_WASM_WRITERUTILS_H Index: wasm/WriterUtils.cpp =================================================================== --- wasm/WriterUtils.cpp +++ wasm/WriterUtils.cpp @@ -187,3 +187,32 @@ } } // namespace lld + +std::string lld::toString(ValType Type) { + switch (Type) { + case ValType::I32: + return "I32"; + case ValType::I64: + return "I64"; + case ValType::F32: + return "F32"; + case ValType::F64: + return "F64"; + } + llvm_unreachable("Invalid wasm::ValType"); +} + +std::string lld::toString(const WasmSignature &Sig) { + SmallString<128> S("("); + for (uint32_t Type : Sig.ParamTypes) { + if (S.size() != 1) + S += ", "; + S += toString(static_cast(Type)); + } + S += ") -> "; + if (Sig.ReturnType == WASM_TYPE_NORESULT) + S += "void"; + else + S += toString(static_cast(Sig.ReturnType)); + return S.str(); +}