forked from nulang-org/nulang
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmod.rs
More file actions
213 lines (193 loc) · 8.65 KB
/
Copy pathmod.rs
File metadata and controls
213 lines (193 loc) · 8.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
//! AOT (Ahead-of-Time) native code compilation backend.
//!
//! Compiles Nulang MIR modules to native code via Cranelift, leveraging
//! compile-time type information to emit unboxed operations.
//!
//! # Architecture
//!
//! - `codegen`: MIR → Cranelift CLIF compilation (per-function)
//! - This module: orchestrates module-level compilation, registers runtime
//! helpers, and provides the execution entry point.
//!
//! # Current status
//!
//! Uses `cranelift_jit::JITModule` (same as the tiered JIT) rather than
//! true AOT object-file emission. This gives us native code without needing
//! a linker — the trampoline calls into the JIT module at startup.
pub mod codegen;
use cranelift::prelude::*;
use cranelift_frontend::FunctionBuilderContext;
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::Module;
use crate::mir;
use crate::types::{NuResult, Span};
/// Compiled AOT module ready for execution.
pub struct AotModule {
/// The Cranelift JIT module that owns compiled code memory.
#[allow(dead_code)]
jit_module: JITModule,
/// Reusable function builder context.
#[allow(dead_code)]
builder_context: FunctionBuilderContext,
/// Compiled function pointers indexed by MIR function index.
compiled_funcs: Vec<*const u8>,
/// Entry point index (the `__main` or `main` function).
entry_idx: Option<usize>,
}
impl AotModule {
/// Compile a MIR module to native code.
pub fn compile(mir_module: &mir::Module) -> NuResult<Self> {
// Set up Cranelift with the native target ISA.
let mut flag_builder = settings::builder();
let _ = flag_builder.set("enable_simd", "true");
let isa_builder =
cranelift_native::builder().map_err(|msg| crate::types::NuError::VMError {
msg: format!("host machine not supported: {}", msg),
span: Span::default(),
})?;
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.map_err(|e| crate::types::NuError::VMError {
msg: format!("failed to finalize ISA: {}", e),
span: Span::default(),
})?;
let mut jit_builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
// Register NaN-tag-aware runtime helpers.
register_runtime_helpers(&mut jit_builder);
let mut jit_module = JITModule::new(jit_builder);
let mut builder_context = FunctionBuilderContext::new();
// Pass 1: declare all functions so forward references resolve.
let mut func_ids: Vec<cranelift_module::FuncId> =
Vec::with_capacity(mir_module.functions.len());
// Unboxed variants for all-Int functions (same indices, empty for non-Int).
let mut unboxed_ids: Vec<Option<cranelift_module::FuncId>> =
vec![None; mir_module.functions.len()];
for (idx, func) in mir_module.functions.iter().enumerate() {
let func_name = format!("nulang_fn_{}", idx);
let mut sig = jit_module.make_signature();
for _ in &func.params {
sig.params.push(AbiParam::new(types::I64));
}
sig.returns.push(AbiParam::new(types::I64));
let fid = jit_module
.declare_function(&func_name, cranelift_module::Linkage::Local, &sig)
.map_err(|e| crate::types::NuError::VMError {
msg: format!("failed to declare '{}': {}", func.name, e),
span: Span::default(),
})?;
func_ids.push(fid);
// If the function is all-Int, also declare an unboxed variant.
if codegen::is_all_int(func) {
let ub_name = format!("nulang_fn_{}_unboxed", idx);
let mut ub_sig = jit_module.make_signature();
for _ in &func.params {
ub_sig.params.push(AbiParam::new(types::I64));
}
ub_sig.returns.push(AbiParam::new(types::I64));
let ub_fid = jit_module
.declare_function(&ub_name, cranelift_module::Linkage::Local, &ub_sig)
.map_err(|e| crate::types::NuError::VMError {
msg: format!("failed to declare unboxed '{}': {}", func.name, e),
span: Span::default(),
})?;
unboxed_ids[idx] = Some(ub_fid);
}
}
// Pass 2: compile each function body (boxed + optionally unboxed).
let mut entry_idx: Option<usize> = None;
for (idx, func) in mir_module.functions.iter().enumerate() {
// For all-Int functions: compile unboxed body first, then
// generate a boxing wrapper as the boxed entry point. The
// original boxed body is never compiled.
// For non-all-Int functions: compile boxed body as usual.
if let Some(ub_fid) = unboxed_ids[idx] {
// Compile unboxed variant (self-recursive calls resolve to ub_fid).
let mut ctx2 = codegen::AotContext::new(&mut jit_module, &mut builder_context);
ctx2.func_ids = func_ids.clone();
ctx2.func_ids[idx] = ub_fid; // Step 4d: self-calls use unboxed variant
codegen::compile_mir_function_body(
&mut ctx2,
func,
idx,
ub_fid,
codegen::CompileMode::Unboxed,
)
.map_err(|e| crate::types::NuError::VMError {
msg: format!("AOT compilation of unboxed '{}' failed: {}", func.name, e),
span: Span::default(),
})?;
// Compile boxing wrapper as the boxed function table entry.
let mut ctx3 = codegen::AotContext::new(&mut jit_module, &mut builder_context);
codegen::compile_boxing_wrapper(
&mut ctx3,
func.params.len(),
func_ids[idx],
ub_fid,
)
.map_err(|e| crate::types::NuError::VMError {
msg: format!("AOT boxing wrapper for '{}' failed: {}", func.name, e),
span: Span::default(),
})?;
} else {
// Normal boxed compilation for non-all-Int functions.
let mut ctx = codegen::AotContext::new(&mut jit_module, &mut builder_context);
ctx.func_ids = func_ids.clone();
codegen::compile_mir_function_body(
&mut ctx,
func,
idx,
func_ids[idx],
codegen::CompileMode::Boxed,
)
.map_err(|e| crate::types::NuError::VMError {
msg: format!("AOT compilation of '{}' failed: {}", func.name, e),
span: Span::default(),
})?;
}
if func.name == "__main" || func.name == "main" {
if entry_idx.is_none() || func.name == "__main" {
entry_idx = Some(idx);
}
}
}
jit_module
.finalize_definitions()
.map_err(|e| crate::types::NuError::VMError {
msg: format!("failed to finalize JIT definitions: {}", e),
span: Span::default(),
})?;
let compiled_funcs: Vec<*const u8> = func_ids
.iter()
.map(|fid| jit_module.get_finalized_function(*fid))
.collect();
Ok(AotModule {
jit_module,
builder_context,
compiled_funcs,
entry_idx,
})
}
/// Execute the module entry point and return the result as a u64 value.
///
/// The entry point is `__main` if it exists, otherwise `main`, otherwise
/// the first function. Returns the NaN-tagged result value.
pub fn run(&self) -> NuResult<u64> {
let idx = self.entry_idx.unwrap_or(0);
let ptr = self
.compiled_funcs
.get(idx)
.ok_or_else(|| crate::types::NuError::VMError {
msg: "no compiled entry point".into(),
span: Span::default(),
})?;
// Call the compiled function. Signature: extern "C" fn() -> u64
// (for the entry point with no params).
let func: extern "C" fn() -> u64 = unsafe { std::mem::transmute(*ptr) };
Ok(func())
}
}
/// Register all runtime helper symbols with the JIT builder.
/// Single source of truth: `src/jit/helpers.rs` `define_helpers!` macro.
fn register_runtime_helpers(builder: &mut JITBuilder) {
crate::jit::helpers::register_with_builder(builder);
}