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
1428 lines (1328 loc) · 57.7 KB
/
Copy pathmod.rs
File metadata and controls
1428 lines (1328 loc) · 57.7 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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//! 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::runtime::heap::TypeTag as HeapTypeTag;
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>,
/// Actor behavior names, parallel to `compiled_behaviors`.
behavior_names: Vec<String>,
/// Compiled actor behavior pointers (native code), parallel to
/// `behavior_names`. Empty when the module has no `actor` declarations.
compiled_behaviors: Vec<*const u8>,
/// Entry point index (the `__main` or `main` function).
entry_idx: Option<usize>,
/// Module-wide field name → slot index mapping for records.
#[allow(dead_code)]
field_map: std::collections::HashMap<String, u8>,
/// Constant pool (String literals), for runtime string resolution.
constants: Vec<crate::bytecode::Constant>,
/// The bytecode `CodeModule` compiled from the same MIR, retained so a
/// native behavior can spawn real Runtime actors through
/// `Runtime::spawn_from_module` (which needs a CodeModule). Built
/// best-effort alongside the AOT code.
code_module: Option<crate::bytecode::CodeModule>,
}
impl AotModule {
/// Compile a MIR module to native code for the specified target.
pub fn compile(mir_module: &mir::Module) -> NuResult<Self> {
Self::compile_for_target(mir_module, "native")
}
/// Compile a MIR module to native code for a specific target ISA.
pub fn compile_for_target(mir_module: &mir::Module, target: &str) -> NuResult<Self> {
// Set up Cranelift with the target ISA.
let mut flag_builder = settings::builder();
let _ = flag_builder.set("enable_simd", "true");
let _ = flag_builder.set("opt_level", "speed");
let isa_builder = create_isa_builder(target)?;
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.map_err(|e| crate::types::NuError::VMError {
msg: format!("failed to finalize ISA for target '{}': {}", target, 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();
// Pre-scan: build module-wide field name → slot index map and
// constant pool for string literals.
let mut field_map: std::collections::HashMap<String, u8> = std::collections::HashMap::new();
let mut next_field_id: u8 = 0;
let mut constants: Vec<crate::bytecode::Constant> = Vec::new();
for func in &mir_module.functions {
for block in &func.blocks {
for stmt in &block.stmts {
collect_field_and_consts(
stmt,
&mut field_map,
&mut next_field_id,
&mut constants,
&mir_module.foreign_functions,
);
}
}
}
for func in &mir_module.behaviors {
for block in &func.blocks {
for stmt in &block.stmts {
collect_field_and_consts(
stmt,
&mut field_map,
&mut next_field_id,
&mut constants,
&mir_module.foreign_functions,
);
}
}
}
// 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));
}
for _ in &func.captures {
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));
}
for _ in &func.captures {
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;
ctx2.field_map = field_map.clone();
ctx2.constants = constants.clone();
ctx2.foreign_functions = mir_module.foreign_functions.clone();
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();
ctx.field_map = field_map.clone();
ctx.constants = constants.clone();
ctx.foreign_functions = mir_module.foreign_functions.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);
}
}
}
// Pass: compile actor behaviors to native code, indexed by behavior
// name. Behaviors are ordinary `Function`s (params + blocks); they
// are never `Call` targets, so each compiles into its own native
// entry point keyed by name. The actor runtime can later dispatch
// messages straight to these pointers, bypassing the bytecode VM.
let mut behavior_names: Vec<String> = Vec::new();
let mut behavior_fids: Vec<cranelift_module::FuncId> = Vec::new();
for (idx, func) in mir_module.behaviors.iter().enumerate() {
let func_name = format!("nulang_behavior_{}", 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 behavior '{}': {}", func.name, e),
span: Span::default(),
})?;
let mut ctx = codegen::AotContext::new(&mut jit_module, &mut builder_context);
ctx.func_ids = func_ids.clone();
ctx.field_map = field_map.clone();
ctx.constants = constants.clone();
codegen::compile_mir_function_body(
&mut ctx,
func,
idx,
fid,
codegen::CompileMode::Boxed,
)
.map_err(|e| crate::types::NuError::VMError {
msg: format!("AOT compilation of behavior '{}' failed: {}", func.name, e),
span: Span::default(),
})?;
behavior_names.push(func.name.clone());
behavior_fids.push(fid);
}
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();
let compiled_behaviors: Vec<*const u8> = behavior_fids
.iter()
.map(|fid| jit_module.get_finalized_function(*fid))
.collect();
// Best-effort bytecode companion so native spawn can route through
// `Runtime::spawn_from_module`. The AOT JIT path borrows the MIR
// immutably throughout, so the companion compiles an optimized
// clone rather than mutating the shared module.
let mut optimized = mir_module.clone();
let code_module = crate::mir_codegen::compile_mir(&mut optimized, &mir_module.name).ok();
Ok(AotModule {
jit_module,
builder_context,
compiled_funcs,
behavior_names,
compiled_behaviors,
entry_idx,
field_map,
constants,
code_module,
})
}
/// The bytecode `CodeModule` compiled from the same MIR, if it compiled.
pub fn code_module(&self) -> Option<&crate::bytecode::CodeModule> {
self.code_module.as_ref()
}
/// Look up a compiled behavior's native entry pointer by name.
///
/// Returns `None` when the module has no behavior with that name. The
/// returned pointer is a function with the AOT calling convention:
/// `extern "C" fn(boxed_param_0, boxed_param_1, ...) -> u64`. It is only
/// valid while the `AotModule` is alive (the pointer lives in the JIT
/// code memory it owns).
pub fn fn_ptr_for_behavior(&self, name: &str) -> Option<*const u8> {
self.behavior_names
.iter()
.position(|n| n == name)
.map(|idx| self.compiled_behaviors[idx])
}
/// The module's constant pool (string literals). AOT behavior dispatch
/// sets these so `StateGet`/`StateSet` field names resolve.
pub fn constants(&self) -> &[crate::bytecode::Constant] {
&self.constants
}
/// Unique actor type names declared by this module, derived from the
/// `"{Actor}.{behavior}"` behavior-name prefixes.
pub fn actor_type_names(&self) -> Vec<String> {
let mut names: Vec<String> = self
.behavior_names
.iter()
.filter_map(|n| n.split('.').next().map(str::to_string))
.collect();
names.sort();
names.dedup();
names
}
/// Create a standalone actor of the type referenced by `behavior_idx`
/// (the actor's first behavior's module index, per `spawn_behavior_idx`).
/// Registers all of the actor's behaviors with the AOT adapter (in module
/// order, so the actor's local behavior-table indices match module
/// indices), applies `init` state overrides (name constant idx → value),
/// and returns the new actor's id. The spawned actor is boxed and owned by
/// this module's registry, and its raw pointer is registered in
/// `AOT_ACTORS` so native `send` can deliver to it.
pub fn spawn_actor(
&self,
behavior_idx: usize,
init: Vec<(u64, crate::vm::Value)>,
) -> Option<u64> {
let full = self.behavior_names.get(behavior_idx)?;
let actor_name = full.split('.').next()?.to_string();
let id = AOT_FRESH_ACTOR_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let mut actor = Box::new(crate::runtime::Actor::new(id, actor_name.clone(), 64));
let prefix = format!("{}.", actor_name);
for name in &self.behavior_names {
if let Some(short) = name.strip_prefix(&prefix) {
actor.register_behavior(short.to_string(), aot_behavior_adapter);
}
}
for (name_idx, value) in init {
let s = self
.constants
.get(name_idx as usize)
.and_then(|c| match c {
crate::bytecode::Constant::String(s) => Some(s.clone()),
_ => None,
})
.unwrap_or_default();
actor.set_state_field(s, value);
}
let raw = &mut *actor as *mut crate::runtime::Actor;
AOT_SPAWNED_ACTORS.with(|m| {
m.borrow_mut().insert(id, actor);
});
AOT_ACTORS.with(|m| {
m.borrow_mut().insert(id, raw);
});
Some(id)
}
/// Execute the module entry point and return the result as a u64 value.
///
/// A module with no `__main`/`main` (e.g. only function definitions, a
/// library) has no entry expression; running it yields nil, matching the
/// interpreter. Do NOT fall back to function 0 — that could be a
/// parameterized function, and calling it with no args would return
/// garbage.
pub fn run(&self) -> NuResult<u64> {
// A module with no `__main`/`main` (e.g. only function definitions, a
// library) has no entry expression; running it yields nil, matching the
// interpreter. Do NOT fall back to function 0 — that could be a
// parameterized function, and calling it with no args would return
// garbage.
let Some(idx) = self.entry_idx else {
return Ok(crate::vm::Value::nil().as_raw());
};
let ptr = self
.compiled_funcs
.get(idx)
.ok_or_else(|| crate::types::NuError::VMError {
msg: "no compiled entry point".into(),
span: Span::default(),
})?;
// Set up standalone heap for AOT runtime helpers.
let mut heap = crate::runtime::heap::ActorHeap::new(1024 * 1024);
heap.set_actor_id(0);
crate::jit::runtime::aot_set_heap(heap);
// Set up constant pool for string resolution.
if !self.constants.is_empty() {
unsafe {
crate::jit::runtime::aot_set_constants(&self.constants);
}
}
// Arm the compiled-function context so captured closures can resolve
// their target's native entry point.
set_aot_module_ctx(self);
// Install StandaloneVmCallbacks so perform_builtin_effect works
// (e.g., IO.print, String.length, etc.) in the native backend.
// This mirrors how the bytecode VM uses StandaloneVmCallbacks for
// top-level execution in `VM::run()`.
let callbacks = Box::new(crate::vm::StandaloneVmCallbacks::new());
let callbacks_ptr = Box::into_raw(callbacks) as *mut dyn crate::vm::ActorVmCallbacks;
unsafe {
crate::jit::runtime::set_jit_callbacks(callbacks_ptr);
}
// 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) };
let result = func();
// Clean up: reconstruct Box to drop callbacks and free heap/GC.
unsafe {
crate::jit::runtime::clear_jit_callbacks();
let _ = Box::from_raw(callbacks_ptr as *mut crate::vm::StandaloneVmCallbacks);
}
crate::jit::runtime::aot_clear_constants();
clear_aot_module_ctx();
let _ = crate::jit::runtime::aot_take_heap();
Ok(result)
}
/// Emit assembly text for the compiled module.
pub fn emit_assembly(&self) -> String {
// For now, we'll just show the function names and basic info
// Full assembly emission would require using cranelift_object or TextSectionBuilder
let mut output = String::new();
output.push_str(&format!("; AOT Module for target\n"));
output.push_str(&format!("; Functions: {}\n", self.compiled_funcs.len()));
for (idx, _) in self.compiled_funcs.iter().enumerate() {
output.push_str(&format!("nulang_fn_{}:\n", idx));
output.push_str(" ; [assembly would be emitted here]\n");
}
output
}
}
// ---------------------------------------------------------------------------
// Native behavior dispatch
// ---------------------------------------------------------------------------
// Bridges the actor runtime's plain-fn behavior handler
// (`fn(&mut Actor, &[Value])`) to AOT-compiled native code. The compiled
// behavior functions are `extern "C" fn(boxed_param...) -> u64`; this adapter
// (a) installs an `ActorVmCallbacks` over the target actor so `StateGet` /
// `StateSet` / heap ops inside the native body route to it, and (b) packs the
// message payload into boxed args and calls the native pointer.
//
// The native target is supplied through a thread-local (set by the driver
// immediately before invoking the handler), mirroring how `set_jit_callbacks`
// feeds the VM's tiered JIT. This keeps the adapter a plain `fn` so it can sit
// in `Actor::behavior_table` without a closure.
/// Per-behavior AOT dispatch info the scheduler (or standalone driver) arms
/// before invoking `aot_behavior_adapter`. Holds the native fn pointer, the
/// owning `AotModule` (for the constant pool and spawn context), and the real
/// `Runtime` when dispatching inside the actor runtime (null in the
/// standalone driver).
#[derive(Clone, Copy)]
pub struct AotDispatchTarget {
/// Native entry point of the behavior (`extern "C" fn(boxed...) -> u64`).
pub fn_ptr: *const u8,
/// The module that compiled `fn_ptr`; kept alive by the owning Runtime or
/// the standalone driver for the duration of the dispatch.
pub module: *const AotModule,
/// The real actor `Runtime`, or null when dispatching standalone.
pub runtime: *mut crate::runtime::Runtime,
}
impl AotDispatchTarget {
/// Build a standalone (no real Runtime) target from an `&AotModule`.
pub fn standalone(fn_ptr: *const u8, module: &AotModule) -> Self {
AotDispatchTarget {
fn_ptr,
module: module as *const AotModule,
runtime: std::ptr::null_mut(),
}
}
}
thread_local! {
/// Native target the next `aot_behavior_adapter` call dispatches through.
/// None when no target is armed.
static AOT_DISPATCH: std::cell::RefCell<Option<AotDispatchTarget>> =
std::cell::RefCell::new(None);
}
/// Arm the thread-local native target for the next `aot_behavior_adapter`
/// invocation, and install the module constant pool so `StateGet`/`StateSet`/
/// spawn field-name string constants resolve. The driver must call this
/// immediately before dispatching a message to an AOT-compiled behavior, and
/// `clear_aot_dispatch` after.
pub fn set_aot_dispatch(target: Option<AotDispatchTarget>) {
if let Some(t) = target {
// SAFETY: `t.module` outlives the dispatched native call (owned by the
// Runtime or the standalone driver's local). `aot_set_constants` copies
// the slice.
unsafe {
crate::jit::runtime::aot_set_constants(&(*t.module).constants());
set_aot_module_ctx(&*t.module);
}
}
AOT_DISPATCH.with(|c| *c.borrow_mut() = target);
}
/// Disarm the native target after a dispatched behavior returns.
pub fn clear_aot_dispatch() {
AOT_DISPATCH.with(|c| *c.borrow_mut() = None);
crate::jit::runtime::aot_clear_constants();
clear_aot_module_ctx();
}
thread_local! {
/// The `AotModule` whose `spawn_actor` resolves the next
/// `nulang_aot_spawn` call (armed by the driver around dispatch).
static AOT_SPAWN_CTX: std::cell::RefCell<*const AotModule> =
std::cell::RefCell::new(std::ptr::null());
/// The `AotModule` whose compiled function table resolves the next
/// `nulang_aot_resolve_fn` call (armed around dispatch, so captured
/// closures can look up their target's native entry point).
static AOT_MODULE_CTX: std::cell::RefCell<*const AotModule> =
std::cell::RefCell::new(std::ptr::null());
}
/// Arm the module whose compiled function table resolves closure targets.
/// The caller must clear it after dispatch.
pub fn set_aot_module_ctx(module: &AotModule) {
AOT_MODULE_CTX.with(|c| *c.borrow_mut() = module as *const AotModule);
}
/// Disarm the compiled-function context.
pub fn clear_aot_module_ctx() {
AOT_MODULE_CTX.with(|c| *c.borrow_mut() = std::ptr::null());
}
/// The armed module's constant pool, for callbacks that resolve string
/// arguments (async effect dispatch). Empty when no module is armed.
pub fn aot_module_constants() -> &'static [crate::bytecode::Constant] {
let module = AOT_MODULE_CTX.with(|c| *c.borrow());
if module.is_null() {
&[]
} else {
// SAFETY: the armed module outlives the dispatched native call.
unsafe { (*module).constants() }
}
}
/// Native-code entry point for captured-closure dispatch: resolve a compiled
/// function pointer by MIR function index from the armed module context.
/// Returns the pointer as u64 (0 when no module is armed or the index is out
/// of range). Defined here (not in `jit/runtime.rs`) because it needs
/// `AotModule`; the JIT linker resolves it by symbol name at link time.
#[no_mangle]
pub unsafe extern "C" fn nulang_aot_resolve_fn(fn_idx: u64) -> u64 {
let module = AOT_MODULE_CTX.with(|c| *c.borrow());
if module.is_null() {
return 0;
}
let m = unsafe { &*module };
m.compiled_funcs
.get(fn_idx as usize)
.copied()
.map(|p| p as u64)
.unwrap_or(0)
}
/// Arm the module the next `nulang_aot_spawn` (from native behavior code)
/// uses to create actors. The driver must call this before dispatching a
/// behavior that spawns, and `clear_aot_spawn_ctx` after.
pub fn set_aot_spawn_ctx(module: &AotModule) {
AOT_SPAWN_CTX.with(|c| *c.borrow_mut() = module as *const AotModule);
}
/// Disarm the spawn context after a dispatched native behavior returns.
pub fn clear_aot_spawn_ctx() {
AOT_SPAWN_CTX.with(|c| *c.borrow_mut() = std::ptr::null());
}
/// Native-code entry point for `RValue::Spawn`: creates an actor of the type
/// whose first behavior is at module index `behavior_idx`, applying any queued
/// init pairs. When dispatched inside the real actor `Runtime` (the armed
/// `AOT_DISPATCH` target carries a non-null runtime), the spawn routes through
/// `Runtime::spawn_from_module` so the new actor joins the scheduler and gets
/// AOT-wired; otherwise it creates a boxed standalone actor. Returns the new
/// actor's id (boxed), or nil when no context is armed. Defined here (not in
/// `jit/runtime.rs`) because it needs `AotModule`; the JIT linker resolves it
/// by symbol name at link time.
#[no_mangle]
pub unsafe extern "C" fn nulang_aot_spawn(behavior_idx: u64) -> u64 {
let init = crate::jit::runtime::take_aot_spawn_init();
let dispatch = AOT_DISPATCH.with(|c| *c.borrow());
if let Some(t) = dispatch {
let module = unsafe { &*t.module };
if !t.runtime.is_null() {
// Real Runtime path: spawn through the scheduler so the new actor
// is a live runtime actor (and its behaviors are AOT-wired).
if let Some(code) = module.code_module() {
let init: Vec<(String, crate::vm::Value)> = init
.iter()
.map(|(idx, v)| {
let name = module
.constants()
.get(*idx as usize)
.and_then(|c| match c {
crate::bytecode::Constant::String(s) => Some(s.clone()),
_ => None,
})
.unwrap_or_default();
(name, *v)
})
.collect();
let val =
unsafe { (*t.runtime).spawn_from_module(code, behavior_idx as usize, init) };
return val.as_raw();
}
return crate::vm::Value::nil().as_raw();
}
// Standalone path: spawn a boxed standalone actor.
return match module.spawn_actor(behavior_idx as usize, init) {
Some(id) => crate::vm::Value::actor_ref(id).as_raw(),
None => crate::vm::Value::nil().as_raw(),
};
}
// Fallback: standalone spawn via the explicit spawn context.
let module = AOT_SPAWN_CTX.with(|c| *c.borrow());
if module.is_null() {
return crate::vm::Value::nil().as_raw();
}
match (*module).spawn_actor(behavior_idx as usize, init) {
Some(id) => crate::vm::Value::actor_ref(id).as_raw(),
None => crate::vm::Value::nil().as_raw(),
}
}
// ---------------------------------------------------------------------------
// AOT builtin effect dispatch
// ---------------------------------------------------------------------------
// `perform <Effect>.<op>(args...)` in an AOT-compiled behavior with no
// statically-resolved user handler (`resolved_handler: None`) lowers to an
// arity-matched `nulang_aot_perform_N` call. The helper resolves the effect/
// op strings (TAG_STRING constants from the module pool), collects the boxed
// args, and routes through the current callbacks' `perform_builtin_effect_in_module`,
// which (via the real Runtime) dispatches IO/Actor/Timer/Test/Otp/Http/Workflow
// builtins exactly as the bytecode VM does. This covers the dominant
// builtin-effect usage without continuations. Dynamically-handled user
// effects (an active handler for the same effect at runtime) are not
// supported by the native backend — the compile-time `resolved_handler: None`
// only guarantees no *lexical* handler, matching the bytecode fallback for
// unbound effects. Outside an actor context the helper degrades to nil.
macro_rules! define_aot_perform {
($name:ident, $($arg:ident),*) => {
/// Perform a builtin effect from AOT-compiled code.
#[no_mangle]
pub unsafe extern "C" fn $name(eff_raw: u64, op_raw: u64 $(, $arg: u64)*) -> u64 {
let effect = crate::jit::runtime::resolve_string_coerce(eff_raw).unwrap_or_default();
let op = crate::jit::runtime::resolve_string_coerce(op_raw).unwrap_or_default();
let regs = [$(crate::vm::Value::from_bits($arg)),*];
// The module is only needed by `perform_builtin_effect_in_module`
// for a few effects (Otp/Http resolve against it); the common
// IO/Actor/Timer path ignores it.
let module = AOT_DISPATCH.with(|c| {
c.borrow()
.and_then(|t| unsafe { (&*t.module).code_module() })
.map(|cm| cm as *const crate::bytecode::CodeModule)
.unwrap_or(std::ptr::null())
});
let constants = if module.is_null() {
crate::aot::aot_module_constants()
} else {
unsafe { &(*module).constants }
};
crate::jit::runtime::try_with_callbacks(|cb| {
if module.is_null() {
cb.perform_builtin_effect(&effect, Some(&op), constants, ®s)
} else {
cb.perform_builtin_effect_in_module(
&effect,
Some(&op),
unsafe { &*module },
®s,
)
}
})
.flatten()
.unwrap_or_else(crate::vm::Value::nil)
.as_raw()
}
};
}
define_aot_perform!(nulang_aot_perform_0,);
define_aot_perform!(nulang_aot_perform_1, a0);
define_aot_perform!(nulang_aot_perform_2, a0, a1);
define_aot_perform!(nulang_aot_perform_3, a0, a1, a2);
define_aot_perform!(nulang_aot_perform_4, a0, a1, a2, a3);
define_aot_perform!(nulang_aot_perform_5, a0, a1, a2, a3, a4);
define_aot_perform!(nulang_aot_perform_6, a0, a1, a2, a3, a4, a5);
define_aot_perform!(nulang_aot_perform_7, a0, a1, a2, a3, a4, a5, a6);
define_aot_perform!(nulang_aot_perform_8, a0, a1, a2, a3, a4, a5, a6, a7);
thread_local! {
/// Standalone actor registry: actor id → raw actor pointer. Populated by
/// the AOT driver so `send` from native behavior code can deliver into a
/// target actor's mailbox without a full `Runtime`.
static AOT_ACTORS: std::cell::RefCell<std::collections::HashMap<u64, *mut crate::runtime::Actor>> =
std::cell::RefCell::new(std::collections::HashMap::new());
}
thread_local! {
/// Ownership store for actors created by `AotModule::spawn_actor`. A
/// spawned actor is boxed here (so its heap-allocated pointer is stable)
/// and its raw pointer is also registered in `AOT_ACTORS` for `send`.
static AOT_SPAWNED_ACTORS: std::cell::RefCell<std::collections::HashMap<u64, Box<crate::runtime::Actor>>> =
std::cell::RefCell::new(std::collections::HashMap::new());
}
/// Next id for a standalone-spawned actor, kept clear of the small ids the
/// tests use for manually-created actors.
static AOT_FRESH_ACTOR_ID: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(1_000_000);
/// Register a standalone actor so native `send` can deliver to its mailbox.
/// The pointer must stay valid until `unregister_aot_actor`.
pub fn register_aot_actor(actor: &mut crate::runtime::Actor) {
AOT_ACTORS.with(|c| {
c.borrow_mut()
.insert(actor.id, actor as *mut crate::runtime::Actor);
});
}
/// Ids of every actor registered in the standalone send registry (both
/// driver-registered and spawned).
pub fn aot_actor_ids() -> Vec<u64> {
AOT_ACTORS.with(|c| c.borrow().keys().copied().collect())
}
/// Read the actor pointer for an id owned by the standalone spawn registry.
pub fn aot_spawned_actor(id: u64) -> Option<*mut crate::runtime::Actor> {
AOT_SPAWNED_ACTORS.with(|m| {
m.borrow()
.get(&id)
.map(|b| &**b as *const crate::runtime::Actor as *mut crate::runtime::Actor)
})
}
/// Remove a standalone actor from the native send registry.
pub fn unregister_aot_actor(id: u64) {
AOT_ACTORS.with(|c| {
c.borrow_mut().remove(&id);
});
}
/// Invoke an AOT-compiled behavior with a boxed payload (arity-matched). The
/// target is the `AOT_DISPATCH` thread-local armed by the driver/scheduler.
fn call_aot_behavior(ptr: *const u8, raw: &[u64]) {
// SAFETY (each arm): `ptr` is a finalized AOT behavior with this arity.
match raw.len() {
0 => {
let f: extern "C" fn() -> u64 = unsafe { std::mem::transmute(ptr) };
let _ = f();
}
1 => {
let f: extern "C" fn(u64) -> u64 = unsafe { std::mem::transmute(ptr) };
let _ = f(raw[0]);
}
2 => {
let f: extern "C" fn(u64, u64) -> u64 = unsafe { std::mem::transmute(ptr) };
let _ = f(raw[0], raw[1]);
}
3 => {
let f: extern "C" fn(u64, u64, u64) -> u64 = unsafe { std::mem::transmute(ptr) };
let _ = f(raw[0], raw[1], raw[2]);
}
4 => {
let f: extern "C" fn(u64, u64, u64, u64) -> u64 = unsafe { std::mem::transmute(ptr) };
let _ = f(raw[0], raw[1], raw[2], raw[3]);
}
5 => {
let f: extern "C" fn(u64, u64, u64, u64, u64) -> u64 =
unsafe { std::mem::transmute(ptr) };
let _ = f(raw[0], raw[1], raw[2], raw[3], raw[4]);
}
6 => {
let f: extern "C" fn(u64, u64, u64, u64, u64, u64) -> u64 =
unsafe { std::mem::transmute(ptr) };
let _ = f(raw[0], raw[1], raw[2], raw[3], raw[4], raw[5]);
}
7 => {
let f: extern "C" fn(u64, u64, u64, u64, u64, u64, u64) -> u64 =
unsafe { std::mem::transmute(ptr) };
let _ = f(raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6]);
}
8 => {
let f: extern "C" fn(u64, u64, u64, u64, u64, u64, u64, u64) -> u64 =
unsafe { std::mem::transmute(ptr) };
let _ = f(
raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6], raw[7],
);
}
n => panic!(
"call_aot_behavior: unsupported arity {} (add an arity arm)",
n
),
}
}
/// `Actor::register_behavior` handler that runs the actor's current message
/// through AOT-compiled native code, bypassing the bytecode VM.
///
/// Reads the armed `AOT_DISPATCH` target to find the native entry point and
/// select callbacks: when dispatching inside the real actor `Runtime` (the
/// target's `runtime` is non-null) it uses `AotRuntimeCallbacks`, which route
/// state/send/receive/alloc through the runtime; otherwise it uses the
/// standalone `AotActorCallbacks` over the raw actor.
pub fn aot_behavior_adapter(actor: &mut crate::runtime::Actor, args: &[crate::vm::Value]) {
let target = AOT_DISPATCH.with(|c| *c.borrow());
let target =
target.expect("aot_behavior_adapter: no native target armed (call set_aot_dispatch first)");
assert!(
!target.fn_ptr.is_null(),
"aot_behavior_adapter: null fn ptr"
);
let raw: Vec<u64> = args.iter().map(|v| v.as_raw()).collect();
if target.runtime.is_null() {
// SAFETY: `actor` outlives the native call; `cb` holds a raw pointer
// to it (mirroring `BytecodeRuntimeCallbacks`) so the `dyn
// ActorVmCallbacks` fat pointer coerces to `'static` for the
// thread-local, and is cleared before `cb` (and the borrow) ends.
let mut cb = AotActorCallbacks {
actor: actor as *mut crate::runtime::Actor,
};
unsafe { crate::jit::runtime::set_jit_callbacks(&mut cb) };
call_aot_behavior(target.fn_ptr, &raw);
crate::jit::runtime::clear_jit_callbacks();
crate::jit::runtime::aot_clear_constants();
} else {
// SAFETY: the scheduler holds `&mut Runtime` while dispatching, so the
// raw pointer is a live, exclusively-borrowed handle; the callback is
// cleared before the borrow (and dispatch) ends.
let mut cb = AotRuntimeCallbacks {
runtime: target.runtime,
actor_id: actor.id,
};
unsafe { crate::jit::runtime::set_jit_callbacks(&mut cb) };
call_aot_behavior(target.fn_ptr, &raw);
crate::jit::runtime::clear_jit_callbacks();
crate::jit::runtime::aot_clear_constants();
}
}
/// Minimal `ActorVmCallbacks` that routes AOT actor operations (state access,
/// heap allocation) to a single `Actor`. Used by `aot_behavior_adapter` so
/// `StateGet`/`StateSet` and object allocation inside a native behavior body
/// target the right actor. Spawn/Send are unsupported in the standalone
/// native path (they need the full `Runtime`).
struct AotActorCallbacks {
/// Raw pointer to the actor, kept alive by the caller across the native
/// call. Mirrors `BytecodeRuntimeCallbacks` (raw `*mut Runtime`) so the
/// fat pointer stored in `JIT_CALLBACKS` is `'static`.
actor: *mut crate::runtime::Actor,
}
impl std::fmt::Debug for AotActorCallbacks {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "AotActorCallbacks(actor={:p})", self.actor)
}
}
impl crate::vm::ActorVmCallbacks for AotActorCallbacks {
fn current_actor_id(&self) -> Option<u64> {
// SAFETY: `actor` is the caller's live `&mut Actor`.
Some(unsafe { (*self.actor).id })
}
fn alloc(&mut self, size: usize, type_tag: HeapTypeTag) -> Option<*mut u8> {
// SAFETY: `actor` is the caller's live `&mut Actor`.
unsafe { (*self.actor).heap.alloc(size, type_tag) }
}
fn drop_ref(&mut self, ptr: *mut u8) {
// SAFETY: both raw pointers are valid; `ptr` is from this actor's heap.
unsafe {
(*self.actor)
.orca_gc
.drop_local_ref(&mut (*self.actor).heap, ptr)
};
}
fn retain_ref(&mut self, ptr: *mut u8) {
// SAFETY: both raw pointers are valid; `ptr` is from this actor's heap.
unsafe { (*self.actor).orca_gc.local_ref(&(*self.actor).heap, ptr) };
}
fn array_len(&self, ptr: *mut u8) -> Option<usize> {
// SAFETY: `ptr` is a valid heap pointer from this actor's heap.
unsafe {
let header = &*crate::runtime::heap::ActorHeap::header_of(ptr);
if header.type_tag == HeapTypeTag::Array {
let payload = header
.size
.saturating_sub(crate::runtime::heap::ActorHeap::HEADER_SIZE);
Some(payload / std::mem::size_of::<crate::vm::Value>())
} else {
None
}
}
}
fn get_state_field(&self, field: &str) -> crate::vm::Value {
// SAFETY: `actor` is the caller's live `&mut Actor`.
unsafe {
(*self.actor)
.get_state_field(field)
.unwrap_or(crate::vm::Value::nil())
}
}
fn set_state_field(&mut self, field: &str, value: crate::vm::Value) {
// SAFETY: `actor` is the caller's live `&mut Actor`.
unsafe { (*self.actor).set_state_field(field, value) };
}
fn spawn_actor(
&mut self,
_module: &crate::bytecode::CodeModule,
_behavior_idx: usize,
_init: Vec<(String, crate::vm::Value)>,
) -> crate::vm::Value {
crate::vm::Value::actor_ref(0)
}
fn try_receive(&mut self) -> Option<(u16, crate::vm::Value)> {
// SAFETY: `actor` is the caller's live `&mut Actor`; mailbox access
// runs on the owning thread (the standalone driver's dispatcher).
unsafe { (*self.actor).mailbox.pop() }.map(|msg| {
let first = msg
.payload
.first()
.copied()
.unwrap_or(crate::vm::Value::nil());
(msg.behavior_id, first)
})
}
fn try_receive_match(
&mut self,
behavior_ids: &[u16],
) -> Option<(usize, Vec<crate::vm::Value>)> {
// SAFETY: `actor` is the caller's live `&mut Actor`; mailbox access
// runs on the owning thread (the standalone driver's dispatcher).
unsafe { (*self.actor).mailbox.receive_match(behavior_ids) }
.map(|(pos, payload)| (pos, payload.to_vec()))
}
fn send_message(
&mut self,
target: crate::vm::Value,
behavior_id: u16,
args: &[crate::vm::Value],
) {
let Some(target_id) = target.as_actor_id() else {
return;
};
// SAFETY: registry entries are registered by the driver and unregistered
// before the actor drops; the pointer is valid for this dispatch.
let target_actor = AOT_ACTORS.with(|c| c.borrow().get(&target_id).copied());