v0.11.0: adaptive specialization and sys.monitoring#19
Merged
Conversation
Port pycore_backoff.h and the cache structs from pycore_code.h as the foundation for v0.11 PEP 659 work. Cache widths are pinned by test against the _PyOpcode_Caches[] table from CPython 3.14 so the Go layout cannot silently drift from the C one.
The PEP 659 specializer needs to name LOAD_ATTR_INSTANCE_VALUE, BINARY_OP_ADD_INT, CALL_PY_EXACT_ARGS, etc. by symbol. The generator only walked the `opmap` dict; the specialized variants live in `_specialized_opmap`. Read both and emit all 238 opcodes.
Quickened code now ticks the BackoffCounter on each adaptive opcode and, when the counter triggers, calls into the per-family specializer in specialize/. Specialized variants written into the bytecode are folded back to their adaptive parent on entry to dispatch so the generic body still runs (no fast-path arms yet). The cache-bearing arms in eval_simple.go and eval_gen.go now use cacheAdvance to step past the inline cache cells when running on Quickened code; raw test bytecode without padding still advances one codeunit.
Bring across the PEP 669 event identifiers, the tool slot constants, and the per-code _PyCoMonitoringData layout that the instrumentation runtime will read from. No fire-event entry points yet; those come once the shadow walk lands.
Brings across the cluster of fields CPython stamps on PyInterpreterState for sys.monitoring: the global event mask, per-tool callback table, tool names, version counters, and the tool-id / event-set lifecycle helpers that back use_tool_id, free_tool_id, set_events, and register_callback. state.Interpreter now allocates one of these per interpreter so the shadow walk and fire-event entry points have somewhere to hang state.
Brings across the fan-out path tools call from the eval loop. EnterScope refreshes the per-event MonState bitmask when the global version drifts; fireEvent walks the active tools by most-significant-bit and invokes each registered callback with the PEP 669 argument signature. Disable returns clear an instrumented event from the per-MonState mask and clear the slot on non-instrumented events so a tool that misuses the sentinel does not get called again. Also adds the Disable / Missing sentinels (bare singleton objects whose identity callers compare against, mirroring _PyInstrumentation_DISABLE and _MISSING).
Brings across set_local_events / get_local_events so a tool can subscribe to instrumented events on a specific code object only. The per-code monitoring slab (CoMonitoringData) hangs off objects.Code.MonitoringData as an any to keep the objects package free of a monitor import. The interpreter and code-local versions both bump on a real change so the shadow walk knows to re-instrument; no-op writes leave them alone.
Brings across the bytecode-rewriting pass that turns subscribed events into INSTRUMENTED_<X> opcodes. The walk diffs the new active-tool union against the cached one so a no-op rebump skips the rewrite, and falls back to the base opcode (deopting from any specialized form) when the last tool drops off an offset. Per-codeunit tool bitmasks live on CoMonitoringData.Tools so the dispatch fan-out can read them by instruction index. Skips line and per-instruction arms; those land with #406.
Hook fireInstrumented into the dispatch entry so an INSTRUMENTED_<X> opcode resolves the active tool bitmask out of CoMonitoringData.Tools, fans out through the matching FireXxx entry, and reflects any Disable returns back into the per-codeunit mask before the base body runs.
Allocate the per-code line table on first LINE subscription, walk the bytecode to stamp original opcodes against PEP 626 line starts, install / clear INSTRUMENTED_LINE through the shadow walk, and let dispatch fire LINE plus recover the original opcode via CallInstrumentationLine before falling through to the normal body.
Build the PEP 669 module surface from a per-interpreter InterpState: use_tool_id / free_tool_id / clear_tool_id / get_tool, register_callback, get_events / set_events, get_local_events / set_local_events, restart_events, _all_events, plus the DISABLE / MISSING sentinels and the events sub-namespace. set_events folds the grouped BRANCH bit into BRANCH_LEFT | BRANCH_RIGHT so callers can subscribe with the public name.
Bridges PEP 669 events back to the classic Py_tracefunc shape so sys.settrace and sys.setprofile can keep working. Each tool slot (SYS_PROFILE = 6, SYS_TRACE = 7) gets its own fan-out of small handlers; SetProfile / SetTrace stamp the global event mask through monitor.InterpState and bump the per-thread function pointer that the handlers dispatch to.
Adds the four sys.* trace/profile builtins on top of the legacy tracing bridge. Each one takes a Python callable, wraps it in a trampoline that matches LegacyTraceFunc, and hands it to the SetTrace/SetProfile entry points. RegisterSysTraceBuiltins drops the four into a sys dict for the lifecycle wiring to pick up.
Drives the specializer, PEP 669 monitor, sys.settrace bridge, and sys.monitoring builtin surface through their public entry points so we catch regressions in the wiring between them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The specializer and monitor drop. v0.11 lights up the two big
runtime features 3.12 / 3.13 added: PEP 659 adaptive
specialization that rewrites bytecode into typed variants on
first hit, and PEP 669
sys.monitoringinstrumentation thatfans 19 events out to up to seven tools. The legacy
sys.settrace/sys.setprofilecallbacks now ride on top ofthe same monitor fan-out, so a single dispatch path drives both
shapes.
Shipped
specialize/carries the PEP 659 port: backoff counter andinline cache layouts (
backoff.go,cache.go),_PyCode_Quicken, the_PyOpcode_Cachesand_PyOpcode_Deopttables, and per-family entry points for
LOAD_ATTR,STORE_ATTR,LOAD_GLOBAL,LOAD_SUPER_ATTR,BINARY_OP,COMPARE_OP,CONTAINS_OP,TO_BOOL,STORE_SUBSCR,UNPACK_SEQUENCE,FOR_ITER,SEND,CALL, andCALL_KW.Each family lands with its hit / miss / deopt path and the
tp_version_tag/dk_versionguards the fast variants assume.vm/adaptive.goanddispatch.gowire the rewrite into the evalloop.
monitor/carries the PEP 669 port:InterpStateper-interpstate slab, the 19 fire-event entry points, the
_Py_Instrumentshadow walk that rewrites quickened bytecode to
INSTRUMENTED_*variants in place, the per-code tool-slot lifecycle, line
instrumentation driven by the PEP 626 line table, and the
shared callback runner that honours
Disable/Missing. Thesys.monitoringbuiltin module surfaces every entry the userneeds (
use_tool_id,register_callback,set_events,set_local_events,restart_events, plus the constants).vm/legacy_tracing.gobridges PEP 669 fan-out back to thePy_tracefuncshapesys.settrace/sys.setprofileexpect,registering as PEP 669 tools 6 and 7.
vm/sys_trace_builtins.goexposes the four Python-visible builtins
(
settrace,setprofile,gettrace,getprofile) on top ofthe bridge. Per-frame
TraceLines/TraceOpcodes/Linenoslots on
frame.Frameso the bridge can drive line and opcodeevents on demand.
compile/opcodes_gen.gois regenerated to pick up thespecialized variants (
LOAD_ATTR_INSTANCE_VALUE,BINARY_OP_ADD_INT, ...) and theINSTRUMENTED_*mirror set.The cache-count column lines up with
_PyOpcode_Cachesso thedispatch loop strides over the inline cache slots correctly.
Test plan
go build ./...clean.go test ./specialize/...green;cache_test.gopinsevery cache width against
_PyOpcode_Caches[].go test ./monitor/...green;install_test.goandline_test.goexercise the shadow walk and the line tabledecode.
go test ./vm/...green;adaptive_test.godrives aTO_BOOL rewrite and
legacy_tracing_test.godrives thebridge.
vmtest/v011_gate_test.goend-to-end gate runs throughthe specializer, monitor fan-out, sys.settrace bridge, and
sys.monitoringbuiltin surface in one go.(
TestRunStringCPythonSmoke/comparison_eq) is pre-existing onmain and unrelated to this drop.