refactor: consolidate RVM instruction variants and clean up VM internals (#651)

Merge the three separate Assert* instructions (AssertNot, AssertCondition,
AssertNotUndefined) into a single `Guard { register, mode }` instruction
with a GuardMode enum. This cuts duplicated match arms across display,
listing, parser, dispatch, and all compiler emit sites.

Drop the unnecessary `#[repr(C)]` from the Instruction enum. It was never
exposed across FFI, so the C-compatible 4-byte discriminant was pure waste.
Without it Rust picks a 1-byte discriminant, shrinking every instruction
from 8 bytes to 6. A new `instruction_size` unit test locks this at 6.

While touching these files, also clean up several long-standing issues:

- Deduplicate the iteration-state setup in loops.rs by extracting a shared
  resolve_iteration_state() helper -- the stack-based and stackless paths
  had near-identical 40-line blocks.
- Collapse the ExitWithSuccess / ExitWithFailure match arms into one.
- In rules.rs, stop cloning Arc<Program> just to borrow a RuleInfo -- clone
  the small RuleInfo struct directly and extract a get_rule_info() helper.
- Move the memory check into dispatch (runs per instruction) and remove the
  now-dead enforce_memory_check() entry-point calls.
- Apply map_or_else style throughout listing.rs for consistency.
This commit is contained in:
Anand Krishnamoorthi
2026-04-01 05:34:33 -05:00
committed by GitHub
parent 1a8fc08773
commit 126cc12eb5
17 changed files with 699 additions and 609 deletions

View File

@@ -3,6 +3,7 @@
#![cfg(feature = "rvm")]
use regorus::languages::rego::compiler::Compiler;
use regorus::rvm::instructions::GuardMode;
use regorus::rvm::Instruction;
use regorus::{Engine, Rc, Value};
use std::collections::BTreeSet;
@@ -128,7 +129,11 @@ fn non_constant_array_is_not_hoisted() {
);
}
// --- AssertEq fusion tests ---
// --- Eq + Guard(Condition) tests ---
//
// The compiler emits `Eq { dest, left, right }` followed by
// `Guard { register: dest, mode: Condition }` for equality checks,
// rather than a fused `AssertEq`.
/// Count occurrences of a specific instruction pattern in the program.
fn count_instructions(
@@ -138,56 +143,76 @@ fn count_instructions(
program.instructions.iter().filter(|i| pred(i)).count()
}
/// Helper: check that the program contains an Eq followed by Guard(Condition).
fn has_eq_guard_condition(program: &regorus::rvm::program::Program) -> bool {
program.instructions.windows(2).any(|w| {
matches!(w[0], Instruction::Eq { .. })
&& matches!(
w[1],
Instruction::Guard {
mode: GuardMode::Condition,
..
}
)
})
}
#[test]
fn equality_check_emits_assert_eq() {
// Assignment `x = 1` followed by `x = 1` triggers EqualityCheck in destructuring.
fn equality_check_emits_eq_guard() {
// Assignment `x = 1` followed by `x = 1` triggers Eq + Guard(Condition).
let program = compile_rule(
r#"
package test
p if { x = 1; x = 1 }
"#,
);
let assert_eq_count =
count_instructions(&program, |i| matches!(i, Instruction::AssertEq { .. }));
assert!(
assert_eq_count > 0,
"expected AssertEq instruction for equality check"
has_eq_guard_condition(&program),
"expected Eq + Guard(Condition) pair for equality check"
);
}
#[test]
fn destructuring_equality_emits_assert_eq() {
fn destructuring_equality_emits_eq_guard() {
let program = compile_rule(
r#"
package test
p if { [1, x] := [1, 2] }
"#,
);
let assert_eq_count =
count_instructions(&program, |i| matches!(i, Instruction::AssertEq { .. }));
assert!(
assert_eq_count > 0,
"expected AssertEq for destructuring equality"
has_eq_guard_condition(&program),
"expected Eq + Guard(Condition) for destructuring equality"
);
}
#[test]
fn not_expr_emits_assert_not() {
fn not_expr_emits_not_plus_guard_condition() {
let program = compile_rule(
r#"
package test
p if { not false }
"#,
);
let assert_not_count =
count_instructions(&program, |i| matches!(i, Instruction::AssertNot { .. }));
assert!(
assert_not_count > 0,
"expected AssertNot for `not` expression"
);
// The Not+AssertCondition pair should be fused — no separate Not instruction.
// The compiler emits Not { dest, operand } + Guard { register: dest, mode: Condition }.
let not_count = count_instructions(&program, |i| matches!(i, Instruction::Not { .. }));
assert_eq!(not_count, 0, "Not should be fused into AssertNot");
assert!(
not_count > 0,
"expected Not instruction for `not` expression"
);
let guard_cond_count = count_instructions(&program, |i| {
matches!(
i,
Instruction::Guard {
mode: GuardMode::Condition,
..
}
)
});
assert!(
guard_cond_count > 0,
"expected Guard(Condition) after Not instruction"
);
}
// --- B-11: early_exit_on_first_success flag tests ---