diff --git a/lld/docs/WebAssembly.rst b/lld/docs/WebAssembly.rst --- a/lld/docs/WebAssembly.rst +++ b/lld/docs/WebAssembly.rst @@ -71,7 +71,31 @@ .. option:: --allow-undefined - Allow undefined symbols in linked binary. + Allow undefined symbols in linked binary. This is the legacy + flag which corresponds to ``--unresolved-symbols=import-functions``. + +.. option:: --unresolved-symbols= + + This is a more full featured version of ``--allow-undefined``. + The semanatics of the different methods are as follows: + + report-all: + + Report all unresolved symbols. This is the default. Normally the linker + will generate an error message for each reported unresolved symbol but the + option ``--warn-unresolved-symbols`` can change this to a warning. + + ignore-all: + + Resolve all undefined symbols to zero. For data and function addresses + this is trivial. For direct function calls, the linker will generate a + trapping stub function in place of the undefined function. + + import-functions: + + Generate WebAssembly imports for any undefined functions. Undefined data + symbols are resolved to zero as in ``ignore-all``. This corresponds to + the legacy ``--allow-undefined`` flag. .. option:: --import-memory diff --git a/lld/test/wasm/archive-weak-undefined.ll b/lld/test/wasm/archive-weak-undefined.ll --- a/lld/test/wasm/archive-weak-undefined.ll +++ b/lld/test/wasm/archive-weak-undefined.ll @@ -39,5 +39,5 @@ ; CHECK-NOT: Type: DATA ; CHECK-DATA: Type: DATA -; CHECK: Name: 'undefined:ret32' +; CHECK: Name: 'undefined_weak:ret32' ; CHECK-NOT: Name: ret32 diff --git a/lld/test/wasm/cxx-mangling.ll b/lld/test/wasm/cxx-mangling.ll --- a/lld/test/wasm/cxx-mangling.ll +++ b/lld/test/wasm/cxx-mangling.ll @@ -47,8 +47,8 @@ ; CHECK-NEXT: Name: name ; CHECK-NEXT: FunctionNames: ; CHECK-NEXT: - Index: 0 -; DEMANGLE-NEXT: Name: 'undefined:bar(int)' -; MANGLE-NEXT: Name: 'undefined:_Z3bari' +; DEMANGLE-NEXT: Name: 'undefined_weak:bar(int)' +; MANGLE-NEXT: Name: 'undefined_weak:_Z3bari' ; CHECK-NEXT: - Index: 1 ; DEMANGLE-NEXT: Name: 'foo(int)' ; MANGLE-NEXT: Name: _Z3fooi diff --git a/lld/test/wasm/lto/weak-undefined.ll b/lld/test/wasm/lto/weak-undefined.ll --- a/lld/test/wasm/lto/weak-undefined.ll +++ b/lld/test/wasm/lto/weak-undefined.ll @@ -17,4 +17,4 @@ ret void } -; CHECK: Name: 'undefined:foo' +; CHECK: Name: 'undefined_weak:foo' diff --git a/lld/test/wasm/undefined-weak-call.ll b/lld/test/wasm/undefined-weak-call.ll --- a/lld/test/wasm/undefined-weak-call.ll +++ b/lld/test/wasm/undefined-weak-call.ll @@ -89,11 +89,11 @@ ; CHECK-NEXT: Name: name ; CHECK-NEXT: FunctionNames: ; CHECK-NEXT: - Index: 0 -; CHECK-NEXT: Name: 'undefined:weakFunc1' +; CHECK-NEXT: Name: 'undefined_weak:weakFunc1' ; CHECK-NEXT: - Index: 1 -; CHECK-NEXT: Name: 'undefined:weakFunc2' +; CHECK-NEXT: Name: 'undefined_weak:weakFunc2' ; CHECK-NEXT: - Index: 2 -; CHECK-NEXT: Name: 'undefined:weakFunc3' +; CHECK-NEXT: Name: 'undefined_weak:weakFunc3' ; CHECK-NEXT: - Index: 3 ; CHECK-NEXT: Name: callWeakFuncs ; CHECK-NEXT: ... diff --git a/lld/test/wasm/unresolved-symbols.s b/lld/test/wasm/unresolved-symbols.s new file mode 100644 --- /dev/null +++ b/lld/test/wasm/unresolved-symbols.s @@ -0,0 +1,94 @@ +# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %s -o %t1.o + +## Check that %t1.o contains undefined symbol undef. +# RUN: not wasm-ld %t1.o -o /dev/null 2>&1 | \ +# RUN: FileCheck -check-prefix=ERRUND %s +# ERRUND: error: {{.*}}1.o: undefined symbol: undef + +## report-all is the default one. Check that we get the same error +# RUN: not wasm-ld %t1.o -o /dev/null --unresolved-symbols=report-all 2>&1 | \ +# RUN: FileCheck -check-prefix=ERRUND %s + +## Error out if unknown option value was set. +# RUN: not wasm-ld %t1.o -o /dev/null --unresolved-symbols=xxx 2>&1 | \ +# RUN: FileCheck -check-prefix=ERR1 %s +# ERR1: unknown --unresolved-symbols value: xxx +## Check alias. +# RUN: not wasm-ld %t1.o -o /dev/null --unresolved-symbols xxx 2>&1 | \ +# RUN: FileCheck -check-prefix=ERR1 %s + +## Ignore all should not produce error and should not produce +# any imports. It should create a stub function in the place of the missing +# function symbol. +# RUN: wasm-ld %t1.o -o %t2.wasm --unresolved-symbols=ignore-all +# RUN: obj2yaml %t2.wasm | FileCheck -check-prefix=IGNORE %s +# IGNORE-NOT: - Type: IMPORT +# IGNORE-NOT: - Type: ELEM +# +# IGNORE: - Type: CODE +# IGNORE-NEXT: Functions: +# IGNORE-NEXT: - Index: 0 +# IGNORE-NEXT: Locals: [] +# IGNORE-NEXT: Body: 000B +# IGNORE-NEXT: - Index: 1 +# IGNORE-NEXT: Locals: [] +# IGNORE-NEXT: Body: 1080808080001082808080001083808080000B +# IGNORE-NEXT: - Index: 2 +# IGNORE-NEXT: Locals: [] +# IGNORE-NEXT: Body: 4180808080000F0B +# IGNORE-NEXT: - Index: 3 +# IGNORE-NEXT: Locals: [] +# IGNORE-NEXT: Body: 4180808080000F0B +# +# IGNORE: - Type: CUSTOM +# IGNORE-NEXT: Name: name +# IGNORE-NEXT: FunctionNames: +# IGNORE-NEXT: - Index: 0 +# IGNORE-NEXT: Name: undefined +# IGNORE-NEXT: - Index: 1 +# IGNORE-NEXT: Name: _start +# IGNORE-NEXT: - Index: 2 +# IGNORE-NEXT: Name: get_data_addr +# IGNORE-NEXT: - Index: 3 +# IGNORE-NEXT: Name: get_func_addr + +## import-functions should not produce errors and should resolve in +# imports for the missing functions but not the missing data symbols. +# `--allow-undefined` should behave exactly the same. +# RUN: wasm-ld %t1.o -o %t3.wasm --unresolved-symbols=import-functions +# RUN: obj2yaml %t3.wasm | FileCheck -check-prefix=IMPORT %s +# IMPORT: - Type: IMPORT +# IMPORT-NEXT: Imports: +# IMPORT-NEXT: - Module: env +# IMPORT-NEXT: Field: undef +# IMPORT-NEXT: Kind: FUNCTION +# IMPORT-NEXT: SigIndex: 0 +# IMPORT-NEXT: - Type: FUNCTION + +## Do not report undefines if linking relocatable. +# RUN: wasm-ld -r %t1.o -o %t4.wasm --unresolved-symbols=report-all +# RUN: llvm-readobj %t4.wasm > /dev/null 2>&1 + +.globl _start +_start: + .functype _start () -> () + call undef + call get_data_addr + call get_func_addr + end_function + +.globl get_data_addr +get_data_addr: + .functype get_data_addr () -> (i32) + i32.const undef_data + return + end_function + +.globl get_func_addr +get_func_addr: + .functype get_func_addr () -> (i32) + i32.const undef + return + end_function + +.functype undef () -> () diff --git a/lld/test/wasm/weak-undefined.ll b/lld/test/wasm/weak-undefined.ll --- a/lld/test/wasm/weak-undefined.ll +++ b/lld/test/wasm/weak-undefined.ll @@ -24,6 +24,7 @@ entry: %call1 = call i32* @get_address_of_global_var() %call2 = call i8* @get_address_of_foo() + call i32 @foo() ret void } @@ -41,7 +42,7 @@ ; CHECK-NEXT: ParamTypes: [] ; CHECK-NEXT: ReturnTypes: [] ; CHECK-NEXT: - Type: FUNCTION -; CHECK-NEXT: FunctionTypes: [ 0, 0, 1 ] +; CHECK-NEXT: FunctionTypes: [ 0, 0, 0, 1 ] ; CHECK-NEXT: - Type: TABLE ; CHECK-NEXT: Tables: ; CHECK-NEXT: - Index: 0 @@ -68,16 +69,19 @@ ; CHECK-NEXT: Index: 0 ; CHECK-NEXT: - Name: _start ; CHECK-NEXT: Kind: FUNCTION -; CHECK-NEXT: Index: 2 +; CHECK-NEXT: Index: 3 ; CHECK-NEXT: - Type: CODE ; CHECK-NEXT: Functions: ; CHECK-NEXT: - Index: 0 ; CHECK-NEXT: Locals: -; CHECK-NEXT: Body: 4180808080000B +; CHECK-NEXT: Body: 000B ; CHECK-NEXT: - Index: 1 ; CHECK-NEXT: Locals: ; CHECK-NEXT: Body: 4180808080000B ; CHECK-NEXT: - Index: 2 ; CHECK-NEXT: Locals: -; CHECK-NEXT: Body: 1081808080001A1080808080001A0B +; CHECK-NEXT: Body: 4180808080000B +; CHECK-NEXT: - Index: 3 +; CHECK-NEXT: Locals: +; CHECK-NEXT: Body: 1082808080001A1081808080001A1080808080001A0B ; CHECK-NEXT: ... diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h --- a/lld/wasm/Config.h +++ b/lld/wasm/Config.h @@ -17,12 +17,17 @@ namespace lld { namespace wasm { +// For --unresolved-symbols. +// The `ImportFuncs` mode is an additional mode that corresponds to the +// --allow-undefined flag which turns undefined functions in imports +// as opposed ed to Ignore or Warn which turn them into unreachables. +enum class UnresolvedPolicy { ReportError, Warn, Ignore, ImportFuncs }; + // This struct contains the global configuration for the linker. // Most fields are direct mapping from the command line options // and such fields have the same name as the corresponding options. // Most fields are initialized by the driver. struct Configuration { - bool allowUndefined; bool bsymbolic; bool checkFeatures; bool compressRelocations; @@ -57,6 +62,7 @@ unsigned ltoo; unsigned optimize; llvm::StringRef thinLTOJobs; + UnresolvedPolicy unresolvedSymbols; llvm::StringRef entry; llvm::StringRef mapFile; diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -331,9 +331,35 @@ return arg->getValue(); } +// Determines what we should do if there are remaining unresolved +// symbols after the name resolution. +static UnresolvedPolicy getUnresolvedSymbolPolicy(opt::InputArgList &args) { + UnresolvedPolicy errorOrWarn = args.hasFlag(OPT_error_unresolved_symbols, + OPT_warn_unresolved_symbols, true) + ? UnresolvedPolicy::ReportError + : UnresolvedPolicy::Warn; + + if (auto *arg = args.getLastArg(OPT_unresolved_symbols)) { + StringRef s = arg->getValue(); + if (s == "ignore-all") + return UnresolvedPolicy::Ignore; + if (s == "import-functions") + return UnresolvedPolicy::ImportFuncs; + if (s == "report-all") + return errorOrWarn; + error("unknown --unresolved-symbols value: " + s); + } + + // Legacy --allow-undefined flag which is equivalent to + // --unresolve-symbols=ignore-all + if (args.hasArg(OPT_allow_undefined)) + return UnresolvedPolicy::ImportFuncs; + + return errorOrWarn; +} + // Initializes Config members by the command line options. static void readConfigs(opt::InputArgList &args) { - config->allowUndefined = args.hasArg(OPT_allow_undefined); config->bsymbolic = args.hasArg(OPT_Bsymbolic); config->checkFeatures = args.hasFlag(OPT_check_features, OPT_no_check_features, true); @@ -376,6 +402,7 @@ config->thinLTOCachePolicy = CHECK( parseCachePruningPolicy(args.getLastArgValue(OPT_thinlto_cache_policy)), "--thinlto-cache-policy: invalid cache policy"); + config->unresolvedSymbols = getUnresolvedSymbolPolicy(args); errorHandler().verbose = args.hasArg(OPT_verbose); LLVM_DEBUG(errorHandler().verbose = true); @@ -440,7 +467,7 @@ if (config->shared) { config->importMemory = true; - config->allowUndefined = true; + config->unresolvedSymbols = UnresolvedPolicy::ImportFuncs; } } @@ -939,9 +966,11 @@ Symbol *sym = symtab->find(arg->getValue()); if (sym && sym->isDefined()) sym->forceExport = true; - else if (!config->allowUndefined) + else if (config->unresolvedSymbols == UnresolvedPolicy::ReportError) error(Twine("symbol exported via --export not found: ") + arg->getValue()); + else if (config->unresolvedSymbols == UnresolvedPolicy::Warn) + warn(Twine("symbol exported via --export not found: ") + arg->getValue()); } if (!config->relocatable) { diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td --- a/lld/wasm/Options.td +++ b/lld/wasm/Options.td @@ -36,6 +36,9 @@ def emit_relocs: F<"emit-relocs">, HelpText<"Generate relocations in output">; +def error_unresolved_symbols: F<"error-unresolved-symbols">, + HelpText<"Report unresolved symbols as errors">; + defm export_dynamic: B<"export-dynamic", "Put symbols in the dynamic symbol table", "Do not put symbols in the dynamic symbol table (default)">; @@ -112,18 +115,24 @@ defm undefined: Eq<"undefined", "Force undefined symbol during linking">; +defm unresolved_symbols: + Eq<"unresolved-symbols", "Determine how to handle unresolved symbols">; + def v: Flag<["-"], "v">, HelpText<"Display the version number">; def verbose: F<"verbose">, HelpText<"Verbose mode">; def version: F<"version">, HelpText<"Display the version number and exit">; -def z: JoinedOrSeparate<["-"], "z">, MetaVarName<"