Skip to content

Commit 156e92b

Browse files
Release code observers only if a CodeInfo is removed from the code cache and update handling of lazy deopt frames.
Update runtimedebuginfotests. Combine release and releaseOnTeardown for code observers.
1 parent fca0ddb commit 156e92b

File tree

9 files changed

+62
-64
lines changed

9 files changed

+62
-64
lines changed

substratevm/debug/gdbpy/gdb-debughelpers.py

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,7 +1637,7 @@ def __init__(self, svm_util: SVMUtil):
16371637
self.lazy_deopt_stub_object_adr = None
16381638
self.svm_util = svm_util
16391639

1640-
def __call__(self, pending_frame: gdb.Frame):
1640+
def __call__(self, pending_frame: gdb.PendingFrame):
16411641
if self.eager_deopt_stub_adr is None:
16421642
self.eager_deopt_stub_adr = SVMUtil.get_eager_deopt_stub_adr()
16431643
self.lazy_deopt_stub_primitive_adr = SVMUtil.get_lazy_deopt_stub_primitive_adr()
@@ -1656,20 +1656,23 @@ def __call__(self, pending_frame: gdb.Frame):
16561656
source_frame_size = encoded_frame_size & ~self.svm_util.frame_size_status_mask
16571657

16581658
# Now find the register-values for the caller frame
1659-
unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(sp, pc))
16601659
caller_sp = sp + int(source_frame_size)
1661-
unwind_info.add_saved_register('sp', gdb.Value(caller_sp))
16621660
# try to fetch return address directly from stack
16631661
caller_pc = gdb.Value(caller_sp - 8).cast(self.svm_util.stack_type.pointer()).dereference()
1662+
1663+
# Build the unwind info
1664+
unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(caller_sp, caller_pc))
1665+
unwind_info.add_saved_register('sp', gdb.Value(caller_sp))
16641666
unwind_info.add_saved_register('pc', gdb.Value(caller_pc))
16651667
return unwind_info
16661668
elif int(pc) == self.lazy_deopt_stub_primitive_adr or int(pc) == self.lazy_deopt_stub_object_adr:
1667-
# Now find the register-values for the caller frame
1668-
# We only have the original pc for lazy deoptimization -> unwind to original pc with same sp
1669-
# This is the best guess we can make without knowing the return address and frame size of the lazily deoptimized frame
1669+
# We only need the original pc for lazy deoptimization -> unwind to original pc with same sp
1670+
# Since this is lazy deoptimization we can still use the debug frame info at the current sp
1671+
caller_pc = sp.cast(self.svm_util.stack_type.pointer()).dereference()
1672+
1673+
# build the unwind info
16701674
unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(sp, pc))
16711675
unwind_info.add_saved_register('sp', gdb.Value(sp))
1672-
caller_pc = sp.cast(self.svm_util.stack_type.pointer()).dereference()
16731676
unwind_info.add_saved_register('pc', gdb.Value(caller_pc))
16741677
return unwind_info
16751678
except Exception as ex:
@@ -1696,18 +1699,26 @@ def filter(self, frame_iter: Iterable) -> FrameDecorator:
16961699
self.lazy_deopt_stub_primitive_adr = SVMUtil.get_lazy_deopt_stub_primitive_adr()
16971700
self.lazy_deopt_stub_object_adr = SVMUtil.get_lazy_deopt_stub_object_adr()
16981701

1702+
lazy_deopt = False
16991703
for frame in frame_iter:
17001704
frame = frame.inferior_frame()
17011705
pc = int(frame.pc())
17021706
if pc == self.eager_deopt_stub_adr:
1703-
yield SVMFrameEagerDeopt(self.svm_util, frame)
1707+
yield SVMFrameEagerDeopt(frame, self.svm_util)
17041708
elif pc == self.lazy_deopt_stub_primitive_adr or pc == self.lazy_deopt_stub_object_adr:
1705-
yield SVMFrameLazyDeopt(self.svm_util, frame)
1709+
lazy_deopt = True
1710+
continue # the next frame is the one with the corrected pc
17061711
else:
1707-
yield SVMFrame(frame)
1712+
yield SVMFrame(frame, lazy_deopt)
1713+
lazy_deopt = False
17081714

