Skip to content

Commit a8b5005

Browse files
authored
Merge pull request #73 from upstat-io/dev
feat(journey): deterministic scoring rubric, codegen fixes, Code Journeys web UI
2 parents c6ed32f + b47e8d3 commit a8b5005

104 files changed

Lines changed: 11110 additions & 4060 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

compiler/ori_arc/src/decision_tree/emit.rs

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,67 @@ use super::{DecisionTree, PathInstruction, ScrutineePath};
2424
/// variables that may be reassigned in arm bodies.
2525
pub(crate) struct EmitContext {
2626
/// The root scrutinee variable.
27-
pub root_scrutinee: ArcVarId,
27+
pub(crate) root_scrutinee: ArcVarId,
2828
/// Pool type of the root scrutinee (for type-aware path resolution).
29-
pub root_scrutinee_ty: Idx,
29+
pub(crate) root_scrutinee_ty: Idx,
3030
/// The merge block all arms jump to after executing their body.
31-
pub merge_block: crate::ir::ArcBlockId,
31+
pub(crate) merge_block: crate::ir::ArcBlockId,
3232
/// The body expression for each arm (indexed by `arm_index`).
33-
pub arm_bodies: Vec<ori_ir::canon::CanId>,
33+
pub(crate) arm_bodies: Vec<ori_ir::canon::CanId>,
3434
/// Span of the match expression.
35-
pub span: Span,
35+
pub(crate) span: Span,
3636
/// Scope snapshot from before the match. Each arm resets to this
3737
/// before lowering its body, ensuring arm-to-arm scope isolation.
38-
pub pre_scope: ArcScope,
38+
pub(crate) pre_scope: ArcScope,
3939
/// Mutable variable names to pass as merge block params (in order).
4040
/// These serve as SSA phi inputs at the match convergence point.
41-
pub mutable_var_names: Vec<Name>,
41+
pub(crate) mutable_var_names: Vec<Name>,
4242
/// Variant context stack for type-aware path resolution.
4343
///
4444
/// Each entry is `(enum_type, variant_index)` pushed by `emit_tag_switch`
4545
/// when entering a specific variant's case block. `resolve_path` uses this
4646
/// to look up the actual field type for `TagPayload` steps, which is
4747
/// critical for recursive enums where fields may be RC-boxed pointers.
48-
pub variant_stack: Vec<(Idx, u32)>,
48+
variant_stack: Vec<(Idx, u32)>,
49+
}
50+
51+
impl EmitContext {
52+
/// Create a new emit context for decision tree emission.
53+
pub(crate) fn new(
54+
root_scrutinee: ArcVarId,
55+
root_scrutinee_ty: Idx,
56+
merge_block: crate::ir::ArcBlockId,
57+
arm_bodies: Vec<ori_ir::canon::CanId>,
58+
span: Span,
59+
pre_scope: ArcScope,
60+
mutable_var_names: Vec<Name>,
61+
) -> Self {
62+
Self {
63+
root_scrutinee,
64+
root_scrutinee_ty,
65+
merge_block,
66+
arm_bodies,
67+
span,
68+
pre_scope,
69+
mutable_var_names,
70+
variant_stack: Vec::new(),
71+
}
72+
}
73+
74+
/// Push a variant onto the context stack (entering a variant's case block).
75+
pub(crate) fn push_variant(&mut self, enum_type: Idx, variant_index: u32) {
76+
self.variant_stack.push((enum_type, variant_index));
77+
}
78+
79+
/// Pop the most recent variant from the context stack (leaving a variant's case block).
80+
pub(crate) fn pop_variant(&mut self) {
81+
self.variant_stack.pop();
82+
}
83+
84+
/// Get the current variant stack (for path resolution).
85+
pub(crate) fn variant_stack(&self) -> &[(Idx, u32)] {
86+
&self.variant_stack
87+
}
4988
}
5089

