Skip to content

Commit b2054b7

Browse files
authored
Make call_ref type annotations mandatory (#5246)
They were optional for a while to allow users to gracefully transition to using them, but now make them mandatory to match the upstream WasmGC spec.
1 parent 8225e48 commit b2054b7

22 files changed

+100
-161
lines changed

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ Current Trunk
1818
- Add extra `memory64` argument for `BinaryenSetMemory` and new
1919
`BinaryenMemoryIs64` C-API method to determine 64-bit memory. (#4963)
2020
- `TypeBuilderSetSubType` now takes a supertype as the second argument.
21-
- `call_ref` can now take a signature type immediate in the text format. The
22-
type immediate will become mandatory in the future.
21+
- `call_ref` now takes a mandatory signature type immediate.
2322
- If `THROW_ON_FATAL` is defined at compile-time, then fatal errors will throw a
2423
`std::runtime_error` instead of terminating the process. This may be used by
2524
embedders of Binaryen to recover from errors.

src/wasm-binary.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,8 +1094,7 @@ enum ASTNodes {
10941094

10951095
// typed function references opcodes
10961096

1097-
CallRefUnannotated = 0x14,
1098-
CallRef = 0x17,
1097+
CallRef = 0x14,
10991098
RetCallRef = 0x15,
11001099

11011100
// gc opcodes
@@ -1743,8 +1742,7 @@ class WasmBinaryBuilder {
17431742
void visitTryOrTryInBlock(Expression*& out);
17441743
void visitThrow(Throw* curr);
17451744
void visitRethrow(Rethrow* curr);
1746-
void visitCallRef(CallRef* curr,
1747-
std::optional<HeapType> maybeType = std::nullopt);
1745+
void visitCallRef(CallRef* curr);
17481746
void visitRefAs(RefAs* curr, uint8_t code);
17491747

17501748
[[noreturn]] void throwError(std::string text);

src/wasm-builder.h

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,35 +1342,6 @@ class ValidatingBuilder : public Builder {
13421342
}
13431343
return makeBrOn(op, name, ref);
13441344
}
1345-
1346-
template<typename T>
1347-
Expression* validateAndMakeCallRef(Expression* target,
1348-
const T& args,
1349-
bool isReturn = false) {
1350-
if (target->type != Type::unreachable && !target->type.isRef()) {
1351-
throw ParseException("Non-reference type for a call_ref", line, col);
1352-
}
1353-
// TODO: This won't be necessary once type annotations are mandatory on
1354-
// call_ref.
1355-
if (target->type == Type::unreachable ||
1356-
target->type.getHeapType() == HeapType::nofunc) {
1357-
// An unreachable target is not supported. Similiar to br_on_cast, just
1358-
// emit an unreachable sequence, since we don't have enough information
1359-
// to create a full call_ref.
1360-
std::vector<Expression*> children;
1361-
for (auto* arg : args) {
1362-
children.push_back(makeDrop(arg));
1363-
}
1364-
children.push_back(makeDrop(target));
1365-
children.push_back(makeUnreachable());
1366-
return makeBlock(children, Type::unreachable);
1367-
}
1368-
auto heapType = target->type.getHeapType();
1369-
if (!heapType.isSignature()) {
1370-
throw ParseException("Invalid reference type for a call_ref", line, col);
1371-
}
1372-
return makeCallRef(target, args, heapType.getSignature().results, isReturn);
1373-
}
13741345
};
13751346

13761347
} // namespace wasm

src/wasm/wasm-binary.cpp

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3846,15 +3846,12 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
38463846
visitMemoryGrow(grow);
38473847
break;
38483848
}
3849-
case BinaryConsts::CallRefUnannotated:
3850-
visitCallRef((curr = allocator.alloc<CallRef>())->cast<CallRef>());
3851-
break;
38523849
case BinaryConsts::CallRef:
38533850
case BinaryConsts::RetCallRef: {
38543851
auto call = allocator.alloc<CallRef>();
38553852
call->isReturn = code == BinaryConsts::RetCallRef;
38563853
curr = call;
3857-
visitCallRef(call, getTypeByIndex(getU32LEB()));
3854+
visitCallRef(call);
38583855
break;
38593856
}
38603857
case BinaryConsts::AtomicPrefix: {
@@ -6851,29 +6848,13 @@ void WasmBinaryBuilder::visitRethrow(Rethrow* curr) {
68516848
curr->finalize();
68526849
}
68536850

6854-
void WasmBinaryBuilder::visitCallRef(CallRef* curr,
6855-
std::optional<HeapType> maybeType) {
6851+
void WasmBinaryBuilder::visitCallRef(CallRef* curr) {
68566852
BYN_TRACE("zz node: CallRef\n");
68576853
curr->target = popNonVoidExpression();
6858-
HeapType heapType;
6859-
if (maybeType) {
6860-
heapType = *maybeType;
6861-
if (!Type::isSubType(curr->target->type, Type(heapType, Nullable))) {
6862-
throwError("Call target has invalid type: " +
6863-
curr->target->type.toString());
6864-
}
6865-
} else {
6866-
auto type = curr->target->type;
6867-
if (type == Type::unreachable) {
6868-
// If our input is unreachable, then we cannot even find out how many
6869-
// inputs we have, and just set ourselves to unreachable as well.
6870-
curr->finalize(type);
6871-
return;
6872-
}
6873-
if (!type.isRef()) {
6874-
throwError("Non-ref type for a call_ref: " + type.toString());
6875-
}
6876-
heapType = type.getHeapType();
6854+
HeapType heapType = getTypeByIndex(getU32LEB());
6855+
if (!Type::isSubType(curr->target->type, Type(heapType, Nullable))) {
6856+
throwError("Call target has invalid type: " +
6857+
curr->target->type.toString());
68776858
}
68786859
if (!heapType.isSignature()) {
68796860
throwError("Invalid reference type for a call_ref: " + heapType.toString());

src/wasm/wasm-s-parser.cpp

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,35 +2744,20 @@ Expression* SExpressionWasmBuilder::makeTupleExtract(Element& s) {
27442744
}
27452745

27462746
Expression* SExpressionWasmBuilder::makeCallRef(Element& s, bool isReturn) {
2747-
Index operandsStart = 1;
2748-
std::optional<HeapType> sigType;
2749-
try {
2750-
sigType = parseHeapType(*s[1]);
2751-
operandsStart = 2;
2752-
} catch (ParseException& p) {
2753-
// The type annotation is required for return_call_ref but temporarily
2754-
// optional for call_ref.
2755-
if (isReturn) {
2756-
throw;
2757-
}
2758-
}
2747+
HeapType sigType = parseHeapType(*s[1]);
27592748
std::vector<Expression*> operands;
2760-
parseOperands(s, operandsStart, s.size() - 1, operands);
2749+
parseOperands(s, 2, s.size() - 1, operands);
27612750
auto* target = parseExpression(s[s.size() - 1]);
27622751

2763-
if (sigType) {
2764-
if (!sigType->isSignature()) {
2765-
throw ParseException(
2766-
std::string(isReturn ? "return_call_ref" : "call_ref") +
2767-
" type annotation should be a signature",
2768-
s.line,
2769-
s.col);
2770-
}
2771-
return Builder(wasm).makeCallRef(
2772-
target, operands, sigType->getSignature().results, isReturn);
2752+
if (!sigType.isSignature()) {
2753+
throw ParseException(
2754+
std::string(isReturn ? "return_call_ref" : "call_ref") +
2755+
" type annotation should be a signature",
2756+
s.line,
2757+
s.col);
27732758
}
2774-
return ValidatingBuilder(wasm, s.line, s.col)
2775-
.validateAndMakeCallRef(target, operands, isReturn);
2759+
return Builder(wasm).makeCallRef(
2760+
target, operands, sigType.getSignature().results, isReturn);
27762761
}
27772762

27782763
Expression* SExpressionWasmBuilder::makeI31New(Element& s) {

test/lit/passes/dae_all-features.wast

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@
510510
;; CHECK-NEXT: )
511511
;; CHECK-NEXT: )
512512
(func $1
513-
(call_ref
513+
(call_ref $i64
514514
(i64.const 0)
515515
(global.get $global$0)
516516
)

test/lit/passes/gufa-refs.wast

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@
888888
(local.get $y)
889889
)
890890
;; Send a non-null value only to the second param.
891-
(call_ref
891+
(call_ref $two-params
892892
(ref.as_non_null
893893
(ref.null $struct)
894894
)
@@ -4592,19 +4592,19 @@
45924592
;; Call $i1 twice with the same value, and $i2 twice with different values.
45934593
;; Note that structurally the types are identical, but we still
45944594
;; differentiate them, allowing us to optimize.
4595-
(call_ref
4595+
(call_ref $i1
45964596
(i32.const 42)
45974597
(ref.func $reffed1)
45984598
)
4599-
(call_ref
4599+
(call_ref $i1
46004600
(i32.const 42)
46014601
(ref.func $reffed1)
46024602
)
4603-
(call_ref
4603+
(call_ref $i2
46044604
(i32.const 1337)
46054605
(ref.func $reffed2)
46064606
)
4607-
(call_ref
4607+
(call_ref $i2
46084608
(i32.const 99999)
46094609
(ref.func $reffed2)
46104610
)

test/lit/passes/inlining-optimizing.wast

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
;; unreachable.)
3333
(call $0)
3434
(drop
35-
(call_ref
35+
(call_ref $none_=>_i32
3636
(ref.cast_static $none_=>_i32
3737
(ref.func $0)
3838
)

test/lit/passes/local-cse_all-features.wast

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@
44
;; RUN: foreach %s %t wasm-opt --local-cse --all-features -S -o - | filecheck %s
55

66
(module
7-
;; CHECK: (type $i32_=>_i32 (func (param i32) (result i32)))
7+
;; CHECK: (type $f (func (param i32) (result i32)))
8+
(type $f (func (param i32) (result i32)))
89

910
;; CHECK: (type $none_=>_none (func))
1011

1112
;; CHECK: (elem declare func $calls $ref.func)
1213

1314
;; CHECK: (func $calls (param $x i32) (result i32)
1415
;; CHECK-NEXT: (drop
15-
;; CHECK-NEXT: (call_ref $i32_=>_i32
16+
;; CHECK-NEXT: (call_ref $f
1617
;; CHECK-NEXT: (i32.const 10)
1718
;; CHECK-NEXT: (ref.func $calls)
1819
;; CHECK-NEXT: )
1920
;; CHECK-NEXT: )
2021
;; CHECK-NEXT: (drop
21-
;; CHECK-NEXT: (call_ref $i32_=>_i32
22+
;; CHECK-NEXT: (call_ref $f
2223
;; CHECK-NEXT: (i32.const 10)
2324
;; CHECK-NEXT: (ref.func $calls)
2425
;; CHECK-NEXT: )
@@ -28,10 +29,10 @@
2829
(func $calls (param $x i32) (result i32)
2930
;; The side effects of calls prevent optimization.
3031
(drop
31-
(call_ref (i32.const 10) (ref.func $calls))
32+
(call_ref $f (i32.const 10) (ref.func $calls))
3233
)
3334
(drop
34-
(call_ref (i32.const 10) (ref.func $calls))
35+
(call_ref $f (i32.const 10) (ref.func $calls))
3536
)
3637
(i32.const 20)
3738
)

test/lit/passes/optimize-instructions-call_ref-roundtrip.wast

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,17 @@
6969
(func $call-table-get (param $x i32)
7070
;; The heap type of the call_indirects that we emit here should be the
7171
;; identical one as on the table that they correspond to.
72-
(call_ref
72+
(call_ref $v1
7373
(table.get $table-1
7474
(local.get $x)
7575
)
7676
)
77-
(call_ref
77+
(call_ref $v2
7878
(table.get $table-2
7979
(local.get $x)
8080
)
8181
)
82-
(call_ref
82+
(call_ref $v3
8383
(table.get $table-3
8484
(local.get $x)
8585
)

test/lit/passes/optimize-instructions-call_ref.wast

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
;; CHECK-NEXT: )
5252
(func $call_ref-to-direct (param $x i32) (param $y i32)
5353
;; This call_ref should become a direct call.
54-
(call_ref
54+
(call_ref $i32_i32_=>_none
5555
(local.get $x)
5656
(local.get $y)
5757
(ref.func $foo)
@@ -84,7 +84,7 @@
8484
;; This call_ref should become a direct call, even though it doesn't have a
8585
;; simple ref.func as the target - we need to look into the fallthrough, and
8686
;; handle things with locals.
87-
(call_ref
87+
(call_ref $i32_i32_=>_none
8888
;; Write to $x before the block, and write to it in the block; we should not
8989
;; reorder these things as the side effects could alter what value appears
9090
;; in the get of $x. (There is a risk of reordering here if we naively moved
@@ -116,7 +116,7 @@
116116
(func $fallthrough-no-params (result i32)
117117
;; A fallthrough appears here, but there are no operands so this is easier to
118118
;; optimize: we can just drop the call_ref's target before the call.
119-
(call_ref
119+
(call_ref $none_=>_i32
120120
(block (result (ref $none_=>_i32))
121121
(nop)
122122
(ref.func $fallthrough-no-params)
@@ -148,7 +148,7 @@
148148
;; nullable, which means we must be careful when we create a temp local for
149149
;; it: the local should be nullable, and gets of it should use a
150150
;; ref.as_non_null so that we validate.
151-
(call_ref
151+
(call_ref $data_=>_none
152152
(local.get $x)
153153
(block (result (ref $data_=>_none))
154154
(nop)
@@ -174,7 +174,7 @@
174174
;; emit non-validating code here, which would happen if we replace the
175175
;; call_ref that returns nothing with a call that returns an i32. In fact, we
176176
;; end up optimizing the cast into an unreachable.
177-
(call_ref
177+
(call_ref $none_=>_i32
178178
(ref.cast_static $none_=>_i32
179179
(ref.func $return-nothing)
180180
)
@@ -199,7 +199,7 @@
199199
;; CHECK-NEXT: )
200200
(func $fallthrough-unreachable
201201
;; If the call is not reached, do not optimize it.
202-
(call_ref
202+
(call_ref $i32_i32_=>_none
203203
(unreachable)
204204
(unreachable)
205205
(block (result (ref $i32_i32_=>_none))
@@ -210,14 +210,16 @@
210210
)
211211

212212
;; CHECK: (func $ignore-unreachable
213-
;; CHECK-NEXT: (drop
213+
;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit)
214+
;; CHECK-NEXT: (drop
215+
;; CHECK-NEXT: (unreachable)
216+
;; CHECK-NEXT: )
214217
;; CHECK-NEXT: (unreachable)
215218
;; CHECK-NEXT: )
216-
;; CHECK-NEXT: (unreachable)
217219
;; CHECK-NEXT: )
218220
(func $ignore-unreachable
219221
;; Ignore an unreachable call_ref target entirely.
220-
(call_ref
222+
(call_ref $i32_i32_=>_none
221223
(unreachable)
222224
)
223225
)
@@ -230,7 +232,7 @@
230232
;; CHECK-NEXT: )
231233
;; CHECK-NEXT: )
232234
(func $call-table-get (param $x i32)
233-
(call_ref
235+
(call_ref $i32_i32_=>_none
234236
(i32.const 1)
235237
(i32.const 2)
236238
(table.get $table-1
@@ -273,7 +275,7 @@
273275
;; CHECK-NEXT: )
274276
(func $call_ref-to-select (param $x i32) (param $y i32) (param $z i32) (param $f (ref $i32_i32_=>_none))
275277
;; This call_ref should become an if over two direct calls.
276-
(call_ref
278+
(call_ref $i32_i32_=>_none
277279
(local.get $x)
278280
(local.get $y)
279281
(select
@@ -284,7 +286,7 @@
284286
)
285287

286288
;; But here one arm is not constant, so we do not optimize.
287-
(call_ref
289+
(call_ref $i32_i32_=>_none
288290
(local.get $x)
289291
(local.get $y)
290292
(select

test/lit/passes/precompute-gc.wast

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1198,7 +1198,7 @@
11981198
(drop
11991199
;; Read from the local, checking whether precompute set a value there (it
12001200
;; should not, as the cast fails).
1201-
(call_ref
1201+
(call_ref $func-return-i32
12021202
(local.get $temp)
12031203
)
12041204
)

0 commit comments

Comments
 (0)