[vm/bytecode] Emit source positions for bytecode instructions

Change-Id: I83963771db301089317823f6bf14eb4a2728dbfb
Reviewed-on: https://dart-review.googlesource.com/c/81740
Auto-Submit: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Régis Crelier <regis@google.com>
Reviewed-by: Régis Crelier <regis@google.com>
diff --git a/pkg/vm/bin/gen_kernel.dart b/pkg/vm/bin/gen_kernel.dart
index 86333d7..bdb5df6 100644
--- a/pkg/vm/bin/gen_kernel.dart
+++ b/pkg/vm/bin/gen_kernel.dart
@@ -44,6 +44,8 @@
       help: 'Whether kernel constant evaluation will be enabled.',
       defaultsTo: true)
   ..addFlag('gen-bytecode', help: 'Generate bytecode', defaultsTo: false)
+  ..addFlag('emit-bytecode-source-positions',
+      help: 'Emit source positions in bytecode', defaultsTo: false)
   ..addFlag('drop-ast',
       help: 'Drop AST for members with bytecode', defaultsTo: false)
   ..addFlag('use-future-bytecode-format',
@@ -83,6 +85,8 @@
   final bool aot = options['aot'];
   final bool tfa = options['tfa'];
   final bool genBytecode = options['gen-bytecode'];
+  final bool emitBytecodeSourcePositions =
+      options['emit-bytecode-source-positions'];
   final bool dropAST = options['drop-ast'];
   final bool useFutureBytecodeFormat = options['use-future-bytecode-format'];
   final bool enableAsserts = options['enable-asserts'];
@@ -115,6 +119,7 @@
       useGlobalTypeFlowAnalysis: tfa,
       environmentDefines: environmentDefines,
       genBytecode: genBytecode,
+      emitBytecodeSourcePositions: emitBytecodeSourcePositions,
       dropAST: dropAST,
       useFutureBytecodeFormat: useFutureBytecodeFormat,
       enableAsserts: enableAsserts,
diff --git a/pkg/vm/lib/bytecode/assembler.dart b/pkg/vm/lib/bytecode/assembler.dart
index 3309d43..2bf1127 100644
--- a/pkg/vm/lib/bytecode/assembler.dart
+++ b/pkg/vm/lib/bytecode/assembler.dart
@@ -6,8 +6,11 @@
 
 import 'dart:typed_data';
 
+import 'package:kernel/ast.dart' show TreeNode;
+
 import 'dbc.dart';
 import 'exceptions.dart' show ExceptionsTable;
+import 'source_positions.dart' show SourcePositions;
 
 class Label {
   final bool allowsBackwardJumps;
@@ -48,7 +51,9 @@
   final Uint32List _encodeBufferIn;
   final Uint8List _encodeBufferOut;
   final ExceptionsTable exceptionsTable = new ExceptionsTable();
+  final SourcePositions sourcePositions = new SourcePositions();
   bool isUnreachable = false;
+  int currentSourcePosition = TreeNode.noOffset;
 
   BytecodeAssembler._(this._encodeBufferIn, this._encodeBufferOut);
 
@@ -70,6 +75,12 @@
     }
   }
 
+  void emitSourcePosition() {
+    if (currentSourcePosition != TreeNode.noOffset && !isUnreachable) {
+      sourcePositions.add(offsetInWords, currentSourcePosition);
+    }
+  }
+
   void emitWord(int word) {
     if (isUnreachable) {
       return;
@@ -150,6 +161,7 @@
 
   void emitBytecode0(Opcode opcode) {
     assert(BytecodeFormats[opcode].encoding == Encoding.k0);
+    emitSourcePosition();
     emitWord(_encode0(opcode));
   }
 
@@ -255,18 +267,22 @@
   }
 
   void emitIndirectStaticCall(int ra, int rd) {
+    emitSourcePosition();
     emitWord(_encodeAD(Opcode.kIndirectStaticCall, ra, rd));
   }
 
   void emitInstanceCall(int ra, int rd) {
+    emitSourcePosition();
     emitWord(_encodeAD(Opcode.kInstanceCall, ra, rd));
   }
 
   void emitNativeCall(int rd) {
+    emitSourcePosition();
     emitWord(_encodeD(Opcode.kNativeCall, rd));
   }
 
   void emitStoreStaticTOS(int rd) {
+    emitSourcePosition();
     emitWord(_encodeD(Opcode.kStoreStaticTOS, rd));
   }
 
@@ -279,10 +295,12 @@
   }
 
   void emitAllocate(int rd) {
+    emitSourcePosition();
     emitWord(_encodeD(Opcode.kAllocate, rd));
   }
 
   void emitAllocateT() {
+    emitSourcePosition();
     emitWord(_encode0(Opcode.kAllocateT));
   }
 
@@ -291,6 +309,7 @@
   }
 
   void emitStoreFieldTOS(int rd) {
+    emitSourcePosition();
     emitWord(_encodeD(Opcode.kStoreFieldTOS, rd));
   }
 
@@ -323,6 +342,7 @@
   }
 
   void emitThrow(int ra) {
+    emitSourcePosition();
     emitWord(_encodeA(Opcode.kThrow, ra));
     isUnreachable = true;
   }
@@ -352,30 +372,37 @@
   }
 
   void emitInstantiateType(int rd) {
+    emitSourcePosition();
     emitWord(_encodeD(Opcode.kInstantiateType, rd));
   }
 
   void emitInstantiateTypeArgumentsTOS(int ra, int rd) {
+    emitSourcePosition();
     emitWord(_encodeAD(Opcode.kInstantiateTypeArgumentsTOS, ra, rd));
   }
 
   void emitAssertAssignable(int ra, int rd) {
+    emitSourcePosition();
     emitWord(_encodeAD(Opcode.kAssertAssignable, ra, rd));
   }
 
   void emitAssertSubtype() {
+    emitSourcePosition();
     emitWord(_encode0(Opcode.kAssertSubtype));
   }
 
   void emitAssertBoolean(int ra) {
+    emitSourcePosition();
     emitWord(_encodeA(Opcode.kAssertBoolean, ra));
   }
 
   void emitCheckStack(int ra) {
+    emitSourcePosition();
     emitWord(_encodeA(Opcode.kCheckStack, ra));
   }
 
   void emitCheckFunctionTypeArgs(int ra, int rd) {
+    emitSourcePosition();
     emitWord(_encodeAD(Opcode.kCheckFunctionTypeArgs, ra, rd));
   }
 
