diff --git a/pkg/dart2wasm/lib/async.dart b/pkg/dart2wasm/lib/async.dart index bbcf089d1cb..9e376be11c2 100644 --- a/pkg/dart2wasm/lib/async.dart +++ b/pkg/dart2wasm/lib/async.dart @@ -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); diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 1a0bd0b71cc..2906b008083 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -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); diff --git a/pkg/dart2wasm/lib/state_machine.dart b/pkg/dart2wasm/lib/state_machine.dart index 15af9144ff1..39202debfa5 100644 --- a/pkg/dart2wasm/lib/state_machine.dart +++ b/pkg/dart2wasm/lib/state_machine.dart @@ -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. diff --git a/pkg/wasm_builder/lib/src/builder/builder.dart b/pkg/wasm_builder/lib/src/builder/builder.dart index c2e024960e4..6a380e18e01 100644 --- a/pkg/wasm_builder/lib/src/builder/builder.dart +++ b/pkg/wasm_builder/lib/src/builder/builder.dart @@ -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? _built; diff --git a/pkg/wasm_builder/lib/src/builder/instructions.dart b/pkg/wasm_builder/lib/src/builder/instructions.dart index ea73a24dabd..bf36f42e1f5 100644 --- a/pkg/wasm_builder/lib/src/builder/instructions.dart +++ b/pkg/wasm_builder/lib/src/builder/instructions.dart @@ -91,6 +91,72 @@ class Try extends Label { List get targetTypes => outputs; } +class TryTable extends Label { + final List catches; + + TryTable(super.inputs, super.outputs, this.catches) : super._(); + + @override + List 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 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 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 caughtValues() => + [...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 caughtValues() => []; +} + +class CatchAllRef extends TryTableCatch { + CatchAllRef(super.label); + + @override + ir.TryTableCatch toIr(int labelIndex) => ir.CatchAllRef(labelIndex); + + @override + List caughtValues() => + [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.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 { 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 { 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 { _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 { _reachable = false; } + /// Emit a `try_table` instruction. + Label try_table(List catches, + [List inputs = const [], + List 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 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 [], diff --git a/pkg/wasm_builder/lib/src/ir/instruction.dart b/pkg/wasm_builder/lib/src/ir/instruction.dart index fd48d5a5300..712730ec88a 100644 --- a/pkg/wasm_builder/lib/src/ir/instruction.dart +++ b/pkg/wasm_builder/lib/src/ir/instruction.dart @@ -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 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 catches; + + BeginOneOutputTryTable(this.type, this.catches); + + @override + List 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 catches; + + BeginFunctionTryTable(this.type, this.catches); + + @override + List 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); + } +} diff --git a/pkg/wasm_builder/lib/src/ir/type.dart b/pkg/wasm_builder/lib/src/ir/type.dart index 2e3a8902915..0859e616840 100644 --- a/pkg/wasm_builder/lib/src/ir/type.dart +++ b/pkg/wasm_builder/lib/src/ir/type.dart @@ -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 inputs; diff --git a/pkg/wasm_builder/lib/wasm_builder.dart b/pkg/wasm_builder/lib/wasm_builder.dart index 24af094eb92..80ee196f088 100644 --- a/pkg/wasm_builder/lib/wasm_builder.dart +++ b/pkg/wasm_builder/lib/wasm_builder.dart @@ -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';