Skip to content

Commit da7ed11

Browse files
authored
[custom-descriptors] Branching descriptor casts (#7622)
Implement basic support for `br_on_cast_desc` and `br_on_cast_desc_fail` as new variants of `BrOn`. Include binary and text parsing and printing as well as validation. Also handle the new operations anywhere the compiler would otherwise complain about a non-exhaustive switch over the `BrCastOp` enum. For validation of type immediates during parsing, relax the requirement that the cast type is a subtype of the input type. Continue validating in the IR that the non-descriptor branching casts are downcasts, but do not validate this for the new descriptor casts. See WebAssembly/custom-descriptors#37 for context. Add new validation and parsing tests in the form of spec tests in preparation for creating an upstream spec test suite. While we're at it, move the ref.get_desc tests to spec tests, add a few test cases, and fix a bug when the ref.get_desc operand is null.
1 parent 0ac7afe commit da7ed11

24 files changed

+875
-186
lines changed

scripts/gen-s-parser.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,8 +613,10 @@
613613
("ref.get_desc", "makeRefGetDesc()"),
614614
("br_on_null", "makeBrOnNull()"),
615615
("br_on_non_null", "makeBrOnNull(true)"),
616-
("br_on_cast", "makeBrOnCast()"),
617-
("br_on_cast_fail", "makeBrOnCast(true)"),
616+
("br_on_cast", "makeBrOnCast(BrOnCast)"),
617+
("br_on_cast_fail", "makeBrOnCast(BrOnCastFail)"),
618+
("br_on_cast_desc", "makeBrOnCast(BrOnCastDesc)"),
619+
("br_on_cast_desc_fail", "makeBrOnCast(BrOnCastDescFail)"),
618620
("struct.new", "makeStructNew(false)"),
619621
("struct.new_default", "makeStructNew(true)"),
620622
("struct.get", "makeStructGet()"),

scripts/test/fuzzing.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@
113113
'vacuum-stack-switching.wast',
114114
# TODO: fuzzer support for custom descriptors
115115
'custom-descriptors.wast',
116+
'br_on_cast_desc.wast',
117+
'ref.get_cast.wast',
116118
# TODO: fix split_wast() on tricky escaping situations like a string ending
117119
# in \\" (the " is not escaped - there is an escaped \ before it)
118120
'string-lifting-section.wast',

src/gen-s-parser.inc

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,16 +209,38 @@ switch (buf[0]) {
209209
switch (buf[10]) {
210210
case '\0':
211211
if (op == "br_on_cast"sv) {
212-
CHECK_ERR(makeBrOnCast(ctx, pos, annotations));
212+
CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCast));
213213
return Ok{};
214214
}
215215
goto parse_error;
216-
case '_':
217-
if (op == "br_on_cast_fail"sv) {
218-
CHECK_ERR(makeBrOnCast(ctx, pos, annotations, true));
219-
return Ok{};
216+
case '_': {
217+
switch (buf[11]) {
218+
case 'd': {
219+
switch (buf[15]) {
220+
case '\0':
221+
if (op == "br_on_cast_desc"sv) {
222+
CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCastDesc));
223+
return Ok{};
224+
}
225+
goto parse_error;
226+
case '_':
227+
if (op == "br_on_cast_desc_fail"sv) {
228+
CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCastDescFail));
229+
return Ok{};
230+
}
231+
goto parse_error;
232+
default: goto parse_error;
233+
}
234+
}
235+
case 'f':
236+
if (op == "br_on_cast_fail"sv) {
237+
CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCastFail));
238+
return Ok{};
239+
}
240+
goto parse_error;
241+
default: goto parse_error;
220242
}
221-
goto parse_error;
243+
}
222244
default: goto parse_error;
223245
}
224246
}