diff --git a/pkg/vm/lib/bytecode/disassembler.dart b/pkg/vm/lib/bytecode/disassembler.dart
index c9b6e46..afe5a82 100644
--- a/pkg/vm/lib/bytecode/disassembler.dart
+++ b/pkg/vm/lib/bytecode/disassembler.dart
@@ -24,10 +24,14 @@
   Map<int, String> _labels;
   Map<int, List<String>> _markers;
 
-  String disassemble(List<int> bytecode, ExceptionsTable exceptionsTable) {
+  String disassemble(List<int> bytecode, ExceptionsTable exceptionsTable,
+      {List<Map<int, String>> annotations}) {
     _init(bytecode);
     _scanForJumpTargets();
     _markTryBlocks(exceptionsTable);
+    if (annotations != null) {
+      _markAnnotations(annotations);
+    }
     return _disasm();
   }
 
@@ -116,6 +120,14 @@
     }
   }
 
+  void _markAnnotations(List<Map<int, String>> annotations) {
+    for (var map in annotations) {
+      map.forEach((int pc, String annotation) {
+        _addMarker(pc, '# $annotation');
+      });
+    }
+  }
+
   void _addMarker(int pc, String marker) {
     final markers = (_markers[pc] ??= <String>[]);
     markers.add(marker);
diff --git a/pkg/vm/lib/bytecode/gen_bytecode.dart b/pkg/vm/lib/bytecode/gen_bytecode.dart
index 12c4793f..36ef9d4 100644
--- a/pkg/vm/lib/bytecode/gen_bytecode.dart
+++ b/pkg/vm/lib/bytecode/gen_bytecode.dart
@@ -38,7 +38,8 @@
 
 void generateBytecode(Component component,
     {bool dropAST: false,
-    bool omitSourcePositions: false,
+    bool emitSourcePositions: false,
+    bool omitAssertSourcePositions: false,
     bool useFutureBytecodeFormat: false,
     Map<String, String> environmentDefines,
     ErrorReporter errorReporter}) {
@@ -57,7 +58,8 @@
           hierarchy,
           typeEnvironment,
           constantsBackend,
-          omitSourcePositions,
+          emitSourcePositions,
+          omitAssertSourcePositions,
           useFutureBytecodeFormat,
           errorReporter)
       .visitComponent(component);
@@ -72,7 +74,8 @@
   final ClassHierarchy hierarchy;
   final TypeEnvironment typeEnvironment;
   final ConstantsBackend constantsBackend;
-  final bool omitSourcePositions;
+  final bool emitSourcePositions;
+  final bool omitAssertSourcePositions;
   final bool useFutureBytecodeFormat;
   final ErrorReporter errorReporter;
   final BytecodeMetadataRepository metadata = new BytecodeMetadataRepository();
@@ -111,7 +114,8 @@
       this.hierarchy,
       this.typeEnvironment,
       this.constantsBackend,
-      this.omitSourcePositions,
+      this.emitSourcePositions,
+      this.omitAssertSourcePositions,
       this.useFutureBytecodeFormat,
       this.errorReporter)
       : recognizedMethods = new RecognizedMethods(typeEnvironment) {
@@ -150,7 +154,7 @@
           if (node.isConst) {
             _genPushConstExpr(node.initializer);
           } else {
-            node.initializer.accept(this);
+            _generateNode(node.initializer);
           }
           _genReturnTOS();
           end(node);
@@ -168,7 +172,7 @@
           }
           _genNativeCall(nativeName);
         } else {
-          node.function?.body?.accept(this);
+          _generateNode(node.function?.body);
           // BytecodeAssembler eliminates this bytecode if it is unreachable.
           asm.emitPushNull();
         }
@@ -287,6 +291,26 @@
   Procedure get unsafeCast => _unsafeCast ??=
       libraryIndex.getTopLevelMember('dart:_internal', 'unsafeCast');
 
+  void _recordSourcePosition(TreeNode node) {
+    if (emitSourcePositions) {
+      asm.currentSourcePosition = node.fileOffset;
+    }
+  }
+
+  void _generateNode(TreeNode node) {
+    if (node == null) {
+      return;
+    }
+    final savedSourcePosition = asm.currentSourcePosition;
+    _recordSourcePosition(node);
+    node.accept(this);
+    asm.currentSourcePosition = savedSourcePosition;
+  }
+
+  void _generateNodeList(List<TreeNode> nodes) {
+    nodes.forEach(_generateNode);
+  }
+
   void _genConstructorInitializers(Constructor node) {
     final bool isRedirecting =
         node.initializers.any((init) => init is RedirectingInitializer);
@@ -300,7 +324,7 @@
       }
     }
 
-    visitList(node.initializers, this);
+    _generateNodeList(node.initializers);
 
     if (!isRedirecting) {
       nullableFields = <Reference>[];
@@ -321,7 +345,7 @@
     }
 
     _genPushReceiver();
-    initializer.accept(this);
+    _generateNode(initializer);
 
     final int cpIndex = cp.add(new ConstantInstanceField(field));
     asm.emitStoreFieldTOS(cpIndex);
@@ -333,9 +357,9 @@
     if (arguments.types.isNotEmpty) {
       _genTypeArguments(arguments.types);
     }
-    receiver?.accept(this);
-    visitList(arguments.positional, this);
-    arguments.named.forEach((NamedExpression ne) => ne.value.accept(this));
+    _generateNode(receiver);
+    _generateNodeList(arguments.positional);
+    arguments.named.forEach((NamedExpression ne) => _generateNode(ne.value));
   }
 
   void _genPushBool(bool value) {
@@ -636,7 +660,7 @@
       condition = (condition as Not).operand;
       negated = true;
     }
-    condition.accept(this);
+    _generateNode(condition);
     asm.emitAssertBoolean(0);
     return negated;
   }
