[vm/kernel/bytecode] Support try-catch-finally in bytecode generator

Change-Id: I2882546741b1dd8f6e03a21d85b5487fb754b742
Reviewed-on: https://dart-review.googlesource.com/55324
Reviewed-by: RĂ©gis Crelier <regis@google.com>
Reviewed-by: Zach Anderson <zra@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/pkg/vm/lib/bytecode/assembler.dart b/pkg/vm/lib/bytecode/assembler.dart
index ac3b157..fdf239e 100644
--- a/pkg/vm/lib/bytecode/assembler.dart
+++ b/pkg/vm/lib/bytecode/assembler.dart
@@ -7,6 +7,7 @@
 import 'dart:typed_data';
 
 import 'package:vm/bytecode/dbc.dart';
+import 'package:vm/bytecode/exceptions.dart' show ExceptionsTable;
 
 class Label {
   List<int> _jumps = <int>[];
@@ -42,6 +43,7 @@
   final List<int> bytecode = new List<int>();
   final Uint32List _encodeBufferIn;
   final Uint8List _encodeBufferOut;
+  final ExceptionsTable exceptionsTable = new ExceptionsTable();
 
   BytecodeAssembler._(this._encodeBufferIn, this._encodeBufferOut);
 
@@ -51,6 +53,7 @@
   }
 
   int get offset => bytecode.length;
+  int get offsetInWords => bytecode.length >> kLog2BytesPerBytecode;
 
   void bind(Label label) {
     final List<int> jumps = label.bind(offset);
@@ -819,8 +822,8 @@
     emitWord(_encode0(Opcode.kCloneContext));
   }
 
