Files
regorus/docs/knowledge/rego-compiler.md
copilot-swe-agent[bot] 524aab5528 Add knowledge docs, agent definitions, and skill files
Add comprehensive documentation and GitHub Copilot configuration:

- docs/knowledge/: 17 deep-dive knowledge files covering value semantics,
  RVM architecture, builtins, FFI boundary, feature composition, error
  handling migration, policy evaluation security, Rego semantics,
  interpreter/compiler architecture, Azure Policy/RBAC, engine API,
  time builtins, language extension guide, tooling architecture,
  causality/partial eval, Rego compiler, Azure Policy aliases, and
  telemetry/diagnostics

- .github/agents/: 16 role-specific AI agent definitions (red-teamer,
  semantics-expert, architect, performance-engineer, test-engineer,
  verification-engineer, security-auditor, reliability-engineer,
  support-engineer, ci-engineer, refactorer, api-steward, program-manager,
  demo-engineer, dx-engineer, tech-lead)

- .github/skills/: 6 workflow skill definitions (thorough-review,
  design-alternatives, add-builtin, opa-conformance, security-review,
  verification)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: anakrish <35780660+anakrish@users.noreply.github.com>
2026-04-25 21:20:10 +00:00

287 lines
9.8 KiB
Markdown

<!-- Copyright (c) Microsoft Corporation. All rights reserved. -->
<!-- Licensed under the MIT License. -->
# Knowledge: Rego Compiler
Deep knowledge about the Rego → RVM bytecode compiler in
`src/languages/rego/compiler/`. Read this before modifying rule compilation,
expression codegen, register allocation, or optimization passes.
See also `compilation-pipeline.md` for the scheduler and loop hoisting stages
that feed into this compiler.
## Module Structure
```
src/languages/rego/compiler/
mod.rs Compiler struct, scope management, register allocation
core.rs Variable resolution, register helpers, instruction emission
program.rs finish() — default rules, rule info construction, metadata
rules.rs Worklist algorithm, per-definition rule compilation
queries.rs Statement compilation, loop hoisting integration
expressions.rs Expression dispatch, recursive compilation
references.rs Chained reference parsing (obj.a[x].b[y])
function_calls.rs Builtin vs. user-defined function dispatch
loops.rs `every` quantifier, loop mode handling
comprehensions.rs Array/Set/Object comprehension compilation
destructuring.rs Function parameter binding/validation
error.rs Error types with span tracking
```
## Worklist Algorithm
Rule compilation uses a worklist (depth-first queue) rather than
recursive descent. This provides three benefits:
1. **Dependency ordering** — rules are compiled in reference order
2. **Recursion detection** — a call stack tracks in-progress rules
3. **Deduplication** — already-compiled rules are skipped
```
while worklist not empty:
pop (rule_path, call_stack) from worklist
if rule_path in call_stack → compile-time recursion error
if rule_path already compiled → skip
push rule_path onto call_stack
compile all definitions of rule_path
mark rule as compiled
```
When compiling a rule body encounters `CallRule` to another rule, that
target rule is pushed onto the worklist. This ensures rules are compiled
in call order.
## Variable Resolution
The compiler resolves variable names through a priority chain
(`core.rs`):
```
1. "input" → emit LoadInput (cached per rule definition)
2. "data" → emit LoadData (cached per rule definition)
3. scope → use bound register from current scope
4. fallback → treat as rule call: data.{package}.{name}
```
**Input/data caching**: `LoadInput` and `LoadData` are emitted at most
once per rule definition. The cached register is reused for subsequent
references. The cache is reset between definitions to prevent stale state.
## Register Allocation
### Three-Tier Strategy
**Dispatch window** — initial registers for entry point dispatch and
temporary work. Sized by `dispatch_window_size`.
**Per-rule window** — max registers within any single rule definition.
Register 0 is always the result accumulator. The VM allocates a fixed
frame per rule based on `max_rule_window_size`.
**Per-definition reset**`register_counter` resets to 0 at each
definition start. This minimizes frame size and enables tail calls.
### Special Registers
| Register | Purpose |
|----------|---------|
| 0 | Rule result accumulator |
| `current_input_register` | Cached `LoadInput` (per definition) |
| `current_data_register` | Cached `LoadData` (per definition) |
| 0..N-1 (functions) | Function parameter bindings |
**Limit**: u8 register counter (max 255). The compiler asserts
`register_counter < 255`.
## Expression Compilation
Each `Expr` variant maps to one or more RVM instructions:
| Expr | Instructions | Notes |
|------|-------------|-------|
| Literal (Num/Str/Bool) | `Load` | Literals go to literal table |
| `true`/`false`/`null` | `LoadTrue`/`LoadFalse`/`LoadNull` | Special-cased |
| Var (in scope) | — | Reuse bound register |
| Var (unresolved) | `CallRule` | Treat as rule reference |
| RefDot | `IndexLiteral` | Literal key optimization |
| RefBrack | `Index` or loop | Depends on bound/unbound index |
| Chained ref | `ChainedIndex` | `obj.a[x].b[y]` → single instruction |
| ArithExpr | `Add`/`Sub`/`Mul`/`Div`/`Mod` | |
| BoolExpr | `Eq`/`Ne`/`Lt`/`Le`/`Gt`/`Ge` | |
| Not | `Not` | |
| Call (builtin) | `BuiltinCall` | Via builtin_call_params table |
| Call (user) | `FunctionCall` | Via function_call_params table |
| ArrayCompr | `ComprehensionBegin..Yield..End` | Mode: Array |
| SetCompr | `ComprehensionBegin..Yield..End` | Mode: Set |
| ObjectCompr | `ComprehensionBegin..Yield..End` | Mode: Object |
| Every | `LoopStart { mode: Every }` | Quantifier loop |
| SomeIn | `LoopStart` | Iteration with binding |
| UnaryMinus | `Sub` (0 - x) | |
### Chained References
Multi-level property access like `input.request.headers["content-type"]`
compiles to a single `ChainedIndex` instruction with parameters:
```rust
ChainedIndexParams {
dest: u8,
root: ChainedIndexRoot, // Var or Expr
components: Vec<Component>, // Field(literal_idx) or Expr(register)
}
```
This avoids emitting multiple `Index` instructions and intermediate
registers.
## Rule Type Compilation
### Complete Rules
```rego
allow := input.admin == true
```
- Body compiled as normal statements
- Success: `RuleReturn {}` (stores result in register 0)
- **Static value optimization**: if all definitions yield the same constant,
the rule gets `early_exit_on_first_success = true` — VM stops after
first successful definition
### Partial Set Rules
```rego
ports contains p if { ... }
```
- Emit `ComprehensionYield { value_reg, key_reg: None }`
- Result register accumulates a set of all yielded values
### Partial Object Rules
```rego
people[name] = age if { ... }
```
- Emit `ComprehensionYield { value_reg, key_reg: Some(k) }`
- Result register accumulates key-value pairs
### Functions
```rego
f(x, y) := x + y
```
- Parameters bound to registers 0..N-1 before body compilation
- `DestructuringSuccess {}` emitted after parameter validation
- Consistent parameter count enforced across all definitions
- After compilation, `FunctionInfo` recorded with param names
## Comprehension Compilation
All comprehensions follow the same pattern:
```
ComprehensionBegin { mode, collection_reg, body_start, end }
[body: hoisted loops → statements → ComprehensionYield]
ComprehensionEnd {}
```
Modes: `Array`, `Set`, `Object`. The VM creates the appropriate
collection type and appends each yielded value.
**Context stack**: the compiler pushes a comprehension context to
track that yield should go to the comprehension (not the rule).
## Optimization Passes
### Constant Folding
`try_eval_const()` evaluates pure expressions at compile time:
- Array/Set/Object literals with all-constant elements
- Index operations on constant collections
- Result stored in literal table, emitted as `Load`
### Static Value Detection
After compiling all definitions of a complete rule, the compiler checks
if every definition yields the same static value. If so:
- `early_exit_on_first_success = true`
- VM stops after first successful definition body
- Common pattern: `default allow := false` + `allow := true { ... }`
### Literal Key Optimization
`obj["literal"]` compiles to `IndexLiteral { literal_idx }` instead of
loading the string into a register and using `Index`. Avoids a register
allocation and a `Load` instruction.
### Lazy Builtin Indexing
Builtins are assigned indices only when first used during compilation.
The builtin info table contains only actually-referenced builtins,
kept in deterministic order (BTreeMap).
## Compile-Time Safety
### Recursion Detection
The worklist's call stack detects compile-time recursion:
```
Rule A calls Rule B calls Rule A → error
```
This prevents infinite compilation loops for mutually recursive rules.
### Register Overflow
`alloc_register()` asserts `register_counter < 255`. If a rule body
requires more than 255 registers, compilation fails rather than silently
wrapping.
## Program Output
The compiler produces `Arc<Program>` containing:
```rust
struct Program {
instructions: Vec<Instruction>, // Bytecode stream
literals: Vec<Value>, // Constant value table
builtin_info_table: Vec<BuiltinInfo>, // Referenced builtins
rule_infos: Vec<RuleInfo>, // Rule metadata
entry_points: IndexMap<String, usize>, // Rule path → instruction offset
instruction_data: InstructionData, // Extended params tables
span_infos: Vec<SpanInfo>, // Source mapping (1:1 with instructions)
}
```
Every instruction has a corresponding `SpanInfo` for source mapping,
enabling debugging and IDE integration.
## Key Invariants
1. **Register 0 = result** — every rule's result is in register 0
2. **Input/data cache reset per definition** — prevents stale references
3. **Worklist ordering** — rules compiled in call-graph order
4. **Instruction ↔ SpanInfo 1:1** — every instruction has source location
5. **Literal table is append-only** — indices are stable after emission
## Common Pitfalls
1. **Scope nesting** — comprehensions and `every` push new scopes.
Variables bound in inner scopes are not visible in outer scopes.
2. **Hoisted loop coordination** — the compiler must query the hoisting
table for each statement to know which loops to emit. Missing a
hoisted loop causes incorrect variable binding at runtime.
3. **Multi-definition rules** — each definition resets registers but
shares the same `RuleInfo`. The `definitions` array in `RuleInfo`
records instruction ranges for each definition.
4. **Function parameter count** — all definitions of a function must
have the same number of parameters. The compiler enforces this.
5. **Builtin vs user function** — the compiler must distinguish builtin
calls (which use `BuiltinCall` with the builtin registry) from user
function calls (which use `FunctionCall` with the rule index).