Conversation
tlively
reviewed
Sep 21, 2026
tlively
left a comment
Member
There was a problem hiding this comment.
Seems promising! Can you:
- Rewrite the description to remove extraneous detail.
- Measure how this change affects the performance of validation.
Comment on lines
+2226
to
+2228
| // ChildTyper constrains each child to the eqref family, but each note() | ||
| // call runs with a fresh VarAssignments, so the sharedness variable does | ||
| // not unify across left and right. Keep the cross-child check here. |
Member
There was a problem hiding this comment.
Presumably if we had a utility that gave the full principal type of an expression, we wouldn't have this problem any more. Such a utility would also be useful for e.g. the Outlining pass.
| (func (result i32) | ||
| (i32.add (i32.const 0) (f64.const 0.0)) | ||
| ) | ||
| ) No newline at end of file |
Member
There was a problem hiding this comment.
Please add newlines at the end of the test files.
Phase 2 of WebAssembly#6613. Add a ValidatorTypeChecker subclass of ChildTyper<ValidatorTypeChecker> whose note callback validates each child's type against its constraint via PrincipalType::matches. Replace the manual per-opcode switch in FunctionValidator::visitUnary (~200 lines) with a single call. The none guard and the feature check stay, since neither is a child-type constraint. Unreachable children are skipped to preserve the existing shouldBeEqualOrFirstIsUnreachable semantics. Coverage diff between the pre-patch visitUnary case labels and ChildTyper::visitUnary case labels is empty, so no opcode is lost. The diagnostic changes from opcode-specific messages to "child type does not match its constraint" at the failing child, exercised by test/lit/validation/unary-bad-operand.wast. Partially fixes WebAssembly#6613.
Phase 3 Batch 1 of WebAssembly#6613. Extend ValidatorTypeChecker to visitBinary. The tuple guard and sibling left->type == right->type check stay in the validator, since ChildTyper does not express them. Restore the FP16 feature gates that Phase 2 removed from visitUnary and that this change would have removed from visitBinary. ChildTyper cannot express the gate because FP16 ops share their operand types with their non-FP16 counterparts; the gate is applied via requiresFP16(UnaryOp) and requiresFP16(BinaryOp) before the ChildTyper check, matching the pre-WebAssembly#6613 ordering. Coverage diffs for both methods are empty. Exercised by test/lit/validation/binary-bad-operand.wast, and by the pre-existing fp16-unary.wast and fp16.wast, which now pass. Refs WebAssembly#6613.
Phase 3 Batch 2 of WebAssembly#6613. Extend ValidatorTypeChecker to the two ref visitors whose ChildTyper constraints are polymorphic over the heap type and sharedness dimensions, and therefore match the validator's semantic exactly. Feature gates stay in the validator. The cross-child shareability check in visitRefEq also stays: ValidatorTypeChecker calls PrincipalType::matches, which builds a fresh VarAssignments per child, so the sharedness variable that ChildTyper's visitRefEq emits does not unify across the two note() calls. Unifying it is a Phase 4 change that requires a persistent VarAssignments member. visitI31Get is not migrated. ChildTyper::visitI31Get constrains the argument to the unshared Type(HeapType::i31, Nullable), but the validator's pre-existing check uses shouldBeSubTypeIgnoringShared, which accepts (ref null (shared i31)) as well. Migrating would reject shared i31 modules and regress basic/shared-i31.wast, passes/make-shared-objects.wast, and passes/unsubtyping.wast. The discrepancy is the same sharedness gap that the unsubtyping work tracks; resolving it requires a sharedness-polymorphic constraint facility in ValidatorTypeChecker, which is Phase 4 scope. Adds two regression tests: - test/lit/validation/ref-is-null-bad-operand.wast locks in the migrated visitRefIsNull rejection path. - test/lit/validation/i31-get-bad-operand.wast locks in the pre-existing visitI31Get rejection path (a function reference is not an i31ref). The fixture uses (ref.func $f) rather than (ref.null $struct) because the parser types the latter as nullref, which is correctly accepted as an i31ref subtype. visitTupleExtract, visitTupleMake, visitRefI31, visitRefAs, and the SIMD family are skipped in this batch. Their switches are dominated by result-type checks, immediate validation, or feature gates, with at most one migratable child-type check apiece; migration would add a cross-file dependency without a code reduction. Refs WebAssembly#6613.
ArkadySkv
force-pushed
the
fix-use-ChildTyper-in-the-validator-6613
branch
from
September 21, 2026 08:45
adf4589 to
d884472
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem:
FunctionValidator::visitUnaryandvisitBinarycontain~400 lines of per-opcode child-type checks expressed as
shouldBeEqual/shouldBeEqualOrFirstIsUnreachablecalls. Thesame constraints are already encoded in
src/ir/child-typer.handconsumed by the IRBuilder. The duplication is error-prone: a missed
case is a soundness hole.
visitRefIsNullandvisitRefEqhavethe same shape at smaller scale.
Fix (Path A): Add a
ValidatorTypeCheckersubclass ofChildTyper<ValidatorTypeChecker>inwasm-validator.cpp. Itsnote()callback checks each child's type against the emittedconstraint via
PrincipalType::matches, skipping unreachablechildren. Replace the switches in
visitUnaryandvisitBinarywith single calls. Migrate the per-child checks in
visitRefIsNullandvisitRefEq.Checks that
ChildTyperdoes not express are kept in thevalidator:
visitUnaryandvisitBinary; GC andreference-types on
visitRefIsNullandvisitRefEq.left->type == right->typecheck andthe tuple-arity guard on
visitBinary.visitRefEq.ValidatorTypeCheckercallsPrincipalType::matches, whichbuilds a fresh
VarAssignmentsper child, so theVarSharednessvariable thatChildTyper::visitRefEqemitsdoes not unify across the two
note()calls.ValidatorTypeCheckergains a no-opnoteUnknown(), required forCRTP instantiation.
Verification:
Result: both diffs empty;
check.py litpasses with 1002 tests.New regression tests:
test/lit/validation/binary-bad-operand.wasttest/lit/validation/ref-is-null-bad-operand.wasttest/lit/validation/i31-get-bad-operand.wast(locks in thepre-existing
visitI31Getrejection message; that method isnot migrated)
Two migrations were attempted and reverted:
visitI31Get.ChildTyper::visitI31Getemits the unsharedType(HeapType::i31, Nullable). The validator's pre-existingcheck uses
shouldBeSubTypeIgnoringShared, which accepts(ref null (shared i31)). Migrating rejected shared i31 andbroke
basic/shared-i31.wast,passes/make-shared-objects.wast,and
passes/unsubtyping.wast.visitCallRef.ChildTyper::visitCallRefnotes the target asType(getSignature(), Nullable), which drops sharedness.Shared function references were rejected, breaking 25 lit
tests. The IRBuilder enforces
call_refoperand types atparse time, so the migrated check was redundant for WAT input.
Diagnostic change: per-opcode messages ("i32 op", "expected v128
operand", etc.) become "child type does not match its constraint",
reported at the failing child rather than the parent.
Follow-up: See #NNNN (ChildTyper sharedness) and #NNNN (complete
the remaining migration). The sharedness issue blocks
visitI31Getand
visitCallRef; the completion issue covers the ~70 methodswith
ChildTypercounterparts that remain unexamined. The SIMDand tuple families are deliberately not migrated: their switches
are dominated by immediate validation, memory helpers, and result
type checks.
Refs #6613. Does not close it.