Skip to content

Commit baa8377

Browse files
committed
feat: Packrat memoization fixes exponential PEG backtracking, hello_simple runs end-to-end
Wire up the pre-existing MemoCache infrastructure in execute_rule to implement Packrat parsing. Rules like `comparison_expr = comparison_with_op | comparison_no_op` and `ternary_expr = python_ternary | ternary_with_branches | null_coalesce_expr` were re-parsing sub-expressions 2-3x each on backtracking, causing 500K+ rule calls for a 23KB prelude file (effectively an infinite loop). The memo cache converts this to O(n * grammar_size). Also fixes array literal lowering to emit proper List<T> struct layout {i64 data_ptr, i64 len, i64 capacity} instead of the raw ZRTL byte format that caused misaligned pointer crashes in Tensor::zeros([2,3]). Adds HIR dump module (hir_dump.rs) for CLIF-inspired MIR debugging. hello_simple.zynml now runs end-to-end: tensor arange, zeros, ones, arithmetic operators (+,-,*), sum(), and mean() all produce correct output.
1 parent 908fc0f commit baa8377

17 files changed

Lines changed: 1351 additions & 396 deletions

File tree

crates/compiler/src/cranelift_backend.rs

Lines changed: 105 additions & 106 deletions
Large diffs are not rendered by default.

crates/compiler/src/hir_dump.rs

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

crates/compiler/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
//! - Memory safety validated before code generation
1919
2020
pub mod hir;
21+
pub mod hir_dump; // CLIF-inspired HIR text dump for debugging
2122
pub mod hir_builder; // HIR Builder API for direct HIR construction
2223
pub mod bytecode; // HIR bytecode serialization/deserialization
2324
pub mod stdlib; // Standard library implementation using HIR Builder

crates/compiler/src/ssa.rs

Lines changed: 79 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1854,8 +1854,9 @@ impl SsaBuilder {
18541854
}
18551855

