Repository navigation
The AML interpreter is walked in declaration order and says what a call took - #766
Conversation
…took, and takes its wait limit from the caller Slice I1 of the next ACPI stage, host only: what the server's later slices ask of `toyos-aml` before any of them runs a method on a machine. `Interpreter::walk` reads the namespace from the root down, read-only: each object after the one it is in, siblings in the order their tables declared them, an Alias left out, a predefined scope reported as a scope and descended. The order is the arena's, which has been a stack since the meter's change and so is creation order with no number a node: two passes over it make a first-child and a next-sibling link a node, and nothing sorts or recurses. An entry is a depth, a name and a kind; the path of the entry last returned is kept in one string. A caller that kept a path an entry would hold gigabytes for a namespace nested as deep as the meter admits, which a table can choose. The links and the path are taken from the meter while the walk lasts, so a full interpreter refuses to be walked rather than allocate past its bound. `Interpreter::usage` is what the last load or evaluation took: steps, microseconds asked to wait with the refused request included, and the meter's reading at its end. A refusal has one too. `Interpreter::evaluate_within` takes the wait limit; `evaluate` and a load keep ten seconds, now `pub` as `MAX_WAIT_US`. Not here: stores bounded apart from one evaluation's holdings, which is the owner's decision on a full interpreter and a change of its own. Measured on the real machine's tables, outside the tree: 14 of 14 load in 39,419 steps (the DSDT 32,946), the meter at 2,412,886 bytes over 1,819,038 of heap; the walk returns 6,890 entries, 7 deep at most, and held 55,163 bytes; 217 of 267 parents declare their children against name order; the embedded controller is below the PCI root bridge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
Mutation patches for the body's table, as run at The script, with the test command restated as the one workspace runs it: #!/bin/sh
# Each mutation: checked, applied, the crate's whole suite run, its exit code read, restored.
S=<scratch>
cd <worktree> || exit 1
for p in $S/mut/*.patch; do
n=$(basename "$p" .patch)
git apply --check "$p" || { echo "$n: DOES NOT APPLY"; continue; }
git apply "$p"
cargo test -p toyos-aml --no-fail-fast > "$S/mut/$n.log" 2>&1
code=$?
git apply -R "$p"
echo "$n: EXIT=$code; tree: $(git status --porcelain | wc -l | tr -d ' ') changed"
doneThe runs' exits ( m1-siblings-by-name.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..5db2a3717 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -294,11 +294,13 @@ impl Namespace {
walk.path.reserve_exact(path);
// Newest first, each node goes to the front of its parent's list,
// which leaves the oldest there.
- for (i, n) in self.nodes.iter().enumerate().rev() {
- walk.next[i] = 0;
- if let (Some(p), None) = (n.parent, n.alias) {
- walk.next[i] = walk.first[p as usize];
- walk.first[p as usize] = i as u32;
+ walk.next.fill(0);
+ for (p, n) in self.nodes.iter().enumerate() {
+ for &c in n.children.values().rev() {
+ if self.nodes[c as usize].alias.is_none() {
+ walk.next[c as usize] = walk.first[p];
+ walk.first[p] = c;
+ }
}
}
walk.at = walk.first[0];m2-alias-walked.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..853242bd5 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -296,7 +296,7 @@ impl Namespace {
// which leaves the oldest there.
for (i, n) in self.nodes.iter().enumerate().rev() {
walk.next[i] = 0;
- if let (Some(p), None) = (n.parent, n.alias) {
+ if let Some(p) = n.parent {
walk.next[i] = walk.first[p as usize];
walk.first[p as usize] = i as u32;
}m3-limit-exclusive.patchdiff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs
index e7bbe2675..b65edffce 100644
--- a/userland/acpiserver/aml/src/exec.rs
+++ b/userland/acpiserver/aml/src/exec.rs
@@ -280,7 +280,7 @@ impl<'a> Machine<'a> {
fn wait(&mut self, us: u64) -> Result<(), Error> {
self.waited_us = self.waited_us.saturating_add(us);
- if self.waited_us > self.wait_us {
+ if self.waited_us >= self.wait_us {
return Err(Error::Bound("more time asleep than one evaluation may spend"));
}
Ok(())m4-callers-limit-ignored.patchdiff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs
index 76190c046..fc2f58325 100644
--- a/userland/acpiserver/aml/src/lib.rs
+++ b/userland/acpiserver/aml/src/lib.rs
@@ -392,7 +392,8 @@ impl Interpreter {
let id = self.named(path)?;
let args = args.iter().map(|a| self.object_of(a, w, 0)).collect::<Result<Vec<_>, _>>()?;
let meter = self.meter.clone();
- let mut m = Machine::new(&mut self.ns, host, w, meter.clone(), wait_us);
+ let _ = wait_us;
+ let mut m = Machine::new(&mut self.ns, host, w, meter.clone(), MAX_WAIT_US);
let mut handed = 0;
let r = m.evaluate(id, args).and_then(|o| value_of(&mut m, &meter, &mut handed, o, 0));
// The value is the caller's from here, and no longer this interpreter's.m5-load-unlimited.patchdiff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs
index 76190c046..e4111a837 100644
--- a/userland/acpiserver/aml/src/lib.rs
+++ b/userland/acpiserver/aml/src/lib.rs
@@ -354,7 +354,7 @@ impl Interpreter {
let table = self.meter.bytes(bytes)?;
let (root, made) = (self.ns.root(), self.ns.mark());
let mut f = Frame::new(root, Vec::new(), table.clone(), 0);
- let mut m = Machine::new(&mut self.ns, host, w, self.meter.clone(), MAX_WAIT_US);
+ let mut m = Machine::new(&mut self.ns, host, w, self.meter.clone(), u64::MAX);
let bytes = table.borrow();
let mut c = stream::Cursor::new(&bytes, toyos_acpi::SDT_HEADER_LEN, bytes.len());
let r = m.term_list(&mut f, &mut c).and_then(|flow| match flow {m6-walk-unmetered.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..b7bd5fbdd 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -273,8 +273,8 @@ impl Namespace {
pub(crate) fn walk(&self) -> Result<Walk<'_>, Error> {
let count = self.nodes.len();
let links = 2 * count * core::mem::size_of::<u32>();
- self.meter.take(links)?;
- let mut walk = Walk { ns: self, first: Vec::new(), next: Vec::new(), at: 0, depth: 1, path: String::new(), held: links };
+ let _ = links;
+ let mut walk = Walk { ns: self, first: Vec::new(), next: Vec::new(), at: 0, depth: 1, path: String::new(), held: 0 };
for links in [&mut walk.first, &mut walk.next] {
links.reserve_exact(count);
links.resize(count, 0);m7-walk-kept.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..2da5eb933 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -308,7 +308,7 @@ impl Namespace {
impl Drop for Walk<'_> {
fn drop(&mut self) {
- self.ns.meter.give(self.held);
+ let _ = self.held;
}
}
m8-usage-not-reset.patchdiff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs
index 76190c046..d3d030162 100644
--- a/userland/acpiserver/aml/src/lib.rs
+++ b/userland/acpiserver/aml/src/lib.rs
@@ -381,7 +381,6 @@ impl Interpreter {
/// at most, which the caller names for a method it knows to ask more
/// than [`MAX_WAIT_US`].
pub fn evaluate_within(&mut self, host: &mut dyn Host, path: &str, args: &[Value], wait_us: u64) -> Result<Value, Error> {
- self.last = Usage::default();
let r = self.evaluate_in(host, path, args, wait_us);
self.last.live = self.meter.live();
rm9-usage-of-a-value-only.patchdiff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs
index e7bbe2675..e685d0cca 100644
--- a/userland/acpiserver/aml/src/exec.rs
+++ b/userland/acpiserver/aml/src/exec.rs
@@ -243,7 +243,9 @@ impl<'a> Machine<'a> {
/// control method cannot exit while still holding ownership of a Mutex").
/// What it took goes to `took`, whichever way it ends.
pub(crate) fn finish<T>(mut self, r: Result<T, Error>, took: &mut Usage) -> Result<T, Error> {
- (took.steps, took.waited_us) = (self.steps, self.waited_us);
+ if r.is_ok() {
+ (took.steps, took.waited_us) = (self.steps, self.waited_us);
+ }
let held = !self.held.is_empty();
for m in self.held.drain(..) {
m.held.set(0);m10-live-before-the-call.patchdiff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs
index 76190c046..40ace2918 100644
--- a/userland/acpiserver/aml/src/lib.rs
+++ b/userland/acpiserver/aml/src/lib.rs
@@ -382,8 +382,8 @@ impl Interpreter {
/// than [`MAX_WAIT_US`].
pub fn evaluate_within(&mut self, host: &mut dyn Host, path: &str, args: &[Value], wait_us: u64) -> Result<Value, Error> {
self.last = Usage::default();
- let r = self.evaluate_in(host, path, args, wait_us);
self.last.live = self.meter.live();
+ let r = self.evaluate_in(host, path, args, wait_us);
r
}
m11-scope-as-device.patchdiff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs
index 76190c046..44030829c 100644
--- a/userland/acpiserver/aml/src/lib.rs
+++ b/userland/acpiserver/aml/src/lib.rs
@@ -278,7 +278,7 @@ impl Iterator for Walk<'_> {
Object::Field(_) => Kind::FieldUnit,
Object::BufField(_) => Kind::BufferField,
Object::Ref(_) | Object::Lazy(_) => Kind::Reference,
- Object::Scope => Kind::Scope,
+ Object::Scope => Kind::Device,
Object::Device => Kind::Device,
Object::Processor => Kind::Processor,
Object::ThermalZone => Kind::ThermalZone, |
|
Review of Net lines ( BLOCKER
NOTE
What the brief asked to be held
Clean room: the diff and its comments cite the specification alone. Checks the landing rests on: SEND BACK |
Review round 1 of #766. `Interpreter::evaluate_within`, `Machine::wait_us` and the `pub` on `MAX_WAIT_US` are deleted: nothing called them, and no reading shows a method that asks for more than ten seconds. The limit is the constant again, for a load and an evaluation alike, and the track issue records which slice brings a caller's limit back and on what reading. The wait test keeps the exact-at-ten-seconds and load cases. `a_walk_takes_its_links_and_its_deepest_path_from_the_bound` leaves a namespace nested 12,000 deep exactly the bytes its walk takes, eight a node and five a level, and then one byte less: the first is walked, the second refused with the links it had taken given back. Nothing before it could fail on the path's charge, on the refusal at it, or on the links being charged as one vector's. `usage_is_each_calls_own_and_a_refusals_too` now reads a load refused before anything ran, a second DSDT after a load that took steps and time: zero of both, which holds `load`'s reset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
Review round 1, answered at the script#!/bin/sh
# Slice I1, review round 1: wt/toyos-amli1 at 14bfcea31 (a6cd8521b, #764's head 52ef7a10f merged,
# and the round's commit). Host tests of one crate and of the server; every log is under $R.
R=<scratch>
cd <worktree> || { echo "worktree EXIT=1"; exit 1; }
: > "$R/exits"
say() { echo "$1" | tee -a "$R/exits"; }
say "head $(git rev-parse HEAD); tree: $(git status --porcelain --ignore-submodules=none | wc -l | tr -d ' ') changed"
cargo metadata --locked --format-version 1 > "$R/metadata.json" 2> "$R/metadata.log"
say "metadata EXIT=$?"
cargo test -p toyos-aml > "$R/aml.log" 2>&1
say "aml EXIT=$?"
cargo test --manifest-path userland/acpiserver/Cargo.toml > "$R/acpiserver.log" 2>&1
say "acpiserver EXIT=$?"
# One mutation: checked, applied, its named test run alone, restored. RED only where the log
# shows that test failing; a patch that does not build is no red.
mutate() {
n=$1; bin=$2; t=$3; p="$R/mut/$n.patch"
git apply --check "$p" || { say "$n DOES NOT APPLY EXIT=1"; return; }
git apply "$p"
cargo test -p toyos-aml --test "$bin" -- --exact "$t" > "$R/mut/$n.log" 2>&1
code=$?
git apply -R "$p"
if grep -q "^test $t \.\.\. FAILED" "$R/mut/$n.log"; then v=RED
elif [ $code -eq 0 ]; then v=GREEN
else v="NO TEST RAN RED (a build failure?)"; fi
say "$n ($bin::$t) EXIT=$code $v; tree: $(git status --porcelain --ignore-submodules=none | wc -l | tr -d ' ') changed"
}
mutate m1-siblings-by-name walk siblings_are_read_in_the_order_they_were_declared
mutate m2-alias-walked walk an_alias_is_not_walked
mutate m3-limit-exclusive hostile the_wait_limit_is_exact_and_a_loads_too
mutate m6-walk-unmetered heap a_walk_is_held_against_the_bound_while_it_lasts
mutate m7-walk-kept heap a_walk_is_held_against_the_bound_while_it_lasts
mutate m8-usage-not-reset hostile usage_is_each_calls_own_and_a_refusals_too
mutate m9-usage-of-a-value-only hostile usage_is_each_calls_own_and_a_refusals_too
mutate m10-live-before-the-call hostile usage_is_each_calls_own_and_a_refusals_too
mutate m11-scope-as-device walk every_object_is_walked_as_what_it_is
mutate m12-path-not-charged walk a_walk_takes_its_links_and_its_deepest_path_from_the_bound
mutate m13-load-usage-not-reset hostile usage_is_each_calls_own_and_a_refusals_too
mutate m14-links-charged-as-one-vector walk a_walk_takes_its_links_and_its_deepest_path_from_the_bound
mutate m15-links-kept-by-a-walk-refused-for-its-path walk a_walk_takes_its_links_and_its_deepest_path_from_the_bound
say "done; head $(git rev-parse HEAD); tree: $(git status --porcelain --ignore-submodules=none | wc -l | tr -d ' ') changed"m1-siblings-by-name.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..5db2a3717 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -294,11 +294,13 @@ impl Namespace {
walk.path.reserve_exact(path);
// Newest first, each node goes to the front of its parent's list,
// which leaves the oldest there.
- for (i, n) in self.nodes.iter().enumerate().rev() {
- walk.next[i] = 0;
- if let (Some(p), None) = (n.parent, n.alias) {
- walk.next[i] = walk.first[p as usize];
- walk.first[p as usize] = i as u32;
+ walk.next.fill(0);
+ for (p, n) in self.nodes.iter().enumerate() {
+ for &c in n.children.values().rev() {
+ if self.nodes[c as usize].alias.is_none() {
+ walk.next[c as usize] = walk.first[p];
+ walk.first[p] = c;
+ }
}
}
walk.at = walk.first[0];m2-alias-walked.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..853242bd5 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -296,7 +296,7 @@ impl Namespace {
// which leaves the oldest there.
for (i, n) in self.nodes.iter().enumerate().rev() {
walk.next[i] = 0;
- if let (Some(p), None) = (n.parent, n.alias) {
+ if let Some(p) = n.parent {
walk.next[i] = walk.first[p as usize];
walk.first[p as usize] = i as u32;
}m3-limit-exclusive.patchdiff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs
index 20143bcb6..0f0dbf9e6 100644
--- a/userland/acpiserver/aml/src/exec.rs
+++ b/userland/acpiserver/aml/src/exec.rs
@@ -278,7 +278,7 @@ impl<'a> Machine<'a> {
fn wait(&mut self, us: u64) -> Result<(), Error> {
self.waited_us = self.waited_us.saturating_add(us);
- if self.waited_us > MAX_WAIT_US {
+ if self.waited_us >= MAX_WAIT_US {
return Err(Error::Bound("more time asleep than one evaluation may spend"));
}
Ok(())m6-walk-unmetered.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..b7bd5fbdd 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -273,8 +273,8 @@ impl Namespace {
pub(crate) fn walk(&self) -> Result<Walk<'_>, Error> {
let count = self.nodes.len();
let links = 2 * count * core::mem::size_of::<u32>();
- self.meter.take(links)?;
- let mut walk = Walk { ns: self, first: Vec::new(), next: Vec::new(), at: 0, depth: 1, path: String::new(), held: links };
+ let _ = links;
+ let mut walk = Walk { ns: self, first: Vec::new(), next: Vec::new(), at: 0, depth: 1, path: String::new(), held: 0 };
for links in [&mut walk.first, &mut walk.next] {
links.reserve_exact(count);
links.resize(count, 0);m7-walk-kept.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..2da5eb933 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -308,7 +308,7 @@ impl Namespace {
impl Drop for Walk<'_> {
fn drop(&mut self) {
- self.ns.meter.give(self.held);
+ let _ = self.held;
}
}
m8-usage-not-reset.patchdiff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs
index f75947a35..ad07e3e4b 100644
--- a/userland/acpiserver/aml/src/lib.rs
+++ b/userland/acpiserver/aml/src/lib.rs
@@ -371,7 +371,6 @@ impl Interpreter {
/// Evaluates the object at an absolute path, written `\_SB.PCI0._STA`: a
/// method is invoked with `args`, anything else is its value.
pub fn evaluate(&mut self, host: &mut dyn Host, path: &str, args: &[Value]) -> Result<Value, Error> {
- self.last = Usage::default();
let r = self.evaluate_in(host, path, args);
self.last.live = self.meter.live();
rm9-usage-of-a-value-only.patchdiff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs
index e7bbe2675..e685d0cca 100644
--- a/userland/acpiserver/aml/src/exec.rs
+++ b/userland/acpiserver/aml/src/exec.rs
@@ -243,7 +243,9 @@ impl<'a> Machine<'a> {
/// control method cannot exit while still holding ownership of a Mutex").
/// What it took goes to `took`, whichever way it ends.
pub(crate) fn finish<T>(mut self, r: Result<T, Error>, took: &mut Usage) -> Result<T, Error> {
- (took.steps, took.waited_us) = (self.steps, self.waited_us);
+ if r.is_ok() {
+ (took.steps, took.waited_us) = (self.steps, self.waited_us);
+ }
let held = !self.held.is_empty();
for m in self.held.drain(..) {
m.held.set(0);m10-live-before-the-call.patchdiff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs
index f75947a35..3de881460 100644
--- a/userland/acpiserver/aml/src/lib.rs
+++ b/userland/acpiserver/aml/src/lib.rs
@@ -372,8 +372,8 @@ impl Interpreter {
/// method is invoked with `args`, anything else is its value.
pub fn evaluate(&mut self, host: &mut dyn Host, path: &str, args: &[Value]) -> Result<Value, Error> {
self.last = Usage::default();
- let r = self.evaluate_in(host, path, args);
self.last.live = self.meter.live();
+ let r = self.evaluate_in(host, path, args);
r
}
m11-scope-as-device.patchdiff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs
index 76190c046..44030829c 100644
--- a/userland/acpiserver/aml/src/lib.rs
+++ b/userland/acpiserver/aml/src/lib.rs
@@ -278,7 +278,7 @@ impl Iterator for Walk<'_> {
Object::Field(_) => Kind::FieldUnit,
Object::BufField(_) => Kind::BufferField,
Object::Ref(_) | Object::Lazy(_) => Kind::Reference,
- Object::Scope => Kind::Scope,
+ Object::Scope => Kind::Device,
Object::Device => Kind::Device,
Object::Processor => Kind::Processor,
Object::ThermalZone => Kind::ThermalZone,m12-path-not-charged.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..54fd7f979 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -289,8 +289,6 @@ impl Namespace {
}
}
let path = path_len(deepest as usize);
- self.meter.take(path)?;
- walk.held += path;
walk.path.reserve_exact(path);
// Newest first, each node goes to the front of its parent's list,
// which leaves the oldest there.m13-load-usage-not-reset.patchdiff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs
index f75947a35..19cf45dc6 100644
--- a/userland/acpiserver/aml/src/lib.rs
+++ b/userland/acpiserver/aml/src/lib.rs
@@ -333,7 +333,6 @@ impl Interpreter {
/// Loads a DSDT or SSDT (§5.4.2): the DSDT first, then each SSDT. The
/// table's header, length and checksum are [`toyos_acpi::Table::open`]'s.
pub fn load<P: toyos_acpi::Phys>(&mut self, host: &mut dyn Host, table: &toyos_acpi::Table<P>) -> Result<(), Error> {
- self.last = Usage::default();
let r = self.load_in(host, table);
self.last.live = self.meter.live();
rm14-links-charged-as-one-vector.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..55113af4d 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -272,7 +272,7 @@ const fn path_len(depth: usize) -> usize {
impl Namespace {
pub(crate) fn walk(&self) -> Result<Walk<'_>, Error> {
let count = self.nodes.len();
- let links = 2 * count * core::mem::size_of::<u32>();
+ let links = count * core::mem::size_of::<u32>();
self.meter.take(links)?;
let mut walk = Walk { ns: self, first: Vec::new(), next: Vec::new(), at: 0, depth: 1, path: String::new(), held: links };
for links in [&mut walk.first, &mut walk.next] {m15-links-kept-by-a-walk-refused-for-its-path.patchdiff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs
index cf888b4c1..7756ac081 100644
--- a/userland/acpiserver/aml/src/namespace.rs
+++ b/userland/acpiserver/aml/src/namespace.rs
@@ -274,7 +274,7 @@ impl Namespace {
let count = self.nodes.len();
let links = 2 * count * core::mem::size_of::<u32>();
self.meter.take(links)?;
- let mut walk = Walk { ns: self, first: Vec::new(), next: Vec::new(), at: 0, depth: 1, path: String::new(), held: links };
+ let mut walk = Walk { ns: self, first: Vec::new(), next: Vec::new(), at: 0, depth: 1, path: String::new(), held: 0 };
for links in [&mut walk.first, &mut walk.next] {
links.reserve_exact(count);
links.resize(count, 0);
@@ -290,7 +290,7 @@ impl Namespace {
}
let path = path_len(deepest as usize);
self.meter.take(path)?;
- walk.held += path;
+ walk.held = links + path;
walk.path.reserve_exact(path);
// Newest first, each node goes to the front of its parent's list,
// which leaves the oldest there. |
|
Review of Net lines, the branch's own ( Round 1's BLOCKERs
Round 1's NOTEsAll five closed: the second-DSDT assertion is at What changed since
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Review round 1 of #766. `Interpreter::evaluate_within`, `Machine::wait_us` and the `pub` on `MAX_WAIT_US` are deleted: nothing called them, and no reading shows a method that asks for more than ten seconds. The limit is the constant again, for a load and an evaluation alike, and the track issue records which slice brings a caller's limit back and on what reading. The wait test keeps the exact-at-ten-seconds and load cases. `a_walk_takes_its_links_and_its_deepest_path_from_the_bound` leaves a namespace nested 12,000 deep exactly the bytes its walk takes, eight a node and five a level, and then one byte less: the first is walked, the second refused with the links it had taken given back. Nothing before it could fail on the path's charge, on the refusal at it, or on the links being charged as one vector's. `usage_is_each_calls_own_and_a_refusals_too` now reads a load refused before anything ran, a second DSDT after a load that took steps and time: zero of both, which holds `load`'s reset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
… wait limit The walk's bullet claimed the T14's initialisation does not hang on sibling order. What was read is less: 45 `_INI`, a dry run under mostly zero answers that found two of them writing, and those two parent and child. The bullet now says that and what nothing has read. A new bullet in the interpreter stage's list records that ten seconds is the only wait limit and which slice, on what reading, brings a caller's. The lines on `main` owned by the power-off stage keep that owner: this branch lands before the slice that retires that stage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
Review of Net lines ( Round 2's BLOCKERs
What round 3 was to read
BLOCKER
Nothing else is open, and neither of these is the implementer's to answer: no change to the branch is asked. They close by reading, not by another round. What the landing rests onRun 37768135128, or a later
All three green at that head: both BLOCKERs are closed by those readings, the body's two "no result yet" rows are replaced by the run's number, jobs and exits, and the merge is armed. Any of them red, cancelled or skipped: SEND BACK, with that job's log. NOTE
LAND AFTER NAMED CHANGES |
CI's clippy (rust 1.99) raises manual_is_multiple_of on `left % 200 == 0` in tests/walk.rs's `deeper`; the development machine's older clippy does not. `left` is a usize and the divisor a non-zero literal, so the value is the same. It is the only `%` the branch adds, and the branch adds no `wrapping_neg`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
The track issue conflicted in the meter paragraph, which #766 rewrote with the T14's reading and this branch had re-owned. It takes main's text whole, its two new bullets included, with the one owner line this branch changed: "the power-off stage" is this slice, whose stage paragraph the branch deletes, so the meter's owner reads "this stage". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Slice I1 of the next ACPI stage: what the server's later slices ask of the AML interpreter (
userland/acpiserver/aml) before any of them runs a method on a machine:Interpreter::walkandInterpreter::usage. No kernel change and no syscall. The server calls neither yet, butloadandevaluatechanged under it, and it runs both at every boot.Head
9cf510ce1: the slice's commit26b40a452,origin/main1084ddc9amerged (a6cd8521b),origin/maind78350c71(#761) merged (c20b02f3c, no conflict), review round 1's answer (885d8d327), the track issue's two bullets (bd7b86547) and one line of a test helper that CI's clippy refused (9cf510ce1). The branch carries no commit of another pull request: its diff againstmainis its own.It lands before #764 (slice S2), on which nothing in its code depends. Review round 2 sent back a head that had #764 merged in; that merge is out, and the branch was rebuilt from
a6cd8521b. Checked at the new head:git diff 14bfcea31 bd7b86547 -- userland/acpiserver/aml/src userland/acpiserver/aml/tests/heap.rs userland/acpiserver/aml/tests/hostile.rs userland/acpiserver/aml/tests/walk.rs: empty, 0 bytes. The code round 2 reviewed is the code here.git diff origin/main...bd7b86547 --stat: eight files, +703 −29, the track issue and underuserland/acpiserver/amlsrc/exec.rs,src/lib.rs,src/namespace.rs,src/object.rs,tests/heap.rs,tests/hostile.rs,tests/walk.rs. Nothing of Power-off goes through the ACPI server: the claim's holder supplies the sleep type, and the kernel's S5 byte scan is deleted #764's.git merge-base --is-ancestor f2a90a291 bd7b86547: exit 1, Power-off goes through the ACPI server: the claim's holder supplies the sleep type, and the kernel's S5 byte scan is deleted #764's merge is no ancestor.In the track issue the walk's bullet and the wait limit's are this branch's, written onto
main's text; the paragraph on the meter carries this branch's reading of the real tables undermain's "Owner: the power-off stage", and the two other lines of that owner are untouched. The wait bullet's "Owner: this stage" is the interpreter stage, in whose list it stands. #764 will meet the meter paragraph from the other side: it changes that owner to "this stage" where this branch changed the paragraph's reading, so its next merge ofmainconflicts there and is resolved by keeping both.What changed, per decision
Interpreter::walk: the namespace read from the root down, in declaration order. Each object after the one it is in, siblings in the order their tables declared them, a later table's after an earlier one's.Kind::Scope, descended and reported as what it is; the caller decides what is a device. Nothing here evaluates_STAor_INI, or reads §6.5.1.Walk::pathis the path of the entry last returned, in the textevaluatetakes. A caller leaves a subtree out by skipping to the next entry no deeper than its top.walkreturnsResult) rather than allocate pastMAX_LIVE.Interpreter::usage: what the last load or evaluation took. Steps, microseconds asked to Sleep, Stall and Wait (the request the limit refused included), and the meter's reading at its end. A refused call has one; a call refused before anything ran reads zero steps and zero time, whatever the call before it took.Interpreter::evaluate_within,Machine::wait_uswith its constructor argument, and thepubonMAX_WAIT_USare deleted: nothing called them, the 14 real tables asked for 0 µs, and no reading shows a method refused at ten seconds. A caller's limit also removed the one ceiling on what hostile AML may ask ofHost::sleep. It comes back in the slice that carries a reading of a method refused at ten seconds; the track issue's bullet "An evaluation may ask to wait ten seconds, and no caller names another limit" says so, with owner and exit.Error::Bridge, onmainsince the server's load). Nothing to do.What is left of I1
Stores are not bounded apart from one evaluation's holdings. A method that fills the interpreter still leaves every later evaluation that must hold something refused. The track's bullet "A refused evaluation keeps what it stored" keeps its exit and
main's owner, the power-off stage. The caller's wait limit is the second thing left, recorded in the bullet named above.Measured on the real machine's tables
Run outside the tree by a scratch harness that depends on the crate by path, at
26b40a452:cargo run --release -- <the tables' directory> <the PCI reading taken under Linux>(aarch64 macOS, load averages near 40, no figure a timing). The log ismeasure.login the first round's scratchpad; it records no exit code, and runs to its last line. The DSDT's one memory word answered, every PCI function as Linux reads it, everything else zero. Its lines, without the two that carry a local path (cargo'sCompilingof the harness and itsRunning) and the two on the memory word:MAX_LIVE) over 1,819,038 bytes of heap by a counting allocator._INI, all methods, and under these answers, every read but the one word and the functions' headers zero, a dry run found two of them writing anything, that bridge's and that controller's. What the 45 do under the machine's own answers nothing has read, and the issue's bullet now says only this.The arena's capacity is still read by nothing: 8,192 slots is its doubling, not a reading, and the issue's exit says so. The sources of the crate have changed since
26b40a452only by round 1's deletion and by nothing the harness's figures rest on; the harness was not rerun at this head.Checks of high-risk code (a trust boundary)
The namespace is built from firmware's bytes, so the walk is bounded by what the meter admits and tested against what a table can choose:
siblings_are_read_in_the_order_they_were_declaredan_alias_is_not_walkedevery_object_is_walked_as_what_it_isa_namespace_nested_deep_is_walked_wholea_parent_of_thousands_is_walked_in_their_ordera_walk_is_held_against_the_bound_while_it_lasts8 × nodes + 5 × depthbytes and then one lessa_walk_takes_its_links_and_its_deepest_path_from_the_bound(new in round 1)the_wait_limit_is_exact_and_a_loads_toousage_is_each_calls_own_and_a_refusals_tooThe byte-exact pair. The new test stores buffers through a method until the interpreter has exactly the walk's bytes left, read from
usage().liveandMAX_LIVE: it is walked, and holds afterwards what it held before. With one byte less, which is room for the links and for all of the path but a byte,walk()isErr(Bound)andusage().liveafter the next evaluation is what it was. The reviewer's sizing, room between one link vector's bytes and two, cannot be built at this nesting: one vector is 48,048 bytes and the path 60,000, so a walk charged one vector is refused for its path in all of that range. The pair pins the sum to the byte instead, and m14 is the one-vector patch, red on it.Independent oracle: for the walk's heap, the allocator's own count of requested bytes, which shares nothing with the meter's arithmetic (
heap.rsasserts8 * nodes + 5held and zero after). For order, the ACPI specification gives none to hold the walk against: declaration order is the design's choice, and the tests state it.Mutations, all rerun at
bd7b86547, againstmain'stoyos-acpi. Each is a checked patch (git apply --check), applied, its named test run alone (cargo test -p toyos-aml --test <binary> -- --exact <test>), reverted, the tree clean after each. RED is the log'stest <name> ... FAILEDline, not the exit alone, so a patch that did not build would not count; every log showsFinishedand one test run. The thirteen patches are byte for byte round 1's, posted in that round's comment, and each passesgit apply --checkat this head.siblings_are_read_in_the_order_they_were_declaredan_alias_is_not_walked>=)the_wait_limit_is_exact_and_a_loads_too,hostile.rs:546: the evaluation asking exactly ten seconds is refuseda_walk_is_held_against_the_bound_while_it_lastsevaluate's usage not resetusage_is_each_calls_own_and_a_refusals_tooevery_object_is_walked_as_what_it_isa_walk_takes_its_links_and_its_deepest_path_from_the_bound,walk.rs:281:left: Ok(12011),right: Err(Bound(..))load's usage not reset (the reviewer's NOTE)usage_is_each_calls_own_and_a_refusals_too,hostile.rs:617:left: Usage { steps: 106, waited_us: 7000, live: 3540 }walk.rs:281, walked where it must be refusedwalk.rs:283: 16,717,217 held against 16,621,121, the 96,096 bytes of 12,012 nodes' linksm4 and m5 are gone with the code they mutated. m4 ignored the caller's limit and m5 gave a load
u64::MAXthrough the constructor argument; both are deleted, and with one constant inMachine::waita load has no limit of its own to mutate. The load case stays in the wait test.Gates
cargo metadata --lockedbd7b86547cargo test -p toyos-amlbd7b86547evaluate27,heap19,hostile24,namespace18,regions18,walk6 passed, the library's own 0; 0 failed in eachcargo test --manifest-path userland/acpiserver/Cargo.tomlbd7b86547acpiserveragainst the changed crate andmain'stoyos-acpi; 26 passed, 0 failedbd7b86547cargo test -p toyos-aml9cf510ce1bd7b86547with the one line uncommitted;git show 9cf510ce1is that diff, byte for byte9cf510ce1a_walk_takes_its_links_and_its_deepest_path_from_the_bound, the test of theirs that calls the helper the line is in; m12 atwalk.rs:281,left: Ok(12011), as before. Same tree as the row above, 19 s for bothhost(cargo run -- --ci host)bd7b86547[ci] Host: 1 of 78 step(s) red. The step iscargo clippy --manifest-path userland/acpiserver/aml/Cargo.toml --all-targets -- $ADOPTED -D warnings, and its one errormanual_is_multiple_ofattests/walk.rs:201,left % 200 == 0, raised by CI's clippy (rust 1.99) and not by the development machine's older onehost9cf510ce1[ci] Host: 78 step(s), all green. The line is nowleft.is_multiple_of(200),leftausize, the value unchanged; this run is the first whole read of the crate by CI's clippy, and it is cleantoolchain / build,guest / suite9cf510ce1PASS acpi_power_button (3s),test result: ok. 32 passed, 32 total,[ci] Guest: 5 step(s), all greentoolchain / buildbd7b86547guest / suitebd7b86547PASS acpi_power_button.9cf510ce1differs from that head in one file, the host testuserland/acpiserver/aml/tests/walk.rs; the suite has not run at9cf510ce1The three green gates at
bd7b86547and the thirteen mutations were run by the orchestrator from round 2's request script, round 1's with its log directory changed, 37 s in all. The measurements at14bfcea31are not carried: that head compiled #764'stoyos-acpi.Unsure of
Kind::Methodto carry its argument count.evaluaterefuses a wrong count by name, so nothing needs it yet.Kind::Referencecovers both a reference a Name holds and a package element's unresolved name; the second cannot be a named object by my reading ofdefine's callers, and the match is exhaustive rather than trusting that.\_S5evaluates in zero steps on the real tables (a package's value is read without a term).Growth
git diff --shortstat origin/main...9cf510ce1: 8 files, +703 −29, the last commit one line for one. Productionsrc/: +253 −17. Tests: +416. Issue text: +34 −12. Round 1 moved production from a net 248 lines to 236 and tests from 371 to 416. No dependency added.🤖 Generated with Claude Code
https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A