5190
/// Emit a decision tree as ARC IR basic blocks.
@@ -266,10 +305,35 @@ pub(super) fn resolve_path(
266305
};
267306
(*idx, elem_ty)
268307
}
269-
// List rest (slicing) not yet supported in LLVM backend.
270-
// Emit element 0 as a placeholder — will be replaced when
271-
// list pattern codegen is fully implemented.
272-
PathInstruction::ListRest(_) => (0, Idx::UNIT),
308+
PathInstruction::ListRest(start_idx) => {
309+
// Emit a runtime call to slice the list from `start_idx` onward.
310+
// The ARC IR uses Apply("ori_list_slice_drop", [list, start]),
311+
// and the LLVM emitter expands this into the full sret call
312+
// (extracting data/len/cap, computing elem_size, calling runtime).
313+
let resolved = pool.resolve_fully(current_ty);
314+
let list_ty = current_ty;
315+
let elem_ty = if pool.tag(resolved) == Tag::List {
316+
pool.list_elem(resolved)
317+
} else {
318+
Idx::UNIT
319+
};
320+
let _ = elem_ty; // Used by LLVM emitter via the list type
321+
let start_const = lowerer.builder.emit_let(
322+
Idx::INT,
323+
crate::ir::ArcValue::Literal(crate::ir::LitValue::Int(i64::from(*start_idx))),
324+
Some(span),
325+
);
326+
let slice_fn = lowerer.interner.intern("ori_list_slice_drop");
327+
let result = lowerer.builder.emit_apply(
328+
list_ty,
329+
slice_fn,
330+
vec![current, start_const],
331+
Some(span),
332+
);
333+
current = result;
334+
current_ty = list_ty;
335+
continue;
336+
}
273337
};
274338
current = lowerer
275339
.builder
@@ -332,7 +396,7 @@ fn bind_pattern_variables(
332396
ctx.root_scrutinee_ty,
333397
path,
334398
ctx.span,
335-
&ctx.variant_stack,
399+
ctx.variant_stack(),
336400
);
337401
lowerer.scope.bind(*name, var);
338402
}

compiler/ori_arc/src/decision_tree/emit_switches/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub(super) fn emit_switch(
3434
ctx.root_scrutinee_ty,
3535
path,
3636
ctx.span,
37-
&ctx.variant_stack,
37+
ctx.variant_stack(),
3838
);
3939

4040
match test_kind {

compiler/ori_arc/src/decision_tree/emit_switches/tag.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ pub(in crate::decision_tree) fn emit_tag_switch(
5959
TestValue::Tag { variant_index, .. } => *variant_index,
6060
_ => 0,
6161
};
62-
ctx.variant_stack.push((scrut_ty, variant_index));
62+
ctx.push_variant(scrut_ty, variant_index);
6363
emit_tree(lowerer, subtree, ctx);
64-
ctx.variant_stack.pop();
64+
ctx.pop_variant();
6565
}
6666

6767
// Emit the default block.

compiler/ori_arc/src/decision_tree/flatten.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,11 @@ impl<'a> FlattenCtx<'a> {
229229

230230
/// Resolve a variant name to its discriminant index.
231231
///
232-
/// Handles three cases:
232+
/// Handles four cases:
233233
/// - `Tag::Enum`: looks up variant by name in the enum definition
234234
/// - `Tag::Option`: `Some` = 0, `None` = 1
235235
/// - `Tag::Result`: `Ok` = 0, `Err` = 1
236+
/// - `Tag::Ordering`: `Less` = 0, `Equal` = 1, `Greater` = 2
236237
#[expect(
237238
clippy::cast_possible_truncation,
238239
reason = "variant indices never exceed u32"
@@ -258,6 +259,19 @@ impl<'a> FlattenCtx<'a> {
258259
// Convention: Ok = 0, Err = 1 (matches evaluator's Value::Ok/Err)
259260
return u32::from(self.interner.lookup(variant_name) == "Err");
260261
}
262+
Tag::Ordering => {
263+
// Convention: Less = 0, Equal = 1, Greater = 2
264+
let name_str = self.interner.lookup(variant_name);
265+
return match name_str {
266+
"Less" => 0,
267+
"Equal" => 1,
268+
"Greater" => 2,
269+
other => {
270+
debug_assert!(false, "unknown Ordering variant: {other}");
271+
0
272+
}
273+
};
274+
}
261275
_ => {}
262276
}
263277
tracing::debug!(
@@ -270,11 +284,12 @@ impl<'a> FlattenCtx<'a> {
270284
0
271285
}
272286

273-
/// Get the field types for a specific variant of an enum, Option, or Result.
287+
/// Get the field types for a specific variant of an enum, Option, Result, or Ordering.
274288
///
275289
/// - `Tag::Enum`: returns field types from the enum definition
276290
/// - `Tag::Option`: `Some` (index 0) has one field (the inner type), `None` (index 1) has none
277291
/// - `Tag::Result`: `Ok` (index 0) has one field (ok type), `Err` (index 1) has one field (err type)
292+
/// - `Tag::Ordering`: all variants (Less, Equal, Greater) are unit — no fields
278293
fn resolve_variant_field_types(
279294
&self,
280295
enum_ty: ori_types::Idx,
@@ -305,6 +320,8 @@ impl<'a> FlattenCtx<'a> {
305320
}
306321
return vec![self.pool.result_ok(resolved)];
307322
}
323+
// Ordering variants (Less, Equal, Greater) are all unit — no fields.
324+
Tag::Ordering => return Vec::new(),
308325
_ => {}
309326
}
310327
Vec::new()

