Skip to main content

ling_codegen/cranelift/
runtime.rs

1use std::sync::OnceLock;
2
3// ─── NaN-boxed Value Encoding ───────────────────────────────────────────────
4//
5// u64 representation of Ling values using NaN-boxing:
6//
7//   If (val >> 48) == 0x7F: tagged special value
8//     - bits 63-56 = 0x7F (NaN sentinel)
9//     - bits 55-48 = discriminator (0xF5 = general tag, 0x00-0x09 = kind)
10//     - bits  0-47 = payload (pointer or discriminator data)
11//   Else: raw f64 number (bitcast)
12//
13// Tags:
14pub const TAG_UNIT: u64 = 0x7F00_0000_0000_0000;
15pub const TAG_FALSE: u64 = 0x7F01_0000_0000_0000;
16pub const TAG_TRUE: u64 = 0x7F02_0000_0000_0000;
17pub const TAG_MASK: u64 = 0xFFFF_0000_0000_0000;
18pub const TAG_PATTERN: u64 = 0x7F00_0000_0000_0000;
19pub const PTR_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
20
21// The tag discriminator occupies bits 48-55, above the 48-bit pointer area.
22// Bits 56-63 hold the 0x7F NaN sentinel.
23pub const TAG_KIND_UNIT: u64 = 0x0000_0000_0000_0000;
24pub const TAG_KIND_BOOL_FALSE: u64 = 0x0001_0000_0000_0000;
25pub const TAG_KIND_BOOL_TRUE: u64 = 0x0002_0000_0000_0000;
26// For heap-allocated types, the tag kind identifies the variant:
27pub const TAG_KIND_STRING: u64 = 0x0003_0000_0000_0000;
28pub const TAG_KIND_LIST: u64 = 0x0004_0000_0000_0000;
29pub const TAG_KIND_STRUCT: u64 = 0x0005_0000_0000_0000;
30pub const TAG_KIND_VARIANT: u64 = 0x0006_0000_0000_0000;
31pub const TAG_KIND_CLOSURE: u64 = 0x0007_0000_0000_0000;
32pub const TAG_KIND_OK: u64 = 0x0008_0000_0000_0000;
33pub const TAG_KIND_ERR: u64 = 0x0009_0000_0000_0000;
34
35// ─── Builtin Dispatch ──────────────────────────────────────────────────────
36
37/// Signature of the builtin dispatch function provided by the host `ling` crate.
38/// Receives builtin name (ptr+len), args array (ptr+len), returns encoded Value.
39pub type BuiltinDispatch = unsafe extern "C" fn(
40    name_ptr: *const u8,
41    name_len: usize,
42    args_ptr: *const u64,
43    args_len: usize,
44) -> u64;
45
46static BUILTIN_DISPATCH: OnceLock<BuiltinDispatch> = OnceLock::new();
47
48pub fn register_builtin_dispatch(f: BuiltinDispatch) {
49    BUILTIN_DISPATCH.set(f).ok();
50}
51
52/// Call a builtin by name from JIT'd code.
53#[inline]
54pub unsafe fn call_builtin(name: &str, args: &[u64]) -> u64 {
55    let dispatch = BUILTIN_DISPATCH
56        .get()
57        .expect("BUILTIN_DISPATCH not registered");
58    dispatch(name.as_ptr(), name.len(), args.as_ptr(), args.len())
59}
60
61// ─── Value Constructors / Destructors ──────────────────────────────────────
62
63#[inline]
64pub fn encode_f64(val: f64) -> u64 {
65    val.to_bits()
66}
67
68#[inline]
69pub fn decode_f64(val: u64) -> f64 {
70    f64::from_bits(val)
71}
72
73#[inline]
74pub fn is_number(val: u64) -> bool {
75    (val >> 56) != 0x7F
76}
77
78#[inline]
79pub fn is_tagged(val: u64) -> bool {
80    (val >> 56) == 0x7F
81}
82
83#[inline]
84pub fn encode_bool(val: bool) -> u64 {
85    if val {
86        TAG_TRUE
87    } else {
88        TAG_FALSE
89    }
90}
91
92#[inline]
93pub fn decode_bool(val: u64) -> bool {
94    val == TAG_TRUE
95}
96
97#[inline]
98pub const fn encode_unit() -> u64 {
99    TAG_UNIT
100}
101
102/// Encode a heap-allocated value (pointer must be valid and aligned).
103/// The pointer's lower 48 bits are stored, higher bits must be zero-extendable.
104/// On x86-64, user-space pointers are < 2^48 so this is safe.
105#[inline]
106pub fn encode_heap(tag: u64, ptr: *const u8) -> u64 {
107    TAG_PATTERN | tag | (ptr as u64 & PTR_MASK)
108}
109
110/// Extract the pointer from a heap-allocated tagged value.
111#[inline]
112pub fn decode_ptr(val: u64) -> *const u8 {
113    (val & PTR_MASK) as *const u8
114}
115
116/// Extract the kind tag from a tagged value.
117#[inline]
118pub fn tag_kind(val: u64) -> u64 {
119    val & 0x00FF_0000_0000_0000
120}
121
122// ─── Runtime Symbol Table ──────────────────────────────────────────────────
123//
124// Maps JIT symbol names --> extern "C" function names.
125// Used to register symbols with the JIT builder.
126// Each entry: (jit_symbol_name, c_function_name)
127//
128// The JIT compiler emits calls to `__ling_*` symbols which are resolved
129// to C ABI functions either from this crate or registered externally.
130
131pub static SYMBOL_MAP: &[(&str, &[u8])] = &[
132    // Core allocation / panics
133    ("__ling_alloc", b"ling_alloc\0"),
134    ("__ling_free", b"ling_free\0"),
135    ("__ling_panic", b"ling_panic\0"),
136    // Arithmetic
137    ("__ling_add", b"ling_add\0"),
138    ("__ling_sub", b"ling_sub\0"),
139    ("__ling_mul", b"ling_mul\0"),
140    ("__ling_div", b"ling_div\0"),
141    ("__ling_rem", b"ling_rem\0"),
142    ("__ling_neg", b"ling_neg\0"),
143    // Comparison
144    ("__ling_eq", b"ling_eq\0"),
145    ("__ling_ne", b"ling_ne\0"),
146    ("__ling_lt", b"ling_lt\0"),
147    ("__ling_le", b"ling_le\0"),
148    ("__ling_gt", b"ling_gt\0"),
149    ("__ling_ge", b"ling_ge\0"),
150    // Logics
151    ("__ling_and", b"ling_and\0"),
152    ("__ling_or", b"ling_or\0"),
153    ("__ling_not", b"ling_not\0"),
154    // Builtin dispatch (all builtins go through this)
155    ("__ling_builtin", b"ling_builtin\0"),
156    // String operations
157    ("__ling_str_new", b"ling_str_new\0"),
158    ("__ling_str_len", b"ling_str_len\0"),
159    ("__ling_str_concat", b"ling_str_concat\0"),
160    ("__ling_str_eq", b"ling_str_eq\0"),
161    // List operations
162    ("__ling_list_new", b"ling_list_new\0"),
163    ("__ling_list_push", b"ling_list_push\0"),
164    ("__ling_list_get", b"ling_list_get\0"),
165    ("__ling_list_len", b"ling_list_len\0"),
166    // Struct operations
167    ("__ling_struct_new", b"ling_struct_new\0"),
168    ("__ling_struct_get", b"ling_struct_get\0"),
169    // Print / output
170    ("__ling_print", b"ling_print\0"),
171    ("__ling_print_val", b"ling_print_val\0"),
172    ("__ling_print_newline", b"ling_print_newline\0"),
173    ("__ling_time_now", b"ling_time_now\0"),
174    // Numeric helpers (for JIT-inlined operations)
175    ("__ling_f64_add", b"ling_f64_add\0"),
176    ("__ling_f64_sub", b"ling_f64_sub\0"),
177    ("__ling_f64_mul", b"ling_f64_mul\0"),
178    ("__ling_f64_div", b"ling_f64_div\0"),
179    ("__ling_f64_rem", b"ling_f64_rem\0"),
180    ("__ling_f64_neg", b"ling_f64_neg\0"),
181    ("__ling_f64_eq", b"ling_f64_eq\0"),
182    ("__ling_f64_lt", b"ling_f64_lt\0"),
183    ("__ling_f64_gt", b"ling_f64_gt\0"),
184    ("__ling_f64_le", b"ling_f64_le\0"),
185    ("__ling_f64_ge", b"ling_f64_ge\0"),
186    ("__ling_sin", b"ling_sin\0"),
187    ("__ling_cos", b"ling_cos\0"),
188    ("__ling_sqrt", b"ling_sqrt\0"),
189    ("__ling_abs", b"ling_abs\0"),
190    ("__ling_floor", b"ling_floor\0"),
191    ("__ling_ceil", b"ling_ceil\0"),
192    ("__ling_round", b"ling_round\0"),
193    // Bool helpers
194    ("__ling_bool_to_u64", b"ling_bool_to_u64\0"),
195];
196
197// ─── Extern C Reference Functions ──────────────────────────────────────────
198//
199// These are the C ABI entry points called from JIT'd code.
200// They must be provided by the host crate via `register_runtime_symbols`.
201//
202// We declare them as extern "C" references so the JIT builder can bind them.
203
204// Helper: wrap/unwrap f64 values as boxed Values
205extern "C" {
206    pub fn ling_f64_add(a: f64, b: f64) -> f64;
207    pub fn ling_f64_sub(a: f64, b: f64) -> f64;
208    pub fn ling_f64_mul(a: f64, b: f64) -> f64;
209    pub fn ling_f64_div(a: f64, b: f64) -> f64;
210    pub fn ling_f64_rem(a: f64, b: f64) -> f64;
211    pub fn ling_f64_neg(a: f64) -> f64;
212    pub fn ling_f64_eq(a: f64, b: f64) -> u64;
213    pub fn ling_f64_lt(a: f64, b: f64) -> u64;
214    pub fn ling_f64_gt(a: f64, b: f64) -> u64;
215    pub fn ling_f64_le(a: f64, b: f64) -> u64;
216    pub fn ling_f64_ge(a: f64, b: f64) -> u64;
217    pub fn ling_sin(a: f64) -> f64;
218    pub fn ling_cos(a: f64) -> f64;
219    pub fn ling_sqrt(a: f64) -> f64;
220    pub fn ling_abs(a: f64) -> f64;
221    pub fn ling_floor(a: f64) -> f64;
222    pub fn ling_ceil(a: f64) -> f64;
223    pub fn ling_round(a: f64) -> f64;
224    pub fn ling_bool_to_u64(b: u64) -> u64;
225
226    pub fn ling_add(a: u64, b: u64) -> u64;
227    pub fn ling_sub(a: u64, b: u64) -> u64;
228    pub fn ling_mul(a: u64, b: u64) -> u64;
229    pub fn ling_div(a: u64, b: u64) -> u64;
230    pub fn ling_rem(a: u64, b: u64) -> u64;
231    pub fn ling_neg(a: u64) -> u64;
232    pub fn ling_eq(a: u64, b: u64) -> u64;
233    pub fn ling_ne(a: u64, b: u64) -> u64;
234    pub fn ling_lt(a: u64, b: u64) -> u64;
235    pub fn ling_le(a: u64, b: u64) -> u64;
236    pub fn ling_gt(a: u64, b: u64) -> u64;
237    pub fn ling_ge(a: u64, b: u64) -> u64;
238    pub fn ling_and(a: u64, b: u64) -> u64;
239    pub fn ling_or(a: u64, b: u64) -> u64;
240    pub fn ling_not(a: u64) -> u64;
241
242    pub fn ling_alloc(size: u64) -> u64;
243    pub fn ling_free(ptr: u64) -> u64;
244    pub fn ling_panic(msg: u64);
245
246    pub fn ling_str_new(ptr: *const u8, len: usize) -> u64;
247    pub fn ling_str_len(val: u64) -> u64;
248    pub fn ling_str_concat(a: u64, b: u64) -> u64;
249    pub fn ling_str_eq(a: u64, b: u64) -> u64;
250
251    pub fn ling_list_new() -> u64;
252    pub fn ling_list_push(list: u64, val: u64) -> u64;
253    pub fn ling_list_get(list: u64, idx: u64) -> u64;
254    pub fn ling_list_len(list: u64) -> u64;
255
256    pub fn ling_struct_new(
257        name_ptr: *const u8,
258        name_len: usize,
259        args_ptr: *const u64,
260        args_len: usize,
261    ) -> u64;
262    pub fn ling_struct_get(val: u64, field_ptr: *const u8, field_len: usize) -> u64;
263
264    pub fn ling_print(cstr: *const u8);
265    pub fn ling_print_val(val: u64);
266
267    /// Generic builtin dispatcher – calls builtin by name.
268    pub fn ling_builtin(
269        name_ptr: *const u8,
270        name_len: usize,
271        args_ptr: *const u64,
272        args_len: usize,
273    ) -> u64;
274}