18561856
TypedExpression::Array(elements) => {
1857-
// Create ZRTL array format: [i32 capacity][i32 length][elements...]
1858-
// This format allows plugins to safely read array length
1857+
// Lower array literal as List<T> struct: { data: i64 (ptr), len: i64, capacity: i64 }
1858+
// This matches the List<T> struct definition in prelude.zynml and allows
1859+
// field access (shape.data, shape.len) via extractvalue to work correctly.
18591860
let elem_ty = if let Type::Array { element_type, .. } = &expr.ty {
18601861
self.convert_type(element_type)
18611862
} else {
@@ -1873,58 +1874,28 @@ impl SsaBuilder {
18731874
};
18741875
let num_elements = elements.len();
18751876

1876-
// ZRTL array header: 8 bytes (capacity i32 + length i32)
1877-
const ZRTL_HEADER_BYTES: usize = 8;
1878-
let total_size = ZRTL_HEADER_BYTES + num_elements * elem_size;
1879-
1880-
// Allocate raw bytes for the entire ZRTL array
1881-
let alloc_ty = HirType::Array(Box::new(HirType::U8), total_size as u64);
1882-
let alloc_result = self.create_value(HirType::Ptr(Box::new(HirType::I32)), HirValueKind::Instruction);
1883-
1877+
// Step 1: Allocate element data buffer
1878+
let data_buf_size = num_elements * elem_size;
1879+
let data_alloc_ty = HirType::Array(Box::new(elem_ty.clone()), num_elements as u64);
1880+
let data_ptr = self.create_value(HirType::Ptr(Box::new(elem_ty.clone())), HirValueKind::Instruction);
18841881
self.add_instruction(block_id, HirInstruction::Alloca {
1885-
result: alloc_result,
1886-
ty: alloc_ty,
1882+
result: data_ptr,
1883+
ty: data_alloc_ty,
18871884
count: None,
1888-
align: 8,
1885+
align: elem_size.min(8) as u32,
18891886
});
18901887

1891-
// Store capacity at offset 0 (i32)
1892-
let cap_const = self.create_value(HirType::I32, HirValueKind::Constant(crate::hir::HirConstant::I32(num_elements as i32)));
1893-
self.add_instruction(block_id, HirInstruction::Store {
1894-
value: cap_const,
1895-
ptr: alloc_result,
1896-
align: 4,
1897-
volatile: false,
1898-
});
1899-
1900-
// Store length at offset 4 (i32) - need to calculate ptr + 4
1901-
let len_const = self.create_value(HirType::I32, HirValueKind::Constant(crate::hir::HirConstant::I32(num_elements as i32)));
1902-
let offset_4 = self.create_value(HirType::I64, HirValueKind::Constant(crate::hir::HirConstant::I64(4)));
1903-
let len_ptr = self.create_value(HirType::Ptr(Box::new(HirType::I32)), HirValueKind::Instruction);
1904-
self.add_instruction(block_id, HirInstruction::GetElementPtr {
1905-
result: len_ptr,
1906-
ty: HirType::U8, // Base type for byte offset calculation
1907-
ptr: alloc_result,
1908-
indices: vec![offset_4],
1909-
});
1910-
self.add_instruction(block_id, HirInstruction::Store {
1911-
value: len_const,
1912-
ptr: len_ptr,
1913-
align: 4,
1914-
volatile: false,
1915-
});
1916-
1917-
// Store each element starting at offset 8
1888+
// Step 2: Store each element into the data buffer
19181889
for (i, elem_expr) in elements.iter().enumerate() {
19191890
let elem_val = self.translate_expression(block_id, elem_expr)?;
19201891

1921-
let offset = ZRTL_HEADER_BYTES + i * elem_size;
1892+
let offset = i * elem_size;
19221893
let offset_const = self.create_value(HirType::I64, HirValueKind::Constant(crate::hir::HirConstant::I64(offset as i64)));
19231894
let elem_ptr = self.create_value(HirType::Ptr(Box::new(elem_ty.clone())), HirValueKind::Instruction);
19241895
self.add_instruction(block_id, HirInstruction::GetElementPtr {
19251896
result: elem_ptr,
19261897
ty: HirType::U8, // Base type for byte offset calculation
1927-
ptr: alloc_result,
1898+
ptr: data_ptr,
19281899
indices: vec![offset_const],
19291900
});
19301901

@@ -1936,8 +1907,71 @@ impl SsaBuilder {
19361907
});
19371908
}
19381909

1939-
// Return pointer to the ZRTL array header
1940-
Ok(alloc_result)
1910+
// Step 3: Build List<T> struct: { data: i64 (ptr), len: i64, capacity: i64 }
1911+
let list_struct_ty = HirType::Struct(crate::hir::HirStructType {
1912+
name: None,
1913+
fields: vec![HirType::I64, HirType::I64, HirType::I64],
1914+
packed: false,
1915+
});
1916+
let list_alloc = self.create_value(HirType::Ptr(Box::new(list_struct_ty.clone())), HirValueKind::Instruction);
1917+
self.add_instruction(block_id, HirInstruction::Alloca {
1918+
result: list_alloc,
1919+
ty: list_struct_ty,
1920+
count: None,
1921+
align: 8,
1922+
});
1923+
1924+
// Store data pointer (field 0) — cast ptr to i64 for storage
1925+
let data_as_i64 = self.create_value(HirType::I64, HirValueKind::Instruction);
1926+
self.add_instruction(block_id, HirInstruction::Cast {
1927+
result: data_as_i64,
1928+
ty: HirType::I64,
1929+
operand: data_ptr,
1930+
op: crate::hir::CastOp::PtrToInt,
1931+
});
1932+
self.add_instruction(block_id, HirInstruction::Store {
1933+
value: data_as_i64,
1934+
ptr: list_alloc,
1935+
align: 8,
1936+
volatile: false,
1937+
});
1938+
1939+
// Store len (field 1) at offset 8
1940+
let len_const = self.create_value(HirType::I64, HirValueKind::Constant(crate::hir::HirConstant::I64(num_elements as i64)));
1941+
let offset_8 = self.create_value(HirType::I64, HirValueKind::Constant(crate::hir::HirConstant::I64(8)));
1942+
let len_field_ptr = self.create_value(HirType::Ptr(Box::new(HirType::I64)), HirValueKind::Instruction);
1943+
self.add_instruction(block_id, HirInstruction::GetElementPtr {
1944+
result: len_field_ptr,
1945+
ty: HirType::U8, // byte offset
1946+
ptr: list_alloc,
1947+
indices: vec![offset_8],
1948+
});
1949+
self.add_instruction(block_id, HirInstruction::Store {
1950+
value: len_const,
1951+
ptr: len_field_ptr,
1952+
align: 8,
1953+
volatile: false,
1954+
});
1955+
1956+
// Store capacity (field 2) at offset 16
1957+
let cap_const = self.create_value(HirType::I64, HirValueKind::Constant(crate::hir::HirConstant::I64(num_elements as i64)));
1958+
let offset_16 = self.create_value(HirType::I64, HirValueKind::Constant(crate::hir::HirConstant::I64(16)));
1959+
let cap_field_ptr = self.create_value(HirType::Ptr(Box::new(HirType::I64)), HirValueKind::Instruction);
1960+
self.add_instruction(block_id, HirInstruction::GetElementPtr {
1961+
result: cap_field_ptr,
1962+
ty: HirType::U8, // byte offset
1963+
ptr: list_alloc,
1964+
indices: vec![offset_16],
1965+
});
1966+
self.add_instruction(block_id, HirInstruction::Store {
1967+
value: cap_const,
1968+
ptr: cap_field_ptr,
1969+
align: 8,
1970+
volatile: false,
1971+
});
1972+
1973+
// Return pointer to the List struct
1974+
Ok(list_alloc)
19411975
}
19421976

