[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>
diff --git a/pkg/dart2wasm/lib/async.dart b/pkg/dart2wasm/lib/async.dart
index bbcf089..9e376be 100644
--- a/pkg/dart2wasm/lib/async.dart
+++ b/pkg/dart2wasm/lib/async.dart
@@ -275,13 +275,13 @@
     }
 
     // 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 1a0bd0b..2906b00 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -978,7 +978,7 @@
 
     // 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 @@
         .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 @@
     }
 
     // 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 15af914..39202de 100644
--- a/pkg/dart2wasm/lib/state_machine.dart
+++ b/pkg/dart2wasm/lib/state_machine.dart
@@ -358,7 +358,7 @@
         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 @@
       }
 
       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 c2e0249..6a380e1 100644
--- a/pkg/wasm_builder/lib/src/builder/builder.dart
+++ b/pkg/wasm_builder/lib/src/builder/builder.dart
@@ -17,7 +17,16 @@
 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;
diff --git a/pkg/wasm_builder/lib/src/builder/instructions.dart b/pkg/wasm_builder/lib/src/builder/instructions.dart
index ea73a24..bf36f42 100644
--- a/pkg/wasm_builder/lib/src/builder/instructions.dart
+++ b/pkg/wasm_builder/lib/src/builder/instructions.dart
@@ -91,6 +91,72 @@
   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 @@
           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 @@
     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 @@
         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 @@
     _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 @@
     _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 [],
diff --git a/pkg/wasm_builder/lib/src/ir/instruction.dart b/pkg/wasm_builder/lib/src/ir/instruction.dart
index fd48d5a..712730e 100644
--- a/pkg/wasm_builder/lib/src/ir/instruction.dart
+++ b/pkg/wasm_builder/lib/src/ir/instruction.dart
@@ -199,10 +199,10 @@
   }
 }
 
-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 CatchAll extends SingleByteInstruction {
-  const CatchAll() : super(0x19);
+class CatchAllLegacy extends SingleByteInstruction {
+  const CatchAllLegacy() : super(0x19);
 }
 
 class Throw extends Instruction {
@@ -227,6 +227,15 @@
   }
 }
 
+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 @@
     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);
+  }
+}
diff --git a/pkg/wasm_builder/lib/src/ir/type.dart b/pkg/wasm_builder/lib/src/ir/type.dart
index 2e3a890..0859e61 100644
--- a/pkg/wasm_builder/lib/src/ir/type.dart
+++ b/pkg/wasm_builder/lib/src/ir/type.dart
@@ -191,6 +191,9 @@
   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 @@
   /// 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 @@
   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;
diff --git a/pkg/wasm_builder/lib/wasm_builder.dart b/pkg/wasm_builder/lib/wasm_builder.dart
index 24af094..80ee196 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';