[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:
committed by
Commit Queue
parent
889c6709e0
commit
c0e8f907e3
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 [],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
Reference in New Issue
Block a user