[vm/bytecode] Support --causal_async_stacks in bytecode generator

Also, standalone_2/causal_async_stack_test is updated to actually
test --causal_async_stacks after Dart 2 sync-async.

Change-Id: I28a7a281963828707461652f19494ff54bdd21c8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106760
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: RĂ©gis Crelier <regis@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
diff --git a/pkg/kernel/lib/transformations/continuation.dart b/pkg/kernel/lib/transformations/continuation.dart
index b599cdf..8cb5c3f 100644
--- a/pkg/kernel/lib/transformations/continuation.dart
+++ b/pkg/kernel/lib/transformations/continuation.dart
@@ -15,6 +15,7 @@
 class ContinuationVariables {
   static const awaitJumpVar = ':await_jump_var';
   static const awaitContextVar = ':await_ctx_var';
+  static const asyncStackTraceVar = ':async_stack_trace';
   static const exceptionParam = ':exception';
   static const stackTraceParam = ':stack_trace';
 
@@ -270,7 +271,7 @@
   final VariableDeclaration nestedClosureVariable =
       new VariableDeclaration(":async_op");
   final VariableDeclaration stackTraceVariable =
-      new VariableDeclaration(":async_stack_trace");
+      new VariableDeclaration(ContinuationVariables.asyncStackTraceVar);
   final VariableDeclaration thenContinuationVariable =
       new VariableDeclaration(":async_op_then");
   final VariableDeclaration catchErrorContinuationVariable =
diff --git a/pkg/vm/lib/bytecode/gen_bytecode.dart b/pkg/vm/lib/bytecode/gen_bytecode.dart
index d79d963..1e80cc7 100644
--- a/pkg/vm/lib/bytecode/gen_bytecode.dart
+++ b/pkg/vm/lib/bytecode/gen_bytecode.dart
@@ -59,6 +59,7 @@
 void generateBytecode(
   ast.Component component, {
   bool enableAsserts: true,
+  bool causalAsyncStacks,
   bool emitSourcePositions: false,
   bool emitSourceFiles: false,
   bool emitLocalVarInfo: false,
@@ -79,6 +80,8 @@
   final constantsBackend = new VmConstantsBackend(coreTypes);
   final errorReporter = new ForwardConstantEvaluationErrors();
   libraries ??= component.libraries;
+  causalAsyncStacks ??=
+      environmentDefines['dart.developer.causal_async_stacks'] == 'true';
   try {
     final bytecodeGenerator = new BytecodeGenerator(
         component,
@@ -88,6 +91,7 @@
         constantsBackend,
         environmentDefines,
         enableAsserts,
+        causalAsyncStacks,
         emitSourcePositions,
         emitSourceFiles,
         emitLocalVarInfo,
@@ -112,6 +116,7 @@
   final ConstantsBackend constantsBackend;
   final Map<String, String> environmentDefines;
   final bool enableAsserts;
+  final bool causalAsyncStacks;
   final bool emitSourcePositions;
   final bool emitSourceFiles;
   final bool emitLocalVarInfo;
@@ -167,6 +172,7 @@
       this.constantsBackend,
       this.environmentDefines,
       this.enableAsserts,
+      this.causalAsyncStacks,
       this.emitSourcePositions,
       this.emitSourceFiles,
       this.emitLocalVarInfo,
@@ -852,6 +858,15 @@
       _asyncAwaitCompleterGetFuture ??= libraryIndex.getMember(
           'dart:async', '_AsyncAwaitCompleter', 'get:future');
 
+  Procedure _setAsyncThreadStackTrace;
+  Procedure get setAsyncThreadStackTrace => _setAsyncThreadStackTrace ??=
+      libraryIndex.getTopLevelMember('dart:async', '_setAsyncThreadStackTrace');
+
+  Procedure _clearAsyncThreadStackTrace;
+  Procedure get clearAsyncThreadStackTrace =>
+      _clearAsyncThreadStackTrace ??= libraryIndex.getTopLevelMember(
+          'dart:async', '_clearAsyncThreadStackTrace');
+
   Library _dartFfiLibrary;
   Library get dartFfiLibrary =>
       _dartFfiLibrary ??= libraryIndex.tryGetLibrary('dart:ffi');
@@ -976,6 +991,15 @@
   }
 
   void _genReturnTOS() {
+    if (causalAsyncStacks &&
+        parentFunction != null &&
+        (parentFunction.dartAsyncMarker == AsyncMarker.Async ||
+            parentFunction.dartAsyncMarker == AsyncMarker.AsyncStar)) {
+      _genDirectCall(
+          clearAsyncThreadStackTrace, objectTable.getArgDescHandle(0), 0);
+      asm.emitDrop1();
+    }
+
     asm.emitReturnTOS();
   }
 
@@ -1345,7 +1369,7 @@
     savedMaxSourcePositions = <int>[];
     maxSourcePosition = node.fileOffset;
 
-    locals = new LocalVariables(node, enableAsserts);
+    locals = new LocalVariables(node, enableAsserts, causalAsyncStacks);
     locals.enterScope(node);
     assert(!locals.isSyncYieldingFrame);
 
@@ -1891,6 +1915,17 @@
     _recordSourcePosition(function.fileOffset);
     _genPrologue(node, function);
 
+    if (causalAsyncStacks &&
+        parentFunction != null &&
+        (parentFunction.dartAsyncMarker == AsyncMarker.Async ||
+            parentFunction.dartAsyncMarker == AsyncMarker.AsyncStar)) {
+      _genLoadVar(locals.asyncStackTraceVar,
+          currentContextLevel: locals.contextLevelAtEntry);
+      _genDirectCall(
+          setAsyncThreadStackTrace, objectTable.getArgDescHandle(1), 1);
+      asm.emitDrop1();
+    }
+
     Label continuationSwitchLabel;
     int continuationSwitchVar;
     if (locals.isSyncYieldingFrame) {
@@ -1903,8 +1938,6 @@
     _setupInitialContext(function);
     _checkArguments(function);
 
-    // TODO(alexmarkov): support --causal_async_stacks.
-
     _generateNode(function.body);
 
     // BytecodeAssembler eliminates this bytecode if it is unreachable.
@@ -3279,12 +3312,12 @@
         _getEnclosingTryFinallyBlocks(node, null);
     if (tryFinallyBlocks.isEmpty) {
       _generateNode(expr);
-      asm.emitReturnTOS();
+      _genReturnTOS();
     } else {
       if (expr is BasicLiteral) {
         _addFinallyBlocks(tryFinallyBlocks, () {
           _generateNode(expr);
-          asm.emitReturnTOS();
+          _genReturnTOS();
         });
       } else {
         // Keep return value in a variable as try-catch statements
@@ -3294,7 +3327,7 @@
 
         _addFinallyBlocks(tryFinallyBlocks, () {
           asm.emitPush(locals.returnVarIndexInFrame);
-          asm.emitReturnTOS();
+          _genReturnTOS();
         });
       }
     }
@@ -3663,7 +3696,7 @@
     // return <expression>
     // Note: finally blocks are *not* executed on the way out.
     _generateNode(node.expression);
-    asm.emitReturnTOS();
+    _genReturnTOS();
 
     asm.bind(continuationLabel);
 
diff --git a/pkg/vm/lib/bytecode/local_vars.dart b/pkg/vm/lib/bytecode/local_vars.dart
index c321b4e..14c5025 100644
--- a/pkg/vm/lib/bytecode/local_vars.dart
+++ b/pkg/vm/lib/bytecode/local_vars.dart
@@ -25,6 +25,7 @@
   final Map<ForInStatement, VariableDeclaration> _capturedIteratorVars =
       <ForInStatement, VariableDeclaration>{};
   final bool enableAsserts;
+  final bool causalAsyncStacks;
 
   Scope _currentScope;
   Frame _currentFrame;
@@ -137,6 +138,13 @@
         .getSyntheticVar(ContinuationVariables.awaitContextVar);
   }
 
+  VariableDeclaration get asyncStackTraceVar {
+    assert(causalAsyncStacks);
+    assert(_currentFrame.isSyncYielding);
+    return _currentFrame.parent
+        .getSyntheticVar(ContinuationVariables.asyncStackTraceVar);
+  }
+
   VariableDeclaration capturedSavedContextVar(TreeNode node) =>
       _capturedSavedContextVars[node];
   VariableDeclaration capturedExceptionVar(TreeNode node) =>
@@ -176,7 +184,7 @@
   List<VariableDeclaration> get sortedNamedParameters =>
       _currentFrame.sortedNamedParameters;
 
-  LocalVariables(Member node, this.enableAsserts) {
+  LocalVariables(Member node, this.enableAsserts, this.causalAsyncStacks) {
     final scopeBuilder = new _ScopeBuilder(this);
     node.accept(scopeBuilder);
 
@@ -230,7 +238,7 @@
   bool hasOptionalParameters = false;
   bool hasCapturedParameters = false;
   bool hasClosures = false;
-  bool isDartSync = true;
+  AsyncMarker dartAsyncMarker = AsyncMarker.Sync;
   bool isSyncYielding = false;
   VariableDeclaration receiverVar;
   VariableDeclaration capturedReceiverVar;
@@ -311,7 +319,7 @@
       FunctionNode function = (node as dynamic).function;
       assert(function != null);
 
-      _currentFrame.isDartSync = function.dartAsyncMarker == AsyncMarker.Sync;
+      _currentFrame.dartAsyncMarker = function.dartAsyncMarker;
 
       _currentFrame.isSyncYielding =
           function.asyncMarker == AsyncMarker.SyncYielding;
@@ -363,6 +371,14 @@
             .getSyntheticVar(ContinuationVariables.awaitJumpVar));
         _useVariable(_currentFrame.parent
             .getSyntheticVar(ContinuationVariables.awaitContextVar));
+
+        if (locals.causalAsyncStacks &&
+            (_currentFrame.parent.dartAsyncMarker == AsyncMarker.Async ||
+                _currentFrame.parent.dartAsyncMarker ==
+                    AsyncMarker.AsyncStar)) {
+          _useVariable(_currentFrame.parent
+              .getSyntheticVar(ContinuationVariables.asyncStackTraceVar));
+        }
       }
 
       if (node is Constructor) {
@@ -535,7 +551,8 @@
   visitVariableDeclaration(VariableDeclaration node) {
     _declareVariable(node);
 
-    if (!_currentFrame.isDartSync && node.name[0] == ':') {
+    if (_currentFrame.dartAsyncMarker != AsyncMarker.Sync &&
+        node.name[0] == ':') {
       _currentFrame.syntheticVars ??= <String, VariableDeclaration>{};
       assert(_currentFrame.syntheticVars[node.name] == null);
       _currentFrame.syntheticVars[node.name] = node;
diff --git a/tests/standalone_2/causal_async_stack_test.dart b/tests/standalone_2/causal_async_stack_test.dart
index faebf3e..eab14af 100644
--- a/tests/standalone_2/causal_async_stack_test.dart
+++ b/tests/standalone_2/causal_async_stack_test.dart
@@ -5,7 +5,12 @@
 
 import "package:expect/expect.dart";
 
+noop() async => Future.value(null);
+
 baz() async {
+  // Throw exception after the first continuation, when there is no
+  // original stack trace.
+  await noop();
   throw "Bad!";
 }