Skip to content

Commit c6ed32f

Browse files
authored
Merge pull request #72 from upstat-io/dev
feat(llvm): codegen overhaul with EH personality and expanded AOT tests
2 parents fc8eb5e + b471d8d commit c6ed32f

202 files changed

Lines changed: 11436 additions & 9641 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.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

blog/cross-compilation-nightmare.md

Lines changed: 254 additions & 0 deletions
Large diffs are not rendered by default.

compiler/ori_arc/src/lower/control_flow/for_loops/for_range.rs

Lines changed: 132 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ impl ArcLowerer<'_> {
1111
/// Lower `for i in <range> do body` using direct start/end projection.
1212
///
1313
/// Range layout: `{i64 start, i64 end, i64 step, i64 inclusive}`.
14-
/// Inclusive ranges (`0..=5`) use `i < end + inclusive` so both exclusive
15-
/// and inclusive work with a single `Lt` comparison.
14+
/// The loop condition uses sign-aware comparison to avoid overflow:
15+
/// - Ascending (step > 0): `i < end` (exclusive) or `i <= end` (inclusive)
16+
/// - Descending (step < 0): `i > end` (exclusive) or `i >= end` (inclusive)
17+
///
18+
/// The old approach (`i < end + inclusive`) overflows for `0..=INT_MAX`
19+
/// because `INT_MAX + 1` wraps to `INT_MIN`.
1620
#[expect(
1721
clippy::too_many_lines,
1822
reason = "range loop lowering with guard/latch/mutable-var SSA merge is inherently sequential"
@@ -80,18 +84,70 @@ impl ArcLowerer<'_> {
8084

8185
let start = self.builder.emit_project(Idx::INT, iter_val, 0, None);
8286
let end = self.builder.emit_project(Idx::INT, iter_val, 1, None);
83-
// Field 3 = inclusive flag (0 or 1). Adding it to end makes `i < end + inclusive`
84-
// work for both exclusive (i < end) and inclusive (i < end + 1 ≡ i <= end).
87+
let step = self.builder.emit_project(Idx::INT, iter_val, 2, None);
8588
let inclusive = self.builder.emit_project(Idx::INT, iter_val, 3, None);
86-
let adjusted_end = self.builder.emit_let(
87-
Idx::INT,
89+
90+
// Pre-compute loop-invariant direction/inclusive flags.
91+
// These are defined in the entry block and dominate the header.
92+
let zero = self
93+
.builder
94+
.emit_let(Idx::INT, ArcValue::Literal(LitValue::Int(0)), None);
95+
let step_pos = self.builder.emit_let(
96+
Idx::BOOL,
8897
ArcValue::PrimOp {
89-
op: PrimOp::Binary(ori_ir::BinaryOp::Add),
90-
args: vec![end, inclusive],
98+
op: PrimOp::Binary(ori_ir::BinaryOp::Gt),
99+
args: vec![step, zero],
100+
},
101+
None,
102+
);
103+
let step_neg = self.builder.emit_let(
104+
Idx::BOOL,
105+
ArcValue::PrimOp {
106+
op: PrimOp::Binary(ori_ir::BinaryOp::Lt),
107+
args: vec![step, zero],
108+
},
109+
None,
110+
);
111+
let is_incl = self.builder.emit_let(
112+
Idx::BOOL,
113+
ArcValue::PrimOp {
114+
op: PrimOp::Binary(ori_ir::BinaryOp::Gt),
115+
args: vec![inclusive, zero],
91116
},
92117
None,
93118
);
94119

120+
// Zero-step guard: panic at runtime if step == 0.
121+
// Prevents infinite loop for ranges like `0..=0 by 0`.
122+
let step_is_zero = self.builder.emit_let(
123+
Idx::BOOL,
124+
ArcValue::PrimOp {
125+
op: PrimOp::Binary(ori_ir::BinaryOp::Eq),
126+
args: vec![step, zero],
127+
},
128+
None,
129+
);
130+
let panic_block = self.builder.new_block();
131+
let loop_entry_block = self.builder.new_block();
132+
self.builder
133+
.terminate_branch(step_is_zero, panic_block, loop_entry_block);
134+
135+
// Panic block: emit "range step cannot be zero" and halt.
136+
self.builder.position_at(panic_block);
137+
let panic_msg = self.interner.intern("range step cannot be zero");
138+
let msg_var = self.builder.emit_let(
139+
Idx::STR,
140+
ArcValue::Literal(LitValue::String(panic_msg)),
141+
None,
142+
);
143+
let panic_fn = self.interner.intern("ori_panic");
144+
self.builder
145+
.emit_apply(Idx::UNIT, panic_fn, vec![msg_var], None);
146+
self.builder.terminate_unreachable();
147+
148+
// Continue in loop entry block.
149+
self.builder.position_at(loop_entry_block);
150+
95151
// Entry jump args match header param order: [start, mut0, mut1, ...]
96152
let mut entry_args = vec![start];
97153
entry_args.extend(header_mut_params.iter().map(|(_, pre_var, _)| *pre_var));
@@ -104,11 +160,76 @@ impl ArcLowerer<'_> {
104160
self.scope.bind_mutable(name, param_var);
105161
}
106162

