mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 16:41:07 +00:00
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 .
This commit is contained in:
parent
e9ebfd4b39
commit
af1875be91
|
@ -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<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 {
|
||||
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 <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 {
|
||||
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) {
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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' ]);
|
||||
|
|
|
@ -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' ]);
|
||||
|
|
|
@ -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' ]);
|
||||
|
|
|
@ -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']);
|
173
runtime/observatory/tests/service/rewind_test.dart
Normal file
173
runtime/observatory/tests/service/rewind_test.dart
Normal 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']);
|
|
@ -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.
|
||||
|
|
|
@ -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'));
|
||||
|
||||
|
|
|
@ -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' ]);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -90,15 +90,12 @@ class _ServiceTesteeLauncher {
|
|||
Future<Process> _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<String> 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<Process> _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<String> 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<Process> _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<String> 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<Uri> 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<String> 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<Uri> completer = new Completer<Uri>();
|
||||
process = p;
|
||||
Uri uri;
|
||||
|
@ -288,12 +271,11 @@ void setupAddresses(Uri serverAddress) {
|
|||
|
||||
class _ServiceTesterRunner {
|
||||
void run({List<String> mainArgs,
|
||||
List<String> extraArgs,
|
||||
List<VMTest> vmTests,
|
||||
List<IsolateTest> 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<String> 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<String> extraArgs}) async {
|
||||
assert(!pause_on_start || testeeBefore == null);
|
||||
if (_isTestee()) {
|
||||
new _ServiceTesteeRunner().run(testeeBefore: testeeBefore,
|
||||
|
@ -379,11 +359,10 @@ Future runIsolateTests(List<String> 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<String> 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<String> extraArgs}) {
|
||||
assert(!pause_on_start || testeeBefore == null);
|
||||
if (_isTestee()) {
|
||||
new _ServiceTesteeRunner().runSync(testeeBeforeSync: testeeBefore,
|
||||
|
@ -419,11 +397,10 @@ void runIsolateTestsSynchronous(List<String> 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<String> 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<String> extraArgs}) async {
|
||||
if (_isTestee()) {
|
||||
new _ServiceTesteeRunner().run(testeeBefore: testeeBefore,
|
||||
testeeConcurrent: testeeConcurrent,
|
||||
|
@ -452,11 +428,10 @@ Future runVMTests(List<String> 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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<PendingLazyDeopt>* 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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -53,6 +53,7 @@ enum JSONRpcErrorCode {
|
|||
kStreamNotSubscribed = 104,
|
||||
kIsolateMustBeRunnable = 105,
|
||||
kIsolateMustBePaused = 106,
|
||||
kCannotResume = 107,
|
||||
|
||||
// Experimental (used in private rpcs).
|
||||
kIsolateIsReloading = 1000,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace dart {
|
|||
V(TraceFunctionEntry) \
|
||||
V(TraceFunctionExit) \
|
||||
V(DeoptimizeMaterialize) \
|
||||
V(RewindPostDeopt) \
|
||||
V(UpdateFieldCid) \
|
||||
V(InitStaticField) \
|
||||
V(GrowRegExpStack) \
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3715,14 +3715,25 @@ void Simulator::JumpToFrame(uword pc, uword sp, uword fp, Thread* thread) {
|
|||
fp_ = reinterpret_cast<RawObject**>(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;
|
||||
}
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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).
|
||||
|
|
Loading…
Reference in a new issue