-  void emitMoveSpecial(int ra, int rd) {
-    emitWord(_encodeAD(Opcode.kMoveSpecial, ra, rd));
+  void emitMoveSpecial(int ra, SpecialIndex rd) {
+    emitWord(_encodeAD(Opcode.kMoveSpecial, ra, rd.index));
   }
 
   void emitInstantiateType(int rd) {
diff --git a/pkg/vm/lib/bytecode/dbc.dart b/pkg/vm/lib/bytecode/dbc.dart
index 9e3bb31..6f48e18 100644
--- a/pkg/vm/lib/bytecode/dbc.dart
+++ b/pkg/vm/lib/bytecode/dbc.dart
@@ -235,6 +235,7 @@
   reg, // register (unsigned FP relative local)
   xeg, // x-register (signed FP relative local)
   tgt, // jump target relative to the PC of the current instruction
+  spe, // SpecialIndex
 }
 
 class Format {
@@ -589,7 +590,7 @@
   Opcode.kCloneContext: const Format(
       Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
   Opcode.kMoveSpecial: const Format(
-      Encoding.kAD, const [Operand.reg, Operand.imm, Operand.none]),
+      Encoding.kAD, const [Operand.reg, Operand.spe, Operand.none]),
   Opcode.kInstantiateType: const Format(
       Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
   Opcode.kInstantiateTypeArgumentsTOS: const Format(
@@ -650,3 +651,8 @@
 // Prefix used to distinguish setters in ICData target names.
 // Should match constant in runtime/vm/object.cc.
 const String kSetterPrefix = 'set:';
+
+enum SpecialIndex {
+  exception,
+  stackTrace,
+}
diff --git a/pkg/vm/lib/bytecode/disassembler.dart b/pkg/vm/lib/bytecode/disassembler.dart
index 9230268..279d492 100644
--- a/pkg/vm/lib/bytecode/disassembler.dart
+++ b/pkg/vm/lib/bytecode/disassembler.dart
@@ -7,6 +7,7 @@
 import 'dart:typed_data';
 
 import 'package:vm/bytecode/dbc.dart';
+import 'package:vm/bytecode/exceptions.dart';
 
 class _Instruction {
   final Opcode opcode;
@@ -20,11 +21,13 @@
 
   List<_Instruction> _instructions;
   int _labelCount;
-  Map<int, int> _labels;
+  Map<int, String> _labels;
+  Map<int, List<String>> _markers;
 
-  String disassemble(List<int> bytecode) {
+  String disassemble(List<int> bytecode, ExceptionsTable exceptionsTable) {
     _init(bytecode);
     _scanForJumpTargets();
+    _markTryBlocks(exceptionsTable);
     return _disasm();
   }
 
@@ -39,7 +42,8 @@
     }
 
     _labelCount = 0;
-    _labels = <int, int>{};
+    _labels = <int, String>{};
+    _markers = <int, List<String>>{};
   }
 
   _Instruction _decodeInstruction(int word) {
@@ -94,17 +98,35 @@
       if (instr.opcode == Opcode.kJump) {
         final target = i + instr.operands[0];
         assert(0 <= target && target < _instructions.length);
-        _labels[target] ??= (++_labelCount);
+        if (!_labels.containsKey(target)) {
+          final label = 'L${++_labelCount}';
+          _labels[target] = label;
+          _addMarker(target, '$label:');
+        }
       }
     }
   }
 
+  void _markTryBlocks(ExceptionsTable exceptionsTable) {
+    for (var tryBlock in exceptionsTable.blocks) {
+      final int tryIndex = tryBlock.tryIndex;
+      _addMarker(tryBlock.startPC, 'Try #$tryIndex start:');
+      _addMarker(tryBlock.endPC, 'Try #$tryIndex end:');
+      _addMarker(tryBlock.handlerPC, 'Try #$tryIndex handler:');
+    }
+  }
+
+  void _addMarker(int pc, String marker) {
+    final markers = (_markers[pc] ??= <String>[]);
+    markers.add(marker);
+  }
+
   String _disasm() {
     StringBuffer out = new StringBuffer();
     for (int i = 0; i < _instructions.length; i++) {
-      int label = _labels[i];
-      if (label != null) {
-        out.writeln('L$label:');
+      List<String> markers = _markers[i];
+      if (markers != null) {
+        markers.forEach(out.writeln);
       }
       _writeInstruction(out, i, _instructions[i]);
     }
@@ -158,7 +180,11 @@
       case Operand.xeg:
         return (value < 0) ? 'FP[$value]' : 'r$value';
       case Operand.tgt:
-        return 'L${_labels[bci + value] ?? (throw 'Label not found')}';
+        return _labels[bci + value] ?? (throw 'Label not found');
+      case Operand.spe:
+        return SpecialIndex.values[value]
+            .toString()
+            .substring('SpecialIndex.'.length);
     }
     throw 'Unexpected operand format $fmt';
   }
diff --git a/pkg/vm/lib/bytecode/exceptions.dart b/pkg/vm/lib/bytecode/exceptions.dart
new file mode 100644
index 0000000..7c955a3
--- /dev/null
+++ b/pkg/vm/lib/bytecode/exceptions.dart
@@ -0,0 +1,139 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+library vm.bytecode.exceptions;
+
+import 'package:kernel/ast.dart' show BinarySink, BinarySource;
+
+/*
+
+In kernel binary, try blocks are encoded in the following way
+(using notation from pkg/kernel/binary.md):
+
+// Offset of a bytecode instruction, in DBC words.
+type BytecodeOffset = UInt;
+
+type TryBlock {
+  UInt outerTryIndexPlus1;
+  BytecodeOffset startPC; // Inclusive.
+  BytecodeOffset endPC; // Exclusive.
+  BytecodeOffset handlerPC;
+  Byte flags (needsStackTrace, isSynthetic);
+  List<ConstantIndex> types;
+}
+
+type ExceptionsTable {
+  // Ordered by startPC, then by nesting (outer precedes inner).
+  // Try blocks are properly nested. It means there are no partially
+  // overlapping try blocks - each pair of try block regions either
+  // has no intersection or one try block region encloses another.
+  List<TryBlock> tryBlocks;
+}
+
+*/
+
+class TryBlock {
+  static const int flagNeedsStackTrace = 1 << 0;
+  static const int flagIsSynthetic = 1 << 1;
+
+  final int tryIndex;
+  final int outerTryIndex;
+  final int startPC;
+  int endPC;
+  int handlerPC;
+  int flags = 0;
+  List<int> types = <int>[];
+
+  TryBlock._(this.tryIndex, this.outerTryIndex, this.startPC);
+
+  bool get needsStackTrace => (flags & flagNeedsStackTrace) != 0;
+
+  void set needsStackTrace(bool value) {
+    flags = (flags & ~flagNeedsStackTrace) | (value ? flagNeedsStackTrace : 0);
+  }
+
+  bool get isSynthetic => (flags & flagIsSynthetic) != 0;
+
+  void set isSynthetic(bool value) {
+    flags = (flags & ~flagIsSynthetic) | (value ? flagIsSynthetic : 0);
+  }
+
+  void writeToBinary(BinarySink sink) {
+    sink.writeUInt30(outerTryIndex + 1);
+    sink.writeUInt30(startPC);
+    sink.writeUInt30(endPC);
+    sink.writeUInt30(handlerPC);
+    sink.writeByte(flags);
+    sink.writeUInt30(types.length);
+    types.forEach(sink.writeUInt30);
+  }
+
+  factory TryBlock.readFromBinary(BinarySource source, int tryIndex) {
+    final outerTryIndex = source.readUInt() - 1;
+    final startPC = source.readUInt();
+    final tryBlock = new TryBlock._(tryIndex, outerTryIndex, startPC);
+
+    tryBlock.endPC = source.readUInt();
+    tryBlock.handlerPC = source.readUInt();
+    tryBlock.flags = source.readByte();
+    tryBlock.types =
+        new List<int>.generate(source.readUInt(), (_) => source.readUInt());
+
+    return tryBlock;
+  }
+
+  @override
+  String toString() => 'try-index $tryIndex, outer $outerTryIndex, '
+      'start $startPC, end $endPC, handler $handlerPC, '
+      '${needsStackTrace ? 'needs-stack-trace, ' : ''}'
+      '${isSynthetic ? 'synthetic, ' : ''}'
+      'types ${types.map((t) => 'CP#$t').toList()}';
+}
+
+class ExceptionsTable {
+  List<TryBlock> blocks = <TryBlock>[];
+
+  ExceptionsTable();
+
+  TryBlock enterTryBlock(int startPC) {
+    assert(blocks.isEmpty || blocks.last.startPC <= startPC);
+    final tryBlock =
+        new TryBlock._(blocks.length, _outerTryBlockIndex(startPC), startPC);
+    blocks.add(tryBlock);
+    return tryBlock;
+  }
+
+  int _outerTryBlockIndex(int startPC) {
+    for (int i = blocks.length - 1; i >= 0; --i) {
+      final tryBlock = blocks[i];
+      if (tryBlock.endPC == null || tryBlock.endPC > startPC) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  void writeToBinary(BinarySink sink) {
+    sink.writeUInt30(blocks.length);
+    blocks.forEach((b) => b.writeToBinary(sink));
+  }
+
+  ExceptionsTable.readFromBinary(BinarySource source)
+      : blocks = new List<TryBlock>.generate(source.readUInt(),
+            (int index) => new TryBlock.readFromBinary(source, index));
+
+  @override
+  String toString() {
+    if (blocks.isEmpty) {
+      return '';
+    }
+    StringBuffer sb = new StringBuffer();
+    sb.writeln('ExceptionsTable {');
+    for (var tryBlock in blocks) {
+      sb.writeln('  $tryBlock');
+    }
+    sb.writeln('}');
+    return sb.toString();
+  }
+}
diff --git a/pkg/vm/lib/bytecode/gen_bytecode.dart b/pkg/vm/lib/bytecode/gen_bytecode.dart
index 29ea146..8d189b7 100644
--- a/pkg/vm/lib/bytecode/gen_bytecode.dart
+++ b/pkg/vm/lib/bytecode/gen_bytecode.dart
@@ -17,6 +17,7 @@
 import 'package:vm/bytecode/assembler.dart';
 import 'package:vm/bytecode/constant_pool.dart';
 import 'package:vm/bytecode/dbc.dart';
+import 'package:vm/bytecode/exceptions.dart';
 import 'package:vm/bytecode/local_vars.dart' show LocalVariables;
 import 'package:vm/metadata/bytecode.dart';
 
@@ -60,6 +61,9 @@
   ConstantEvaluator constantEvaluator;
   Map<LabeledStatement, Label> labeledStatements;
   Map<SwitchCase, Label> switchCases;
+  Map<TryCatch, TryBlock> tryCatches;
+  Map<TryFinally, List<FinallyBlock>> finallyBlocks;
+  Map<TreeNode, int> contextLevels;
   List<ClosureBytecode> closures;
   ConstantPool cp;
   ConstantEmitter constantEmitter;
@@ -401,6 +405,22 @@
     asm.emitPush(tempIndexInFrame);
   }
 
+  /// Generates is-test for the value at TOS.
+  void _genInstanceOf(DartType type) {
+    // TODO(alexmarkov): generate _simpleInstanceOf if possible
+
+    if (hasTypeParameters([type])) {
+      _genPushInstantiatorAndFunctionTypeArguments([type]);
+    } else {
+      _genPushNull(); // Instantiator type arguments.
+      _genPushNull(); // Function type arguments.
+    }
+    asm.emitPushConstant(cp.add(new ConstantType(type)));
+    final argDescIndex = cp.add(new ConstantArgDesc(4));
+    final icdataIndex = cp.add(new ConstantICData('_instanceOf', argDescIndex));
+    asm.emitInstanceCall1(4, icdataIndex);
+  }
+
   void start(Member node) {
     enclosingClass = node.enclosingClass;
     enclosingMember = node;
@@ -411,6 +431,9 @@
       ..env = new EvaluationEnvironment();
     labeledStatements = <LabeledStatement, Label>{};
     switchCases = <SwitchCase, Label>{};
+    tryCatches = <TryCatch, TryBlock>{};
+    finallyBlocks = <TryFinally, List<FinallyBlock>>{};
+    contextLevels = <TreeNode, int>{};
     closures = <ClosureBytecode>[];
     cp = new ConstantPool();
     constantEmitter = new ConstantEmitter(cp);
@@ -446,7 +469,8 @@
   }
 
   void end(Member node) {
-    metadata.mapping[node] = new BytecodeMetadata(asm.bytecode, cp, closures);
+    metadata.mapping[node] =
+        new BytecodeMetadata(asm.bytecode, asm.exceptionsTable, cp, closures);
 
     if (isTraceEnabled) {
       print('Generated bytecode for $node');
@@ -458,6 +482,9 @@
     constantEvaluator = null;
     labeledStatements = null;
     switchCases = null;
+    tryCatches = null;
+    finallyBlocks = null;
+    contextLevels = null;
     closures = null;
     cp = null;
     constantEmitter = null;
@@ -578,7 +605,8 @@
 
     locals.leaveScope();
 
-    closures.add(new ClosureBytecode(closureFunctionIndex, asm.bytecode));
+    closures.add(new ClosureBytecode(
+        closureFunctionIndex, asm.bytecode, asm.exceptionsTable));
     _popAssemblerState();
 
     return closureFunctionIndex;
@@ -643,12 +671,74 @@
 
   void _leaveScope() {
     if (locals.currentContextSize > 0) {
+      _genUnwindContext(locals.currentContextLevel - 1);
+    }
+    locals.leaveScope();
+  }
+
+  void _genUnwindContext(int targetContextLevel) {
+    int currentContextLevel = locals.currentContextLevel;
+    assert(currentContextLevel >= targetContextLevel);
+    while (currentContextLevel > targetContextLevel) {
       asm.emitPush(locals.contextVarIndexInFrame);
       asm.emitLoadFieldTOS(cp.add(new ConstantContextOffset.parent()));
       asm.emitPopLocal(locals.contextVarIndexInFrame);
+      --currentContextLevel;
+    }
+  }
+
+  /// Returns the list of try-finally blocks between [from] and [to],
+  /// ordered from inner to outer. If [to] is null, returns all enclosing
+  /// try-finally blocks up to the function boundary.
+  List<TryFinally> _getEnclosingTryFinallyBlocks(TreeNode from, TreeNode to) {
+    List<TryFinally> blocks = <TryFinally>[];
+    TreeNode node = from;
+    for (;;) {
+      if (node == to) {
+        return blocks;
+      }
+      if (node == null || node is FunctionNode || node is Member) {
+        if (to == null) {
+          return blocks;
+        } else {
+          throw 'Unable to find node $to up from $from';
+        }
+      }
+      // Inspect parent as we only need try-finally blocks enclosing [node]
+      // in the body, and not in the finally-block.
+      final parent = node.parent;
+      if (parent is TryFinally && parent.body == node) {
+        blocks.add(parent);
+      }
+      node = parent;
+    }
+  }
+
+  /// Generates non-local transfer from inner node [from] into the outer
+  /// node, executing finally blocks on the way out. [to] can be null,
+  /// in such case all enclosing finally blocks are executed.
+  /// [continuation] is invoked to generate control transfer code following
+  /// the last finally block.
+  void _generateNonLocalControlTransfer(
+      TreeNode from, TreeNode to, GenerateContinuation continuation) {
+    List<TryFinally> tryFinallyBlocks = _getEnclosingTryFinallyBlocks(from, to);
+
+    // Add finally blocks to all try-finally from outer to inner.
+    // The outermost finally block should generate continuation, each inner
+    // finally block should proceed to a corresponding outer block.
+    for (var tryFinally in tryFinallyBlocks.reversed) {
+      final finallyBlock = new FinallyBlock(continuation);
+      finallyBlocks[tryFinally].add(finallyBlock);
+
+      final Label nextFinally = finallyBlock.entry;
+      continuation = () {
+        asm.emitJump(nextFinally);
+      };
     }
 
-    locals.leaveScope();
+    // Generate jump to the innermost finally (or to the original
+    // continuation if there are no try-finally blocks).
+    continuation();
   }
 
   @override
@@ -785,20 +875,7 @@
   @override
   visitIsExpression(IsExpression node) {
     node.operand.accept(this);
-
-    // TODO(alexmarkov): generate _simpleInstanceOf if possible
-
-    if (hasTypeParameters([node.type])) {
-      _genPushInstantiatorAndFunctionTypeArguments([node.type]);
-    } else {
-      _genPushNull(); // Instantiator type arguments.
-      _genPushNull(); // Function type arguments.
-    }
-    final typeIndex = cp.add(new ConstantType(node.type));
-    asm.emitPushConstant(typeIndex);
-    final argDescIndex = cp.add(new ConstantArgDesc(4));
-    final icdataIndex = cp.add(new ConstantICData('_instanceOf', argDescIndex));
-    asm.emitInstanceCall1(4, icdataIndex);
+    _genInstanceOf(node.type);
   }
 
   @override
@@ -1001,9 +1078,21 @@
     asm.emitPushConstant(cpIndex);
   }
 
-//  @override
-//  visitRethrow(Rethrow node) {
-//  }
+  @override
+  visitRethrow(Rethrow node) {
+    TryCatch tryCatch;
+    for (var parent = node.parent;; parent = parent.parent) {
+      if (parent is Catch) {
+        tryCatch = parent.parent as TryCatch;
+        break;
+      }
+      if (parent == null || parent is FunctionNode) {
+        throw 'Unable to find enclosing catch for $node';
+      }
+    }
+    tryCatches[tryCatch].needsStackTrace = true;
+    _genRethrow(tryCatch);
+  }
 
   bool _hasTrivialInitializer(Field field) =>
       (field.initializer == null) ||
@@ -1205,18 +1294,26 @@
 
   @override
   visitBreakStatement(BreakStatement node) {
-    // TODO(alexmarkov): execute all finally blocks on the way out.
-    final label = labeledStatements[node.target] ??
+    final targetLabel = labeledStatements[node.target] ??
         (throw 'Target label ${node.target} was not registered for break $node');
-    asm.emitJump(label);
+    final targetContextLevel = contextLevels[node.target];
+
+    _generateNonLocalControlTransfer(node, node.target, () {
+      _genUnwindContext(targetContextLevel);
+      asm.emitJump(targetLabel);
+    });
   }
 
   @override
   visitContinueSwitchStatement(ContinueSwitchStatement node) {
-    // TODO(alexmarkov): execute all finally blocks on the way out.
-    final label = switchCases[node.target] ??
+    final targetLabel = switchCases[node.target] ??
         (throw 'Target label ${node.target} was not registered for continue-switch $node');
-    asm.emitJump(label);
+    final targetContextLevel = contextLevels[node.target.parent];
+
+    _generateNonLocalControlTransfer(node, node.target.parent, () {
+      _genUnwindContext(targetContextLevel);
+      asm.emitJump(targetLabel);
+    });
   }
 
   @override
@@ -1358,9 +1455,11 @@
   visitLabeledStatement(LabeledStatement node) {
     final label = new Label();
     labeledStatements[node] = label;
+    contextLevels[node] = locals.currentContextLevel;
     node.body.accept(this);
     asm.bind(label);
-    labeledStatements[node] = null;
+    labeledStatements.remove(node);
+    contextLevels.remove(node);
   }
 
   @override
@@ -1370,11 +1469,18 @@
     } else {
       _genPushNull();
     }
-    asm.emitReturnTOS();
+
+    // TODO(alexmarkov): Do we need to save return value
+    // to a variable?
+    _generateNonLocalControlTransfer(node, null, () {
+      asm.emitReturnTOS();
+    });
   }
 
   @override
   visitSwitchStatement(SwitchStatement node) {
+    contextLevels[node] = locals.currentContextLevel;
+
     node.expression.accept(this);
 
     final int temp = locals.tempIndexInFrame(node);
@@ -1419,15 +1525,169 @@
 
     asm.bind(done);
     node.cases.forEach(switchCases.remove);
+    contextLevels.remove(node);
   }
 
-//  @override
-//  visitTryCatch(TryCatch node) {
-//  }
-//
-//  @override
-//  visitTryFinally(TryFinally node) {
-//  }
+  bool _isTryBlock(TreeNode node) => node is TryCatch || node is TryFinally;
+
+  int _savedContextVar(TreeNode node) {
+    assert(_isTryBlock(node));
+    return locals.tempIndexInFrame(node, tempIndex: 0);
+  }
+
+  // Exception var occupies the same slot as saved context, so context
+  // should be restored first, before loading exception.
+  int _exceptionVar(TreeNode node) {
+    assert(_isTryBlock(node));
+    return locals.tempIndexInFrame(node, tempIndex: 0);
+  }
+
+  int _stackTraceVar(TreeNode node) {
+    assert(_isTryBlock(node));
+    return locals.tempIndexInFrame(node, tempIndex: 1);
+  }
+
+  _saveContextForTryBlock(TreeNode node) {
+    if (locals.hasContextVar) {
+      asm.emitPush(locals.contextVarIndexInFrame);
+      asm.emitPopLocal(_savedContextVar(node));
+    }
+  }
+
+  _restoreContextForTryBlock(TreeNode node) {
+    if (locals.hasContextVar) {
+      asm.emitPush(_savedContextVar(node));
+      asm.emitPopLocal(locals.contextVarIndexInFrame);
+    }
+  }
+
+  /// Start try block
+  TryBlock _startTryBlock(TreeNode node) {
+    assert(_isTryBlock(node));
+
+    _saveContextForTryBlock(node);
+
+    return asm.exceptionsTable.enterTryBlock(asm.offsetInWords);
+  }
+
+  /// End try block and start its handler.
+  void _endTryBlock(TreeNode node, TryBlock tryBlock) {
+    tryBlock.endPC = asm.offsetInWords;
+    tryBlock.handlerPC = asm.offsetInWords;
+
+    // TODO(alexmarkov): Consider emitting SetFrame to cut expression stack.
+    // In such case, we need to save return value to a variable in visitReturn.
+
+    _restoreContextForTryBlock(node);
+
+    asm.emitMoveSpecial(_exceptionVar(node), SpecialIndex.exception);
+    asm.emitMoveSpecial(_stackTraceVar(node), SpecialIndex.stackTrace);
+  }
+
+  void _genRethrow(TreeNode node) {
+    asm.emitPush(_exceptionVar(node));
+    asm.emitPush(_stackTraceVar(node));
+    asm.emitThrow(1);
+  }
+
+  @override
+  visitTryCatch(TryCatch node) {
+    final Label done = new Label();
+
+    final TryBlock tryBlock = _startTryBlock(node);
+    tryBlock.isSynthetic = node.isSynthetic;
+    tryCatches[node] = tryBlock; // Used by rethrow.
+
+    node.body.accept(this);
+    asm.emitJump(done);
+
+    _endTryBlock(node, tryBlock);
+
+    final int exception = _exceptionVar(node);
+    final int stackTrace = _stackTraceVar(node);
+
+    bool hasCatchAll = false;
+
+    for (Catch catchClause in node.catches) {
+      tryBlock.types.add(cp.add(new ConstantType(catchClause.guard)));
+
+      Label skipCatch;
+      if (catchClause.guard == const DynamicType()) {
+        hasCatchAll = true;
+      } else {
+        asm.emitPush(exception);
+        _genInstanceOf(catchClause.guard);
+
+        skipCatch = new Label();
+        _genJumpIfFalse(/* negated = */ false, skipCatch);
+      }
+
+      _enterScope(catchClause);
+
+      if (catchClause.exception != null) {
+        _genPushContextIfCaptured(catchClause.exception);
+        asm.emitPush(exception);
+        _genStoreVar(catchClause.exception);
+      }
+
+      if (catchClause.stackTrace != null) {
+        tryBlock.needsStackTrace = true;
+        _genPushContextIfCaptured(catchClause.stackTrace);
+        asm.emitPush(stackTrace);
+        _genStoreVar(catchClause.stackTrace);
+      }
+
+      catchClause.body.accept(this);
+
+      _leaveScope();
+      asm.emitJump(done);
+
+      if (skipCatch != null) {
+        asm.bind(skipCatch);
+      }
+    }
+
+    if (!hasCatchAll) {
+      tryBlock.needsStackTrace = true;
+      _genRethrow(node);
+    }
+
+    asm.bind(done);
+    tryCatches.remove(node);
+  }
+
+  @override
+  visitTryFinally(TryFinally node) {
+    final TryBlock tryBlock = _startTryBlock(node);
+    finallyBlocks[node] = <FinallyBlock>[];
+
+    node.body.accept(this);
+
+    // TODO(alexmarkov): Do not generate normal continuation if control
+    // does not return from body.
+    final normalContinuation =
+        new FinallyBlock(() {/* do nothing (fall through) */});
+    finallyBlocks[node].add(normalContinuation);
+    asm.emitJump(normalContinuation.entry);
+
+    _endTryBlock(node, tryBlock);
+
+    tryBlock.types.add(cp.add(new ConstantType(const DynamicType())));
+
+    node.finalizer.accept(this);
+
+    tryBlock.needsStackTrace = true; // For rethrowing.
+    _genRethrow(node);
+
+    for (var finallyBlock in finallyBlocks[node]) {
+      asm.bind(finallyBlock.entry);
+      _restoreContextForTryBlock(node);
+      node.finalizer.accept(this);
+      finallyBlock.generateContinuation();
+    }
+
+    finallyBlocks.remove(node);
+  }
 
   @override
   visitVariableDeclaration(VariableDeclaration node) {
@@ -1665,3 +1925,12 @@
 
   bool _hasBytecode(Member node) => metadata.mapping.containsKey(node);
 }
+
+typedef void GenerateContinuation();
+
+class FinallyBlock {
+  final Label entry = new Label();
+  final GenerateContinuation generateContinuation;
+
+  FinallyBlock(this.generateContinuation);
+}
diff --git a/pkg/vm/lib/bytecode/local_vars.dart b/pkg/vm/lib/bytecode/local_vars.dart
index bb717f2..1bae25a 100644
--- a/pkg/vm/lib/bytecode/local_vars.dart
+++ b/pkg/vm/lib/bytecode/local_vars.dart
@@ -13,7 +13,7 @@
   final Map<TreeNode, Scope> _scopes = <TreeNode, Scope>{};
   final Map<VariableDeclaration, VarDesc> _vars =
       <VariableDeclaration, VarDesc>{};
-  final Map<TreeNode, int> _temps = <TreeNode, int>{};
+  final Map<TreeNode, List<int>> _temps = <TreeNode, List<int>>{};
 
   Scope _currentScope;
   Frame _currentFrame;
@@ -43,9 +43,13 @@
       _getVarDesc(variable).originalParamSlotIndex ??
       (throw 'Variablie $variable does not have originalParamSlotIndex');
 
-  int tempIndexInFrame(TreeNode node) =>
-      _temps[node] ??
-      (throw 'Temp is not allocated for node ${node.runtimeType} $node');
+  int tempIndexInFrame(TreeNode node, {int tempIndex: 0}) {
+    final temps = _temps[node];
+    if (temps == null) {
+      throw 'Temp is not allocated for node ${node.runtimeType} $node';
+    }
+    return temps[tempIndex];
+  }
 
   int get currentContextSize => _currentScope.contextSize;
   int get currentContextLevel => _currentScope.contextLevel;
@@ -64,6 +68,8 @@
           .contextVar ??
       (throw 'Context variable is not declared in ${_currentFrame.function}'));
 
+  bool get hasContextVar => _currentFrame.contextVar != null;
+
   int get scratchVarIndexInFrame => getVarIndexInFrame(_currentFrame
           .scratchVar ??
       (throw 'Scratch variable is not declared in ${_currentFrame.function}'));
@@ -452,23 +458,31 @@
         max(_currentFrame.frameSize, _currentScope.localsUsed);
   }
 
-  void _allocateTemp(TreeNode node) {
+  void _allocateTemp(TreeNode node, {int count: 1}) {
     assert(locals._temps[node] == null);
-    if (_currentScope.tempsUsed >= _currentFrame.temporaries.length) {
-      // Allocate a new local slot for temporary variable.
-      int local = _currentScope.localsUsed++;
+    if (_currentScope.tempsUsed + count > _currentFrame.temporaries.length) {
+      // Allocate new local slots for temporary variables.
+      final int newSlots =
+          (_currentScope.tempsUsed + count) - _currentFrame.temporaries.length;
+      int local = _currentScope.localsUsed;
+      _currentScope.localsUsed += newSlots;
       _updateFrameSize();
-      _currentFrame.temporaries.add(local);
+      for (int i = 0; i < newSlots; i++) {
+        _currentFrame.temporaries.add(local + i);
+      }
     }
-    int index = _currentFrame.temporaries[_currentScope.tempsUsed++];
-    locals._temps[node] = index;
+    locals._temps[node] = _currentFrame.temporaries
+        .sublist(_currentScope.tempsUsed, _currentScope.tempsUsed + count);
+    _currentScope.tempsUsed += count;
   }
 
-  void _freeTemp(TreeNode node) {
-    assert(_currentScope.tempsUsed > 0);
-    assert(locals._temps[node] ==
-        _currentFrame.temporaries[_currentScope.tempsUsed - 1]);
-    --_currentScope.tempsUsed;
+  void _freeTemp(TreeNode node, {int count: 1}) {
+    assert(_currentScope.tempsUsed >= count);
+    _currentScope.tempsUsed -= count;
+    assert(listEquals(
+        locals._temps[node],
+        _currentFrame.temporaries.sublist(
+            _currentScope.tempsUsed, _currentScope.tempsUsed + count)));
   }
 
   void _allocateVariable(VariableDeclaration variable, {int paramSlotIndex}) {
@@ -598,18 +612,18 @@
     _leaveScope();
   }
 
-  void _visit(TreeNode node, {scope: false, temp: false}) {
+  void _visit(TreeNode node, {bool scope: false, int temps: 0}) {
     if (scope) {
       _enterScope(node);
     }
-    if (temp) {
-      _allocateTemp(node);
+    if (temps > 0) {
+      _allocateTemp(node, count: temps);
     }
 
     node.visitChildren(this);
 
-    if (temp) {
-      _freeTemp(node);
+    if (temps > 0) {
+      _freeTemp(node, count: temps);
     }
     if (scope) {
       _leaveScope();
@@ -688,7 +702,7 @@
     if (node.isConst) {
       return;
     }
-    _visit(node, temp: true);
+    _visit(node, temps: 1);
   }
 
   @override
@@ -696,7 +710,7 @@
     if (node.isConst) {
       return;
     }
-    _visit(node, temp: true);
+    _visit(node, temps: 1);
   }
 
   @override
@@ -704,37 +718,37 @@
     if (node.isConst) {
       return;
     }
-    _visit(node, temp: true);
+    _visit(node, temps: 1);
   }
 
   @override
   visitStringConcatenation(StringConcatenation node) {
-    _visit(node, temp: true);
+    _visit(node, temps: 1);
   }
 
   @override
   visitConditionalExpression(ConditionalExpression node) {
-    _visit(node, temp: true);
+    _visit(node, temps: 1);
   }
 
   @override
   visitLogicalExpression(LogicalExpression node) {
-    _visit(node, temp: true);
+    _visit(node, temps: 1);
   }
 
   @override
   visitPropertySet(PropertySet node) {
-    _visit(node, temp: true);
+    _visit(node, temps: 1);
   }
 
   @override
   visitSwitchStatement(SwitchStatement node) {
-    _visit(node, temp: true);
+    _visit(node, temps: 1);
   }
 
   @override
   visitVariableSet(VariableSet node) {
-    _visit(node, temp: locals.isCaptured(node.variable));
+    _visit(node, temps: locals.isCaptured(node.variable) ? 1 : 0);
   }
 
   @override
@@ -742,4 +756,14 @@
     _allocateTemp(node);
     super.visitStaticSet(node);
   }
+
+  @override
+  visitTryCatch(TryCatch node) {
+    _visit(node, temps: 2);
+  }
+
+  @override
+  visitTryFinally(TryFinally node) {
+    _visit(node, temps: 2);
+  }
 }
diff --git a/pkg/vm/lib/metadata/bytecode.dart b/pkg/vm/lib/metadata/bytecode.dart
index 11fd237..3d5261f 100644
--- a/pkg/vm/lib/metadata/bytecode.dart
+++ b/pkg/vm/lib/metadata/bytecode.dart
@@ -7,6 +7,7 @@
 import 'package:kernel/ast.dart';
 import 'package:vm/bytecode/constant_pool.dart' show ConstantPool;
 import 'package:vm/bytecode/disassembler.dart' show BytecodeDisassembler;
+import 'package:vm/bytecode/exceptions.dart' show ExceptionsTable;
 
 /// Metadata containing bytecode.
 ///
@@ -14,6 +15,7 @@
 ///
 /// type BytecodeMetadata {
 ///   List<Byte> bytecodes
+///   ExceptionsTable exceptionsTable
 ///   ConstantPool constantPool
 ///   List<ClosureBytecode> closures
 /// }
@@ -21,22 +23,29 @@
 /// type ClosureBytecode {
 ///   ConstantIndex closureFunction
 ///   List<Byte> bytecodes
+///   ExceptionsTable exceptionsTable
 /// }
 ///
+/// Encoding of ExceptionsTable is described in
+/// pkg/vm/lib/bytecode/exceptions.dart.
+///
 /// Encoding of ConstantPool is described in
 /// pkg/vm/lib/bytecode/constant_pool.dart.
 ///
 class BytecodeMetadata {
   final List<int> bytecodes;
+  final ExceptionsTable exceptionsTable;
   final ConstantPool constantPool;
   final List<ClosureBytecode> closures;
 
-  BytecodeMetadata(this.bytecodes, this.constantPool, this.closures);
+  BytecodeMetadata(
+      this.bytecodes, this.exceptionsTable, this.constantPool, this.closures);
 
   @override
   String toString() => "\n"
       "Bytecode {\n"
-      "${new BytecodeDisassembler().disassemble(bytecodes)}}\n"
+      "${new BytecodeDisassembler().disassemble(bytecodes, exceptionsTable)}}\n"
+      "$exceptionsTable"
       "$constantPool"
       "${closures.join('\n')}";
 }
@@ -46,25 +55,31 @@
 class ClosureBytecode {
   final int closureFunctionConstantIndex;
   final List<int> bytecodes;
+  final ExceptionsTable exceptionsTable;
 
-  ClosureBytecode(this.closureFunctionConstantIndex, this.bytecodes);
+  ClosureBytecode(
+      this.closureFunctionConstantIndex, this.bytecodes, this.exceptionsTable);
 
   void writeToBinary(BinarySink sink) {
     sink.writeUInt30(closureFunctionConstantIndex);
     sink.writeByteList(bytecodes);
+    exceptionsTable.writeToBinary(sink);
   }
 
   factory ClosureBytecode.readFromBinary(BinarySource source) {
     final closureFunctionConstantIndex = source.readUInt();
     final List<int> bytecodes = source.readByteList();
-    return new ClosureBytecode(closureFunctionConstantIndex, bytecodes);
+    final exceptionsTable = new ExceptionsTable.readFromBinary(source);
+    return new ClosureBytecode(
+        closureFunctionConstantIndex, bytecodes, exceptionsTable);
   }
 
   @override
   String toString() {
     StringBuffer sb = new StringBuffer();
     sb.writeln('Closure CP#$closureFunctionConstantIndex {');
-    sb.writeln(new BytecodeDisassembler().disassemble(bytecodes));
+    sb.writeln(
+        new BytecodeDisassembler().disassemble(bytecodes, exceptionsTable));
     sb.writeln('}');
     return sb.toString();
   }
@@ -82,6 +97,7 @@
   @override
   void writeToBinary(BytecodeMetadata metadata, Node node, BinarySink sink) {
     sink.writeByteList(metadata.bytecodes);
+    metadata.exceptionsTable.writeToBinary(sink);
     metadata.constantPool.writeToBinary(node, sink);
     sink.writeUInt30(metadata.closures.length);
     metadata.closures.forEach((c) => c.writeToBinary(sink));
@@ -90,10 +106,12 @@
   @override
   BytecodeMetadata readFromBinary(Node node, BinarySource source) {
     final List<int> bytecodes = source.readByteList();
+    final exceptionsTable = new ExceptionsTable.readFromBinary(source);
     final ConstantPool constantPool =
         new ConstantPool.readFromBinary(node, source);
     final List<ClosureBytecode> closures = new List<ClosureBytecode>.generate(
         source.readUInt(), (_) => new ClosureBytecode.readFromBinary(source));
-    return new BytecodeMetadata(bytecodes, constantPool, closures);
+    return new BytecodeMetadata(
+        bytecodes, exceptionsTable, constantPool, closures);
   }
 }
diff --git a/pkg/vm/testcases/bytecode/try_blocks.dart b/pkg/vm/testcases/bytecode/try_blocks.dart
new file mode 100644
index 0000000..d07c18f
--- /dev/null
+++ b/pkg/vm/testcases/bytecode/try_blocks.dart
@@ -0,0 +1,153 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+testTryCatch1() {
+  try {
+    print('danger!');
+  } catch (e) {
+    print('caught $e');
+  }
+}
+
+testTryCatch2() {
+  try {
+    print('danger!');
+  } on TypeError {
+    print('caught type error');
+  } on AssertionError catch (e) {
+    print('caught assertion error $e');
+  } on Error catch (e, st) {
+    print('caught error $e $st');
+  } catch (e, st) {
+    print('caught something $e $st');
+  }
+}
+
+testTryCatch3() {
+  int x = 1;
+  try {
+    int y = 2;
+    void foo() {
+      try {
+        print('danger foo');
+      } catch (e) {
+        print(x);
+        y = 3;
+      }
+    }
+
+    foo();
+    print(y);
+  } catch (e, st) {
+    print('caught $e $st');
+
+    void bar() {
+      try {
+        print('danger bar');
+      } on Error catch (e) {
+        print('error $e, captured stack trace: $st');
+      }
+    }
+
+    return bar;
+  }
+}
+
+testRethrow(bool cond) {
+  try {
+    try {
+      print('try 1 > try 2');
+    } catch (e) {
+      try {
+        print('try 1 > catch 2 > try 3');
+        if (cond) {
+          rethrow;
+        }
+      } catch (e) {
+        print('try 1 > catch 2 > catch 3');
+      }
+    }
+  } catch (e, st) {
+    print('catch 1');
+    print(st);
+  }
+}
+
+testTryFinally1() {
+  for (int i = 0; i < 10; i++) {
+    try {
+      if (i > 5) {
+        break;
+      }
+    } finally {
+      print(i);
+    }
+  }
+}
+
+testTryFinally2(int x) {
+  switch (x) {
+    case 1:
+      try {
+        print('before try 1');
+        int y = 3;
+        try {
+          print('try');
+          void foo() {
+            print(x);
+            print(y);
+          }
+
+          foo();
+          continue L;
+        } finally {
+          print('finally 1');
+        }
+        print('after try 1');
+      } finally {
+        print('finally 2');
+      }
+      break;
+    L:
+    case 2:
+      print('case 2');
+      break;
+  }
+}
+
+testTryFinally3() {
+  int x = 11;
+  var y;
+  try {
+    y = () {
+      print(x);
+      try {
+        print('try 1');
+        return 42;
+      } finally {
+        try {
+          print('try 2');
+          return 43;
+        } finally {
+          print(x);
+        }
+      }
+    };
+  } finally {
+    print(x);
+    y();
+  }
+}
+
+testTryCatchFinally() {
+  try {
+    print('try');
+  } catch (e) {
+    print('catch');
+  } finally {
+    print('finally');
+  }
+}
+
+main() {}
diff --git a/pkg/vm/testcases/bytecode/try_blocks.dart.expect b/pkg/vm/testcases/bytecode/try_blocks.dart.expect
new file mode 100644
index 0000000..0dbaad8
--- /dev/null
+++ b/pkg/vm/testcases/bytecode/try_blocks.dart.expect
@@ -0,0 +1,1379 @@
+library #lib;
+import self as self;
+import "dart:core" as core;
+
+[@vm.bytecode=
+Bytecode {
+  Entry                4
+  CheckStack
+Try #0 start:
+  PushConstant         CP#0
+  PushConstant         CP#2
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L1
+Try #0 end:
+Try #0 handler:
+  MoveSpecial          r0, exception
+  MoveSpecial          r1, stackTrace
+  Push                 r0
+  PopLocal             r2
+  PushConstant         CP#4
+  PushConstant         CP#5
+  CreateArrayTOS
+  StoreLocal           r3
+  Push                 r3
+  PushConstant         CP#6
+  PushConstant         CP#7
+  StoreIndexedTOS
+  Push                 r3
+  PushConstant         CP#8
+  Push                 r2
+  StoreIndexedTOS
+  PushConstant         CP#9
+  IndirectStaticCall   1, CP#1
+  PushConstant         CP#10
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L1
+L1:
+  PushConstant         CP#4
+  ReturnTOS
+}
+ExceptionsTable {
+  try-index 0, outer -1, start 2, end 7, handler 7, types [CP#3]
+}
+ConstantPool {
+  [0] = String 'danger!'
+  [1] = ArgDesc num-args 1, num-type-args 0, names []
+  [2] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [3] = Type dynamic
+  [4] = Null
+  [5] = Int 2
+  [6] = Int 0
+  [7] = String 'caught '
+  [8] = Int 1
+  [9] = StaticICData target 'dart.core::_StringBase::_interpolate', arg-desc CP#1
+  [10] = StaticICData target 'dart.core::print', arg-desc CP#1
+}
+]static method testTryCatch1() → dynamic {
+  try {
+    core::print("danger!");
+  }
+  on dynamic catch(final dynamic e) {
+    core::print("caught ${e}");
+  }
+}
+[@vm.bytecode=
+Bytecode {
+  Entry                5
+  CheckStack
+Try #0 start:
+  PushConstant         CP#0
+  PushConstant         CP#2
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L1
+Try #0 end:
+Try #0 handler:
+  MoveSpecial          r0, exception
+  MoveSpecial          r1, stackTrace
+  Push                 r0
+  PushConstant         CP#4
+  PushConstant         CP#4
+  PushConstant         CP#3
+  InstanceCall1        4, CP#6
+  PushConstant         CP#7
+  IfNeStrictTOS
+  Jump                 L2
+  PushConstant         CP#8
+  PushConstant         CP#9
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L1
+L2:
+  Push                 r0
+  PushConstant         CP#4
+  PushConstant         CP#4
+  PushConstant         CP#10
+  InstanceCall1        4, CP#11
+  PushConstant         CP#7
+  IfNeStrictTOS
+  Jump                 L3
+  Push                 r0
+  PopLocal             r2
+  PushConstant         CP#4
+  PushConstant         CP#12
+  CreateArrayTOS
+  StoreLocal           r3
+  Push                 r3
+  PushConstant         CP#13
+  PushConstant         CP#14
+  StoreIndexedTOS
+  Push                 r3
+  PushConstant         CP#15
+  Push                 r2
+  StoreIndexedTOS
+  PushConstant         CP#16
+  IndirectStaticCall   1, CP#1
+  PushConstant         CP#17
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L1
+L3:
+  Push                 r0
+  PushConstant         CP#4
+  PushConstant         CP#4
+  PushConstant         CP#18
+  InstanceCall1        4, CP#19
+  PushConstant         CP#7
+  IfNeStrictTOS
+  Jump                 L4
+  Push                 r0
+  PopLocal             r2
+  Push                 r1
+  PopLocal             r3
+  PushConstant         CP#4
+  PushConstant         CP#20
+  CreateArrayTOS
+  StoreLocal           r4
+  Push                 r4
+  PushConstant         CP#13
+  PushConstant         CP#21
+  StoreIndexedTOS
+  Push                 r4
+  PushConstant         CP#15
+  Push                 r2
+  StoreIndexedTOS
+  Push                 r4
+  PushConstant         CP#12
+  PushConstant         CP#22
+  StoreIndexedTOS
+  Push                 r4
+  PushConstant         CP#23
+  Push                 r3
+  StoreIndexedTOS
+  PushConstant         CP#24
+  IndirectStaticCall   1, CP#1
+  PushConstant         CP#25
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L1
+L4:
+  Push                 r0
+  PopLocal             r2
+  Push                 r1
+  PopLocal             r3
+  PushConstant         CP#4
+  PushConstant         CP#20
+  CreateArrayTOS
+  StoreLocal           r4
+  Push                 r4
+  PushConstant         CP#13
+  PushConstant         CP#27
+  StoreIndexedTOS
+  Push                 r4
+  PushConstant         CP#15
+  Push                 r2
+  StoreIndexedTOS
+  Push                 r4
+  PushConstant         CP#12
+  PushConstant         CP#22
+  StoreIndexedTOS
+  Push                 r4
+  PushConstant         CP#23
+  Push                 r3
+  StoreIndexedTOS
+  PushConstant         CP#28
+  IndirectStaticCall   1, CP#1
+  PushConstant         CP#29
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L1
+L1:
+  PushConstant         CP#4
+  ReturnTOS
+}
+ExceptionsTable {
+  try-index 0, outer -1, start 2, end 7, handler 7, needs-stack-trace, types [CP#3, CP#10, CP#18, CP#26]
+}
+ConstantPool {
+  [0] = String 'danger!'
+  [1] = ArgDesc num-args 1, num-type-args 0, names []
+  [2] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [3] = Type dart.core::TypeError
+  [4] = Null
+  [5] = ArgDesc num-args 4, num-type-args 0, names []
+  [6] = ICData target-name '_instanceOf', arg-desc CP#5
+  [7] = Bool true
+  [8] = String 'caught type error'
+  [9] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [10] = Type dart.core::AssertionError
+  [11] = ICData target-name '_instanceOf', arg-desc CP#5
+  [12] = Int 2
+  [13] = Int 0
+  [14] = String 'caught assertion error '
+  [15] = Int 1
+  [16] = StaticICData target 'dart.core::_StringBase::_interpolate', arg-desc CP#1
+  [17] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [18] = Type dart.core::Error
+  [19] = ICData target-name '_instanceOf', arg-desc CP#5
+  [20] = Int 4
+  [21] = String 'caught error '
+  [22] = String ' '
+  [23] = Int 3
+  [24] = StaticICData target 'dart.core::_StringBase::_interpolate', arg-desc CP#1
+  [25] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [26] = Type dynamic
+  [27] = String 'caught something '
+  [28] = StaticICData target 'dart.core::_StringBase::_interpolate', arg-desc CP#1
+  [29] = StaticICData target 'dart.core::print', arg-desc CP#1
+}
+]static method testTryCatch2() → dynamic {
+  try {
+    core::print("danger!");
+  }
+  on core::TypeError catch(no-exception-var) {
+    core::print("caught type error");
+  }
+  on core::AssertionError catch(final core::AssertionError e) {
+    core::print("caught assertion error ${e}");
+  }
+  on core::Error catch(final core::Error e, final core::StackTrace st) {
+    core::print("caught error ${e} ${st}");
+  }
+  on dynamic catch(final dynamic e, final core::StackTrace st) {
+    core::print("caught something ${e} ${st}");
+  }
+}
+[@vm.bytecode=
+Bytecode {
+  Entry                7
+  CheckStack
+  AllocateContext      1
+  StoreLocal           r1
+  Push                 r1
+  Push                 r0
+  StoreFieldTOS        CP#0
+  PopLocal             r0
+  Push                 r0
+  PushConstant         CP#1
+  StoreFieldTOS        CP#2
+  Push                 r0
+  PopLocal             r2
+Try #0 start:
+  AllocateContext      1
+  StoreLocal           r1
+  Push                 r1
+  Push                 r0
+  StoreFieldTOS        CP#0
+  PopLocal             r0
+  Push                 r0
+  PushConstant         CP#3
+  StoreFieldTOS        CP#2
+  Allocate             CP#14
+  StoreLocal           r5
+  Push                 r5
+  PushConstant         CP#12
+  StoreFieldTOS        CP#15
+  Push                 r5
+  PushConstant         CP#12
+  StoreFieldTOS        CP#16
+  Push                 r5
+  PushConstant         CP#4
+  StoreFieldTOS        CP#17
+  Push                 r5
+  Push                 r0
+  StoreFieldTOS        CP#5
+  PopLocal             r4
+  Push                 r4
+  InstanceCall1        1, CP#18
+  Drop1
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#19
+  IndirectStaticCall   1, CP#7
+  Drop1
+  Push                 r0
+  LoadFieldTOS         CP#0
+  PopLocal             r0
+  Jump                 L1
+Try #0 end:
+Try #0 handler:
+  Push                 r2
+  PopLocal             r0
+  MoveSpecial          r2, exception
+  MoveSpecial          r3, stackTrace
+  AllocateContext      1
+  StoreLocal           r1
+  Push                 r1
+  Push                 r0
+  StoreFieldTOS        CP#0
+  PopLocal             r0
+  Push                 r2
+  PopLocal             r4
+  Push                 r0
+  Push                 r3
+  StoreFieldTOS        CP#2
+  PushConstant         CP#12
+  PushConstant         CP#20
+  CreateArrayTOS
+  StoreLocal           r5
+  Push                 r5
+  PushConstant         CP#21
+  PushConstant         CP#22
+  StoreIndexedTOS
+  Push                 r5
+  PushConstant         CP#1
+  Push                 r4
+  StoreIndexedTOS
+  Push                 r5
+  PushConstant         CP#3
+  PushConstant         CP#23
+  StoreIndexedTOS
+  Push                 r5
+  PushConstant         CP#11
+  Push                 r0
+  LoadFieldTOS         CP#2
+  StoreIndexedTOS
+  PushConstant         CP#24
+  IndirectStaticCall   1, CP#7
+  PushConstant         CP#25
+  IndirectStaticCall   1, CP#7
+  Drop1
+  Allocate             CP#14
+  StoreLocal           r5
+  Push                 r5
+  PushConstant         CP#12
+  StoreFieldTOS        CP#15
+  Push                 r5
+  PushConstant         CP#12
+  StoreFieldTOS        CP#16
+  Push                 r5
+  PushConstant         CP#26
+  StoreFieldTOS        CP#17
+  Push                 r5
+  Push                 r0
+  StoreFieldTOS        CP#5
+  PopLocal             r6
+  Push                 r6
+  ReturnTOS
+  Push                 r0
+  LoadFieldTOS         CP#0
+  PopLocal             r0
+  Jump                 L1
+L1:
+  Push                 r0
+  LoadFieldTOS         CP#0
+  PopLocal             r0
+  PushConstant         CP#12
+  ReturnTOS
+}
+ExceptionsTable {
+  try-index 0, outer -1, start 13, end 49, handler 49, needs-stack-trace, types [CP#9]
+}
+ConstantPool {
+  [0] = ContextOffset parent
+  [1] = Int 1
+  [2] = ContextOffset var [0]
+  [3] = Int 2
+  [4] = ClosureFunction foo () → void;
+  [5] = FieldOffset dart.core::_Closure::_context
+  [6] = String 'danger foo'
+  [7] = ArgDesc num-args 1, num-type-args 0, names []
+  [8] = StaticICData target 'dart.core::print', arg-desc CP#7
+  [9] = Type dynamic
+  [10] = StaticICData target 'dart.core::print', arg-desc CP#7
+  [11] = Int 3
+  [12] = Null
+  [13] = EndClosureFunctionScope
+  [14] = Class dart.core::_Closure
+  [15] = FieldOffset dart.core::_Closure::_instantiator_type_arguments
+  [16] = FieldOffset dart.core::_Closure::_function_type_arguments
+  [17] = FieldOffset dart.core::_Closure::_function
+  [18] = ICData target-name 'call', arg-desc CP#7
+  [19] = StaticICData target 'dart.core::print', arg-desc CP#7
+  [20] = Int 4
+  [21] = Int 0
+  [22] = String 'caught '
+  [23] = String ' '
+  [24] = StaticICData target 'dart.core::_StringBase::_interpolate', arg-desc CP#7
+  [25] = StaticICData target 'dart.core::print', arg-desc CP#7
+  [26] = ClosureFunction bar () → void;
+  [27] = String 'danger bar'
+  [28] = StaticICData target 'dart.core::print', arg-desc CP#7
+  [29] = Type dart.core::Error
+  [30] = ArgDesc num-args 4, num-type-args 0, names []
+  [31] = ICData target-name '_instanceOf', arg-desc CP#30
+  [32] = Bool true
+  [33] = String 'error '
+  [34] = String ', captured stack trace: '
+  [35] = StaticICData target 'dart.core::_StringBase::_interpolate', arg-desc CP#7
+  [36] = StaticICData target 'dart.core::print', arg-desc CP#7
+  [37] = EndClosureFunctionScope
+}
+Closure CP#4 {
+  Entry                6
+  CheckStack
+  Push                 FP[-5]
+  LoadFieldTOS         CP#5
+  PopLocal             r0
+  Push                 r0
+  PopLocal             r2
+Try #0 start:
+  PushConstant         CP#6
+  PushConstant         CP#8
+  IndirectStaticCall   1, CP#7
+  Drop1
+  Jump                 L1
+Try #0 end:
+Try #0 handler:
+  Push                 r2
+  PopLocal             r0
+  MoveSpecial          r2, exception
+  MoveSpecial          r3, stackTrace
+  Push                 r2
+  PopLocal             r4
+  Push                 r0
+  LoadFieldTOS         CP#0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#10
+  IndirectStaticCall   1, CP#7
+  Drop1
+  Push                 r0
+  PushConstant         CP#11
+  StoreLocal           r5
+  StoreFieldTOS        CP#2
+  Push                 r5
+  Drop1
+  Jump                 L1
+L1:
+  PushConstant         CP#12
+  ReturnTOS
+
+}
+
+Closure CP#26 {
+  Entry                6
+  CheckStack
+  Push                 FP[-5]
+  LoadFieldTOS         CP#5
+  PopLocal             r0
+  Push                 r0
+  PopLocal             r2
+Try #0 start:
+  PushConstant         CP#27
+  PushConstant         CP#28
+  IndirectStaticCall   1, CP#7
+  Drop1
+  Jump                 L1
+Try #0 end:
+Try #0 handler:
+  Push                 r2
+  PopLocal             r0
+  MoveSpecial          r2, exception
+  MoveSpecial          r3, stackTrace
+  Push                 r2
+  PushConstant         CP#12
+  PushConstant         CP#12
+  PushConstant         CP#29
+  InstanceCall1        4, CP#31
+  PushConstant         CP#32
+  IfNeStrictTOS
+  Jump                 L2
+  Push                 r2
+  PopLocal             r4
+  PushConstant         CP#12
+  PushConstant         CP#20
+  CreateArrayTOS
+  StoreLocal           r5
+  Push                 r5
+  PushConstant         CP#21
+  PushConstant         CP#33
+  StoreIndexedTOS
+  Push                 r5
+  PushConstant         CP#1
+  Push                 r4
+  StoreIndexedTOS
+  Push                 r5
+  PushConstant         CP#3
+  PushConstant         CP#34
+  StoreIndexedTOS
+  Push                 r5
+  PushConstant         CP#11
+  Push                 r0
+  LoadFieldTOS         CP#2
+  StoreIndexedTOS
+  PushConstant         CP#35
+  IndirectStaticCall   1, CP#7
+  PushConstant         CP#36
+  IndirectStaticCall   1, CP#7
+  Drop1
+  Jump                 L1
+L2:
+  Push                 r2
+  Push                 r3
+  Throw                1
+L1:
+  PushConstant         CP#12
+  ReturnTOS
+
+}
+]static method testTryCatch3() → dynamic {
+  core::int x = 1;
+  try {
+    core::int y = 2;
+    function foo() → void {
+      try {
+        core::print("danger foo");
+      }
+      on dynamic catch(final dynamic e) {
+        core::print(x);
+        y = 3;
+      }
+    }
+    foo.call();
+    core::print(y);
+  }
+  on dynamic catch(final dynamic e, final core::StackTrace st) {
+    core::print("caught ${e} ${st}");
+    function bar() → void {
+      try {
+        core::print("danger bar");
+      }
+      on core::Error catch(final core::Error e) {
+        core::print("error ${e}, captured stack trace: ${st}");
+      }
+    }
+    return bar;
+  }
+}
+[@vm.bytecode=
+Bytecode {
+  Entry                8
+  CheckStack
+Try #0 start:
+Try #1 start:
+  PushConstant         CP#0
+  PushConstant         CP#2
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L1
+Try #1 end:
+Try #1 handler:
+  MoveSpecial          r2, exception
+  MoveSpecial          r3, stackTrace
+  Push                 r2
+  PopLocal             r4
+Try #2 start:
+  PushConstant         CP#4
+  PushConstant         CP#5
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Push                 FP[-5]
+  PushConstant         CP#6
+  IfNeStrictTOS
+  Jump                 L2
+  Push                 r2
+  Push                 r3
+  Throw                1
+  Drop1
+L2:
+  Jump                 L3
+Try #2 end:
+Try #2 handler:
+  MoveSpecial          r5, exception
+  MoveSpecial          r6, stackTrace
+  Push                 r5
+  PopLocal             r7
+  PushConstant         CP#7
+  PushConstant         CP#8
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L3
+L3:
+  Jump                 L1
+L1:
+  Jump                 L4
+Try #0 end:
+Try #0 handler:
+  MoveSpecial          r0, exception
+  MoveSpecial          r1, stackTrace
+  Push                 r0
+  PopLocal             r2
+  Push                 r1
+  PopLocal             r3
+  PushConstant         CP#9
+  PushConstant         CP#10
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Push                 r3
+  PushConstant         CP#11
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L4
+L4:
+  PushConstant         CP#12
+  ReturnTOS
+}
+ExceptionsTable {
+  try-index 0, outer -1, start 2, end 35, handler 35, needs-stack-trace, types [CP#3]
+  try-index 1, outer 0, start 2, end 7, handler 7, needs-stack-trace, types [CP#3]
+  try-index 2, outer 0, start 11, end 24, handler 24, types [CP#3]
+}
+ConstantPool {
+  [0] = String 'try 1 > try 2'
+  [1] = ArgDesc num-args 1, num-type-args 0, names []
+  [2] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [3] = Type dynamic
+  [4] = String 'try 1 > catch 2 > try 3'
+  [5] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [6] = Bool true
+  [7] = String 'try 1 > catch 2 > catch 3'
+  [8] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [9] = String 'catch 1'
+  [10] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [11] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [12] = Null
+}
+]static method testRethrow(core::bool cond) → dynamic {
+  try {
+    try {
+      core::print("try 1 > try 2");
+    }
+    on dynamic catch(final dynamic e) {
+      try {
+        core::print("try 1 > catch 2 > try 3");
+        if(cond) {
+          rethrow;
+        }
+      }
+      on dynamic catch(final dynamic e) {
+        core::print("try 1 > catch 2 > catch 3");
+      }
+    }
+  }
+  on dynamic catch(final dynamic e, final core::StackTrace st) {
+    core::print("catch 1");
+    core::print(st);
+  }
+}
+[@vm.bytecode=
+Bytecode {
+  Entry                3
+  CheckStack
+  PushConstant         CP#0
+  PopLocal             r0
+L5:
+  CheckStack
+  Push                 r0
+  PushConstant         CP#1
+  InstanceCall1        2, CP#3
+  PushConstant         CP#4
+  IfNeStrictTOS
+  Jump                 L1
+Try #0 start:
+  Push                 r0
+  PushConstant         CP#5
+  InstanceCall1        2, CP#6
+  PushConstant         CP#4
+  IfNeStrictTOS
+  Jump                 L2
+  Jump                 L3
+L2:
+  Jump                 L4
+Try #0 end:
+Try #0 handler:
+  MoveSpecial          r1, exception
+  MoveSpecial          r2, stackTrace
+  Push                 r0
+  PushConstant         CP#9
+  IndirectStaticCall   1, CP#8
+  Drop1
+  Push                 r1
+  Push                 r2
+  Throw                1
+L3:
+  Push                 r0
+  PushConstant         CP#10
+  IndirectStaticCall   1, CP#8
+  Drop1
+  Jump                 L1
+L4:
+  Push                 r0
+  PushConstant         CP#11
+  IndirectStaticCall   1, CP#8
+  Drop1
+  Push                 r0
+  PushConstant         CP#12
+  InstanceCall1        2, CP#13
+  StoreLocal           r0
+  Drop1
+  Jump                 L5
+L1:
+  PushConstant         CP#14
+  ReturnTOS
+}
+ExceptionsTable {
+  try-index 0, outer -1, start 11, end 19, handler 19, needs-stack-trace, types [CP#7]
+}
+ConstantPool {
+  [0] = Int 0
+  [1] = Int 10
+  [2] = ArgDesc num-args 2, num-type-args 0, names []
+  [3] = ICData target-name '<', arg-desc CP#2
+  [4] = Bool true
+  [5] = Int 5
+  [6] = ICData target-name '>', arg-desc CP#2
+  [7] = Type dynamic
+  [8] = ArgDesc num-args 1, num-type-args 0, names []
+  [9] = StaticICData target 'dart.core::print', arg-desc CP#8
+  [10] = StaticICData target 'dart.core::print', arg-desc CP#8
+  [11] = StaticICData target 'dart.core::print', arg-desc CP#8
+  [12] = Int 1
+  [13] = ICData target-name '+', arg-desc CP#2
+  [14] = Null
+}
+]static method testTryFinally1() → dynamic {
+  #L1:
+  for (core::int i = 0; i.{core::num::<}(10); i = i.{core::num::+}(1)) {
+    try {
+      if(i.{core::num::>}(5)) {
+        break #L1;
+      }
+    }
+    finally {
+      core::print(i);
+    }
+  }
+}
+[@vm.bytecode=
+Bytecode {
+  Entry                9
+  CheckStack
+  AllocateContext      1
+  StoreLocal           r1
+  Push                 r1
+  Push                 r0
+  StoreFieldTOS        CP#0
+  PopLocal             r0
+  Push                 r0
+  Push                 FP[-5]
+  StoreFieldTOS        CP#1
+  Push                 r0
+  LoadFieldTOS         CP#1
+  PopLocal             r2
+  Push                 r2
+  PushConstant         CP#3
+  InstanceCall2        2, CP#4
+  PushConstant         CP#5
+  IfEqStrictTOS
+  Jump                 L1
+  Push                 r2
+  PushConstant         CP#6
+  InstanceCall2        2, CP#7
+  PushConstant         CP#5
+  IfEqStrictTOS
+  Jump                 L2
+  Jump                 L3
+L1:
+  Push                 r0
+  PopLocal             r3
+Try #0 start:
+  AllocateContext      1
+  StoreLocal           r1
+  Push                 r1
+  Push                 r0
+  StoreFieldTOS        CP#0
+  PopLocal             r0
+  PushConstant         CP#8
+  PushConstant         CP#10
+  IndirectStaticCall   1, CP#9
+  Drop1
+  Push                 r0
+  PushConstant         CP#11
+  StoreFieldTOS        CP#1
+  Push                 r0
+  PopLocal             r5
+Try #1 start:
+  PushConstant         CP#12
+  PushConstant         CP#13
+  IndirectStaticCall   1, CP#9
+  Drop1
+  Allocate             CP#20
+  StoreLocal           r8
+  Push                 r8
+  PushConstant         CP#18
+  StoreFieldTOS        CP#21
+  Push                 r8
+  PushConstant         CP#18
+  StoreFieldTOS        CP#22
+  Push                 r8
+  PushConstant         CP#14
+  StoreFieldTOS        CP#23
+  Push                 r8
+  Push                 r0
+  StoreFieldTOS        CP#15
+  PopLocal             r7
+  Push                 r7
+  InstanceCall1        1, CP#24
+  Drop1
+  Jump                 L4
+  Jump                 L5
+Try #1 end:
+Try #1 handler:
+  Push                 r5
+  PopLocal             r0
+  MoveSpecial          r5, exception
+  MoveSpecial          r6, stackTrace
+  PushConstant         CP#26
+  PushConstant         CP#27
+  IndirectStaticCall   1, CP#9
+  Drop1
+  Push                 r5
+  Push                 r6
+  Throw                1
+L4:
+  Push                 r5
+  PopLocal             r0
+  PushConstant         CP#26
+  PushConstant         CP#28
+  IndirectStaticCall   1, CP#9
+  Drop1
+  Jump                 L6
+L5:
+  Push                 r5
+  PopLocal             r0
+  PushConstant         CP#26
+  PushConstant         CP#29
+  IndirectStaticCall   1, CP#9
+  Drop1
+  PushConstant         CP#30
+  PushConstant         CP#31
+  IndirectStaticCall   1, CP#9
+  Drop1
+  Push                 r0
+  LoadFieldTOS         CP#0
+  PopLocal             r0
+  Jump                 L7
+Try #0 end:
+Try #0 handler:
+  Push                 r3
+  PopLocal             r0
+  MoveSpecial          r3, exception
+  MoveSpecial          r4, stackTrace
+  PushConstant         CP#32
+  PushConstant         CP#33
+  IndirectStaticCall   1, CP#9
+  Drop1
+  Push                 r3
+  Push                 r4
+  Throw                1
+L6:
+  Push                 r3
+  PopLocal             r0
+  PushConstant         CP#32
+  PushConstant         CP#34
+  IndirectStaticCall   1, CP#9
+  Drop1
+  Jump                 L2
+L7:
+  Push                 r3
+  PopLocal             r0
+  PushConstant         CP#32
+  PushConstant         CP#35
+  IndirectStaticCall   1, CP#9
+  Drop1
+  Jump                 L3
+L2:
+  PushConstant         CP#36
+  PushConstant         CP#37
+  IndirectStaticCall   1, CP#9
+  Drop1
+  Jump                 L3
+L3:
+  PushConstant         CP#18
+  ReturnTOS
+}
+ExceptionsTable {
+  try-index 0, outer -1, start 29, end 100, handler 100, needs-stack-trace, types [CP#25]
+  try-index 1, outer 0, start 44, end 68, handler 68, needs-stack-trace, types [CP#25]
+}
+ConstantPool {
+  [0] = ContextOffset parent
+  [1] = ContextOffset var [0]
+  [2] = ArgDesc num-args 2, num-type-args 0, names []
+  [3] = Int 1
+  [4] = ICData target-name '==', arg-desc CP#2
+  [5] = Bool true
+  [6] = Int 2
+  [7] = ICData target-name '==', arg-desc CP#2
+  [8] = String 'before try 1'
+  [9] = ArgDesc num-args 1, num-type-args 0, names []
+  [10] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [11] = Int 3
+  [12] = String 'try'
+  [13] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [14] = ClosureFunction foo () → void;
+  [15] = FieldOffset dart.core::_Closure::_context
+  [16] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [17] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [18] = Null
+  [19] = EndClosureFunctionScope
+  [20] = Class dart.core::_Closure
+  [21] = FieldOffset dart.core::_Closure::_instantiator_type_arguments
+  [22] = FieldOffset dart.core::_Closure::_function_type_arguments
+  [23] = FieldOffset dart.core::_Closure::_function
+  [24] = ICData target-name 'call', arg-desc CP#9
+  [25] = Type dynamic
+  [26] = String 'finally 1'
+  [27] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [28] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [29] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [30] = String 'after try 1'
+  [31] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [32] = String 'finally 2'
+  [33] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [34] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [35] = StaticICData target 'dart.core::print', arg-desc CP#9
+  [36] = String 'case 2'
+  [37] = StaticICData target 'dart.core::print', arg-desc CP#9
+}
+Closure CP#14 {
+  Entry                2
+  CheckStack
+  Push                 FP[-5]
+  LoadFieldTOS         CP#15
+  PopLocal             r0
+  Push                 r0
+  LoadFieldTOS         CP#0
+  LoadFieldTOS         CP#1
+  PushConstant         CP#16
+  IndirectStaticCall   1, CP#9
+  Drop1
+  Push                 r0
+  LoadFieldTOS         CP#1
+  PushConstant         CP#17
+  IndirectStaticCall   1, CP#9
+  Drop1
+  PushConstant         CP#18
+  ReturnTOS
+
+}
+]static method testTryFinally2(core::int x) → dynamic {
+  #L2:
+  switch(x) {
+    #L3:
+    case 1:
+      {
+        try {
+          core::print("before try 1");
+          core::int y = 3;
+          try {
+            core::print("try");
+            function foo() → void {
+              core::print(x);
+              core::print(y);
+            }
+            foo.call();
+            continue #L4;
+          }
+          finally {
+            core::print("finally 1");
+          }
+          core::print("after try 1");
+        }
+        finally {
+          core::print("finally 2");
+        }
+        break #L2;
+      }
+    #L4:
+    case 2:
+      {
+        core::print("case 2");
+        break #L2;
+      }
+  }
+}
+[@vm.bytecode=
+Bytecode {
+  Entry                6
+  CheckStack
+  AllocateContext      1
+  StoreLocal           r1
+  Push                 r1
+  Push                 r0
+  StoreFieldTOS        CP#0
+  PopLocal             r0
+  Push                 r0
+  PushConstant         CP#1
+  StoreFieldTOS        CP#2
+  PushConstant         CP#3
+  PopLocal             r2
+  Push                 r0
+  PopLocal             r3
+Try #0 start:
+  Allocate             CP#27
+  StoreLocal           r5
+  Push                 r5
+  PushConstant         CP#3
+  StoreFieldTOS        CP#28
+  Push                 r5
+  PushConstant         CP#3
+  StoreFieldTOS        CP#29
+  Push                 r5
+  PushConstant         CP#4
+  StoreFieldTOS        CP#30
+  Push                 r5
+  Push                 r0
+  StoreFieldTOS        CP#5
+  StoreLocal           r2
+  Drop1
+  Jump                 L1
+Try #0 end:
+Try #0 handler:
+  Push                 r3
+  PopLocal             r0
+  MoveSpecial          r3, exception
+  MoveSpecial          r4, stackTrace
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#31
+  IndirectStaticCall   1, CP#6
+  Drop1
+  Push                 r2
+  InstanceCall1        1, CP#32
+  Drop1
+  Push                 r3
+  Push                 r4
+  Throw                1
+L1:
+  Push                 r3
+  PopLocal             r0
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#33
+  IndirectStaticCall   1, CP#6
+  Drop1
+  Push                 r2
+  InstanceCall1        1, CP#34
+  Drop1
+  Push                 r0
+  LoadFieldTOS         CP#0
+  PopLocal             r0
+  PushConstant         CP#3
+  ReturnTOS
+}
+ExceptionsTable {
+  try-index 0, outer -1, start 15, end 32, handler 32, needs-stack-trace, types [CP#11]
+}
+ConstantPool {
+  [0] = ContextOffset parent
+  [1] = Int 11
+  [2] = ContextOffset var [0]
+  [3] = Null
+  [4] = ClosureFunction <anonymous closure> () → dart.core::int;
+  [5] = FieldOffset dart.core::_Closure::_context
+  [6] = ArgDesc num-args 1, num-type-args 0, names []
+  [7] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [8] = String 'try 1'
+  [9] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [10] = Int 42
+  [11] = Type dynamic
+  [12] = String 'try 2'
+  [13] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [14] = Int 43
+  [15] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [16] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [17] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [18] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [19] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [20] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [21] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [22] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [23] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [24] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [25] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [26] = EndClosureFunctionScope
+  [27] = Class dart.core::_Closure
+  [28] = FieldOffset dart.core::_Closure::_instantiator_type_arguments
+  [29] = FieldOffset dart.core::_Closure::_function_type_arguments
+  [30] = FieldOffset dart.core::_Closure::_function
+  [31] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [32] = ICData target-name 'call', arg-desc CP#6
+  [33] = StaticICData target 'dart.core::print', arg-desc CP#6
+  [34] = ICData target-name 'call', arg-desc CP#6
+}
+Closure CP#4 {
+  Entry                6
+  CheckStack
+  Push                 FP[-5]
+  LoadFieldTOS         CP#5
+  PopLocal             r0
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#7
+  IndirectStaticCall   1, CP#6
+  Drop1
+  Push                 r0
+  PopLocal             r2
+Try #0 start:
+  PushConstant         CP#8
+  PushConstant         CP#9
+  IndirectStaticCall   1, CP#6
+  Drop1
+  PushConstant         CP#10
+  Jump                 L1
+  Jump                 L2
+Try #0 end:
+Try #0 handler:
+  Push                 r2
+  PopLocal             r0
+  MoveSpecial          r2, exception
+  MoveSpecial          r3, stackTrace
+  Push                 r0
+  PopLocal             r4
+Try #1 start:
+  PushConstant         CP#12
+  PushConstant         CP#13
+  IndirectStaticCall   1, CP#6
+  Drop1
+  PushConstant         CP#14
+  Jump                 L3
+  Jump                 L4
+Try #1 end:
+Try #1 handler:
+  Push                 r4
+  PopLocal             r0
+  MoveSpecial          r4, exception
+  MoveSpecial          r5, stackTrace
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#15
+  IndirectStaticCall   1, CP#6
+  Drop1
+  Push                 r4
+  Push                 r5
+  Throw                1
+L3:
+  Push                 r4
+  PopLocal             r0
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#16
+  IndirectStaticCall   1, CP#6
+  Drop1
+  ReturnTOS
+L4:
+  Push                 r4
+  PopLocal             r0
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#17
+  IndirectStaticCall   1, CP#6
+  Drop1
+  Push                 r2
+  Push                 r3
+  Throw                1
+L1:
+  Push                 r2
+  PopLocal             r0
+  Push                 r0
+  PopLocal             r4
+Try #2 start:
+  PushConstant         CP#12
+  PushConstant         CP#18
+  IndirectStaticCall   1, CP#6
+  Drop1
+  PushConstant         CP#14
+  Jump                 L5
+  Jump                 L6
+Try #2 end:
+Try #2 handler:
+  Push                 r4
+  PopLocal             r0
+  MoveSpecial          r4, exception
+  MoveSpecial          r5, stackTrace
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#19
+  IndirectStaticCall   1, CP#6
+  Drop1
+  Push                 r4
+  Push                 r5
+  Throw                1
+L5:
+  Push                 r4
+  PopLocal             r0
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#20
+  IndirectStaticCall   1, CP#6
+  Drop1
+  ReturnTOS
+L6:
+  Push                 r4
+  PopLocal             r0
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#21
+  IndirectStaticCall   1, CP#6
+  Drop1
+  ReturnTOS
+L2:
+  Push                 r2
+  PopLocal             r0
+  Push                 r0
+  PopLocal             r4
+Try #3 start:
+  PushConstant         CP#12
+  PushConstant         CP#22
+  IndirectStaticCall   1, CP#6
+  Drop1
+  PushConstant         CP#14
+  Jump                 L7
+  Jump                 L8
+Try #3 end:
+Try #3 handler:
+  Push                 r4
+  PopLocal             r0
+  MoveSpecial          r4, exception
+  MoveSpecial          r5, stackTrace
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#23
+  IndirectStaticCall   1, CP#6
+  Drop1
+  Push                 r4
+  Push                 r5
+  Throw                1
+L7:
+  Push                 r4
+  PopLocal             r0
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#24
+  IndirectStaticCall   1, CP#6
+  Drop1
+  ReturnTOS
+L8:
+  Push                 r4
+  PopLocal             r0
+  Push                 r0
+  LoadFieldTOS         CP#2
+  PushConstant         CP#25
+  IndirectStaticCall   1, CP#6
+  Drop1
+  PushConstant         CP#3
+  ReturnTOS
+
+}
+]static method testTryFinally3() → dynamic {
+  core::int x = 11;
+  dynamic y;
+  try {
+    y = () → core::int {
+      core::print(x);
+      try {
+        core::print("try 1");
+        return 42;
+      }
+      finally {
+        try {
+          core::print("try 2");
+          return 43;
+        }
+        finally {
+          core::print(x);
+        }
+      }
+    };
+  }
+  finally {
+    core::print(x);
+    y.call();
+  }
+}
+[@vm.bytecode=
+Bytecode {
+  Entry                5
+  CheckStack
+Try #0 start:
+Try #1 start:
+  PushConstant         CP#0
+  PushConstant         CP#2
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L1
+Try #1 end:
+Try #1 handler:
+  MoveSpecial          r2, exception
+  MoveSpecial          r3, stackTrace
+  Push                 r2
+  PopLocal             r4
+  PushConstant         CP#4
+  PushConstant         CP#5
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Jump                 L1
+L1:
+  Jump                 L2
+Try #0 end:
+Try #0 handler:
+  MoveSpecial          r0, exception
+  MoveSpecial          r1, stackTrace
+  PushConstant         CP#6
+  PushConstant         CP#7
+  IndirectStaticCall   1, CP#1
+  Drop1
+  Push                 r0
+  Push                 r1
+  Throw                1
+L2:
+  PushConstant         CP#6
+  PushConstant         CP#8
+  IndirectStaticCall   1, CP#1
+  Drop1
+  PushConstant         CP#9
+  ReturnTOS
+}
+ExceptionsTable {
+  try-index 0, outer -1, start 2, end 17, handler 17, needs-stack-trace, types [CP#3]
+  try-index 1, outer 0, start 2, end 7, handler 7, types [CP#3]
+}
+ConstantPool {
+  [0] = String 'try'
+  [1] = ArgDesc num-args 1, num-type-args 0, names []
+  [2] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [3] = Type dynamic
+  [4] = String 'catch'
+  [5] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [6] = String 'finally'
+  [7] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [8] = StaticICData target 'dart.core::print', arg-desc CP#1
+  [9] = Null
+}
+]static method testTryCatchFinally() → dynamic {
+  try
+    try {
+      core::print("try");
+    }
+    on dynamic catch(final dynamic e) {
+      core::print("catch");
+    }
+  finally {
+    core::print("finally");
+  }
+}
+[@vm.bytecode=
+Bytecode {
+  Entry                0
+  CheckStack
+  PushConstant         CP#0
+  ReturnTOS
+}
+ConstantPool {
+  [0] = Null
+}
+]static method main() → dynamic {}