107-
let in_bounds = self.builder.emit_let(
163+
// Sign-aware loop condition using boolean arithmetic:
164+
// asc_part = (step > 0) && (i < end)
165+
// desc_part = (step < 0) && (i > end)
166+
// base = asc_part || desc_part
167+
// incl_part = inclusive && (i == end)
168+
// in_bounds = base || incl_part
169+
//
170+
// For compile-time constant step/inclusive, LLVM constant-folds
171+
// the dead terms away, producing optimal `icmp slt`/`icmp sle`.
172+
let lt_val = self.builder.emit_let(
108173
Idx::BOOL,
109174
ArcValue::PrimOp {
110175
op: PrimOp::Binary(ori_ir::BinaryOp::Lt),
111-
args: vec![i_var, adjusted_end],
176+
args: vec![i_var, end],
177+
},
178+
None,
179+
);
180+
let gt_val = self.builder.emit_let(
181+
Idx::BOOL,
182+
ArcValue::PrimOp {
183+
op: PrimOp::Binary(ori_ir::BinaryOp::Gt),
184+
args: vec![i_var, end],
185+
},
186+
None,
187+
);
188+
let eq_val = self.builder.emit_let(
189+
Idx::BOOL,
190+
ArcValue::PrimOp {
191+
op: PrimOp::Binary(ori_ir::BinaryOp::Eq),
192+
args: vec![i_var, end],
193+
},
194+
None,
195+
);
196+
let asc_part = self.builder.emit_let(
197+
Idx::BOOL,
198+
ArcValue::PrimOp {
199+
op: PrimOp::Binary(ori_ir::BinaryOp::And),
200+
args: vec![step_pos, lt_val],
201+
},
202+
None,
203+
);
204+
let desc_part = self.builder.emit_let(
205+
Idx::BOOL,
206+
ArcValue::PrimOp {
207+
op: PrimOp::Binary(ori_ir::BinaryOp::And),
208+
args: vec![step_neg, gt_val],
209+
},
210+
None,
211+
);
212+
let base = self.builder.emit_let(
213+
Idx::BOOL,
214+
ArcValue::PrimOp {
215+
op: PrimOp::Binary(ori_ir::BinaryOp::Or),
216+
args: vec![asc_part, desc_part],
217+
},
218+
None,
219+
);
220+
let incl_part = self.builder.emit_let(
221+
Idx::BOOL,
222+
ArcValue::PrimOp {
223+
op: PrimOp::Binary(ori_ir::BinaryOp::And),
224+
args: vec![is_incl, eq_val],
225+
},
226+
None,
227+
);
228+
let in_bounds = self.builder.emit_let(
229+
Idx::BOOL,
230+
ArcValue::PrimOp {
231+
op: PrimOp::Binary(ori_ir::BinaryOp::Or),
232+
args: vec![base, incl_part],
112233
},
113234
None,
114235
);
@@ -160,14 +281,11 @@ impl ArcLowerer<'_> {
160281
self.loop_ctx = prev_loop;
161282

162283
self.builder.position_at(latch_block);
163-
let one = self
164-
.builder
165-
.emit_let(Idx::INT, ArcValue::Literal(LitValue::Int(1)), None);
166284
let next = self.builder.emit_let(
167285
Idx::INT,
168286
ArcValue::PrimOp {
169287
op: PrimOp::Binary(ori_ir::BinaryOp::Add),
170-
args: vec![i_var, one],
288+
args: vec![i_var, step],
171289
},
172290
None,
173291
);

compiler/ori_llvm/src/codegen/arc_emitter/catch_thunk.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
//! SEH catch trampoline for `catch(expr:)` on Windows MSVC.
22
//!
3-
//! On MSVC, `std::panic::panic_any` throws a C++ exception via
4-
//! `_CxxThrowException`. If an LLVM-generated `catchpad` catches it,
5-
//! Rust's runtime detects the foreign handler and aborts with:
6-
//! "Rust panics must be rethrown"
7-
//!
8-
//! The solution: wrap the function call in a runtime trampoline that uses
9-
//! `std::panic::catch_unwind` (which IS the blessed Rust mechanism).
3+
//! On MSVC, Ori panics raise a custom SEH exception via `RaiseException`
4+
//! (implemented in `eh_personality.c`). The `ori_try_call` C function
5+
//! catches this with `__try`/`__except`, avoiding LLVM `catchpad` entirely.
106
//!
117
//! # Architecture
128
//!
@@ -16,7 +12,7 @@
1612
//! from a context struct, calls the real function, stores the result back.
1713
//!
1814
//! 2. **Call site**: allocates context, stores args, calls `ori_try_call`
19-
//! (which wraps the thunk in `catch_unwind`), branches on the result.
15+
//! (C, `__try`/`__except`), branches on the result.
2016
//!
2117
//! 3. **Catch block**: now a regular block (no catchpad), reached via the
2218
//! failure branch of `ori_try_call`.

0 commit comments

Comments
 (0)