Skip to content

Commit 41cfd56

Browse files
Allow i64 and ref eq control words in struct.wait
1 parent 6906bf0 commit 41cfd56

6 files changed

Lines changed: 160 additions & 17 deletions

File tree

src/ir/child-typer.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1038,10 +1038,19 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
10381038
}
10391039
ht = curr->ref->type.getHeapType();
10401040
}
1041+
const auto& fields = ht->getStruct().fields;
1042+
if (curr->index >= fields.size()) {
1043+
self().noteUnknown();
1044+
return;
1045+
}
10411046

10421047
note(&curr->ref, Type(*ht, Nullable));
10431048
note(&curr->waitqueue, Type(HeapTypes::sharedWaitqueue, Nullable));
1044-
note(&curr->expected, Type(Type::BasicType::i32));
1049+
auto expectedType = fields[curr->index].type;
1050+
if (expectedType.isRef()) {
1051+
expectedType = Type(HeapTypes::eq.getBasic(Shared), Nullable);
1052+
}
1053+
note(&curr->expected, expectedType);
10451054
note(&curr->timeout, Type(Type::BasicType::i64));
10461055
}
10471056

src/wasm-interpreter.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2324,6 +2324,7 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
23242324

23252325
Flow visitStructWait(StructWait* curr) {
23262326
VISIT(ref, curr->ref)
2327+
VISIT(waitqueue, curr->waitqueue)
23272328
VISIT(expected, curr->expected)
23282329
VISIT(timeout, curr->timeout)
23292330

@@ -2335,8 +2336,11 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
23352336
if (!data) {
23362337
trap("null ref");
23372338
}
2339+
if (!waitqueue.getSingleValue().getGCData()) {
2340+
trap("null ref");
2341+
}
23382342
auto& field = data->values[curr->index];
2339-
if (field.geti32() != expected.getSingleValue().geti32()) {
2343+
if (field != expected.getSingleValue()) {
23402344
return Literal(int32_t{1}); // not equal
23412345
}
23422346
// TODO: Add threads support. For now, report a host limit here, as there

src/wasm/wasm-ir-builder.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,6 +2426,7 @@ Result<> IRBuilder::makeStructWait(HeapType type, Index index) {
24262426
}
24272427

24282428
StructWait curr(wasm.allocator);
2429+
curr.index = index;
24292430
CHECK_ERR(ChildPopper{*this}.visitStructWait(&curr, type));
24302431
CHECK_ERR(validateTypeAnnotation(type, curr.ref));
24312432
push(builder.makeStructWait(

src/wasm/wasm-validator.cpp

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,8 @@ struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> {
600600
bool shouldBeTrue(bool result, T curr, const char* text) {
601601
return info.shouldBeTrue(result, curr, text, getFunction());
602602
}
603+
604+
// Returns true if the assertion was met, i.e. returns !result.
603605
template<typename T>
604606
bool shouldBeFalse(bool result, T curr, const char* text) {
605607
return info.shouldBeFalse(result, curr, text, getFunction());
@@ -3644,6 +3646,10 @@ void FunctionValidator::visitStructCmpxchg(StructCmpxchg* curr) {
36443646
}
36453647

36463648
void FunctionValidator::visitStructWait(StructWait* curr) {
3649+
// In IRBuilder, we check that the struct ref matches the type immediate.
3650+
// We can't check this here because we've already discarded the type immediate
3651+
// at this point. All other validations are here.
3652+
36473653
shouldBeTrue(
36483654
!getModule() || getModule()->features.hasSharedEverything(),
36493655
curr,
@@ -3653,20 +3659,44 @@ void FunctionValidator::visitStructWait(StructWait* curr) {
36533659
Type(HeapTypes::sharedWaitqueue, Nullable),
36543660
curr,
36553661
"struct.wait waitqueue must be a shared waitqueue reference");
3656-
shouldBeEqual(curr->expected->type,
3657-
Type(Type::BasicType::i32),
3658-
curr,
3659-
"struct.wait expected must be an i32");
36603662
shouldBeEqual(curr->timeout->type,
36613663
Type(Type::BasicType::i64),
36623664
curr,
36633665
"struct.wait timeout must be an i64");
36643666

3665-
// Checks to the ref argument's type are done in IRBuilder where we have the
3666-
// type annotation immediate available. We check that
3667-
// * The reference arg is a subtype of the type immediate
3668-
// * The index immediate is a valid field index of the type immediate (and
3669-
// thus valid for the reference's type too)
3667+
if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) {
3668+
return;
3669+
}
3670+
if (!shouldBeTrue(curr->ref->type.isStruct(),
3671+
curr->ref,
3672+
"struct.wait ref must be a struct")) {
3673+
return;
3674+
}
3675+
const auto& fields = curr->ref->type.getHeapType().getStruct().fields;
3676+
if (!shouldBeTrue(
3677+
curr->index < fields.size(), curr, "out of bounds struct.wait field")) {
3678+
return;
3679+
}
3680+
auto& field = fields[curr->index];
3681+
if (!shouldBeFalse(
3682+
field.isPacked(), curr, "struct.wait field must not be packed")) {
3683+
return;
3684+
}
3685+
3686+
if (
3687+
!shouldBeTrue(
3688+
field.type == Type::i32 || field.type == Type::i64 ||
3689+
Type::isSubType(field.type,
3690+
Type(HeapTypes::eq.getBasic(Shared), Nullable)),
3691+
curr,
3692+
R"(struct.wait control word field must be i32, i64 or a subtype of (ref null (shared eq)))")) {
3693+
return;
3694+
}
3695+
3696+
shouldBeSubType(curr->expected->type,
3697+
field.type,
3698+
curr,
3699+
"struct.wait expected value must match the field immediate");
36703700
}
36713701

36723702
void FunctionValidator::visitWaitqueueNew(WaitqueueNew* curr) {

test/lit/validation/waitqueue.wast

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
;; RUN: not wasm-opt --enable-reference-types --enable-gc %s 2>&1 | filecheck %s
2+
3+
;; Tests feature-related validations.
4+
;; Other validations are in the spec test spec/waitqueue.wast.
25
(module
36
(type $struct (struct (field i32)))
47
;; CHECK: waitqueue.new requires shared-everything [--enable-shared-everything]
5-
(func
8+
(func $new
69
(drop (waitqueue.new))
710
)
11+
;; CHECK: struct.wait requires shared-everything [--enable-shared-everything]
12+
(func $wait-no-feature (param $ref (ref $struct)) (param $wq (ref null (shared waitqueue)))
13+
(drop (struct.wait $struct 0 (local.get $ref) (local.get $wq) (i32.const 0) (i64.const 0)))
14+
)
815
)
9-

test/spec/waitqueue.wast

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
(func (param $expected i32) (param $timeout i64) (result i32)
1515
(struct.wait $t 2 (ref.null $t) (global.get $wq) (local.get $expected) (local.get $timeout))
1616
)
17-
) "struct index out of bounds"
17+
) "out of bounds struct.wait field"
1818
)
1919

2020
(assert_invalid
@@ -25,7 +25,29 @@
2525
(func (param $expected i32) (param $timeout i64) (result i32)
2626
(struct.wait $t 0 (global.get $g) (global.get $wq) (i64.const 0) (local.get $timeout))
2727
)
28-
) "struct.wait expected must be an i32"
28+
) "struct.wait expected value must match the field immediate"
29+
)
30+
31+
(assert_invalid
32+
(module
33+
(type $t (shared (struct (field f32))))
34+
(global $g (ref $t) (struct.new $t (f32.const 0)))
35+
(global $wq (ref (shared waitqueue)) (waitqueue.new))
36+
(func (param $expected f32) (param $timeout i64) (result i32)
37+
(struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (local.get $timeout))
38+
)
39+
) "struct.wait control word field must be i32, i64 or a subtype of (ref null (shared eq))"
40+
)
41+
42+
(assert_invalid
43+
(module
44+
(type $t (shared (struct (field i8))))
45+
(global $g (ref $t) (struct.new $t (i32.const 0)))
46+
(global $wq (ref (shared waitqueue)) (waitqueue.new))
47+
(func (param $expected i32) (param $timeout i64) (result i32)
48+
(struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (local.get $timeout))
49+
)
50+
) "struct.wait field must not be packed"
2951
)
3052

3153
(assert_invalid
@@ -41,8 +63,6 @@
4163

4264
(assert_invalid
4365
(module
44-
(type $t (shared (struct (field i32))))
45-
(global $wq (ref (shared waitqueue)) (waitqueue.new))
4666
(func (param $count i32) (result i32)
4767
(waitqueue.notify (ref.null waitqueue) (local.get $count))
4868
)
@@ -74,6 +94,7 @@
7494
)
7595
)
7696

97+
;; i32 control word
7798
(module
7899
(type $t (shared (struct (field (mut i32)))))
79100

@@ -119,6 +140,78 @@
119140
(assert_trap (invoke "struct.wait" (i32.const 0) (i64.const 0)) "null ref")
120141
(assert_trap (invoke "waitqueue.notify" (i32.const 0)) "null ref")
121142

143+
;; i64 control word
144+
(module
145+
(type $t (shared (struct (field (mut i64)))))
146+
147+
(global $g (mut (ref null $t)) (struct.new $t (i64.const 0)))
148+
(global $wq (mut (ref (shared waitqueue))) (waitqueue.new))
149+
150+
(func (export "struct.wait") (param $expected i64) (param $timeout i64) (result i32)
151+
(struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (local.get $timeout))
152+
)
153+
154+
(func (export "struct.set") (param $val i64)
155+
(struct.set $t 0 (global.get $g) (local.get $val))
156+
)
157+
158+
(func (export "struct.get") (result i64)
159+
(struct.get $t 0 (global.get $g))
160+
)
161+
)
162+
163+
(invoke "struct.set" (i64.const 42))
164+
(assert_return (invoke "struct.get") (i64.const 42))
165+
(assert_return (invoke "struct.wait" (i64.const 0) (i64.const 100)) (i32.const 1))
166+
(assert_return (invoke "struct.wait" (i64.const 42) (i64.const 0)) (i32.const 2))
167+
168+
;; (ref null (shared eq)) control word
169+
(module
170+
(type $control (shared (struct)))
171+
172+
(type $t (shared (struct
173+
(field (mut (ref null (shared eq))))
174+
)))
175+
176+
(global $control1 (ref $control) (struct.new $control))
177+
(global $control2 (ref $control) (struct.new $control))
178+
179+
(global $g (mut (ref null $t)) (struct.new $t
180+
(global.get $control1)
181+
))
182+
183+
(global $wq (mut (ref null (shared waitqueue))) (waitqueue.new))
184+
185+
(func (export "wait_control1") (result i32)
186+
(struct.wait $t 0 (global.get $g) (global.get $wq) (global.get $control1) (i64.const 0))
187+
)
188+
189+
(func (export "wait_control2") (result i32)
190+
(struct.wait $t 0 (global.get $g) (global.get $wq) (global.get $control2) (i64.const 0))
191+
)
192+
193+
(func (export "wait_null") (result i32)
194+
(struct.wait $t 0 (global.get $g) (global.get $wq) (ref.null (shared eq)) (i64.const 0))
195+
)
196+
197+
(func (export "set_control_to_null")
198+
(struct.set $t 0 (global.get $g) (ref.null (shared eq)))
199+
)
200+
)
201+
202+
;; $control1 is the control word, wait 0ns and return 2.
203+
(assert_return (invoke "wait_control1") (i32.const 2))
204+
;; $control2 is not the control work, don't wait and return 1.
205+
(assert_return (invoke "wait_control2") (i32.const 1))
206+
;; ditto for null.
207+
(assert_return (invoke "wait_null") (i32.const 1))
208+
209+
(invoke "set_control_to_null")
210+
211+
;; null is now the control word.
212+
(assert_return (invoke "wait_null") (i32.const 2))
213+
(assert_return (invoke "wait_control1") (i32.const 1))
214+
122215
;; Binary format test for waitqueue and nowaitqueue.
123216
(module binary
124217
"\00asm\01\00\00\00" ;; Wasm header

0 commit comments

Comments
 (0)