1use crate::artifact::NativeArch;
19use crate::artifact::NativeImage;
20use crate::artifact::NativeOs;
21use crate::data::Data;
22use crate::instr::Instr;
23
24pub mod abi {
27 use super::NativeCtx;
28 use core::mem::offset_of;
29
30 pub const VERSION: u32 = 1;
33
34 pub const CTX_REGS: i32 = offset_of!(NativeCtx, regs) as i32;
35 pub const CTX_OBJS: i32 = offset_of!(NativeCtx, objs) as i32;
36 pub const CTX_STATUS: i32 = offset_of!(NativeCtx, status) as i32;
37 pub const CTX_ERR_AT: i32 = offset_of!(NativeCtx, err_at) as i32;
38 pub const CTX_ERR_A: i32 = offset_of!(NativeCtx, err_a) as i32;
39 pub const CTX_ERR_B: i32 = offset_of!(NativeCtx, err_b) as i32;
40 pub const CTX_MARKING: i32 = offset_of!(NativeCtx, marking) as i32;
41 pub const CTX_RET: i32 = offset_of!(NativeCtx, ret) as i32;
42 pub const CTX_DYLIBS: i32 = offset_of!(NativeCtx, dylibs) as i32;
43 pub const CTX_SHADE: i32 = offset_of!(NativeCtx, shade) as i32;
44 pub const CTX_PRINT: i32 = offset_of!(NativeCtx, print) as i32;
45 pub const CTX_CALL_VM: i32 = offset_of!(NativeCtx, call_vm) as i32;
46 pub const CTX_PUSH_ROOTS: i32 = offset_of!(NativeCtx, push_roots) as i32;
47 pub const CTX_POP_ROOTS: i32 = offset_of!(NativeCtx, pop_roots) as i32;
48
49 pub const STATUS_OK: u32 = 0;
51 pub const STATUS_INDEX: u32 = 1;
53 pub const STATUS_DEPTH: u32 = 2;
55 pub const STATUS_HALT: u32 = 3;
57 pub const STATUS_VM: u32 = 4;
60 pub const STATUS_SHIFT: u32 = 5;
62 pub const STATUS_PANIC: u32 = 6;
64
65 #[must_use]
71 pub fn object_layout() -> [u8; 3] {
72 let mut probe: Vec<crate::data::Data> = Vec::with_capacity(7);
73 probe.push(crate::data::NULL);
74 probe.push(crate::data::NULL);
75 probe.push(crate::data::NULL);
76 let words: [usize; 3] = unsafe { core::mem::transmute_copy(&probe) };
77 let offset = |value: usize| {
78 words
79 .iter()
80 .position(|word| *word == value)
81 .map_or(u8::MAX, |i| (i * size_of::<usize>()) as u8)
82 };
83 [
84 size_of::<Vec<crate::data::Data>>() as u8,
85 offset(probe.as_ptr() as usize),
86 offset(probe.len()),
87 ]
88 }
89}
90
91#[repr(C)]
95pub struct NativeCtx {
96 pub regs: *mut Data,
98 pub objs: *const u8,
101 pub status: u32,
103 pub err_at: u32,
105 pub err_a: i64,
106 pub err_b: i64,
107 pub marking: u64,
110 pub ret: [u64; 2],
112 pub ceiling: i64,
116 pub segment_base: i64,
118 pub dylibs: *const *const u8,
120 pub machine: *mut core::ffi::c_void,
122 pub shade: extern "C" fn(*mut Self, u64, u64),
124 pub print: extern "C" fn(*mut Self, u64, u64),
126 pub call_vm: extern "C" fn(*mut Self, u64, u64, u64, i64, *const u64, u64) -> u32,
130 pub push_roots: extern "C" fn(*mut Self, *const u64, u64),
132 pub pop_roots: extern "C" fn(*mut Self, u64),
134}
135
136pub type Entry = unsafe extern "C" fn(*mut NativeCtx, i64) -> u32;
139
140pub struct NativeFunction {
142 pub entry: Entry,
143 pub loc: u32,
145 pub returns: bool,
146}
147
148pub struct NativeCode {
150 _memory: CodeMemory,
151 pub functions: Vec<NativeFunction>,
152 pub main: Option<usize>,
154 originals: Vec<(u32, Instr)>,
158 pub exit: u32,
160 pub frame_bytes: u32,
162 pub dylibs: Vec<*const u8>,
164}
165
166impl NativeCode {
167 #[must_use]
170 pub fn original(&self, at: usize, current: Instr) -> Instr {
171 if !matches!(current, Instr::CallNative(..) | Instr::CallNativeRec(..)) {
172 return current;
173 }
174 self.originals
175 .binary_search_by_key(&(at as u32), |(i, _)| *i)
176 .map_or(current, |k| self.originals[k].1)
177 }
178}
179
180const HOST: Option<(NativeArch, NativeOs)> = {
182 let arch = if cfg!(target_arch = "x86_64") {
183 Some(NativeArch::X86_64)
184 } else if cfg!(target_arch = "aarch64") {
185 Some(NativeArch::Aarch64)
186 } else {
187 None
188 };
189 let os = if cfg!(target_os = "linux") {
190 Some(NativeOs::Linux)
191 } else if cfg!(target_os = "macos") {
192 Some(NativeOs::MacOs)
193 } else if cfg!(target_os = "windows") {
194 Some(NativeOs::Windows)
195 } else {
196 None
197 };
198 match (arch, os) {
199 (Some(arch), Some(os)) => Some((arch, os)),
200 _ => None,
201 }
202};
203
204fn cpu_has(feature: &str) -> bool {
208 #[cfg(target_arch = "x86_64")]
209 {
210 match feature {
211 "sse3" => std::arch::is_x86_feature_detected!("sse3"),
212 "ssse3" => std::arch::is_x86_feature_detected!("ssse3"),
213 "sse4.1" => std::arch::is_x86_feature_detected!("sse4.1"),
214 "sse4.2" => std::arch::is_x86_feature_detected!("sse4.2"),
215 "popcnt" => std::arch::is_x86_feature_detected!("popcnt"),
216 "avx" => std::arch::is_x86_feature_detected!("avx"),
217 "avx2" => std::arch::is_x86_feature_detected!("avx2"),
218 "bmi1" => std::arch::is_x86_feature_detected!("bmi1"),
219 "bmi2" => std::arch::is_x86_feature_detected!("bmi2"),
220 "lzcnt" => std::arch::is_x86_feature_detected!("lzcnt"),
221 "fma" => std::arch::is_x86_feature_detected!("fma"),
222 _ => false,
223 }
224 }
225 #[cfg(all(target_arch = "aarch64", not(target_os = "windows")))]
226 {
227 match feature {
228 "lse" => std::arch::is_aarch64_feature_detected!("lse"),
229 "fp16" => std::arch::is_aarch64_feature_detected!("fp16"),
230 "paca" => std::arch::is_aarch64_feature_detected!("paca"),
231 _ => false,
232 }
233 }
234 #[cfg(not(any(
235 target_arch = "x86_64",
236 all(target_arch = "aarch64", not(target_os = "windows"))
237 )))]
238 {
239 let _ = feature;
240 false
241 }
242}
243
244fn fits(section: &NativeImage) -> bool {
248 HOST == Some((section.arch, section.os))
249 && section.abi == abi::VERSION
250 && section.object_layout == abi::object_layout()
251 && section.cpu_features.iter().all(|f| cpu_has(f))
252}
253
254fn consistent(section: &NativeImage, instructions: &[Instr]) -> bool {
259 let functions = §ion.functions;
260 instructions.len() <= usize::from(u16::MAX)
263 && !functions.is_empty()
264 && functions.len() <= usize::from(u16::MAX)
265 && section.frame_bytes > 0
266 && functions.iter().all(|f| {
267 (f.offset as usize) < section.code.len() && (f.loc as usize) < instructions.len()
268 })
269 && section
270 .main
271 .is_none_or(|main| (main as usize) < functions.len())
272 && section.sites.iter().all(|site| {
273 functions.get(site.function as usize).is_some_and(|f| {
274 matches!(
275 instructions.get(site.at as usize),
276 Some(Instr::CallFunc(loc, _) | Instr::CallFuncRecursive(loc, _))
277 if u32::from(*loc) == f.loc
278 )
279 })
280 })
281}
282
283pub fn load(
288 sections: &[NativeImage],
289 instructions: &mut Vec<Instr>,
290 dylibs: Vec<*const u8>,
291) -> Option<NativeCode> {
292 let section = sections.iter().find(|s| fits(s))?;
293 if !consistent(section, instructions) {
294 return None;
295 }
296 let memory = CodeMemory::new(§ion.code)?;
297
298 let functions = section
299 .functions
300 .iter()
301 .map(|f| NativeFunction {
302 entry: unsafe { std::mem::transmute::<*const u8, Entry>(memory.at(f.offset as usize)) },
305 loc: f.loc,
306 returns: f.returns,
307 })
308 .collect();
309
310 let mut originals = Vec::with_capacity(section.sites.len());
311 for site in §ion.sites {
312 let at = site.at as usize;
313 let before = instructions[at];
314 instructions[at] = match before {
315 Instr::CallFunc(_, ret) => Instr::CallNative(site.function as u16, ret),
316 Instr::CallFuncRecursive(_, ret) => Instr::CallNativeRec(site.function as u16, ret),
317 other => other,
318 };
319 originals.push((site.at, before));
320 }
321 originals.sort_unstable_by_key(|(at, _)| *at);
322 originals.dedup_by_key(|(at, _)| *at);
323
324 let exit = instructions.len() as u32;
325 instructions.push(Instr::NativeExit);
326
327 Some(NativeCode {
328 _memory: memory,
329 functions,
330 main: section.main.map(|m| m as usize),
331 originals,
332 exit,
333 frame_bytes: section.frame_bytes,
334 dylibs,
335 })
336}
337
338struct CodeMemory {
341 ptr: *mut u8,
342 len: usize,
343}
344
345impl CodeMemory {
346 fn at(&self, offset: usize) -> *const u8 {
348 unsafe { self.ptr.add(offset) }
350 }
351}
352
353#[cfg(unix)]
354mod os {
355 use core::ffi::c_int;
356 use core::ffi::c_void;
357
358 unsafe extern "C" {
359 fn mmap(
360 addr: *mut c_void,
361 len: usize,
362 prot: c_int,
363 flags: c_int,
364 fd: c_int,
365 off: i64,
366 ) -> *mut c_void;
367 fn mprotect(addr: *mut c_void, len: usize, prot: c_int) -> c_int;
368 fn munmap(addr: *mut c_void, len: usize) -> c_int;
369 }
370 #[cfg(target_os = "macos")]
371 unsafe extern "C" {
372 fn sys_icache_invalidate(start: *mut c_void, len: usize);
373 }
374
375 const PROT_READ: c_int = 1;
376 const PROT_WRITE: c_int = 2;
377 const PROT_EXEC: c_int = 4;
378 const MAP_PRIVATE: c_int = 2;
379 #[cfg(target_os = "macos")]
380 const MAP_ANONYMOUS: c_int = 0x1000;
381 #[cfg(not(target_os = "macos"))]
382 const MAP_ANONYMOUS: c_int = 0x20;
383
384 pub fn map_code(code: &[u8]) -> Option<*mut u8> {
387 let len = code.len();
388 unsafe {
389 let ptr = mmap(
390 core::ptr::null_mut(),
391 len,
392 PROT_READ | PROT_WRITE,
393 MAP_PRIVATE | MAP_ANONYMOUS,
394 -1,
395 0,
396 );
397 if ptr as isize == -1 || ptr.is_null() {
398 return None;
399 }
400 core::ptr::copy_nonoverlapping(code.as_ptr(), ptr.cast::<u8>(), len);
401 flush(ptr, len);
402 if mprotect(ptr, len, PROT_READ | PROT_EXEC) != 0 {
403 munmap(ptr, len);
404 return None;
405 }
406 Some(ptr.cast())
407 }
408 }
409
410 pub fn unmap(ptr: *mut u8, len: usize) {
411 unsafe {
412 munmap(ptr.cast(), len);
413 }
414 }
415
416 #[allow(unused_variables)]
419 unsafe fn flush(ptr: *mut c_void, len: usize) {
420 #[cfg(all(target_arch = "aarch64", target_os = "macos"))]
421 unsafe {
422 sys_icache_invalidate(ptr, len);
423 }
424 #[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
425 unsafe {
426 super::flush_aarch64(ptr.cast(), len);
427 }
428 }
429}
430
431#[cfg(windows)]
432mod os {
433 use core::ffi::c_void;
434
435 #[link(name = "kernel32")]
436 unsafe extern "system" {
437 fn VirtualAlloc(addr: *mut c_void, size: usize, kind: u32, protect: u32) -> *mut c_void;
438 fn VirtualProtect(addr: *mut c_void, size: usize, protect: u32, old: *mut u32) -> i32;
439 fn VirtualFree(addr: *mut c_void, size: usize, kind: u32) -> i32;
440 fn FlushInstructionCache(process: *mut c_void, addr: *const c_void, size: usize) -> i32;
441 fn GetCurrentProcess() -> *mut c_void;
442 }
443
444 const MEM_COMMIT: u32 = 0x1000;
445 const MEM_RESERVE: u32 = 0x2000;
446 const MEM_RELEASE: u32 = 0x8000;
447 const PAGE_READWRITE: u32 = 0x04;
448 const PAGE_EXECUTE_READ: u32 = 0x20;
449
450 pub fn map_code(code: &[u8]) -> Option<*mut u8> {
453 let len = code.len();
454 unsafe {
455 let ptr = VirtualAlloc(
456 core::ptr::null_mut(),
457 len,
458 MEM_COMMIT | MEM_RESERVE,
459 PAGE_READWRITE,
460 );
461 if ptr.is_null() {
462 return None;
463 }
464 core::ptr::copy_nonoverlapping(code.as_ptr(), ptr.cast::<u8>(), len);
465 let mut old = 0;
466 if VirtualProtect(ptr, len, PAGE_EXECUTE_READ, &raw mut old) == 0 {
467 VirtualFree(ptr, 0, MEM_RELEASE);
468 return None;
469 }
470 FlushInstructionCache(GetCurrentProcess(), ptr, len);
471 Some(ptr.cast())
472 }
473 }
474
475 pub fn unmap(ptr: *mut u8, _len: usize) {
476 unsafe {
477 VirtualFree(ptr.cast(), 0, MEM_RELEASE);
478 }
479 }
480}
481
482#[cfg(not(any(unix, windows)))]
483mod os {
484 pub fn map_code(_code: &[u8]) -> Option<*mut u8> {
486 None
487 }
488
489 pub fn unmap(_ptr: *mut u8, _len: usize) {}
490}
491
492#[cfg(all(target_arch = "aarch64", unix, not(target_os = "macos")))]
496unsafe fn flush_aarch64(ptr: *const u8, len: usize) {
497 let ctr: u64;
498 unsafe {
499 core::arch::asm!("mrs {}, ctr_el0", out(reg) ctr, options(nomem, nostack));
500 }
501 let dline = 4usize << ((ctr >> 16) & 0xf);
502 let iline = 4usize << (ctr & 0xf);
503 let start = ptr as usize;
504 let end = start + len;
505 let mut addr = start & !(dline - 1);
506 while addr < end {
507 unsafe {
508 core::arch::asm!("dc cvau, {}", in(reg) addr, options(nostack));
509 }
510 addr += dline;
511 }
512 unsafe {
513 core::arch::asm!("dsb ish", options(nostack));
514 }
515 let mut addr = start & !(iline - 1);
516 while addr < end {
517 unsafe {
518 core::arch::asm!("ic ivau, {}", in(reg) addr, options(nostack));
519 }
520 addr += iline;
521 }
522 unsafe {
523 core::arch::asm!("dsb ish", "isb", options(nostack));
524 }
525}
526
527impl CodeMemory {
528 fn new(code: &[u8]) -> Option<Self> {
529 if code.is_empty() {
530 return None;
531 }
532 let ptr = os::map_code(code)?;
533 Some(Self {
534 ptr,
535 len: code.len(),
536 })
537 }
538}
539
540impl Drop for CodeMemory {
541 fn drop(&mut self) {
542 os::unmap(self.ptr, self.len);
543 }
544}