src/ir/ReFinalize.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ void ReFinalize::visitRefGetDesc(RefGetDesc* curr) { curr->finalize(); }
151151
void ReFinalize::visitBrOn(BrOn* curr) {
152152
curr->finalize();
153153
if (curr->type == Type::unreachable) {
154-
replaceUntaken(curr->ref, nullptr);
154+
replaceUntaken(curr->ref, curr->desc);
155155
} else {
156156
updateBreakValueType(curr->name, curr->getSentType());
157157
}
@@ -218,11 +218,11 @@ void ReFinalize::updateBreakValueType(Name name, Type type) {
218218
}
219219

220220
// Replace an untaken branch/switch with an unreachable value.
221-
// A condition may also exist and may or may not be unreachable.
222-
void ReFinalize::replaceUntaken(Expression* value, Expression* condition) {
221+
// Another child may also exist and may or may not be unreachable.
222+
void ReFinalize::replaceUntaken(Expression* value, Expression* otherChild) {
223223
assert(value->type == Type::unreachable);
224224
auto* replacement = value;
225-
if (condition) {
225+
if (otherChild) {
226226
Builder builder(*getModule());
227227
// Even if we have
228228
// (block
@@ -233,10 +233,10 @@ void ReFinalize::replaceUntaken(Expression* value, Expression* condition) {
233233
// the value is unreachable, and necessary since the type of
234234
// the condition did not have an impact before (the break/switch
235235
// type was unreachable), and might not fit in.
236-
if (condition->type.isConcrete()) {
237-
condition = builder.makeDrop(condition);
236+
if (otherChild->type.isConcrete()) {
237+
otherChild = builder.makeDrop(otherChild);
238238
}
239-
replacement = builder.makeSequence(value, condition);
239+
replacement = builder.makeSequence(value, otherChild);
240240
assert(replacement->type.isBasic() && "Basic type expected");
241241
}
242242
replaceCurrent(replacement);

src/ir/child-typer.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -865,16 +865,25 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
865865
note(&curr->ref, Type(*ht, Nullable));
866866
}
867867

868-
void visitBrOn(BrOn* curr) {
868+
void visitBrOn(BrOn* curr, std::optional<Type> target = std::nullopt) {
869869
switch (curr->op) {
870870
case BrOnNull:
871871
case BrOnNonNull:
872872
noteAnyReference(&curr->ref);
873873
return;
874874
case BrOnCast:
875-
case BrOnCastFail: {
876-
auto top = curr->castType.getHeapType().getTop();
875+
case BrOnCastFail:
876+
case BrOnCastDesc:
877+
case BrOnCastDescFail: {
878+
if (!target) {
879+
target = curr->castType;
880+
}
881+
auto top = target->getHeapType().getTop();
877882
note(&curr->ref, Type(top, Nullable));
883+
if (curr->op == BrOnCastDesc || curr->op == BrOnCastDescFail) {
884+
auto descriptor = *target->getHeapType().getDescriptorType();
885+
note(&curr->desc, Type(descriptor, Nullable));
886+
}
878887
return;
879888
}
880889
}

src/ir/cost.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -673,9 +673,20 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
673673
}
674674
CostType visitBrOn(BrOn* curr) {
675675
// BrOn of a null can be fairly fast, but anything else is a cast check.
676-
CostType base =
677-
curr->op == BrOnNull || curr->op == BrOnNonNull ? 2 : CastCost;
678-
return base + nullCheckCost(curr->ref) + maybeVisit(curr->ref);
676+
switch (curr->op) {
677+
case BrOnNull:
678+
case BrOnNonNull:
679+
return 2 + nullCheckCost(curr->ref) + visit(curr->ref);
680+
case BrOnCast:
681+
case BrOnCastFail:
682+
return CastCost + visit(curr->ref);
683+
case BrOnCastDesc:
684+
case BrOnCastDescFail:
685+
// These are not as expensive as full casts, since they just do a
686+
// identity check on the descriptor.
687+
return 2 + visit(curr->ref) + visit(curr->desc);
688+
}
689+
WASM_UNREACHABLE("unexpected op");
679690
}
680691
CostType visitStructNew(StructNew* curr) {
681692
CostType ret = AllocationCost + curr->operands.size();

src/ir/properties.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ inline bool hasUnwritableTypeImmediate(Expression* curr) {
521521
#define DELEGATE_ID curr->_id
522522

523523
#define DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(id, field) \
524-
{ \
524+
if (curr->cast<id>()->field) { \
525525
auto type = curr->cast<id>()->field->type; \
526526
if (type == Type::unreachable || type.isNull()) { \
527527
return true; \

src/parser/parsers.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,7 @@ template<typename Ctx>
238238
Result<>
239239
makeBrOnNull(Ctx&, Index, const std::vector<Annotation>&, bool onFail = false);
240240
template<typename Ctx>
241-
Result<>
242-
makeBrOnCast(Ctx&, Index, const std::vector<Annotation>&, bool onFail = false);
241+
Result<> makeBrOnCast(Ctx&, Index, const std::vector<Annotation>&, BrOnOp op);
243242
template<typename Ctx>
244243
Result<>
245244
makeStructNew(Ctx&, Index, const std::vector<Annotation>&, bool default_);
@@ -2251,15 +2250,14 @@ template<typename Ctx>
22512250
Result<> makeBrOnCast(Ctx& ctx,
22522251
Index pos,
22532252
const std::vector<Annotation>& annotations,
2254-
bool onFail) {
2253+
BrOnOp op) {
22552254
auto label = labelidx(ctx);
22562255
CHECK_ERR(label);
22572256
auto in = reftype(ctx);
22582257
CHECK_ERR(in);
22592258
auto out = reftype(ctx);
22602259
CHECK_ERR(out);
2261-
return ctx.makeBrOn(
2262-
pos, annotations, *label, onFail ? BrOnCastFail : BrOnCast, *in, *out);
2260+
return ctx.makeBrOn(pos, annotations, *label, op, *in, *out);
22632261
}
22642262

22652263
template<typename Ctx>

src/passes/Print.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,7 +2226,26 @@ struct PrintExpressionContents
22262226
curr->name.print(o);
22272227
return;
22282228
case BrOnCast:
2229-
printMedium(o, "br_on_cast ");
2229+
case BrOnCastDesc:
2230+
case BrOnCastFail:
2231+
case BrOnCastDescFail:
2232+
switch (curr->op) {
2233+
case BrOnCast:
2234+
printMedium(o, "br_on_cast");
2235+
break;
2236+
case BrOnCastFail:
2237+
printMedium(o, "br_on_cast_fail");
2238+
break;
2239+
case BrOnCastDesc:
2240+
printMedium(o, "br_on_cast_desc");
2241+
break;
2242+
case BrOnCastDescFail:
2243+
printMedium(o, "br_on_cast_desc_fail");
2244+
break;
2245+
default:
2246+
WASM_UNREACHABLE("unexpected op");
2247+
}
2248+
o << ' ';
22302249
curr->name.print(o);
22312250
o << ' ';
22322251
if (curr->ref->type == Type::unreachable) {
@@ -2240,8 +2259,9 @@ struct PrintExpressionContents
22402259
o << ' ';
22412260
printType(curr->castType);
22422261
return;
2243-
case BrOnCastFail:
2244-
printMedium(o, "br_on_cast_fail ");
2262+
printMedium(o,
2263+
curr->op == BrOnCastFail ? "br_on_cast_fail "
2264+
: "br_on_cast_desc_fail ");
22452265
curr->name.print(o);
22462266
o << ' ';
22472267
if (curr->ref->type == Type::unreachable) {

src/wasm-binary.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,8 @@ enum ASTNodes {
11741174
RefCastNull = 0x17,
11751175
BrOnCast = 0x18,
11761176
BrOnCastFail = 0x19,
1177+
BrOnCastDesc = 0x25,
1178+
BrOnCastDescFail = 0x26,
11771179
AnyConvertExtern = 0x1a,
11781180
ExternConvertAny = 0x1b,
11791181
RefI31 = 0x1c,

0 commit comments

Comments
 (0)