mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
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:
committed by
GitHub
parent
1a8fc08773
commit
126cc12eb5
@@ -7,7 +7,7 @@ use crate::compiler::destructuring_planner::plans::{
|
|||||||
AssignmentPlan, BindingPlan, DestructuringPlan, WildcardSide,
|
AssignmentPlan, BindingPlan, DestructuringPlan, WildcardSide,
|
||||||
};
|
};
|
||||||
use crate::lexer::Span;
|
use crate::lexer::Span;
|
||||||
use crate::rvm::instructions::Instruction;
|
use crate::rvm::instructions::{GuardMode, Instruction};
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
@@ -91,19 +91,6 @@ impl<'a> Compiler<'a> {
|
|||||||
AssignmentPlan::EqualityCheck { lhs_expr, rhs_expr } => {
|
AssignmentPlan::EqualityCheck { lhs_expr, rhs_expr } => {
|
||||||
let lhs_reg = self.compile_rego_expr_with_span(lhs_expr, lhs_expr.span(), false)?;
|
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)?;
|
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();
|
let dest = self.alloc_register();
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::Eq {
|
Instruction::Eq {
|
||||||
@@ -113,6 +100,15 @@ impl<'a> Compiler<'a> {
|
|||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
|
if !self.soft_assert_mode {
|
||||||
|
self.emit_instruction(
|
||||||
|
Instruction::Guard {
|
||||||
|
register: dest,
|
||||||
|
mode: GuardMode::Condition,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(dest)
|
Ok(dest)
|
||||||
}
|
}
|
||||||
AssignmentPlan::WildcardMatch {
|
AssignmentPlan::WildcardMatch {
|
||||||
@@ -125,7 +121,10 @@ impl<'a> Compiler<'a> {
|
|||||||
let rhs_reg =
|
let rhs_reg =
|
||||||
self.compile_rego_expr_with_span(rhs_expr, rhs_expr.span(), false)?;
|
self.compile_rego_expr_with_span(rhs_expr, rhs_expr.span(), false)?;
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::AssertNotUndefined { register: rhs_reg },
|
Instruction::Guard {
|
||||||
|
register: rhs_reg,
|
||||||
|
mode: GuardMode::NotUndefined,
|
||||||
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
Ok(self.load_bool_literal(true, span))
|
Ok(self.load_bool_literal(true, span))
|
||||||
@@ -134,7 +133,10 @@ impl<'a> Compiler<'a> {
|
|||||||
let lhs_reg =
|
let lhs_reg =
|
||||||
self.compile_rego_expr_with_span(lhs_expr, lhs_expr.span(), false)?;
|
self.compile_rego_expr_with_span(lhs_expr, lhs_expr.span(), false)?;
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::AssertNotUndefined { register: lhs_reg },
|
Instruction::Guard {
|
||||||
|
register: lhs_reg,
|
||||||
|
mode: GuardMode::NotUndefined,
|
||||||
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
Ok(self.load_bool_literal(true, span))
|
Ok(self.load_bool_literal(true, span))
|
||||||
@@ -206,44 +208,44 @@ impl<'a> Compiler<'a> {
|
|||||||
DestructuringPlan::EqualityExpr(expected_expr) => {
|
DestructuringPlan::EqualityExpr(expected_expr) => {
|
||||||
let expected_reg =
|
let expected_reg =
|
||||||
self.compile_rego_expr_with_span(expected_expr, expected_expr.span(), false)?;
|
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 {
|
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));
|
return Ok(Some(cmp_reg));
|
||||||
}
|
}
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::AssertEq {
|
Instruction::Guard {
|
||||||
left: value_register,
|
register: cmp_reg,
|
||||||
right: expected_reg,
|
mode: GuardMode::Condition,
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DestructuringPlan::EqualityValue(expected_value) => {
|
DestructuringPlan::EqualityValue(expected_value) => {
|
||||||
let expected_reg = self.load_literal_value(expected_value, span);
|
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 {
|
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));
|
return Ok(Some(cmp_reg));
|
||||||
}
|
}
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::AssertEq {
|
Instruction::Guard {
|
||||||
left: value_register,
|
register: cmp_reg,
|
||||||
right: expected_reg,
|
mode: GuardMode::Condition,
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
@@ -263,8 +265,9 @@ impl<'a> Compiler<'a> {
|
|||||||
);
|
);
|
||||||
if context.require_defined_values() {
|
if context.require_defined_values() {
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::AssertNotUndefined {
|
Instruction::Guard {
|
||||||
register: element_reg,
|
register: element_reg,
|
||||||
|
mode: GuardMode::NotUndefined,
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
@@ -289,8 +292,9 @@ impl<'a> Compiler<'a> {
|
|||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::AssertNotUndefined {
|
Instruction::Guard {
|
||||||
register: field_reg,
|
register: field_reg,
|
||||||
|
mode: GuardMode::NotUndefined,
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
@@ -310,8 +314,9 @@ impl<'a> Compiler<'a> {
|
|||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::AssertNotUndefined {
|
Instruction::Guard {
|
||||||
register: field_reg,
|
register: field_reg,
|
||||||
|
mode: GuardMode::NotUndefined,
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
@@ -349,7 +354,13 @@ impl<'a> Compiler<'a> {
|
|||||||
self.add_variable(var_name, dest);
|
self.add_variable(var_name, dest);
|
||||||
|
|
||||||
if context.require_defined_values() {
|
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(())
|
Ok(())
|
||||||
@@ -393,13 +404,22 @@ impl<'a> Compiler<'a> {
|
|||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let cmp_reg = self.alloc_register();
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::AssertEq {
|
Instruction::Eq {
|
||||||
|
dest: cmp_reg,
|
||||||
left: actual_len_reg,
|
left: actual_len_reg,
|
||||||
right: expected_len_reg,
|
right: expected_len_reg,
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
|
self.emit_instruction(
|
||||||
|
Instruction::Guard {
|
||||||
|
register: cmp_reg,
|
||||||
|
mode: GuardMode::Condition,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use super::{Compiler, CompilerError, Register, Result};
|
|||||||
use crate::ast::{Expr, ExprRef};
|
use crate::ast::{Expr, ExprRef};
|
||||||
use crate::compiler::destructuring_planner::plans::BindingPlan;
|
use crate::compiler::destructuring_planner::plans::BindingPlan;
|
||||||
use crate::lexer::Span;
|
use crate::lexer::Span;
|
||||||
|
use crate::rvm::instructions::GuardMode;
|
||||||
use crate::rvm::Instruction;
|
use crate::rvm::Instruction;
|
||||||
use crate::Value;
|
use crate::Value;
|
||||||
use alloc::{format, string::ToString};
|
use alloc::{format, string::ToString};
|
||||||
@@ -32,8 +33,9 @@ impl<'a> Compiler<'a> {
|
|||||||
let result_reg = reg;
|
let result_reg = reg;
|
||||||
if assert_condition {
|
if assert_condition {
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::AssertCondition {
|
Instruction::Guard {
|
||||||
condition: result_reg,
|
register: result_reg,
|
||||||
|
mode: GuardMode::Condition,
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
@@ -113,8 +115,9 @@ impl<'a> Compiler<'a> {
|
|||||||
|
|
||||||
if assert_condition {
|
if assert_condition {
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::AssertCondition {
|
Instruction::Guard {
|
||||||
condition: result_reg,
|
register: result_reg,
|
||||||
|
mode: GuardMode::Condition,
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
use super::{Compiler, CompilerError, ComprehensionType, ContextType, Result};
|
use super::{Compiler, CompilerError, ComprehensionType, ContextType, Result};
|
||||||
use crate::ast::{self, LiteralStmt, Query};
|
use crate::ast::{self, LiteralStmt, Query};
|
||||||
|
use crate::rvm::instructions::GuardMode;
|
||||||
use crate::rvm::program::RuleType;
|
use crate::rvm::program::RuleType;
|
||||||
use crate::rvm::Instruction;
|
use crate::rvm::Instruction;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
@@ -242,7 +243,22 @@ impl<'a> Compiler<'a> {
|
|||||||
compiler.compile_rego_expr_with_span(expr, expr.span(), false)
|
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(())
|
Ok(())
|
||||||
|
|||||||
@@ -543,14 +543,12 @@ impl<'a> Compiler<'a> {
|
|||||||
// A definition has a known static value if every body (including
|
// A definition has a known static value if every body (including
|
||||||
// else-branches) would produce the same literal.
|
// else-branches) would produce the same literal.
|
||||||
let def_static_value = if bodies.is_empty() {
|
let def_static_value = if bodies.is_empty() {
|
||||||
// No bodies — value comes from the head's value_expr.
|
|
||||||
let head_value = self
|
let head_value = self
|
||||||
.context_stack
|
.context_stack
|
||||||
.last()
|
.last()
|
||||||
.and_then(|ctx| ctx.value_expr.clone());
|
.and_then(|ctx| ctx.value_expr.clone());
|
||||||
Self::static_value_of_expr(&head_value)
|
Self::static_value_of_expr(&head_value)
|
||||||
} else {
|
} else {
|
||||||
// Replay the same value_expr resolution as the body loop.
|
|
||||||
let head_value = self
|
let head_value = self
|
||||||
.context_stack
|
.context_stack
|
||||||
.last()
|
.last()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use alloc::format;
|
|||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use super::types::GuardMode;
|
||||||
use super::{Instruction, InstructionData, LiteralOrRegister};
|
use super::{Instruction, InstructionData, LiteralOrRegister};
|
||||||
|
|
||||||
impl Instruction {
|
impl Instruction {
|
||||||
@@ -65,32 +66,25 @@ impl Instruction {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Instruction::ObjectCreate { params_index } => {
|
Instruction::ObjectCreate { params_index } => instruction_data
|
||||||
instruction_data
|
.get_object_create_params(params_index)
|
||||||
.get_object_create_params(params_index)
|
.map_or_else(
|
||||||
.map_or_else(
|
|| format!("OBJECT_CREATE P({}) [INVALID INDEX]", params_index),
|
||||||
|| format!("OBJECT_CREATE P({}) [INVALID INDEX]", params_index),
|
|params| {
|
||||||
|params| {
|
let mut field_parts = Vec::new();
|
||||||
let mut field_parts = Vec::new();
|
for &(literal_idx, value_reg) in params.literal_key_field_pairs() {
|
||||||
|
field_parts.push(format!("L({}):R({})", literal_idx, value_reg));
|
||||||
// Add literal key fields
|
}
|
||||||
for &(literal_idx, value_reg) in params.literal_key_field_pairs() {
|
for &(key_reg, value_reg) in params.field_pairs() {
|
||||||
field_parts.push(format!("L({}):R({})", literal_idx, value_reg));
|
field_parts.push(format!("R({}):R({})", key_reg, value_reg));
|
||||||
}
|
}
|
||||||
|
let fields_str = field_parts.join(" ");
|
||||||
// Add non-literal key fields
|
format!(
|
||||||
for &(key_reg, value_reg) in params.field_pairs() {
|
"OBJECT_CREATE R({}) L({}) [{}]",
|
||||||
field_parts.push(format!("R({}):R({})", key_reg, value_reg));
|
params.dest, params.template_literal_idx, fields_str
|
||||||
}
|
)
|
||||||
|
},
|
||||||
let fields_str = field_parts.join(" ");
|
),
|
||||||
format!(
|
|
||||||
"OBJECT_CREATE R({}) L({}) [{}]",
|
|
||||||
params.dest, params.template_literal_idx, fields_str
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Instruction::VirtualDataDocumentLookup { params_index } => instruction_data
|
Instruction::VirtualDataDocumentLookup { params_index } => instruction_data
|
||||||
.get_virtual_data_document_lookup_params(params_index)
|
.get_virtual_data_document_lookup_params(params_index)
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
@@ -245,14 +239,13 @@ impl core::fmt::Display for Instruction {
|
|||||||
Instruction::AssertEq { left, right } => {
|
Instruction::AssertEq { left, right } => {
|
||||||
format!("ASSERT_EQ R({}) R({})", left, right)
|
format!("ASSERT_EQ R({}) R({})", left, right)
|
||||||
}
|
}
|
||||||
Instruction::AssertNot { operand } => {
|
Instruction::Guard { register, mode } => {
|
||||||
format!("ASSERT_NOT R({})", operand)
|
let name = match mode {
|
||||||
}
|
GuardMode::Not => "ASSERT_NOT",
|
||||||
Instruction::AssertCondition { condition } => {
|
GuardMode::Condition => "ASSERT_CONDITION",
|
||||||
format!("ASSERT_CONDITION R({})", condition)
|
GuardMode::NotUndefined => "ASSERT_NOT_UNDEFINED",
|
||||||
}
|
};
|
||||||
Instruction::AssertNotUndefined { register } => {
|
format!("{} R({})", name, register)
|
||||||
format!("ASSERT_NOT_UNDEFINED R({})", register)
|
|
||||||
}
|
}
|
||||||
Instruction::LoopStart { params_index } => {
|
Instruction::LoopStart { params_index } => {
|
||||||
format!("LOOP_START P({})", params_index)
|
format!("LOOP_START P({})", params_index)
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ pub use params::{
|
|||||||
FunctionCallParams, InstructionData, LoopStartParams, ObjectCreateParams, SetCreateParams,
|
FunctionCallParams, InstructionData, LoopStartParams, ObjectCreateParams, SetCreateParams,
|
||||||
VirtualDataDocumentLookupParams,
|
VirtualDataDocumentLookupParams,
|
||||||
};
|
};
|
||||||
pub use types::{ComprehensionMode, LiteralOrRegister, LoopMode};
|
pub use types::{ComprehensionMode, GuardMode, LiteralOrRegister, LoopMode};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// RVM Instructions - simplified enum-based design
|
/// RVM Instructions - simplified enum-based design
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub enum Instruction {
|
pub enum Instruction {
|
||||||
/// Load literal value from literal table into register
|
/// Load literal value from literal table into register
|
||||||
@@ -131,8 +130,6 @@ pub enum Instruction {
|
|||||||
left: u8,
|
left: u8,
|
||||||
right: u8,
|
right: u8,
|
||||||
},
|
},
|
||||||
/// Rego negation - produces `true` if operand is `false` or undefined,
|
|
||||||
/// `false` for any other defined value (including non-booleans).
|
|
||||||
Not {
|
Not {
|
||||||
dest: u8,
|
dest: u8,
|
||||||
operand: u8,
|
operand: u8,
|
||||||
@@ -251,19 +248,10 @@ pub enum Instruction {
|
|||||||
right: u8,
|
right: u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Assert negation - succeed if operand is false or undefined, fail if true
|
/// Consolidated guard instruction — replaces AssertNot, AssertCondition, AssertNotUndefined.
|
||||||
AssertNot {
|
Guard {
|
||||||
operand: u8,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Assert condition - if register contains false or undefined, return undefined immediately
|
|
||||||
AssertCondition {
|
|
||||||
condition: u8,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Assert not undefined - if register contains undefined, return undefined immediately
|
|
||||||
AssertNotUndefined {
|
|
||||||
register: u8,
|
register: u8,
|
||||||
|
mode: GuardMode,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Start a loop over a collection with specified semantics - uses parameter table
|
/// Start a loop over a collection with specified semantics - uses parameter table
|
||||||
@@ -392,3 +380,26 @@ impl Instruction {
|
|||||||
Self::ComprehensionEnd {}
|
Self::ComprehensionEnd {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use core::mem::size_of;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn instruction_size() {
|
||||||
|
// Lock the instruction size to detect unintended growth.
|
||||||
|
// Without repr(C), Rust picks a 1-byte discriminant (< 256 variants)
|
||||||
|
// plus 4 bytes for the largest payload variant + 1 byte alignment
|
||||||
|
// padding for u16 fields = 6 bytes total.
|
||||||
|
//
|
||||||
|
// TODO: Reduce to 4 bytes by making LoopNext zero-payload (both fields
|
||||||
|
// are redundant with LoopStart params) and moving IndexLiteral to a
|
||||||
|
// params table.
|
||||||
|
let size = size_of::<Instruction>();
|
||||||
|
assert_eq!(
|
||||||
|
size, 6,
|
||||||
|
"Instruction size changed from 6 to {size} — review new variants for bloat"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ pub enum LiteralOrRegister {
|
|||||||
|
|
||||||
/// Loop execution modes for different Rego iteration constructs
|
/// Loop execution modes for different Rego iteration constructs
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum LoopMode {
|
pub enum LoopMode {
|
||||||
/// Any quantification: some x in arr, x := arr[_], etc.
|
/// Any quantification: some x in arr, x := arr[_], etc.
|
||||||
/// Succeeds if ANY iteration succeeds, exits early on first success
|
/// Succeeds if ANY iteration succeeds, exits early on first success
|
||||||
@@ -45,3 +45,15 @@ pub enum ComprehensionMode {
|
|||||||
/// Collects successful key-value pairs into an object
|
/// Collects successful key-value pairs into an object
|
||||||
Object,
|
Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Guard sub-modes for the consolidated `Guard` instruction.
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum GuardMode {
|
||||||
|
/// Assert negation — succeed if operand is false/undefined, fail if true.
|
||||||
|
Not,
|
||||||
|
/// Assert condition — fail (return undefined) if register is false/undefined.
|
||||||
|
Condition,
|
||||||
|
/// Assert not undefined — fail (return undefined) if register is undefined.
|
||||||
|
NotUndefined,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
#![allow(clippy::option_if_let_else)]
|
|
||||||
|
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
use alloc::string::{String, ToString as _};
|
use alloc::string::{String, ToString as _};
|
||||||
@@ -106,6 +105,36 @@ pub fn generate_assembly_listing(program: &Program, config: &AssemblyListingConf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add metadata section
|
||||||
|
{
|
||||||
|
push_line(&mut output, format_args!(";"));
|
||||||
|
push_line(&mut output, format_args!("; METADATA:"));
|
||||||
|
push_line(
|
||||||
|
&mut output,
|
||||||
|
format_args!(
|
||||||
|
"; compiler_version: {}",
|
||||||
|
program.metadata.compiler_version
|
||||||
|
),
|
||||||
|
);
|
||||||
|
push_line(
|
||||||
|
&mut output,
|
||||||
|
format_args!("; compiled_at: {}", program.metadata.compiled_at),
|
||||||
|
);
|
||||||
|
if !program.metadata.source_info.is_empty() {
|
||||||
|
push_line(
|
||||||
|
&mut output,
|
||||||
|
format_args!("; source_info: {}", program.metadata.source_info),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
push_line(
|
||||||
|
&mut output,
|
||||||
|
format_args!(
|
||||||
|
"; optimization_level: {}",
|
||||||
|
program.metadata.optimization_level
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
push_line(&mut output, format_args!(";"));
|
push_line(&mut output, format_args!(";"));
|
||||||
|
|
||||||
for (pc, instruction) in program.instructions.iter().enumerate() {
|
for (pc, instruction) in program.instructions.iter().enumerate() {
|
||||||
@@ -230,14 +259,14 @@ fn format_instruction_readable(
|
|||||||
match *instruction {
|
match *instruction {
|
||||||
Instruction::Load { dest, literal_idx } => {
|
Instruction::Load { dest, literal_idx } => {
|
||||||
let base = format!("{}Load r{} ← L{}", indent, dest, literal_idx);
|
let base = format!("{}Load r{} ← L{}", indent, dest, literal_idx);
|
||||||
let comment = match program.literals.get(usize::from(literal_idx)) {
|
let comment = program.literals.get(usize::from(literal_idx)).map_or_else(
|
||||||
Some(literal) => {
|
|| "Load literal: <invalid index>".to_string(),
|
||||||
|
|literal| {
|
||||||
let literal_json =
|
let literal_json =
|
||||||
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
|
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
|
||||||
format!("Load literal: {}", literal_json)
|
format!("Load literal: {}", literal_json)
|
||||||
}
|
},
|
||||||
None => "Load literal: <invalid index>".to_string(),
|
);
|
||||||
};
|
|
||||||
align_comment(&base, &comment, config.comment_column)
|
align_comment(&base, &comment, config.comment_column)
|
||||||
}
|
}
|
||||||
Instruction::LoadTrue { dest } => {
|
Instruction::LoadTrue { dest } => {
|
||||||
@@ -358,78 +387,81 @@ fn format_instruction_readable(
|
|||||||
}
|
}
|
||||||
Instruction::Not { dest, operand } => {
|
Instruction::Not { dest, operand } => {
|
||||||
let base = format!("{}Not r{} ← ¬r{}", indent, dest, operand);
|
let base = format!("{}Not r{} ← ¬r{}", indent, dest, operand);
|
||||||
let comment = format!(
|
let comment = format!("Logical NOT: !r{}", operand);
|
||||||
"Rego negation: true if r{} is false/undefined, false otherwise",
|
|
||||||
operand
|
|
||||||
);
|
|
||||||
align_comment(&base, &comment, config.comment_column)
|
align_comment(&base, &comment, config.comment_column)
|
||||||
}
|
}
|
||||||
Instruction::BuiltinCall { params_index } => {
|
Instruction::BuiltinCall { params_index } => instruction_data
|
||||||
if let Some(params) = instruction_data.get_builtin_call_params(params_index) {
|
.get_builtin_call_params(params_index)
|
||||||
let args_str = params
|
.map_or_else(
|
||||||
.arg_registers()
|
|| {
|
||||||
.iter()
|
let base = format!("{}BuiltinCall [INVALID P({})]", indent, params_index);
|
||||||
.map(|&r| format!("r{}", r))
|
align_comment(
|
||||||
.collect::<Vec<_>>()
|
&base,
|
||||||
.join(", ");
|
"ERROR: Invalid builtin call parameters",
|
||||||
|
config.comment_column,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|params| {
|
||||||
|
let args_str = params
|
||||||
|
.arg_registers()
|
||||||
|
.iter()
|
||||||
|
.map(|&r| format!("r{}", r))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
let builtin_name = program
|
let builtin_name = program
|
||||||
.builtin_info_table
|
.builtin_info_table
|
||||||
.get(usize::from(params.builtin_index))
|
.get(usize::from(params.builtin_index))
|
||||||
.map(|info| info.name.as_str())
|
.map(|info| info.name.as_str())
|
||||||
.unwrap_or("<invalid>");
|
.unwrap_or("<invalid>");
|
||||||
|
|
||||||
let base = format!(
|
let base = format!(
|
||||||
"{}BuiltinCall r{} ← {}({})",
|
"{}BuiltinCall r{} ← {}({})",
|
||||||
indent, params.dest, builtin_name, args_str
|
indent, params.dest, builtin_name, args_str
|
||||||
);
|
);
|
||||||
let comment = format!(
|
let comment = format!(
|
||||||
"Call builtin '{}' (B{}) with {} args",
|
"Call builtin '{}' (B{}) with {} args",
|
||||||
builtin_name, params.builtin_index, params.num_args
|
builtin_name, params.builtin_index, params.num_args
|
||||||
);
|
);
|
||||||
align_comment(&base, &comment, config.comment_column)
|
align_comment(&base, &comment, config.comment_column)
|
||||||
} else {
|
},
|
||||||
let base = format!("{}BuiltinCall [INVALID P({})]", indent, params_index);
|
),
|
||||||
align_comment(
|
Instruction::FunctionCall { params_index } => instruction_data
|
||||||
&base,
|
.get_function_call_params(params_index)
|
||||||
"ERROR: Invalid builtin call parameters",
|
.map_or_else(
|
||||||
config.comment_column,
|
|| {
|
||||||
)
|
let base = format!("{}FunctionCall [INVALID P({})]", indent, params_index);
|
||||||
}
|
align_comment(
|
||||||
}
|
&base,
|
||||||
Instruction::FunctionCall { params_index } => {
|
"ERROR: Invalid function call parameters",
|
||||||
if let Some(params) = instruction_data.get_function_call_params(params_index) {
|
config.comment_column,
|
||||||
let args_str = params
|
)
|
||||||
.arg_registers()
|
},
|
||||||
.iter()
|
|params| {
|
||||||
.map(|&r| format!("r{}", r))
|
let args_str = params
|
||||||
.collect::<Vec<_>>()
|
.arg_registers()
|
||||||
.join(", ");
|
.iter()
|
||||||
|
.map(|&r| format!("r{}", r))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
let func_name = program
|
let func_name = program
|
||||||
.rule_infos
|
.rule_infos
|
||||||
.get(usize::from(params.func_rule_index))
|
.get(usize::from(params.func_rule_index))
|
||||||
.map(|info| info.name.as_str())
|
.map(|info| info.name.as_str())
|
||||||
.unwrap_or("<invalid>");
|
.unwrap_or("<invalid>");
|
||||||
|
|
||||||
let base = format!(
|
let base = format!(
|
||||||
"{}FunctionCall r{} ← {}({})",
|
"{}FunctionCall r{} ← {}({})",
|
||||||
indent, params.dest, func_name, args_str
|
indent, params.dest, func_name, args_str
|
||||||
);
|
);
|
||||||
let comment = format!(
|
let comment = format!(
|
||||||
"Call function '{}' (R{}) with {} args",
|
"Call function '{}' (R{}) with {} args",
|
||||||
func_name, params.func_rule_index, params.num_args
|
func_name, params.func_rule_index, params.num_args
|
||||||
);
|
);
|
||||||
align_comment(&base, &comment, config.comment_column)
|
align_comment(&base, &comment, config.comment_column)
|
||||||
} else {
|
},
|
||||||
let base = format!("{}FunctionCall [INVALID P({})]", indent, params_index);
|
),
|
||||||
align_comment(
|
|
||||||
&base,
|
|
||||||
"ERROR: Invalid function call parameters",
|
|
||||||
config.comment_column,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Instruction::HostAwait { dest, arg, id } => {
|
Instruction::HostAwait { dest, arg, id } => {
|
||||||
let base = format!(
|
let base = format!(
|
||||||
"{}HostAwait r{} ← await r{} (id r{})",
|
"{}HostAwait r{} ← await r{} (id r{})",
|
||||||
@@ -463,14 +495,16 @@ fn format_instruction_readable(
|
|||||||
indent,
|
indent,
|
||||||
params.map_or(0, |p| p.dest)
|
params.map_or(0, |p| p.dest)
|
||||||
);
|
);
|
||||||
let comment = match params {
|
let comment = params.map_or_else(
|
||||||
Some(p) => format!(
|
|| format!("Create object (P{} - INVALID)", params_index),
|
||||||
"Create object with {} fields (P{})",
|
|p| {
|
||||||
p.field_count(),
|
format!(
|
||||||
params_index
|
"Create object with {} fields (P{})",
|
||||||
),
|
p.field_count(),
|
||||||
None => format!("Create object (P{} - INVALID)", params_index),
|
params_index,
|
||||||
};
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
align_comment(&base, &comment, config.comment_column)
|
align_comment(&base, &comment, config.comment_column)
|
||||||
}
|
}
|
||||||
Instruction::Index {
|
Instruction::Index {
|
||||||
@@ -494,17 +528,19 @@ fn format_instruction_readable(
|
|||||||
"{}IndexLiteral r{} ← r{}[L{}]",
|
"{}IndexLiteral r{} ← r{}[L{}]",
|
||||||
indent, dest, container, literal_idx
|
indent, dest, container, literal_idx
|
||||||
);
|
);
|
||||||
let comment = match program.literals.get(usize::from(literal_idx)) {
|
let comment = program.literals.get(usize::from(literal_idx)).map_or_else(
|
||||||
Some(literal) => {
|
|| {
|
||||||
|
format!(
|
||||||
|
"Index with literal: r{}[L{}] (invalid index)",
|
||||||
|
container, literal_idx,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|literal| {
|
||||||
let literal_json =
|
let literal_json =
|
||||||
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
|
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
|
||||||
format!("Index with literal key: r{}[{}]", container, literal_json)
|
format!("Index with literal key: r{}[{}]", container, literal_json)
|
||||||
}
|
},
|
||||||
None => format!(
|
);
|
||||||
"Index with literal: r{}[L{}] (invalid index)",
|
|
||||||
container, literal_idx
|
|
||||||
),
|
|
||||||
};
|
|
||||||
align_comment(&base, &comment, config.comment_column)
|
align_comment(&base, &comment, config.comment_column)
|
||||||
}
|
}
|
||||||
Instruction::ArrayNew { dest } => {
|
Instruction::ArrayNew { dest } => {
|
||||||
@@ -516,24 +552,25 @@ fn format_instruction_readable(
|
|||||||
let comment = format!("Append r{} to array r{}", value, arr);
|
let comment = format!("Append r{} to array r{}", value, arr);
|
||||||
align_comment(&base, &comment, config.comment_column)
|
align_comment(&base, &comment, config.comment_column)
|
||||||
}
|
}
|
||||||
Instruction::ArrayCreate { params_index } => {
|
Instruction::ArrayCreate { params_index } => instruction_data
|
||||||
if let Some(params) = instruction_data.get_array_create_params(params_index) {
|
.get_array_create_params(params_index)
|
||||||
let elements = params
|
.map_or_else(
|
||||||
.element_registers()
|
|| format!("{}ArrayCreate <invalid params P{}>", indent, params_index),
|
||||||
.iter()
|
|params| {
|
||||||
.map(|r| format!("r{}", r))
|
let elements = params
|
||||||
.collect::<Vec<_>>()
|
.element_registers()
|
||||||
.join(", ");
|
.iter()
|
||||||
let base = format!("{}ArrayCreate r{} ← [{}]", indent, params.dest, elements);
|
.map(|r| format!("r{}", r))
|
||||||
let comment = format!(
|
.collect::<Vec<_>>()
|
||||||
"Create array from {} elements (undefined if any element is undefined)",
|
.join(", ");
|
||||||
params.element_count()
|
let base = format!("{}ArrayCreate r{} ← [{}]", indent, params.dest, elements);
|
||||||
);
|
let comment = format!(
|
||||||
align_comment(&base, &comment, config.comment_column)
|
"Create array from {} elements (undefined if any element is undefined)",
|
||||||
} else {
|
params.element_count()
|
||||||
format!("{}ArrayCreate <invalid params P{}>", indent, params_index)
|
);
|
||||||
}
|
align_comment(&base, &comment, config.comment_column)
|
||||||
}
|
},
|
||||||
|
),
|
||||||
Instruction::SetNew { dest } => {
|
Instruction::SetNew { dest } => {
|
||||||
let base = format!("{}SetNew r{} ← set()", indent, dest);
|
let base = format!("{}SetNew r{} ← set()", indent, dest);
|
||||||
align_comment(&base, "Create new empty set", config.comment_column)
|
align_comment(&base, "Create new empty set", config.comment_column)
|
||||||
@@ -543,24 +580,26 @@ fn format_instruction_readable(
|
|||||||
let comment = format!("Add r{} to set r{}", value, set);
|
let comment = format!("Add r{} to set r{}", value, set);
|
||||||
align_comment(&base, &comment, config.comment_column)
|
align_comment(&base, &comment, config.comment_column)
|
||||||
}
|
}
|
||||||
Instruction::SetCreate { params_index } => {
|
Instruction::SetCreate { params_index } => instruction_data
|
||||||
if let Some(params) = instruction_data.get_set_create_params(params_index) {
|
.get_set_create_params(params_index)
|
||||||
let elements = params
|
.map_or_else(
|
||||||
.element_registers()
|
|| format!("{}SetCreate <invalid params P{}>", indent, params_index),
|
||||||
.iter()
|
|params| {
|
||||||
.map(|r| format!("r{}", r))
|
let elements = params
|
||||||
.collect::<Vec<_>>()
|
.element_registers()
|
||||||
.join(", ");
|
.iter()
|
||||||
let base = format!("{}SetCreate r{} ← {{{}}}", indent, params.dest, elements);
|
.map(|r| format!("r{}", r))
|
||||||
let comment = format!(
|
.collect::<Vec<_>>()
|
||||||
"Create set from {} elements (undefined if any element is undefined)",
|
.join(", ");
|
||||||
params.element_count()
|
let base =
|
||||||
);
|
format!("{}SetCreate r{} ← {{{}}}", indent, params.dest, elements);
|
||||||
align_comment(&base, &comment, config.comment_column)
|
let comment = format!(
|
||||||
} else {
|
"Create set from {} elements (undefined if any element is undefined)",
|
||||||
format!("{}SetCreate <invalid params P{}>", indent, params_index)
|
params.element_count()
|
||||||
}
|
);
|
||||||
}
|
align_comment(&base, &comment, config.comment_column)
|
||||||
|
},
|
||||||
|
),
|
||||||
Instruction::Contains {
|
Instruction::Contains {
|
||||||
dest,
|
dest,
|
||||||
collection,
|
collection,
|
||||||
@@ -581,61 +620,67 @@ fn format_instruction_readable(
|
|||||||
Instruction::AssertEq { left, right } => {
|
Instruction::AssertEq { left, right } => {
|
||||||
let base = format!("{}AssertEq assert r{} == r{}", indent, left, right);
|
let base = format!("{}AssertEq assert r{} == r{}", indent, left, right);
|
||||||
let comment = format!(
|
let comment = format!(
|
||||||
"Assert r{} equals r{} (exit if unequal/undefined)",
|
"Assert r{} equals r{} (exit if either undefined or different)",
|
||||||
left, right
|
left, right
|
||||||
);
|
);
|
||||||
align_comment(&base, &comment, config.comment_column)
|
align_comment(&base, &comment, config.comment_column)
|
||||||
}
|
}
|
||||||
Instruction::AssertNot { operand } => {
|
Instruction::Guard { register, mode } => {
|
||||||
let base = format!("{}AssertNot assert !r{}", indent, operand);
|
let (keyword, comment) = match mode {
|
||||||
let comment = format!(
|
crate::rvm::instructions::GuardMode::Not => (
|
||||||
"Assert r{} is false/undefined (exit if any defined truthy value)",
|
format!("{}AssertNot assert !r{}", indent, register),
|
||||||
operand
|
format!("Assert r{} is false/undefined (exit if true)", register),
|
||||||
);
|
),
|
||||||
align_comment(&base, &comment, config.comment_column)
|
crate::rvm::instructions::GuardMode::Condition => (
|
||||||
}
|
format!("{}Assert assert r{}", indent, register),
|
||||||
Instruction::AssertCondition { condition } => {
|
format!("Assert r{} is true (exit if false/undefined)", register),
|
||||||
let base = format!("{}Assert assert r{}", indent, condition);
|
),
|
||||||
let comment = format!("Assert r{} is true (exit if false/undefined)", condition);
|
crate::rvm::instructions::GuardMode::NotUndefined => (
|
||||||
align_comment(&base, &comment, config.comment_column)
|
format!(
|
||||||
}
|
"{}AssertNotUndefined assert_not_undefined r{}",
|
||||||
Instruction::AssertNotUndefined { register } => {
|
indent, register
|
||||||
let base = format!(
|
),
|
||||||
"{}AssertNotUndefined assert_not_undefined r{}",
|
format!("Assert r{} is not undefined (exit if undefined)", register),
|
||||||
indent, register
|
),
|
||||||
);
|
};
|
||||||
let comment = format!("Assert r{} is not undefined (exit if undefined)", register);
|
align_comment(&keyword, &comment, config.comment_column)
|
||||||
align_comment(&base, &comment, config.comment_column)
|
|
||||||
}
|
}
|
||||||
Instruction::LoopStart { params_index } => {
|
Instruction::LoopStart { params_index } => {
|
||||||
if let Some(params) = instruction_data.get_loop_params(params_index) {
|
instruction_data.get_loop_params(params_index).map_or_else(
|
||||||
let mode_str = match params.mode {
|
|| {
|
||||||
LoopMode::Any => "any",
|
let base = format!("{}LoopStart [INVALID P({})] {{", indent, params_index);
|
||||||
LoopMode::Every => "every",
|
align_comment(
|
||||||
LoopMode::ForEach => "foreach",
|
&base,
|
||||||
};
|
"ERROR: Invalid loop parameters",
|
||||||
let base = format!(
|
config.comment_column,
|
||||||
"{}LoopStart {} r{},r{} in r{} → r{} {{",
|
)
|
||||||
indent,
|
},
|
||||||
mode_str,
|
|params| {
|
||||||
params.key_reg,
|
let mode_str = match params.mode {
|
||||||
params.value_reg,
|
LoopMode::Any => "any",
|
||||||
params.collection,
|
LoopMode::Every => "every",
|
||||||
params.result_reg
|
LoopMode::ForEach => "foreach",
|
||||||
);
|
};
|
||||||
let comment = format!(
|
let base = format!(
|
||||||
"{} loop over r{}, body: {}-{} (P{})",
|
"{}LoopStart {} r{},r{} in r{} → r{} {{",
|
||||||
mode_str, params.collection, params.body_start, params.loop_end, params_index
|
indent,
|
||||||
);
|
mode_str,
|
||||||
align_comment(&base, &comment, config.comment_column)
|
params.key_reg,
|
||||||
} else {
|
params.value_reg,
|
||||||
let base = format!("{}LoopStart [INVALID P({})] {{", indent, params_index);
|
params.collection,
|
||||||
align_comment(
|
params.result_reg
|
||||||
&base,
|
);
|
||||||
"ERROR: Invalid loop parameters",
|
let comment = format!(
|
||||||
config.comment_column,
|
"{} loop over r{}, body: {}-{} (P{})",
|
||||||
)
|
mode_str,
|
||||||
}
|
params.collection,
|
||||||
|
params.body_start,
|
||||||
|
params.loop_end,
|
||||||
|
params_index
|
||||||
|
);
|
||||||
|
align_comment(&base, &comment, config.comment_column)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Instruction::LoopNext {
|
Instruction::LoopNext {
|
||||||
body_start,
|
body_start,
|
||||||
@@ -684,52 +729,58 @@ fn format_instruction_readable(
|
|||||||
align_comment(&base, "End of rule evaluation", config.comment_column)
|
align_comment(&base, "End of rule evaluation", config.comment_column)
|
||||||
}
|
}
|
||||||
Instruction::ChainedIndex { params_index } => {
|
Instruction::ChainedIndex { params_index } => {
|
||||||
let (base, comment) =
|
let (base, comment) = instruction_data
|
||||||
if let Some(params) = instruction_data.get_chained_index_params(params_index) {
|
.get_chained_index_params(params_index)
|
||||||
let chain_parts: Vec<String> = params
|
.map_or_else(
|
||||||
.path_components
|
|| {
|
||||||
.iter()
|
let base_str = format!("{}ChainedIndex chained_index", indent);
|
||||||
.map(|component| match *component {
|
let comment_str =
|
||||||
crate::rvm::instructions::LiteralOrRegister::Literal(idx) => {
|
"Multi-level chained indexing (invalid params)".to_string();
|
||||||
if let Some(literal) = program.literals.get(usize::from(idx)) {
|
(base_str, comment_str)
|
||||||
match *literal {
|
},
|
||||||
crate::Value::String(ref s) => format!(".{}", s.as_ref()),
|
|params| {
|
||||||
ref other => format!(
|
let chain_parts: Vec<String> = params
|
||||||
"[{}]",
|
.path_components
|
||||||
serde_json::to_string(other)
|
.iter()
|
||||||
.unwrap_or_else(|_| "?".to_string())
|
.map(|component| match *component {
|
||||||
),
|
crate::rvm::instructions::LiteralOrRegister::Literal(idx) => {
|
||||||
}
|
program.literals.get(usize::from(idx)).map_or_else(
|
||||||
} else {
|
|| format!("[L{}?]", idx),
|
||||||
format!("[L{}?]", idx)
|
|literal| match *literal {
|
||||||
|
crate::Value::String(ref s) => {
|
||||||
|
format!(".{}", s.as_ref())
|
||||||
|
}
|
||||||
|
ref other => format!(
|
||||||
|
"[{}]",
|
||||||
|
serde_json::to_string(other)
|
||||||
|
.unwrap_or_else(|_| "?".to_string())
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
crate::rvm::instructions::LiteralOrRegister::Register(reg) => {
|
||||||
crate::rvm::instructions::LiteralOrRegister::Register(reg) => {
|
format!("[r{}]", reg)
|
||||||
format!("[r{}]", reg)
|
}
|
||||||
}
|
})
|
||||||
})
|
.collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
let chain_display = if chain_parts.is_empty() {
|
let chain_display = if chain_parts.is_empty() {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
format!(" r{}{}", params.root, chain_parts.join(""))
|
format!(" r{}{}", params.root, chain_parts.join(""))
|
||||||
};
|
};
|
||||||
|
|
||||||
let base_str = format!(
|
let base_str = format!(
|
||||||
"{}ChainedIndex r{} ← r{}{}",
|
"{}ChainedIndex r{} ← r{}{}",
|
||||||
indent, params.dest, params.root, chain_display
|
indent, params.dest, params.root, chain_display
|
||||||
);
|
);
|
||||||
let comment_str = format!(
|
let comment_str = format!(
|
||||||
"Multi-level chained indexing: r{} → r{}",
|
"Multi-level chained indexing: r{} → r{}",
|
||||||
params.root, params.dest
|
params.root, params.dest
|
||||||
);
|
);
|
||||||
(base_str, comment_str)
|
(base_str, comment_str)
|
||||||
} else {
|
},
|
||||||
let base_str = format!("{}ChainedIndex chained_index", indent);
|
);
|
||||||
let comment_str = "Multi-level chained indexing (invalid params)".to_string();
|
|
||||||
(base_str, comment_str)
|
|
||||||
};
|
|
||||||
|
|
||||||
align_comment(&base, &comment, config.comment_column)
|
align_comment(&base, &comment, config.comment_column)
|
||||||
}
|
}
|
||||||
@@ -756,51 +807,59 @@ fn format_instruction_readable(
|
|||||||
let base = format!("{}Halt halt", indent);
|
let base = format!("{}Halt halt", indent);
|
||||||
align_comment(&base, "Stop execution", config.comment_column)
|
align_comment(&base, "Stop execution", config.comment_column)
|
||||||
}
|
}
|
||||||
Instruction::ComprehensionBegin { params_index } => {
|
Instruction::ComprehensionBegin { params_index } => instruction_data
|
||||||
if let Some(params) = instruction_data.get_comprehension_begin_params(params_index) {
|
.get_comprehension_begin_params(params_index)
|
||||||
let mode_str = match params.mode {
|
.map_or_else(
|
||||||
crate::rvm::instructions::ComprehensionMode::Array => "array",
|
|| {
|
||||||
crate::rvm::instructions::ComprehensionMode::Set => "set",
|
let base = format!("{}CompBegin [INVALID P({})] {{", indent, params_index);
|
||||||
crate::rvm::instructions::ComprehensionMode::Object => "object",
|
align_comment(
|
||||||
};
|
&base,
|
||||||
let (source_desc, result_desc) = if params.collection_reg == params.result_reg {
|
"ERROR: Invalid comprehension parameters",
|
||||||
(
|
config.comment_column,
|
||||||
format!("r{}", params.collection_reg),
|
|
||||||
format!("r{}", params.result_reg),
|
|
||||||
)
|
)
|
||||||
} else {
|
},
|
||||||
(
|
|params| {
|
||||||
format!("r{} (src)", params.collection_reg),
|
let mode_str = match params.mode {
|
||||||
format!("r{} (dst)", params.result_reg),
|
crate::rvm::instructions::ComprehensionMode::Array => "array",
|
||||||
)
|
crate::rvm::instructions::ComprehensionMode::Set => "set",
|
||||||
};
|
crate::rvm::instructions::ComprehensionMode::Object => "object",
|
||||||
let base = format!(
|
};
|
||||||
"{}CompBegin {} {} → {} k:{} v:{} {{",
|
let (source_desc, result_desc) = if params.collection_reg == params.result_reg {
|
||||||
indent, mode_str, source_desc, result_desc, params.key_reg, params.value_reg
|
(
|
||||||
);
|
format!("r{}", params.collection_reg),
|
||||||
let comment = format!(
|
format!("r{}", params.result_reg),
|
||||||
"{} comprehension in r{}, body: {}-{} (P{})",
|
)
|
||||||
mode_str,
|
} else {
|
||||||
params.collection_reg,
|
(
|
||||||
params.body_start,
|
format!("r{} (src)", params.collection_reg),
|
||||||
params.comprehension_end,
|
format!("r{} (dst)", params.result_reg),
|
||||||
params_index
|
)
|
||||||
);
|
};
|
||||||
align_comment(&base, &comment, config.comment_column)
|
let base = format!(
|
||||||
} else {
|
"{}CompBegin {} {} → {} k:{} v:{} {{",
|
||||||
let base = format!("{}CompBegin [INVALID P({})] {{", indent, params_index);
|
indent,
|
||||||
align_comment(
|
mode_str,
|
||||||
&base,
|
source_desc,
|
||||||
"ERROR: Invalid comprehension parameters",
|
result_desc,
|
||||||
config.comment_column,
|
params.key_reg,
|
||||||
)
|
params.value_reg
|
||||||
}
|
);
|
||||||
}
|
let comment = format!(
|
||||||
|
"{} comprehension in r{}, body: {}-{} (P{})",
|
||||||
|
mode_str,
|
||||||
|
params.collection_reg,
|
||||||
|
params.body_start,
|
||||||
|
params.comprehension_end,
|
||||||
|
params_index
|
||||||
|
);
|
||||||
|
align_comment(&base, &comment, config.comment_column)
|
||||||
|
},
|
||||||
|
),
|
||||||
Instruction::ComprehensionYield { value_reg, key_reg } => {
|
Instruction::ComprehensionYield { value_reg, key_reg } => {
|
||||||
let base = match key_reg {
|
let base = key_reg.map_or_else(
|
||||||
Some(k) => format!("{}CompYield r{} r{}", indent, k, value_reg),
|
|| format!("{}CompYield r{}", indent, value_reg),
|
||||||
None => format!("{}CompYield r{}", indent, value_reg),
|
|k| format!("{}CompYield r{} r{}", indent, k, value_reg),
|
||||||
};
|
);
|
||||||
align_comment(&base, "Yield value to comprehension", config.comment_column)
|
align_comment(&base, "Yield value to comprehension", config.comment_column)
|
||||||
}
|
}
|
||||||
Instruction::ComprehensionEnd {} => {
|
Instruction::ComprehensionEnd {} => {
|
||||||
@@ -919,9 +978,11 @@ const fn get_instruction_name(instruction: &Instruction) -> &'static str {
|
|||||||
Instruction::Contains { .. } => "CONTAINS",
|
Instruction::Contains { .. } => "CONTAINS",
|
||||||
Instruction::Count { .. } => "COUNT",
|
Instruction::Count { .. } => "COUNT",
|
||||||
Instruction::AssertEq { .. } => "ASSERT_EQ",
|
Instruction::AssertEq { .. } => "ASSERT_EQ",
|
||||||
Instruction::AssertNot { .. } => "ASSERT_NOT",
|
Instruction::Guard { mode, .. } => match mode {
|
||||||
Instruction::AssertCondition { .. } => "ASSERT",
|
crate::rvm::instructions::GuardMode::Not => "ASSERT_NOT",
|
||||||
Instruction::AssertNotUndefined { .. } => "ASSERT_NOT_UNDEF",
|
crate::rvm::instructions::GuardMode::Condition => "ASSERT",
|
||||||
|
crate::rvm::instructions::GuardMode::NotUndefined => "ASSERT_NOT_UNDEF",
|
||||||
|
},
|
||||||
Instruction::LoopStart { .. } => "LOOP_START",
|
Instruction::LoopStart { .. } => "LOOP_START",
|
||||||
Instruction::LoopNext { .. } => "LOOP_NEXT",
|
Instruction::LoopNext { .. } => "LOOP_NEXT",
|
||||||
Instruction::CallRule { .. } => "CALL_RULE",
|
Instruction::CallRule { .. } => "CALL_RULE",
|
||||||
@@ -974,14 +1035,15 @@ fn format_operation_compact(
|
|||||||
format!("{}r{} ← r{}[L{}]", indent, dest, container, literal_idx)
|
format!("{}r{} ← r{}[L{}]", indent, dest, container, literal_idx)
|
||||||
}
|
}
|
||||||
Instruction::LoopStart { params_index } => {
|
Instruction::LoopStart { params_index } => {
|
||||||
if let Some(params) = instruction_data.get_loop_params(params_index) {
|
instruction_data.get_loop_params(params_index).map_or_else(
|
||||||
format!(
|
|| format!("{}loop P({}) {{", indent, params_index),
|
||||||
"{}loop r{} in r{} {{",
|
|params| {
|
||||||
indent, params.value_reg, params.collection
|
format!(
|
||||||
)
|
"{}loop r{} in r{} {{",
|
||||||
} else {
|
indent, params.value_reg, params.collection
|
||||||
format!("{}loop P({}) {{", indent, params_index)
|
)
|
||||||
}
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Instruction::LoopNext { .. } => {
|
Instruction::LoopNext { .. } => {
|
||||||
format!("{}}}", indent)
|
format!("{}}}", indent)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
clippy::pattern_type_mismatch
|
clippy::pattern_type_mismatch
|
||||||
)] // tests unwrap conversions and slice math for brevity
|
)] // tests unwrap conversions and slice math for brevity
|
||||||
|
|
||||||
use crate::rvm::instructions::{Instruction, LoopMode};
|
use crate::rvm::instructions::{GuardMode, Instruction, LoopMode};
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
@@ -63,8 +63,10 @@ pub fn parse_instruction(text: &str) -> Result<Instruction> {
|
|||||||
"SetAdd" => parse_set_add(params_text),
|
"SetAdd" => parse_set_add(params_text),
|
||||||
"Contains" => parse_contains(params_text),
|
"Contains" => parse_contains(params_text),
|
||||||
"Count" => parse_count(params_text),
|
"Count" => parse_count(params_text),
|
||||||
"AssertCondition" => parse_assert_condition(params_text),
|
"AssertEq" => parse_assert_eq(params_text),
|
||||||
"AssertNotUndefined" => parse_assert_not_undefined(params_text),
|
"AssertNot" => parse_guard(params_text, GuardMode::Not),
|
||||||
|
"AssertCondition" => parse_guard(params_text, GuardMode::Condition),
|
||||||
|
"AssertNotUndefined" => parse_guard(params_text, GuardMode::NotUndefined),
|
||||||
"BuiltinCall" => parse_builtin_call(params_text),
|
"BuiltinCall" => parse_builtin_call(params_text),
|
||||||
"FunctionCall" => parse_function_call(params_text),
|
"FunctionCall" => parse_function_call(params_text),
|
||||||
"CallRule" => parse_call_rule(params_text),
|
"CallRule" => parse_call_rule(params_text),
|
||||||
@@ -464,20 +466,24 @@ fn parse_count(params_text: &str) -> Result<Instruction> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_assert_condition(params_text: &str) -> Result<Instruction> {
|
fn parse_assert_eq(params_text: &str) -> Result<Instruction> {
|
||||||
let params = parse_params(params_text)?;
|
let params = parse_params(params_text)?;
|
||||||
let condition = get_param_u16(¶ms, "condition")?;
|
let left: u8 = get_param_u16(¶ms, "left")?.try_into().unwrap();
|
||||||
Ok(Instruction::AssertCondition {
|
let right: u8 = get_param_u16(¶ms, "right")?.try_into().unwrap();
|
||||||
condition: condition.try_into().unwrap(),
|
Ok(Instruction::AssertEq { left, right })
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_assert_not_undefined(params_text: &str) -> Result<Instruction> {
|
fn parse_guard(params_text: &str, mode: GuardMode) -> Result<Instruction> {
|
||||||
let params = parse_params(params_text)?;
|
let params = parse_params(params_text)?;
|
||||||
let register = get_param_u16(¶ms, "register")?;
|
// Accept the original field name for each mode so YAML tests don't change.
|
||||||
Ok(Instruction::AssertNotUndefined {
|
let register: u8 = match mode {
|
||||||
register: register.try_into().unwrap(),
|
GuardMode::Not => get_param_u16(¶ms, "operand")?,
|
||||||
})
|
GuardMode::Condition => get_param_u16(¶ms, "condition")?,
|
||||||
|
GuardMode::NotUndefined => get_param_u16(¶ms, "register")?,
|
||||||
|
}
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
Ok(Instruction::Guard { register, mode })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_loop_start(params_text: &str) -> Result<Instruction> {
|
fn parse_loop_start(params_text: &str) -> Result<Instruction> {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
#![allow(clippy::pattern_type_mismatch)]
|
|
||||||
|
// `pattern_type_mismatch` requires explicit `&`+`ref` patterns for tuple matches
|
||||||
|
// on (&Value, &Value), which conflicts with `needless_borrowed_reference`.
|
||||||
|
// Disable both to keep patterns consistent within this file.
|
||||||
|
#![allow(clippy::pattern_type_mismatch, clippy::needless_borrowed_reference)]
|
||||||
|
|
||||||
use alloc::collections::BTreeSet;
|
use alloc::collections::BTreeSet;
|
||||||
|
|
||||||
@@ -14,7 +18,7 @@ impl RegoVM {
|
|||||||
/// Add two values using interpreter's arithmetic logic
|
/// Add two values using interpreter's arithmetic logic
|
||||||
pub(super) fn add_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
pub(super) fn add_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(Value::Number(x), Value::Number(y)) => Ok(Value::from(x.add(y)?)),
|
(&Value::Number(ref x), &Value::Number(ref y)) => Ok(Value::from(x.add(y)?)),
|
||||||
_ => Err(VmError::InvalidAddition {
|
_ => Err(VmError::InvalidAddition {
|
||||||
left: a.clone(),
|
left: a.clone(),
|
||||||
right: b.clone(),
|
right: b.clone(),
|
||||||
@@ -26,8 +30,8 @@ impl RegoVM {
|
|||||||
/// Subtract two values using interpreter's arithmetic logic
|
/// Subtract two values using interpreter's arithmetic logic
|
||||||
pub(super) fn sub_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
pub(super) fn sub_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(Value::Number(x), Value::Number(y)) => Ok(Value::from(x.sub(y)?)),
|
(&Value::Number(ref x), &Value::Number(ref y)) => Ok(Value::from(x.sub(y)?)),
|
||||||
(Value::Set(left), Value::Set(right)) => {
|
(&Value::Set(ref left), &Value::Set(ref right)) => {
|
||||||
let diff: BTreeSet<Value> = left.difference(right).cloned().collect();
|
let diff: BTreeSet<Value> = left.difference(right).cloned().collect();
|
||||||
Ok(Value::from_set(diff))
|
Ok(Value::from_set(diff))
|
||||||
}
|
}
|
||||||
@@ -42,7 +46,7 @@ impl RegoVM {
|
|||||||
/// Multiply two values using interpreter's arithmetic logic
|
/// Multiply two values using interpreter's arithmetic logic
|
||||||
pub(super) fn mul_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
pub(super) fn mul_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(Value::Number(x), Value::Number(y)) => Ok(Value::from(x.mul(y)?)),
|
(&Value::Number(ref x), &Value::Number(ref y)) => Ok(Value::from(x.mul(y)?)),
|
||||||
_ => Err(VmError::InvalidMultiplication {
|
_ => Err(VmError::InvalidMultiplication {
|
||||||
left: a.clone(),
|
left: a.clone(),
|
||||||
right: b.clone(),
|
right: b.clone(),
|
||||||
@@ -54,7 +58,7 @@ impl RegoVM {
|
|||||||
/// Divide two values using interpreter's arithmetic logic
|
/// Divide two values using interpreter's arithmetic logic
|
||||||
pub(super) fn div_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
pub(super) fn div_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(Value::Number(x), Value::Number(y)) => {
|
(&Value::Number(ref x), &Value::Number(ref y)) => {
|
||||||
if *y == Number::from(0_u64) {
|
if *y == Number::from(0_u64) {
|
||||||
if self.strict_builtin_errors {
|
if self.strict_builtin_errors {
|
||||||
return Err(VmError::InvalidDivision {
|
return Err(VmError::InvalidDivision {
|
||||||
@@ -79,7 +83,7 @@ impl RegoVM {
|
|||||||
/// Modulo two values using interpreter's arithmetic logic
|
/// Modulo two values using interpreter's arithmetic logic
|
||||||
pub(super) fn mod_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
pub(super) fn mod_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(Value::Number(x), Value::Number(y)) => {
|
(&Value::Number(ref x), &Value::Number(ref y)) => {
|
||||||
if *y == Number::from(0_u64) {
|
if *y == Number::from(0_u64) {
|
||||||
if self.strict_builtin_errors {
|
if self.strict_builtin_errors {
|
||||||
return Err(VmError::InvalidModulo {
|
return Err(VmError::InvalidModulo {
|
||||||
@@ -110,8 +114,8 @@ impl RegoVM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) const fn to_bool(&self, value: &Value) -> Option<bool> {
|
pub(super) const fn to_bool(&self, value: &Value) -> Option<bool> {
|
||||||
match value {
|
match *value {
|
||||||
Value::Bool(b) => Some(*b),
|
Value::Bool(b) => Some(b),
|
||||||
Value::Null if !self.strict_builtin_errors => Some(true),
|
Value::Null if !self.strict_builtin_errors => Some(true),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -435,6 +435,8 @@ impl RegoVM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.set_register(result_reg_idx, current_result)?;
|
||||||
|
|
||||||
let (iteration_state_snapshot, body_start, comprehension_end) = {
|
let (iteration_state_snapshot, body_start, comprehension_end) = {
|
||||||
let frame = self.execution_stack.get_mut(comprehension_index).ok_or(
|
let frame = self.execution_stack.get_mut(comprehension_index).ok_or(
|
||||||
VmError::InvalidIteration {
|
VmError::InvalidIteration {
|
||||||
@@ -485,8 +487,6 @@ impl RegoVM {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.set_register(result_reg_idx, current_result)?;
|
|
||||||
|
|
||||||
if let Some(state) = iteration_state_snapshot.as_ref() {
|
if let Some(state) = iteration_state_snapshot.as_ref() {
|
||||||
let has_next = self.setup_next_iteration(state, key_reg_idx, value_reg_idx)?;
|
let has_next = self.setup_next_iteration(state, key_reg_idx, value_reg_idx)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// 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::rvm::program::Program;
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use alloc::collections::BTreeSet;
|
use alloc::collections::BTreeSet;
|
||||||
@@ -26,6 +26,7 @@ impl RegoVM {
|
|||||||
program: &Program,
|
program: &Program,
|
||||||
instruction: Instruction,
|
instruction: Instruction,
|
||||||
) -> Result<InstructionOutcome> {
|
) -> Result<InstructionOutcome> {
|
||||||
|
self.memory_check()?;
|
||||||
self.execute_load_and_move(program, instruction)
|
self.execute_load_and_move(program, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,9 +318,6 @@ impl RegoVM {
|
|||||||
}
|
}
|
||||||
Not { dest, operand } => {
|
Not { dest, operand } => {
|
||||||
let operand_value = self.get_register(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 {
|
let negated = match *operand_value {
|
||||||
Value::Undefined => true,
|
Value::Undefined => true,
|
||||||
Value::Bool(b) => !b,
|
Value::Bool(b) => !b,
|
||||||
@@ -335,35 +333,24 @@ impl RegoVM {
|
|||||||
self.handle_condition(passed)?;
|
self.handle_condition(passed)?;
|
||||||
Ok(InstructionOutcome::Continue)
|
Ok(InstructionOutcome::Continue)
|
||||||
}
|
}
|
||||||
AssertNot { operand } => {
|
Guard { register, mode } => {
|
||||||
let value = self.get_register(operand)?;
|
let value = self.get_register(register)?;
|
||||||
let passed = match *value {
|
let passed = match mode {
|
||||||
Value::Undefined => true,
|
GuardMode::Not => match *value {
|
||||||
Value::Bool(b) => !b,
|
Value::Undefined => true,
|
||||||
_ => false,
|
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)?;
|
self.handle_condition(passed)?;
|
||||||
Ok(InstructionOutcome::Continue)
|
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),
|
other => self.execute_call_instruction(program, other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -747,6 +734,7 @@ impl RegoVM {
|
|||||||
available: loop_params_len,
|
available: loop_params_len,
|
||||||
})?;
|
})?;
|
||||||
let mode = loop_params.mode;
|
let mode = loop_params.mode;
|
||||||
|
|
||||||
let params = LoopParams {
|
let params = LoopParams {
|
||||||
collection: loop_params.collection,
|
collection: loop_params.collection,
|
||||||
key_reg: loop_params.key_reg,
|
key_reg: loop_params.key_reg,
|
||||||
|
|||||||
@@ -161,7 +161,6 @@ impl RegoVM {
|
|||||||
self.reset_execution_state();
|
self.reset_execution_state();
|
||||||
self.reset_execution_timer_state();
|
self.reset_execution_timer_state();
|
||||||
self.execution_state = ExecutionState::Running;
|
self.execution_state = ExecutionState::Running;
|
||||||
self.enforce_memory_check()?;
|
|
||||||
match self.jump_to(0_u32) {
|
match self.jump_to(0_u32) {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
self.execution_state = ExecutionState::Completed {
|
self.execution_state = ExecutionState::Completed {
|
||||||
@@ -180,7 +179,6 @@ impl RegoVM {
|
|||||||
self.reset_execution_state();
|
self.reset_execution_state();
|
||||||
self.reset_execution_timer_state();
|
self.reset_execution_timer_state();
|
||||||
self.execution_state = ExecutionState::Running;
|
self.execution_state = ExecutionState::Running;
|
||||||
self.enforce_memory_check()?;
|
|
||||||
match self.run_stackless_from(0) {
|
match self.run_stackless_from(0) {
|
||||||
Ok(result) => Ok(result),
|
Ok(result) => Ok(result),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -193,7 +191,6 @@ impl RegoVM {
|
|||||||
fn execute_suspendable_entry(&mut self, entry_point_pc: usize) -> Result<Value> {
|
fn execute_suspendable_entry(&mut self, entry_point_pc: usize) -> Result<Value> {
|
||||||
self.execution_state = ExecutionState::Running;
|
self.execution_state = ExecutionState::Running;
|
||||||
self.reset_execution_timer_state();
|
self.reset_execution_timer_state();
|
||||||
self.enforce_memory_check()?;
|
|
||||||
match self.run_stackless_from(entry_point_pc) {
|
match self.run_stackless_from(entry_point_pc) {
|
||||||
Ok(result) => Ok(result),
|
Ok(result) => Ok(result),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -204,18 +201,15 @@ impl RegoVM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn resume(&mut self, resume_value: Option<Value>) -> Result<Value> {
|
pub fn resume(&mut self, resume_value: Option<Value>) -> Result<Value> {
|
||||||
let old_state = core::mem::replace(&mut self.execution_state, ExecutionState::Running);
|
let (reason, mut last_result) = match self.execution_state.clone() {
|
||||||
let (reason, mut last_result) = match old_state {
|
|
||||||
ExecutionState::Suspended {
|
ExecutionState::Suspended {
|
||||||
reason,
|
reason,
|
||||||
last_result,
|
last_result,
|
||||||
..
|
..
|
||||||
} => (reason, last_result),
|
} => (reason, last_result),
|
||||||
current_state => {
|
current_state => {
|
||||||
let desc = alloc::format!("{:?}", current_state);
|
|
||||||
self.execution_state = current_state;
|
|
||||||
return Err(VmError::InvalidResumeState {
|
return Err(VmError::InvalidResumeState {
|
||||||
state: desc,
|
state: alloc::format!("{:?}", current_state),
|
||||||
pc: self.pc,
|
pc: self.pc,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,16 @@ use super::errors::{Result, VmError};
|
|||||||
use super::execution_model::{ExecutionFrame, ExecutionMode, FrameKind};
|
use super::execution_model::{ExecutionFrame, ExecutionMode, FrameKind};
|
||||||
use super::machine::RegoVM;
|
use super::machine::RegoVM;
|
||||||
|
|
||||||
|
/// Result for a loop over a non-iterable value (null, string, number, bool, Undefined).
|
||||||
|
/// `Every` over empty is vacuously `true`.
|
||||||
|
#[inline]
|
||||||
|
const fn non_collection_result(mode: &LoopMode) -> Value {
|
||||||
|
match *mode {
|
||||||
|
LoopMode::Every => Value::Bool(true),
|
||||||
|
LoopMode::Any | LoopMode::ForEach => Value::Bool(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_body_resume_pc(loop_start_pc: usize, body_start: u16) -> usize {
|
fn compute_body_resume_pc(loop_start_pc: usize, body_start: u16) -> usize {
|
||||||
if body_start == 0 {
|
if body_start == 0 {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -72,49 +82,11 @@ impl RegoVM {
|
|||||||
mode: &LoopMode,
|
mode: &LoopMode,
|
||||||
params: LoopParams,
|
params: LoopParams,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let initial_result = match *mode {
|
self.set_register(params.result_reg, Value::Bool(false))?;
|
||||||
LoopMode::Any | LoopMode::Every | LoopMode::ForEach => Value::Bool(false),
|
|
||||||
};
|
|
||||||
self.set_register(params.result_reg, initial_result.clone())?;
|
|
||||||
let collection_value = self.get_register(params.collection)?.clone();
|
|
||||||
|
|
||||||
let iteration_state = match collection_value {
|
let iteration_state = match self.resolve_iteration_state(mode, ¶ms)? {
|
||||||
Value::Array(ref items) => {
|
Some(state) => state,
|
||||||
if items.is_empty() {
|
None => return Ok(()),
|
||||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
IterationState::Array {
|
|
||||||
items: items.clone(),
|
|
||||||
index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Object(ref obj) => {
|
|
||||||
if obj.is_empty() {
|
|
||||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
IterationState::Object {
|
|
||||||
obj: obj.clone(),
|
|
||||||
current_key: None,
|
|
||||||
first_iteration: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Set(ref set) => {
|
|
||||||
if set.is_empty() {
|
|
||||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
IterationState::Set {
|
|
||||||
items: set.clone(),
|
|
||||||
current_item: None,
|
|
||||||
first_iteration: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let has_next =
|
let has_next =
|
||||||
@@ -169,13 +141,9 @@ impl RegoVM {
|
|||||||
let action = Self::determine_loop_action(&loop_ctx.mode, iteration_succeeded);
|
let action = Self::determine_loop_action(&loop_ctx.mode, iteration_succeeded);
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
LoopAction::ExitWithSuccess => {
|
LoopAction::ExitWithSuccess | LoopAction::ExitWithFailure => {
|
||||||
self.set_register(loop_ctx.result_reg, Value::Bool(true))?;
|
let result_value = matches!(action, LoopAction::ExitWithSuccess);
|
||||||
self.pc = usize::from(loop_end_local.saturating_sub(1));
|
self.set_register(loop_ctx.result_reg, Value::Bool(result_value))?;
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
LoopAction::ExitWithFailure => {
|
|
||||||
self.set_register(loop_ctx.result_reg, Value::Bool(false))?;
|
|
||||||
self.pc = usize::from(loop_end_local.saturating_sub(1));
|
self.pc = usize::from(loop_end_local.saturating_sub(1));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -236,50 +204,11 @@ impl RegoVM {
|
|||||||
mode: &LoopMode,
|
mode: &LoopMode,
|
||||||
params: LoopParams,
|
params: LoopParams,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let initial_result = match *mode {
|
self.set_register(params.result_reg, Value::Bool(false))?;
|
||||||
LoopMode::Any | LoopMode::Every | LoopMode::ForEach => Value::Bool(false),
|
|
||||||
};
|
|
||||||
self.set_register(params.result_reg, initial_result.clone())?;
|
|
||||||
|
|
||||||
let collection_value = self.get_register(params.collection)?.clone();
|
let iteration_state = match self.resolve_iteration_state(mode, ¶ms)? {
|
||||||
|
Some(state) => state,
|
||||||
let iteration_state = match collection_value {
|
None => return Ok(()),
|
||||||
Value::Array(ref items) => {
|
|
||||||
if items.is_empty() {
|
|
||||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
IterationState::Array {
|
|
||||||
items: items.clone(),
|
|
||||||
index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Object(ref obj) => {
|
|
||||||
if obj.is_empty() {
|
|
||||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
IterationState::Object {
|
|
||||||
obj: obj.clone(),
|
|
||||||
current_key: None,
|
|
||||||
first_iteration: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Set(ref set) => {
|
|
||||||
if set.is_empty() {
|
|
||||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
IterationState::Set {
|
|
||||||
items: set.clone(),
|
|
||||||
current_item: None,
|
|
||||||
first_iteration: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let has_next =
|
let has_next =
|
||||||
@@ -367,21 +296,9 @@ impl RegoVM {
|
|||||||
let action = Self::determine_loop_action(&loop_mode, iteration_succeeded);
|
let action = Self::determine_loop_action(&loop_mode, iteration_succeeded);
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
LoopAction::ExitWithSuccess => {
|
LoopAction::ExitWithSuccess | LoopAction::ExitWithFailure => {
|
||||||
self.set_register(result_reg, Value::Bool(true))?;
|
let result_value = matches!(action, LoopAction::ExitWithSuccess);
|
||||||
let completed_frame = self
|
self.set_register(result_reg, Value::Bool(result_value))?;
|
||||||
.execution_stack
|
|
||||||
.pop()
|
|
||||||
.ok_or(VmError::AssertionFailed { pc: self.pc })?;
|
|
||||||
if let Some(parent) = self.execution_stack.last_mut() {
|
|
||||||
parent.pc = resume_pc;
|
|
||||||
self.frame_pc_overridden = true;
|
|
||||||
}
|
|
||||||
drop(completed_frame);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
LoopAction::ExitWithFailure => {
|
|
||||||
self.set_register(result_reg, Value::Bool(false))?;
|
|
||||||
let completed_frame = self
|
let completed_frame = self
|
||||||
.execution_stack
|
.execution_stack
|
||||||
.pop()
|
.pop()
|
||||||
@@ -496,6 +413,60 @@ impl RegoVM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve the collection value into an `IterationState`, handling empty
|
||||||
|
/// collections and non-iterable values.
|
||||||
|
///
|
||||||
|
/// Returns `Some(state)` when iteration should proceed, or `None` when the
|
||||||
|
/// loop was short-circuited (registers and PC already adjusted).
|
||||||
|
fn resolve_iteration_state(
|
||||||
|
&mut self,
|
||||||
|
mode: &LoopMode,
|
||||||
|
params: &LoopParams,
|
||||||
|
) -> Result<Option<IterationState>> {
|
||||||
|
let collection_value = self.get_register(params.collection)?.clone();
|
||||||
|
|
||||||
|
match collection_value {
|
||||||
|
Value::Array(ref items) => {
|
||||||
|
if items.is_empty() {
|
||||||
|
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(Some(IterationState::Array {
|
||||||
|
items: items.clone(),
|
||||||
|
index: 0,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Value::Object(ref obj) => {
|
||||||
|
if obj.is_empty() {
|
||||||
|
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(Some(IterationState::Object {
|
||||||
|
obj: obj.clone(),
|
||||||
|
current_key: None,
|
||||||
|
first_iteration: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Value::Set(ref set) => {
|
||||||
|
if set.is_empty() {
|
||||||
|
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(Some(IterationState::Set {
|
||||||
|
items: set.clone(),
|
||||||
|
current_item: None,
|
||||||
|
first_iteration: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let result = non_collection_result(mode);
|
||||||
|
self.set_register(params.result_reg, result)?;
|
||||||
|
self.pc = usize::from(params.loop_end).saturating_sub(1);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_empty_collection(
|
fn handle_empty_collection(
|
||||||
&mut self,
|
&mut self,
|
||||||
mode: &LoopMode,
|
mode: &LoopMode,
|
||||||
|
|||||||
@@ -524,21 +524,11 @@ impl RegoVM {
|
|||||||
limits::check_memory_limit_if_needed().map_err(|err| self.map_limit_error(err))
|
limits::check_memory_limit_if_needed().map_err(|err| self.map_limit_error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "allocator-memory-limits", not(miri)))]
|
|
||||||
pub(super) fn enforce_memory_check(&mut self) -> Result<()> {
|
|
||||||
limits::enforce_memory_limit().map_err(|err| self.map_limit_error(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(miri, not(feature = "allocator-memory-limits")))]
|
#[cfg(any(miri, not(feature = "allocator-memory-limits")))]
|
||||||
pub(super) fn memory_check(&mut self) -> Result<()> {
|
pub(super) fn memory_check(&mut self) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(miri, not(feature = "allocator-memory-limits")))]
|
|
||||||
pub(super) fn enforce_memory_check(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get or create the cached dummy span for builtin calls.
|
/// Get or create the cached dummy span for builtin calls.
|
||||||
pub(super) fn get_dummy_span(&mut self) -> Result<&crate::lexer::Span> {
|
pub(super) fn get_dummy_span(&mut self) -> Result<&crate::lexer::Span> {
|
||||||
if self.dummy_span.is_none() {
|
if self.dummy_span.is_none() {
|
||||||
|
|||||||
@@ -158,17 +158,16 @@ impl RegoVM {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone the Arc (cheap atomic increment) so we can borrow &RuleInfo
|
let rule_info = self
|
||||||
// without holding an immutable borrow on self.
|
.program
|
||||||
let program = self.program.clone();
|
|
||||||
let rule_info = program
|
|
||||||
.rule_infos
|
.rule_infos
|
||||||
.get(rule_idx)
|
.get(rule_idx)
|
||||||
.ok_or(VmError::RuleInfoMissing {
|
.ok_or(VmError::RuleInfoMissing {
|
||||||
index: rule_index,
|
index: rule_index,
|
||||||
pc: self.pc,
|
pc: self.pc,
|
||||||
available: program.rule_infos.len(),
|
available: self.program.rule_infos.len(),
|
||||||
})?;
|
})?
|
||||||
|
.clone();
|
||||||
|
|
||||||
let is_function_rule = rule_info.function_info.is_some();
|
let is_function_rule = rule_info.function_info.is_some();
|
||||||
|
|
||||||
@@ -220,7 +219,7 @@ impl RegoVM {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let (final_result, rule_failed_due_to_inconsistency) = self
|
let (final_result, rule_failed_due_to_inconsistency) = self
|
||||||
.execute_rule_definitions_common(&rule_definitions, rule_info, function_call_params)?;
|
.execute_rule_definitions_common(&rule_definitions, &rule_info, function_call_params)?;
|
||||||
|
|
||||||
self.set_register(dest, Value::Undefined)?;
|
self.set_register(dest, Value::Undefined)?;
|
||||||
|
|
||||||
@@ -236,7 +235,7 @@ impl RegoVM {
|
|||||||
Value::Undefined
|
Value::Undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
self.set_register(dest, result_from_rule)?;
|
self.set_register(dest, result_from_rule.clone())?;
|
||||||
|
|
||||||
if self.get_register(dest)? == &Value::Undefined && !rule_failed_due_to_inconsistency {
|
if self.get_register(dest)? == &Value::Undefined && !rule_failed_due_to_inconsistency {
|
||||||
match call_context.rule_type {
|
match call_context.rule_type {
|
||||||
@@ -278,7 +277,7 @@ impl RegoVM {
|
|||||||
pc: self.pc,
|
pc: self.pc,
|
||||||
available,
|
available,
|
||||||
})?;
|
})?;
|
||||||
*entry = (true, final_value);
|
*entry = (true, final_value.clone());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -307,15 +306,16 @@ impl RegoVM {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let program = self.program.clone();
|
let rule_info = self
|
||||||
let rule_info = program
|
.program
|
||||||
.rule_infos
|
.rule_infos
|
||||||
.get(rule_idx)
|
.get(rule_idx)
|
||||||
.ok_or(VmError::RuleInfoMissing {
|
.ok_or(VmError::RuleInfoMissing {
|
||||||
index: rule_index,
|
index: rule_index,
|
||||||
pc: self.pc,
|
pc: self.pc,
|
||||||
available: program.rule_infos.len(),
|
available: self.program.rule_infos.len(),
|
||||||
})?;
|
})?
|
||||||
|
.clone();
|
||||||
|
|
||||||
let is_function_rule = rule_info.function_info.is_some();
|
let is_function_rule = rule_info.function_info.is_some();
|
||||||
|
|
||||||
@@ -430,7 +430,7 @@ impl RegoVM {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let initial_pc = self
|
let initial_pc = self
|
||||||
.prepare_rule_frame_initial_pc(&mut frame_data, rule_info)?
|
.prepare_rule_frame_initial_pc(&mut frame_data, &rule_info)?
|
||||||
.ok_or(VmError::RuleFrameMissingInitialPc { pc: self.pc })?;
|
.ok_or(VmError::RuleFrameMissingInitialPc { pc: self.pc })?;
|
||||||
|
|
||||||
let frame = ExecutionFrame::new(initial_pc, FrameKind::Rule(frame_data));
|
let frame = ExecutionFrame::new(initial_pc, FrameKind::Rule(frame_data));
|
||||||
@@ -651,15 +651,16 @@ impl RegoVM {
|
|||||||
} = frame_data;
|
} = frame_data;
|
||||||
|
|
||||||
let rule_idx = usize::from(rule_index);
|
let rule_idx = usize::from(rule_index);
|
||||||
let program = self.program.clone();
|
let rule_info = self
|
||||||
let rule_info = program
|
.program
|
||||||
.rule_infos
|
.rule_infos
|
||||||
.get(rule_idx)
|
.get(rule_idx)
|
||||||
.ok_or(VmError::RuleInfoMissing {
|
.ok_or(VmError::RuleInfoMissing {
|
||||||
index: rule_index,
|
index: rule_index,
|
||||||
pc: self.pc,
|
pc: self.pc,
|
||||||
available: program.rule_infos.len(),
|
available: self.program.rule_infos.len(),
|
||||||
})?;
|
})?
|
||||||
|
.clone();
|
||||||
|
|
||||||
let result_from_rule = if rule_failed_due_to_inconsistency {
|
let result_from_rule = if rule_failed_due_to_inconsistency {
|
||||||
Value::Undefined
|
Value::Undefined
|
||||||
@@ -771,7 +772,6 @@ impl RegoVM {
|
|||||||
pc: self.pc,
|
pc: self.pc,
|
||||||
available,
|
available,
|
||||||
})?;
|
})?;
|
||||||
// Clone into cache; return the original below.
|
|
||||||
*entry = (true, final_value.clone());
|
*entry = (true, final_value.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,20 +790,12 @@ impl RegoVM {
|
|||||||
&mut self,
|
&mut self,
|
||||||
frame_data: &mut RuleFrameData,
|
frame_data: &mut RuleFrameData,
|
||||||
) -> Result<Option<usize>> {
|
) -> Result<Option<usize>> {
|
||||||
let program = self.program.clone();
|
let rule_info = self.get_rule_info(frame_data.rule_index)?;
|
||||||
let rule_info = program
|
|
||||||
.rule_infos
|
|
||||||
.get(usize::from(frame_data.rule_index))
|
|
||||||
.ok_or(VmError::RuleInfoMissing {
|
|
||||||
index: frame_data.rule_index,
|
|
||||||
pc: self.pc,
|
|
||||||
available: program.rule_infos.len(),
|
|
||||||
})?;
|
|
||||||
match frame_data.phase {
|
match frame_data.phase {
|
||||||
RuleFramePhase::ExecutingDestructuring => {
|
RuleFramePhase::ExecutingDestructuring => {
|
||||||
self.rule_frame_after_destructuring_success(frame_data, rule_info)
|
self.rule_frame_after_destructuring_success(frame_data, &rule_info)
|
||||||
}
|
}
|
||||||
RuleFramePhase::ExecutingBody => self.rule_frame_after_success(frame_data, rule_info),
|
RuleFramePhase::ExecutingBody => self.rule_frame_after_success(frame_data, &rule_info),
|
||||||
RuleFramePhase::Initializing | RuleFramePhase::Finalizing => Ok(None),
|
RuleFramePhase::Initializing | RuleFramePhase::Finalizing => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -812,16 +804,21 @@ impl RegoVM {
|
|||||||
&mut self,
|
&mut self,
|
||||||
frame_data: &mut RuleFrameData,
|
frame_data: &mut RuleFrameData,
|
||||||
) -> Result<Option<usize>> {
|
) -> Result<Option<usize>> {
|
||||||
let program = self.program.clone();
|
let rule_info = self.get_rule_info(frame_data.rule_index)?;
|
||||||
let rule_info = program
|
self.rule_frame_after_failure(frame_data, &rule_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rule_info(&self, rule_index: u16) -> Result<RuleInfo> {
|
||||||
|
let idx = usize::from(rule_index);
|
||||||
|
self.program
|
||||||
.rule_infos
|
.rule_infos
|
||||||
.get(usize::from(frame_data.rule_index))
|
.get(idx)
|
||||||
|
.cloned()
|
||||||
.ok_or(VmError::RuleInfoMissing {
|
.ok_or(VmError::RuleInfoMissing {
|
||||||
index: frame_data.rule_index,
|
index: rule_index,
|
||||||
pc: self.pc,
|
pc: self.pc,
|
||||||
available: program.rule_infos.len(),
|
available: self.program.rule_infos.len(),
|
||||||
})?;
|
})
|
||||||
self.rule_frame_after_failure(frame_data, rule_info)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn checked_add_one(&self, value: usize, context: &'static str) -> Result<usize> {
|
pub(super) fn checked_add_one(&self, value: usize, context: &'static str) -> Result<usize> {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#![cfg(feature = "rvm")]
|
#![cfg(feature = "rvm")]
|
||||||
|
|
||||||
use regorus::languages::rego::compiler::Compiler;
|
use regorus::languages::rego::compiler::Compiler;
|
||||||
|
use regorus::rvm::instructions::GuardMode;
|
||||||
use regorus::rvm::Instruction;
|
use regorus::rvm::Instruction;
|
||||||
use regorus::{Engine, Rc, Value};
|
use regorus::{Engine, Rc, Value};
|
||||||
use std::collections::BTreeSet;
|
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.
|
/// Count occurrences of a specific instruction pattern in the program.
|
||||||
fn count_instructions(
|
fn count_instructions(
|
||||||
@@ -138,56 +143,76 @@ fn count_instructions(
|
|||||||
program.instructions.iter().filter(|i| pred(i)).count()
|
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: ®orus::rvm::program::Program) -> bool {
|
||||||
|
program.instructions.windows(2).any(|w| {
|
||||||
|
matches!(w[0], Instruction::Eq { .. })
|
||||||
|
&& matches!(
|
||||||
|
w[1],
|
||||||
|
Instruction::Guard {
|
||||||
|
mode: GuardMode::Condition,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn equality_check_emits_assert_eq() {
|
fn equality_check_emits_eq_guard() {
|
||||||
// Assignment `x = 1` followed by `x = 1` triggers EqualityCheck in destructuring.
|
// Assignment `x = 1` followed by `x = 1` triggers Eq + Guard(Condition).
|
||||||
let program = compile_rule(
|
let program = compile_rule(
|
||||||
r#"
|
r#"
|
||||||
package test
|
package test
|
||||||
p if { x = 1; x = 1 }
|
p if { x = 1; x = 1 }
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
let assert_eq_count =
|
|
||||||
count_instructions(&program, |i| matches!(i, Instruction::AssertEq { .. }));
|
|
||||||
assert!(
|
assert!(
|
||||||
assert_eq_count > 0,
|
has_eq_guard_condition(&program),
|
||||||
"expected AssertEq instruction for equality check"
|
"expected Eq + Guard(Condition) pair for equality check"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn destructuring_equality_emits_assert_eq() {
|
fn destructuring_equality_emits_eq_guard() {
|
||||||
let program = compile_rule(
|
let program = compile_rule(
|
||||||
r#"
|
r#"
|
||||||
package test
|
package test
|
||||||
p if { [1, x] := [1, 2] }
|
p if { [1, x] := [1, 2] }
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
let assert_eq_count =
|
|
||||||
count_instructions(&program, |i| matches!(i, Instruction::AssertEq { .. }));
|
|
||||||
assert!(
|
assert!(
|
||||||
assert_eq_count > 0,
|
has_eq_guard_condition(&program),
|
||||||
"expected AssertEq for destructuring equality"
|
"expected Eq + Guard(Condition) for destructuring equality"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn not_expr_emits_assert_not() {
|
fn not_expr_emits_not_plus_guard_condition() {
|
||||||
let program = compile_rule(
|
let program = compile_rule(
|
||||||
r#"
|
r#"
|
||||||
package test
|
package test
|
||||||
p if { not false }
|
p if { not false }
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
let assert_not_count =
|
// The compiler emits Not { dest, operand } + Guard { register: dest, mode: Condition }.
|
||||||
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.
|
|
||||||
let not_count = count_instructions(&program, |i| matches!(i, Instruction::Not { .. }));
|
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 ---
|
// --- B-11: early_exit_on_first_success flag tests ---
|
||||||
|
|||||||
Reference in New Issue
Block a user