Skip to content

Commit 2dfb421

Browse files
committed
feat: Add LLVM JIT runtime symbol linking and TypedAST JSON support
- Add runtime symbol registration to LLVM JIT backend using add_global_mapping() - Fix Void type translation in LLVM backend (use empty struct as unit type) - Fix SSA builder infinite recursion in read_variable_recursive - Add plugin registry integration for runtime symbols (stdlib + Haxe plugins) - Improve TypedAST JSON parsing with While loop support - Add Reflaxe/Haxe for-loop iterator test infrastructure
1 parent 505d1d4 commit 2dfb421

18 files changed

Lines changed: 1618 additions & 3453 deletions

crates/compiler/src/llvm_backend.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2745,6 +2745,10 @@ impl<'ctx> LLVMBackend<'ctx> {
27452745
use HirType::*;
27462746

27472747
let result = match ty {
2748+
Void => {
2749+
// Void/Unit is represented as an empty struct (zero-sized type)
2750+
self.context.struct_type(&[], false).into()
2751+
}
27482752
I8 => self.context.i8_type().into(),
27492753
I16 => self.context.i16_type().into(),
27502754
I32 => self.context.i32_type().into(),

crates/compiler/src/llvm_jit_backend.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ use inkwell::{
2626
execution_engine::ExecutionEngine,
2727
targets::{InitializationConfig, Target},
2828
};
29-
3029
use crate::{CompilerError, CompilerResult};
3130
use crate::hir::{HirModule, HirFunction, HirId};
3231
use crate::llvm_backend::LLVMBackend;
@@ -47,6 +46,10 @@ pub struct LLVMJitBackend<'ctx> {
4746

4847
/// Optimization level
4948
opt_level: OptimizationLevel,
49+
50+
/// Runtime symbols to register with the execution engine
51+
/// Maps function name to function pointer address
52+
runtime_symbols: IndexMap<String, usize>,
5053
}
5154

5255
impl<'ctx> LLVMJitBackend<'ctx> {
@@ -69,9 +72,24 @@ impl<'ctx> LLVMJitBackend<'ctx> {
6972
execution_engine: None,
7073
function_pointers: IndexMap::new(),
7174
opt_level,
75+
runtime_symbols: IndexMap::new(),
7276
})
7377
}
7478