17091715

17101716
class SVMFrame(FrameDecorator):
1717+
1718+
def __init__(self, frame: gdb.Frame, lazy_deopt: bool):
1719+
super().__init__(frame)
1720+
self.__lazy_deopt = lazy_deopt
1721+
17111722
def function(self) -> str:
17121723
frame = self.inferior_frame()
17131724
if not frame.name():
@@ -1725,7 +1736,8 @@ def function(self) -> str:
17251736
else:
17261737
eclipse_filename = ''
17271738

1728-
return func_name + eclipse_filename
1739+
prefix = '[LAZY DEOPT FRAME] ' if self.__lazy_deopt else ''
1740+
return prefix + func_name + eclipse_filename
17291741

17301742

17311743
class SymValueWrapper:
@@ -1741,9 +1753,9 @@ def symbol(self):
17411753
return self.sym
17421754

17431755

1744-
class SVMFrameEagerDeopt(SVMFrame):
1756+
class SVMFrameEagerDeopt(FrameDecorator):
17451757

1746-
def __init__(self, svm_util: SVMUtil, frame: gdb.Frame):
1758+
def __init__(self, frame: gdb.Frame, svm_util: SVMUtil):
17471759
super().__init__(frame)
17481760

17491761
# fetch deoptimized frame from stack
@@ -1827,25 +1839,6 @@ def frame_locals(self):
18271839
return None
18281840

18291841

