Files
danos/system/devices/aml/interpreter.zig
T
Daniel Samson 767a2a9a7c Notify dispatch and GPE handlers (M21.2)
The AML interpreter now handles the Notify opcode (0x86, previously
unhandled): it resolves the target device, evaluates the code, and
records the pair in a bounded per-evaluate queue the caller drains with
takeNotifications. A host unit test with hand-encoded AML — a method that
issues Notify(DEV_, 0x80) — proves the device and code come back; aml.zig
joins the zig build test loop so the interpreter is covered on the host.

The acpi service's SCI handler now services general-purpose events too:
for each set-and-enabled GPE bit it evaluates the \_GPE._Lxx (level) or
_Exx (edge) handler method, drains the Notify queue that produced, and
publishes a domain event per notified device — PNP0C0A battery, ACPI0003
AC, PNP0C0D lid, else generic notify — then clears the status bit and
acks. The embedded controller's _Qxx queries are out of scope (hardware
track). QEMU raises no GPEs on this config, so the QEMU suite is the
regression net (the power button still works with GPE servicing in the
path); correctness is the unit test. Suite 59/59.
2026-07-13 05:44:58 +01:00

778 lines
33 KiB
Zig

//! A tree-walking AML interpreter — the evaluation stage on top of the parser's
//! structural namespace. It executes control methods (their bodies captured by
//! the parser) far enough to serve device discovery: device status (`_STA`, is a
//! device present), current resource settings (`_CRS`), and the operators, control
//! flow, locals/args, and
//! OperationRegion field access those methods reach for.
//!
//! Scope: integers, buffers, strings, packages, and references; If/Else/While/
//! Return; the arithmetic/logic operators; method invocation; Name/Local/Arg
//! access; CreateField buffer patching (the common current-resource-settings
//! (`_CRS`) idiom); and field
//! reads/writes against SystemMemory and SystemIO regions. Opcodes outside this
//! set return `error.Unsupported`, which callers treat as "couldn't evaluate" and
//! fall back — never a hard failure.
const std = @import("std");
const opcode = @import("opcodes.zig");
const Node = @import("namespace.zig").Node;
const Namespace = @import("namespace.zig").Namespace;
/// Injected hardware access for OperationRegion reads/writes (the architecture VMM + pio).
pub const Hal = struct {
mapMmio: *const fn (physical: u64, len: u64, writable: bool) u64,
pioRead: *const fn (width: u8, port: u16) u32,
pioWrite: *const fn (width: u8, port: u16, value: u32) void,
};
pub const Error = error{ Unsupported, Truncated, DivByZero } || std.mem.Allocator.Error;
/// A runtime AML value.
pub const Object = union(enum) {
uninitialized,
integer: u64,
buffer: []u8,
string: []u8,
package: []Object,
reference: *Node,
pub fn asInteger(self: Object) Error!u64 {
return switch (self) {
.integer => |v| v,
.buffer => |b| blk: {
var v: u64 = 0;
for (b, 0..) |byte, i| {
if (i >= 8) break;
v |= @as(u64, byte) << @intCast(i * 8);
}
break :blk v;
},
else => error.Unsupported,
};
}
};
const maximum_segments = 16;
const NamePath = struct {
rooted: bool = false,
parents: u8 = 0,
segments: [maximum_segments][4]u8 = undefined,
count: usize = 0,
fn slice(self: *const NamePath) []const [4]u8 {
return self.segments[0..self.count];
}
};
const Cursor = struct {
b: []const u8,
i: usize = 0,
fn eof(self: *Cursor) bool {
return self.i >= self.b.len;
}
fn peek(self: *Cursor) ?u8 {
return if (self.eof()) null else self.b[self.i];
}
fn byte(self: *Cursor) Error!u8 {
if (self.eof()) return error.Truncated;
const v = self.b[self.i];
self.i += 1;
return v;
}
fn take(self: *Cursor, n: usize) Error![]const u8 {
if (self.i + n > self.b.len) return error.Truncated;
const s = self.b[self.i .. self.i + n];
self.i += n;
return s;
}
fn packageLength(self: *Cursor) Error!usize {
const lead = try self.byte();
const follow: usize = lead >> 6;
if (follow == 0) return lead & 0x3F;
var value: usize = lead & 0x0F;
var k: usize = 0;
while (k < follow) : (k += 1) value |= @as(usize, try self.byte()) << @intCast(4 + k * 8);
return value;
}
fn nameString(self: *Cursor) Error!NamePath {
var name_path = NamePath{};
if (self.peek() == opcode.root_char) {
name_path.rooted = true;
self.i += 1;
} else {
while (self.peek() == opcode.parent_prefix_char) : (self.i += 1) name_path.parents += 1;
}
const lead = self.peek() orelse return name_path;
switch (lead) {
0x00 => self.i += 1,
opcode.dual_name_prefix => {
self.i += 1;
try self.segment(&name_path);
try self.segment(&name_path);
},
opcode.multi_name_prefix => {
self.i += 1;
const count = try self.byte();
var k: usize = 0;
while (k < count) : (k += 1) try self.segment(&name_path);
},
else => try self.segment(&name_path),
}
return name_path;
}
fn segment(self: *Cursor, name_path: *NamePath) Error!void {
const s = try self.take(4);
if (name_path.count < maximum_segments) {
name_path.segments[name_path.count] = s[0..4].*;
name_path.count += 1;
}
}
};
const Frame = struct {
args: [7]Object = .{.uninitialized} ** 7,
locals: [8]Object = .{.uninitialized} ** 8,
scope: *Node,
ret: Object = .uninitialized,
returned: bool = false,
broke: bool = false,
};
/// A CreateField binding: a name that indexes into a buffer object.
const BufferField = struct { buffer: *Node, byte_off: usize, bit_width: u32 };
/// One Notify(device, code) the interpreter executed.
pub const NotifyEvent = struct { node: *Node, code: u64 };
pub const Interpreter = struct {
namespace: *Namespace,
hal: Hal,
arena: std.mem.Allocator,
/// Runtime object overrides for Name nodes (Store targets, patched buffers).
dynamic_overrides: std.AutoHashMapUnmanaged(*Node, Object) = .{},
/// CreateField bindings active for the current evaluation.
fields: std.AutoHashMapUnmanaged(*Node, BufferField) = .{},
/// Notify(device, code) operations the last evaluation executed — a GPE or
/// EC handler tells the OS "look at this device" this way. Bounded; the
/// caller drains it with `takeNotifications` after `evaluate` (M21).
notify_queue: [16]NotifyEvent = undefined,
notify_count: usize = 0,
pub fn init(namespace: *Namespace, hal: Hal, arena: std.mem.Allocator) Interpreter {
return .{ .namespace = namespace, .hal = hal, .arena = arena };
}
/// Evaluate a namespace object: invoke a Method, read a Name's value, or read a
/// Field. Resets per-evaluation runtime state first.
pub fn evaluate(self: *Interpreter, node: *Node, args: []const Object) Error!Object {
self.notify_count = 0;
self.dynamic_overrides.clearRetainingCapacity();
self.fields.clearRetainingCapacity();
return self.invoke(node, args);
}
fn invoke(self: *Interpreter, node: *Node, args: []const Object) Error!Object {
switch (node.kind) {
.method => {
var frame = Frame{ .scope = node };
for (args, 0..) |a, i| {
if (i < frame.args.len) frame.args[i] = a;
}
var current = Cursor{ .b = node.value };
try self.executeList(&current, &frame);
return frame.ret;
},
.name => {
if (self.dynamic_overrides.get(node)) |o| return o;
var current = Cursor{ .b = node.value };
var frame = Frame{ .scope = node.parent orelse self.namespace.root };
return self.term(&current, &frame);
},
.field => return .{ .integer = try self.readField(node) },
else => return .{ .reference = node },
}
}
/// Execute a TermList until it ends or the frame returns/breaks.
fn executeList(self: *Interpreter, current: *Cursor, frame: *Frame) Error!void {
while (!current.eof() and !frame.returned and !frame.broke) {
_ = try self.term(current, frame);
}
}
/// Evaluate/execute one term, returning its value (`.uninitialized` for pure
/// statements).
fn term(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const lead = current.peek() orelse return error.Truncated;
if (isNameStart(lead)) return self.nameReference(current, frame);
_ = try current.byte();
return switch (lead) {
opcode.zero_opcode => Object{ .integer = 0 },
opcode.one_opcode => Object{ .integer = 1 },
opcode.ones_opcode => Object{ .integer = ~@as(u64, 0) },
opcode.byte_prefix => Object{ .integer = try self.readConstant(current, 1) },
opcode.word_prefix => Object{ .integer = try self.readConstant(current, 2) },
opcode.dword_prefix => Object{ .integer = try self.readConstant(current, 4) },
opcode.qword_prefix => Object{ .integer = try self.readConstant(current, 8) },
opcode.string_prefix => try self.readString(current),
opcode.buffer_opcode => try self.buffer(current, frame),
opcode.package_opcode, opcode.var_package_opcode => try self.package(current, frame, lead == opcode.var_package_opcode),
opcode.local0_opcode...opcode.local7_opcode => frame.locals[lead - opcode.local0_opcode],
opcode.arg0_opcode...opcode.arg6_opcode => frame.args[lead - opcode.arg0_opcode],
opcode.return_opcode => blk: {
frame.ret = try self.term(current, frame);
frame.returned = true;
break :blk .uninitialized;
},
opcode.break_opcode => blk: {
frame.broke = true;
break :blk .uninitialized;
},
opcode.continue_opcode, opcode.noop_opcode => .uninitialized,
opcode.if_opcode => try self.ifElse(current, frame),
opcode.while_opcode => try self.whileLoop(current, frame),
opcode.store_opcode => try self.store(current, frame),
opcode.increment_opcode => try self.incDec(current, frame, 1),
opcode.decrement_opcode => try self.incDec(current, frame, -1),
opcode.add_opcode => try self.binary(current, frame, .add),
opcode.subtract_opcode => try self.binary(current, frame, .sub),
opcode.multiply_opcode => try self.binary(current, frame, .mul),
opcode.mod_opcode => try self.binary(current, frame, .mod),
opcode.and_opcode => try self.binary(current, frame, .band),
opcode.or_opcode => try self.binary(current, frame, .bor),
opcode.xor_opcode => try self.binary(current, frame, .bxor),
opcode.nand_opcode => try self.binary(current, frame, .nand),
opcode.nor_opcode => try self.binary(current, frame, .nor),
opcode.shift_left_opcode => try self.binary(current, frame, .shl),
opcode.shift_right_opcode => try self.binary(current, frame, .shr),
opcode.divide_opcode => try self.divide(current, frame),
opcode.land_opcode => try self.logic2(current, frame, .land),
opcode.lor_opcode => try self.logic2(current, frame, .lor),
opcode.lequal_opcode => try self.logic2(current, frame, .eq),
opcode.lgreater_opcode => try self.logic2(current, frame, .gt),
opcode.lless_opcode => try self.logic2(current, frame, .lt),
opcode.lnot_opcode => try self.lnot(current, frame),
opcode.not_opcode => blk: {
const v = try self.evaluateInteger(current, frame);
const r = ~v;
try self.storeTarget(current, frame, .{ .integer = r });
break :blk .{ .integer = r };
},
opcode.size_of_opcode => try self.sizeOf(current, frame),
opcode.index_opcode => try self.index(current, frame),
opcode.dereference_of_opcode => try self.dereferenceOf(current, frame),
opcode.to_integer_opcode => blk: {
const v = try self.evaluateInteger(current, frame);
try self.storeTarget(current, frame, .{ .integer = v });
break :blk .{ .integer = v };
},
opcode.to_buffer_opcode => try self.passThroughUnary(current, frame),
opcode.notify_opcode => try self.notify(current, frame),
opcode.extended_opcode_prefix => try self.ext(current, frame),
// CreateXField: source, index, name (bit widths differ by op)
opcode.create_bit_field_opcode => try self.createField(current, frame, 1),
opcode.create_byte_field_opcode => try self.createField(current, frame, 8),
opcode.create_word_field_opcode => try self.createField(current, frame, 16),
opcode.create_dword_field_opcode => try self.createField(current, frame, 32),
opcode.create_qword_field_opcode => try self.createField(current, frame, 64),
else => error.Unsupported,
};
}
// --- name references ----------------------------------------------------
fn nameReference(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const name_path = try current.nameString();
const node = self.namespace.resolve(frame.scope, name_path.rooted, name_path.parents, name_path.slice()) orelse
return .uninitialized; // unknown name -> treat as uninitialised
switch (node.kind) {
.method => {
var argbuf: [7]Object = undefined;
var i: usize = 0;
while (i < node.arg_count and i < argbuf.len) : (i += 1) argbuf[i] = try self.term(current, frame);
return self.invoke(node, argbuf[0..@min(node.arg_count, argbuf.len)]);
},
.field => return .{ .integer = try self.readField(node) },
.name => return self.invoke(node, &.{}),
else => return .{ .reference = node },
}
}
// --- data objects -------------------------------------------------------
fn readConstant(self: *Interpreter, current: *Cursor, n: usize) Error!u64 {
_ = self;
const bytes = try current.take(n);
var v: u64 = 0;
for (bytes, 0..) |b, i| v |= @as(u64, b) << @intCast(i * 8);
return v;
}
fn readString(self: *Interpreter, current: *Cursor) Error!Object {
const start = current.i;
while (current.peek()) |c| {
current.i += 1;
if (c == 0) break;
}
const raw = current.b[start .. current.i - 1];
const s = try self.arena.dupe(u8, raw);
return .{ .string = s };
}
fn buffer(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const start = current.i;
const len = try current.packageLength();
const end = @min(start + len, current.b.len);
const size = try self.evaluateInteger(current, frame);
const data = current.b[@min(current.i, end)..end];
const bytes = try self.arena.alloc(u8, @intCast(size));
@memset(bytes, 0);
@memcpy(bytes[0..@min(bytes.len, data.len)], data[0..@min(bytes.len, data.len)]);
current.i = end;
return .{ .buffer = bytes };
}
fn package(self: *Interpreter, current: *Cursor, frame: *Frame, variable: bool) Error!Object {
const start = current.i;
const len = try current.packageLength();
const end = @min(start + len, current.b.len);
const count: usize = if (variable) @intCast(try self.evaluateInteger(current, frame)) else try current.byte();
const elems = try self.arena.alloc(Object, count);
var i: usize = 0;
while (i < count and current.i < end) : (i += 1) elems[i] = try self.term(current, frame);
while (i < count) : (i += 1) elems[i] = .uninitialized;
current.i = end;
return .{ .package = elems };
}
// --- operators ----------------------------------------------------------
const BinaryOperation = enum { add, sub, mul, mod, band, bor, bxor, nand, nor, shl, shr };
fn binary(self: *Interpreter, current: *Cursor, frame: *Frame, kind: BinaryOperation) Error!Object {
const a = try self.evaluateInteger(current, frame);
const b = try self.evaluateInteger(current, frame);
const r: u64 = switch (kind) {
.add => a +% b,
.sub => a -% b,
.mul => a *% b,
.mod => if (b == 0) return error.DivByZero else a % b,
.band => a & b,
.bor => a | b,
.bxor => a ^ b,
.nand => ~(a & b),
.nor => ~(a | b),
.shl => if (b >= 64) 0 else a << @intCast(b),
.shr => if (b >= 64) 0 else a >> @intCast(b),
};
try self.storeTarget(current, frame, .{ .integer = r });
return .{ .integer = r };
}
fn divide(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const a = try self.evaluateInteger(current, frame);
const b = try self.evaluateInteger(current, frame);
if (b == 0) return error.DivByZero;
try self.storeTarget(current, frame, .{ .integer = a % b }); // remainder target
try self.storeTarget(current, frame, .{ .integer = a / b }); // quotient target
return .{ .integer = a / b };
}
const LogicOperation = enum { land, lor, eq, gt, lt };
fn logic2(self: *Interpreter, current: *Cursor, frame: *Frame, kind: LogicOperation) Error!Object {
const a = try self.evaluateInteger(current, frame);
const b = try self.evaluateInteger(current, frame);
const r = switch (kind) {
.land => a != 0 and b != 0,
.lor => a != 0 or b != 0,
.eq => a == b,
.gt => a > b,
.lt => a < b,
};
return .{ .integer = if (r) ~@as(u64, 0) else 0 };
}
fn lnot(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
// 0x92 0x93/94/95 are the compound comparisons.
const b = current.peek() orelse return error.Truncated;
switch (b) {
opcode.lnot.not_equal => {
current.i += 1;
const x = try self.evaluateInteger(current, frame);
const y = try self.evaluateInteger(current, frame);
return .{ .integer = if (x != y) ~@as(u64, 0) else 0 };
},
opcode.lnot.less_equal => {
current.i += 1;
const x = try self.evaluateInteger(current, frame);
const y = try self.evaluateInteger(current, frame);
return .{ .integer = if (x <= y) ~@as(u64, 0) else 0 };
},
opcode.lnot.greater_equal => {
current.i += 1;
const x = try self.evaluateInteger(current, frame);
const y = try self.evaluateInteger(current, frame);
return .{ .integer = if (x >= y) ~@as(u64, 0) else 0 };
},
else => {
const x = try self.evaluateInteger(current, frame);
return .{ .integer = if (x == 0) ~@as(u64, 0) else 0 };
},
}
}
fn incDec(self: *Interpreter, current: *Cursor, frame: *Frame, delta: i64) Error!Object {
// Operand is a SuperName that is both read and written.
const save = current.i;
const current_value = try self.term(current, frame);
const v = try current_value.asInteger();
const r = if (delta > 0) v +% 1 else v -% 1;
var tcur = Cursor{ .b = current.b, .i = save };
try self.storeInto(&tcur, frame, .{ .integer = r });
return .{ .integer = r };
}
fn sizeOf(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const o = try self.term(current, frame);
return .{ .integer = switch (o) {
.buffer => |b| b.len,
.string => |s| s.len,
.package => |p| p.len,
else => 0,
} };
}
fn passThroughUnary(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const o = try self.term(current, frame);
try self.storeTarget(current, frame, o);
return o;
}
fn index(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const source = try self.term(current, frame);
const element_index: usize = @intCast(try self.evaluateInteger(current, frame));
// Optional target (a reference); we don't materialise references, so store
// the indexed value if a target is present.
const value: Object = switch (source) {
.buffer => |b| .{ .integer = if (element_index < b.len) b[element_index] else 0 },
.package => |p| if (element_index < p.len) p[element_index] else .uninitialized,
.string => |s| .{ .integer = if (element_index < s.len) s[element_index] else 0 },
else => .uninitialized,
};
try self.storeTarget(current, frame, value);
return value;
}
fn dereferenceOf(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const o = try self.term(current, frame);
return switch (o) {
.reference => |n| self.invoke(n, &.{}),
else => o,
};
}
// --- control flow -------------------------------------------------------
fn ifElse(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const start = current.i;
const end = @min(start + try current.packageLength(), current.b.len);
const cond = try self.evaluateInteger(current, frame);
if (cond != 0) {
var body = Cursor{ .b = current.b[0..end], .i = current.i };
try self.executeList(&body, frame);
current.i = end;
// Skip a trailing Else.
if (current.peek() == opcode.else_opcode) {
current.i += 1;
const es = current.i;
const ee = @min(es + try current.packageLength(), current.b.len);
current.i = ee;
}
} else {
current.i = end;
if (current.peek() == opcode.else_opcode) {
current.i += 1;
const es = current.i;
const ee = @min(es + try current.packageLength(), current.b.len);
var body = Cursor{ .b = current.b[0..ee], .i = current.i };
try self.executeList(&body, frame);
current.i = ee;
}
}
return .uninitialized;
}
fn whileLoop(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const start = current.i;
const end = @min(start + try current.packageLength(), current.b.len);
const pred_at = current.i;
var guard: usize = 0;
while (guard < 100_000) : (guard += 1) {
var pc = Cursor{ .b = current.b[0..end], .i = pred_at };
const cond = try self.evaluateInteger(&pc, frame);
if (cond == 0) break;
var body = Cursor{ .b = current.b[0..end], .i = pc.i };
try self.executeList(&body, frame);
if (frame.returned) break;
if (frame.broke) {
frame.broke = false;
break;
}
}
current.i = end;
return .uninitialized;
}
// --- store --------------------------------------------------------------
fn store(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const value = try self.term(current, frame);
try self.storeInto(current, frame, value);
return value;
}
/// A Store *target* that may be NullName (no store).
fn storeTarget(self: *Interpreter, current: *Cursor, frame: *Frame, value: Object) Error!void {
if (current.peek() == 0x00) {
current.i += 1; // NullName
return;
}
try self.storeInto(current, frame, value);
}
/// Notify(SuperName, NotifyValue): resolve the named device, evaluate the
/// code, and record the pair for the caller to dispatch. AML control flow
/// continues (Notify returns nothing).
fn notify(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const lead = current.peek() orelse return error.Truncated;
var target: ?*Node = null;
if (isNameStart(lead)) {
const name_path = try current.nameString();
target = self.namespace.resolve(frame.scope, name_path.rooted, name_path.parents, name_path.slice());
} else {
// A non-name SuperName (Local/Arg holding a reference).
const obj = try self.term(current, frame);
if (obj == .reference) target = obj.reference;
}
const code = try self.evaluateInteger(current, frame);
if (target) |node| {
if (self.notify_count < self.notify_queue.len) {
self.notify_queue[self.notify_count] = .{ .node = node, .code = code };
self.notify_count += 1;
}
}
return .uninitialized;
}
/// The Notify events the last `evaluate` produced. Valid until the next
/// `evaluate` clears the queue.
pub fn takeNotifications(self: *Interpreter) []const NotifyEvent {
return self.notify_queue[0..self.notify_count];
}
fn storeInto(self: *Interpreter, current: *Cursor, frame: *Frame, value: Object) Error!void {
const lead = current.peek() orelse return error.Truncated;
if (isNameStart(lead)) {
const name_path = try current.nameString();
const node = self.namespace.resolve(frame.scope, name_path.rooted, name_path.parents, name_path.slice()) orelse return;
if (self.fields.get(node)) |buffer_field| {
try self.writeBufferField(buffer_field, try value.asInteger());
} else if (node.kind == .field) {
try self.writeField(node, try value.asInteger());
} else {
try self.dynamic_overrides.put(self.arena, node, value);
}
return;
}
_ = try current.byte();
switch (lead) {
0x00 => {}, // NullName
opcode.local0_opcode...opcode.local7_opcode => frame.locals[lead - opcode.local0_opcode] = value,
opcode.arg0_opcode...opcode.arg6_opcode => frame.args[lead - opcode.arg0_opcode] = value,
opcode.index_opcode => {
const source = try self.term(current, frame);
const element_index: usize = @intCast(try self.evaluateInteger(current, frame));
switch (source) {
.buffer => |b| if (element_index < b.len) {
b[element_index] = @truncate(try value.asInteger());
},
.package => |p| if (element_index < p.len) {
p[element_index] = value;
},
else => {},
}
},
else => return error.Unsupported,
}
}
// --- CreateField (buffer patching) --------------------------------------
fn createField(self: *Interpreter, current: *Cursor, frame: *Frame, bit_width: u32) Error!Object {
const source = try self.term(current, frame); // source buffer (as a reference or value)
const bit_index = try self.evaluateInteger(current, frame);
const name_path = try current.nameString();
const node = self.namespace.resolve(frame.scope, name_path.rooted, name_path.parents, name_path.slice()) orelse return .uninitialized;
// Bind the new name to the source buffer's node so stores land in it.
const buffer_node: *Node = switch (source) {
.reference => |n| n,
else => return .uninitialized,
};
// Materialise the buffer into `dynamic_overrides` so patches persist and are returned.
if (self.dynamic_overrides.get(buffer_node) == null) {
const value = try self.invoke(buffer_node, &.{});
try self.dynamic_overrides.put(self.arena, buffer_node, value);
}
const byte_off: usize = @intCast(bit_index / 8);
try self.fields.put(self.arena, node, .{ .buffer = buffer_node, .byte_off = byte_off, .bit_width = bit_width });
return .uninitialized;
}
fn writeBufferField(self: *Interpreter, buffer_field: BufferField, value: u64) Error!void {
const obj = self.dynamic_overrides.get(buffer_field.buffer) orelse return;
const bytes = switch (obj) {
.buffer => |b| b,
else => return,
};
const byte_count = (buffer_field.bit_width + 7) / 8;
var k: usize = 0;
while (k < byte_count and buffer_field.byte_off + k < bytes.len) : (k += 1) {
bytes[buffer_field.byte_off + k] = @truncate(value >> @intCast(k * 8));
}
}
// --- OperationRegion field access ---------------------------------------
fn readField(self: *Interpreter, field: *Node) Error!u64 {
const region = field.region orelse return error.Unsupported;
if (field.bit_width == 0 or field.bit_width > 64) return error.Unsupported;
const base = try self.regionBase(region);
const start_byte = base + field.bit_offset / 8;
const shift: u7 = @intCast(field.bit_offset % 8);
const total = @as(usize, shift) + field.bit_width;
const byte_count = (total + 7) / 8;
var raw: u128 = 0;
var k: usize = 0;
while (k < byte_count) : (k += 1) {
raw |= @as(u128, try self.readRegionByte(region.region_space, start_byte + k)) << @intCast(k * 8);
}
const masked = (raw >> shift) & bitMask(field.bit_width);
return @truncate(masked);
}
fn writeField(self: *Interpreter, field: *Node, value: u64) Error!void {
const region = field.region orelse return error.Unsupported;
if (field.bit_width == 0 or field.bit_width > 64) return error.Unsupported;
const base = try self.regionBase(region);
const start_byte = base + field.bit_offset / 8;
const shift: u7 = @intCast(field.bit_offset % 8);
const total = @as(usize, shift) + field.bit_width;
const byte_count = (total + 7) / 8;
// Read-modify-write byte by byte.
var raw: u128 = 0;
var k: usize = 0;
while (k < byte_count) : (k += 1) {
raw |= @as(u128, try self.readRegionByte(region.region_space, start_byte + k)) << @intCast(k * 8);
}
const mask = bitMask(field.bit_width) << shift;
raw = (raw & ~mask) | ((@as(u128, value) << shift) & mask);
k = 0;
while (k < byte_count) : (k += 1) {
try self.writeRegionByte(region.region_space, start_byte + k, @truncate(raw >> @intCast(k * 8)));
}
}
fn regionBase(self: *Interpreter, region: *Node) Error!u64 {
var current = Cursor{ .b = region.region_offset_aml };
var frame = Frame{ .scope = region.parent orelse self.namespace.root };
return (try self.term(&current, &frame)).asInteger();
}
fn readRegionByte(self: *Interpreter, space: u8, address: u64) Error!u8 {
switch (space) {
0 => { // SystemMemory
const virtual = self.hal.mapMmio(address & ~@as(u64, 0xFFF), 0x1000, true);
const p: *align(1) const volatile u8 = @ptrFromInt(virtual + (address & 0xFFF));
return p.*;
},
1 => return @truncate(self.hal.pioRead(1, @intCast(address & 0xFFFF))), // SystemIO
else => return error.Unsupported,
}
}
fn writeRegionByte(self: *Interpreter, space: u8, address: u64, value: u8) Error!void {
switch (space) {
0 => {
const virtual = self.hal.mapMmio(address & ~@as(u64, 0xFFF), 0x1000, true);
const p: *align(1) volatile u8 = @ptrFromInt(virtual + (address & 0xFFF));
p.* = value;
},
1 => self.hal.pioWrite(1, @intCast(address & 0xFFFF), value),
else => return error.Unsupported,
}
}
// --- extended opcodes ---------------------------------------------------
fn ext(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const e = try current.byte();
switch (e) {
opcode.extended.debug => return .uninitialized,
opcode.extended.revision => return .{ .integer = 2 },
opcode.extended.timer => return .{ .integer = 0 },
// Mutex/Event ops are no-ops in this single-threaded evaluator.
opcode.extended.acquire => {
_ = try self.term(current, frame); // mutex SuperName
_ = try current.take(2); // timeout
return .{ .integer = 0 }; // acquired
},
opcode.extended.release, opcode.extended.reset, opcode.extended.signal => {
_ = try self.term(current, frame);
return .uninitialized;
},
opcode.extended.wait => {
_ = try self.term(current, frame);
_ = try self.term(current, frame);
return .{ .integer = 0 };
},
opcode.extended.sleep, opcode.extended.stall => {
_ = try self.term(current, frame);
return .uninitialized;
},
else => return error.Unsupported,
}
}
fn evaluateInteger(self: *Interpreter, current: *Cursor, frame: *Frame) Error!u64 {
return (try self.term(current, frame)).asInteger();
}
};
fn bitMask(width: u32) u128 {
if (width >= 128) return ~@as(u128, 0);
return (@as(u128, 1) << @intCast(width)) - 1;
}
fn isNameStart(b: u8) bool {
return (b >= opcode.name_char_start and b <= opcode.name_char_end) or
b == opcode.name_char_underscore or
b == opcode.root_char or
b == opcode.parent_prefix_char or
b == opcode.dual_name_prefix or
b == opcode.multi_name_prefix;
}