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
+17 -29
View File
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::instructions::{Instruction, LiteralOrRegister};
use crate::rvm::instructions::{GuardMode, Instruction, LiteralOrRegister};
use crate::rvm::program::Program;
use crate::value::Value;
use alloc::collections::BTreeSet;
@@ -26,6 +26,7 @@ impl RegoVM {
program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
self.memory_check()?;
self.execute_load_and_move(program, instruction)
}
@@ -317,9 +318,6 @@ impl RegoVM {
}
Not { dest, operand } => {
let operand_value = self.get_register(operand)?;
// In Rego, `not expr` succeeds when `expr` is undefined or false,
// and fails for any other defined value (including non-booleans like 42).
let negated = match *operand_value {
Value::Undefined => true,
Value::Bool(b) => !b,
@@ -335,35 +333,24 @@ impl RegoVM {
self.handle_condition(passed)?;
Ok(InstructionOutcome::Continue)
}
AssertNot { operand } => {
let value = self.get_register(operand)?;
let passed = match *value {
Value::Undefined => true,
Value::Bool(b) => !b,
_ => false,
Guard { register, mode } => {
let value = self.get_register(register)?;
let passed = match mode {
GuardMode::Not => match *value {
Value::Undefined => true,
Value::Bool(b) => !b,
_ => false,
},
GuardMode::Condition => match *value {
Value::Bool(b) => b,
Value::Undefined => false,
_ => true,
},
GuardMode::NotUndefined => !matches!(value, Value::Undefined),
};
self.handle_condition(passed)?;
Ok(InstructionOutcome::Continue)
}
AssertCondition { condition } => {
let value = self.get_register(condition)?;
let condition_result = match *value {
Value::Bool(b) => b,
Value::Undefined => false,
_ => true,
};
self.handle_condition(condition_result)?;
Ok(InstructionOutcome::Continue)
}
AssertNotUndefined { register } => {
let value = self.get_register(register)?;
let is_undefined = matches!(value, Value::Undefined);
self.handle_condition(!is_undefined)?;
Ok(InstructionOutcome::Continue)
}
other => self.execute_call_instruction(program, other),
}
}
@@ -747,6 +734,7 @@ impl RegoVM {
available: loop_params_len,
})?;
let mode = loop_params.mode;
let params = LoopParams {
collection: loop_params.collection,
key_reg: loop_params.key_reg,