1830-
class SVMFrameLazyDeopt(SVMFrame):
1831-
1832-
def __init__(self, svm_util: SVMUtil, frame: gdb.Frame):
1833-
super().__init__(frame)
1834-
1835-
# fetch deoptimized frame from stack
1836-
sp = frame.read_register('sp')
1837-
real_pc = sp.cast(svm_util.stack_type.pointer()).dereference().cast(svm_util.stack_type.pointer())
1838-
self.__gdb_text = str(real_pc)
1839-
self.__svm_util = svm_util
1840-
1841-
def function(self) -> str:
1842-
if self.__gdb_text is None:
1843-
# we have no more information about the frame
1844-
return '[LAZY DEOPT FRAME ...]'
1845-
1846-
return '[LAZY DEOPT FRAME] at ' + self.__gdb_text
1847-
1848-
18491842
try:
18501843
svminitfile = os.path.expandvars('${SVMGDBINITFILE}')
18511844
exec(open(svminitfile).read())

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1242,7 +1242,7 @@ def run_js_test(eager: bool = False):
12421242
svm_experimental_options([
12431243
'-H:+SourceLevelDebug',
12441244
'-H:+RuntimeDebugInfo',
1245-
'-H:+LazyDeoptimization' if eager else '-H:-LazyDeoptimization',
1245+
'-H:-LazyDeoptimization' if eager else '-H:+LazyDeoptimization',
12461246
]) +
12471247
['-g', '-O0', '--macro:jsvm-library']
12481248
))

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserver.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ interface InstalledCodeObserverHandleAccessor {
7070
default void activate(InstalledCodeObserverHandle handle) {
7171
}
7272

73+
@Uninterruptible(reason = "Called during GC or teardown.")
7374
default void release(InstalledCodeObserverHandle handle) {
7475
}
7576

@@ -78,9 +79,5 @@ default void detachFromCurrentIsolate(InstalledCodeObserverHandle handle) {
7879

7980
default void attachToCurrentIsolate(InstalledCodeObserverHandle handle) {
8081
}
81-
82-
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
83-
default void releaseOnTearDown(InstalledCodeObserverHandle handle) {
84-
}
8582
}
8683
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ public static void removeObserversOnTearDown(NonmovableArray<InstalledCodeObserv
141141
for (int i = 0; i < length; i++) {
142142
InstalledCodeObserverHandle handle = NonmovableArrays.getWord(observerHandles, i);
143143
if (handle.isNonNull()) {
144-
getAccessor(handle).releaseOnTearDown(handle);
144+
getAccessor(handle).release(handle);
145145
NonmovableArrays.setWord(observerHandles, i, Word.nullPointer());
146146
}
147147
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,9 @@ private void prepareInvalidation(CodeInfo info) {
292292
}
293293

294294
private void continueInvalidation(CodeInfo info, boolean removeNow) {
295-
InstalledCodeObserverSupport.removeObservers(RuntimeCodeInfoAccess.getCodeObserverHandles(info));
296295
if (removeNow) {
297296
/* If removeNow, then the CodeInfo is immediately removed from the code cache. */
297+
InstalledCodeObserverSupport.removeObservers(RuntimeCodeInfoAccess.getCodeObserverHandles(info));
298298
removeFromCodeCache(info);
299299
RuntimeCodeInfoHistory.singleton().logInvalidate(info);
300300
} else {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) {
168168
}
169169

170170
@Override
171-
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
171+
@Uninterruptible(reason = "Called during GC or teardown.")
172172
public void release(InstalledCodeObserverHandle installedCodeObserverHandle) {
173173
Handle handle = (Handle) installedCodeObserverHandle;
174174
GdbJitInterface.JITCodeEntry entry = handle.getRawHandle();
@@ -193,12 +193,6 @@ public void attachToCurrentIsolate(InstalledCodeObserverHandle installedCodeObse
193193
Handle handle = (Handle) installedCodeObserverHandle;
194194
NonmovableArrays.trackUnmanagedArray(handle.getDebugInfoData());
195195
}
196-
197-
@Override
198-
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
199-
public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverHandle) {
200-
release(installedCodeObserverHandle);
201-
}
202196
}
203197

204198
@Override

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@
4545
boolean_rexp = re.compile(r"(true|false)")
4646
array_rexp = re.compile(r'.+\[\d+]\s*=\s*{.*}')
4747
args_rexp = re.compile(r'.*\(.*\)\s*\((?P<args>.*)\)')
48-
hex_rexp = re.compile(r"[\da-fA-F]+")
48+
hex_pattern = r"[\da-fA-F]+"
49+
hex_rexp = re.compile(hex_pattern)
50+
value_pattern = r"[a-zA-Z0-9$_<> ]+"
4951

5052

5153
def gdb_execute(command: str) -> str:
@@ -118,6 +120,11 @@ def gdb_delete_breakpoints() -> None:
118120
gdb_execute("delete breakpoints")
119121

120122

123+
def gdb_disable_breakpoints() -> None:
124+
logger.info("Disabling all breakpoints")
125+
gdb_execute("disable breakpoints")
126+
127+
121128
def gdb_run() -> None:
122129
logger.info('Run current program')
123130
gdb_execute('run')

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,14 @@ def test_update_breakpoint(self):
6969
# new breakpoint address is always added after
7070
self.assertEqual(breakpoint_info_after.split(breakpoint_info_before.split('0x')[-1])[-1].count('com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest'), 1)
7171

72-
# run until the runtime compilation is invalidated and check if the breakpoint is removed
73-
gdb_set_breakpoint('com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl::invalidate')
74-
gdb_continue() # run until invalidation
75-
gdb_finish() # finish invalidation - this should trigger an unregister call to gdb
72+
# run until the end and check if breakpoints for the run-time debuginfo are removed
73+
gdb_disable_breakpoints()
74+
gdb_continue() # run until the end
7675
breakpoint_info_after_invalidation = gdb_execute('info breakpoints')
77-
# check if additional breakpoint is cleared after invalidate
76+
# check if additional breakpoint is removed
7877
# breakpoint info is still printed as multi-breakpoint, thus we check if exactly one valid breakpoint is remaining
7978
self.assertEqual(breakpoint_info_after_invalidation.count('com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest'), 1)
80-
# breakpoint info must change after invalidation
79+
# breakpoint info must change
8180
self.assertNotEqual(breakpoint_info_after, breakpoint_info_after_invalidation)
8281

8382
# this test requires gdb to first load a new objfile at runtime and then remove it as the compilation is invalidated
@@ -93,11 +92,10 @@ def test_load_objfile(self):
9392
objfiles = gdb.objfiles()
9493
self.assertTrue(any([o.filename.startswith('<in-memory@') for o in objfiles]))
9594

96-
# run until the runtime compilation is invalidated and check if the objfile is removed
97-
gdb_set_breakpoint('com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl::invalidate')
98-
gdb_continue() # run until invalidation
99-
gdb_finish() # finish invalidation - this should trigger an unregister call to gdb
100-
# compilation is invalidated, check if objfile was removed
95+
# run until the end and check if run-time debuginfo object file is removed
96+
gdb_disable_breakpoints()
97+
gdb_continue() # run until the end
98+
# check if objfiles are removed
10199
objfiles = gdb.objfiles()
102100
self.assertFalse(any([o.filename.startswith('<in-memory@') for o in objfiles]))
103101

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__))))
3434

