Files
StarPilot/openpilot/starpilot/models/runtime_bridge/qcom.py
T
2026-10-22 13:52:19 -05:00

134 lines
7.3 KiB
Python

"""Guard legacy QCOM CPU submissions without changing compiled GPU payloads."""
from collections import Counter
from dataclasses import replace
import ctypes
import hashlib
from pathlib import Path
from typing import TYPE_CHECKING, cast
from tinygrad.device import Buffer, HCQ_RUNTIME_DEV
from tinygrad.dtype import AddrSpace, dtypes
from tinygrad.engine.realize import lower_and_compile
from tinygrad.uop.ops import AxisType, Ops, UOp
from tinygrad.runtime.support.hcq2 import HCQInfo
if TYPE_CHECKING:
from openpilot.starpilot.models.runtime_bridge.legacy_types import LegacyBufferFactory, LegacyCall, LegacyUOpFactory
def byte_inventory(root):
return Counter(hashlib.sha256(u.arg).hexdigest() for u in root.toposort()
if u.op is Ops.BINARY and isinstance(u.arg, bytes))
class Protection:
def __init__(self, library):
self.library = ctypes.CDLL(str(Path(library).resolve()))
self.function = self.library.starpilot_bounded_wait
self.function.argtypes = (ctypes.POINTER(ctypes.c_uint64), ctypes.c_uint64)
self.function.restype = ctypes.c_uint64
address = ctypes.cast(self.function, ctypes.c_void_p).value
assert address is not None
self.pointer = cast("LegacyBufferFactory", Buffer)(cast(str, HCQ_RUNTIME_DEV.value), 1, dtypes.uint64,
initial_value=address.to_bytes(8, 'little'))
self.records = []
def protect_call(self, call):
info, program = call.arg.aux, call.body
if not isinstance(info, HCQInfo) or info.device != ('QCOM',):
raise ValueError('Only the explicit QCOM compatibility path is supported')
if program.op is not Ops.PROGRAM or program.arg.target.device != 'CPU' or len(program.src) != 4:
raise ValueError('Expected a stored CPU host submission')
sink = program.src[0]
if sink.op is not Ops.SINK or sink.arg.name != 'hcq_submit':
raise ValueError('Expected the exact host submission source')
if info.nargs != len(call.src) - 1:
raise ValueError('Host argument binding changed')
before = byte_inventory(sink)
# New pointer is appended to buffers; named ALU arguments follow all buffers.
params = {u: u.replace(arg=replace(u.arg, slot=u.arg.slot + 1)) for u in sink.toposort()
if u.op is Ops.PARAM and u.arg.addrspace is AddrSpace.ALU}
sink = sink.substitute(params, enter_calls=True)
pointer = UOp.param(info.nargs, dtypes.uint64, 1, cast(str, HCQ_RUNTIME_DEV.value), name='bounded_wait_pointer')
callee = UOp.custom_function('starpilot_bounded_wait', pointer.index(0).load())
replacements, seen_loops = {}, set()
for end in sink.toposort():
if end.op is not Ops.END or len(end.src) != 3:
continue
done, loop, condition = end.src
if loop.op is not Ops.RANGE or loop.arg != (0, AxisType.WEAK) or loop.src != (UOp(Ops.NOOP),):
continue
if (done.op is not Ops.LOAD or condition.op is not Ops.CMPLT or condition.src[0] is not done or
done.dtype != dtypes.uint64 or condition.src[1].dtype != dtypes.uint64):
raise ValueError('Unknown conditional fence')
index = done.src[0]
if index.op is not Ops.INDEX or len(index.src) != 2 or index.src[1].val != 0:
raise ValueError('Unknown signal index')
signal = index.src[0].without_after
if signal.op is not Ops.PARAM or signal.dtype != dtypes.uint64 or not 0 <= signal.arg.slot < info.nargs:
raise ValueError('Unknown timeline parameter')
outer = call.src[1 + signal.arg.slot].without_after
if outer.op is not Ops.PARAM or outer.tag != 'timeline' or tuple(outer.device) != ('QCOM',):
raise ValueError('Timeline is not the native QCOM fence')
target = condition.src[1]
if loop in target.toposort():
raise ValueError('Fence target depends on its polling loop')
signal_index = index.substitute({loop: UOp(Ops.NOOP)}, enter_calls=True)
replacements[end] = cast("LegacyCall", callee).call(signal_index, target, ret_dtype=dtypes.uint64)
seen_loops.add(loop)
weak = {u for u in sink.toposort() if u.op is Ops.RANGE and u.src and u.src[0].op is Ops.NOOP}
if len(replacements) != 1 or weak != seen_loops:
raise ValueError('Host fence shape is not exactly the qualified QCOM shape')
rewritten = sink.substitute(replacements, enter_calls=True)
if byte_inventory(rewritten) != before:
raise ValueError('Opaque GPU payload changed')
if any(u.op is Ops.RANGE and u.src and u.src[0].op is Ops.NOOP for u in rewritten.toposort()):
raise ValueError('An unbounded host loop remains')
if any(u.op is Ops.CALL and u.body.op in (Ops.SINK, Ops.PROGRAM) for u in rewritten.toposort()):
raise ValueError('Nested compilation request in host source')
# The only compilation request is this CPU SINK.
new_call = call.replace(src=(rewritten, *call.src[1:], cast("LegacyUOpFactory", UOp).from_buffer(self.pointer)),
arg=replace(call.arg, aux=replace(info, nargs=info.nargs + 1)))
if new_call.device != cast(str, HCQ_RUNTIME_DEV.value) or cast(str, HCQ_RUNTIME_DEV.value).split(':')[0] != 'CPU':
raise ValueError('Refusing any non-CPU host compilation')
compiled = lower_and_compile(UOp(Ops.LINEAR, src=(new_call,))).src[0]
if compiled.body.arg.target.device != 'CPU' or byte_inventory(compiled.body.src[0]) != before:
raise ValueError('Host-only compilation contract failed')
self.records.append({'kernels': len(info.kernels), 'fences': len(replacements),
'gpu_payload_hashes_preserved': True, 'new_globals': compiled.body.arg.globals})
return compiled
def artifact(self, artifact):
if artifact.get('execution_device') != 'QCOM':
raise ValueError('Chestnut bridge is not qualified')
count = 0
for jit in artifact.values():
cap = getattr(jit, 'captured', None)
if cap is None:
continue
if 'linear' in cap.__dict__:
raise ValueError('Protection must precede captured execution')
for entry in cap._linear.src:
call = entry.without_after
if call.op is not Ops.CALL or not (call.body.op is Ops.COPY or isinstance(call.arg.aux, HCQInfo)):
raise ValueError('Unexpected top-level captured schedule entry')
old_cpu = {u.src[-1] for u in cap._linear.toposort() if u.op is Ops.PROGRAM and u.arg.target.device == 'CPU'}
old_payload = Counter(hashlib.sha256(u.arg).hexdigest() for u in cap._linear.toposort()
if u.op is Ops.BINARY and u not in old_cpu)
replacements = {u: self.protect_call(u) for u in cap._linear.toposort()
if u.op is Ops.CALL and isinstance(u.arg.aux, HCQInfo)}
if not replacements:
raise ValueError('Captured graph has no qualified native submission')
proposed = cap._linear.substitute(replacements, enter_calls=True)
new_cpu = {u.src[-1] for u in proposed.toposort() if u.op is Ops.PROGRAM and u.arg.target.device == 'CPU'}
new_payload = Counter(hashlib.sha256(u.arg).hexdigest() for u in proposed.toposort()
if u.op is Ops.BINARY and u not in new_cpu)
if old_payload != new_payload:
raise ValueError('Captured opaque runtime payload changed')
self.records[-1]['opaque_payload_count'] = sum(old_payload.values())
cap._linear = proposed
count += 1
if count != 3:
raise ValueError('Expected policy and both camera captures')
return artifact