diff --git a/lld/test/wasm/target-feature-disallowed.yaml b/lld/test/wasm/target-feature-disallowed.yaml --- a/lld/test/wasm/target-feature-disallowed.yaml +++ b/lld/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 - %t1.o %t.disallowed.o | obj2yaml | FileCheck %s --check-prefix DISALLOWED @@ -29,6 +33,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: diff --git a/lld/test/wasm/target-feature-none.yaml b/lld/test/wasm/target-feature-none.yaml new file mode 100644 --- /dev/null +++ b/lld/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: ... diff --git a/lld/test/wasm/target-feature-required.yaml b/lld/test/wasm/target-feature-required.yaml --- a/lld/test/wasm/target-feature-required.yaml +++ b/lld/test/wasm/target-feature-required.yaml @@ -1,14 +1,24 @@ # 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 - %t1.o %t.required.o | obj2yaml | FileCheck %s --check-prefix REQUIRED # RUN: yaml2obj %S/Inputs/disallow-feature-foo.yaml -o %t.disallowed.o # RUN: not wasm-ld --no-entry -o - %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 - %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: # @@ -30,6 +40,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: @@ -37,6 +71,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.{{$}} + +# 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: Missing required target feature "foo" +# 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: ... diff --git a/lld/test/wasm/target-feature-used.yaml b/lld/test/wasm/target-feature-used.yaml --- a/lld/test/wasm/target-feature-used.yaml +++ b/lld/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 - %t1.o %t.used.o | obj2yaml | FileCheck %s --check-prefix USED @@ -9,6 +15,8 @@ # RUN: yaml2obj %S/Inputs/disallow-feature-foo.yaml -o %t.disallowed.o # RUN: not wasm-ld --no-entry -o - %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 - %t1.o %t.none.o | obj2yaml | FileCheck %s --check-prefix NONE @@ -34,6 +42,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: @@ -48,7 +80,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 diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h --- a/lld/wasm/Config.h +++ b/lld/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; diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/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); @@ -338,6 +341,15 @@ 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); + } else { + Config->Features = llvm::Optional>(); + } } // Some command line options or some combinations of them are not allowed. diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td --- a/lld/wasm/Options.td +++ b/lld/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; diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -871,18 +871,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: @@ -895,6 +907,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; @@ -903,11 +929,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."); } } }