compiler/ori_arc/src/decision_tree/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
//! Type definitions live in `ori_ir::canon::tree` (shared across crates).
1616
//! The compilation algorithm and ARC IR emission logic live here in `ori_arc`.
1717
//!
18+
//! NOTE: `decision_tree::compile` and `flatten` are pattern-matching primitives
19+
//! temporarily housed in `ori_arc`. They are consumed by `ori_canon` (which
20+
//! imports `ori_arc`), creating an upward dependency from canonicalization into
21+
//! ARC optimization. These will move to `ori_canon::patterns` in `eval_v2`
22+
//! Section 03 — see `ori_ir::canon::tree` for the migration plan.
23+
//!
1824
//! # References
1925
//!
2026
//! - Maranget (2008): foundational algorithm

compiler/ori_arc/src/lower/control_flow/mod.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -322,17 +322,21 @@ impl ArcLowerer<'_> {
322322
}
323323
}
324324
CanExpr::Field { receiver, field: _ } => {
325-
let recv = self.lower_expr(receiver);
326-
let setter_fn = self.interner.intern("__set_field");
327-
self.builder
328-
.emit_apply(Idx::UNIT, setter_fn, vec![recv, rhs], Some(span));
325+
let _recv = self.lower_expr(receiver);
326+
// TODO(section-03): field assignment in ARC lowering — blocked on COW
327+
// codegen. `__set_field` does not exist as a runtime function or LLVM
328+
// intrinsic. The evaluator desugars `obj.field = val` into
329+
// `obj = { ...obj, field: val }` before it reaches ARC lowering, so
330+
// this arm should be unreachable once desugaring is complete.
331+
tracing::warn!("field assignment not yet supported in ARC lowering");
329332
}
330333
CanExpr::Index { receiver, index } => {
331-
let recv = self.lower_expr(receiver);
332-
let idx_var = self.lower_expr(index);
333-
let setter_fn = self.interner.intern("__set_index");
334-
self.builder
335-
.emit_apply(Idx::UNIT, setter_fn, vec![recv, idx_var, rhs], Some(span));
334+
let _recv = self.lower_expr(receiver);
335+
let _idx_var = self.lower_expr(index);
336+
// TODO(section-03): index assignment in ARC lowering — blocked on COW
337+
// codegen. `__set_index` does not exist as a runtime function or LLVM
338+
// intrinsic. See field assignment comment above.
339+
tracing::warn!("index assignment not yet supported in ARC lowering");
336340
}
337341
_ => {
338342
tracing::warn!("unsupported assignment target in ARC IR");
@@ -395,16 +399,15 @@ impl ArcLowerer<'_> {
395399
let tree = self.canon.decision_trees.get_shared(tree_id);
396400

397401
let scrut_ty = self.builder.var_type(scrut_var);
398-
let mut ctx = crate::decision_tree::emit::EmitContext {
399-
root_scrutinee: scrut_var,
400-
root_scrutinee_ty: scrut_ty,
402+
let mut ctx = crate::decision_tree::emit::EmitContext::new(
403+
scrut_var,
404+
scrut_ty,
401405
merge_block,
402-
arm_bodies: arm_ids,
406+
arm_ids,
403407
span,
404-
pre_scope: pre_scope.clone(),
408+
pre_scope.clone(),
405409
mutable_var_names,
406-
variant_stack: Vec::new(),
407-
};
410+
);
408411

409412
crate::decision_tree::emit::emit_tree(self, &tree, &mut ctx);
410413

compiler/ori_arc/src/rc_insert/block_rc.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,20 @@ pub(super) fn process_block_rc(
9696
}
9797
}
9898