79+
/// Register a runtime symbol that will be available to JIT-compiled code
80+
///
81+
/// Call this before `compile_module` to make external functions available.
82+
pub fn register_symbol(&mut self, name: impl Into<String>, ptr: *const u8) {
83+
self.runtime_symbols.insert(name.into(), ptr as usize);
84+
}
85+
86+
/// Register multiple runtime symbols at once
87+
pub fn register_symbols(&mut self, symbols: &[(&str, *const u8)]) {
88+
for (name, ptr) in symbols {
89+
self.runtime_symbols.insert((*name).to_string(), *ptr as usize);
90+
}
91+
}
92+
7593
/// Compile a full HIR module
7694
///
7795
/// Translates all functions to LLVM IR and JIT compiles them.
@@ -84,21 +102,46 @@ impl<'ctx> LLVMJitBackend<'ctx> {
84102
let mut backend = LLVMBackend::new(self.context, "zyntax_jit");
85103
let _llvm_ir = backend.compile_module(hir_module)?;
86104

87-
// Step 2: Create execution engine from the FULLY POPULATED module
88-
// This is critical - MCJIT takes ownership of the module and compiles it
89-
let execution_engine = backend.into_module()
105+
// Step 2: Collect external function declarations from the module BEFORE consuming it
106+
// We need the function values for add_global_mapping
107+
let mut external_functions: Vec<(String, inkwell::values::FunctionValue<'ctx>)> = Vec::new();
108+
for (_, function) in &hir_module.functions {
109+
if function.is_external {
110+
if let Some(name) = function.name.resolve_global() {
111+
if let Some(llvm_func) = backend.module().get_function(&name) {
112+
external_functions.push((name, llvm_func));
113+
}
114+
}
115+
}
116+
}
117+
118+
// Step 3: Consume the module and create execution engine
119+
let module = backend.into_module();
120+
let execution_engine = module
90121
.create_jit_execution_engine(self.opt_level)
91122
.map_err(|e| CompilerError::Backend(format!("Failed to create JIT execution engine: {}", e)))?;
92123

93-
// Step 3: Extract function pointers from the execution engine
124+
// Step 4: Register runtime symbols with the execution engine using add_global_mapping
125+
for (name, llvm_func) in &external_functions {
126+
if let Some(addr) = self.runtime_symbols.get(name) {
127+
execution_engine.add_global_mapping(llvm_func, *addr);
128+
log::debug!("Registered runtime symbol '{}' at address 0x{:x}", name, addr);
129+
}
130+
}
131+
132+
// Step 5: Extract function pointers from the execution engine
94133
for (id, function) in &hir_module.functions {
134+
// Skip external functions - they don't have compiled code in this module
135+
if function.is_external {
136+
continue;
137+
}
138+
95139
// Must match naming logic in llvm_backend.rs:
96-
// - External functions use their actual name for linking
97140
// - Main function uses actual name for entry point
98141
// - Other functions use mangled name with HirId
99142
let actual_name = function.name.resolve_global()
100143
.unwrap_or_else(|| format!("{:?}", function.name));
101-
let fn_name = if function.is_external || actual_name == "main" {
144+
let fn_name = if actual_name == "main" {
102145
actual_name
103146
} else {
104147
format!("func_{:?}", id)

crates/compiler/src/ssa.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,19 +1982,39 @@ impl SsaBuilder {
19821982
// After IDF placement, don't create new phis
19831983
// Instead, try to read from predecessors to find the value
19841984
if self.idf_placement_done {
1985-
// Try reading from each predecessor
1985+
// CRITICAL: Create a temporary placeholder phi to break cycles in loops
1986+
// Without this, while loop CFG (header -> body -> header) causes infinite recursion
1987+
let ty = self.var_types.get(&var).cloned().unwrap_or(HirType::I64);
1988+
let placeholder = self.create_value(ty.clone(), HirValueKind::Instruction);
1989+
1990+
// Write the placeholder to break cycles
1991+
self.write_variable(var, block, placeholder);
1992+
1993+
// Now try reading from each predecessor
1994+
let mut found_value = None;
19861995
for pred in &predecessors {
19871996
let val = self.read_variable(var, *pred);
1988-
// Check if it's not undef
1989-
if let Some(v) = self.function.values.get(&val) {
1990-
if !matches!(v.kind, HirValueKind::Undef) {
1991-
return val;
1997+
// Check if it's not undef and not our placeholder
1998+
if val != placeholder {
1999+
if let Some(v) = self.function.values.get(&val) {
2000+
if !matches!(v.kind, HirValueKind::Undef) {
2001+
found_value = Some(val);
2002+
break;
2003+
}
19922004
}
19932005
}
19942006
}
1995-
// All predecessors returned undef - variable is truly undefined
1996-
let ty = self.var_types.get(&var).cloned().unwrap_or(HirType::I64);
1997-
return self.create_undef(ty);
2007+
2008+
if let Some(val) = found_value {
2009+
// Found a real value - update definition to use it
2010+
self.write_variable(var, block, val);
2011+
return val;
2012+
}
2013+
2014+
// All predecessors returned undef or placeholder - variable is truly undefined
2015+
let undef = self.create_undef(ty);
2016+
self.write_variable(var, block, undef);
2017+
return undef;
19982018
}
19992019

20002020
let ty = self.var_types.get(&var).cloned()

crates/typed_ast/src/type_registry.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,9 +766,10 @@ pub enum Mutability {
766766
}
767767

768768
/// Visibility annotation
769-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
769+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
770770
pub enum Visibility {
771771
Public,
772+
#[default]
772773
Private,
773774
Protected,
774775
Internal,

crates/typed_ast/src/typed_ast.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,24 @@ pub enum TypedDeclaration {
6060
}
6161

6262
/// Function declaration
63-
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
6464
pub struct TypedFunction {
65+
#[serde(default)]
6566
pub name: InternedString,
67+
#[serde(default)]
6668
pub type_params: Vec<TypedTypeParam>, // Generic type parameters
69+
#[serde(default)]
6770
pub params: Vec<TypedParameter>,
71+
#[serde(default)]
6872
pub return_type: Type,
6973
pub body: Option<TypedBlock>, // None for extern functions
74+
#[serde(default)]
7075
pub visibility: Visibility,
76+
#[serde(default)]
7177
pub is_async: bool,
78+
#[serde(default)]
7279
pub is_external: bool, // True for extern/foreign functions
80+
#[serde(default)]
7381
pub calling_convention: CallingConvention, // Calling convention (C, Rust, System, etc.)
7482
pub link_name: Option<InternedString>, // Override symbol name for linking
7583
}

crates/zyntax_cli/src/backends/llvm_aot.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,31 @@ pub fn compile_and_run_llvm(
161161
info!("Initializing LLVM JIT backend...");
162162
}
163163

164+
// Create plugin registry and register all available runtime plugins
165+
let mut registry = zyntax_compiler::plugin::PluginRegistry::new();
166+
167+
// Register standard library plugin (generic I/O functions)
168+
registry.register(zyntax_runtime::get_plugin())
169+
.map_err(|e| format!("Failed to register stdlib plugin: {}", e))?;
170+
171+
// Register Haxe plugin (frontend-specific runtime)
172+
registry.register(haxe_zyntax_runtime::get_plugin())
173+
.map_err(|e| format!("Failed to register Haxe plugin: {}", e))?;
174+
175+
if verbose {
176+
info!("Registered plugins: {:?}", registry.list_plugins());
177+
}
178+
179+
// Collect all runtime symbols from registered plugins
180+
let runtime_symbols = registry.collect_symbols();
181+
182+
if verbose {
183+
info!("Collected {} runtime symbols", runtime_symbols.len());
184+
for (name, _) in &runtime_symbols {
185+
debug!(" - {}", name);
186+
}
187+
}
188+
164189
// Create LLVM context
165190
let context = Context::create();
166191

@@ -176,6 +201,11 @@ pub fn compile_and_run_llvm(
176201
let mut backend = LLVMJitBackend::with_opt_level(&context, llvm_opt)
177202
.map_err(|e| format!("Failed to create JIT backend: {}", e))?;
178203

204+
// Register runtime symbols with the LLVM JIT backend
205+
for (name, ptr) in &runtime_symbols {
206+
backend.register_symbol(*name, *ptr);
207+
}
208+
179209
if verbose {
180210
info!("Compiling module with LLVM JIT...");
181211
}

0 commit comments

Comments
 (0)