1use crate::error::RasError;
4use lamina_platform::{TargetArchitecture, TargetOperatingSystem};
5
6#[cfg(windows)]
7mod windows_memory {
8 use std::ffi::c_void;
9
10 pub const MEM_COMMIT: u32 = 0x1000;
11 pub const MEM_RELEASE: u32 = 0x8000;
12 pub const MEM_RESERVE: u32 = 0x2000;
13 pub const PAGE_EXECUTE_READ: u32 = 0x20;
14 pub const PAGE_READWRITE: u32 = 0x04;
15
16 unsafe extern "system" {
17 pub fn VirtualAlloc(
18 address: *mut c_void,
19 size: usize,
20 allocation_type: u32,
21 protect: u32,
22 ) -> *mut c_void;
23 pub fn VirtualFree(address: *mut c_void, size: usize, free_type: u32) -> i32;
24 pub fn VirtualProtect(
25 address: *mut c_void,
26 size: usize,
27 new_protect: u32,
28 old_protect: *mut u32,
29 ) -> i32;
30
31 #[cfg(target_arch = "aarch64")]
32 pub fn FlushInstructionCache(
33 process: *mut c_void,
34 base_address: *const c_void,
35 size: usize,
36 ) -> i32;
37 #[cfg(target_arch = "aarch64")]
38 pub fn GetCurrentProcess() -> *mut c_void;
39 }
40}
41
42#[cfg(feature = "encoder")]
43use crate::assembler::RasAssembler;
44
45#[cfg(feature = "encoder")]
46use lamina_mir::Module as MirModule;
47
48pub struct ExecutableMemory {
50 ptr: *mut u8,
51 size: usize,
52}
53
54impl ExecutableMemory {
55 pub fn allocate_writable(size: usize) -> Result<Self, RasError> {
56 #[cfg(unix)]
57 {
58 use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE, mmap};
59 use std::ptr;
60
61 let aligned_size = (size + 4095) & !4095;
62
63 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
64 const MAP_JIT: libc::c_int = 0x0800;
65
66 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
67 let map_flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_JIT;
68
69 #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
70 let map_flags = MAP_ANONYMOUS | MAP_PRIVATE;
71
72 let ptr = unsafe {
73 mmap(
74 ptr::null_mut(),
75 aligned_size,
76 PROT_READ | PROT_WRITE,
77 map_flags,
78 -1,
79 0,
80 )
81 };
82
83 if ptr == libc::MAP_FAILED {
84 return Err(RasError::IoError(format!(
85 "mmap failed (size: {} bytes)",
86 size
87 )));
88 }
89
90 Ok(Self {
91 ptr: ptr as *mut u8,
92 size: aligned_size,
93 })
94 }
95
96 #[cfg(windows)]
97 {
98 use crate::runtime::windows_memory::{
99 MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE, VirtualAlloc,
100 };
101
102 let ptr = unsafe {
103 VirtualAlloc(
104 std::ptr::null_mut(),
105 size,
106 MEM_COMMIT | MEM_RESERVE,
107 PAGE_READWRITE,
108 )
109 };
110
111 if ptr.is_null() {
112 return Err(RasError::IoError("VirtualAlloc failed".to_string()));
113 }
114
115 Ok(Self {
116 ptr: ptr as *mut u8,
117 size,
118 })
119 }
120
121 #[cfg(not(any(unix, windows)))]
122 {
123 Err(RasError::UnsupportedTarget(
124 "Executable memory allocation not supported on this platform".to_string(),
125 ))
126 }
127 }
128
129 pub fn make_executable(&mut self) -> Result<(), RasError> {
130 #[cfg(unix)]
131 {
132 use libc::{PROT_EXEC, PROT_READ, mprotect};
133
134 let result = unsafe {
135 mprotect(
136 self.ptr as *mut libc::c_void,
137 self.size,
138 PROT_READ | PROT_EXEC,
139 )
140 };
141
142 if result != 0 {
143 return Err(RasError::IoError("mprotect failed".to_string()));
144 }
145
146 #[cfg(target_arch = "aarch64")]
147 {
148 unsafe {
149 std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
150 unsafe extern "C" {
151 fn __clear_cache(start: *const u8, end: *const u8);
152 }
153 __clear_cache(self.ptr, self.ptr.add(self.size));
154 std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);
155 }
156 }
157
158 Ok(())
159 }
160
161 #[cfg(windows)]
162 {
163 use crate::runtime::windows_memory::{PAGE_EXECUTE_READ, VirtualProtect};
164
165 let mut old_protect = 0;
166 let result = unsafe {
167 VirtualProtect(
168 self.ptr.cast(),
169 self.size,
170 PAGE_EXECUTE_READ,
171 &mut old_protect,
172 )
173 };
174
175 if result == 0 {
176 return Err(RasError::IoError("VirtualProtect failed".to_string()));
177 }
178
179 #[cfg(target_arch = "aarch64")]
182 unsafe {
183 use crate::runtime::windows_memory::{FlushInstructionCache, GetCurrentProcess};
184 FlushInstructionCache(
185 GetCurrentProcess(),
186 self.ptr.cast(),
187 self.size,
188 );
189 }
190
191 Ok(())
192 }
193
194 #[cfg(not(any(unix, windows)))]
195 {
196 Err(RasError::UnsupportedTarget(
197 "Making memory executable not supported".to_string(),
198 ))
199 }
200 }
201
202 pub fn allocate(size: usize) -> Result<Self, RasError> {
203 let mut mem = Self::allocate_writable(size)?;
204 mem.make_executable()?;
205 Ok(mem)
206 }
207
208 pub fn write_code(&mut self, code: &[u8]) -> Result<(), RasError> {
209 if code.len() > self.size {
210 return Err(RasError::IoError("Code too large".to_string()));
211 }
212
213 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
214 {
215 unsafe {
216 unsafe extern "C" {
217 fn pthread_jit_write_protect_np(value: libc::c_int);
218 }
219 pthread_jit_write_protect_np(0);
220 }
221 }
222
223 unsafe {
224 std::ptr::copy_nonoverlapping(code.as_ptr(), self.ptr, code.len());
225 }
226
227 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
228 {
229 unsafe {
230 unsafe extern "C" {
231 fn sys_icache_invalidate(start: *mut libc::c_void, size: libc::size_t);
232 }
233 sys_icache_invalidate(self.ptr as *mut libc::c_void, code.len());
234 }
235 }
236
237 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
238 {
239 unsafe {
240 unsafe extern "C" {
241 fn pthread_jit_write_protect_np(value: libc::c_int);
242 }
243 pthread_jit_write_protect_np(1);
244 }
245 }
246
247 Ok(())
248 }
249
250 pub fn code_start(&self) -> *const u8 {
251 self.ptr
252 }
253
254 pub unsafe fn entry_fn<F: Sized>(&self) -> F {
265 debug_assert_eq!(core::mem::size_of::<F>(), core::mem::size_of::<*mut u8>());
266 unsafe { core::mem::transmute_copy(&self.ptr) }
267 }
268}
269
270impl Drop for ExecutableMemory {
271 fn drop(&mut self) {
272 #[cfg(unix)]
273 {
274 use libc::munmap;
275 unsafe {
276 munmap(self.ptr as *mut libc::c_void, self.size);
277 }
278 }
279
280 #[cfg(windows)]
281 {
282 use crate::runtime::windows_memory::{MEM_RELEASE, VirtualFree};
283 unsafe {
284 VirtualFree(self.ptr.cast(), 0, MEM_RELEASE);
285 }
286 }
287 }
288}
289
290pub struct RasRuntime {
292 #[cfg(feature = "encoder")]
293 target_arch: TargetArchitecture,
294 #[cfg(feature = "encoder")]
295 target_os: TargetOperatingSystem,
296}
297
298impl RasRuntime {
299 pub fn new(
300 #[cfg(feature = "encoder")] target_arch: TargetArchitecture,
301 #[cfg(not(feature = "encoder"))] _target_arch: TargetArchitecture,
302 #[cfg(feature = "encoder")] target_os: TargetOperatingSystem,
303 #[cfg(not(feature = "encoder"))] _target_os: TargetOperatingSystem,
304 ) -> Self {
305 Self {
306 #[cfg(feature = "encoder")]
307 target_arch,
308 #[cfg(feature = "encoder")]
309 target_os,
310 }
311 }
312
313 #[cfg(feature = "encoder")]
314 pub fn compile_to_memory(&mut self, module: &MirModule) -> Result<ExecutableMemory, RasError> {
315 let mut assembler = RasAssembler::new(self.target_arch, self.target_os)?;
316 let (code, _) = assembler.compile_mir_to_binary_function(module, None)?;
317
318 let mut mem = ExecutableMemory::allocate_writable(code.len())?;
319 mem.write_code(&code)?;
320 mem.make_executable()?;
321
322 Ok(mem)
323 }
324
325 #[cfg(feature = "encoder")]
328 pub fn compile_function<T>(
329 &mut self,
330 module: &MirModule,
331 _function_name: &str,
332 ) -> Result<unsafe extern "C" fn() -> T, RasError> {
333 let mem = self.compile_to_memory(module)?;
334 let f: unsafe extern "C" fn() -> T = unsafe { core::mem::transmute(mem.ptr) };
335 std::mem::forget(mem);
336 Ok(f)
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use lamina_platform::{TargetArchitecture, TargetOperatingSystem};
344
345 #[test]
346 fn executable_memory_allocate_and_write() {
347 let mut mem = ExecutableMemory::allocate_writable(4096).expect("alloc failed");
350 let code = [0x90u8, 0x90, 0x90, 0xc3]; mem.write_code(&code).expect("write failed");
352 mem.make_executable().expect("make_executable failed");
354 assert!(!mem.code_start().is_null());
355 }
356
357 #[test]
358 fn executable_memory_too_large_returns_error() {
359 let mem = ExecutableMemory::allocate_writable(4096).expect("alloc failed");
362 let big = vec![0u8; 8192];
363 let result = {
364 let mut m = mem;
365 m.write_code(&big)
366 };
367 assert!(result.is_err(), "writing beyond capacity should fail");
368 }
369
370 #[test]
371 fn ras_runtime_new_does_not_panic() {
372 let _rt = RasRuntime::new(TargetArchitecture::X86_64, TargetOperatingSystem::Linux);
373 }
374}
375
376#[cfg(all(test, feature = "encoder"))]
379mod jit_host_exec_tests {
380 use std::str::FromStr;
381
382 use lamina_mir::block::Block;
383 use lamina_mir::function::{Function, Parameter, Signature};
384 use lamina_mir::instruction::{Immediate, Instruction, IntBinOp, IntCmpOp, Operand};
385 use lamina_mir::module::Module;
386 use lamina_mir::register::{Register, VirtualReg};
387 use lamina_mir::types::{MirType, ScalarType};
388 use lamina_platform::{
389 TargetArchitecture, TargetOperatingSystem, detect_host_architecture_only, detect_host_os,
390 };
391
392 use crate::assembler::core::RasAssembler;
393 use crate::error::RasError;
394 use crate::runtime::ExecutableMemory;
395
396 fn host_jit_pair() -> Option<(TargetArchitecture, TargetOperatingSystem)> {
397 let arch = TargetArchitecture::from_str(detect_host_architecture_only()).ok()?;
398 let os = TargetOperatingSystem::from_str(detect_host_os()).ok()?;
399 match arch {
400 TargetArchitecture::X86_64 | TargetArchitecture::Aarch64 => Some((arch, os)),
401 _ => None,
402 }
403 }
404
405 fn compile_to_executable(
406 arch: TargetArchitecture,
407 os: TargetOperatingSystem,
408 module: &lamina_mir::Module,
409 ) -> Result<ExecutableMemory, RasError> {
410 let mut asm = RasAssembler::new(arch, os)?;
411 let (code, _) = asm.compile_mir_to_binary_function(module, None)?;
412 let mut mem = ExecutableMemory::allocate_writable(code.len())?;
413 mem.write_code(&code)?;
414 mem.make_executable()?;
415 Ok(mem)
416 }
417
418 fn exec_host_i32(module: &Module) -> Option<i32> {
419 let (arch, os) = host_jit_pair()?;
420 let mem = compile_to_executable(arch, os, module).ok()?;
421 let f = unsafe { mem.entry_fn::<extern "C" fn() -> i32>() };
422 Some(f())
423 }
424
425 fn exec_host_i64(module: &Module) -> Option<i64> {
426 let (arch, os) = host_jit_pair()?;
427 let mem = compile_to_executable(arch, os, module).ok()?;
428 let f = unsafe { mem.entry_fn::<extern "C" fn() -> i64>() };
429 Some(f())
430 }
431
432 fn exec_host_i64_binary(module: &Module, a: i64, b: i64) -> Option<i64> {
433 let (arch, os) = host_jit_pair()?;
434 let mem = compile_to_executable(arch, os, module).ok()?;
435 let f = unsafe { mem.entry_fn::<extern "C" fn(i64, i64) -> i64>() };
436 Some(f(a, b))
437 }
438
439 fn ii32(v: i32) -> Operand {
440 Operand::Immediate(Immediate::I32(v))
441 }
442
443 fn ii64(v: i64) -> Operand {
444 Operand::Immediate(Immediate::I64(v))
445 }
446
447 fn module_scalar_binop(scalar: ScalarType, op: IntBinOp, lhs: Operand, rhs: Operand) -> Module {
448 let ty = MirType::Scalar(scalar);
449 let out = Register::Virtual(VirtualReg::gpr(0));
450 let sig = Signature::new("f").with_return(ty.clone());
451 let mut f = Function::new(sig);
452 let mut entry = Block::new("entry");
453 entry.push(Instruction::IntBinary {
454 op,
455 ty: ty.clone(),
456 dst: out.clone(),
457 lhs,
458 rhs,
459 });
460 entry.push(Instruction::Ret {
461 value: Some(Operand::Register(out.clone())),
462 });
463 f.add_block(entry);
464 let mut module = Module::new("host_scalar_binop");
465 module.add_function(f);
466 module
467 }
468
469 fn module_i64_binop_params(op: IntBinOp) -> Module {
470 let i64_ty = MirType::Scalar(ScalarType::I64);
471 let a = Register::Virtual(VirtualReg::gpr(0));
472 let b = Register::Virtual(VirtualReg::gpr(1));
473 let out = Register::Virtual(VirtualReg::gpr(2));
474 let sig = Signature::new("g")
475 .with_params(vec![
476 Parameter::new(a.clone(), i64_ty.clone()),
477 Parameter::new(b.clone(), i64_ty.clone()),
478 ])
479 .with_return(i64_ty.clone());
480 let mut f = Function::new(sig);
481 let mut entry = Block::new("entry");
482 entry.push(Instruction::IntBinary {
483 op,
484 ty: i64_ty.clone(),
485 dst: out.clone(),
486 lhs: Operand::Register(a.clone()),
487 rhs: Operand::Register(b.clone()),
488 });
489 entry.push(Instruction::Ret {
490 value: Some(Operand::Register(out.clone())),
491 });
492 f.add_block(entry);
493 let mut module = Module::new("host_i64_params");
494 module.add_function(f);
495 module
496 }
497
498 fn module_i32_cmp_eq(lhs: Operand, rhs: Operand) -> Module {
499 let i32_ty = MirType::Scalar(ScalarType::I32);
500 let out = Register::Virtual(VirtualReg::gpr(0));
501 let sig = Signature::new("cmp").with_return(i32_ty.clone());
502 let mut f = Function::new(sig);
503 let mut entry = Block::new("entry");
504 entry.push(Instruction::IntCmp {
505 op: IntCmpOp::Eq,
506 ty: i32_ty.clone(),
507 dst: out.clone(),
508 lhs,
509 rhs,
510 });
511 entry.push(Instruction::Ret {
512 value: Some(Operand::Register(out.clone())),
513 });
514 f.add_block(entry);
515 let mut module = Module::new("host_i32_cmp");
516 module.add_function(f);
517 module
518 }
519
520 #[test]
521 fn jit_host_exec_i64_add_immediates() {
522 let Some(got) = exec_host_i64(&module_scalar_binop(
523 ScalarType::I64,
524 IntBinOp::Add,
525 ii64(10),
526 ii64(32),
527 )) else {
528 return;
529 };
530 assert_eq!(got, 42);
531 }
532
533 #[test]
534 fn jit_host_exec_i64_sub_immediates() {
535 let Some(got) = exec_host_i64(&module_scalar_binop(
536 ScalarType::I64,
537 IntBinOp::Sub,
538 ii64(100),
539 ii64(33),
540 )) else {
541 return;
542 };
543 assert_eq!(got, 67);
544 }
545
546 #[test]
547 fn jit_host_exec_i64_mul_immediates() {
548 let Some(got) = exec_host_i64(&module_scalar_binop(
549 ScalarType::I64,
550 IntBinOp::Mul,
551 ii64(6),
552 ii64(7),
553 )) else {
554 return;
555 };
556 assert_eq!(got, 42);
557 }
558
559 #[test]
560 fn jit_host_exec_i64_add_two_arguments() {
561 let Some(got) = exec_host_i64_binary(&module_i64_binop_params(IntBinOp::Add), 7, 35) else {
562 return;
563 };
564 assert_eq!(got, 42);
565 }
566
567 #[test]
568 fn jit_host_exec_i64_mul_two_arguments() {
569 let Some(got) = exec_host_i64_binary(&module_i64_binop_params(IntBinOp::Mul), 6, 7) else {
570 return;
571 };
572 assert_eq!(got, 42);
573 }
574
575 #[test]
576 fn jit_host_exec_i32_sub_immediates() {
577 let Some(got) = exec_host_i32(&module_scalar_binop(
578 ScalarType::I32,
579 IntBinOp::Sub,
580 ii32(50),
581 ii32(8),
582 )) else {
583 return;
584 };
585 assert_eq!(got, 42);
586 }
587
588 #[test]
589 fn jit_host_exec_i32_mul_immediates() {
590 let Some(got) = exec_host_i32(&module_scalar_binop(
591 ScalarType::I32,
592 IntBinOp::Mul,
593 ii32(6),
594 ii32(7),
595 )) else {
596 return;
597 };
598 assert_eq!(got, 42);
599 }
600
601 #[test]
602 fn jit_host_exec_i32_and_or_xor_immediates() {
603 let Some(a) = exec_host_i32(&module_scalar_binop(
604 ScalarType::I32,
605 IntBinOp::And,
606 ii32(0x0F),
607 ii32(0x33),
608 )) else {
609 return;
610 };
611 assert_eq!(a, 3);
612 let Some(o) = exec_host_i32(&module_scalar_binop(
613 ScalarType::I32,
614 IntBinOp::Or,
615 ii32(8),
616 ii32(2),
617 )) else {
618 return;
619 };
620 assert_eq!(o, 10);
621 let Some(x) = exec_host_i32(&module_scalar_binop(
622 ScalarType::I32,
623 IntBinOp::Xor,
624 ii32(15),
625 ii32(5),
626 )) else {
627 return;
628 };
629 assert_eq!(x, 10);
630 }
631
632 #[test]
633 fn jit_host_exec_i32_udiv_immediates() {
634 let Some(got) = exec_host_i32(&module_scalar_binop(
635 ScalarType::I32,
636 IntBinOp::UDiv,
637 ii32(10),
638 ii32(3),
639 )) else {
640 return;
641 };
642 assert_eq!(got, 3);
643 }
644
645 #[test]
646 fn jit_host_exec_i32_sdiv_negative_dividend() {
647 let Some(got) = exec_host_i32(&module_scalar_binop(
648 ScalarType::I32,
649 IntBinOp::SDiv,
650 ii32(-7),
651 ii32(2),
652 )) else {
653 return;
654 };
655 assert_eq!(got, -3);
656 }
657
658 #[test]
659 fn jit_host_exec_i32_urem_srem_immediates() {
660 let Some(u) = exec_host_i32(&module_scalar_binop(
661 ScalarType::I32,
662 IntBinOp::URem,
663 ii32(10),
664 ii32(3),
665 )) else {
666 return;
667 };
668 assert_eq!(u, 1);
669 let Some(s) = exec_host_i32(&module_scalar_binop(
670 ScalarType::I32,
671 IntBinOp::SRem,
672 ii32(-7),
673 ii32(3),
674 )) else {
675 return;
676 };
677 assert_eq!(s, -1);
678 }
679
680 #[test]
681 fn jit_host_exec_i32_shl_immediates() {
682 let Some(got) = exec_host_i32(&module_scalar_binop(
683 ScalarType::I32,
684 IntBinOp::Shl,
685 ii32(3),
686 ii32(4),
687 )) else {
688 return;
689 };
690 assert_eq!(got, 48);
691 }
692
693 #[test]
694 fn jit_host_exec_i32_lshr_ashr_immediates() {
695 let Some(l) = exec_host_i32(&module_scalar_binop(
696 ScalarType::I32,
697 IntBinOp::LShr,
698 ii32(128),
699 ii32(3),
700 )) else {
701 return;
702 };
703 assert_eq!(l, 16);
704 let Some(a) = exec_host_i32(&module_scalar_binop(
705 ScalarType::I32,
706 IntBinOp::AShr,
707 ii32(-16),
708 ii32(2),
709 )) else {
710 return;
711 };
712 assert_eq!(a, -4);
713 }
714
715 #[test]
716 fn jit_host_exec_i32_intcmp_eq_immediates() {
717 let Some(eq) = exec_host_i32(&module_i32_cmp_eq(ii32(9), ii32(9))) else {
718 return;
719 };
720 assert_eq!(eq, 1);
721 let Some(ne) = exec_host_i32(&module_i32_cmp_eq(ii32(2), ii32(3))) else {
722 return;
723 };
724 assert_eq!(ne, 0);
725 }
726}