99+
// ApplyIndirect borrows the closure (position 0): the callee is
100+
// invoked through the closure's fn_ptr but does not take ownership
101+
// of the environment. The caller must Dec the closure after its
102+
// last use, just like any other borrowing position.
103+
if let ArcInstr::ApplyIndirect { closure, .. } = instr {
104+
if needs_rc_trackable(*closure, ctx) && !live.contains(closure) {
105+
new_body.push(ArcInstr::RcDec {
106+
var: *closure,
107+
strategy: rc_strategy(ctx, *closure),
108+
});
109+
new_spans.push(None);
110+
}
111+
}
112+
99113
new_body.push(instr.clone());
100114
new_spans.push(span);
101115

@@ -307,6 +321,16 @@ fn process_instruction_uses(
307321
continue;
308322
}
309323

324+
// ApplyIndirect borrows the closure (position 0): calling through
325+
// a closure's fn_ptr does not consume the closure environment.
326+
// Treat as a borrowing use — add to live, no Inc.
327+
if let ArcInstr::ApplyIndirect { closure, .. } = instr {
328+
if var == *closure {
329+
live.insert(var);
330+
continue;
331+
}
332+
}
333+
310334
// Normal (non-borrowed) var.
311335
if !seen.insert(var) {
312336
// Duplicate arg in the same instruction — already handled below.

compiler/ori_canon/src/const_fold/arithmetic.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ pub(super) fn fold_binary(
6666
}
6767
})
6868
}
69-
7069
// Float arithmetic.
7170
(BinaryOp::Add, ConstValue::Float(a), ConstValue::Float(b)) => Some(ConstValue::Float(
7271
(f64::from_bits(*a) + f64::from_bits(*b)).to_bits(),

compiler/ori_canon/src/exhaustiveness/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ fn check_enum_tag(
203203
ori_types::Tag::Result => {
204204
check_result(&covered, missing, nesting);
205205
}
206+
ori_types::Tag::Ordering => {
207+
check_ordering(&covered, missing, nesting);
208+
}
206209
// Unknown or non-enum type at this path — skip (conservative).
207210
// This can happen if the type checker couldn't resolve the scrutinee.
208211
_ => {}
@@ -290,6 +293,25 @@ fn check_result(
290293
}
291294
}
292295

296+
/// Check coverage for the builtin `Ordering` type.
297+
///
298+
/// Ordering has exactly 3 variants: `Less` (index 0), `Equal` (index 1), `Greater` (index 2).
299+
fn check_ordering(
300+
covered: &rustc_hash::FxHashSet<u32>,
301+
missing: &mut Vec<String>,
302+
nesting: &[String],
303+
) {
304+
if !covered.contains(&0) {
305+
missing.push(wrap_pattern(nesting, "Less"));
306+
}
307+
if !covered.contains(&1) {
308+
missing.push(wrap_pattern(nesting, "Equal"));
309+
}
310+
if !covered.contains(&2) {
311+
missing.push(wrap_pattern(nesting, "Greater"));
312+
}
313+
}
314+
293315
/// Check coverage for a `ListLen` switch with no default.
294316
///
295317
/// List lengths are quasi-finite: a rest pattern (`[x, ..rest]`, encoded as

0 commit comments

Comments
 (0)