From af1875be910a24f34f1983b314f45c1ecd8097ad Mon Sep 17 00:00:00 2001 From: Todd Turnidge Date: Tue, 22 Nov 2016 15:26:08 -0800 Subject: [PATCH] Implement rewind: drop one or more frames from the debugger. Not yet implemented on dbc. BUG= R=johnmccutchan@google.com Review URL: https://codereview.chromium.org/2523053002 . --- .../lib/src/elements/debugger.dart | 124 +++++-- .../observatory/lib/src/service/object.dart | 10 +- .../service/pause_idle_isolate_test.dart | 5 +- .../service/pause_on_start_and_exit_test.dart | 5 +- .../pause_on_start_then_step_test.dart | 5 +- .../service/rewind_optimized_out_test.dart | 86 +++++ .../tests/service/rewind_test.dart | 173 ++++++++++ .../observatory/tests/service/service.status | 2 + .../tests/service/service_test_common.dart | 4 + .../tests/service/set_vm_name_rpc_test.dart | 4 +- .../tests/service/step_over_await_test.dart | 2 +- .../tests/service/test_helper.dart | 87 ++--- runtime/vm/code_generator.cc | 8 + runtime/vm/dart_entry.cc | 2 +- runtime/vm/debugger.cc | 323 +++++++++++++++++- runtime/vm/debugger.h | 40 ++- runtime/vm/debugger_api_impl.cc | 6 +- runtime/vm/exceptions.cc | 20 +- runtime/vm/exceptions.h | 3 +- runtime/vm/flow_graph_compiler.cc | 1 + runtime/vm/intermediate_language.cc | 2 + runtime/vm/json_stream.cc | 2 + runtime/vm/json_stream.h | 1 + runtime/vm/object.cc | 2 + runtime/vm/raw_object.h | 3 +- runtime/vm/runtime_entry_list.h | 1 + runtime/vm/service.cc | 74 ++-- runtime/vm/simulator_dbc.cc | 15 +- runtime/vm/stub_code.h | 8 + runtime/vm/stub_code_arm.cc | 33 +- runtime/vm/stub_code_arm64.cc | 33 +- runtime/vm/stub_code_dbc.cc | 6 + runtime/vm/stub_code_ia32.cc | 27 +- runtime/vm/stub_code_mips.cc | 33 +- runtime/vm/stub_code_x64.cc | 32 +- 35 files changed, 1000 insertions(+), 182 deletions(-) create mode 100644 runtime/observatory/tests/service/rewind_optimized_out_test.dart create mode 100644 runtime/observatory/tests/service/rewind_test.dart diff --git a/runtime/observatory/lib/src/elements/debugger.dart b/runtime/observatory/lib/src/elements/debugger.dart index 8b91efffe5b..a01064fb9e1 100644 --- a/runtime/observatory/lib/src/elements/debugger.dart +++ b/runtime/observatory/lib/src/elements/debugger.dart @@ -209,7 +209,8 @@ class DownCommand extends DebuggerCommand { debugger.downFrame(count); debugger.console.print('frame = ${debugger.currentFrame}'); } catch (e) { - debugger.console.print('frame must be in range [${e.start},${e.end-1}]'); + debugger.console.print( + 'frame must be in range [${e.start}..${e.end-1}]'); } return new Future.value(null); } @@ -243,7 +244,8 @@ class UpCommand extends DebuggerCommand { debugger.upFrame(count); debugger.console.print('frame = ${debugger.currentFrame}'); } on RangeError catch (e) { - debugger.console.print('frame must be in range [${e.start},${e.end-1}]'); + debugger.console.print( + 'frame must be in range [${e.start}..${e.end-1}]'); } return new Future.value(null); } @@ -279,7 +281,8 @@ class FrameCommand extends DebuggerCommand { debugger.currentFrame = frame; debugger.console.print('frame = ${debugger.currentFrame}'); } on RangeError catch (e) { - debugger.console.print('frame must be in range [${e.start},${e.end-1}]'); + debugger.console.print( + 'frame must be in range [${e.start}..${e.end-1}]'); } return new Future.value(null); } @@ -403,6 +406,73 @@ class StepCommand extends DebuggerCommand { 'Syntax: step\n'; } +class RewindCommand extends DebuggerCommand { + RewindCommand(Debugger debugger) : super(debugger, 'rewind', []); + + Future run(List args) async { + try { + int count = 1; + if (args.length == 1) { + count = int.parse(args[0]); + } else if (args.length > 1) { + debugger.console.print('rewind expects 0 or 1 argument'); + return; + } else if (count < 1 || count > debugger.stackDepth) { + debugger.console.print( + 'frame must be in range [1..${debugger.stackDepth - 1}]'); + return; + } + await debugger.rewind(count); + } on S.ServerRpcException catch(e) { + if (e.code == S.ServerRpcException.kCannotResume) { + debugger.console.printRed(e.data['details']); + } else { + rethrow; + } + } + } + + String helpShort = 'Rewind the stack to a previous frame'; + + String helpLong = + 'Rewind the stack to a previous frame.\n' + '\n' + 'Syntax: rewind\n' + ' rewind \n'; +} + +class ReloadCommand extends DebuggerCommand { + ReloadCommand(Debugger debugger) : super(debugger, 'reload', []); + + Future run(List args) async { + try { + int count = 1; + if (args.length > 0) { + debugger.console.print('reload expects no arguments'); + return; + } + await debugger.isolate.reloadSources(); + debugger.console.print('reload complete'); + await debugger.refreshStack(); + } on S.ServerRpcException catch(e) { + if (e.code == S.ServerRpcException.kIsolateReloadBarred || + e.code == S.ServerRpcException.kIsolateReloadFailed || + e.code == S.ServerRpcException.kIsolateIsReloading) { + debugger.console.printRed(e.data['details']); + } else { + rethrow; + } + } + } + + String helpShort = 'Reload the sources for the current isolate'; + + String helpLong = + 'Reload the sources for the current isolate.\n' + '\n' + 'Syntax: reload\n'; +} + class ClsCommand extends DebuggerCommand { ClsCommand(Debugger debugger) : super(debugger, 'cls', []) {} @@ -663,8 +733,8 @@ class BreakCommand extends DebuggerCommand { var script = loc.script; await script.load(); if (loc.line < 1 || loc.line > script.lines.length) { - debugger.console - .print('line number must be in range [1,${script.lines.length}]'); + debugger.console.print( + 'line number must be in range [1..${script.lines.length}]'); return; } try { @@ -744,7 +814,7 @@ class ClearCommand extends DebuggerCommand { var script = loc.script; if (loc.line < 1 || loc.line > script.lines.length) { debugger.console - .print('line number must be in range [1,${script.lines.length}]'); + .print('line number must be in range [1..${script.lines.length}]'); return; } var lineInfo = script.getLine(loc.line); @@ -899,7 +969,6 @@ class IsolateCommand extends DebuggerCommand { : super(debugger, 'isolate', [ new IsolateListCommand(debugger), new IsolateNameCommand(debugger), - new IsolateReloadCommand(debugger), ]) { alias = 'i'; } @@ -1042,27 +1111,6 @@ class IsolateNameCommand extends DebuggerCommand { 'Syntax: isolate name \n'; } -class IsolateReloadCommand extends DebuggerCommand { - IsolateReloadCommand(Debugger debugger) : super(debugger, 'reload', []); - - Future run(List args) async { - if (debugger.isolate == null) { - debugger.console.print('There is no current vm'); - return; - } - - await debugger.isolate.reloadSources(); - - debugger.console.print('Isolate reloading....'); - } - - String helpShort = 'Reload the sources for the current isolate.'; - - String helpLong = 'Reload the sources for the current isolate.\n' - '\n' - 'Syntax: reload\n'; -} - class InfoCommand extends DebuggerCommand { InfoCommand(Debugger debugger) : super(debugger, 'info', [ @@ -1371,7 +1419,9 @@ class ObservatoryDebugger extends Debugger { new LogCommand(this), new PauseCommand(this), new PrintCommand(this), + new ReloadCommand(this), new RefreshCommand(this), + new RewindCommand(this), new SetCommand(this), new SmartNextCommand(this), new StepCommand(this), @@ -1879,6 +1929,20 @@ class ObservatoryDebugger extends Debugger { return new Future.value(null); } } + + Future rewind(int count) { + if (isolatePaused()) { + var event = isolate.pauseEvent; + if (event is M.PauseExitEvent) { + console.print("Type 'continue' [F7] to exit the isolate"); + return new Future.value(null); + } + return isolate.rewind(count); + } else { + console.print('The program must be paused'); + return new Future.value(null); + } + } } class DebuggerPageElement extends HtmlElement implements Renderable { @@ -2513,7 +2577,9 @@ class DebuggerFrameElement extends HtmlElement implements Renderable { } bool matchFrame(S.Frame newFrame) { - return newFrame.function.id == _frame.function.id; + return (newFrame.function.id == _frame.function.id && + newFrame.location.script.id == + frame.location.script.id); } void updateFrame(S.Frame newFrame) { diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart index c638dd90d9f..13202aa8cf7 100644 --- a/runtime/observatory/lib/src/service/object.dart +++ b/runtime/observatory/lib/src/service/object.dart @@ -42,11 +42,13 @@ class ServerRpcException extends RpcException implements M.RequestException { static const kStreamNotSubscribed = 104; static const kIsolateMustBeRunnable = 105; static const kIsolateMustBePaused = 106; + static const kCannotResume = 107; static const kIsolateIsReloading = 1000; static const kFileSystemAlreadyExists = 1001; static const kFileSystemDoesNotExist = 1002; static const kFileDoesNotExist = 1003; static const kIsolateReloadFailed = 1004; + static const kIsolateReloadBarred = 1005; int code; Map data; @@ -1279,7 +1281,6 @@ class Isolate extends ServiceObjectOwner implements M.Isolate { bool loading = true; bool runnable = false; bool ioEnabled = false; - bool reloading = false; M.IsolateStatus get status { if (paused) { return M.IsolateStatus.paused; @@ -1344,13 +1345,12 @@ class Isolate extends ServiceObjectOwner implements M.Isolate { params['pause'] = pause; } return invokeRpc('reloadSources', params).then((result) { - reloading = true; + _cache.clear(); return result; }); } void _handleIsolateReloadEvent(ServiceEvent event) { - reloading = false; if (event.reloadError != null) { // Failure. print('Reload failed: ${event.reloadError}'); @@ -1768,6 +1768,10 @@ class Isolate extends ServiceObjectOwner implements M.Isolate { return invokeRpc('resume', {'step': 'Out'}); } + Future rewind(int count) { + return invokeRpc('resume', {'step': 'Rewind', 'frameIndex': count}); + } + Future setName(String newName) { return invokeRpc('setName', {'name': newName}); } diff --git a/runtime/observatory/tests/service/pause_idle_isolate_test.dart b/runtime/observatory/tests/service/pause_idle_isolate_test.dart index 484230b2cad..d042f4b8558 100644 --- a/runtime/observatory/tests/service/pause_idle_isolate_test.dart +++ b/runtime/observatory/tests/service/pause_idle_isolate_test.dart @@ -52,5 +52,6 @@ hasStoppedAtBreakpoint, main(args) => runIsolateTests(args, tests, testeeConcurrent: testMain, - trace_service: true, - verbose_vm: true); + verbose_vm: true, + extraArgs: [ '--trace-service', + '--trace-service-verbose' ]); diff --git a/runtime/observatory/tests/service/pause_on_start_and_exit_test.dart b/runtime/observatory/tests/service/pause_on_start_and_exit_test.dart index 146be22ae44..b292b31d811 100644 --- a/runtime/observatory/tests/service/pause_on_start_and_exit_test.dart +++ b/runtime/observatory/tests/service/pause_on_start_and_exit_test.dart @@ -86,5 +86,6 @@ main(args) => runIsolateTests(args, tests, testeeConcurrent: testMain, pause_on_start: true, pause_on_exit: true, - trace_service: true, - verbose_vm: true); + verbose_vm: true, + extraArgs: [ '--trace-service', + '--trace-service-verbose' ]); diff --git a/runtime/observatory/tests/service/pause_on_start_then_step_test.dart b/runtime/observatory/tests/service/pause_on_start_then_step_test.dart index 107d2ee8ce3..480d9846881 100644 --- a/runtime/observatory/tests/service/pause_on_start_then_step_test.dart +++ b/runtime/observatory/tests/service/pause_on_start_then_step_test.dart @@ -89,5 +89,6 @@ main(args) => runIsolateTests(args, tests, testeeConcurrent: testMain, pause_on_start: true, pause_on_exit: true, - trace_service: true, - verbose_vm: true); + verbose_vm: true, + extraArgs: [ '--trace-service', + '--trace-service-verbose' ]); diff --git a/runtime/observatory/tests/service/rewind_optimized_out_test.dart b/runtime/observatory/tests/service/rewind_optimized_out_test.dart new file mode 100644 index 00000000000..23b8c9a9a67 --- /dev/null +++ b/runtime/observatory/tests/service/rewind_optimized_out_test.dart @@ -0,0 +1,86 @@ +// Copyright (c) 2016, 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. +// VMOptions=--error_on_bad_type --error_on_bad_override + +import 'dart:developer'; +import 'package:observatory/service_io.dart'; +import 'package:unittest/unittest.dart'; +import 'service_test_common.dart'; +import 'test_helper.dart'; + +const alwaysInline = "AlwaysInline"; +const noInline = "NeverInline"; + +int LINE_A = 35; +int LINE_B = 40; +int LINE_C = 43; +int LINE_D = 47; + +int global = 0; + +@noInline +b3(x) { + int sum = 0; + try { + for (int i = 0; i < x; i++) { + sum += x; + } + } catch (e) { + print("caught $e"); + } + if (global >= 100) { + debugger(); + } + global = global + 1; // Line A + return sum; +} + +@alwaysInline +b2(x) => b3(x); // Line B + +@alwaysInline +b1(x) => b2(x); // Line C + +test() { + while (true) { + b1(10000); // Line D + } +} + +var tests = [ + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_A), + + (Isolate isolate) async { + // We are at our breakpoint with global=100. + var result = await isolate.rootLibrary.evaluate('global'); + print('global is $result'); + expect(result.type, equals('Instance')); + expect(result.valueAsString, equals('100')); + + // Rewind the top stack frame. + bool caughtException; + try { + result = await isolate.rewind(1); + expect(false, isTrue, reason:'Unreachable'); + } on ServerRpcException catch(e) { + caughtException = true; + expect(e.code, equals(ServerRpcException.kCannotResume)); + expect(e.message, + 'Cannot rewind to frame 1 due to conflicting compiler ' + 'optimizations. Run the vm with --no-prune-dead-locals to ' + 'disallow these optimizations. Next valid rewind frame is 4.'); + } + expect(caughtException, isTrue); + }, +]; + + +main(args) => runIsolateTests(args, tests, testeeConcurrent: test, + extraArgs: + ['--trace-rewind', + '--prune-dead-locals', + '--enable-inlining-annotations', + '--no-background-compilation', + '--optimization-counter-threshold=10']); diff --git a/runtime/observatory/tests/service/rewind_test.dart b/runtime/observatory/tests/service/rewind_test.dart new file mode 100644 index 00000000000..152d711d40d --- /dev/null +++ b/runtime/observatory/tests/service/rewind_test.dart @@ -0,0 +1,173 @@ +// Copyright (c) 2016, 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. +// VMOptions=--error_on_bad_type --error_on_bad_override + +import 'dart:developer'; +import 'package:observatory/service_io.dart'; +import 'package:unittest/unittest.dart'; +import 'service_test_common.dart'; +import 'test_helper.dart'; + +const alwaysInline = "AlwaysInline"; +const noInline = "NeverInline"; + +int LINE_A = 35; +int LINE_B = 40; +int LINE_C = 43; +int LINE_D = 47; + +int global = 0; + +@noInline +b3(x) { + int sum = 0; + try { + for (int i = 0; i < x; i++) { + sum += x; + } + } catch (e) { + print("caught $e"); + } + if (global >= 100) { + debugger(); + } + global = global + 1; // Line A + return sum; +} + +@alwaysInline +b2(x) => b3(x); // Line B + +@alwaysInline +b1(x) => b2(x); // Line C + +test() { + while (true) { + b1(10000); // Line D + } +} + +var tests = [ + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_A), + + (Isolate isolate) async { + // We are not able to rewind frame 0. + bool caughtException; + try { + await isolate.rewind(0); + expect(false, isTrue, reason:'Unreachable'); + } on ServerRpcException catch(e) { + caughtException = true; + expect(e.code, equals(ServerRpcException.kCannotResume)); + expect(e.message, 'Frame must be in bounds [1..9]: saw 0'); + } + expect(caughtException, isTrue); + }, + + (Isolate isolate) async { + // We are not able to rewind frame 10. + bool caughtException; + try { + await isolate.rewind(10); + expect(false, isTrue, reason:'Unreachable'); + } on ServerRpcException catch(e) { + caughtException = true; + expect(e.code, equals(ServerRpcException.kCannotResume)); + expect(e.message, 'Frame must be in bounds [1..9]: saw 10'); + } + expect(caughtException, isTrue); + }, + + (Isolate isolate) async { + // We are at our breakpoint with global=100. + var result = await isolate.rootLibrary.evaluate('global'); + print('global is $result'); + expect(result.type, equals('Instance')); + expect(result.valueAsString, equals('100')); + + // Rewind the top stack frame. + result = await isolate.rewind(1); + expect(result['type'], equals('Success')); + }, + + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_B), + + (Isolate isolate) async { + var result = await isolate.resume(); + expect(result['type'], equals('Success')); + }, + + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_A), + + (Isolate isolate) async { + // global still is equal to 100. We did not execute "global++". + var result = await isolate.rootLibrary.evaluate('global'); + print('global is $result'); + expect(result.type, equals('Instance')); + expect(result.valueAsString, equals('100')); + + // Resume again, for fun. + result = await isolate.resume(); + expect(result['type'], equals('Success')); + }, + + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_A), + + (Isolate isolate) async { + // global is now 101. + var result = await isolate.rootLibrary.evaluate('global'); + print('global is $result'); + expect(result.type, equals('Instance')); + expect(result.valueAsString, equals('101')); + + // Rewind up to 'test'/ + result = await isolate.rewind(3); + expect(result['type'], equals('Success')); + }, + + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_D), + + (Isolate isolate) async { + // Reset global to 0 and start again. + var result = await isolate.rootLibrary.evaluate('global=0'); + print('set global to $result'); + expect(result.type, equals('Instance')); + expect(result.valueAsString, equals('0')); + + result = await isolate.resume(); + expect(result['type'], equals('Success')); + }, + + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_A), + + (Isolate isolate) async { + // We are at our breakpoint with global=100. + var result = await isolate.rootLibrary.evaluate('global'); + print('global is $result'); + expect(result.type, equals('Instance')); + expect(result.valueAsString, equals('100')); + + // Rewind the top 2 stack frames. + result = await isolate.rewind(2); + expect(result['type'], equals('Success')); + }, + + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_C), +]; + + +main(args) => runIsolateTests(args, tests, testeeConcurrent: test, + extraArgs: + ['--trace-rewind', + '--no-prune-dead-locals', + '--enable-inlining-annotations', + '--no-background-compilation', + '--optimization-counter-threshold=10']); diff --git a/runtime/observatory/tests/service/service.status b/runtime/observatory/tests/service/service.status index bb0c4331206..18d213acb21 100644 --- a/runtime/observatory/tests/service/service.status +++ b/runtime/observatory/tests/service/service.status @@ -63,6 +63,8 @@ evaluate_activation_in_method_class_test: CompileTimeError # Issue 24478 get_allocation_samples_test: RuntimeError # Profiling unimplemented. get_cpu_profile_timeline_rpc_test: RuntimeError # Profiling unimplemented. implicit_getter_setter_test: RuntimeError # Field guards unimplemented. +rewind_test: RuntimeError # Issue 27878 +rewind_optimized_out_test: RuntimeError # Issue 27878 [ $hot_reload || $hot_reload_rollback ] # Skip all service tests because random reloads interfere. diff --git a/runtime/observatory/tests/service/service_test_common.dart b/runtime/observatory/tests/service/service_test_common.dart index 2a68a528c6b..243b41a2c4e 100644 --- a/runtime/observatory/tests/service/service_test_common.dart +++ b/runtime/observatory/tests/service/service_test_common.dart @@ -234,6 +234,10 @@ IsolateTest stoppedAtLine(int line) { return (Isolate isolate) async { print("Checking we are at line $line"); + // Make sure that the isolate has stopped. + isolate.reload(); + expect(isolate.pauseEvent is! M.ResumeEvent, isTrue); + ServiceMap stack = await isolate.getStack(); expect(stack.type, equals('Stack')); diff --git a/runtime/observatory/tests/service/set_vm_name_rpc_test.dart b/runtime/observatory/tests/service/set_vm_name_rpc_test.dart index 7e3a53a8e6f..8554ad3f275 100644 --- a/runtime/observatory/tests/service/set_vm_name_rpc_test.dart +++ b/runtime/observatory/tests/service/set_vm_name_rpc_test.dart @@ -32,4 +32,6 @@ var tests = [ }, ]; -main(args) async => runVMTests(args, tests, trace_service: true); +main(args) async => runVMTests(args, tests, + extraArgs: [ '--trace-service', + '--trace-service-verbose' ]); diff --git a/runtime/observatory/tests/service/step_over_await_test.dart b/runtime/observatory/tests/service/step_over_await_test.dart index 1bbb3b791b7..d5f946cb937 100644 --- a/runtime/observatory/tests/service/step_over_await_test.dart +++ b/runtime/observatory/tests/service/step_over_await_test.dart @@ -1,7 +1,7 @@ // Copyright (c) 2015, 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. -// VMOptions=--error_on_bad_type --error_on_bad_override --verbose_debug --trace_service +// VMOptions=--error_on_bad_type --error_on_bad_override --verbose_debug import 'dart:async'; import 'dart:developer'; diff --git a/runtime/observatory/tests/service/test_helper.dart b/runtime/observatory/tests/service/test_helper.dart index 9e8caa883ff..f444bf83be1 100644 --- a/runtime/observatory/tests/service/test_helper.dart +++ b/runtime/observatory/tests/service/test_helper.dart @@ -90,15 +90,12 @@ class _ServiceTesteeLauncher { Future _spawnProcess(bool pause_on_start, bool pause_on_exit, bool pause_on_unhandled_exceptions, - bool trace_service, - bool trace_compiler, bool testeeControlsServer, - bool useAuthToken) { + bool useAuthToken, + List extraArgs) { assert(pause_on_start != null); assert(pause_on_exit != null); assert(pause_on_unhandled_exceptions != null); - assert(trace_service != null); - assert(trace_compiler != null); assert(testeeControlsServer != null); assert(useAuthToken != null); @@ -106,39 +103,29 @@ class _ServiceTesteeLauncher { return _spawnSkyProcess(pause_on_start, pause_on_exit, pause_on_unhandled_exceptions, - trace_service, - trace_compiler, - testeeControlsServer); + testeeControlsServer, + extraArgs); } else { return _spawnDartProcess(pause_on_start, pause_on_exit, pause_on_unhandled_exceptions, - trace_service, - trace_compiler, testeeControlsServer, - useAuthToken); + useAuthToken, + extraArgs); } } Future _spawnDartProcess(bool pause_on_start, bool pause_on_exit, bool pause_on_unhandled_exceptions, - bool trace_service, - bool trace_compiler, bool testeeControlsServer, - bool useAuthToken) { + bool useAuthToken, + List extraArgs) { assert(!_shouldLaunchSkyShell()); String dartExecutable = Platform.executable; var fullArgs = []; - if (trace_service) { - fullArgs.add('--trace-service'); - fullArgs.add('--trace-service-verbose'); - } - if (trace_compiler) { - fullArgs.add('--trace-compiler'); - } if (pause_on_start) { fullArgs.add('--pause-isolates-on-start'); } @@ -148,6 +135,9 @@ class _ServiceTesteeLauncher { if (pause_on_unhandled_exceptions) { fullArgs.add('--pause-isolates-on-unhandled-exceptions'); } + if (extraArgs != null) { + fullArgs.addAll(extraArgs); + } fullArgs.addAll(Platform.executableArguments); if (!testeeControlsServer) { @@ -166,22 +156,14 @@ class _ServiceTesteeLauncher { Future _spawnSkyProcess(bool pause_on_start, bool pause_on_exit, bool pause_on_unhandled_exceptions, - bool trace_service, - bool trace_compiler, - bool testeeControlsServer) { + bool testeeControlsServer, + List extraArgs) { assert(_shouldLaunchSkyShell()); String dartExecutable = _skyShellPath(); var dartFlags = []; var fullArgs = []; - if (trace_service) { - dartFlags.add('--trace_service'); - dartFlags.add('--trace_service_verbose'); - } - if (trace_compiler) { - dartFlags.add('--trace_compiler'); - } if (pause_on_start) { dartFlags.add('--pause_isolates_on_start'); fullArgs.add('--start-paused'); @@ -194,6 +176,9 @@ class _ServiceTesteeLauncher { } // Override mirrors. dartFlags.add('--enable_mirrors=true'); + if (extraArgs != null) { + fullArgs.addAll(extraArgs); + } fullArgs.addAll(Platform.executableArguments); if (!testeeControlsServer) { @@ -223,17 +208,15 @@ class _ServiceTesteeLauncher { Future launch(bool pause_on_start, bool pause_on_exit, bool pause_on_unhandled_exceptions, - bool trace_service, - bool trace_compiler, bool testeeControlsServer, - bool useAuthToken) { + bool useAuthToken, + List extraArgs) { return _spawnProcess(pause_on_start, pause_on_exit, pause_on_unhandled_exceptions, - trace_service, - trace_compiler, testeeControlsServer, - useAuthToken).then((p) { + useAuthToken, + extraArgs).then((p) { Completer completer = new Completer(); process = p; Uri uri; @@ -288,12 +271,11 @@ void setupAddresses(Uri serverAddress) { class _ServiceTesterRunner { void run({List mainArgs, + List extraArgs, List vmTests, List isolateTests, bool pause_on_start: false, bool pause_on_exit: false, - bool trace_service: false, - bool trace_compiler: false, bool verbose_vm: false, bool pause_on_unhandled_exceptions: false, bool testeeControlsServer: false, @@ -301,9 +283,8 @@ class _ServiceTesterRunner { var process = new _ServiceTesteeLauncher(); process.launch(pause_on_start, pause_on_exit, pause_on_unhandled_exceptions, - trace_service, trace_compiler, testeeControlsServer, - useAuthToken).then((Uri serverAddress) async { + useAuthToken, extraArgs).then((Uri serverAddress) async { if (mainArgs.contains("--gdb")) { var pid = process.process.pid; var wait = new Duration(seconds: 10); @@ -364,12 +345,11 @@ Future runIsolateTests(List mainArgs, testeeConcurrent(), bool pause_on_start: false, bool pause_on_exit: false, - bool trace_service: false, - bool trace_compiler: false, bool verbose_vm: false, bool pause_on_unhandled_exceptions: false, bool testeeControlsServer: false, - bool useAuthToken: false}) async { + bool useAuthToken: false, + List extraArgs}) async { assert(!pause_on_start || testeeBefore == null); if (_isTestee()) { new _ServiceTesteeRunner().run(testeeBefore: testeeBefore, @@ -379,11 +359,10 @@ Future runIsolateTests(List mainArgs, } else { new _ServiceTesterRunner().run( mainArgs: mainArgs, + extraArgs: extraArgs, isolateTests: tests, pause_on_start: pause_on_start, pause_on_exit: pause_on_exit, - trace_service: trace_service, - trace_compiler: trace_compiler, verbose_vm: verbose_vm, pause_on_unhandled_exceptions: pause_on_unhandled_exceptions, testeeControlsServer: testeeControlsServer, @@ -406,10 +385,9 @@ void runIsolateTestsSynchronous(List mainArgs, void testeeConcurrent(), bool pause_on_start: false, bool pause_on_exit: false, - bool trace_service: false, - bool trace_compiler: false, bool verbose_vm: false, - bool pause_on_unhandled_exceptions: false}) { + bool pause_on_unhandled_exceptions: false, + List extraArgs}) { assert(!pause_on_start || testeeBefore == null); if (_isTestee()) { new _ServiceTesteeRunner().runSync(testeeBeforeSync: testeeBefore, @@ -419,11 +397,10 @@ void runIsolateTestsSynchronous(List mainArgs, } else { new _ServiceTesterRunner().run( mainArgs: mainArgs, + extraArgs: extraArgs, isolateTests: tests, pause_on_start: pause_on_start, pause_on_exit: pause_on_exit, - trace_service: trace_service, - trace_compiler: trace_compiler, verbose_vm: verbose_vm, pause_on_unhandled_exceptions: pause_on_unhandled_exceptions); } @@ -440,10 +417,9 @@ Future runVMTests(List mainArgs, testeeConcurrent(), bool pause_on_start: false, bool pause_on_exit: false, - bool trace_service: false, - bool trace_compiler: false, bool verbose_vm: false, - bool pause_on_unhandled_exceptions: false}) async { + bool pause_on_unhandled_exceptions: false, + List extraArgs}) async { if (_isTestee()) { new _ServiceTesteeRunner().run(testeeBefore: testeeBefore, testeeConcurrent: testeeConcurrent, @@ -452,11 +428,10 @@ Future runVMTests(List mainArgs, } else { new _ServiceTesterRunner().run( mainArgs: mainArgs, + extraArgs: extraArgs, vmTests: tests, pause_on_start: pause_on_start, pause_on_exit: pause_on_exit, - trace_service: trace_service, - trace_compiler: trace_compiler, verbose_vm: verbose_vm, pause_on_unhandled_exceptions: pause_on_unhandled_exceptions); } diff --git a/runtime/vm/code_generator.cc b/runtime/vm/code_generator.cc index 4a35895f3fa..b2fca6b9229 100644 --- a/runtime/vm/code_generator.cc +++ b/runtime/vm/code_generator.cc @@ -2215,6 +2215,14 @@ DEFINE_RUNTIME_ENTRY(DeoptimizeMaterialize, 0) { } +DEFINE_RUNTIME_ENTRY(RewindPostDeopt, 0) { +#if !defined(DART_PRECOMPILED_RUNTIME) + isolate->debugger()->RewindPostDeopt(); +#else + UNREACHABLE(); +#endif // !DART_PRECOMPILED_RUNTIME +} + DEFINE_LEAF_RUNTIME_ENTRY(intptr_t, BigintCompare, 2, diff --git a/runtime/vm/dart_entry.cc b/runtime/vm/dart_entry.cc index 3346befc81d..ae0f13f0713 100644 --- a/runtime/vm/dart_entry.cc +++ b/runtime/vm/dart_entry.cc @@ -551,7 +551,7 @@ RawObject* DartLibraryCalls::HandleMessage(const Object& handler, // If the isolate is being debugged and the debugger was stepping // through code, enable single stepping so debugger will stop // at the first location the user is interested in. - isolate->debugger()->SetSingleStep(); + isolate->debugger()->SetResumeAction(Debugger::kStepInto); } const Object& result = Object::Handle(zone, DartEntry::InvokeFunction(function, args)); diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc index 3cdf810c8d6..5c54271bc56 100644 --- a/runtime/vm/debugger.cc +++ b/runtime/vm/debugger.cc @@ -6,6 +6,8 @@ #include "include/dart_api.h" +#include "platform/address_sanitizer.h" + #include "vm/code_generator.h" #include "vm/code_patcher.h" #include "vm/compiler.h" @@ -42,6 +44,7 @@ DEFINE_FLAG(bool, trace_debugger_stacktrace, false, "Trace debugger stacktrace collection"); +DEFINE_FLAG(bool, trace_rewind, false, "Trace frame rewind"); DEFINE_FLAG(bool, verbose_debug, false, "Verbose debugger messages"); DEFINE_FLAG(bool, steal_breakpoints, @@ -850,6 +853,24 @@ RawObject* ActivationFrame::GetStackVar(intptr_t slot_index) { } +bool ActivationFrame::IsRewindable() const { + if (deopt_frame_.IsNull()) { + return true; + } + // TODO(turnidge): This is conservative. It looks at all values in + // the deopt_frame_ even though some of them may correspond to other + // inlined frames. + Object& obj = Object::Handle(); + for (int i = 0; i < deopt_frame_.Length(); i++) { + obj = deopt_frame_.At(i); + if (obj.raw() == Symbols::OptimizedOut().raw()) { + return false; + } + } + return true; +} + + void ActivationFrame::PrintContextMismatchError(intptr_t ctx_slot, intptr_t frame_ctx_level, intptr_t var_ctx_level) { @@ -1108,9 +1129,13 @@ void ActivationFrame::PrintToJSONObject(JSONObject* jsobj, bool full) { } } +static bool IsFunctionVisible(const Function& function) { + return FLAG_show_invisible_frames || function.is_visible(); +} + void DebuggerStackTrace::AddActivation(ActivationFrame* frame) { - if (FLAG_show_invisible_frames || frame->function().is_visible()) { + if (IsFunctionVisible(frame->function())) { trace_.Add(frame); } } @@ -1237,6 +1262,8 @@ Debugger::Debugger() breakpoint_locations_(NULL), code_breakpoints_(NULL), resume_action_(kContinue), + resume_frame_index_(-1), + post_deopt_frame_index_(-1), ignore_breakpoints_(false), pause_event_(NULL), obj_cache_(NULL), @@ -1300,10 +1327,13 @@ static RawFunction* ResolveLibraryFunction(const Library& library, } -bool Debugger::SetupStepOverAsyncSuspension() { +bool Debugger::SetupStepOverAsyncSuspension(const char** error) { ActivationFrame* top_frame = TopDartFrame(); if (!IsAtAsyncJump(top_frame)) { // Not at an async operation. + if (error) { + *error = "Isolate must be paused at an async suspension point"; + } return false; } Object& closure = Object::Handle(top_frame->GetAsyncOperation()); @@ -1313,24 +1343,42 @@ bool Debugger::SetupStepOverAsyncSuspension() { Breakpoint* bpt = SetBreakpointAtActivation(Instance::Cast(closure), true); if (bpt == NULL) { // Unable to set the breakpoint. + if (error) { + *error = "Unable to set breakpoint at async suspension point"; + } return false; } return true; } -void Debugger::SetSingleStep() { - resume_action_ = kSingleStep; -} - - -void Debugger::SetStepOver() { - resume_action_ = kStepOver; -} - - -void Debugger::SetStepOut() { - resume_action_ = kStepOut; +bool Debugger::SetResumeAction(ResumeAction action, + intptr_t frame_index, + const char** error) { + if (error) { + *error = NULL; + } + resume_frame_index_ = -1; + switch (action) { + case kStepInto: + case kStepOver: + case kStepOut: + case kContinue: + resume_action_ = action; + return true; + case kStepRewind: + if (!CanRewindFrame(frame_index, error)) { + return false; + } + resume_action_ = kStepRewind; + resume_frame_index_ = frame_index; + return true; + case kStepOverAsyncSuspension: + return SetupStepOverAsyncSuspension(error); + default: + UNREACHABLE(); + return false; + } } @@ -1622,6 +1670,7 @@ void Debugger::PauseException(const Instance& exc) { ASSERT(stack_trace_ == NULL); stack_trace_ = stack_trace; Pause(&event); + HandleSteppingRequest(stack_trace_); // we may get a rewind request stack_trace_ = NULL; } @@ -2547,7 +2596,7 @@ void Debugger::EnterSingleStepMode() { void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, bool skip_next_step) { stepping_fp_ = 0; - if (resume_action_ == kSingleStep) { + if (resume_action_ == kStepInto) { // When single stepping, we need to deoptimize because we might be // stepping into optimized code. This happens in particular if // the isolate has been interrupted, but can happen in other cases @@ -2557,7 +2606,7 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, isolate_->set_single_step(true); skip_next_step_ = skip_next_step; if (FLAG_verbose_debug) { - OS::Print("HandleSteppingRequest- kSingleStep\n"); + OS::Print("HandleSteppingRequest- kStepInto\n"); } } else if (resume_action_ == kStepOver) { DeoptimizeWorld(); @@ -2582,6 +2631,244 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, if (FLAG_verbose_debug) { OS::Print("HandleSteppingRequest- kStepOut %" Px "\n", stepping_fp_); } + } else if (resume_action_ == kStepRewind) { + if (FLAG_trace_rewind) { + OS::PrintErr("Rewinding to frame %" Pd "\n", resume_frame_index_); + OS::PrintErr( + "-------------------------\n" + "All frames...\n\n"); + StackFrameIterator iterator(false); + StackFrame* frame = iterator.NextFrame(); + intptr_t num = 0; + while ((frame != NULL)) { + OS::PrintErr("#%04" Pd " %s\n", num++, frame->ToCString()); + frame = iterator.NextFrame(); + } + } + RewindToFrame(resume_frame_index_); + UNREACHABLE(); + } +} + + +static intptr_t FindNextRewindFrameIndex(DebuggerStackTrace* stack, + intptr_t frame_index) { + for (intptr_t i = frame_index + 1; i < stack->Length(); i++) { + ActivationFrame* frame = stack->FrameAt(i); + if (frame->IsRewindable()) { + return i; + } + } + return -1; +} + + +// Can the top frame be rewound? +bool Debugger::CanRewindFrame(intptr_t frame_index, const char** error) const { + // check rewind pc is found + DebuggerStackTrace* stack = Isolate::Current()->debugger()->StackTrace(); + intptr_t num_frames = stack->Length(); + if (frame_index < 1 || frame_index >= num_frames) { + if (error) { + *error = Thread::Current()->zone()->PrintToString( + "Frame must be in bounds [1..%" Pd + "]: " + "saw %" Pd "", + num_frames - 1, frame_index); + } + return false; + } + ActivationFrame* frame = stack->FrameAt(frame_index); + if (!frame->IsRewindable()) { + intptr_t next_index = FindNextRewindFrameIndex(stack, frame_index); + if (next_index > 0) { + *error = Thread::Current()->zone()->PrintToString( + "Cannot rewind to frame %" Pd + " due to conflicting compiler " + "optimizations. " + "Run the vm with --no-prune-dead-locals to disallow these " + "optimizations. " + "Next valid rewind frame is %" Pd ".", + frame_index, next_index); + } else { + *error = Thread::Current()->zone()->PrintToString( + "Cannot rewind to frame %" Pd + " due to conflicting compiler " + "optimizations. " + "Run the vm with --no-prune-dead-locals to disallow these " + "optimizations.", + frame_index); + } + return false; + } + return true; +} + + +// Given a return address pc, find the "rewind" pc, which is the pc +// before the corresponding call. +static uword LookupRewindPc(const Code& code, uword pc) { + ASSERT(!code.is_optimized()); + ASSERT(code.ContainsInstructionAt(pc)); + + uword pc_offset = pc - code.PayloadStart(); + const PcDescriptors& descriptors = + PcDescriptors::Handle(code.pc_descriptors()); + PcDescriptors::Iterator iter( + descriptors, RawPcDescriptors::kRewind | RawPcDescriptors::kIcCall | + RawPcDescriptors::kUnoptStaticCall); + intptr_t rewind_deopt_id = -1; + uword rewind_pc = 0; + while (iter.MoveNext()) { + if (iter.Kind() == RawPcDescriptors::kRewind) { + // Remember the last rewind so we don't need to iterator twice. + rewind_pc = code.PayloadStart() + iter.PcOffset(); + rewind_deopt_id = iter.DeoptId(); + } + if ((pc_offset == iter.PcOffset()) && (iter.DeoptId() == rewind_deopt_id)) { + return rewind_pc; + } + } + return 0; +} + + +void Debugger::RewindToFrame(intptr_t frame_index) { + Thread* thread = Thread::Current(); + Zone* zone = thread->zone(); + Code& code = Code::Handle(zone); + Function& function = Function::Handle(zone); + + // Find the requested frame. + StackFrameIterator iterator(false); + intptr_t current_frame = 0; + for (StackFrame* frame = iterator.NextFrame(); frame != NULL; + frame = iterator.NextFrame()) { + ASSERT(frame->IsValid()); + if (frame->IsDartFrame()) { + code = frame->LookupDartCode(); + function = code.function(); + if (!IsFunctionVisible(function)) { + continue; + } + if (code.is_optimized()) { + intptr_t sub_index = 0; + for (InlinedFunctionsIterator it(code, frame->pc()); !it.Done(); + it.Advance()) { + if (current_frame == frame_index) { + RewindToOptimizedFrame(frame, code, sub_index); + UNREACHABLE(); + } + current_frame++; + sub_index++; + } + } else { + if (current_frame == frame_index) { + // We are rewinding to an unoptimized frame. + RewindToUnoptimizedFrame(frame, code); + UNREACHABLE(); + } + current_frame++; + } + } + } + UNIMPLEMENTED(); +} + + +void Debugger::RewindToUnoptimizedFrame(StackFrame* frame, const Code& code) { + // We will be jumping out of the debugger rather than exiting this + // function, so prepare the debugger state. + stack_trace_ = NULL; + resume_action_ = kContinue; + resume_frame_index_ = -1; + EnterSingleStepMode(); + + uword rewind_pc = LookupRewindPc(code, frame->pc()); + if (FLAG_trace_rewind && rewind_pc == 0) { + OS::PrintErr("Unable to find rewind pc for pc(%" Px ")\n", frame->pc()); + } + ASSERT(rewind_pc != 0); + if (FLAG_trace_rewind) { + OS::PrintErr( + "===============================\n" + "Rewinding to unoptimized frame:\n" + " rewind_pc(0x%" Px ") sp(0x%" Px ") fp(0x%" Px + ")\n" + "===============================\n", + rewind_pc, frame->sp(), frame->fp()); + } + Exceptions::JumpToFrame(Thread::Current(), rewind_pc, frame->sp(), + frame->fp(), true /* clear lazy deopt at target */); + UNREACHABLE(); +} + + +void Debugger::RewindToOptimizedFrame(StackFrame* frame, + const Code& optimized_code, + intptr_t sub_index) { + post_deopt_frame_index_ = sub_index; + + // We will be jumping out of the debugger rather than exiting this + // function, so prepare the debugger state. + stack_trace_ = NULL; + resume_action_ = kContinue; + resume_frame_index_ = -1; + EnterSingleStepMode(); + + if (FLAG_trace_rewind) { + OS::PrintErr( + "===============================\n" + "Deoptimizing frame for rewind:\n" + " deopt_pc(0x%" Px ") sp(0x%" Px ") fp(0x%" Px + ")\n" + "===============================\n", + frame->pc(), frame->sp(), frame->fp()); + } + Thread* thread = Thread::Current(); + thread->set_resume_pc(frame->pc()); + uword deopt_stub_pc = StubCode::DeoptForRewind_entry()->EntryPoint(); + Exceptions::JumpToFrame(thread, deopt_stub_pc, frame->sp(), frame->fp(), + true /* clear lazy deopt at target */); + UNREACHABLE(); +} + + +void Debugger::RewindPostDeopt() { + intptr_t rewind_frame = post_deopt_frame_index_; + post_deopt_frame_index_ = -1; + if (FLAG_trace_rewind) { + OS::PrintErr("Post deopt, jumping to frame %" Pd "\n", rewind_frame); + OS::PrintErr( + "-------------------------\n" + "All frames...\n\n"); + StackFrameIterator iterator(false); + StackFrame* frame = iterator.NextFrame(); + intptr_t num = 0; + while ((frame != NULL)) { + OS::PrintErr("#%04" Pd " %s\n", num++, frame->ToCString()); + frame = iterator.NextFrame(); + } + } + + Thread* thread = Thread::Current(); + Zone* zone = thread->zone(); + Code& code = Code::Handle(zone); + + StackFrameIterator iterator(false); + intptr_t current_frame = 0; + for (StackFrame* frame = iterator.NextFrame(); frame != NULL; + frame = iterator.NextFrame()) { + ASSERT(frame->IsValid()); + if (frame->IsDartFrame()) { + code = frame->LookupDartCode(); + ASSERT(!code.is_optimized()); + if (current_frame == rewind_frame) { + RewindToUnoptimizedFrame(frame, code); + UNREACHABLE(); + } + current_frame++; + } } } @@ -2793,7 +3080,7 @@ RawError* Debugger::PauseBreakpoint() { // We are at the entry of an async function. // We issue a step over to resume at the point after the await statement. - SetStepOver(); + SetResumeAction(kStepOver); // When we single step from a user breakpoint, our next stepping // point will be at the exact same pc. Skip it. HandleSteppingRequest(stack_trace_, true /* skip next step */); @@ -2846,7 +3133,7 @@ void Debugger::PauseDeveloper(const String& msg) { // We are in the native call to Developer_debugger. the developer // gets a better experience by not seeing this call. To accomplish // this, we continue execution until the call exits (step out). - SetStepOut(); + SetResumeAction(kStepOut); HandleSteppingRequest(stack_trace_); stack_trace_ = NULL; diff --git a/runtime/vm/debugger.h b/runtime/vm/debugger.h index 7319c8e41fd..74fd1ea5a5e 100644 --- a/runtime/vm/debugger.h +++ b/runtime/vm/debugger.h @@ -276,6 +276,9 @@ class ActivationFrame : public ZoneAllocated { // to the user and can be debugged. bool IsDebuggable() const; + // Returns true if it is possible to rewind the debugger to this frame. + bool IsRewindable() const; + // The context level of a frame is the context level at the // PC/token index of the frame. It determines the depth of the context // chain that belongs to the function of this activation frame. @@ -373,6 +376,15 @@ class DebuggerStackTrace : public ZoneAllocated { class Debugger { public: + enum ResumeAction { + kContinue, + kStepInto, + kStepOver, + kStepOut, + kStepRewind, + kStepOverAsyncSuspension, + }; + typedef void EventHandler(ServiceEvent* event); Debugger(); @@ -412,11 +424,10 @@ class Debugger { void RemoveBreakpoint(intptr_t bp_id); Breakpoint* GetBreakpointById(intptr_t id); - // Will return false if we are not at an await. - bool SetupStepOverAsyncSuspension(); - void SetStepOver(); - void SetSingleStep(); - void SetStepOut(); + bool SetResumeAction(ResumeAction action, + intptr_t frame_index = 1, + const char** error = NULL); + bool IsStepping() const { return resume_action_ != kContinue; } bool IsPaused() const { return pause_event_ != NULL; } @@ -513,11 +524,15 @@ class Debugger { intptr_t limitBreakpointId() { return next_id_; } - private: - enum ResumeAction { kContinue, kStepOver, kStepOut, kSingleStep }; + // Callback to the debugger to continue frame rewind, post-deoptimization. + void RewindPostDeopt(); + private: RawError* PauseRequest(ServiceEvent::EventKind kind); + // Will return false if we are not at an await. + bool SetupStepOverAsyncSuspension(const char** error); + bool NeedsIsolateEvents(); bool NeedsDebugEvents(); void InvokeEventHandler(ServiceEvent* event); @@ -588,6 +603,15 @@ class Debugger { void HandleSteppingRequest(DebuggerStackTrace* stack_trace, bool skip_next_step = false); + // Can we rewind to the indicated frame? + bool CanRewindFrame(intptr_t frame_index, const char** error) const; + + void RewindToFrame(intptr_t frame_index); + void RewindToUnoptimizedFrame(StackFrame* frame, const Code& code); + void RewindToOptimizedFrame(StackFrame* frame, + const Code& code, + intptr_t post_deopt_frame_index); + Isolate* isolate_; Dart_Port isolate_id_; // A unique ID for the isolate in the debugger. bool initialized_; @@ -601,6 +625,8 @@ class Debugger { // Tells debugger what to do when resuming execution after a breakpoint. ResumeAction resume_action_; + intptr_t resume_frame_index_; + intptr_t post_deopt_frame_index_; // Do not call back to breakpoint handler if this flag is set. // Effectively this means ignoring breakpoints. Set when Dart code may diff --git a/runtime/vm/debugger_api_impl.cc b/runtime/vm/debugger_api_impl.cc index 71610d2b096..77b7b272fc4 100644 --- a/runtime/vm/debugger_api_impl.cc +++ b/runtime/vm/debugger_api_impl.cc @@ -485,7 +485,7 @@ DART_EXPORT Dart_Handle Dart_SetStepOver() { DARTSCOPE(Thread::Current()); Isolate* I = T->isolate(); CHECK_DEBUGGER(I); - I->debugger()->SetStepOver(); + I->debugger()->SetResumeAction(Debugger::kStepOver); return Api::Success(); } @@ -494,7 +494,7 @@ DART_EXPORT Dart_Handle Dart_SetStepInto() { DARTSCOPE(Thread::Current()); Isolate* I = T->isolate(); CHECK_DEBUGGER(I); - I->debugger()->SetSingleStep(); + I->debugger()->SetResumeAction(Debugger::kStepInto); return Api::Success(); } @@ -503,7 +503,7 @@ DART_EXPORT Dart_Handle Dart_SetStepOut() { DARTSCOPE(Thread::Current()); Isolate* I = T->isolate(); CHECK_DEBUGGER(I); - I->debugger()->SetStepOut(); + I->debugger()->SetResumeAction(Debugger::kStepOut); return Api::Success(); } diff --git a/runtime/vm/exceptions.cc b/runtime/vm/exceptions.cc index 71eb55aea03..560de5541be 100644 --- a/runtime/vm/exceptions.cc +++ b/runtime/vm/exceptions.cc @@ -189,7 +189,6 @@ static bool FindExceptionHandler(Thread* thread, static void FindErrorHandler(uword* handler_pc, uword* handler_sp, uword* handler_fp) { - // TODO(turnidge): Is there a faster way to get the next entry frame? StackFrameIterator frames(StackFrameIterator::kDontValidateFrames); StackFrame* frame = frames.NextFrame(); ASSERT(frame != NULL); @@ -228,7 +227,17 @@ static uword RemapExceptionPCForDeopt(Thread* thread, break; } } + } +#endif // !DBC + return program_counter; +} + +static void ClearLazyDeopts(Thread* thread, uword frame_pointer) { +#if !defined(TARGET_ARCH_DBC) + MallocGrowableArray* pending_deopts = + thread->isolate()->pending_deopts(); + if (pending_deopts->length() > 0) { // We may be jumping over frames scheduled for lazy deopt. Remove these // frames from the pending deopt table, but only after unmarking them so // any stack walk that happens before the stack is unwound will still work. @@ -264,7 +273,6 @@ static uword RemapExceptionPCForDeopt(Thread* thread, #endif } #endif // !DBC - return program_counter; } @@ -281,14 +289,18 @@ static void JumpToExceptionHandler(Thread* thread, thread->set_resume_pc(remapped_pc); uword run_exception_pc = StubCode::RunExceptionHandler_entry()->EntryPoint(); Exceptions::JumpToFrame(thread, run_exception_pc, stack_pointer, - frame_pointer); + frame_pointer, false /* do not clear deopt */); } void Exceptions::JumpToFrame(Thread* thread, uword program_counter, uword stack_pointer, - uword frame_pointer) { + uword frame_pointer, + bool clear_deopt_at_target) { + uword fp_for_clearing = + (clear_deopt_at_target ? frame_pointer + 1 : frame_pointer); + ClearLazyDeopts(thread, fp_for_clearing); #if defined(USING_SIMULATOR) // Unwinding of the C++ frames and destroying of their stack resources is done // by the simulator, because the target stack_pointer is a simulated stack diff --git a/runtime/vm/exceptions.h b/runtime/vm/exceptions.h index c3fc4082d37..2b6fd7503d2 100644 --- a/runtime/vm/exceptions.h +++ b/runtime/vm/exceptions.h @@ -83,7 +83,8 @@ class Exceptions : AllStatic { static void JumpToFrame(Thread* thread, uword program_counter, uword stack_pointer, - uword frame_pointer); + uword frame_pointer, + bool clear_deopt_at_target); private: DISALLOW_COPY_AND_ASSIGN(Exceptions); diff --git a/runtime/vm/flow_graph_compiler.cc b/runtime/vm/flow_graph_compiler.cc index c6b6663ba45..405b37f7b9f 100644 --- a/runtime/vm/flow_graph_compiler.cc +++ b/runtime/vm/flow_graph_compiler.cc @@ -1271,6 +1271,7 @@ void FlowGraphCompiler::GenerateStaticCall(intptr_t deopt_id, kNumArgsChecked) ->raw(); } + AddCurrentDescriptor(RawPcDescriptors::kRewind, deopt_id, token_pos); EmitUnoptimizedStaticCall(argument_count, deopt_id, token_pos, locs, call_ic_data); } diff --git a/runtime/vm/intermediate_language.cc b/runtime/vm/intermediate_language.cc index 49126f9b6e7..373aa483729 100644 --- a/runtime/vm/intermediate_language.cc +++ b/runtime/vm/intermediate_language.cc @@ -3075,6 +3075,8 @@ void InstanceCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) { } else { // Unoptimized code. ASSERT(!HasICData()); + compiler->AddCurrentDescriptor(RawPcDescriptors::kRewind, deopt_id(), + token_pos()); bool is_smi_two_args_op = false; const StubEntry* stub_entry = TwoArgsSmiOpInlineCacheEntry(token_kind()); if (stub_entry != NULL) { diff --git a/runtime/vm/json_stream.cc b/runtime/vm/json_stream.cc index 8310e70a603..e75ba30f6c3 100644 --- a/runtime/vm/json_stream.cc +++ b/runtime/vm/json_stream.cc @@ -139,6 +139,8 @@ static const char* GetJSONRpcErrorMessage(intptr_t code) { return "Isolate must be runnable"; case kIsolateMustBePaused: return "Isolate must be paused"; + case kCannotResume: + return "Cannot resume execution"; case kIsolateIsReloading: return "Isolate is reloading"; case kFileSystemAlreadyExists: diff --git a/runtime/vm/json_stream.h b/runtime/vm/json_stream.h index b54bfb24f13..bdf1b3a58c7 100644 --- a/runtime/vm/json_stream.h +++ b/runtime/vm/json_stream.h @@ -53,6 +53,7 @@ enum JSONRpcErrorCode { kStreamNotSubscribed = 104, kIsolateMustBeRunnable = 105, kIsolateMustBePaused = 106, + kCannotResume = 107, // Experimental (used in private rpcs). kIsolateIsReloading = 1000, diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 2b05ec58e92..0e8a0f74aeb 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -11702,6 +11702,8 @@ const char* PcDescriptors::KindAsStr(RawPcDescriptors::Kind kind) { return "runtime-call "; case RawPcDescriptors::kOsrEntry: return "osr-entry "; + case RawPcDescriptors::kRewind: + return "rewind "; case RawPcDescriptors::kOther: return "other "; case RawPcDescriptors::kAnyKind: diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h index 9097b1d6501..8ca70b7686a 100644 --- a/runtime/vm/raw_object.h +++ b/runtime/vm/raw_object.h @@ -1197,7 +1197,8 @@ class RawPcDescriptors : public RawObject { kUnoptStaticCall = kIcCall << 1, // Call to a known target via stub. kRuntimeCall = kUnoptStaticCall << 1, // Runtime call. kOsrEntry = kRuntimeCall << 1, // OSR entry point in unopt. code. - kOther = kOsrEntry << 1, + kRewind = kOsrEntry << 1, // Call rewind target address. + kOther = kRewind << 1, kLastKind = kOther, kAnyKind = -1 }; diff --git a/runtime/vm/runtime_entry_list.h b/runtime/vm/runtime_entry_list.h index d450ee4b9eb..edc0e41a426 100644 --- a/runtime/vm/runtime_entry_list.h +++ b/runtime/vm/runtime_entry_list.h @@ -40,6 +40,7 @@ namespace dart { V(TraceFunctionEntry) \ V(TraceFunctionExit) \ V(DeoptimizeMaterialize) \ + V(RewindPostDeopt) \ V(UpdateFieldCid) \ V(InitStaticField) \ V(GrowRegExpStack) \ diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index 5e34b4a9a4b..deedaed5e3f 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -2988,18 +2988,57 @@ static bool GetVMTimeline(Thread* thread, JSONStream* js) { } +static const char* const step_enum_names[] = { + "None", "Into", "Over", "Out", "Rewind", "OverAsyncSuspension", NULL, +}; + + +static const Debugger::ResumeAction step_enum_values[] = { + Debugger::kContinue, Debugger::kStepInto, + Debugger::kStepOver, Debugger::kStepOut, + Debugger::kStepRewind, Debugger::kStepOverAsyncSuspension, + Debugger::kContinue, // Default value +}; + + static const MethodParameter* resume_params[] = { - RUNNABLE_ISOLATE_PARAMETER, NULL, + RUNNABLE_ISOLATE_PARAMETER, + new EnumParameter("step", false, step_enum_names), + new UIntParameter("frameIndex", false), NULL, }; static bool Resume(Thread* thread, JSONStream* js) { const char* step_param = js->LookupParam("step"); + Debugger::ResumeAction step = Debugger::kContinue; + if (step_param != NULL) { + step = EnumMapper(step_param, step_enum_names, step_enum_values); + } +#if defined(TARGET_ARCH_DBC) + if (step == Debugger::kStepRewind) { + js->PrintError(kCannotResume, + "Rewind not yet implemented on this architecture"); + return true; + } +#endif + intptr_t frame_index = 1; + const char* frame_index_param = js->LookupParam("frameIndex"); + if (frame_index_param != NULL) { + if (step != Debugger::kStepRewind) { + // Only rewind supports the frameIndex parameter. + js->PrintError( + kInvalidParams, + "%s: the 'frameIndex' parameter can only be used when rewinding", + js->method()); + return true; + } + frame_index = UIntParameter::Parse(js->LookupParam("frameIndex")); + } Isolate* isolate = thread->isolate(); if (isolate->message_handler()->is_paused_on_start()) { // If the user is issuing a 'Over' or an 'Out' step, that is the // same as a regular resume request. - if ((step_param != NULL) && (strcmp(step_param, "Into") == 0)) { + if (step == Debugger::kStepInto) { isolate->debugger()->EnterSingleStepMode(); } isolate->message_handler()->set_should_pause_on_start(false); @@ -3028,31 +3067,18 @@ static bool Resume(Thread* thread, JSONStream* js) { PrintSuccess(js); return true; } - if (isolate->debugger()->PauseEvent() != NULL) { - if (step_param != NULL) { - if (strcmp(step_param, "Into") == 0) { - isolate->debugger()->SetSingleStep(); - } else if (strcmp(step_param, "Over") == 0) { - isolate->debugger()->SetStepOver(); - } else if (strcmp(step_param, "Out") == 0) { - isolate->debugger()->SetStepOut(); - } else if (strcmp(step_param, "OverAsyncSuspension") == 0) { - if (!isolate->debugger()->SetupStepOverAsyncSuspension()) { - js->PrintError(kInvalidParams, - "Isolate must be paused at an async suspension point"); - return true; - } - } else { - PrintInvalidParamError(js, "step"); - return true; - } - } - isolate->SetResumeRequest(); - PrintSuccess(js); + if (isolate->debugger()->PauseEvent() == NULL) { + js->PrintError(kIsolateMustBePaused, NULL); return true; } - js->PrintError(kIsolateMustBePaused, NULL); + const char* error = NULL; + if (!isolate->debugger()->SetResumeAction(step, frame_index, &error)) { + js->PrintError(kCannotResume, error); + return true; + } + isolate->SetResumeRequest(); + PrintSuccess(js); return true; } diff --git a/runtime/vm/simulator_dbc.cc b/runtime/vm/simulator_dbc.cc index 8febf134689..445397360ed 100644 --- a/runtime/vm/simulator_dbc.cc +++ b/runtime/vm/simulator_dbc.cc @@ -3715,14 +3715,25 @@ void Simulator::JumpToFrame(uword pc, uword sp, uword fp, Thread* thread) { fp_ = reinterpret_cast(fp); if (pc == StubCode::RunExceptionHandler_entry()->EntryPoint()) { - // Instead of executing the RunException stub, we implement its - // behavior here. + // The RunExceptionHandler stub is a placeholder. We implement + // its behavior here. RawObject* raw_exception = thread->active_exception(); RawObject* raw_stacktrace = thread->active_stacktrace(); ASSERT(raw_exception != Object::null()); special_[kExceptionSpecialIndex] = raw_exception; special_[kStacktraceSpecialIndex] = raw_stacktrace; pc_ = thread->resume_pc(); + } else if (pc == StubCode::DeoptForRewind_entry()->EntryPoint()) { + // The DeoptForRewind stub is a placeholder. We will eventually + // implement its behavior here. + // + // TODO(turnidge): Refactor the Deopt bytecode so that we can use + // the implementation here too. The deopt pc is stored in + // Thread::resume_pc(). After invoking deoptimization, we usually + // call into Debugger::RewindPostDeopt(), but I need to figure out + // if that makes any sense (it would JumpToFrame during a + // JumpToFrame, which seems wrong). + UNIMPLEMENTED(); } else { pc_ = pc; } diff --git a/runtime/vm/stub_code.h b/runtime/vm/stub_code.h index 3a2cea016c3..04b03602fc7 100644 --- a/runtime/vm/stub_code.h +++ b/runtime/vm/stub_code.h @@ -27,6 +27,7 @@ class Deserializer; V(GetStackPointer) \ V(JumpToFrame) \ V(RunExceptionHandler) \ + V(DeoptForRewind) \ V(UpdateStoreBuffer) \ V(PrintStopMessage) \ V(CallToRuntime) \ @@ -75,6 +76,7 @@ class Deserializer; V(LazyCompile) \ V(OptimizeFunction) \ V(RunExceptionHandler) \ + V(DeoptForRewind) \ V(FixCallersTarget) \ V(Deoptimize) \ V(DeoptimizeLazyFromReturn) \ @@ -195,6 +197,12 @@ class StubCode : public AllStatic { enum DeoptStubKind { kLazyDeoptFromReturn, kLazyDeoptFromThrow, kEagerDeopt }; +// Zap value used to indicate unused CODE_REG in deopt. +static const int64_t kZapCodeReg = 0xf1f1f1f1; + +// Zap value used to indicate unused return address in deopt. +static const int64_t kZapReturnAddress = 0xe1e1e1e1; + } // namespace dart #endif // RUNTIME_VM_STUB_CODE_H_ diff --git a/runtime/vm/stub_code_arm.cc b/runtime/vm/stub_code_arm.cc index 87b054d36ae..58124b2428e 100644 --- a/runtime/vm/stub_code_arm.cc +++ b/runtime/vm/stub_code_arm.cc @@ -537,19 +537,20 @@ static void GenerateDeoptimizationSequence(Assembler* assembler, __ LeaveStubFrame(); // Remove materialization arguments. __ add(SP, SP, Operand(R2, ASR, kSmiTagSize)); - __ Ret(); + // The caller is responsible for emitting the return instruction. } // R0: result, must be preserved void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { // Push zap value instead of CODE_REG for lazy deopt. - __ LoadImmediate(IP, 0xf1f1f1f1); + __ LoadImmediate(IP, kZapCodeReg); __ Push(IP); // Return address for "call" to deopt stub. - __ LoadImmediate(LR, 0xe1e1e1e1); + __ LoadImmediate(LR, kZapReturnAddress); __ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset())); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn); + __ Ret(); } @@ -557,17 +558,19 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { // R1: stacktrace, must be preserved void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) { // Push zap value instead of CODE_REG for lazy deopt. - __ LoadImmediate(IP, 0xf1f1f1f1); + __ LoadImmediate(IP, kZapCodeReg); __ Push(IP); // Return address for "call" to deopt stub. - __ LoadImmediate(LR, 0xe1e1e1e1); + __ LoadImmediate(LR, kZapReturnAddress); __ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset())); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow); + __ Ret(); } void StubCode::GenerateDeoptimizeStub(Assembler* assembler) { GenerateDeoptimizationSequence(assembler, kEagerDeopt); + __ Ret(); } @@ -1899,6 +1902,26 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) { } +// Deoptimize a frame on the call stack before rewinding. +// The arguments are stored in the Thread object. +// No result. +void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) { + // Push zap value instead of CODE_REG. + __ LoadImmediate(IP, kZapCodeReg); + __ Push(IP); + + // Load the deopt pc into LR. + __ LoadFromOffset(kWord, LR, THR, Thread::resume_pc_offset()); + GenerateDeoptimizationSequence(assembler, kEagerDeopt); + + // After we have deoptimized, jump to the correct frame. + __ EnterStubFrame(); + __ CallRuntime(kRewindPostDeoptRuntimeEntry, 0); + __ LeaveStubFrame(); + __ bkpt(0); +} + + // Calls to the runtime to optimize the given function. // R8: function to be reoptimized. // R4: argument descriptor (preserved). diff --git a/runtime/vm/stub_code_arm64.cc b/runtime/vm/stub_code_arm64.cc index 5a5a156ae60..385e7038d2a 100644 --- a/runtime/vm/stub_code_arm64.cc +++ b/runtime/vm/stub_code_arm64.cc @@ -558,19 +558,20 @@ static void GenerateDeoptimizationSequence(Assembler* assembler, __ LeaveStubFrame(); // Remove materialization arguments. __ add(SP, SP, Operand(R2)); - __ ret(); + // The caller is responsible for emitting the return instruction. } // R0: result, must be preserved void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { // Push zap value instead of CODE_REG for lazy deopt. - __ LoadImmediate(TMP, 0xf1f1f1f1); + __ LoadImmediate(TMP, kZapCodeReg); __ Push(TMP); // Return address for "call" to deopt stub. - __ LoadImmediate(LR, 0xe1e1e1e1); + __ LoadImmediate(LR, kZapReturnAddress); __ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset())); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn); + __ ret(); } @@ -578,17 +579,19 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { // R1: stacktrace, must be preserved void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) { // Push zap value instead of CODE_REG for lazy deopt. - __ LoadImmediate(TMP, 0xf1f1f1f1); + __ LoadImmediate(TMP, kZapCodeReg); __ Push(TMP); // Return address for "call" to deopt stub. - __ LoadImmediate(LR, 0xe1e1e1e1); + __ LoadImmediate(LR, kZapReturnAddress); __ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset())); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow); + __ ret(); } void StubCode::GenerateDeoptimizeStub(Assembler* assembler) { GenerateDeoptimizationSequence(assembler, kEagerDeopt); + __ ret(); } @@ -1949,6 +1952,26 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) { } +// Deoptimize a frame on the call stack before rewinding. +// The arguments are stored in the Thread object. +// No result. +void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) { + // Push zap value instead of CODE_REG. + __ LoadImmediate(TMP, kZapCodeReg); + __ Push(TMP); + + // Load the deopt pc into LR. + __ LoadFromOffset(LR, THR, Thread::resume_pc_offset()); + GenerateDeoptimizationSequence(assembler, kEagerDeopt); + + // After we have deoptimized, jump to the correct frame. + __ EnterStubFrame(); + __ CallRuntime(kRewindPostDeoptRuntimeEntry, 0); + __ LeaveStubFrame(); + __ brk(0); +} + + // Calls to the runtime to optimize the given function. // R6: function to be re-optimized. // R4: argument descriptor (preserved). diff --git a/runtime/vm/stub_code_dbc.cc b/runtime/vm/stub_code_dbc.cc index da24e19b745..ef4118222cf 100644 --- a/runtime/vm/stub_code_dbc.cc +++ b/runtime/vm/stub_code_dbc.cc @@ -47,6 +47,12 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) { } +// Not executed, but used as a sentinel in Simulator::JumpToFrame. +void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) { + __ Trap(); +} + + // TODO(vegorov) Don't generate this stub. void StubCode::GenerateFixCallersTargetStub(Assembler* assembler) { __ Trap(); diff --git a/runtime/vm/stub_code_ia32.cc b/runtime/vm/stub_code_ia32.cc index ca70617824b..ce6521d339d 100644 --- a/runtime/vm/stub_code_ia32.cc +++ b/runtime/vm/stub_code_ia32.cc @@ -351,8 +351,6 @@ static void PushArgumentsArray(Assembler* assembler) { // +------------------+ // | return-address | (deoptimization point) // +------------------+ -// | Saved CODE_REG | -// +------------------+ // | ... | <- SP of optimized frame // // Parts of the code cannot GC, part of the code can GC. @@ -462,15 +460,16 @@ static void GenerateDeoptimizationSequence(Assembler* assembler, __ popl(ECX); // Pop return address. __ addl(ESP, EBX); // Remove materialization arguments. __ pushl(ECX); // Push return address. - __ ret(); + // The caller is responsible for emitting the return instruction. } // EAX: result, must be preserved void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { // Return address for "call" to deopt stub. - __ pushl(Immediate(0xe1e1e1e1)); + __ pushl(Immediate(kZapReturnAddress)); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn); + __ ret(); } @@ -478,13 +477,15 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { // EDX: stacktrace, must be preserved void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) { // Return address for "call" to deopt stub. - __ pushl(Immediate(0xe1e1e1e1)); + __ pushl(Immediate(kZapReturnAddress)); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow); + __ ret(); } void StubCode::GenerateDeoptimizeStub(Assembler* assembler) { GenerateDeoptimizationSequence(assembler, kEagerDeopt); + __ ret(); } @@ -1835,6 +1836,22 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) { } +// Deoptimize a frame on the call stack before rewinding. +// The arguments are stored in the Thread object. +// No result. +void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) { + // Push the deopt pc. + __ pushl(Address(THR, Thread::resume_pc_offset())); + GenerateDeoptimizationSequence(assembler, kEagerDeopt); + + // After we have deoptimized, jump to the correct frame. + __ EnterStubFrame(); + __ CallRuntime(kRewindPostDeoptRuntimeEntry, 0); + __ LeaveFrame(); + __ int3(); +} + + // Calls to the runtime to optimize the given function. // EBX: function to be reoptimized. // EDX: argument descriptor (preserved). diff --git a/runtime/vm/stub_code_mips.cc b/runtime/vm/stub_code_mips.cc index d52e6cc6439..53394cf82eb 100644 --- a/runtime/vm/stub_code_mips.cc +++ b/runtime/vm/stub_code_mips.cc @@ -549,18 +549,19 @@ static void GenerateDeoptimizationSequence(Assembler* assembler, // Remove materialization arguments. __ SmiUntag(T1); __ addu(SP, SP, T1); - __ Ret(); + // The caller is responsible for emitting the return instruction. } // V0: result, must be preserved void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { // Push zap value instead of CODE_REG for lazy deopt. - __ LoadImmediate(TMP, 0xf1f1f1f1); + __ LoadImmediate(TMP, kZapCodeReg); __ Push(TMP); // Return address for "call" to deopt stub. - __ LoadImmediate(RA, 0xe1e1e1e1); + __ LoadImmediate(RA, kZapReturnAddress); __ lw(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset())); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn); + __ Ret(); } @@ -568,17 +569,19 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { // V1: stacktrace, must be preserved void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) { // Push zap value instead of CODE_REG for lazy deopt. - __ LoadImmediate(TMP, 0xf1f1f1f1); + __ LoadImmediate(TMP, kZapCodeReg); __ Push(TMP); // Return address for "call" to deopt stub. - __ LoadImmediate(RA, 0xe1e1e1e1); + __ LoadImmediate(RA, kZapReturnAddress); __ lw(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset())); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow); + __ Ret(); } void StubCode::GenerateDeoptimizeStub(Assembler* assembler) { GenerateDeoptimizationSequence(assembler, kEagerDeopt); + __ Ret(); } @@ -2028,6 +2031,26 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) { } +// Deoptimize a frame on the call stack before rewinding. +// The arguments are stored in the Thread object. +// No result. +void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) { + // Push zap value instead of CODE_REG. + __ LoadImmediate(TMP, kZapCodeReg); + __ Push(TMP); + + // Load the deopt pc into RA. + __ lw(RA, Address(THR, Thread::resume_pc_offset())); + GenerateDeoptimizationSequence(assembler, kEagerDeopt); + + // After we have deoptimized, jump to the correct frame. + __ EnterStubFrame(); + __ CallRuntime(kRewindPostDeoptRuntimeEntry, 0); + __ LeaveStubFrame(); + __ break_(0); +} + + // Calls to the runtime to optimize the given function. // T0: function to be reoptimized. // S4: argument descriptor (preserved). diff --git a/runtime/vm/stub_code_x64.cc b/runtime/vm/stub_code_x64.cc index 167154b0879..0341f268f80 100644 --- a/runtime/vm/stub_code_x64.cc +++ b/runtime/vm/stub_code_x64.cc @@ -498,18 +498,19 @@ static void GenerateDeoptimizationSequence(Assembler* assembler, __ popq(RCX); // Pop return address. __ addq(RSP, RBX); // Remove materialization arguments. __ pushq(RCX); // Push return address. - __ ret(); + // The caller is responsible for emitting the return instruction. } // RAX: result, must be preserved void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { // Push zap value instead of CODE_REG for lazy deopt. - __ pushq(Immediate(0xf1f1f1f1)); + __ pushq(Immediate(kZapCodeReg)); // Return address for "call" to deopt stub. - __ pushq(Immediate(0xe1e1e1e1)); + __ pushq(Immediate(kZapReturnAddress)); __ movq(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset())); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn); + __ ret(); } @@ -517,16 +518,18 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { // RDX: stacktrace, must be preserved void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) { // Push zap value instead of CODE_REG for lazy deopt. - __ pushq(Immediate(0xf1f1f1f1)); + __ pushq(Immediate(kZapCodeReg)); // Return address for "call" to deopt stub. - __ pushq(Immediate(0xe1e1e1e1)); + __ pushq(Immediate(kZapReturnAddress)); __ movq(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset())); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow); + __ ret(); } void StubCode::GenerateDeoptimizeStub(Assembler* assembler) { GenerateDeoptimizationSequence(assembler, kEagerDeopt); + __ ret(); } @@ -1891,6 +1894,25 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) { } +// Deoptimize a frame on the call stack before rewinding. +// The arguments are stored in the Thread object. +// No result. +void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) { + // Push zap value instead of CODE_REG. + __ pushq(Immediate(kZapCodeReg)); + + // Push the deopt pc. + __ pushq(Address(THR, Thread::resume_pc_offset())); + GenerateDeoptimizationSequence(assembler, kEagerDeopt); + + // After we have deoptimized, jump to the correct frame. + __ EnterStubFrame(); + __ CallRuntime(kRewindPostDeoptRuntimeEntry, 0); + __ LeaveStubFrame(); + __ int3(); +} + + // Calls to the runtime to optimize the given function. // RDI: function to be reoptimized. // R10: argument descriptor (preserved).