@@ -804,6 +828,7 @@
     locals.enterScope(node);
     assert(!locals.isSyncYieldingFrame);
 
+    _recordSourcePosition(node);
     _genPrologue(node, node.function);
     _setupInitialContext(node.function);
     if (node is Procedure && node.isInstanceMember) {
@@ -836,8 +861,14 @@
       final formatVersion = useFutureBytecodeFormat
           ? futureBytecodeFormatVersion
           : stableBytecodeFormatVersion;
-      metadata.mapping[node] = new BytecodeMetadata(formatVersion, cp,
-          asm.bytecode, asm.exceptionsTable, nullableFields, closures);
+      metadata.mapping[node] = new BytecodeMetadata(
+          formatVersion,
+          cp,
+          asm.bytecode,
+          asm.exceptionsTable,
+          asm.sourcePositions,
+          nullableFields,
+          closures);
     }
 
     typeEnvironment.thisType = null;
@@ -1270,7 +1301,7 @@
 
     // TODO(alexmarkov): support --causal_async_stacks.
 
-    function.body.accept(this);
+    _generateNode(function.body);
 
     // BytecodeAssembler eliminates this bytecode if it is unreachable.
     asm.emitPushNull();
@@ -1295,8 +1326,8 @@
 
     locals.leaveScope();
 
-    closures.add(new ClosureBytecode(
-        closureFunctionIndex, asm.bytecode, asm.exceptionsTable));
+    closures.add(new ClosureBytecode(closureFunctionIndex, asm.bytecode,
+        asm.exceptionsTable, asm.sourcePositions));
 
     _popAssemblerState();
     yieldPoints = savedYieldPoints;
