mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Project private constant specializations in public adaptive bytes
Traverse immutable canonical boundaries and preserve arguments and cache cells. Retain public specialization IDs and reject the unsupported legacy iterator encoding. Add ordinary warmed and monitoring regressions. Assisted-by: OpenAI Codex:model identifier unavailable
This commit is contained in:
1 parent
cafc576332
commit
b33adafe86
4 files changed
+158
-10
No files matched your search
@@ -807,6 +807,13 @@ impl CodeUnits {
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of code units, without borrowing the live mutable instruction slice.
|
||||
/// Constructors and Clone keep the out-of-line counter array at this length.
|
||||
#[must_use]
|
||||
pub fn unit_count(&self) -> usize {
|
||||
self.adaptive_counters.len()
|
||||
}
|
||||
|
||||
/// Replace the opcode at `index` in-place without changing the arg byte.
|
||||
/// Uses atomic Release store to ensure prior cache writes are visible
|
||||
/// to threads that subsequently read the new opcode with Acquire.
|
||||
|
||||
@@ -1198,15 +1198,47 @@ impl Py<PyCode> {
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
|
||||
// Return current (possibly quickened/specialized) bytecode
|
||||
let bytes = unsafe {
|
||||
core::slice::from_raw_parts(
|
||||
self.code.instructions.as_ptr() as *const u8,
|
||||
self.code.instructions.len() * 2,
|
||||
)
|
||||
pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
|
||||
let incompatible = || vm.new_value_error("incompatible adaptive bytecode layout");
|
||||
let legacy =
|
||||
|| vm.new_value_error("legacy private GET_YIELD_FROM_ITER has no public 3.15 encoding");
|
||||
let (canonical, []) = self.canonical_code.as_chunks::<2>() else {
|
||||
return Err(incompatible());
|
||||
};
|
||||
vm.ctx.new_bytes(bytes.to_vec())
|
||||
let instructions = &self.code.instructions;
|
||||
if canonical.len() != instructions.unit_count() {
|
||||
return Err(incompatible());
|
||||
}
|
||||
let mut bytes = Vec::with_capacity(self.canonical_code.len());
|
||||
let mut index = 0;
|
||||
while index < canonical.len() {
|
||||
let base = Instruction::try_from(canonical[index][0]).map_err(|_| incompatible())?;
|
||||
if matches!(base, Instruction::GetYieldFromIter) {
|
||||
return Err(legacy());
|
||||
}
|
||||
let next = index
|
||||
.checked_add(1 + base.cache_entries())
|
||||
.filter(|&next| next <= canonical.len())
|
||||
.ok_or_else(incompatible)?;
|
||||
// Boundaries come only from this code's immutable canonical backing.
|
||||
// Never decode a cache payload or use a live monitoring wrapper's width.
|
||||
let op = match instructions.read_op(index) {
|
||||
op @ (Instruction::LoadConstImmortal | Instruction::LoadConstMortal) => {
|
||||
op.deoptimize()
|
||||
}
|
||||
Instruction::GetYieldFromIter => return Err(legacy()),
|
||||
op => op,
|
||||
};
|
||||
bytes.push(u8::from(op));
|
||||
bytes.push(u8::from(instructions.read_arg(index)));
|
||||
for cache in index + 1..next {
|
||||
bytes.extend_from_slice(&instructions.read_cache_u16(cache).to_ne_bytes());
|
||||
}
|
||||
index = next;
|
||||
}
|
||||
// Per-cell atomic observation, not a coherent cross-thread snapshot.
|
||||
// This does not repair the interpreter's other live-storage access paths.
|
||||
Ok(vm.ctx.new_bytes(bytes))
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
"""Public adaptive bytes preserve layout without exposing private constant opcodes."""
|
||||
|
||||
import dis
|
||||
import marshal
|
||||
import opcode
|
||||
import sys
|
||||
|
||||
FILENAME = "adaptive_public_export.py"
|
||||
CONSTANT_SOURCE = (
|
||||
'def warm_const():\n return ("ordinary constant payload", 123456789)\n'
|
||||
)
|
||||
LOOP_SOURCE = "def total(n):\n result = 0\n for value in range(n):\n result += value\n return result\n"
|
||||
PUBLIC = set(opcode.opmap.values()) | set(opcode._specialized_opmap.values())
|
||||
|
||||
|
||||
def make(source, name):
|
||||
namespace = {}
|
||||
exec(compile(source, FILENAME, "exec"), namespace)
|
||||
return namespace[name]
|
||||
|
||||
|
||||
def check_layout(code, canonical, offsets):
|
||||
adaptive = code._co_code_adaptive
|
||||
assert len(adaptive) == len(canonical)
|
||||
for offset in offsets:
|
||||
assert adaptive[offset] in PUBLIC, (offset, adaptive[offset])
|
||||
assert adaptive[offset] not in (219, 220, 221)
|
||||
assert adaptive[offset + 1] == canonical[offset + 1]
|
||||
assert code.co_code == canonical
|
||||
return adaptive
|
||||
|
||||
|
||||
function = make(CONSTANT_SOURCE, "warm_const")
|
||||
code = function.__code__
|
||||
canonical = code.co_code
|
||||
offsets = tuple(i.offset for i in dis.get_instructions(code))
|
||||
cold = check_layout(code, canonical, offsets)
|
||||
constant_offset = next(
|
||||
i.offset for i in dis.get_instructions(code) if i.opname == "LOAD_CONST"
|
||||
)
|
||||
assert cold[constant_offset] == opcode.opmap["LOAD_CONST"]
|
||||
before = marshal.dumps(code, 6)
|
||||
for _ in range(1000):
|
||||
assert function() == ("ordinary constant payload", 123456789)
|
||||
assert marshal.dumps(code, 6) == before
|
||||
adaptive = check_layout(code, canonical, offsets)
|
||||
constants = [
|
||||
i for i in dis.get_instructions(code, adaptive=True) if i.opname == "LOAD_CONST"
|
||||
]
|
||||
assert len(constants) == 1
|
||||
assert constants[0].argval == ("ordinary constant payload", 123456789)
|
||||
assert adaptive[constants[0].offset] == opcode.opmap["LOAD_CONST"]
|
||||
|
||||
loop = make(LOOP_SOURCE, "total")
|
||||
code = loop.__code__
|
||||
canonical = code.co_code
|
||||
offsets = tuple(i.offset for i in dis.get_instructions(code))
|
||||
for _ in range(1000):
|
||||
assert loop(10) == 45
|
||||
first = check_layout(code, canonical, offsets)
|
||||
second = check_layout(code, canonical, offsets)
|
||||
assert first == second
|
||||
assert any(i.cache_info for i in dis.get_instructions(code))
|
||||
rows = tuple(dis.get_instructions(code, adaptive=True))
|
||||
assert tuple(i.offset for i in rows) == offsets
|
||||
assert any(i.opcode in opcode._specialized_opmap.values() for i in rows)
|
||||
assert all(first[i.offset] == i.opcode for i in rows)
|
||||
|
||||
monitoring = sys.monitoring
|
||||
available = [tool for tool in range(6) if monitoring.get_tool(tool) is None]
|
||||
assert available
|
||||
TOOL = available[0]
|
||||
monitoring.use_tool_id(TOOL, "adaptive-public-export")
|
||||
try:
|
||||
for event in (monitoring.events.LINE, monitoring.events.INSTRUCTION):
|
||||
function = make(LOOP_SOURCE, "total")
|
||||
code = function.__code__
|
||||
canonical = code.co_code
|
||||
offsets = tuple(i.offset for i in dis.get_instructions(code))
|
||||
observed = []
|
||||
|
||||
def inspect(actual_code, location):
|
||||
assert actual_code is code
|
||||
if not observed:
|
||||
# First adaptive export for this code happens under active wrappers.
|
||||
observed.append(check_layout(actual_code, canonical, offsets))
|
||||
assert marshal.loads(marshal.dumps(actual_code, 6)).co_code == canonical
|
||||
|
||||
monitoring.register_callback(TOOL, event, inspect)
|
||||
monitoring.set_local_events(TOOL, code, event)
|
||||
try:
|
||||
assert function(10) == 45
|
||||
assert len(observed) == 1
|
||||
finally:
|
||||
monitoring.set_local_events(TOOL, code, 0)
|
||||
monitoring.register_callback(TOOL, event, None)
|
||||
check_layout(code, canonical, offsets)
|
||||
assert function(5) == 10
|
||||
finally:
|
||||
monitoring.set_events(TOOL, 0)
|
||||
monitoring.free_tool_id(TOOL)
|
||||
@@ -53,8 +53,16 @@ they do not alter the three strict comparison domains.
|
||||
|
||||
Canonical `co_code` uses immutable prepublication bytes and a stable bytes owner;
|
||||
the common marshal writer uses that actual field rather than live monitoring
|
||||
instructions. The adaptive getter remains a separate gap: private Rust
|
||||
`LOAD_CONST` specializations can appear as unknown IDs in canonical `dis`.
|
||||
instructions. The adaptive getter projects private Rust `LOAD_CONST` IDs220/221
|
||||
to public `LOAD_CONST` at immutable canonical instruction boundaries, preserving
|
||||
arguments, public specialization IDs and cache payload bytes. Legacy private
|
||||
ID219 has no equivalent one-unit public encoding and raises `ValueError` on
|
||||
adaptive export. Length and truncated canonical cache spans are checked; arbitrary
|
||||
same-length postpublication embedding mutation remains outside the finalized-code
|
||||
contract. Per-cell atomic reads do not establish a coherent concurrent snapshot
|
||||
or repair inherited shared-storage accessor and mixed-width dispatch concerns.
|
||||
The projection neither mutates executable instructions nor changes canonical or
|
||||
marshal bytes. Runtime specialization choices need not match CPython.
|
||||
Retained private `GET_YIELD_FROM_ITER` must never be emitted by the compiler.
|
||||
The current local tree includes the separately reviewed builtin `frozendict`
|
||||
prerequisite and exact final `Lib/opcode.py`, `Lib/_opcode_metadata.py`, and
|
||||
|
||||
Reference in new issue
Block a user