3535
from gdb_helper import *
36+
import re
3637

3738

3839
# requires the gdb patch to be available
@@ -95,13 +96,21 @@ def test_backtrace_with_deopt(self):
9596
else:
9697
self.assertIn('SubstrateOptimizedCallTarget = {...}, __1=java.lang.Object[5] = {...})', backtrace)
9798
self.assertIn('SubstrateOptimizedCallTargetInstalledCode::doInvoke', backtrace)
98-
99-
self.assertNotIn('??', backtrace)
100-
self.assertNotIn('Unknown Frame at', backtrace)
10199
else:
102100
# must be lazy deopt frame
103-
# we can't be sure it is handled properly, but at least it should show up as lazy deopt frame in the backtrace
104-
self.assertIn('[LAZY DEOPT FRAME] at', backtrace)
101+
self.assertIn('[LAZY DEOPT FRAME]', backtrace)
102+
103+
if 'SubstrateEnterpriseOptimizedCallTarget' in backtrace:
104+
# check if we still see the parameters of the lazily deoptimized method on the backtrace
105+
self.assertRegex(backtrace, re.compile(rf'0x{hex_pattern} in \[LAZY DEOPT FRAME\] .* \(callTarget={value_pattern}, args={value_pattern}, __Object2={value_pattern}, __int3={value_pattern}, __int4={value_pattern}, __boolean5={value_pattern}\)'))
106+
else:
107+
# must be a SubstrateOptimizedCallTarget
108+
self.assertIn('SubstrateOptimizedCallTarget', backtrace)
109+
self.assertRegex(backtrace, re.compile(rf'0x{hex_pattern} in \[LAZY DEOPT FRAME\] .* \(this={value_pattern}, originalArguments={value_pattern}\)'))
110+
111+
# the backtrace should contain no unknown frames
112+
self.assertNotIn('??', backtrace)
113+
self.assertNotIn('Unknown Frame at', backtrace)
105114

106115
# the js deopt test uses the jsvm-library
107116
# so the debugging symbols come from the js shared library
@@ -117,7 +126,7 @@ def test_opaque_types_with_shared_library(self):
117126
self.assertNotIn('<unknown type in <in-memory@', backtrace)
118127
else:
119128
self.assertIn('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot', backtrace)
120-
self.assertIn('(this=<optimized out>, originalArguments=)', backtrace)
129+
self.assertIn('(this=<optimized out>, originalArguments=', backtrace)
121130
self.assertNotIn('this=<unknown type in <in-memory@', backtrace)
122131

123132

0 commit comments

Comments
 (0)