Implement rewind on all platforms except for dbc.

BUG=

Review URL: https://codereview.chromium.org/2534413005 .
This commit is contained in:
Todd Turnidge 2016-12-01 17:19:31 -08:00
parent 1ebeb79942
commit e0d350aa27
35 changed files with 1001 additions and 182 deletions

View file

@ -209,7 +209,8 @@ class DownCommand extends DebuggerCommand {
debugger.downFrame(count); debugger.downFrame(count);
debugger.console.print('frame = ${debugger.currentFrame}'); debugger.console.print('frame = ${debugger.currentFrame}');
} catch (e) { } 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); return new Future.value(null);
} }
@ -243,7 +244,8 @@ class UpCommand extends DebuggerCommand {
debugger.upFrame(count); debugger.upFrame(count);
debugger.console.print('frame = ${debugger.currentFrame}'); debugger.console.print('frame = ${debugger.currentFrame}');
} on RangeError catch (e) { } 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); return new Future.value(null);
} }
@ -279,7 +281,8 @@ class FrameCommand extends DebuggerCommand {
debugger.currentFrame = frame; debugger.currentFrame = frame;
debugger.console.print('frame = ${debugger.currentFrame}'); debugger.console.print('frame = ${debugger.currentFrame}');
} on RangeError catch (e) { } 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); return new Future.value(null);
} }
@ -403,6 +406,73 @@ class StepCommand extends DebuggerCommand {
'Syntax: step\n'; 'Syntax: step\n';
} }
class RewindCommand extends DebuggerCommand {
RewindCommand(Debugger debugger) : super(debugger, 'rewind', []);
Future run(List<String> 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 <count>\n';
}
class ReloadCommand extends DebuggerCommand {
ReloadCommand(Debugger debugger) : super(debugger, 'reload', []);
Future run(List<String> 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 { class ClsCommand extends DebuggerCommand {
ClsCommand(Debugger debugger) : super(debugger, 'cls', []) {} ClsCommand(Debugger debugger) : super(debugger, 'cls', []) {}
@ -663,8 +733,8 @@ class BreakCommand extends DebuggerCommand {
var script = loc.script; var script = loc.script;
await script.load(); await script.load();
if (loc.line < 1 || loc.line > script.lines.length) { if (loc.line < 1 || loc.line > script.lines.length) {
debugger.console debugger.console.print(
.print('line number must be in range [1,${script.lines.length}]'); 'line number must be in range [1..${script.lines.length}]');
return; return;
} }
try { try {
@ -744,7 +814,7 @@ class ClearCommand extends DebuggerCommand {
var script = loc.script; var script = loc.script;
if (loc.line < 1 || loc.line > script.lines.length) { if (loc.line < 1 || loc.line > script.lines.length) {
debugger.console 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; return;
} }
var lineInfo = script.getLine(loc.line); var lineInfo = script.getLine(loc.line);
@ -899,7 +969,6 @@ class IsolateCommand extends DebuggerCommand {
: super(debugger, 'isolate', [ : super(debugger, 'isolate', [
new IsolateListCommand(debugger), new IsolateListCommand(debugger),
new IsolateNameCommand(debugger), new IsolateNameCommand(debugger),
new IsolateReloadCommand(debugger),
]) { ]) {
alias = 'i'; alias = 'i';
} }
@ -1042,27 +1111,6 @@ class IsolateNameCommand extends DebuggerCommand {
'Syntax: isolate name <name>\n'; 'Syntax: isolate name <name>\n';
} }
class IsolateReloadCommand extends DebuggerCommand {
IsolateReloadCommand(Debugger debugger) : super(debugger, 'reload', []);
Future run(List<String> 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 { class InfoCommand extends DebuggerCommand {
InfoCommand(Debugger debugger) InfoCommand(Debugger debugger)
: super(debugger, 'info', [ : super(debugger, 'info', [
@ -1371,7 +1419,9 @@ class ObservatoryDebugger extends Debugger {
new LogCommand(this), new LogCommand(this),
new PauseCommand(this), new PauseCommand(this),
new PrintCommand(this), new PrintCommand(this),
new ReloadCommand(this),
new RefreshCommand(this), new RefreshCommand(this),
new RewindCommand(this),
new SetCommand(this), new SetCommand(this),
new SmartNextCommand(this), new SmartNextCommand(this),
new StepCommand(this), new StepCommand(this),
@ -1879,6 +1929,20 @@ class ObservatoryDebugger extends Debugger {
return new Future.value(null); 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 { class DebuggerPageElement extends HtmlElement implements Renderable {
@ -2513,7 +2577,9 @@ class DebuggerFrameElement extends HtmlElement implements Renderable {
} }
bool matchFrame(S.Frame newFrame) { 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) { void updateFrame(S.Frame newFrame) {

View file

@ -42,11 +42,13 @@ class ServerRpcException extends RpcException implements M.RequestException {
static const kStreamNotSubscribed = 104; static const kStreamNotSubscribed = 104;
static const kIsolateMustBeRunnable = 105; static const kIsolateMustBeRunnable = 105;
static const kIsolateMustBePaused = 106; static const kIsolateMustBePaused = 106;
static const kCannotResume = 107;
static const kIsolateIsReloading = 1000; static const kIsolateIsReloading = 1000;
static const kFileSystemAlreadyExists = 1001; static const kFileSystemAlreadyExists = 1001;
static const kFileSystemDoesNotExist = 1002; static const kFileSystemDoesNotExist = 1002;
static const kFileDoesNotExist = 1003; static const kFileDoesNotExist = 1003;
static const kIsolateReloadFailed = 1004; static const kIsolateReloadFailed = 1004;
static const kIsolateReloadBarred = 1005;
int code; int code;
Map data; Map data;
@ -1279,7 +1281,6 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
bool loading = true; bool loading = true;
bool runnable = false; bool runnable = false;
bool ioEnabled = false; bool ioEnabled = false;
bool reloading = false;
M.IsolateStatus get status { M.IsolateStatus get status {
if (paused) { if (paused) {
return M.IsolateStatus.paused; return M.IsolateStatus.paused;
@ -1344,13 +1345,12 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
params['pause'] = pause; params['pause'] = pause;
} }
return invokeRpc('reloadSources', params).then((result) { return invokeRpc('reloadSources', params).then((result) {
reloading = true; _cache.clear();
return result; return result;
}); });
} }
void _handleIsolateReloadEvent(ServiceEvent event) { void _handleIsolateReloadEvent(ServiceEvent event) {
reloading = false;
if (event.reloadError != null) { if (event.reloadError != null) {
// Failure. // Failure.
print('Reload failed: ${event.reloadError}'); print('Reload failed: ${event.reloadError}');
@ -1777,6 +1777,10 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
return invokeRpc('resume', {'step': 'Out'}); return invokeRpc('resume', {'step': 'Out'});
} }
Future rewind(int count) {
return invokeRpc('resume', {'step': 'Rewind', 'frameIndex': count});
}
Future setName(String newName) { Future setName(String newName) {
return invokeRpc('setName', {'name': newName}); return invokeRpc('setName', {'name': newName});
} }

View file

@ -52,5 +52,6 @@ hasStoppedAtBreakpoint,
main(args) => runIsolateTests(args, tests, main(args) => runIsolateTests(args, tests,
testeeConcurrent: testMain, testeeConcurrent: testMain,
trace_service: true, verbose_vm: true,
verbose_vm: true); extraArgs: [ '--trace-service',
'--trace-service-verbose' ]);

View file

@ -86,5 +86,6 @@ main(args) => runIsolateTests(args, tests,
testeeConcurrent: testMain, testeeConcurrent: testMain,
pause_on_start: true, pause_on_start: true,
pause_on_exit: true, pause_on_exit: true,
trace_service: true, verbose_vm: true,
verbose_vm: true); extraArgs: [ '--trace-service',
'--trace-service-verbose' ]);

View file

@ -89,5 +89,6 @@ main(args) => runIsolateTests(args, tests,
testeeConcurrent: testMain, testeeConcurrent: testMain,
pause_on_start: true, pause_on_start: true,
pause_on_exit: true, pause_on_exit: true,
trace_service: true, verbose_vm: true,
verbose_vm: true); extraArgs: [ '--trace-service',
'--trace-service-verbose' ]);

View file

@ -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']);

View file

@ -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']);

View file

@ -64,6 +64,8 @@ evaluate_activation_in_method_class_test: CompileTimeError # Issue 24478
get_allocation_samples_test: RuntimeError # Profiling unimplemented. get_allocation_samples_test: RuntimeError # Profiling unimplemented.
get_cpu_profile_timeline_rpc_test: RuntimeError # Profiling unimplemented. get_cpu_profile_timeline_rpc_test: RuntimeError # Profiling unimplemented.
implicit_getter_setter_test: RuntimeError # Field guards 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 ] [ $hot_reload || $hot_reload_rollback ]
# Skip all service tests because random reloads interfere. # Skip all service tests because random reloads interfere.

View file

@ -234,6 +234,10 @@ IsolateTest stoppedAtLine(int line) {
return (Isolate isolate) async { return (Isolate isolate) async {
print("Checking we are at line $line"); 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(); ServiceMap stack = await isolate.getStack();
expect(stack.type, equals('Stack')); expect(stack.type, equals('Stack'));

View file

@ -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' ]);

View file

@ -1,7 +1,7 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file // 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 // 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. // 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:async';
import 'dart:developer'; import 'dart:developer';

View file

@ -90,15 +90,12 @@ class _ServiceTesteeLauncher {
Future<Process> _spawnProcess(bool pause_on_start, Future<Process> _spawnProcess(bool pause_on_start,
bool pause_on_exit, bool pause_on_exit,
bool pause_on_unhandled_exceptions, bool pause_on_unhandled_exceptions,
bool trace_service,
bool trace_compiler,
bool testeeControlsServer, bool testeeControlsServer,
bool useAuthToken) { bool useAuthToken,
List<String> extraArgs) {
assert(pause_on_start != null); assert(pause_on_start != null);
assert(pause_on_exit != null); assert(pause_on_exit != null);
assert(pause_on_unhandled_exceptions != null); assert(pause_on_unhandled_exceptions != null);
assert(trace_service != null);
assert(trace_compiler != null);
assert(testeeControlsServer != null); assert(testeeControlsServer != null);
assert(useAuthToken != null); assert(useAuthToken != null);
@ -106,39 +103,29 @@ class _ServiceTesteeLauncher {
return _spawnSkyProcess(pause_on_start, return _spawnSkyProcess(pause_on_start,
pause_on_exit, pause_on_exit,
pause_on_unhandled_exceptions, pause_on_unhandled_exceptions,
trace_service, testeeControlsServer,
trace_compiler, extraArgs);
testeeControlsServer);
} else { } else {
return _spawnDartProcess(pause_on_start, return _spawnDartProcess(pause_on_start,
pause_on_exit, pause_on_exit,
pause_on_unhandled_exceptions, pause_on_unhandled_exceptions,
trace_service,
trace_compiler,
testeeControlsServer, testeeControlsServer,
useAuthToken); useAuthToken,
extraArgs);
} }
} }
Future<Process> _spawnDartProcess(bool pause_on_start, Future<Process> _spawnDartProcess(bool pause_on_start,
bool pause_on_exit, bool pause_on_exit,
bool pause_on_unhandled_exceptions, bool pause_on_unhandled_exceptions,
bool trace_service,
bool trace_compiler,
bool testeeControlsServer, bool testeeControlsServer,
bool useAuthToken) { bool useAuthToken,
List<String> extraArgs) {
assert(!_shouldLaunchSkyShell()); assert(!_shouldLaunchSkyShell());
String dartExecutable = Platform.executable; String dartExecutable = Platform.executable;
var fullArgs = []; 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) { if (pause_on_start) {
fullArgs.add('--pause-isolates-on-start'); fullArgs.add('--pause-isolates-on-start');
} }
@ -148,6 +135,9 @@ class _ServiceTesteeLauncher {
if (pause_on_unhandled_exceptions) { if (pause_on_unhandled_exceptions) {
fullArgs.add('--pause-isolates-on-unhandled-exceptions'); fullArgs.add('--pause-isolates-on-unhandled-exceptions');
} }
if (extraArgs != null) {
fullArgs.addAll(extraArgs);
}
fullArgs.addAll(Platform.executableArguments); fullArgs.addAll(Platform.executableArguments);
if (!testeeControlsServer) { if (!testeeControlsServer) {
@ -166,22 +156,14 @@ class _ServiceTesteeLauncher {
Future<Process> _spawnSkyProcess(bool pause_on_start, Future<Process> _spawnSkyProcess(bool pause_on_start,
bool pause_on_exit, bool pause_on_exit,
bool pause_on_unhandled_exceptions, bool pause_on_unhandled_exceptions,
bool trace_service, bool testeeControlsServer,
bool trace_compiler, List<String> extraArgs) {
bool testeeControlsServer) {
assert(_shouldLaunchSkyShell()); assert(_shouldLaunchSkyShell());
String dartExecutable = _skyShellPath(); String dartExecutable = _skyShellPath();
var dartFlags = []; var dartFlags = [];
var fullArgs = []; 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) { if (pause_on_start) {
dartFlags.add('--pause_isolates_on_start'); dartFlags.add('--pause_isolates_on_start');
fullArgs.add('--start-paused'); fullArgs.add('--start-paused');
@ -194,6 +176,9 @@ class _ServiceTesteeLauncher {
} }
// Override mirrors. // Override mirrors.
dartFlags.add('--enable_mirrors=true'); dartFlags.add('--enable_mirrors=true');
if (extraArgs != null) {
fullArgs.addAll(extraArgs);
}
fullArgs.addAll(Platform.executableArguments); fullArgs.addAll(Platform.executableArguments);
if (!testeeControlsServer) { if (!testeeControlsServer) {
@ -223,17 +208,15 @@ class _ServiceTesteeLauncher {
Future<Uri> launch(bool pause_on_start, Future<Uri> launch(bool pause_on_start,
bool pause_on_exit, bool pause_on_exit,
bool pause_on_unhandled_exceptions, bool pause_on_unhandled_exceptions,
bool trace_service,
bool trace_compiler,
bool testeeControlsServer, bool testeeControlsServer,
bool useAuthToken) { bool useAuthToken,
List<String> extraArgs) {
return _spawnProcess(pause_on_start, return _spawnProcess(pause_on_start,
pause_on_exit, pause_on_exit,
pause_on_unhandled_exceptions, pause_on_unhandled_exceptions,
trace_service,
trace_compiler,
testeeControlsServer, testeeControlsServer,
useAuthToken).then((p) { useAuthToken,
extraArgs).then((p) {
Completer<Uri> completer = new Completer<Uri>(); Completer<Uri> completer = new Completer<Uri>();
process = p; process = p;
Uri uri; Uri uri;
@ -288,12 +271,11 @@ void setupAddresses(Uri serverAddress) {
class _ServiceTesterRunner { class _ServiceTesterRunner {
void run({List<String> mainArgs, void run({List<String> mainArgs,
List<String> extraArgs,
List<VMTest> vmTests, List<VMTest> vmTests,
List<IsolateTest> isolateTests, List<IsolateTest> isolateTests,
bool pause_on_start: false, bool pause_on_start: false,
bool pause_on_exit: false, bool pause_on_exit: false,
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false, bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false, bool pause_on_unhandled_exceptions: false,
bool testeeControlsServer: false, bool testeeControlsServer: false,
@ -301,9 +283,8 @@ class _ServiceTesterRunner {
var process = new _ServiceTesteeLauncher(); var process = new _ServiceTesteeLauncher();
process.launch(pause_on_start, pause_on_exit, process.launch(pause_on_start, pause_on_exit,
pause_on_unhandled_exceptions, pause_on_unhandled_exceptions,
trace_service, trace_compiler,
testeeControlsServer, testeeControlsServer,
useAuthToken).then((Uri serverAddress) async { useAuthToken, extraArgs).then((Uri serverAddress) async {
if (mainArgs.contains("--gdb")) { if (mainArgs.contains("--gdb")) {
var pid = process.process.pid; var pid = process.process.pid;
var wait = new Duration(seconds: 10); var wait = new Duration(seconds: 10);
@ -364,12 +345,11 @@ Future runIsolateTests(List<String> mainArgs,
testeeConcurrent(), testeeConcurrent(),
bool pause_on_start: false, bool pause_on_start: false,
bool pause_on_exit: false, bool pause_on_exit: false,
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false, bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false, bool pause_on_unhandled_exceptions: false,
bool testeeControlsServer: false, bool testeeControlsServer: false,
bool useAuthToken: false}) async { bool useAuthToken: false,
List<String> extraArgs}) async {
assert(!pause_on_start || testeeBefore == null); assert(!pause_on_start || testeeBefore == null);
if (_isTestee()) { if (_isTestee()) {
new _ServiceTesteeRunner().run(testeeBefore: testeeBefore, new _ServiceTesteeRunner().run(testeeBefore: testeeBefore,
@ -379,11 +359,10 @@ Future runIsolateTests(List<String> mainArgs,
} else { } else {
new _ServiceTesterRunner().run( new _ServiceTesterRunner().run(
mainArgs: mainArgs, mainArgs: mainArgs,
extraArgs: extraArgs,
isolateTests: tests, isolateTests: tests,
pause_on_start: pause_on_start, pause_on_start: pause_on_start,
pause_on_exit: pause_on_exit, pause_on_exit: pause_on_exit,
trace_service: trace_service,
trace_compiler: trace_compiler,
verbose_vm: verbose_vm, verbose_vm: verbose_vm,
pause_on_unhandled_exceptions: pause_on_unhandled_exceptions, pause_on_unhandled_exceptions: pause_on_unhandled_exceptions,
testeeControlsServer: testeeControlsServer, testeeControlsServer: testeeControlsServer,
@ -406,10 +385,9 @@ void runIsolateTestsSynchronous(List<String> mainArgs,
void testeeConcurrent(), void testeeConcurrent(),
bool pause_on_start: false, bool pause_on_start: false,
bool pause_on_exit: false, bool pause_on_exit: false,
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false, bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false}) { bool pause_on_unhandled_exceptions: false,
List<String> extraArgs}) {
assert(!pause_on_start || testeeBefore == null); assert(!pause_on_start || testeeBefore == null);
if (_isTestee()) { if (_isTestee()) {
new _ServiceTesteeRunner().runSync(testeeBeforeSync: testeeBefore, new _ServiceTesteeRunner().runSync(testeeBeforeSync: testeeBefore,
@ -419,11 +397,10 @@ void runIsolateTestsSynchronous(List<String> mainArgs,
} else { } else {
new _ServiceTesterRunner().run( new _ServiceTesterRunner().run(
mainArgs: mainArgs, mainArgs: mainArgs,
extraArgs: extraArgs,
isolateTests: tests, isolateTests: tests,
pause_on_start: pause_on_start, pause_on_start: pause_on_start,
pause_on_exit: pause_on_exit, pause_on_exit: pause_on_exit,
trace_service: trace_service,
trace_compiler: trace_compiler,
verbose_vm: verbose_vm, verbose_vm: verbose_vm,
pause_on_unhandled_exceptions: pause_on_unhandled_exceptions); pause_on_unhandled_exceptions: pause_on_unhandled_exceptions);
} }
@ -440,10 +417,9 @@ Future runVMTests(List<String> mainArgs,
testeeConcurrent(), testeeConcurrent(),
bool pause_on_start: false, bool pause_on_start: false,
bool pause_on_exit: false, bool pause_on_exit: false,
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false, bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false}) async { bool pause_on_unhandled_exceptions: false,
List<String> extraArgs}) async {
if (_isTestee()) { if (_isTestee()) {
new _ServiceTesteeRunner().run(testeeBefore: testeeBefore, new _ServiceTesteeRunner().run(testeeBefore: testeeBefore,
testeeConcurrent: testeeConcurrent, testeeConcurrent: testeeConcurrent,
@ -452,11 +428,10 @@ Future runVMTests(List<String> mainArgs,
} else { } else {
new _ServiceTesterRunner().run( new _ServiceTesterRunner().run(
mainArgs: mainArgs, mainArgs: mainArgs,
extraArgs: extraArgs,
vmTests: tests, vmTests: tests,
pause_on_start: pause_on_start, pause_on_start: pause_on_start,
pause_on_exit: pause_on_exit, pause_on_exit: pause_on_exit,
trace_service: trace_service,
trace_compiler: trace_compiler,
verbose_vm: verbose_vm, verbose_vm: verbose_vm,
pause_on_unhandled_exceptions: pause_on_unhandled_exceptions); pause_on_unhandled_exceptions: pause_on_unhandled_exceptions);
} }

View file

@ -2215,6 +2215,15 @@ DEFINE_RUNTIME_ENTRY(DeoptimizeMaterialize, 0) {
} }
DEFINE_RUNTIME_ENTRY(RewindPostDeopt, 0) {
#if !defined(DART_PRECOMPILED_RUNTIME)
#if !defined(PRODUCT)
isolate->debugger()->RewindPostDeopt();
#endif // !PRODUCT
#endif // !DART_PRECOMPILED_RUNTIME
UNREACHABLE();
}
DEFINE_LEAF_RUNTIME_ENTRY(intptr_t, DEFINE_LEAF_RUNTIME_ENTRY(intptr_t,
BigintCompare, BigintCompare,
2, 2,

View file

@ -551,7 +551,7 @@ RawObject* DartLibraryCalls::HandleMessage(const Object& handler,
// If the isolate is being debugged and the debugger was stepping // If the isolate is being debugged and the debugger was stepping
// through code, enable single stepping so debugger will stop // through code, enable single stepping so debugger will stop
// at the first location the user is interested in. // at the first location the user is interested in.
isolate->debugger()->SetSingleStep(); isolate->debugger()->SetResumeAction(Debugger::kStepInto);
} }
const Object& result = const Object& result =
Object::Handle(zone, DartEntry::InvokeFunction(function, args)); Object::Handle(zone, DartEntry::InvokeFunction(function, args));

View file

@ -6,6 +6,8 @@
#include "include/dart_api.h" #include "include/dart_api.h"
#include "platform/address_sanitizer.h"
#include "vm/code_generator.h" #include "vm/code_generator.h"
#include "vm/code_patcher.h" #include "vm/code_patcher.h"
#include "vm/compiler.h" #include "vm/compiler.h"
@ -42,6 +44,7 @@ DEFINE_FLAG(bool,
trace_debugger_stacktrace, trace_debugger_stacktrace,
false, false,
"Trace debugger stacktrace collection"); "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, verbose_debug, false, "Verbose debugger messages");
DEFINE_FLAG(bool, DEFINE_FLAG(bool,
steal_breakpoints, 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, void ActivationFrame::PrintContextMismatchError(intptr_t ctx_slot,
intptr_t frame_ctx_level, intptr_t frame_ctx_level,
intptr_t var_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) { void DebuggerStackTrace::AddActivation(ActivationFrame* frame) {
if (FLAG_show_invisible_frames || frame->function().is_visible()) { if (IsFunctionVisible(frame->function())) {
trace_.Add(frame); trace_.Add(frame);
} }
} }
@ -1237,6 +1262,8 @@ Debugger::Debugger()
breakpoint_locations_(NULL), breakpoint_locations_(NULL),
code_breakpoints_(NULL), code_breakpoints_(NULL),
resume_action_(kContinue), resume_action_(kContinue),
resume_frame_index_(-1),
post_deopt_frame_index_(-1),
ignore_breakpoints_(false), ignore_breakpoints_(false),
pause_event_(NULL), pause_event_(NULL),
obj_cache_(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(); ActivationFrame* top_frame = TopDartFrame();
if (!IsAtAsyncJump(top_frame)) { if (!IsAtAsyncJump(top_frame)) {
// Not at an async operation. // Not at an async operation.
if (error) {
*error = "Isolate must be paused at an async suspension point";
}
return false; return false;
} }
Object& closure = Object::Handle(top_frame->GetAsyncOperation()); Object& closure = Object::Handle(top_frame->GetAsyncOperation());
@ -1313,24 +1343,42 @@ bool Debugger::SetupStepOverAsyncSuspension() {
Breakpoint* bpt = SetBreakpointAtActivation(Instance::Cast(closure), true); Breakpoint* bpt = SetBreakpointAtActivation(Instance::Cast(closure), true);
if (bpt == NULL) { if (bpt == NULL) {
// Unable to set the breakpoint. // Unable to set the breakpoint.
if (error) {
*error = "Unable to set breakpoint at async suspension point";
}
return false; return false;
} }
return true; return true;
} }
void Debugger::SetSingleStep() { bool Debugger::SetResumeAction(ResumeAction action,
resume_action_ = kSingleStep; intptr_t frame_index,
} const char** error) {
if (error) {
*error = NULL;
void Debugger::SetStepOver() { }
resume_action_ = kStepOver; resume_frame_index_ = -1;
} switch (action) {
case kStepInto:
case kStepOver:
void Debugger::SetStepOut() { case kStepOut:
resume_action_ = 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); ASSERT(stack_trace_ == NULL);
stack_trace_ = stack_trace; stack_trace_ = stack_trace;
Pause(&event); Pause(&event);
HandleSteppingRequest(stack_trace_); // we may get a rewind request
stack_trace_ = NULL; stack_trace_ = NULL;
} }
@ -2547,7 +2596,7 @@ void Debugger::EnterSingleStepMode() {
void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace,
bool skip_next_step) { bool skip_next_step) {
stepping_fp_ = 0; stepping_fp_ = 0;
if (resume_action_ == kSingleStep) { if (resume_action_ == kStepInto) {
// When single stepping, we need to deoptimize because we might be // When single stepping, we need to deoptimize because we might be
// stepping into optimized code. This happens in particular if // stepping into optimized code. This happens in particular if
// the isolate has been interrupted, but can happen in other cases // 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); isolate_->set_single_step(true);
skip_next_step_ = skip_next_step; skip_next_step_ = skip_next_step;
if (FLAG_verbose_debug) { if (FLAG_verbose_debug) {
OS::Print("HandleSteppingRequest- kSingleStep\n"); OS::Print("HandleSteppingRequest- kStepInto\n");
} }
} else if (resume_action_ == kStepOver) { } else if (resume_action_ == kStepOver) {
DeoptimizeWorld(); DeoptimizeWorld();
@ -2582,6 +2631,244 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace,
if (FLAG_verbose_debug) { if (FLAG_verbose_debug) {
OS::Print("HandleSteppingRequest- kStepOut %" Px "\n", stepping_fp_); 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 are at the entry of an async function.
// We issue a step over to resume at the point after the await statement. // 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 // When we single step from a user breakpoint, our next stepping
// point will be at the exact same pc. Skip it. // point will be at the exact same pc. Skip it.
HandleSteppingRequest(stack_trace_, true /* skip next step */); 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 // We are in the native call to Developer_debugger. the developer
// gets a better experience by not seeing this call. To accomplish // gets a better experience by not seeing this call. To accomplish
// this, we continue execution until the call exits (step out). // this, we continue execution until the call exits (step out).
SetStepOut(); SetResumeAction(kStepOut);
HandleSteppingRequest(stack_trace_); HandleSteppingRequest(stack_trace_);
stack_trace_ = NULL; stack_trace_ = NULL;

View file

@ -276,6 +276,9 @@ class ActivationFrame : public ZoneAllocated {
// to the user and can be debugged. // to the user and can be debugged.
bool IsDebuggable() const; 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 // 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 // PC/token index of the frame. It determines the depth of the context
// chain that belongs to the function of this activation frame. // chain that belongs to the function of this activation frame.
@ -373,6 +376,15 @@ class DebuggerStackTrace : public ZoneAllocated {
class Debugger { class Debugger {
public: public:
enum ResumeAction {
kContinue,
kStepInto,
kStepOver,
kStepOut,
kStepRewind,
kStepOverAsyncSuspension,
};
typedef void EventHandler(ServiceEvent* event); typedef void EventHandler(ServiceEvent* event);
Debugger(); Debugger();
@ -412,11 +424,10 @@ class Debugger {
void RemoveBreakpoint(intptr_t bp_id); void RemoveBreakpoint(intptr_t bp_id);
Breakpoint* GetBreakpointById(intptr_t id); Breakpoint* GetBreakpointById(intptr_t id);
// Will return false if we are not at an await. bool SetResumeAction(ResumeAction action,
bool SetupStepOverAsyncSuspension(); intptr_t frame_index = 1,
void SetStepOver(); const char** error = NULL);
void SetSingleStep();
void SetStepOut();
bool IsStepping() const { return resume_action_ != kContinue; } bool IsStepping() const { return resume_action_ != kContinue; }
bool IsPaused() const { return pause_event_ != NULL; } bool IsPaused() const { return pause_event_ != NULL; }
@ -513,11 +524,15 @@ class Debugger {
intptr_t limitBreakpointId() { return next_id_; } intptr_t limitBreakpointId() { return next_id_; }
private: // Callback to the debugger to continue frame rewind, post-deoptimization.
enum ResumeAction { kContinue, kStepOver, kStepOut, kSingleStep }; void RewindPostDeopt();
private:
RawError* PauseRequest(ServiceEvent::EventKind kind); RawError* PauseRequest(ServiceEvent::EventKind kind);
// Will return false if we are not at an await.
bool SetupStepOverAsyncSuspension(const char** error);
bool NeedsIsolateEvents(); bool NeedsIsolateEvents();
bool NeedsDebugEvents(); bool NeedsDebugEvents();
void InvokeEventHandler(ServiceEvent* event); void InvokeEventHandler(ServiceEvent* event);
@ -588,6 +603,15 @@ class Debugger {
void HandleSteppingRequest(DebuggerStackTrace* stack_trace, void HandleSteppingRequest(DebuggerStackTrace* stack_trace,
bool skip_next_step = false); 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_; Isolate* isolate_;
Dart_Port isolate_id_; // A unique ID for the isolate in the debugger. Dart_Port isolate_id_; // A unique ID for the isolate in the debugger.
bool initialized_; bool initialized_;
@ -601,6 +625,8 @@ class Debugger {
// Tells debugger what to do when resuming execution after a breakpoint. // Tells debugger what to do when resuming execution after a breakpoint.
ResumeAction resume_action_; 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. // Do not call back to breakpoint handler if this flag is set.
// Effectively this means ignoring breakpoints. Set when Dart code may // Effectively this means ignoring breakpoints. Set when Dart code may

View file

@ -485,7 +485,7 @@ DART_EXPORT Dart_Handle Dart_SetStepOver() {
DARTSCOPE(Thread::Current()); DARTSCOPE(Thread::Current());
Isolate* I = T->isolate(); Isolate* I = T->isolate();
CHECK_DEBUGGER(I); CHECK_DEBUGGER(I);
I->debugger()->SetStepOver(); I->debugger()->SetResumeAction(Debugger::kStepOver);
return Api::Success(); return Api::Success();
} }
@ -494,7 +494,7 @@ DART_EXPORT Dart_Handle Dart_SetStepInto() {
DARTSCOPE(Thread::Current()); DARTSCOPE(Thread::Current());
Isolate* I = T->isolate(); Isolate* I = T->isolate();
CHECK_DEBUGGER(I); CHECK_DEBUGGER(I);
I->debugger()->SetSingleStep(); I->debugger()->SetResumeAction(Debugger::kStepInto);
return Api::Success(); return Api::Success();
} }
@ -503,7 +503,7 @@ DART_EXPORT Dart_Handle Dart_SetStepOut() {
DARTSCOPE(Thread::Current()); DARTSCOPE(Thread::Current());
Isolate* I = T->isolate(); Isolate* I = T->isolate();
CHECK_DEBUGGER(I); CHECK_DEBUGGER(I);
I->debugger()->SetStepOut(); I->debugger()->SetResumeAction(Debugger::kStepOut);
return Api::Success(); return Api::Success();
} }

View file

@ -189,7 +189,6 @@ static bool FindExceptionHandler(Thread* thread,
static void FindErrorHandler(uword* handler_pc, static void FindErrorHandler(uword* handler_pc,
uword* handler_sp, uword* handler_sp,
uword* handler_fp) { uword* handler_fp) {
// TODO(turnidge): Is there a faster way to get the next entry frame?
StackFrameIterator frames(StackFrameIterator::kDontValidateFrames); StackFrameIterator frames(StackFrameIterator::kDontValidateFrames);
StackFrame* frame = frames.NextFrame(); StackFrame* frame = frames.NextFrame();
ASSERT(frame != NULL); ASSERT(frame != NULL);
@ -228,7 +227,17 @@ static uword RemapExceptionPCForDeopt(Thread* thread,
break; break;
} }
} }
}
#endif // !DBC
return program_counter;
}
static void ClearLazyDeopts(Thread* thread, uword frame_pointer) {
#if !defined(TARGET_ARCH_DBC)
MallocGrowableArray<PendingLazyDeopt>* pending_deopts =
thread->isolate()->pending_deopts();
if (pending_deopts->length() > 0) {
// We may be jumping over frames scheduled for lazy deopt. Remove these // We may be jumping over frames scheduled for lazy deopt. Remove these
// frames from the pending deopt table, but only after unmarking them so // 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. // any stack walk that happens before the stack is unwound will still work.
@ -264,7 +273,6 @@ static uword RemapExceptionPCForDeopt(Thread* thread,
#endif #endif
} }
#endif // !DBC #endif // !DBC
return program_counter;
} }
@ -281,14 +289,18 @@ static void JumpToExceptionHandler(Thread* thread,
thread->set_resume_pc(remapped_pc); thread->set_resume_pc(remapped_pc);
uword run_exception_pc = StubCode::RunExceptionHandler_entry()->EntryPoint(); uword run_exception_pc = StubCode::RunExceptionHandler_entry()->EntryPoint();
Exceptions::JumpToFrame(thread, run_exception_pc, stack_pointer, Exceptions::JumpToFrame(thread, run_exception_pc, stack_pointer,
frame_pointer); frame_pointer, false /* do not clear deopt */);
} }
void Exceptions::JumpToFrame(Thread* thread, void Exceptions::JumpToFrame(Thread* thread,
uword program_counter, uword program_counter,
uword stack_pointer, 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) #if defined(USING_SIMULATOR)
// Unwinding of the C++ frames and destroying of their stack resources is done // 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 // by the simulator, because the target stack_pointer is a simulated stack

View file

@ -83,7 +83,8 @@ class Exceptions : AllStatic {
static void JumpToFrame(Thread* thread, static void JumpToFrame(Thread* thread,
uword program_counter, uword program_counter,
uword stack_pointer, uword stack_pointer,
uword frame_pointer); uword frame_pointer,
bool clear_deopt_at_target);
private: private:
DISALLOW_COPY_AND_ASSIGN(Exceptions); DISALLOW_COPY_AND_ASSIGN(Exceptions);

View file

@ -1271,6 +1271,7 @@ void FlowGraphCompiler::GenerateStaticCall(intptr_t deopt_id,
kNumArgsChecked) kNumArgsChecked)
->raw(); ->raw();
} }
AddCurrentDescriptor(RawPcDescriptors::kRewind, deopt_id, token_pos);
EmitUnoptimizedStaticCall(argument_count, deopt_id, token_pos, locs, EmitUnoptimizedStaticCall(argument_count, deopt_id, token_pos, locs,
call_ic_data); call_ic_data);
} }

View file

@ -3075,6 +3075,8 @@ void InstanceCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
} else { } else {
// Unoptimized code. // Unoptimized code.
ASSERT(!HasICData()); ASSERT(!HasICData());
compiler->AddCurrentDescriptor(RawPcDescriptors::kRewind, deopt_id(),
token_pos());
bool is_smi_two_args_op = false; bool is_smi_two_args_op = false;
const StubEntry* stub_entry = TwoArgsSmiOpInlineCacheEntry(token_kind()); const StubEntry* stub_entry = TwoArgsSmiOpInlineCacheEntry(token_kind());
if (stub_entry != NULL) { if (stub_entry != NULL) {

View file

@ -139,6 +139,8 @@ static const char* GetJSONRpcErrorMessage(intptr_t code) {
return "Isolate must be runnable"; return "Isolate must be runnable";
case kIsolateMustBePaused: case kIsolateMustBePaused:
return "Isolate must be paused"; return "Isolate must be paused";
case kCannotResume:
return "Cannot resume execution";
case kIsolateIsReloading: case kIsolateIsReloading:
return "Isolate is reloading"; return "Isolate is reloading";
case kFileSystemAlreadyExists: case kFileSystemAlreadyExists:

View file

@ -53,6 +53,7 @@ enum JSONRpcErrorCode {
kStreamNotSubscribed = 104, kStreamNotSubscribed = 104,
kIsolateMustBeRunnable = 105, kIsolateMustBeRunnable = 105,
kIsolateMustBePaused = 106, kIsolateMustBePaused = 106,
kCannotResume = 107,
// Experimental (used in private rpcs). // Experimental (used in private rpcs).
kIsolateIsReloading = 1000, kIsolateIsReloading = 1000,

View file

@ -11700,6 +11700,8 @@ const char* PcDescriptors::KindAsStr(RawPcDescriptors::Kind kind) {
return "runtime-call "; return "runtime-call ";
case RawPcDescriptors::kOsrEntry: case RawPcDescriptors::kOsrEntry:
return "osr-entry "; return "osr-entry ";
case RawPcDescriptors::kRewind:
return "rewind ";
case RawPcDescriptors::kOther: case RawPcDescriptors::kOther:
return "other "; return "other ";
case RawPcDescriptors::kAnyKind: case RawPcDescriptors::kAnyKind:

View file

@ -1202,7 +1202,8 @@ class RawPcDescriptors : public RawObject {
kUnoptStaticCall = kIcCall << 1, // Call to a known target via stub. kUnoptStaticCall = kIcCall << 1, // Call to a known target via stub.
kRuntimeCall = kUnoptStaticCall << 1, // Runtime call. kRuntimeCall = kUnoptStaticCall << 1, // Runtime call.
kOsrEntry = kRuntimeCall << 1, // OSR entry point in unopt. code. kOsrEntry = kRuntimeCall << 1, // OSR entry point in unopt. code.
kOther = kOsrEntry << 1, kRewind = kOsrEntry << 1, // Call rewind target address.
kOther = kRewind << 1,
kLastKind = kOther, kLastKind = kOther,
kAnyKind = -1 kAnyKind = -1
}; };

View file

@ -40,6 +40,7 @@ namespace dart {
V(TraceFunctionEntry) \ V(TraceFunctionEntry) \
V(TraceFunctionExit) \ V(TraceFunctionExit) \
V(DeoptimizeMaterialize) \ V(DeoptimizeMaterialize) \
V(RewindPostDeopt) \
V(UpdateFieldCid) \ V(UpdateFieldCid) \
V(InitStaticField) \ V(InitStaticField) \
V(GrowRegExpStack) \ V(GrowRegExpStack) \

View file

@ -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[] = { 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) { static bool Resume(Thread* thread, JSONStream* js) {
const char* step_param = js->LookupParam("step"); 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(); Isolate* isolate = thread->isolate();
if (isolate->message_handler()->is_paused_on_start()) { if (isolate->message_handler()->is_paused_on_start()) {
// If the user is issuing a 'Over' or an 'Out' step, that is the // If the user is issuing a 'Over' or an 'Out' step, that is the
// same as a regular resume request. // same as a regular resume request.
if ((step_param != NULL) && (strcmp(step_param, "Into") == 0)) { if (step == Debugger::kStepInto) {
isolate->debugger()->EnterSingleStepMode(); isolate->debugger()->EnterSingleStepMode();
} }
isolate->message_handler()->set_should_pause_on_start(false); isolate->message_handler()->set_should_pause_on_start(false);
@ -3028,31 +3067,18 @@ static bool Resume(Thread* thread, JSONStream* js) {
PrintSuccess(js); PrintSuccess(js);
return true; return true;
} }
if (isolate->debugger()->PauseEvent() != NULL) { if (isolate->debugger()->PauseEvent() == NULL) {
if (step_param != NULL) { js->PrintError(kIsolateMustBePaused, 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);
return true; 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; return true;
} }

View file

@ -3715,14 +3715,25 @@ void Simulator::JumpToFrame(uword pc, uword sp, uword fp, Thread* thread) {
fp_ = reinterpret_cast<RawObject**>(fp); fp_ = reinterpret_cast<RawObject**>(fp);
if (pc == StubCode::RunExceptionHandler_entry()->EntryPoint()) { if (pc == StubCode::RunExceptionHandler_entry()->EntryPoint()) {
// Instead of executing the RunException stub, we implement its // The RunExceptionHandler stub is a placeholder. We implement
// behavior here. // its behavior here.
RawObject* raw_exception = thread->active_exception(); RawObject* raw_exception = thread->active_exception();
RawObject* raw_stacktrace = thread->active_stacktrace(); RawObject* raw_stacktrace = thread->active_stacktrace();
ASSERT(raw_exception != Object::null()); ASSERT(raw_exception != Object::null());
special_[kExceptionSpecialIndex] = raw_exception; special_[kExceptionSpecialIndex] = raw_exception;
special_[kStacktraceSpecialIndex] = raw_stacktrace; special_[kStacktraceSpecialIndex] = raw_stacktrace;
pc_ = thread->resume_pc(); 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 { } else {
pc_ = pc; pc_ = pc;
} }

View file

@ -27,6 +27,7 @@ class Deserializer;
V(GetStackPointer) \ V(GetStackPointer) \
V(JumpToFrame) \ V(JumpToFrame) \
V(RunExceptionHandler) \ V(RunExceptionHandler) \
V(DeoptForRewind) \
V(UpdateStoreBuffer) \ V(UpdateStoreBuffer) \
V(PrintStopMessage) \ V(PrintStopMessage) \
V(CallToRuntime) \ V(CallToRuntime) \
@ -75,6 +76,7 @@ class Deserializer;
V(LazyCompile) \ V(LazyCompile) \
V(OptimizeFunction) \ V(OptimizeFunction) \
V(RunExceptionHandler) \ V(RunExceptionHandler) \
V(DeoptForRewind) \
V(FixCallersTarget) \ V(FixCallersTarget) \
V(Deoptimize) \ V(Deoptimize) \
V(DeoptimizeLazyFromReturn) \ V(DeoptimizeLazyFromReturn) \
@ -195,6 +197,12 @@ class StubCode : public AllStatic {
enum DeoptStubKind { kLazyDeoptFromReturn, kLazyDeoptFromThrow, kEagerDeopt }; enum DeoptStubKind { kLazyDeoptFromReturn, kLazyDeoptFromThrow, kEagerDeopt };
// Zap value used to indicate unused CODE_REG in deopt.
static const uword kZapCodeReg = 0xf1f1f1f1;
// Zap value used to indicate unused return address in deopt.
static const uword kZapReturnAddress = 0xe1e1e1e1;
} // namespace dart } // namespace dart
#endif // RUNTIME_VM_STUB_CODE_H_ #endif // RUNTIME_VM_STUB_CODE_H_

View file

@ -537,19 +537,20 @@ static void GenerateDeoptimizationSequence(Assembler* assembler,
__ LeaveStubFrame(); __ LeaveStubFrame();
// Remove materialization arguments. // Remove materialization arguments.
__ add(SP, SP, Operand(R2, ASR, kSmiTagSize)); __ add(SP, SP, Operand(R2, ASR, kSmiTagSize));
__ Ret(); // The caller is responsible for emitting the return instruction.
} }
// R0: result, must be preserved // R0: result, must be preserved
void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt. // Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(IP, 0xf1f1f1f1); __ LoadImmediate(IP, kZapCodeReg);
__ Push(IP); __ Push(IP);
// Return address for "call" to deopt stub. // 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())); __ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
__ Ret();
} }
@ -557,17 +558,19 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// R1: stacktrace, must be preserved // R1: stacktrace, must be preserved
void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt. // Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(IP, 0xf1f1f1f1); __ LoadImmediate(IP, kZapCodeReg);
__ Push(IP); __ Push(IP);
// Return address for "call" to deopt stub. // 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())); __ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
__ Ret();
} }
void StubCode::GenerateDeoptimizeStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeStub(Assembler* assembler) {
GenerateDeoptimizationSequence(assembler, kEagerDeopt); 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. // Calls to the runtime to optimize the given function.
// R8: function to be reoptimized. // R8: function to be reoptimized.
// R4: argument descriptor (preserved). // R4: argument descriptor (preserved).

View file

@ -558,19 +558,20 @@ static void GenerateDeoptimizationSequence(Assembler* assembler,
__ LeaveStubFrame(); __ LeaveStubFrame();
// Remove materialization arguments. // Remove materialization arguments.
__ add(SP, SP, Operand(R2)); __ add(SP, SP, Operand(R2));
__ ret(); // The caller is responsible for emitting the return instruction.
} }
// R0: result, must be preserved // R0: result, must be preserved
void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt. // Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(TMP, 0xf1f1f1f1); __ LoadImmediate(TMP, kZapCodeReg);
__ Push(TMP); __ Push(TMP);
// Return address for "call" to deopt stub. // 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())); __ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
__ ret();
} }
@ -578,17 +579,19 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// R1: stacktrace, must be preserved // R1: stacktrace, must be preserved
void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt. // Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(TMP, 0xf1f1f1f1); __ LoadImmediate(TMP, kZapCodeReg);
__ Push(TMP); __ Push(TMP);
// Return address for "call" to deopt stub. // 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())); __ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
__ ret();
} }
void StubCode::GenerateDeoptimizeStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeStub(Assembler* assembler) {
GenerateDeoptimizationSequence(assembler, kEagerDeopt); 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. // Calls to the runtime to optimize the given function.
// R6: function to be re-optimized. // R6: function to be re-optimized.
// R4: argument descriptor (preserved). // R4: argument descriptor (preserved).

View file

@ -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. // TODO(vegorov) Don't generate this stub.
void StubCode::GenerateFixCallersTargetStub(Assembler* assembler) { void StubCode::GenerateFixCallersTargetStub(Assembler* assembler) {
__ Trap(); __ Trap();

View file

@ -351,8 +351,6 @@ static void PushArgumentsArray(Assembler* assembler) {
// +------------------+ // +------------------+
// | return-address | (deoptimization point) // | return-address | (deoptimization point)
// +------------------+ // +------------------+
// | Saved CODE_REG |
// +------------------+
// | ... | <- SP of optimized frame // | ... | <- SP of optimized frame
// //
// Parts of the code cannot GC, part of the code can GC. // 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. __ popl(ECX); // Pop return address.
__ addl(ESP, EBX); // Remove materialization arguments. __ addl(ESP, EBX); // Remove materialization arguments.
__ pushl(ECX); // Push return address. __ pushl(ECX); // Push return address.
__ ret(); // The caller is responsible for emitting the return instruction.
} }
// EAX: result, must be preserved // EAX: result, must be preserved
void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// Return address for "call" to deopt stub. // Return address for "call" to deopt stub.
__ pushl(Immediate(0xe1e1e1e1)); __ pushl(Immediate(kZapReturnAddress));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
__ ret();
} }
@ -478,13 +477,15 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// EDX: stacktrace, must be preserved // EDX: stacktrace, must be preserved
void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) {
// Return address for "call" to deopt stub. // Return address for "call" to deopt stub.
__ pushl(Immediate(0xe1e1e1e1)); __ pushl(Immediate(kZapReturnAddress));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
__ ret();
} }
void StubCode::GenerateDeoptimizeStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeStub(Assembler* assembler) {
GenerateDeoptimizationSequence(assembler, kEagerDeopt); 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. // Calls to the runtime to optimize the given function.
// EBX: function to be reoptimized. // EBX: function to be reoptimized.
// EDX: argument descriptor (preserved). // EDX: argument descriptor (preserved).

View file

@ -549,18 +549,19 @@ static void GenerateDeoptimizationSequence(Assembler* assembler,
// Remove materialization arguments. // Remove materialization arguments.
__ SmiUntag(T1); __ SmiUntag(T1);
__ addu(SP, SP, T1); __ addu(SP, SP, T1);
__ Ret(); // The caller is responsible for emitting the return instruction.
} }
// V0: result, must be preserved // V0: result, must be preserved
void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt. // Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(TMP, 0xf1f1f1f1); __ LoadImmediate(TMP, kZapCodeReg);
__ Push(TMP); __ Push(TMP);
// Return address for "call" to deopt stub. // 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())); __ lw(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
__ Ret();
} }
@ -568,17 +569,19 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// V1: stacktrace, must be preserved // V1: stacktrace, must be preserved
void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt. // Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(TMP, 0xf1f1f1f1); __ LoadImmediate(TMP, kZapCodeReg);
__ Push(TMP); __ Push(TMP);
// Return address for "call" to deopt stub. // 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())); __ lw(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
__ Ret();
} }
void StubCode::GenerateDeoptimizeStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeStub(Assembler* assembler) {
GenerateDeoptimizationSequence(assembler, kEagerDeopt); 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. // Calls to the runtime to optimize the given function.
// T0: function to be reoptimized. // T0: function to be reoptimized.
// S4: argument descriptor (preserved). // S4: argument descriptor (preserved).

View file

@ -498,18 +498,19 @@ static void GenerateDeoptimizationSequence(Assembler* assembler,
__ popq(RCX); // Pop return address. __ popq(RCX); // Pop return address.
__ addq(RSP, RBX); // Remove materialization arguments. __ addq(RSP, RBX); // Remove materialization arguments.
__ pushq(RCX); // Push return address. __ pushq(RCX); // Push return address.
__ ret(); // The caller is responsible for emitting the return instruction.
} }
// RAX: result, must be preserved // RAX: result, must be preserved
void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt. // Push zap value instead of CODE_REG for lazy deopt.
__ pushq(Immediate(0xf1f1f1f1)); __ pushq(Immediate(kZapCodeReg));
// Return address for "call" to deopt stub. // 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())); __ movq(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
__ ret();
} }
@ -517,16 +518,18 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// RDX: stacktrace, must be preserved // RDX: stacktrace, must be preserved
void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt. // Push zap value instead of CODE_REG for lazy deopt.
__ pushq(Immediate(0xf1f1f1f1)); __ pushq(Immediate(kZapCodeReg));
// Return address for "call" to deopt stub. // 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())); __ movq(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow); GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
__ ret();
} }
void StubCode::GenerateDeoptimizeStub(Assembler* assembler) { void StubCode::GenerateDeoptimizeStub(Assembler* assembler) {
GenerateDeoptimizationSequence(assembler, kEagerDeopt); 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. // Calls to the runtime to optimize the given function.
// RDI: function to be reoptimized. // RDI: function to be reoptimized.
// R10: argument descriptor (preserved). // R10: argument descriptor (preserved).