From c0e8f907e339ee6a055720ccc612d7d899bfd3f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Thu, 20 Mar 2025 10:10:22 -0700 Subject: [PATCH] [wasm_builder] Add new exception handling instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Martin Kustermann --- pkg/dart2wasm/lib/async.dart | 4 +- pkg/dart2wasm/lib/code_generator.dart | 8 +- pkg/dart2wasm/lib/state_machine.dart | 4 +- pkg/wasm_builder/lib/src/builder/builder.dart | 11 +- .../lib/src/builder/instructions.dart | 102 +++++++++++++- pkg/wasm_builder/lib/src/ir/instruction.dart | 127 +++++++++++++++++- pkg/wasm_builder/lib/src/ir/type.dart | 63 +++++++++ pkg/wasm_builder/lib/wasm_builder.dart | 3 +- 8 files changed, 303 insertions(+), 19 deletions(-) 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';