Index: lld/trunk/test/wasm/target-feature-disallowed.yaml =================================================================== --- lld/trunk/test/wasm/target-feature-disallowed.yaml +++ lld/trunk/test/wasm/target-feature-disallowed.yaml @@ -1,5 +1,9 @@ # RUN: yaml2obj %s -o %t1.o +# RUN: wasm-ld --no-entry --features=foo,bar,baz -o - %t1.o | obj2yaml | FileCheck %s --check-prefix SPECIFIED + +# RUN: wasm-ld --no-entry --features=bar,baz,quux -o - %t1.o | obj2yaml | FileCheck %s --check-prefix UNSPECIFIED + # RUN: yaml2obj %S/Inputs/disallow-feature-foo.yaml -o %t.disallowed.o # RUN: wasm-ld --no-entry -o %t.disallowed.exe %t1.o %t.disallowed.o # RUN: obj2yaml < %t.disallowed.exe | FileCheck %s --check-prefix DISALLOWED @@ -31,6 +35,28 @@ Name: "bar" ... +# SPECIFIED: - Type: CUSTOM +# SPECIFIED-NEXT: Name: target_features +# SPECIFIED-NEXT: Features: +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: bar +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: baz +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: foo +# SPECIFIED-NEXT: ... + +# UNSPECIFIED: - Type: CUSTOM +# UNSPECIFIED-NEXT: Name: target_features +# UNSPECIFIED-NEXT: Features: +# UNSPECIFIED-NEXT: - Prefix: USED +# UNSPECIFIED-NEXT: Name: bar +# UNSPECIFIED-NEXT: - Prefix: USED +# UNSPECIFIED-NEXT: Name: baz +# UNSPECIFIED-NEXT: - Prefix: USED +# UNSPECIFIED-NEXT: Name: quux +# UNSPECIFIED-NEXT: ... + # DISALLOWED: - Type: CUSTOM # DISALLOWED-NEXT: Name: target_features # DISALLOWED-NEXT: Features: Index: lld/trunk/test/wasm/target-feature-none.yaml =================================================================== --- lld/trunk/test/wasm/target-feature-none.yaml +++ lld/trunk/test/wasm/target-feature-none.yaml @@ -0,0 +1,33 @@ +# RUN: yaml2obj %s -o %t1.o + +# RUN: wasm-ld --no-entry -o - %t1.o | obj2yaml | FileCheck %s --check-prefix EMPTY + +# RUN: wasm-ld --no-entry --features= -o - %t1.o | obj2yaml | FileCheck %s --check-prefix EMPTY + +# RUN: wasm-ld --no-entry --features=foo,bar,baz -o - %t1.o | obj2yaml | FileCheck %s --check-prefix SPECIFIED + +--- !WASM +FileHeader: + Version: 0x00000001 +Sections: + - Type: CUSTOM + Name: linking + Version: 2 + - Type: CUSTOM + Name: target_features + Features: [ ] +... + +# section is not emitted if it would be empty +# EMPTY-NOT: target_features + +# SPECIFIED: - Type: CUSTOM +# SPECIFIED-NEXT: Name: target_features +# SPECIFIED-NEXT: Features: +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: bar +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: baz +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: foo +# SPECIFIED-NEXT: ... Index: lld/trunk/test/wasm/target-feature-required.yaml =================================================================== --- lld/trunk/test/wasm/target-feature-required.yaml +++ lld/trunk/test/wasm/target-feature-required.yaml @@ -1,5 +1,11 @@ # RUN: yaml2obj %s -o %t1.o +# RUN: wasm-ld --no-entry --features=foo,bar,baz -o - %t1.o | obj2yaml | FileCheck %s --check-prefix SPECIFIED + +# RUN: not wasm-ld --no-entry --features=bar,baz,quux -o - %t1.o 2>&1 | FileCheck %s --check-prefix UNSPECIFIED + +# RUN: wasm-ld --no-entry --no-check-features --features=bar,baz,quux -o - %t1.o | obj2yaml | FileCheck %s --check-prefix UNSPECIFIED-NOCHECK + # RUN: yaml2obj %S/Inputs/require-feature-foo.yaml -o %t.required.o # RUN: wasm-ld --no-entry -o %t.required.exe %t1.o %t.required.o # RUN: obj2yaml < %t.required.exe | FileCheck %s --check-prefix REQUIRED @@ -7,9 +13,13 @@ # RUN: yaml2obj %S/Inputs/disallow-feature-foo.yaml -o %t.disallowed.o # RUN: not wasm-ld --no-entry -o /dev/null %t1.o %t.disallowed.o 2>&1 | FileCheck %s --check-prefix DISALLOWED +# RUN: wasm-ld --no-entry --no-check-features -o - %t1.o %t.disallowed.o | obj2yaml | FileCheck %s --check-prefix DISALLOWED-NOCHECK + # RUN: yaml2obj %S/Inputs/no-feature-foo.yaml -o %t.none.o # RUN: not wasm-ld --no-entry -o /dev/null %t1.o %t.none.o 2>&1 | FileCheck %s --check-prefix NONE +# RUN: wasm-ld --no-entry --no-check-features -o - %t1.o %t.none.o | obj2yaml | FileCheck %s --check-prefix NONE-NOCHECK + # Check that the following combinations of feature linkage policies # give the expected results: # @@ -31,6 +41,30 @@ Name: "foo" ... +# SPECIFIED: - Type: CUSTOM +# SPECIFIED-NEXT: Name: target_features +# SPECIFIED-NEXT: Features: +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: bar +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: baz +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: foo +# SPECIFIED-NEXT: ... + +# UNSPECIFIED: Target feature 'foo' is not allowed.{{$}} + +# UNSPECIFIED-NOCHECK: - Type: CUSTOM +# UNSPECIFIED-NOCHECK-NEXT: Name: target_features +# UNSPECIFIED-NOCHECK-NEXT: Features: +# UNSPECIFIED-NOCHECK-NEXT: - Prefix: USED +# UNSPECIFIED-NOCHECK-NEXT: Name: bar +# UNSPECIFIED-NOCHECK-NEXT: - Prefix: USED +# UNSPECIFIED-NOCHECK-NEXT: Name: baz +# UNSPECIFIED-NOCHECK-NEXT: - Prefix: USED +# UNSPECIFIED-NOCHECK-NEXT: Name: quux +# UNSPECIFIED-NOCHECK-NEXT: ... + # REQUIRED: - Type: CUSTOM # REQUIRED-NEXT: Name: target_features # REQUIRED-NEXT: Features: @@ -38,6 +72,20 @@ # REQUIRED-NEXT: Name: foo # REQUIRED-NEXT: ... -# DISALLOWED: Target feature "foo" is disallowed +# DISALLOWED: Target feature 'foo' is disallowed. Use --no-check-features to suppress.{{$}} -# NONE: Missing required target feature "foo" +# DISALLOWED-NOCHECK: - Type: CUSTOM +# DISALLOWED-NOCHECK-NEXT: Name: target_features +# DISALLOWED-NOCHECK-NEXT: Features: +# DISALLOWED-NOCHECK-NEXT: - Prefix: USED +# DISALLOWED-NOCHECK-NEXT: Name: foo +# DISALLOWED-NOCHECK-NEXT: ... + +# NONE: Missing required target feature 'foo'. Use --no-check-features to suppress.{{$}} + +# NONE-NOCHECK: - Type: CUSTOM +# NONE-NOCHECK-NEXT: Name: target_features +# NONE-NOCHECK-NEXT: Features: +# NONE-NOCHECK-NEXT: - Prefix: USED +# NONE-NOCHECK-NEXT: Name: foo +# NONE-NOCHECK-NEXT: ... Index: lld/trunk/test/wasm/target-feature-used.yaml =================================================================== --- lld/trunk/test/wasm/target-feature-used.yaml +++ lld/trunk/test/wasm/target-feature-used.yaml @@ -1,5 +1,11 @@ # RUN: yaml2obj %s -o %t1.o +# RUN: wasm-ld --no-entry --features=foo,bar,baz -o - %t1.o | obj2yaml | FileCheck %s --check-prefix SPECIFIED + +# RUN: not wasm-ld --no-entry --features=bar,baz,quux -o - %t1.o 2>&1 | FileCheck %s --check-prefix UNSPECIFIED + +# RUN: wasm-ld --no-entry --no-check-features --features=bar,baz,quux -o - %t1.o | obj2yaml | FileCheck %s --check-prefix UNSPECIFIED-NOCHECK + # RUN: yaml2obj %S/Inputs/use-feature-foo.yaml -o %t.used.o # RUN: wasm-ld --no-entry -o %t.used.exe %t1.o %t.used.o # RUN: obj2yaml < %t.used.exe | FileCheck %s --check-prefix USED @@ -11,6 +17,8 @@ # RUN: yaml2obj %S/Inputs/disallow-feature-foo.yaml -o %t.disallowed.o # RUN: not wasm-ld --no-entry -o /dev/null %t1.o %t.disallowed.o 2>&1 | FileCheck %s --check-prefix DISALLOWED +# RUN: wasm-ld --no-entry --no-check-features -o - %t1.o %t.disallowed.o | obj2yaml | FileCheck %s --check-prefix DISALLOWED-NOCHECK + # RUN: yaml2obj %S/Inputs/no-feature-foo.yaml -o %t.none.o # RUN: wasm-ld --no-entry -o %t.none.exe %t1.o %t.none.o # RUN: obj2yaml %t.none.exe | FileCheck %s --check-prefix NONE @@ -37,6 +45,30 @@ Name: "foo" ... +# SPECIFIED: - Type: CUSTOM +# SPECIFIED-NEXT: Name: target_features +# SPECIFIED-NEXT: Features: +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: bar +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: baz +# SPECIFIED-NEXT: - Prefix: USED +# SPECIFIED-NEXT: Name: foo +# SPECIFIED-NEXT: ... + +# UNSPECIFIED: Target feature 'foo' is not allowed.{{$}} + +# UNSPECIFIED-NOCHECK: - Type: CUSTOM +# UNSPECIFIED-NOCHECK-NEXT: Name: target_features +# UNSPECIFIED-NOCHECK-NEXT: Features: +# UNSPECIFIED-NOCHECK-NEXT: - Prefix: USED +# UNSPECIFIED-NOCHECK-NEXT: Name: bar +# UNSPECIFIED-NOCHECK-NEXT: - Prefix: USED +# UNSPECIFIED-NOCHECK-NEXT: Name: baz +# UNSPECIFIED-NOCHECK-NEXT: - Prefix: USED +# UNSPECIFIED-NOCHECK-NEXT: Name: quux +# UNSPECIFIED-NOCHECK-NEXT: ... + # USED: - Type: CUSTOM # USED-NEXT: Name: target_features # USED-NEXT: Features: @@ -51,7 +83,14 @@ # REQUIRED-NEXT: Name: foo # REQUIRED-NEXT: ... -# DISALLOWED: Target feature "foo" is disallowed +# DISALLOWED: Target feature 'foo' is disallowed. Use --no-check-features to suppress.{{$}} + +# DISALLOWED-NOCHECK: - Type: CUSTOM +# DISALLOWED-NOCHECK-NEXT: Name: target_features +# DISALLOWED-NOCHECK-NEXT: Features: +# DISALLOWED-NOCHECK-NEXT: - Prefix: USED +# DISALLOWED-NOCHECK-NEXT: Name: foo +# DISALLOWED-NOCHECK-NEXT: ... # NONE: - Type: CUSTOM # NONE-NEXT: Name: target_features Index: lld/trunk/wasm/Config.h =================================================================== --- lld/trunk/wasm/Config.h +++ lld/trunk/wasm/Config.h @@ -19,6 +19,7 @@ struct Configuration { bool AllowUndefined; + bool CheckFeatures; bool CompressRelocations; bool Demangle; bool DisableVerify; @@ -54,6 +55,7 @@ llvm::StringSet<> AllowUndefinedSymbols; std::vector SearchPaths; llvm::CachePruningPolicy ThinLTOCachePolicy; + llvm::Optional> Features; // True if we are creating position-independent code. bool Pic; Index: lld/trunk/wasm/Driver.cpp =================================================================== --- lld/trunk/wasm/Driver.cpp +++ lld/trunk/wasm/Driver.cpp @@ -21,6 +21,7 @@ #include "lld/Common/Version.h" #include "llvm/ADT/Twine.h" #include "llvm/Object/Wasm.h" +#include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Path.h" @@ -292,6 +293,8 @@ // of these values. static void setConfigs(opt::InputArgList &Args) { Config->AllowUndefined = Args.hasArg(OPT_allow_undefined); + Config->CheckFeatures = + Args.hasFlag(OPT_check_features, OPT_no_check_features, true); Config->CompressRelocations = Args.hasArg(OPT_compress_relocations); Config->Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, true); Config->DisableVerify = Args.hasArg(OPT_disable_verify); @@ -339,6 +342,13 @@ Config->MaxMemory = args::getInteger(Args, OPT_max_memory, 0); Config->ZStackSize = args::getZOptionValue(Args, OPT_z, "stack-size", WasmPageSize); + + if (auto *Arg = Args.getLastArg(OPT_features)) { + Config->Features = + llvm::Optional>(std::vector()); + for (StringRef S : Arg->getValues()) + Config->Features->push_back(S); + } } // Some command line options or some combinations of them are not allowed. Index: lld/trunk/wasm/Options.td =================================================================== --- lld/trunk/wasm/Options.td +++ lld/trunk/wasm/Options.td @@ -155,6 +155,13 @@ "Force load of all members in a static library", "Do not force load of all members in a static library (default)">; +defm check_features: B<"check-features", + "Check feature compatibility of linked objects (default)", + "Ignore feature compatibility of linked objects">; + +def features: CommaJoined<["--", "-"], "features=">, + HelpText<"Comma-separated used features, inferred from input objects by default.">; + // Aliases def: JoinedOrSeparate<["-"], "e">, Alias; def: J<"entry=">, Alias; Index: lld/trunk/wasm/Writer.cpp =================================================================== --- lld/trunk/wasm/Writer.cpp +++ lld/trunk/wasm/Writer.cpp @@ -878,18 +878,30 @@ } void Writer::calculateTargetFeatures() { + SmallSet Used; SmallSet Required; SmallSet Disallowed; + // Only infer used features if user did not specify features + bool InferFeatures = !Config->Features.hasValue(); + + if (!InferFeatures) { + for (auto &Feature : Config->Features.getValue()) + TargetFeatures.insert(Feature); + // No need to read or check features + if (!Config->CheckFeatures) + return; + } + // Find the sets of used, required, and disallowed features for (ObjFile *File : Symtab->ObjectFiles) { for (auto &Feature : File->getWasmObj()->getTargetFeatures()) { switch (Feature.Prefix) { case WASM_FEATURE_PREFIX_USED: - TargetFeatures.insert(Feature.Name); + Used.insert(Feature.Name); break; case WASM_FEATURE_PREFIX_REQUIRED: - TargetFeatures.insert(Feature.Name); + Used.insert(Feature.Name); Required.insert(Feature.Name); break; case WASM_FEATURE_PREFIX_DISALLOWED: @@ -902,6 +914,20 @@ } } + if (InferFeatures) + TargetFeatures.insert(Used.begin(), Used.end()); + + if (!Config->CheckFeatures) + return; + + // Validate that used features are allowed in output + if (!InferFeatures) { + for (auto &Feature : Used) { + if (!TargetFeatures.count(Feature)) + error(Twine("Target feature '") + Feature + "' is not allowed."); + } + } + // Validate the required and disallowed constraints for each file for (ObjFile *File : Symtab->ObjectFiles) { SmallSet ObjectFeatures; @@ -910,11 +936,13 @@ continue; ObjectFeatures.insert(Feature.Name); if (Disallowed.count(Feature.Name)) - error("Target feature \"" + Feature.Name + "\" is disallowed"); + error(Twine("Target feature '") + Feature.Name + + "' is disallowed. Use --no-check-features to suppress."); } for (auto &Feature : Required) { if (!ObjectFeatures.count(Feature)) - error(Twine("Missing required target feature \"") + Feature + "\""); + error(Twine("Missing required target feature '") + Feature + + "'. Use --no-check-features to suppress."); } } }