[wasm_builder] Add new exception handling instructions

These instructions are not used yet as they're not enabled by default
in Chrome yet.

This CL is mainly tested by the child CL, which uses instructions added
in this CL for exception handling.

Change-Id: I04d767599f47cdb6abc9cca02974647d9e5421fb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/411581
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Ömer Ağacan
2025-03-20 10:10:22 -07:00
committed by Commit Queue
parent 889c6709e0
commit c0e8f907e3
8 changed files with 303 additions and 19 deletions
+2 -2
View File
@@ -275,13 +275,13 @@ class AsyncStateMachineCodeGenerator extends StateMachineCodeGenerator {
}
// Handle Dart exceptions.
b.catch_(translator.getExceptionTag(b.module));
b.catch_legacy(translator.getExceptionTag(b.module));
b.local_set(stackTraceLocal);
b.local_set(exceptionLocal);
callCompleteError();
// Handle JS exceptions.
b.catch_all();
b.catch_all_legacy();
// Create a generic JavaScript error.
call(translator.javaScriptErrorFactory.reference);
+4 -4
View File
@@ -978,7 +978,7 @@ abstract class AstCodeGenerator
// Insert a catch instruction which will catch any thrown Dart
// exceptions.
b.catch_(translator.getExceptionTag(b.module));
b.catch_legacy(translator.getExceptionTag(b.module));
b.local_set(thrownStackTrace);
b.local_set(thrownException);
@@ -1007,7 +1007,7 @@ abstract class AstCodeGenerator
.any((c) => guardCanMatchJSException(translator, c.guard))) {
// This catches any objects that aren't dart exceptions, such as
// JavaScript exceptions or objects.
b.catch_all();
b.catch_all_legacy();
// We can't inspect the thrown object in a catch_all and get a stack
// trace, so we just attach the current stack trace.
@@ -1122,12 +1122,12 @@ abstract class AstCodeGenerator
}
// Handle Dart exceptions.
b.catch_(translator.getExceptionTag(b.module));
b.catch_legacy(translator.getExceptionTag(b.module));
translateStatement(node.finalizer);
b.rethrow_(tryBlock);
// Handle JS exceptions.
b.catch_all();
b.catch_all_legacy();
translateStatement(node.finalizer);
b.rethrow_(tryBlock);
+2 -2
View File
@@ -358,7 +358,7 @@ class ExceptionHandlerStack {
codeGen._jumpToTarget(_handlers[nextHandlerIdx].target);
}
b.catch_(codeGen.translator.getExceptionTag(b.module));
b.catch_legacy(codeGen.translator.getExceptionTag(b.module));
b.local_set(stackTraceLocal);
b.local_set(exceptionLocal);
@@ -375,7 +375,7 @@ class ExceptionHandlerStack {
}
if (canHandleJSExceptions) {
b.catch_all();
b.catch_all_legacy();
// We can't inspect the thrown object in a `catch_all` and get a stack
// trace, so we just attach the current stack trace.
+10 -1
View File
@@ -17,7 +17,16 @@ export 'tables.dart' show TablesBuilder;
export 'table.dart' show TableBuilder;
export 'tags.dart' show TagsBuilder;
export 'types.dart' show TypesBuilder;
export 'instructions.dart' show InstructionsBuilder, Label, ValidationError;
export 'instructions.dart'
show
InstructionsBuilder,
Label,
ValidationError,
TryTableCatch,
Catch,
CatchAll,
CatchRef,
CatchAllRef;
mixin Builder<T> {
T? _built;
@@ -91,6 +91,72 @@ class Try extends Label {
List<ir.ValueType> get targetTypes => outputs;
}
class TryTable extends Label {
final List<TryTableCatch> catches;
TryTable(super.inputs, super.outputs, this.catches) : super._();
@override
List<ir.ValueType> get targetTypes => outputs;
}
abstract class TryTableCatch {
final Label label;
TryTableCatch(this.label);
ir.TryTableCatch toIr(int labelIndex);
/// Values that the catch block catches, i.e. pushes as outputs.
List<ir.ValueType> caughtValues();
}
class Catch extends TryTableCatch {
final ir.Tag tag;
Catch(this.tag, super.label);
@override
ir.TryTableCatch toIr(int labelIndex) => ir.Catch(tag, labelIndex);
@override
List<ir.ValueType> caughtValues() => tag.type.inputs;
}
class CatchRef extends TryTableCatch {
final ir.Tag tag;
CatchRef(this.tag, super.label);
@override
ir.TryTableCatch toIr(int labelIndex) => ir.CatchRef(tag, labelIndex);
@override
List<ir.ValueType> caughtValues() =>
<ir.ValueType>[...tag.type.inputs, ir.RefType.exn(nullable: false)];
}
class CatchAll extends TryTableCatch {
CatchAll(super.label);
@override
ir.TryTableCatch toIr(int labelIndex) => ir.CatchAll(labelIndex);
@override
List<ir.ValueType> caughtValues() => <ir.ValueType>[];
}
class CatchAllRef extends TryTableCatch {
CatchAllRef(super.label);
@override
ir.TryTableCatch toIr(int labelIndex) => ir.CatchAllRef(labelIndex);
@override
List<ir.ValueType> caughtValues() =>
<ir.ValueType>[ir.RefType.exn(nullable: false)];
}
/// A sequence of Wasm instructions.
///
/// Instructions can be added to the sequence by calling the corresponding
@@ -545,8 +611,8 @@ class InstructionsBuilder with Builder<ir.Instructions> {
ir.BeginOneOutputTry.new,
ir.BeginFunctionTry.new);
/// Emit a `catch` instruction.
void catch_(ir.Tag tag) {
/// Emit a legacy `catch` instruction.
void catch_legacy(ir.Tag tag) {
assert(_topOfLabelStack is Try ||
_reportError("Unexpected 'catch' (not in 'try' block)"));
final Try try_ = _topOfLabelStack as Try;
@@ -555,10 +621,10 @@ class InstructionsBuilder with Builder<ir.Instructions> {
assert(tag.enclosingModule == module);
try_.hasCatch = true;
_reachable = try_.reachable;
_add(ir.Catch(tag));
_add(ir.CatchLegacy(tag));
}
void catch_all() {
void catch_all_legacy() {
assert(_topOfLabelStack is Try ||
_reportError("Unexpected 'catch_all' (not in 'try' block)"));
final Try try_ = _topOfLabelStack as Try;
@@ -566,7 +632,7 @@ class InstructionsBuilder with Builder<ir.Instructions> {
trace: ['catch_all'], reachableAfter: try_.reachable, reindent: true));
try_.hasCatch = true;
_reachable = try_.reachable;
_add(const ir.CatchAll());
_add(const ir.CatchAllLegacy());
}
/// Emit a `throw` instruction.
@@ -585,6 +651,12 @@ class InstructionsBuilder with Builder<ir.Instructions> {
_reachable = false;
}
/// Emit a `throw_ref` instruction.
void throw_ref() {
_add(ir.ThrowRef());
_reachable = false;
}
/// Emit an `end` instruction.
void end() {
assert(_verifyEndOfBlock(_topOfLabelStack.outputs,
@@ -632,6 +704,26 @@ class InstructionsBuilder with Builder<ir.Instructions> {
_reachable = false;
}
/// Emit a `try_table` instruction.
Label try_table(List<TryTableCatch> catches,
[List<ir.ValueType> inputs = const [],
List<ir.ValueType> outputs = const []]) {
// Validation: blocks in the table should have the outputs based on the
// types of exceptions they catch.
for (TryTableCatch catch_ in catches) {
assert(_verifyBranchTypes(catch_.label, 0, catch_.caughtValues()));
}
final List<ir.TryTableCatch> irCatches =
catches.map((c) => c.toIr(_labelIndex(c.label))).toList();
final label = _pushLabel(TryTable(inputs, outputs, catches),
trace: const ['try_table']);
return _beginBlock(
label,
() => ir.BeginNoEffectTryTable(irCatches),
(ty) => ir.BeginOneOutputTryTable(ty, irCatches),
(ty) => ir.BeginFunctionTryTable(ty, irCatches));
}
/// Emit a `return` instruction.
void return_() {
assert(_verifyTypes(_labelStack[0].outputs, const [],
+123 -4
View File
@@ -199,10 +199,10 @@ class BeginFunctionTry extends Instruction {
}
}
class Catch extends Instruction {
class CatchLegacy extends Instruction {
final Tag tag;
Catch(this.tag);
CatchLegacy(this.tag);
@override
void serialize(Serializer s) {
@@ -211,8 +211,8 @@ class Catch extends Instruction {
}
}
class CatchAll extends SingleByteInstruction {
const CatchAll() : super(0x19);
class CatchAllLegacy extends SingleByteInstruction {
const CatchAllLegacy() : super(0x19);
}
class Throw extends Instruction {
@@ -227,6 +227,15 @@ class Throw extends Instruction {
}
}
class ThrowRef extends Instruction {
const ThrowRef();
@override
void serialize(Serializer s) {
s.writeByte(0x0a);
}
}
class Rethrow extends Instruction {
final int labelIndex;
@@ -1757,3 +1766,113 @@ class I64TruncSatF64U extends Instruction {
s.writeByte(0x07);
}
}
class BeginNoEffectTryTable extends Instruction {
final List<TryTableCatch> catches;
BeginNoEffectTryTable(this.catches);
@override
void serialize(Serializer s) {
s.writeByte(0x1F);
s.writeByte(0x40);
s.writeUnsigned(catches.length);
for (final catch_ in catches) {
catch_.serialize(s);
}
}
}
class BeginOneOutputTryTable extends Instruction {
final ValueType type;
final List<TryTableCatch> catches;
BeginOneOutputTryTable(this.type, this.catches);
@override
List<ValueType> get usedValueTypes => [type];
@override
void serialize(Serializer s) {
s.writeByte(0x1F);
s.write(type);
s.writeUnsigned(catches.length);
for (final catch_ in catches) {
catch_.serialize(s);
}
}
}
class BeginFunctionTryTable extends Instruction {
final FunctionType type;
final List<TryTableCatch> catches;
BeginFunctionTryTable(this.type, this.catches);
@override
List<DefType> get usedDefTypes => [type];
@override
void serialize(Serializer s) {
s.writeByte(0x1F);
s.write(type);
s.writeUnsigned(catches.length);
for (final catch_ in catches) {
catch_.serialize(s);
}
}
}
abstract class TryTableCatch {
final int labelIndex;
TryTableCatch(this.labelIndex);
void serialize(Serializer s);
}
class Catch extends TryTableCatch {
final Tag tag;
Catch(this.tag, super.labelIndex);
@override
void serialize(Serializer s) {
s.writeByte(0x00);
s.writeUnsigned(tag.index);
s.writeUnsigned(labelIndex);
}
}
class CatchRef extends TryTableCatch {
final Tag tag;
CatchRef(this.tag, super.labelIndex);
@override
void serialize(Serializer s) {
s.writeByte(0x01);
s.writeUnsigned(tag.index);
s.writeUnsigned(labelIndex);
}
}
class CatchAll extends TryTableCatch {
CatchAll(super.labelIndex);
@override
void serialize(Serializer s) {
s.writeByte(0x02);
s.writeUnsigned(labelIndex);
}
}
class CatchAllRef extends TryTableCatch {
CatchAllRef(super.labelIndex);
@override
void serialize(Serializer s) {
s.writeByte(0x03);
s.writeUnsigned(labelIndex);
}
}
+63
View File
@@ -191,6 +191,9 @@ class RefType extends ValueType {
const RefType.nofunc({required bool nullable})
: this._(HeapType.nofunc, nullable);
/// A (possibly nullable) reference to the `exn` heap type.
const RefType.exn({required bool nullable}) : this._(HeapType.exn, nullable);
/// A (possibly nullable) reference to a custom heap type.
RefType.def(DefType defType, {required bool nullable})
: this(defType, nullable: nullable);
@@ -278,6 +281,12 @@ abstract class HeapType implements Serializable {
/// The `nofunc` heap type.
static const nofunc = NoFuncHeapType._();
/// The `exn` heap type.
static const exn = ExnHeapType._();
/// The `noexn` heap type.
static const noexn = NoExnHeapType._();
/// Whether this heap type is nullable by default, i.e. when written with the
/// -`ref` shorthand. A `null` value here means the heap type has no default
/// nullability, so the nullability of a reference has to be specified
@@ -661,6 +670,60 @@ abstract class DefType extends HeapType {
void serializeDefinitionInner(Serializer s);
}
/// The `exn` heap type.
class ExnHeapType extends HeapType {
const ExnHeapType._();
static const defaultNullability = true;
@override
bool? get nullableByDefault => defaultNullability;
@override
HeapType get topType => HeapType.exn;
@override
HeapType get bottomType => HeapType.noexn;
@override
bool isSubtypeOf(HeapType other) =>
other == HeapType.common || other == HeapType.exn;
@override
void serialize(Serializer s) => s.writeByte(0x69); // -0x17
@override
String toString() => "exn";
}
/// The `noexn` heap type.
class NoExnHeapType extends HeapType {
const NoExnHeapType._();
static const defaultNullability = true;
@override
bool? get nullableByDefault => defaultNullability;
@override
HeapType get topType => HeapType.exn;
@override
HeapType get bottomType => HeapType.noexn;
@override
bool isSubtypeOf(HeapType other) =>
other == HeapType.common ||
other == HeapType.exn ||
other == HeapType.noexn;
@override
void serialize(Serializer s) => s.writeByte(0x74); // -0x0c
@override
String toString() => "noexn";
}
/// A custom function type.
class FunctionType extends DefType {
final List<ValueType> inputs;
+2 -1
View File
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
export 'src/ir/ir.dart';
export 'src/ir/ir.dart'
hide Catch, CatchAll, CatchRef, CatchAllRef, TryTableCatch;
export 'src/builder/builder.dart';
export 'src/serialize/serialize.dart';