19431977
TypedExpression::Tuple(elements) => {
@@ -3248,6 +3282,7 @@ impl SsaBuilder {
32483282
PrimitiveType::F32 => HirType::F32,
32493283
PrimitiveType::F64 => HirType::F64,
32503284
PrimitiveType::Unit => HirType::Void,
3285+
PrimitiveType::String => HirType::Ptr(Box::new(HirType::I8)), // String is Ptr<i8>
32513286
_ => HirType::I64, // Default
32523287
},
32533288
Type::Tuple(types) if types.is_empty() => HirType::Void,

crates/compiler/src/trait_lowering.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,20 @@ pub fn convert_type(
345345
))
346346
}
347347

348+
// Extern types (external runtime types like $Tensor, $Audio)
349+
FrontendType::Extern { name, .. } => {
350+
// Convert extern types to opaque types
351+
Ok(HirType::Opaque(*name))
352+
}
353+
354+
// Unresolved types (should be resolved before lowering, but allow as opaque)
355+
FrontendType::Unresolved(name) => {
356+
// Log a warning but allow as opaque type
357+
eprintln!("[WARN] Unresolved type '{}' in HIR lowering - treating as opaque",
358+
name.resolve_global().unwrap_or_default());
359+
Ok(HirType::Opaque(*name))
360+
}
361+
348362
_ => {
349363
// Catch-all for other type variants
350364
Err(crate::CompilerError::Analysis(format!(

crates/whirlwind_adapter/src/adapter.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,9 @@ impl WhirlwindAdapter {
357357
is_external: false,
358358
calling_convention: zyntax_typed_ast::CallingConvention::Default,
359359
link_name: None,
360+
annotations: vec![],
361+
effects: vec![],
362+
is_pure: false,
360363
}),
361364
return_type,
362365
span

crates/zyn_peg/src/runtime2/interpreter.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,30 @@ impl<'g> GrammarInterpreter<'g> {
7373
let start_pos = state.pos();
7474
trace!("execute_rule: {} at pos {}", rule.name, start_pos);
7575

76+
// Packrat memoization: look up (start_pos, rule_id) in the memo cache.
77+
// This converts exponential backtracking to O(n * grammar_size) parsing.
78+
// InProgress entries detect left-recursive rule cycles.
79+
use crate::runtime2::memo::MemoEntry;
80+
let memo_rule_id = self.rule_id_map.get(&rule.name).copied().unwrap_or(usize::MAX);
81+
if let Some(entry) = state.check_memo(memo_rule_id) {
82+
return match entry.clone() {
83+
MemoEntry::Success { value, end_pos } => {
84+
state.set_pos(end_pos);
85+
ParseResult::Success(value, end_pos)
86+
}
87+
MemoEntry::Failure => {
88+
state.set_pos(start_pos);
89+
state.fail("memoized failure")
90+
}
91+
MemoEntry::InProgress => {
92+
state.set_pos(start_pos);
93+
state.fail("left recursion detected")
94+
}
95+
};
96+
}
97+
// Mark this (start_pos, rule_id) as in-progress for left-recursion detection
98+
state.store_memo(memo_rule_id, MemoEntry::InProgress);
99+
76100
// Note: We don't skip whitespace here at the start of a rule.
77101
// Whitespace skipping happens between sequence elements (see execute_pattern).
78102
// This preserves the position for SOI matching.
@@ -122,6 +146,16 @@ impl<'g> GrammarInterpreter<'g> {
122146
// Restore parent bindings after rule completes
123147
state.restore_bindings(saved_bindings);
124148

149+
// Store result in Packrat memo cache at start_pos
150+
let memo_entry = match &final_result {
151+
ParseResult::Success(value, end_pos) => MemoEntry::Success {
152+
value: value.clone(),
153+
end_pos: *end_pos,
154+
},
155+
ParseResult::Failure(_) => MemoEntry::Failure,
156+
};
157+
state.store_memo_at(start_pos, memo_rule_id, memo_entry);
158+
125159
final_result
126160
}
127161

@@ -3673,6 +3707,13 @@ impl<'g> GrammarInterpreter<'g> {
36733707
// Try to match item
36743708
match self.execute_pattern(pattern, state, atomic) {
36753709
ParseResult::Success(v, _) => {
3710+
// Guard: if no input was consumed, break to prevent infinite loop.
3711+
// A zero-width match in a repetition would otherwise spin forever.
3712+
if state.pos() == item_start {
3713+
state.restore_bindings(saved_bindings);
3714+
break;
3715+
}
3716+
36763717
items.push(v.clone());
36773718

36783719
// If there's an inner binding, accumulate its value

crates/zyn_peg/src/runtime2/state.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,16 @@ impl<'a> ParserState<'a> {
389389
self.memo.get(MemoKey { pos: self.pos, rule_id })
390390
}
391391

392-
/// Store result in memo cache
392+
/// Store result in memo cache at current position
393393
pub fn store_memo(&mut self, rule_id: usize, entry: MemoEntry) {
394394
self.memo.insert(MemoKey { pos: self.pos, rule_id }, entry);
395395
}
396396

397+
/// Store result in memo cache at a specific position (use start_pos after rule completes)
398+
pub fn store_memo_at(&mut self, pos: usize, rule_id: usize, entry: MemoEntry) {
399+
self.memo.insert(MemoKey { pos, rule_id }, entry);
400+
}
401+
397402
// =========================================================================
398403
// Binding Management
399404
// =========================================================================

crates/zynml/examples/hello.zynml

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,23 @@ def main() {
2121
let b = Tensor::arange(5.0, 9.0, 1.0) // [5.0, 6.0, 7.0, 8.0]
2222

2323
println("Tensor a:")
24-
println(a)
24+
println_any(a)
2525

2626
println("Tensor b:")
27-
println(b)
27+
println_any(b)
2828

2929
// Arithmetic operations using operator overloading
3030
let sum = a + b
3131
println("a + b:")
32-
println(sum)
32+
println_any(sum)
3333

3434
let diff = b - a
3535
println("b - a:")
36-
println(diff)
36+
println_any(diff)
3737

3838
let prod = a * b
3939
println("a * b (element-wise):")
40-
println(prod)
40+
println_any(prod)
4141

4242
// Reduction operations
4343
let total = a.sum()
@@ -46,14 +46,5 @@ def main() {
4646
let avg = a.mean()
4747
println(f"Mean of a: {avg}")
4848

49-
// Create a matrix
50-
let matrix = Tensor::zeros([2, 3])
51-
println("2x3 zeros matrix:")
52-
println(matrix)
53-
54-
let ones = Tensor::ones([3, 2])
55-
println("3x2 ones matrix:")
56-
println(ones)
57-
5849
println("Done!")
5950
}

0 commit comments

Comments
 (0)