Skip to content

Commit 6c734a4

Browse files
authored
Merge pull request #2059 from vedantk/stepper
[test/Shell] Add stepper test for Benchmark_Onone
2 parents 1c21b96 + a4b0580 commit 6c734a4

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# REQUIRES: swift_Benchmark_Onone
2+
#
3+
# XFAIL: *
4+
# rdar://problem/70552614
5+
#
6+
# - Don't capture a reproducer (it can easily exceed 1GB).
7+
# - Skip `po` testing for now (until we can un-XFAIL `frame var` testing).
8+
# - Just run benchmark #1 for now (there are more; --list lists all of them).
9+
#
10+
# RUN: env LLDB_CAPTURE_REPRODUCER=0 STEPPER_OPTIONS="ONLY_VISIT_SWIFT=True;SKIP_PO=True" \
11+
# RUN: %lldb --batch -o "command script import %p/../helper/stepper.py" -- \
12+
# RUN: %swift_Benchmark_Onone --num-samples 1 --num-iters 1 1 1>&2

lldb/test/Shell/helper/stepper.py

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
from __future__ import print_function
2+
from collections import defaultdict
3+
4+
import os
5+
import re
6+
7+
import lldb
8+
9+
###############################################################################
10+
# Configurable options (see the STEPPER_OPTIONS env var)
11+
###############################################################################
12+
13+
# Only visit unoptimized frames.
14+
ONLY_VISIT_UNOPTIMIZED = True
15+
16+
# Only visit frames with swift code.
17+
ONLY_VISIT_SWIFT = False
18+
19+
# Skip doing "frame var" on all locals, in each frame.
20+
SKIP_FRAMEVAR = False
21+
22+
# Skip doing "po" on all locals, in each frame.
23+
SKIP_PO = False
24+
25+
# Maximum number of times to visit a PC before "finish"ing out.
26+
MAX_VISITS_PER_PC = 50
27+
28+
# Run the process. It's useful to set this to False if you have monitor/test
29+
# lldb's, and the child lldb can't read from stdin to do things like continue
30+
# at a breakpoint.
31+
RUN_THE_PROCESS = True
32+
33+
# Skip N frames between each inspection.
34+
SKIP_N_FRAMES_BETWEEN_INSPECTIONS = 0
35+
36+
###############################################################################
37+
38+
39+
def parse_options():
40+
"""
41+
Parse the STEPPER_OPTIONS environment variable.
42+
43+
Update any constants specified in the option string.
44+
"""
45+
global ONLY_VISIT_UNOPTIMIZED, ONLY_VISIT_SWIFT, SKIP_FRAMEVAR, \
46+
SKIP_PO, MAX_VISITS_PER_PC, RUN_THE_PROCESS, \
47+
SKIP_N_FRAMES_BETWEEN_INSPECTIONS
48+
opts = os.getenv('STEPPER_OPTIONS', '').split(';')
49+
for o in opts:
50+
m = re.match('(\w+)=(.+)', o)
51+
if not m:
52+
print('Unrecognized option:', o)
53+
continue
54+
option, val = m.groups()
55+
if option not in globals():
56+
print('Unrecognized option:', option)
57+
continue
58+
print('Setting', option, '=', val)
59+
globals()[option] = eval(val)
60+
61+
62+
def doit(dbg, cmd):
63+
"Run a driver command."
64+
print('::', cmd)
65+
dbg.HandleCommand(cmd)
66+
return False
67+
68+
69+
def doquit(dbg):
70+
"Quit the stepper script interpreter."
71+
exit(0)
72+
73+
74+
def should_stop_stepping(process):
75+
"Decide whether we should stop stepping."
76+
state = process.GetState()
77+
if state in (lldb.eStateExited, lldb.eStateDetached):
78+
print('Process has exited or has been detached, exiting...')
79+
return True
80+
if state in (lldb.eStateCrashed, lldb.eStateInvalid):
81+
print('Process has crashed or is in an invalid state, exiting...')
82+
return True
83+
return False
84+
85+
86+
def alter_PC(dbg, process, cmd):
87+
"Run a driver command that changes the PC. Return whether to stop stepping."
88+
# Check the process state /after/ we step. Any time we advance the PC,
89+
# the process state may change.
90+
doit(dbg, cmd)
91+
return should_stop_stepping(process)
92+
93+
94+
def return_from_frame(thread, frame):
95+
print(':: Popping current frame...')
96+
old_name = frame.GetFunctionName()
97+
thread.StepOutOfFrame(frame)
98+
new_frame = thread.GetSelectedFrame()
99+
new_name = new_frame.GetFunctionName()
100+
print(':: Transitioned from {} -> {}.'.format(old_name, new_name))
101+
return True
102+
103+
104+
def __lldb_init_module(dbg, internal_dict):
105+
parse_options()
106+
107+
# Make each debugger command synchronous.
108+
dbg.SetAsync(False)
109+
110+
# Run the program and stop it when it reaches main().
111+
if RUN_THE_PROCESS:
112+
doit(dbg, 'breakpoint set -n main')
113+
doit(dbg, 'run')
114+
115+
# Step through the program until it exits.
116+
gen = 0
117+
inspections = 0
118+
target = dbg.GetSelectedTarget()
119+
process = target.GetProcess()
120+
visited_pc_counts = defaultdict(int)
121+
while True:
122+
gen += 1
123+
print(':: Generation {} (# inspections = {})'.format(gen, inspections))
124+
125+
thread = process.GetSelectedThread()
126+
frame = thread.GetSelectedFrame()
127+
128+
do_inspection = True
129+
130+
# Sometimes, lldb gets lost after stepping. This is rdar://70546777.
131+
# Try to remind lldb where it is by running 'frame select'.
132+
if not frame.GetFunctionName():
133+
doit(dbg, 'frame select')
134+
135+
# Skip frames without valid line entries.
136+
line_entry = frame.GetLineEntry()
137+
if do_inspection and not line_entry.IsValid():
138+
do_inspection = False
139+
140+
# Skip optimized frames if asked to do so.
141+
if do_inspection and ONLY_VISIT_UNOPTIMIZED and \
142+
str(frame).endswith(' [opt]'):
143+
do_inspection = False
144+
145+
# Skip non-Swift frames if asked to do so.
146+
skip_inspection_due_to_frame_lang = False
147+
if do_inspection and ONLY_VISIT_SWIFT and \
148+
frame.GuessLanguage() != lldb.eLanguageTypeSwift:
149+
do_inspection = False
150+
skip_inspection_due_to_frame_lang = True
151+
152+
if SKIP_N_FRAMES_BETWEEN_INSPECTIONS > 0 and \
153+
gen % SKIP_N_FRAMES_BETWEEN_INSPECTIONS != 0:
154+
do_inspection = False
155+
156+
# Don't inspect the same PC twice. Some version of this is needed to
157+
# make speedy progress on programs containing loops or recursion. The
158+
# tradeoff is that we lose test coverage (the objects visible at this
159+
# PC may change over time).
160+
cur_pc = frame.GetPC()
161+
visit_count = visited_pc_counts[cur_pc]
162+
visited_pc_counts[cur_pc] += 1
163+
if do_inspection and visit_count > 0:
164+
do_inspection = False
165+
166+
# Inspect the current frame if permitted to do so.
167+
if do_inspection:
168+
inspections += 1
169+
170+
doit(dbg, 'bt')
171+
172+
# Exercise `frame variable`.
173+
if not SKIP_FRAMEVAR:
174+
doit(dbg, 'frame variable')
175+
176+
# Exercise `po`.
177+
if not SKIP_PO:
178+
get_args = True
179+
get_locals = True
180+
get_statics = True
181+
get_in_scope_only = True
182+
variables = frame.GetVariables(get_args, get_locals,
183+
get_statics, get_in_scope_only)
184+
for var in variables:
185+
name = var.GetName()
186+
if not var.GetLocation():
187+
# Debug info doesn't provide a location for the var, so
188+
# `po` cannot succeed. Skip it.
189+
continue
190+
doit(dbg, 'po {0}'.format(name))
191+
192+
# Sometimes, we might visit a PC way too often (or there's nothing for
193+
# us to inspect because there's no line entry). After the first visit of
194+
# a PC, we aren't even inspecting the frame.
195+
#
196+
# To speed things up, we "finish" out of the frame if we think we've
197+
# spent too much time under it. The tradeoff is that we lose test
198+
# coverage (we may fail to step through certain program paths). That's
199+
# probably ok, considering that this can help *increase* test coverage
200+
# by virtue of helping us not get stuck in a hot loop sinkhole.
201+
if visit_count >= MAX_VISITS_PER_PC or \
202+
skip_inspection_due_to_frame_lang or not line_entry.IsValid():
203+
old_func_name = frame.GetFunctionName()
204+
if not old_func_name:
205+
print(':: Stepped to frame without function name!')
206+
doquit(dbg)
207+
return
208+
209+
while frame.GetFunctionName() == old_func_name:
210+
if not return_from_frame(thread, frame):
211+
print(':: Failed to step out of frame!')
212+
doquit(dbg)
213+
return
214+
doit(dbg, 'frame select')
215+
frame = thread.GetSelectedFrame()
216+
continue
217+
218+
if alter_PC(dbg, process, 'step'):
219+
print(':: Failed to step!')
220+
doquit(dbg)
221+
return

lldb/test/Shell/helper/toolchain.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ def use_support_substitutions(config):
138138
]
139139
llvm_config.add_tool_substitutions(tools)
140140

141+
swift_bin_dir = os.path.dirname(config.swiftc)
142+
swift_Benchmark_Onone = os.path.join(swift_bin_dir,
143+
'Benchmark_Onone-{0}'.format(config.target_triple))
144+
if os.path.exists(swift_Benchmark_Onone):
145+
config.substitutions.append(('%swift_Benchmark_Onone',
146+
swift_Benchmark_Onone))
147+
config.available_features.add('swift_Benchmark_Onone')
148+
141149
if sys.platform.startswith('netbsd'):
142150
# needed e.g. to use freshly built libc++
143151
host_flags += ['-L' + config.llvm_libs_dir,

0 commit comments

Comments
 (0)