mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
126cc12eb5
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.
267 lines
9.1 KiB
Rust
267 lines
9.1 KiB
Rust
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
#![allow(
|
|
clippy::indexing_slicing,
|
|
clippy::as_conversions,
|
|
clippy::pattern_type_mismatch
|
|
)]
|
|
|
|
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;
|
|
use alloc::vec::Vec;
|
|
|
|
impl<'a> Compiler<'a> {
|
|
pub(super) fn compile_query(&mut self, query: &Query) -> Result<()> {
|
|
self.push_scope();
|
|
|
|
let result = {
|
|
let schedule = match &self.policy.inner.schedule {
|
|
Some(s) => s
|
|
.queries
|
|
.get_checked(self.current_module_index, query.qidx)
|
|
.map_err(|err| {
|
|
CompilerError::General {
|
|
message: format!("schedule out of bounds: {err}"),
|
|
}
|
|
.at(&query.span)
|
|
})?,
|
|
None => None,
|
|
};
|
|
|
|
let ordered_stmts: Vec<&LiteralStmt> = match schedule {
|
|
Some(schedule) => schedule
|
|
.order
|
|
.iter()
|
|
.map(|i| &query.stmts[*i as usize])
|
|
.collect(),
|
|
None => query.stmts.iter().collect(),
|
|
};
|
|
self.hoist_loops_and_compile_statements(&ordered_stmts)
|
|
};
|
|
|
|
self.pop_scope();
|
|
|
|
result
|
|
}
|
|
|
|
pub(super) fn hoist_loops_and_compile_statements(
|
|
&mut self,
|
|
stmts: &[&LiteralStmt],
|
|
) -> Result<()> {
|
|
for (idx, stmt) in stmts.iter().enumerate() {
|
|
if !stmt.with_mods.is_empty() {
|
|
return Err(CompilerError::WithKeywordUnsupported.at(&stmt.span));
|
|
}
|
|
let loop_exprs = self.get_statement_loops(stmt)?;
|
|
|
|
if !loop_exprs.is_empty() {
|
|
return self.compile_hoisted_loops(&stmts[idx..], &loop_exprs);
|
|
}
|
|
|
|
if matches!(&stmt.literal, ast::Literal::SomeIn { .. }) {
|
|
if let ast::Literal::SomeIn {
|
|
ref key,
|
|
ref value,
|
|
ref collection,
|
|
..
|
|
} = &stmt.literal
|
|
{
|
|
self.compile_some_in_loop_with_remaining_statements(
|
|
key,
|
|
value,
|
|
collection,
|
|
&stmts[idx..],
|
|
)?;
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
self.compile_single_statement(stmt)?;
|
|
}
|
|
|
|
self.hoist_loops_and_emit_context_yield()
|
|
}
|
|
|
|
pub(super) fn hoist_loops_and_emit_context_yield(&mut self) -> Result<()> {
|
|
if let Some(context) = self.context_stack.last() {
|
|
match &context.context_type {
|
|
ContextType::Every => {
|
|
return Ok(());
|
|
}
|
|
ContextType::Rule(_) | ContextType::Comprehension(_) => {}
|
|
}
|
|
}
|
|
|
|
let (key_expr, value_expr) = match self.context_stack.last_mut() {
|
|
Some(context) => {
|
|
if context.key_value_loops_hoisted {
|
|
return self.emit_context_yield();
|
|
}
|
|
(context.key_expr.clone(), context.value_expr.clone())
|
|
}
|
|
None => return Ok(()),
|
|
};
|
|
|
|
let mut key_value_loops = Vec::new();
|
|
|
|
if let Some(expr) = key_expr.as_ref() {
|
|
key_value_loops.extend(self.get_expr_loops(expr)?);
|
|
}
|
|
|
|
if let Some(expr) = value_expr.as_ref() {
|
|
key_value_loops.extend(self.get_expr_loops(expr)?);
|
|
}
|
|
|
|
if !key_value_loops.is_empty() {
|
|
if let Some(context) = self.context_stack.last_mut() {
|
|
context.key_value_loops_hoisted = true;
|
|
}
|
|
self.compile_hoisted_loops(&[], &key_value_loops)
|
|
} else {
|
|
self.emit_context_yield()
|
|
}
|
|
}
|
|
|
|
pub(super) fn emit_context_yield(&mut self) -> Result<()> {
|
|
if let Some(context) = self.context_stack.last().cloned() {
|
|
let dest_register = context.dest_register;
|
|
let span = &context.span;
|
|
let value_register = match context.value_expr {
|
|
Some(expr) => self.compile_rego_expr(&expr)?,
|
|
None => {
|
|
let value_reg = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::LoadBool {
|
|
dest: value_reg,
|
|
value: true,
|
|
},
|
|
span,
|
|
);
|
|
value_reg
|
|
}
|
|
};
|
|
|
|
let key_register = context
|
|
.key_expr
|
|
.map(|key_expr| self.compile_rego_expr(&key_expr))
|
|
.unwrap_or(Ok(value_register))?;
|
|
|
|
match context.context_type {
|
|
ContextType::Comprehension(ComprehensionType::Array) => {
|
|
self.emit_instruction(
|
|
Instruction::ComprehensionYield {
|
|
value_reg: value_register,
|
|
key_reg: None,
|
|
},
|
|
span,
|
|
);
|
|
}
|
|
ContextType::Comprehension(ComprehensionType::Set) => {
|
|
self.emit_instruction(
|
|
Instruction::ComprehensionYield {
|
|
value_reg: value_register,
|
|
key_reg: None,
|
|
},
|
|
span,
|
|
);
|
|
}
|
|
ContextType::Rule(RuleType::PartialSet) => {
|
|
self.emit_instruction(
|
|
Instruction::SetAdd {
|
|
set: dest_register,
|
|
value: value_register,
|
|
},
|
|
span,
|
|
);
|
|
}
|
|
ContextType::Comprehension(ComprehensionType::Object) => {
|
|
self.emit_instruction(
|
|
Instruction::ComprehensionYield {
|
|
value_reg: value_register,
|
|
key_reg: Some(key_register),
|
|
},
|
|
span,
|
|
);
|
|
}
|
|
ContextType::Rule(RuleType::PartialObject) => {
|
|
self.emit_instruction(
|
|
Instruction::ObjectSet {
|
|
obj: dest_register,
|
|
key: key_register,
|
|
value: value_register,
|
|
},
|
|
span,
|
|
);
|
|
}
|
|
ContextType::Rule(RuleType::Complete) => {
|
|
self.emit_instruction(
|
|
Instruction::Move {
|
|
dest: dest_register,
|
|
src: value_register,
|
|
},
|
|
span,
|
|
);
|
|
}
|
|
ContextType::Every => {}
|
|
}
|
|
Ok(())
|
|
} else {
|
|
Err(CompilerError::MissingYieldContext.into())
|
|
}
|
|
}
|
|
|
|
pub(super) fn compile_single_statement(&mut self, stmt: &LiteralStmt) -> Result<()> {
|
|
match &stmt.literal {
|
|
ast::Literal::Expr { expr, .. } => {
|
|
let assert_condition = !matches!(expr.as_ref(), ast::Expr::AssignExpr { .. });
|
|
let _condition_reg =
|
|
self.compile_rego_expr_with_span(expr, &stmt.span, assert_condition)?;
|
|
}
|
|
ast::Literal::SomeIn { .. } => {
|
|
return Err(CompilerError::SomeInNotHoisted.at(&stmt.span));
|
|
}
|
|
ast::Literal::Every {
|
|
key,
|
|
value,
|
|
domain,
|
|
query,
|
|
..
|
|
} => {
|
|
self.compile_every_quantifier(key, value, domain, query, &stmt.span)?;
|
|
}
|
|
ast::Literal::SomeVars { vars, .. } => {
|
|
for var in vars {
|
|
self.add_unbound_variable(var.text());
|
|
}
|
|
}
|
|
ast::Literal::NotExpr { expr, .. } => {
|
|
let expr_reg = self.with_soft_assert_mode(true, |compiler| {
|
|
compiler.compile_rego_expr_with_span(expr, expr.span(), false)
|
|
})?;
|
|
|
|
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(())
|
|
}
|
|
}
|