-
Notifications
You must be signed in to change notification settings - Fork 19
majit dynasm: partition the parallel-move keys, fault outside the operand contract, and fix the field-width witness #1256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
871c4cd
8dd925c
dc5e6a8
255002c
f172c9f
371e660
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,8 +29,22 @@ use majit_metainterp::{Assembler, JitDriver}; | |
| struct PointerFieldStack { | ||
| data: *mut i64, | ||
| size: usize, | ||
| /// An eight-byte field that is not `Copy`, named in the write set below but | ||
| /// declared in none of the field maps, so it takes the undeclared default | ||
| /// and the macro emits its width witness over it. | ||
| /// | ||
| /// A witness written as `|s| s.field` returns the field by value and so | ||
| /// moves out of a shared borrow, which only compiles when the field is | ||
| /// `Copy`. This member is the standing check that it is not written that | ||
| /// way: if it regresses, this crate stops compiling rather than quietly | ||
| /// refusing a struct that has nothing wrong with it. | ||
| generation: Generation, | ||
| } | ||
|
|
||
| /// Deliberately not `Copy` and deliberately eight bytes. | ||
| #[repr(transparent)] | ||
| struct Generation(i64); | ||
|
|
||
| /// An opaque in-place mutator. The body is irrelevant — the JIT never looks | ||
| /// inside a residual — but it has to exist for the concrete path. | ||
| extern "C" fn jit_scramble_pointer_field(stack: usize) { | ||
|
|
@@ -40,6 +54,7 @@ extern "C" fn jit_scramble_pointer_field(stack: usize) { | |
| } | ||
| unsafe { | ||
| (*stack).size = (*stack).size; | ||
| (*stack).generation = Generation((*stack).generation.0 + 1); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -61,7 +76,13 @@ struct PointerFieldState { | |
| array_fields = { PointerFieldStack::data => i64 }, | ||
| calls = { jit_scramble_pointer_field => residual_void }, | ||
| residual_writes = { | ||
| // `size` is here only so the scalar control below has a descr to read. | ||
| // Naming a field in a write-set layout is what mints its descr, and | ||
| // without one the control skips: it asserted nothing, which is the | ||
| // failure mode it exists to rule out for the pointer field. | ||
| sel.data => [jit_scramble_pointer_field], | ||
| sel.size => [jit_scramble_pointer_field], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this test target is compiled for supported Useful? React with 👍 / 👎. |
||
| sel.generation => [jit_scramble_pointer_field], | ||
| }, | ||
| )] | ||
| #[allow(unused_assignments, unused_variables)] | ||
|
|
@@ -165,15 +186,19 @@ fn a_scalar_field_of_the_same_struct_is_still_a_scalar() { | |
| .get(&majit_ir::descr::LLType::Struct(type_id)) | ||
| .and_then(|fields| fields.get("size")) | ||
| .cloned(); | ||
| // `size` is not named by any declaration or access here, so the control is | ||
| // only meaningful if something registered it. Skipping when nothing did is | ||
| // honest; asserting on an absent slot would pass for the wrong reason. | ||
| if let Some(descr) = size_field { | ||
| assert_eq!( | ||
| descr.field_type(), | ||
| majit_ir::Type::Int, | ||
| "`size` is a scalar; only the field a pointer declaration names may \ | ||
| become a Ref", | ||
| ); | ||
| } | ||
| // Require the slot rather than skipping when it is absent. This test read | ||
| // `if let Some(..)` and `size` was in no declaration, so the assertion below | ||
| // never ran once — a control that does not execute rules nothing out, which | ||
| // is exactly what it was written to prevent for the pointer field. The | ||
| // fixture now names `size` in its write set so the descr exists. | ||
| let descr = size_field.expect( | ||
| "`size` must be registered for this control to assert anything; the \ | ||
| fixture's `residual_writes` names it", | ||
| ); | ||
| assert_eq!( | ||
| descr.field_type(), | ||
| majit_ir::Type::Int, | ||
| "`size` is a scalar; only the field a pointer declaration names may \ | ||
| become a Ref", | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a parallel move contains a
Loc::Ebpwith a negative displacement, this check disappears in release builds even thoughRawEbpLoc::newaccepts everyi32and the newly documented operand contract includes bare EBP locations. For example, offset-4097maps through!offsetto4096, exactly the key assigned to GPR 0, so two destinations trigger the duplicate-key panic and source/destination dependencies can alias. Preserve the upstream backend-specific key shapes (x86 keys raw EBP displacements while constrainingFrameLoc; AArch64 keys stack positions) or use a tagged key rather than relying on this debug-only nonnegativity assumption.AGENTS.md reference: AGENTS.md:L288-L290
Useful? React with 👍 / 👎.