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

@@ -7,7 +7,7 @@ use crate::compiler::destructuring_planner::plans::{
AssignmentPlan, BindingPlan, DestructuringPlan, WildcardSide,
};
use crate::lexer::Span;
use crate::rvm::instructions::Instruction;
use crate::rvm::instructions::{GuardMode, Instruction};
use crate::value::Value;
use anyhow::{bail, Result};
@@ -91,19 +91,6 @@ impl<'a> Compiler<'a> {
AssignmentPlan::EqualityCheck { lhs_expr, rhs_expr } => {
let lhs_reg = self.compile_rego_expr_with_span(lhs_expr, lhs_expr.span(), false)?;
let rhs_reg = self.compile_rego_expr_with_span(rhs_expr, rhs_expr.span(), false)?;
if !self.soft_assert_mode {
// AssertEq handles the equality assertion inline; the returned
// register is not used as a boolean by the caller — it is the
// expression's "result register" for potential downstream use.
self.emit_instruction(
Instruction::AssertEq {
left: lhs_reg,
right: rhs_reg,
},
span,
);
return Ok(lhs_reg);
}
let dest = self.alloc_register();
self.emit_instruction(
Instruction::Eq {
@@ -113,6 +100,15 @@ impl<'a> Compiler<'a> {
},
span,
);
if !self.soft_assert_mode {
self.emit_instruction(
Instruction::Guard {
register: dest,
mode: GuardMode::Condition,
},
span,
);
}
Ok(dest)
}
AssignmentPlan::WildcardMatch {
@@ -125,7 +121,10 @@ impl<'a> Compiler<'a> {
let rhs_reg =
self.compile_rego_expr_with_span(rhs_expr, rhs_expr.span(), false)?;
self.emit_instruction(
Instruction::AssertNotUndefined { register: rhs_reg },
Instruction::Guard {
register: rhs_reg,
mode: GuardMode::NotUndefined,
},
span,
);
Ok(self.load_bool_literal(true, span))
@@ -134,7 +133,10 @@ impl<'a> Compiler<'a> {
let lhs_reg =
self.compile_rego_expr_with_span(lhs_expr, lhs_expr.span(), false)?;
self.emit_instruction(
Instruction::AssertNotUndefined { register: lhs_reg },
Instruction::Guard {
register: lhs_reg,
mode: GuardMode::NotUndefined,
},
span,
);
Ok(self.load_bool_literal(true, span))
@@ -206,44 +208,44 @@ impl<'a> Compiler<'a> {
DestructuringPlan::EqualityExpr(expected_expr) => {
let expected_reg =
self.compile_rego_expr_with_span(expected_expr, expected_expr.span(), false)?;
let cmp_reg = self.alloc_register();
self.emit_instruction(
Instruction::Eq {
dest: cmp_reg,
left: value_register,
right: expected_reg,
},
span,
);
if self.soft_assert_mode {
let cmp_reg = self.alloc_register();
self.emit_instruction(
Instruction::Eq {
dest: cmp_reg,
left: value_register,
right: expected_reg,
},
span,
);
return Ok(Some(cmp_reg));
}
self.emit_instruction(
Instruction::AssertEq {
left: value_register,
right: expected_reg,
Instruction::Guard {
register: cmp_reg,
mode: GuardMode::Condition,
},
span,
);
}
DestructuringPlan::EqualityValue(expected_value) => {
let expected_reg = self.load_literal_value(expected_value, span);
let cmp_reg = self.alloc_register();
self.emit_instruction(
Instruction::Eq {
dest: cmp_reg,
left: value_register,
right: expected_reg,
},
span,
);
if self.soft_assert_mode {
let cmp_reg = self.alloc_register();
self.emit_instruction(
Instruction::Eq {
dest: cmp_reg,
left: value_register,
right: expected_reg,
},
span,
);
return Ok(Some(cmp_reg));
}
self.emit_instruction(
Instruction::AssertEq {
left: value_register,
right: expected_reg,
Instruction::Guard {
register: cmp_reg,
mode: GuardMode::Condition,
},
span,
);
@@ -263,8 +265,9 @@ impl<'a> Compiler<'a> {
);
if context.require_defined_values() {
self.emit_instruction(
Instruction::AssertNotUndefined {
Instruction::Guard {
register: element_reg,
mode: GuardMode::NotUndefined,
},
span,
);
@@ -289,8 +292,9 @@ impl<'a> Compiler<'a> {
span,
);
self.emit_instruction(
Instruction::AssertNotUndefined {
Instruction::Guard {
register: field_reg,
mode: GuardMode::NotUndefined,
},
span,
);
@@ -310,8 +314,9 @@ impl<'a> Compiler<'a> {
span,
);
self.emit_instruction(
Instruction::AssertNotUndefined {
Instruction::Guard {
register: field_reg,
mode: GuardMode::NotUndefined,
},
span,
);
@@ -349,7 +354,13 @@ impl<'a> Compiler<'a> {
self.add_variable(var_name, dest);
if context.require_defined_values() {
self.emit_instruction(Instruction::AssertNotUndefined { register: dest }, span);
self.emit_instruction(
Instruction::Guard {
register: dest,
mode: GuardMode::NotUndefined,
},
span,
);
}
Ok(())
@@ -393,13 +404,22 @@ impl<'a> Compiler<'a> {
span,
);
let cmp_reg = self.alloc_register();
self.emit_instruction(
Instruction::AssertEq {
Instruction::Eq {
dest: cmp_reg,
left: actual_len_reg,
right: expected_len_reg,
},
span,
);
self.emit_instruction(
Instruction::Guard {
register: cmp_reg,
mode: GuardMode::Condition,
},
span,
);
Ok(())
}
}

View File

@@ -11,6 +11,7 @@ use super::{Compiler, CompilerError, Register, Result};
use crate::ast::{Expr, ExprRef};
use crate::compiler::destructuring_planner::plans::BindingPlan;
use crate::lexer::Span;
use crate::rvm::instructions::GuardMode;
use crate::rvm::Instruction;
use crate::Value;
use alloc::{format, string::ToString};
@@ -32,8 +33,9 @@ impl<'a> Compiler<'a> {
let result_reg = reg;
if assert_condition {
self.emit_instruction(
Instruction::AssertCondition {
condition: result_reg,
Instruction::Guard {
register: result_reg,
mode: GuardMode::Condition,
},
span,
);
@@ -113,8 +115,9 @@ impl<'a> Compiler<'a> {
if assert_condition {
self.emit_instruction(
Instruction::AssertCondition {
condition: result_reg,
Instruction::Guard {
register: result_reg,
mode: GuardMode::Condition,
},
span,
);

View File

@@ -8,6 +8,7 @@
use super::{Compiler, CompilerError, ComprehensionType, ContextType, Result};
use crate::ast::{self, LiteralStmt, Query};
use crate::rvm::instructions::GuardMode;
use crate::rvm::program::RuleType;
use crate::rvm::Instruction;
use alloc::format;
@@ -242,7 +243,22 @@ impl<'a> Compiler<'a> {
compiler.compile_rego_expr_with_span(expr, expr.span(), false)
})?;
self.emit_instruction(Instruction::AssertNot { operand: expr_reg }, &stmt.span);
let negated_reg = self.alloc_register();
self.emit_instruction(
Instruction::Not {
dest: negated_reg,
operand: expr_reg,
},
&stmt.span,
);
self.emit_instruction(
Instruction::Guard {
register: negated_reg,
mode: GuardMode::Condition,
},
&stmt.span,
);
}
}
Ok(())

View File

@@ -543,14 +543,12 @@ impl<'a> Compiler<'a> {
// A definition has a known static value if every body (including
// else-branches) would produce the same literal.
let def_static_value = if bodies.is_empty() {
// No bodies — value comes from the head's value_expr.
let head_value = self
.context_stack
.last()
.and_then(|ctx| ctx.value_expr.clone());
Self::static_value_of_expr(&head_value)
} else {
// Replay the same value_expr resolution as the body loop.
let head_value = self
.context_stack
.last()