From aaee2c2106f18d20c0f3feedb56518ad2bcb11c2 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 17 Feb 2026 23:47:16 +0000 Subject: [PATCH 1/6] Require shared everything to be enabled when waitqueues are used --- src/wasm/wasm-validator.cpp | 111 +++++++++++++++++++---------- test/lit/validation/waitqueue.wast | 11 +++ 2 files changed, 83 insertions(+), 39 deletions(-) create mode 100644 test/lit/validation/waitqueue.wast diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 9217021f09c..f5da8f7a437 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -37,6 +37,8 @@ namespace wasm { +namespace { + // Print anything that can be streamed to an ostream templatesecond.name.toString(); + } else { + typeName = type.toString(); + } + info.fail("Exact reference in public type not allowed without custom " + "descriptors [--enable-custom-descriptors]", + typeName, + nullptr); + break; + } + } + } +} + +void validateWaitQueue(Module& module, ValidationInfo& info) { + if (module.features.hasSharedEverything()) { + return; + } + + auto types = ModuleUtils::collectHeapTypeInfo( + module, + ModuleUtils::TypeInclusion::AllTypes, + ModuleUtils::VisibilityHandling::NoVisibility); + + for (auto& [type, _] : types) { + if (!type.isStruct()) { + continue; + } + + for (const auto& field : type.getStruct().fields) { + if (field.packedType == Field::PackedType::WaitQueue) { + info.fail( + "Waitqueues require shared-everything [--enable-shared-everything]", + type, + nullptr); + } + } + } +} + std::string getMissingFeaturesList(Module& wasm, FeatureSet feats) { std::stringstream ss; bool first = true; @@ -4358,7 +4414,7 @@ void FunctionValidator::validateAlignment( } } -static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { +void validateBinaryenIR(Module& wasm, ValidationInfo& info) { struct BinaryenIRValidator : public PostWalker> { @@ -4401,35 +4457,12 @@ static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { // Main validator class -static void validateTypes(Module& module, ValidationInfo& info) { - // Check that public types do not contain any exact references if custom - // descriptors is not enabled. If they did, we would erase the exactness - // during binary writing and change the public type identities. - if (module.features.hasCustomDescriptors()) { - return; - } - - for (auto type : ModuleUtils::getPublicHeapTypes(module)) { - for (auto child : type.getTypeChildren()) { - if (child.isExact()) { - std::string typeName; - if (auto it = module.typeNames.find(type); - it != module.typeNames.end()) { - typeName = '$' + it->second.name.toString(); - } else { - typeName = type.toString(); - } - info.fail("Exact reference in public type not allowed without custom " - "descriptors [--enable-custom-descriptors]", - typeName, - nullptr); - break; - } - } - } +void validateTypes(Module& module, ValidationInfo& info) { + validateExactReferences(module, info); + validateWaitQueue(module, info); } -static void validateImports(Module& module, ValidationInfo& info) { +void validateImports(Module& module, ValidationInfo& info) { ModuleUtils::iterImportedFunctions(module, [&](Function* curr) { if (curr->getResults().isTuple()) { info.shouldBeTrue(module.features.hasMultivalue(), @@ -4474,7 +4507,7 @@ static void validateImports(Module& module, ValidationInfo& info) { }); } -static void validateExports(Module& module, ValidationInfo& info) { +void validateExports(Module& module, ValidationInfo& info) { for (auto& curr : module.exports) { if (curr->kind == ExternalKind::Function) { if (info.validateWeb) { @@ -4543,7 +4576,7 @@ static void validateExports(Module& module, ValidationInfo& info) { } } -static void validateGlobals(Module& module, ValidationInfo& info) { +void validateGlobals(Module& module, ValidationInfo& info) { std::unordered_set seen; ModuleUtils::iterDefinedGlobals(module, [&](Global* curr) { info.shouldBeTrue(curr->type.getFeatures() <= module.features, @@ -4589,7 +4622,7 @@ static void validateGlobals(Module& module, ValidationInfo& info) { } } -static void validateMemories(Module& module, ValidationInfo& info) { +void validateMemories(Module& module, ValidationInfo& info) { if (module.memories.size() > 1) { info.shouldBeTrue( module.features.hasMultiMemory(), @@ -4624,7 +4657,7 @@ static void validateMemories(Module& module, ValidationInfo& info) { } } -static void validateDataSegments(Module& module, ValidationInfo& info) { +void validateDataSegments(Module& module, ValidationInfo& info) { for (auto& segment : module.dataSegments) { if (segment->isPassive) { info.shouldBeTrue( @@ -4655,7 +4688,7 @@ static void validateDataSegments(Module& module, ValidationInfo& info) { } } -static void validateTables(Module& module, ValidationInfo& info) { +void validateTables(Module& module, ValidationInfo& info) { FunctionValidator validator(module, &info); if (!module.features.hasReferenceTypes()) { @@ -4764,7 +4797,7 @@ static void validateTables(Module& module, ValidationInfo& info) { } } -static void validateTags(Module& module, ValidationInfo& info) { +void validateTags(Module& module, ValidationInfo& info) { if (!module.tags.empty()) { info.shouldBeTrue( module.features.hasExceptionHandling(), @@ -4797,7 +4830,7 @@ static void validateTags(Module& module, ValidationInfo& info) { } } -static void validateStart(Module& module, ValidationInfo& info) { +void validateStart(Module& module, ValidationInfo& info) { // start if (module.start.is()) { auto func = module.getFunctionOrNull(module.start); @@ -4813,7 +4846,6 @@ static void validateStart(Module& module, ValidationInfo& info) { } } -namespace { template void validateModuleMap(Module& module, ValidationInfo& info, @@ -4839,9 +4871,8 @@ void validateModuleMap(Module& module, // TODO: Also check there is nothing extraneous in the map, but that would // require inspecting private fields of Module. } -} // anonymous namespace -static void validateModuleMaps(Module& module, ValidationInfo& info) { +void validateModuleMaps(Module& module, ValidationInfo& info) { // Module maps should be up to date. validateModuleMap( module, info, module.exports, &Module::getExportOrNull, "Export"); @@ -4866,7 +4897,7 @@ static void validateModuleMaps(Module& module, ValidationInfo& info) { module, info, module.tables, &Module::getTableOrNull, "Table"); } -static void validateFeatures(Module& module, ValidationInfo& info) { +void validateFeatures(Module& module, ValidationInfo& info) { if (module.features.hasGC()) { info.shouldBeTrue(module.features.hasReferenceTypes(), module.features, @@ -4874,6 +4905,8 @@ static void validateFeatures(Module& module, ValidationInfo& info) { } } +} // namespace + // TODO: If we want the validator to be part of libwasm rather than libpasses, // then Using PassRunner::getPassDebug causes a circular dependence. We should // fix that, perhaps by moving some of the pass infrastructure into libsupport. diff --git a/test/lit/validation/waitqueue.wast b/test/lit/validation/waitqueue.wast new file mode 100644 index 00000000000..f803f83852d --- /dev/null +++ b/test/lit/validation/waitqueue.wast @@ -0,0 +1,11 @@ +;; RUN: not wasm-opt --enable-reference-types --enable-gc %s 2>&1 | filecheck %s + +(module + ;; CHECK: [wasm-validator error in module] Waitqueues require shared-everything [--enable-shared-everything], on + ;; CHECK-NEXT: (type $struct.0 (struct (field waitqueue))) +(type $struct.0 (struct (field waitqueue))) + (type $t (struct (field waitqueue))) + + ;; use $t so wasm-opt doesn't drop the definition + (global (ref null $t) (ref.null $t)) +) From 3c4946a6185aa6268b04e0d8f53d7779a49555b7 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 18 Feb 2026 23:43:16 +0000 Subject: [PATCH 2/6] Fix test --- test/lit/validation/waitqueue.wast | 1 - 1 file changed, 1 deletion(-) diff --git a/test/lit/validation/waitqueue.wast b/test/lit/validation/waitqueue.wast index f803f83852d..26b60da06c7 100644 --- a/test/lit/validation/waitqueue.wast +++ b/test/lit/validation/waitqueue.wast @@ -3,7 +3,6 @@ (module ;; CHECK: [wasm-validator error in module] Waitqueues require shared-everything [--enable-shared-everything], on ;; CHECK-NEXT: (type $struct.0 (struct (field waitqueue))) -(type $struct.0 (struct (field waitqueue))) (type $t (struct (field waitqueue))) ;; use $t so wasm-opt doesn't drop the definition From e6a93fc09b82a6869a0b7fb01e44bce19d40d76d Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 19 Feb 2026 17:04:21 +0000 Subject: [PATCH 3/6] Validate in wasm-type.cpp --- src/wasm/wasm-type.cpp | 4 ++++ src/wasm/wasm-validator.cpp | 28 +--------------------------- test/lit/validation/waitqueue.wast | 3 +-- 3 files changed, 6 insertions(+), 29 deletions(-) diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 010ff505fde..256f11af7ff 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -2455,6 +2455,10 @@ validateStruct(const Struct& struct_, FeatureSet feats, bool isShared) { if (auto err = validateType(field.type, feats, isShared)) { return err; } + if (field.packedType == Field::PackedType::WaitQueue && + !feats.hasSharedEverything()) { + return TypeBuilder::ErrorReason::InvalidSharedType; + } } return std::nullopt; } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index f5da8f7a437..48156d008ba 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -269,32 +269,6 @@ void validateExactReferences(Module& module, ValidationInfo& info) { } } -void validateWaitQueue(Module& module, ValidationInfo& info) { - if (module.features.hasSharedEverything()) { - return; - } - - auto types = ModuleUtils::collectHeapTypeInfo( - module, - ModuleUtils::TypeInclusion::AllTypes, - ModuleUtils::VisibilityHandling::NoVisibility); - - for (auto& [type, _] : types) { - if (!type.isStruct()) { - continue; - } - - for (const auto& field : type.getStruct().fields) { - if (field.packedType == Field::PackedType::WaitQueue) { - info.fail( - "Waitqueues require shared-everything [--enable-shared-everything]", - type, - nullptr); - } - } - } -} - std::string getMissingFeaturesList(Module& wasm, FeatureSet feats) { std::stringstream ss; bool first = true; @@ -4458,8 +4432,8 @@ void validateBinaryenIR(Module& wasm, ValidationInfo& info) { // Main validator class void validateTypes(Module& module, ValidationInfo& info) { + // Most validations belong in `validateTypeInfo` in wasm-type.cpp. validateExactReferences(module, info); - validateWaitQueue(module, info); } void validateImports(Module& module, ValidationInfo& info) { diff --git a/test/lit/validation/waitqueue.wast b/test/lit/validation/waitqueue.wast index 26b60da06c7..42f650aaf28 100644 --- a/test/lit/validation/waitqueue.wast +++ b/test/lit/validation/waitqueue.wast @@ -1,8 +1,7 @@ ;; RUN: not wasm-opt --enable-reference-types --enable-gc %s 2>&1 | filecheck %s (module - ;; CHECK: [wasm-validator error in module] Waitqueues require shared-everything [--enable-shared-everything], on - ;; CHECK-NEXT: (type $struct.0 (struct (field waitqueue))) + ;; CHECK: invalid type: Shared types require shared-everything (type $t (struct (field waitqueue))) ;; use $t so wasm-opt doesn't drop the definition From 8bdb886cbf5d5ea31c43ea1d15657504a898e594 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 19 Feb 2026 17:34:51 +0000 Subject: [PATCH 4/6] Revert wasm-validator --- src/wasm/wasm-validator.cpp | 85 +++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 48156d008ba..9217021f09c 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -37,8 +37,6 @@ namespace wasm { -namespace { - // Print anything that can be streamed to an ostream templatesecond.name.toString(); - } else { - typeName = type.toString(); - } - info.fail("Exact reference in public type not allowed without custom " - "descriptors [--enable-custom-descriptors]", - typeName, - nullptr); - break; - } - } - } -} - std::string getMissingFeaturesList(Module& wasm, FeatureSet feats) { std::stringstream ss; bool first = true; @@ -4388,7 +4358,7 @@ void FunctionValidator::validateAlignment( } } -void validateBinaryenIR(Module& wasm, ValidationInfo& info) { +static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { struct BinaryenIRValidator : public PostWalker> { @@ -4431,12 +4401,35 @@ void validateBinaryenIR(Module& wasm, ValidationInfo& info) { // Main validator class -void validateTypes(Module& module, ValidationInfo& info) { - // Most validations belong in `validateTypeInfo` in wasm-type.cpp. - validateExactReferences(module, info); +static void validateTypes(Module& module, ValidationInfo& info) { + // Check that public types do not contain any exact references if custom + // descriptors is not enabled. If they did, we would erase the exactness + // during binary writing and change the public type identities. + if (module.features.hasCustomDescriptors()) { + return; + } + + for (auto type : ModuleUtils::getPublicHeapTypes(module)) { + for (auto child : type.getTypeChildren()) { + if (child.isExact()) { + std::string typeName; + if (auto it = module.typeNames.find(type); + it != module.typeNames.end()) { + typeName = '$' + it->second.name.toString(); + } else { + typeName = type.toString(); + } + info.fail("Exact reference in public type not allowed without custom " + "descriptors [--enable-custom-descriptors]", + typeName, + nullptr); + break; + } + } + } } -void validateImports(Module& module, ValidationInfo& info) { +static void validateImports(Module& module, ValidationInfo& info) { ModuleUtils::iterImportedFunctions(module, [&](Function* curr) { if (curr->getResults().isTuple()) { info.shouldBeTrue(module.features.hasMultivalue(), @@ -4481,7 +4474,7 @@ void validateImports(Module& module, ValidationInfo& info) { }); } -void validateExports(Module& module, ValidationInfo& info) { +static void validateExports(Module& module, ValidationInfo& info) { for (auto& curr : module.exports) { if (curr->kind == ExternalKind::Function) { if (info.validateWeb) { @@ -4550,7 +4543,7 @@ void validateExports(Module& module, ValidationInfo& info) { } } -void validateGlobals(Module& module, ValidationInfo& info) { +static void validateGlobals(Module& module, ValidationInfo& info) { std::unordered_set seen; ModuleUtils::iterDefinedGlobals(module, [&](Global* curr) { info.shouldBeTrue(curr->type.getFeatures() <= module.features, @@ -4596,7 +4589,7 @@ void validateGlobals(Module& module, ValidationInfo& info) { } } -void validateMemories(Module& module, ValidationInfo& info) { +static void validateMemories(Module& module, ValidationInfo& info) { if (module.memories.size() > 1) { info.shouldBeTrue( module.features.hasMultiMemory(), @@ -4631,7 +4624,7 @@ void validateMemories(Module& module, ValidationInfo& info) { } } -void validateDataSegments(Module& module, ValidationInfo& info) { +static void validateDataSegments(Module& module, ValidationInfo& info) { for (auto& segment : module.dataSegments) { if (segment->isPassive) { info.shouldBeTrue( @@ -4662,7 +4655,7 @@ void validateDataSegments(Module& module, ValidationInfo& info) { } } -void validateTables(Module& module, ValidationInfo& info) { +static void validateTables(Module& module, ValidationInfo& info) { FunctionValidator validator(module, &info); if (!module.features.hasReferenceTypes()) { @@ -4771,7 +4764,7 @@ void validateTables(Module& module, ValidationInfo& info) { } } -void validateTags(Module& module, ValidationInfo& info) { +static void validateTags(Module& module, ValidationInfo& info) { if (!module.tags.empty()) { info.shouldBeTrue( module.features.hasExceptionHandling(), @@ -4804,7 +4797,7 @@ void validateTags(Module& module, ValidationInfo& info) { } } -void validateStart(Module& module, ValidationInfo& info) { +static void validateStart(Module& module, ValidationInfo& info) { // start if (module.start.is()) { auto func = module.getFunctionOrNull(module.start); @@ -4820,6 +4813,7 @@ void validateStart(Module& module, ValidationInfo& info) { } } +namespace { template void validateModuleMap(Module& module, ValidationInfo& info, @@ -4845,8 +4839,9 @@ void validateModuleMap(Module& module, // TODO: Also check there is nothing extraneous in the map, but that would // require inspecting private fields of Module. } +} // anonymous namespace -void validateModuleMaps(Module& module, ValidationInfo& info) { +static void validateModuleMaps(Module& module, ValidationInfo& info) { // Module maps should be up to date. validateModuleMap( module, info, module.exports, &Module::getExportOrNull, "Export"); @@ -4871,7 +4866,7 @@ void validateModuleMaps(Module& module, ValidationInfo& info) { module, info, module.tables, &Module::getTableOrNull, "Table"); } -void validateFeatures(Module& module, ValidationInfo& info) { +static void validateFeatures(Module& module, ValidationInfo& info) { if (module.features.hasGC()) { info.shouldBeTrue(module.features.hasReferenceTypes(), module.features, @@ -4879,8 +4874,6 @@ void validateFeatures(Module& module, ValidationInfo& info) { } } -} // namespace - // TODO: If we want the validator to be part of libwasm rather than libpasses, // then Using PassRunner::getPassDebug causes a circular dependence. We should // fix that, perhaps by moving some of the pass infrastructure into libsupport. From c1da866a1e200b3ef63fac74acadc5d1e140c13f Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 19 Feb 2026 17:40:23 +0000 Subject: [PATCH 5/6] Add a new error type for waitqueue --- src/wasm-type.h | 2 ++ src/wasm/wasm-type.cpp | 4 +++- test/lit/validation/waitqueue.wast | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index 591b2312f82..3dca2f2d354 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -907,6 +907,8 @@ struct TypeBuilder { InvalidFuncType, // A shared type with shared-everything disabled. InvalidSharedType, + // WaitQueue was used with shared-everything disabled. + WaitQueueDisabled, // A string type with strings disabled. InvalidStringType, // A non-shared field of a shared heap type. diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 256f11af7ff..b7eb449ce8b 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1447,6 +1447,8 @@ std::ostream& operator<<(std::ostream& os, TypeBuilder::ErrorReason reason) { return os << "Continuation has invalid function type"; case TypeBuilder::ErrorReason::InvalidSharedType: return os << "Shared types require shared-everything"; + case TypeBuilder::ErrorReason::WaitQueueDisabled: + return os << "Waitqueues require shared-everything"; case TypeBuilder::ErrorReason::InvalidStringType: return os << "String types require strings feature"; case TypeBuilder::ErrorReason::InvalidUnsharedField: @@ -2457,7 +2459,7 @@ validateStruct(const Struct& struct_, FeatureSet feats, bool isShared) { } if (field.packedType == Field::PackedType::WaitQueue && !feats.hasSharedEverything()) { - return TypeBuilder::ErrorReason::InvalidSharedType; + return TypeBuilder::ErrorReason::WaitQueueDisabled; } } return std::nullopt; diff --git a/test/lit/validation/waitqueue.wast b/test/lit/validation/waitqueue.wast index 42f650aaf28..adf01d961a6 100644 --- a/test/lit/validation/waitqueue.wast +++ b/test/lit/validation/waitqueue.wast @@ -1,7 +1,7 @@ ;; RUN: not wasm-opt --enable-reference-types --enable-gc %s 2>&1 | filecheck %s (module - ;; CHECK: invalid type: Shared types require shared-everything + ;; CHECK: invalid type: Waitqueues require shared-everything (type $t (struct (field waitqueue))) ;; use $t so wasm-opt doesn't drop the definition From bd18656ee5ab0ab786b129ccb5bcd9b6d08a91fb Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 19 Feb 2026 19:45:40 +0000 Subject: [PATCH 6/6] Rename error value --- src/wasm-type.h | 2 +- src/wasm/wasm-type.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index 3dca2f2d354..8361c9049c3 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -908,7 +908,7 @@ struct TypeBuilder { // A shared type with shared-everything disabled. InvalidSharedType, // WaitQueue was used with shared-everything disabled. - WaitQueueDisabled, + InvalidWaitQueue, // A string type with strings disabled. InvalidStringType, // A non-shared field of a shared heap type. diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index b7eb449ce8b..ab5275c4944 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1447,7 +1447,7 @@ std::ostream& operator<<(std::ostream& os, TypeBuilder::ErrorReason reason) { return os << "Continuation has invalid function type"; case TypeBuilder::ErrorReason::InvalidSharedType: return os << "Shared types require shared-everything"; - case TypeBuilder::ErrorReason::WaitQueueDisabled: + case TypeBuilder::ErrorReason::InvalidWaitQueue: return os << "Waitqueues require shared-everything"; case TypeBuilder::ErrorReason::InvalidStringType: return os << "String types require strings feature"; @@ -2459,7 +2459,7 @@ validateStruct(const Struct& struct_, FeatureSet feats, bool isShared) { } if (field.packedType == Field::PackedType::WaitQueue && !feats.hasSharedEverything()) { - return TypeBuilder::ErrorReason::WaitQueueDisabled; + return TypeBuilder::ErrorReason::InvalidWaitQueue; } } return std::nullopt;