@@ -1518,7 +1549,7 @@
     for (Expression arg in args) {
       asm.emitPush(temp);
       _genPushInt(index++);
-      arg.accept(this);
+      _generateNode(arg);
       if (storeLastArgumentToTemp && index == totalCount) {
         // Arguments array in 'temp' is replaced with the last argument
         // in order to return result of RHS value in case of setter.
@@ -1560,7 +1591,7 @@
 
   @override
   visitAsExpression(AsExpression node) {
-    node.operand.accept(this);
+    _generateNode(node.operand);
 
     final type = node.type;
     if (typeEnvironment.isTop(type)) {
@@ -1594,12 +1625,12 @@
 
     _genConditionAndJumpIf(node.condition, false, otherwisePart);
 
-    node.then.accept(this);
+    _generateNode(node.then);
     asm.emitPopLocal(temp);
     asm.emitJump(done);
 
     asm.bind(otherwisePart);
-    node.otherwise.accept(this);
+    _generateNode(node.otherwise);
     asm.emitPopLocal(temp);
 
     asm.bind(done);
@@ -1653,7 +1684,7 @@
 
   @override
   visitDirectPropertyGet(DirectPropertyGet node) {
-    node.receiver.accept(this);
+    _generateNode(node.receiver);
     final target = node.target;
     if (target is Field || (target is Procedure && target.isGetter)) {
       _genStaticCall(target, new ConstantArgDesc(1), 1, isGet: true);
@@ -1668,8 +1699,8 @@
     final int temp = locals.tempIndexInFrame(node);
     final bool hasResult = !isExpressionWithoutResult(node);
 
-    node.receiver.accept(this);
-    node.value.accept(this);
+    _generateNode(node.receiver);
+    _generateNode(node.value);
 
     if (hasResult) {
       asm.emitStoreLocal(temp);
@@ -1696,7 +1727,7 @@
     final int newClosure = locals.tempIndexInFrame(node, tempIndex: 1);
     final int typeArguments = locals.tempIndexInFrame(node, tempIndex: 2);
 
-    node.expression.accept(this);
+    _generateNode(node.expression);
     asm.emitStoreLocal(oldClosure);
 
     _genTypeArguments(node.typeArguments);
@@ -1735,15 +1766,15 @@
 
   @override
   visitIsExpression(IsExpression node) {
-    node.operand.accept(this);
+    _generateNode(node.operand);
     _genInstanceOf(node.type);
   }
 
   @override
   visitLet(Let node) {
     _enterScope(node);
-    node.variable.accept(this);
-    node.body.accept(this);
+    _generateNode(node.variable);
+    _generateNode(node.body);
     _leaveScope();
   }
 
@@ -1767,7 +1798,7 @@
     for (int i = 0; i < node.expressions.length; i++) {
       asm.emitPush(temp);
       _genPushInt(i);
-      node.expressions[i].accept(this);
+      _generateNode(node.expressions[i]);
       asm.emitStoreIndexedTOS();
     }
 
@@ -1828,12 +1859,12 @@
         // key
         asm.emitPush(temp);
         _genPushInt(i * 2);
-        node.entries[i].key.accept(this);
+        _generateNode(node.entries[i].key);
         asm.emitStoreIndexedTOS();
         // value
         asm.emitPush(temp);
         _genPushInt(i * 2 + 1);
-        node.entries[i].value.accept(this);
+        _generateNode(node.entries[i].value);
         asm.emitStoreIndexedTOS();
       }
     }
@@ -1850,14 +1881,14 @@
     switch (opcode) {
       case Opcode.kEqualsNull:
         if (node.receiver is NullLiteral) {
-          node.arguments.positional.single.accept(this);
+          _generateNode(node.arguments.positional.single);
         } else {
-          node.receiver.accept(this);
+          _generateNode(node.receiver);
         }
         break;
 
       case Opcode.kNegateInt:
-        node.receiver.accept(this);
+        _generateNode(node.receiver);
         break;
 
       case Opcode.kAddInt:
@@ -1875,8 +1906,8 @@
       case Opcode.kCompareIntLt:
       case Opcode.kCompareIntGe:
       case Opcode.kCompareIntLe:
-        node.receiver.accept(this);
-        node.arguments.positional.single.accept(this);
+        _generateNode(node.receiver);
+        _generateNode(node.arguments.positional.single);
         break;
 
       default:
@@ -1909,7 +1940,7 @@
 
   @override
   visitPropertyGet(PropertyGet node) {
-    node.receiver.accept(this);
+    _generateNode(node.receiver);
     final argDescIndex = cp.add(new ConstantArgDesc(1));
     final icdataIndex = cp.add(new ConstantICData(
         InvocationKind.getter, node.name, argDescIndex,
@@ -1922,8 +1953,8 @@
     final int temp = locals.tempIndexInFrame(node);
     final bool hasResult = !isExpressionWithoutResult(node);
 
-    node.receiver.accept(this);
-    node.value.accept(this);
+    _generateNode(node.receiver);
+    _generateNode(node.value);
 
     if (hasResult) {
       asm.emitStoreLocal(temp);
@@ -1989,7 +2020,7 @@
           storeLastArgumentToTemp: hasResult);
     } else {
       _genPushReceiver();
-      node.value.accept(this);
+      _generateNode(node.value);
 
       if (hasResult) {
         asm.emitStoreLocal(temp);
@@ -2081,7 +2112,7 @@
       // The result of the unsafeCast() intrinsic method is its sole argument,
       // without any additional checks or type casts.
       assert(args.named.isEmpty);
-      args.positional.single.accept(this);
+      _generateNode(args.positional.single);
       return;
     }
     if (target.isFactory) {
@@ -2105,7 +2136,7 @@
   visitStaticSet(StaticSet node) {
     final bool hasResult = !isExpressionWithoutResult(node);
 
-    node.value.accept(this);
+    _generateNode(node.value);
 
     if (hasResult) {
       _genDupTOS(locals.tempIndexInFrame(node));
@@ -2124,7 +2155,7 @@
   @override
   visitStringConcatenation(StringConcatenation node) {
     if (node.expressions.length == 1) {
-      node.expressions.single.accept(this);
+      _generateNode(node.expressions.single);
       _genStaticCall(interpolateSingle, new ConstantArgDesc(1), 1);
     } else {
       asm.emitPushNull();
@@ -2137,7 +2168,7 @@
       for (int i = 0; i < node.expressions.length; i++) {
         asm.emitPush(temp);
         _genPushInt(i);
-        node.expressions[i].accept(this);
+        _generateNode(node.expressions[i]);
         asm.emitStoreIndexedTOS();
       }
 
@@ -2163,7 +2194,7 @@
 
   @override
   visitThrow(Throw node) {
-    node.expression.accept(this);
+    _generateNode(node.expression);
     asm.emitThrow(0);
   }
 
@@ -2197,7 +2228,7 @@
     if (locals.isCaptured(v)) {
       _genPushContextForVariable(v);
 
-      node.value.accept(this);
+      _generateNode(node.value);
 
       final int temp = locals.tempIndexInFrame(node);
       if (hasResult) {
@@ -2210,7 +2241,7 @@
         asm.emitPush(temp);
       }
     } else {
-      node.value.accept(this);
+      _generateNode(node.value);
 
       final int localIndex = locals.getVarIndexInFrame(v);
       if (hasResult) {
@@ -2243,11 +2274,11 @@
 
     _genConditionAndJumpIf(node.condition, true, done);
 
-    _genPushInt(omitSourcePositions ? 0 : node.conditionStartOffset);
-    _genPushInt(omitSourcePositions ? 0 : node.conditionEndOffset);
+    _genPushInt(omitAssertSourcePositions ? 0 : node.conditionStartOffset);
+    _genPushInt(omitAssertSourcePositions ? 0 : node.conditionEndOffset);
 
     if (node.message != null) {
-      node.message.accept(this);
+      _generateNode(node.message);
     } else {
       asm.emitPushNull();
     }
@@ -2261,7 +2292,7 @@
   @override
   visitBlock(Block node) {
     _enterScope(node);
-    visitList(node.statements, this);
+    _generateNodeList(node.statements);
     _leaveScope();
   }
 
@@ -2271,7 +2302,7 @@
     asm.emitJumpIfNoAsserts(done);
 
     _enterScope(node);
-    visitList(node.statements, this);
+    _generateNodeList(node.statements);
     _leaveScope();
 
     asm.bind(done);
@@ -2314,7 +2345,7 @@
 
     asm.emitCheckStack(++currentLoopDepth);
 
-    node.body.accept(this);
+    _generateNode(node.body);
 
     _genConditionAndJumpIf(node.condition, true, join);
 
@@ -2329,7 +2360,7 @@
   @override
   visitExpressionStatement(ExpressionStatement node) {
     final expr = node.expression;
-    expr.accept(this);
+    _generateNode(expr);
     if (!isExpressionWithoutResult(expr)) {
       asm.emitDrop1();
     }
@@ -2337,7 +2368,7 @@
 
   @override
   visitForInStatement(ForInStatement node) {
-    node.iterable.accept(this);
+    _generateNode(node.iterable);
 
     const kIterator = 'iterator'; // Iterable.iterator
     const kMoveNext = 'moveNext'; // Iterator.moveNext
@@ -2395,7 +2426,7 @@
 
     _genStoreVar(node.variable);
 
-    node.body.accept(this);
+    _generateNode(node.body);
 
     _leaveScope();
     asm.emitJump(join);
@@ -2408,7 +2439,7 @@
   visitForStatement(ForStatement node) {
     _enterScope(node);
     try {
-      visitList(node.variables, this);
+      _generateNodeList(node.variables);
 
       if (asm.isUnreachable) {
         // Bail out before binding a label which allows backward jumps,
@@ -2426,7 +2457,7 @@
         _genConditionAndJumpIf(node.condition, false, done);
       }
 
-      node.body.accept(this);
+      _generateNode(node.body);
 
       if (locals.currentContextSize > 0) {
         asm.emitPush(locals.contextVarIndexInFrame);
@@ -2435,7 +2466,7 @@
       }
 
       for (var update in node.updates) {
-        update.accept(this);
+        _generateNode(update);
         asm.emitDrop1();
       }
 
@@ -2461,13 +2492,13 @@
 
     _genConditionAndJumpIf(node.condition, false, otherwisePart);
 
-    node.then.accept(this);
+    _generateNode(node.then);
 
     if (node.otherwise != null) {
       final Label done = new Label();
       asm.emitJump(done);
       asm.bind(otherwisePart);
-      node.otherwise.accept(this);
+      _generateNode(node.otherwise);
       asm.bind(done);
     } else {
       asm.bind(otherwisePart);
@@ -2479,7 +2510,7 @@
     final label = new Label();
     labeledStatements[node] = label;
     contextLevels[node] = locals.currentContextLevel;
-    node.body.accept(this);
+    _generateNode(node.body);
     asm.bind(label);
     labeledStatements.remove(node);
     contextLevels.remove(node);
@@ -2492,18 +2523,18 @@
     final List<TryFinally> tryFinallyBlocks =
         _getEnclosingTryFinallyBlocks(node, null);
     if (tryFinallyBlocks.isEmpty) {
-      expr.accept(this);
+      _generateNode(expr);
       asm.emitReturnTOS();
     } else {
       if (expr is BasicLiteral) {
         _addFinallyBlocks(tryFinallyBlocks, () {
-          expr.accept(this);
+          _generateNode(expr);
           asm.emitReturnTOS();
         });
       } else {
         // Keep return value in a variable as try-catch statements
         // inside finally can zap expression stack.
-        node.expression.accept(this);
+        _generateNode(node.expression);
         asm.emitPopLocal(locals.returnVarIndexInFrame);
 
         _addFinallyBlocks(tryFinallyBlocks, () {
@@ -2518,7 +2549,7 @@
   visitSwitchStatement(SwitchStatement node) {
     contextLevels[node] = locals.currentContextLevel;
 
-    node.expression.accept(this);
+    _generateNode(node.expression);
 
     if (asm.isUnreachable) {
       // Bail out before binding labels which allow backward jumps,
@@ -2562,7 +2593,7 @@
       final Label caseLabel = caseLabels[i];
 
       asm.bind(caseLabel);
-      switchCase.body.accept(this);
+      _generateNode(switchCase.body);
 
       // Front-end issues a compile-time error if there is a fallthrough
       // between cases. Also, default case should be the last one.
@@ -2702,7 +2733,7 @@
     tryBlock.isSynthetic = node.isSynthetic;
     tryCatches[node] = tryBlock; // Used by rethrow.
 
-    node.body.accept(this);
+    _generateNode(node.body);
     asm.emitJump(done);
 
     _endTryBlock(node, tryBlock);
@@ -2741,7 +2772,7 @@
         _genStoreVar(catchClause.stackTrace);
       }
 
-      catchClause.body.accept(this);
+      _generateNode(catchClause.body);
 
       _leaveScope();
       asm.emitJump(done);
@@ -2769,7 +2800,7 @@
     final TryBlock tryBlock = _startTryBlock(node);
     finallyBlocks[node] = <FinallyBlock>[];
 
-    node.body.accept(this);
+    _generateNode(node.body);
 
     if (!asm.isUnreachable) {
       final normalContinuation = new FinallyBlock(() {
@@ -2783,7 +2814,7 @@
 
     tryBlock.types.add(cp.add(new ConstantType(const DynamicType())));
 
-    node.finalizer.accept(this);
+    _generateNode(node.finalizer);
 
     tryBlock.needsStackTrace = true; // For rethrowing.
     _genRethrow(node);
@@ -2791,7 +2822,7 @@
     for (var finallyBlock in finallyBlocks[node]) {
       asm.bind(finallyBlock.entry);
       _restoreContextForTryBlock(node);
-      node.finalizer.accept(this);
+      _generateNode(node.finalizer);
       finallyBlock.generateContinuation();
     }
 
@@ -2809,7 +2840,7 @@
         _genPushContextForVariable(node);
       }
       if (node.initializer != null) {
-        node.initializer.accept(this);
+        _generateNode(node.initializer);
       } else {
         asm.emitPushNull();
       }
@@ -2837,7 +2868,7 @@
 
     _genConditionAndJumpIf(node.condition, false, done);
 
-    node.body.accept(this);
+    _generateNode(node.body);
 
     asm.emitJump(join);
     --currentLoopDepth;
@@ -2874,7 +2905,7 @@
 
     // return <expression>
     // Note: finally blocks are *not* executed on the way out.
-    node.expression.accept(this);
+    _generateNode(node.expression);
     asm.emitReturnTOS();
 
     asm.bind(continuationLabel);
@@ -2931,12 +2962,12 @@
 
   @override
   visitLocalInitializer(LocalInitializer node) {
-    node.variable.accept(this);
+    _generateNode(node.variable);
   }
 
   @override
   visitAssertInitializer(AssertInitializer node) {
-    node.statement.accept(this);
+    _generateNode(node.statement);
   }
 
   @override
diff --git a/pkg/vm/lib/bytecode/source_positions.dart b/pkg/vm/lib/bytecode/source_positions.dart
new file mode 100644
index 0000000..055f394
--- /dev/null
+++ b/pkg/vm/lib/bytecode/source_positions.dart
@@ -0,0 +1,174 @@
+// 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.source_positions;
+
+import 'dart:io' show BytesBuilder;
+
+/// Maintains mapping between bytecode instructions and source positions.
+class SourcePositions {
+  final Map<int, int> mapping = <int, int>{}; // PC -> fileOffset
+  int _lastPc = 0;
+  int _lastOffset = 0;
+
+  SourcePositions();
+
+  void add(int pc, int fileOffset) {
+    assert(pc > _lastPc);
+    assert(fileOffset >= 0);
+    if (fileOffset != _lastOffset) {
+      mapping[pc] = fileOffset;
+      _lastPc = pc;
+      _lastOffset = fileOffset;
+    }
+  }
+
+  List<int> toBytes() {
+    final write = new BufferedWriter();
+    write.writePackedUInt30(mapping.length);
+    final encodePC = new PackedUInt30DeltaEncoder();
+    final encodeOffset = new SLEB128DeltaEncoder();
+    mapping.forEach((int pc, int fileOffset) {
+      encodePC.write(write, pc);
+      encodeOffset.write(write, fileOffset);
+    });
+    return write.buffer.takeBytes();
+  }
+
+  SourcePositions.fromBytes(List<int> bytes) {
+    final reader = new BufferedReader(bytes);
+    final int length = reader.readPackedUInt30();
+    final decodePC = new PackedUInt30DeltaDecoder();
+    final decodeOffset = new SLEB128DeltaDecoder();
+    for (int i = 0; i < length; ++i) {
+      int pc = decodePC.read(reader);
+      int fileOffset = decodeOffset.read(reader);
+      add(pc, fileOffset);
+    }
+  }
+
+  @override
+  String toString() => mapping.toString();
+
+  Map<int, String> getBytecodeAnnotations() {
+    return mapping.map((int pc, int fileOffset) =>
+        new MapEntry(pc, 'source position $fileOffset'));
+  }
+}
+
+class BufferedWriter {
+  final BytesBuilder buffer = new BytesBuilder();
+
+  void writePackedUInt30(int value) {
+    if ((value >> 30) != 0) {
+      throw 'Value $value is out of range';
+    }
+    if (value < 0x80) {
+      buffer.addByte(value);
+    } else if (value < 0x4000) {
+      buffer.addByte((value >> 8) | 0x80);
+      buffer.addByte(value & 0xFF);
+    } else {
+      buffer.addByte((value >> 24) | 0xC0);
+      buffer.addByte((value >> 16) & 0xFF);
+      buffer.addByte((value >> 8) & 0xFF);
+      buffer.addByte(value & 0xFF);
+    }
+  }
+
+  void writeSLEB128(int value) {
+    bool last = false;
+    do {
+      int part = value & 0x7f;
+      value >>= 7;
+      if ((value == 0 && (part & 0x40) == 0) ||
+          (value == -1 && (part & 0x40) != 0)) {
+        last = true;
+      } else {
+        part |= 0x80;
+      }
+      buffer.addByte(part);
+    } while (!last);
+  }
+}
+
+class BufferedReader {
+  final List<int> _buffer;
+  int _pos = 0;
+
+  BufferedReader(this._buffer);
+
+  int readByte() => _buffer[_pos++];
+
+  int readPackedUInt30() {
+    var byte = readByte();
+    if (byte & 0x80 == 0) {
+      // 0xxxxxxx
+      return byte;
+    } else if (byte & 0x40 == 0) {
+      // 10xxxxxx
+      return ((byte & 0x3F) << 8) | readByte();
+    } else {
+      // 11xxxxxx
+      return ((byte & 0x3F) << 24) |
+          (readByte() << 16) |
+          (readByte() << 8) |
+          readByte();
+    }
+  }
+
+  int readSLEB128() {
+    int value = 0;
+    int shift = 0;
+    int part = 0;
+    do {
+      part = readByte();
+      value |= (part & 0x7f) << shift;
+      shift += 7;
+    } while ((part & 0x80) != 0);
+    const int kBitsPerInt = 64;
+    if ((shift < kBitsPerInt) && ((part & 0x40) != 0)) {
+      value |= (-1) << shift;
+    }
+    return value;
+  }
+}
+
+class PackedUInt30DeltaEncoder {
+  int _last = 0;
+
+  void write(BufferedWriter write, int value) {
+    write.writePackedUInt30(value - _last);
+    _last = value;
+  }
+}
+
+class PackedUInt30DeltaDecoder {
+  int _last = 0;
+
+  int read(BufferedReader reader) {
+    int value = reader.readPackedUInt30() + _last;
+    _last = value;
+    return value;
+  }
+}
+
+class SLEB128DeltaEncoder {
+  int _last = 0;
+
+  void write(BufferedWriter writer, int value) {
+    writer.writeSLEB128(value - _last);
+    _last = value;
+  }
+}
+
+class SLEB128DeltaDecoder {
+  int _last = 0;
+
+  int read(BufferedReader reader) {
+    int value = reader.readSLEB128() + _last;
+    _last = value;
+    return value;
+  }
+}
diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart
index 03c4a51..be0184f 100644
--- a/pkg/vm/lib/kernel_front_end.dart
+++ b/pkg/vm/lib/kernel_front_end.dart
@@ -50,6 +50,7 @@
     bool useGlobalTypeFlowAnalysis: false,
     Map<String, String> environmentDefines,
     bool genBytecode: false,
+    bool emitBytecodeSourcePositions: false,
     bool dropAST: false,
     bool useFutureBytecodeFormat: false,
     bool enableAsserts: false,
@@ -95,6 +96,7 @@
     await runWithFrontEndCompilerContext(source, options, component, () {
       generateBytecode(component,
           dropAST: dropAST,
+          emitSourcePositions: emitBytecodeSourcePositions,
           useFutureBytecodeFormat: useFutureBytecodeFormat,
           environmentDefines: environmentDefines);
     });
diff --git a/pkg/vm/lib/metadata/bytecode.dart b/pkg/vm/lib/metadata/bytecode.dart
index ef52237..e333cfc 100644
--- a/pkg/vm/lib/metadata/bytecode.dart
+++ b/pkg/vm/lib/metadata/bytecode.dart
@@ -10,6 +10,7 @@
     show stableBytecodeFormatVersion, futureBytecodeFormatVersion;
 import '../bytecode/disassembler.dart' show BytecodeDisassembler;
 import '../bytecode/exceptions.dart' show ExceptionsTable;
+import '../bytecode/source_positions.dart' show SourcePositions;
 
 /// Metadata containing bytecode.
 ///
@@ -17,8 +18,8 @@
 ///
 /// type BytecodeMetadata {
 ///   UInt bytecodeFormatVersion
-///   UInt flags (HasExceptionsTable, HasNullableFields, HasClosures)
-///
+///   UInt flags (HasExceptionsTable, HasSourcePositions, HasNullableFields,
+///               HasClosures)
 ///   ConstantPool constantPool
 ///
 ///   UInt bytecodeSizeInBytes
@@ -29,6 +30,9 @@
 ///   (optional, present if HasExceptionsTable)
 ///   ExceptionsTable exceptionsTable
 ///
+///   (optional, present if HasSourcePositions)
+///   SourcePositions sourcePositionsTabe
+///
 ///   (optional, present if HasNullableFields)
 ///   List<CanonicalName> nullableFields
 ///
@@ -38,13 +42,18 @@
 ///
 /// type ClosureBytecode {
 ///   ConstantIndex closureFunction
+///   UInt flags (HasExceptionsTable, HasSourcePositions)
 ///
 ///   UInt bytecodeSizeInBytes
 ///   Byte paddingSizeInBytes
 ///   Byte[paddingSizeInBytes] padding
 ///   Byte[bytecodeSizeInBytes] bytecodes
 ///
+///   (optional, present if HasExceptionsTable)
 ///   ExceptionsTable exceptionsTable
+///
+///   (optional, present if HasSourcePositions)
+///   SourcePositions sourcePositionsTabe
 /// }
 ///
 /// Encoding of ExceptionsTable is described in
@@ -55,27 +64,37 @@
 ///
 class BytecodeMetadata {
   static const hasExceptionsTableFlag = 1 << 0;
-  static const hasNullableFieldsFlag = 1 << 1;
-  static const hasClosuresFlag = 1 << 2;
+  static const hasSourcePositionsFlag = 1 << 1;
+  static const hasNullableFieldsFlag = 1 << 2;
+  static const hasClosuresFlag = 1 << 3;
 
   final int version;
   final ConstantPool constantPool;
   final List<int> bytecodes;
   final ExceptionsTable exceptionsTable;
+  final SourcePositions sourcePositions;
   final List<Reference> nullableFields;
   final List<ClosureBytecode> closures;
 
   bool get hasExceptionsTable => exceptionsTable.blocks.isNotEmpty;
+  bool get hasSourcePositions => sourcePositions.mapping.isNotEmpty;
   bool get hasNullableFields => nullableFields.isNotEmpty;
   bool get hasClosures => closures.isNotEmpty;
 
   int get flags =>
       (hasExceptionsTable ? hasExceptionsTableFlag : 0) |
+      (hasSourcePositions ? hasSourcePositionsFlag : 0) |
       (hasNullableFields ? hasNullableFieldsFlag : 0) |
       (hasClosures ? hasClosuresFlag : 0);
 
-  BytecodeMetadata(this.version, this.constantPool, this.bytecodes,
-      this.exceptionsTable, this.nullableFields, this.closures);
+  BytecodeMetadata(
+      this.version,
+      this.constantPool,
+      this.bytecodes,
+      this.exceptionsTable,
+      this.sourcePositions,
+      this.nullableFields,
+      this.closures);
 
   // TODO(alexmarkov): Consider printing constant pool before bytecode.
   @override
@@ -84,7 +103,9 @@
       " (version: "
       "${version == stableBytecodeFormatVersion ? 'stable' : version == futureBytecodeFormatVersion ? 'future' : "v$version"}"
       ") {\n"
-      "${new BytecodeDisassembler().disassemble(bytecodes, exceptionsTable)}}\n"
+      "${new BytecodeDisassembler().disassemble(bytecodes, exceptionsTable, annotations: [
+        sourcePositions.getBytecodeAnnotations()
+      ])}}\n"
       "$exceptionsTable"
       "${nullableFields.isEmpty ? '' : 'Nullable fields: ${nullableFields.map((ref) => ref.asField).toList()}\n'}"
       "$constantPool"
@@ -97,30 +118,53 @@
   final int closureFunctionConstantIndex;
   final List<int> bytecodes;
   final ExceptionsTable exceptionsTable;
+  final SourcePositions sourcePositions;
 
-  ClosureBytecode(
-      this.closureFunctionConstantIndex, this.bytecodes, this.exceptionsTable);
+  bool get hasExceptionsTable => exceptionsTable.blocks.isNotEmpty;
+  bool get hasSourcePositions => sourcePositions.mapping.isNotEmpty;
+
+  int get flags =>
+      (hasExceptionsTable ? BytecodeMetadata.hasExceptionsTableFlag : 0) |
+      (hasSourcePositions ? BytecodeMetadata.hasSourcePositionsFlag : 0);
+
+  ClosureBytecode(this.closureFunctionConstantIndex, this.bytecodes,
+      this.exceptionsTable, this.sourcePositions);
 
   void writeToBinary(BinarySink sink) {
     sink.writeUInt30(closureFunctionConstantIndex);
+    sink.writeUInt30(flags);
     _writeBytecodeInstructions(sink, bytecodes);
-    exceptionsTable.writeToBinary(sink);
+    if (hasExceptionsTable) {
+      exceptionsTable.writeToBinary(sink);
+    }
+    if (hasSourcePositions) {
+      sink.writeByteList(sourcePositions.toBytes());
+    }
   }
 
   factory ClosureBytecode.readFromBinary(BinarySource source) {
     final closureFunctionConstantIndex = source.readUInt();
+    final int flags = source.readUInt();
     final List<int> bytecodes = _readBytecodeInstructions(source);
-    final exceptionsTable = new ExceptionsTable.readFromBinary(source);
-    return new ClosureBytecode(
-        closureFunctionConstantIndex, bytecodes, exceptionsTable);
+    final exceptionsTable =
+        ((flags & BytecodeMetadata.hasExceptionsTableFlag) != 0)
+            ? new ExceptionsTable.readFromBinary(source)
+            : new ExceptionsTable();
+    final sourcePositions =
+        ((flags & BytecodeMetadata.hasSourcePositionsFlag) != 0)
+            ? new SourcePositions.fromBytes(source.readByteList())
+            : new SourcePositions();
+    return new ClosureBytecode(closureFunctionConstantIndex, bytecodes,
+        exceptionsTable, sourcePositions);
   }
 
   @override
   String toString() {
     StringBuffer sb = new StringBuffer();
     sb.writeln('Closure CP#$closureFunctionConstantIndex {');
-    sb.writeln(
-        new BytecodeDisassembler().disassemble(bytecodes, exceptionsTable));
+    sb.writeln(new BytecodeDisassembler().disassemble(
+        bytecodes, exceptionsTable,
+        annotations: [sourcePositions.getBytecodeAnnotations()]));
     sb.writeln('}');
     return sb.toString();
   }
@@ -144,6 +188,9 @@
     if (metadata.hasExceptionsTable) {
       metadata.exceptionsTable.writeToBinary(sink);
     }
+    if (metadata.hasSourcePositions) {
+      sink.writeByteList(metadata.sourcePositions.toBytes());
+    }
     if (metadata.hasNullableFields) {
       sink.writeUInt30(metadata.nullableFields.length);
       metadata.nullableFields.forEach((ref) => sink
@@ -165,12 +212,15 @@
     int flags = source.readUInt();
     final ConstantPool constantPool =
         new ConstantPool.readFromBinary(node, source);
-    _readBytecodePadding(source);
     final List<int> bytecodes = _readBytecodeInstructions(source);
     final exceptionsTable =
         ((flags & BytecodeMetadata.hasExceptionsTableFlag) != 0)
             ? new ExceptionsTable.readFromBinary(source)
             : new ExceptionsTable();
+    final sourcePositions =
+        ((flags & BytecodeMetadata.hasSourcePositionsFlag) != 0)
+            ? new SourcePositions.fromBytes(source.readByteList())
+            : new SourcePositions();
     final List<Reference> nullableFields =
         ((flags & BytecodeMetadata.hasNullableFieldsFlag) != 0)
             ? new List<Reference>.generate(source.readUInt(),
@@ -182,7 +232,7 @@
                 (_) => new ClosureBytecode.readFromBinary(source))
             : const <ClosureBytecode>[];
     return new BytecodeMetadata(version, constantPool, bytecodes,
-        exceptionsTable, nullableFields, closures);
+        exceptionsTable, sourcePositions, nullableFields, closures);
   }
 }
 
diff --git a/pkg/vm/test/bytecode/gen_bytecode_test.dart b/pkg/vm/test/bytecode/gen_bytecode_test.dart
index 758f47f..1cb36de 100644
--- a/pkg/vm/test/bytecode/gen_bytecode_test.dart
+++ b/pkg/vm/test/bytecode/gen_bytecode_test.dart
@@ -31,7 +31,7 @@
   await runWithFrontEndCompilerContext(source, options, component, () {
     // Need to omit source positions from bytecode as they are different on
     // Linux and Windows (due to differences in newline characters).
-    generateBytecode(component, omitSourcePositions: true);
+    generateBytecode(component, omitAssertSourcePositions: true);
   });
 
   final actual = kernelLibraryToString(component.mainMethod.enclosingLibrary);
diff --git a/runtime/vm/compiler/frontend/bytecode_reader.cc b/runtime/vm/compiler/frontend/bytecode_reader.cc
index d543349..0f80fbc 100644
--- a/runtime/vm/compiler/frontend/bytecode_reader.cc
+++ b/runtime/vm/compiler/frontend/bytecode_reader.cc
@@ -74,11 +74,13 @@
   }
 
   const int kHasExceptionsTableFlag = 1 << 0;
-  const int kHasNullableFieldsFlag = 1 << 1;
-  const int kHasClosuresFlag = 1 << 2;
+  const int kHasSourcePositionsFlag = 1 << 1;
+  const int kHasNullableFieldsFlag = 1 << 2;
+  const int kHasClosuresFlag = 1 << 3;
 
   const intptr_t flags = helper_->reader_.ReadUInt();
   const bool has_exceptions_table = (flags & kHasExceptionsTableFlag) != 0;
+  const bool has_source_positions = (flags & kHasSourcePositionsFlag) != 0;
   const bool has_nullable_fields = (flags & kHasNullableFieldsFlag) != 0;
   const bool has_closures = (flags & kHasClosuresFlag) != 0;
 
@@ -101,9 +103,10 @@
   const Code& bytecode = Code::Handle(helper_->zone_, ReadBytecode(pool));
   function.AttachBytecode(bytecode);
 
-  // Read exceptions table.
   ReadExceptionsTable(bytecode, has_exceptions_table);
 
+  ReadSourcePositions(bytecode, has_source_positions);
+
   if (FLAG_dump_kernel_bytecode) {
     KernelBytecodeDisassembler::Disassemble(function);
   }
@@ -137,12 +140,18 @@
       ASSERT(closure_index < obj_count);
       closure ^= pool.ObjectAt(closure_index);
 
+      const intptr_t flags = helper_->reader_.ReadUInt();
+      const bool has_exceptions_table = (flags & kHasExceptionsTableFlag) != 0;
+      const bool has_source_positions = (flags & kHasSourcePositionsFlag) != 0;
+
       // Read closure bytecode and attach to closure function.
       closure_bytecode = ReadBytecode(pool);
       closure.AttachBytecode(closure_bytecode);
 
       // Read closure exceptions table.
-      ReadExceptionsTable(closure_bytecode);
+      ReadExceptionsTable(closure_bytecode, has_exceptions_table);
+
+      ReadSourcePositions(closure_bytecode, has_source_positions);
 
       if (FLAG_dump_kernel_bytecode) {
         KernelBytecodeDisassembler::Disassemble(closure);
@@ -716,6 +725,16 @@
   }
 }
 
+void BytecodeMetadataHelper::ReadSourcePositions(const Code& bytecode,
+                                                 bool has_source_positions) {
+  if (!has_source_positions) {
+    return;
+  }
+
+  // TODO(alexmarkov): store offset of source positions into Bytecode object.
+  helper_->SkipBytes(helper_->reader_.ReadUInt());
+}
+
 RawTypedData* BytecodeMetadataHelper::NativeEntry(const Function& function,
                                                   const String& external_name) {
   Zone* zone = helper_->zone_;
diff --git a/runtime/vm/compiler/frontend/bytecode_reader.h b/runtime/vm/compiler/frontend/bytecode_reader.h
index bbde906..0e9d730 100644
--- a/runtime/vm/compiler/frontend/bytecode_reader.h
+++ b/runtime/vm/compiler/frontend/bytecode_reader.h
@@ -33,8 +33,8 @@
                            const ObjectPool& pool,
                            intptr_t from_index);
   RawCode* ReadBytecode(const ObjectPool& pool);
-  void ReadExceptionsTable(const Code& bytecode,
-                           bool has_exceptions_table = true);
+  void ReadExceptionsTable(const Code& bytecode, bool has_exceptions_table);
+  void ReadSourcePositions(const Code& bytecode, bool has_source_positions);
   RawTypedData* NativeEntry(const Function& function,
                             const String& external_name);