jit_assembler/common/mod.rs
1//! Common types and traits shared across all target architectures.
2
3use core::fmt;
4
5/// Register usage tracking functionality
6#[cfg(feature = "register-tracking")]
7pub mod register_usage;
8
9#[cfg(feature = "std")]
10use std::vec::Vec;
11#[cfg(not(feature = "std"))]
12use alloc::vec::Vec;
13
14/// A machine instruction that can be encoded to bytes
15pub trait Instruction: Copy + Clone + fmt::Debug + fmt::Display {
16 /// Get the instruction as a 32-bit or 64-bit value
17 fn value(&self) -> u64;
18
19 /// Get the instruction as bytes (little-endian)
20 fn bytes(&self) -> Vec<u8>;
21
22 /// Get the size of this instruction in bytes
23 fn size(&self) -> usize;
24}
25
26/// ABI classification for registers based on preservation requirements.
27///
28/// This simplified classification focuses on whether registers need to be
29/// preserved across function calls, which is the most critical information
30/// for JIT compilation and register allocation.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum AbiClass {
33 /// Caller-saved registers that don't need to be preserved across calls.
34 ///
35 /// These registers can be freely used by JIT-compiled functions without
36 /// saving/restoring their values. Examples: argument registers, temp registers.
37 CallerSaved,
38
39 /// Callee-saved registers that must be preserved across calls.
40 ///
41 /// If a JIT-compiled function uses these registers, it must save their
42 /// values on entry and restore them before returning. Examples: saved registers.
43 CalleeSaved,
44
45 /// Special-purpose registers with specific ABI requirements.
46 ///
47 /// These registers have specific roles (stack pointer, frame pointer, zero register, etc.)
48 /// and require careful handling. Generally should be avoided in JIT code
49 /// unless specifically needed for their intended purpose.
50 Special,
51}
52
53impl fmt::Display for AbiClass {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 AbiClass::CallerSaved => write!(f, "caller-saved"),
57 AbiClass::CalleeSaved => write!(f, "callee-saved"),
58 AbiClass::Special => write!(f, "special"),
59 }
60 }
61}
62
63/// A register identifier for a target architecture
64pub trait Register: Copy + Clone + fmt::Debug + core::hash::Hash + Eq {
65 /// Get the register number/identifier
66 fn id(&self) -> u32;
67
68 /// Get the ABI classification for this register.
69 ///
70 /// This method should return the appropriate `AbiClass` based on the
71 /// target architecture's calling convention.
72 ///
73 /// # Example
74 ///
75 /// ```rust,ignore
76 /// // For RISC-V
77 /// match self {
78 /// Register::T0 | Register::T1 => AbiClass::CallerSaved,
79 /// Register::S0 | Register::S1 => AbiClass::CalleeSaved,
80 /// Register::SP | Register::FP => AbiClass::Other,
81 /// // ...
82 /// }
83 /// ```
84 fn abi_class(&self) -> AbiClass;
85
86 /// Check if this register is caller-saved.
87 ///
88 /// Convenience method equivalent to `self.abi_class() == AbiClass::CallerSaved`.
89 fn is_caller_saved(&self) -> bool {
90 self.abi_class() == AbiClass::CallerSaved
91 }
92
93 /// Check if this register is callee-saved.
94 ///
95 /// Convenience method equivalent to `self.abi_class() == AbiClass::CalleeSaved`.
96 fn is_callee_saved(&self) -> bool {
97 self.abi_class() == AbiClass::CalleeSaved
98 }
99
100 /// Check if this register is special-purpose.
101 ///
102 /// Convenience method equivalent to `self.abi_class() == AbiClass::Special`.
103 fn is_special(&self) -> bool {
104 self.abi_class() == AbiClass::Special
105 }
106}
107
108/// An instruction builder for a specific architecture
109pub trait InstructionBuilder<I: Instruction> {
110 /// The register type used by this architecture
111 type Register: Register;
112
113 /// Create a new instruction builder
114 fn new() -> Self;
115
116 /// Get the generated instructions
117 fn instructions(&self) -> InstructionCollection<I>;
118
119 /// Add an instruction to the builder
120 fn push(&mut self, instr: I);
121
122 /// Clear all instructions
123 fn clear(&mut self);
124
125 /// Get register usage information (register-tracking feature only)
126 ///
127 /// This method returns information about which registers have been used
128 /// by the instructions in this builder, enabling register allocation
129 /// analysis and ABI compliance checking.
130 #[cfg(feature = "register-tracking")]
131 fn register_usage(&self) -> &crate::common::register_usage::RegisterUsageInfo<Self::Register>;
132
133 /// Get mutable register usage information (register-tracking feature only)
134 ///
135 /// This allows direct manipulation of the usage tracking, which can be
136 /// useful for advanced use cases or manual register tracking.
137 #[cfg(feature = "register-tracking")]
138 fn register_usage_mut(&mut self) -> &mut crate::common::register_usage::RegisterUsageInfo<Self::Register>;
139
140 /// Clear register usage information (register-tracking feature only)
141 ///
142 /// This resets the usage tracking to an empty state, which can be
143 /// useful when reusing a builder for multiple functions.
144 #[cfg(feature = "register-tracking")]
145 fn clear_register_usage(&mut self) {
146 self.register_usage_mut().clear();
147 }
148
149 /// Create a JIT-compiled function from the assembled instructions (std-only)
150 ///
151 /// This method converts the assembled instructions into executable machine code
152 /// that can be called directly as a function. The generic type parameter `F`
153 /// specifies the function signature.
154 ///
155 /// # ABI Compatibility
156 ///
157 /// While you specify a Rust function type like `fn() -> u64`, the actual JIT
158 /// code uses C ABI internally for stability across Rust versions. This conversion
159 /// is handled transparently by the `call()` methods.
160 ///
161 /// # Limitations
162 ///
163 /// Currently supports function signatures with up to 7 arguments. For functions
164 /// with more arguments or complex calling conventions, use manual function pointer
165 /// conversion.
166 ///
167 /// # Safety
168 ///
169 /// This function is unsafe because:
170 /// - It allocates executable memory
171 /// - It assumes the assembled code follows the correct C ABI
172 /// - The caller must ensure the function signature matches the actual code
173 /// - The assembled code must be valid for the target architecture
174 ///
175 #[cfg(feature = "std")]
176 unsafe fn function<F>(&self) -> Result<crate::common::jit::CallableJitFunction<F>, crate::common::jit::JitError>;
177
178 /// Create a raw JIT-compiled function for manual type conversion (std-only)
179 ///
180 /// This method is similar to `function()` but returns a type-erased function
181 /// that allows manual conversion to any function signature. Use this for
182 /// function signatures with more than 7 arguments or custom calling conventions.
183 ///
184 /// # Safety
185 ///
186 /// This function is unsafe because:
187 /// - It allocates executable memory
188 /// - The caller must manually ensure type safety when calling `as_fn()`
189 /// - The assembled code must be valid for the target architecture
190 ///
191 #[cfg(feature = "std")]
192 unsafe fn raw_function(&self) -> Result<crate::common::jit::RawCallableJitFunction, crate::common::jit::JitError>;
193}
194
195/// Convenience functions for instruction collections
196/// These functions work with any collection of instructions
197pub fn instructions_to_bytes<I: Instruction>(instructions: &[I]) -> Vec<u8> {
198 let mut result = Vec::new();
199 for instr in instructions {
200 result.extend_from_slice(&instr.bytes());
201 }
202 result
203}
204
205/// Get the total size in bytes of a collection of instructions
206pub fn instructions_total_size<I: Instruction>(instructions: &[I]) -> usize {
207 instructions.iter().map(|i| i.size()).sum()
208}
209
210/// A collection of instructions with convenient methods for byte manipulation
211#[derive(Debug, Clone, PartialEq, Eq)]
212pub struct InstructionCollection<I: Instruction> {
213 instructions: Vec<I>,
214}
215
216impl<I: Instruction> InstructionCollection<I> {
217 /// Create a new empty instruction collection
218 pub fn new() -> Self {
219 Self {
220 instructions: Vec::new(),
221 }
222 }
223
224 /// Create from a vector of instructions
225 pub fn from_vec(instructions: Vec<I>) -> Self {
226 Self { instructions }
227 }
228
229 /// Create from a slice of instructions
230 pub fn from_slice(instructions: &[I]) -> Self {
231 Self {
232 instructions: instructions.to_vec(),
233 }
234 }
235
236 /// Get the instructions as a slice
237 pub fn as_slice(&self) -> &[I] {
238 &self.instructions
239 }
240
241 /// Get the instructions as a mutable slice
242 pub fn as_mut_slice(&mut self) -> &mut [I] {
243 &mut self.instructions
244 }
245
246 /// Convert instructions to a single byte vector
247 pub fn to_bytes(&self) -> Vec<u8> {
248 instructions_to_bytes(&self.instructions)
249 }
250
251 /// Get the total size in bytes of all instructions
252 pub fn total_size(&self) -> usize {
253 instructions_total_size(&self.instructions)
254 }
255
256 /// Get the number of instructions
257 pub fn len(&self) -> usize {
258 self.instructions.len()
259 }
260
261 /// Check if the collection is empty
262 pub fn is_empty(&self) -> bool {
263 self.instructions.is_empty()
264 }
265
266 /// Add an instruction to the collection
267 pub fn push(&mut self, instruction: I) {
268 self.instructions.push(instruction);
269 }
270
271 /// Remove all instructions
272 pub fn clear(&mut self) {
273 self.instructions.clear();
274 }
275
276 /// Get an iterator over the instructions
277 pub fn iter(&self) -> core::slice::Iter<'_, I> {
278 self.instructions.iter()
279 }
280
281 /// Get a mutable iterator over the instructions
282 pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, I> {
283 self.instructions.iter_mut()
284 }
285
286 /// Convert to a Vec (consumes self)
287 pub fn into_vec(self) -> Vec<I> {
288 self.instructions
289 }
290
291 /// Create a Vec by cloning the instructions
292 pub fn to_vec(&self) -> Vec<I> {
293 self.instructions.clone()
294 }
295
296 /// Get a reference to the instruction at the given index
297 pub fn get(&self, index: usize) -> Option<&I> {
298 self.instructions.get(index)
299 }
300
301 /// Get a mutable reference to the instruction at the given index
302 pub fn get_mut(&mut self, index: usize) -> Option<&mut I> {
303 self.instructions.get_mut(index)
304 }
305}
306
307impl<I: Instruction> Default for InstructionCollection<I> {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313impl<I: Instruction> From<Vec<I>> for InstructionCollection<I> {
314 fn from(instructions: Vec<I>) -> Self {
315 Self::from_vec(instructions)
316 }
317}
318
319impl<I: Instruction> From<&[I]> for InstructionCollection<I> {
320 fn from(instructions: &[I]) -> Self {
321 Self::from_slice(instructions)
322 }
323}
324
325impl<I: Instruction> AsRef<[I]> for InstructionCollection<I> {
326 fn as_ref(&self) -> &[I] {
327 &self.instructions
328 }
329}
330
331impl<I: Instruction> AsMut<[I]> for InstructionCollection<I> {
332 fn as_mut(&mut self) -> &mut [I] {
333 &mut self.instructions
334 }
335}
336
337impl<I: Instruction> core::ops::Index<usize> for InstructionCollection<I> {
338 type Output = I;
339
340 fn index(&self, index: usize) -> &Self::Output {
341 &self.instructions[index]
342 }
343}
344
345impl<I: Instruction> core::ops::IndexMut<usize> for InstructionCollection<I> {
346 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
347 &mut self.instructions[index]
348 }
349}
350
351impl<I: Instruction> IntoIterator for InstructionCollection<I> {
352 type Item = I;
353 type IntoIter = <Vec<I> as IntoIterator>::IntoIter;
354
355 fn into_iter(self) -> Self::IntoIter {
356 self.instructions.into_iter()
357 }
358}
359
360impl<'a, I: Instruction> IntoIterator for &'a InstructionCollection<I> {
361 type Item = &'a I;
362 type IntoIter = core::slice::Iter<'a, I>;
363
364 fn into_iter(self) -> Self::IntoIter {
365 self.instructions.iter()
366 }
367}
368
369impl<'a, I: Instruction> IntoIterator for &'a mut InstructionCollection<I> {
370 type Item = &'a mut I;
371 type IntoIter = core::slice::IterMut<'a, I>;
372
373 fn into_iter(self) -> Self::IntoIter {
374 self.instructions.iter_mut()
375 }
376}
377
378impl<I: Instruction> core::ops::Deref for InstructionCollection<I> {
379 type Target = [I];
380
381 fn deref(&self) -> &Self::Target {
382 &self.instructions
383 }
384}
385
386impl<I: Instruction> core::ops::DerefMut for InstructionCollection<I> {
387 fn deref_mut(&mut self) -> &mut Self::Target {
388 &mut self.instructions
389 }
390}
391
392/// Trait extension for instruction collections
393/// This allows you to call `.to_bytes()` and `.total_size()` directly on slices and vectors
394pub trait InstructionCollectionExt<I: Instruction> {
395 /// Convert instructions to a single byte vector
396 fn to_bytes(&self) -> Vec<u8>;
397
398 /// Get the total size in bytes of all instructions
399 fn total_size(&self) -> usize;
400}
401
402impl<I: Instruction> InstructionCollectionExt<I> for [I] {
403 fn to_bytes(&self) -> Vec<u8> {
404 instructions_to_bytes(self)
405 }
406
407 fn total_size(&self) -> usize {
408 instructions_total_size(self)
409 }
410}
411
412impl<I: Instruction> InstructionCollectionExt<I> for Vec<I> {
413 fn to_bytes(&self) -> Vec<u8> {
414 instructions_to_bytes(self)
415 }
416
417 fn total_size(&self) -> usize {
418 instructions_total_size(self)
419 }
420}
421
422/// Architecture-specific encoding functions
423pub trait ArchitectureEncoder<I: Instruction> {
424 /// Encode an instruction with specific opcode and operands
425 fn encode(&self, opcode: u32, operands: &[u32]) -> I;
426}
427
428/// Common result type for instruction building
429pub type BuildResult<I> = Result<I, BuildError>;
430
431/// Errors that can occur during instruction building
432#[derive(Debug, Clone, Copy, PartialEq, Eq)]
433pub enum BuildError {
434 /// Invalid register identifier
435 InvalidRegister(u32),
436 /// Invalid immediate value
437 InvalidImmediate(i64),
438 /// Unsupported instruction
439 UnsupportedInstruction,
440 /// Invalid operand combination
441 InvalidOperands,
442}
443
444impl fmt::Display for BuildError {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 match self {
447 BuildError::InvalidRegister(id) => write!(f, "Invalid register ID: {}", id),
448 BuildError::InvalidImmediate(val) => write!(f, "Invalid immediate value: {}", val),
449 BuildError::UnsupportedInstruction => write!(f, "Unsupported instruction"),
450 BuildError::InvalidOperands => write!(f, "Invalid operand combination"),
451 }
452 }
453}
454
455#[cfg(feature = "std")]
456impl std::error::Error for BuildError {}
457
458/// JIT execution functionality (std-only)
459#[cfg(feature = "std")]
460pub mod jit {
461 use std::marker::PhantomData;
462 use jit_allocator2::JitAllocator;
463
464 /// A raw JIT-compiled function for manual type conversion
465 ///
466 /// This is a type-erased version of `CallableJitFunction` that allows
467 /// manual function pointer conversion for cases not covered by the
468 /// predefined `call()` methods.
469 ///
470 /// # Usage
471 ///
472 /// ```rust,no_run
473 /// # use jit_assembler::common::jit::*;
474 /// # let code = &[0u8; 4];
475 /// let raw_func = RawCallableJitFunction::new(code)?;
476 ///
477 /// // Manual conversion to any function type
478 /// let func: extern "C" fn(u64, u64, u64, u64, u64, u64, u64, u64) -> u64 =
479 /// unsafe { raw_func.as_fn() };
480 /// let result = func(1, 2, 3, 4, 5, 6, 7, 8);
481 /// # Ok::<(), JitError>(())
482 /// ```
483 pub struct RawCallableJitFunction {
484 _allocator: Box<JitAllocator>,
485 exec_ptr: *const u8,
486 }
487
488 impl RawCallableJitFunction {
489 /// Create a new raw callable JIT function from instruction bytes
490 pub fn new(code: &[u8]) -> Result<Self, JitError> {
491 let mut allocator = JitAllocator::new(Default::default());
492 let (exec_ptr, mut_ptr) = allocator.alloc(code.len()).map_err(JitError::AllocationFailed)?;
493
494 unsafe {
495 std::ptr::copy_nonoverlapping(code.as_ptr(), mut_ptr, code.len());
496 }
497
498 Ok(RawCallableJitFunction {
499 _allocator: allocator,
500 exec_ptr,
501 })
502 }
503
504 /// Convert to a function pointer of the specified type
505 ///
506 /// # Safety
507 ///
508 /// The caller must ensure that:
509 /// - The function signature `F` matches the actual assembled code
510 /// - The assembled code follows the expected calling convention (usually C ABI)
511 /// - The function pointer is called with valid arguments
512 ///
513 /// # Example
514 ///
515 /// ```rust,no_run
516 /// # use jit_assembler::common::jit::*;
517 /// # let code = &[0u8; 4];
518 /// let raw_func = RawCallableJitFunction::new(code)?;
519 ///
520 /// // For complex signatures not covered by CallableJitFunction
521 /// let func: extern "C" fn(u64, u64, u64, u64, u64, u64, u64, u64) -> u64 =
522 /// unsafe { raw_func.as_fn() };
523 /// let result = func(1, 2, 3, 4, 5, 6, 7, 8);
524 /// # Ok::<(), JitError>(())
525 /// ```
526 pub unsafe fn as_fn<F>(&self) -> F {
527 std::mem::transmute_copy(&self.exec_ptr)
528 }
529 }
530
531 /// A JIT-compiled function that can be called directly
532 ///
533 /// This structure wraps executable machine code and provides type-safe
534 /// calling methods. While the type parameter `F` represents a Rust function
535 /// signature, the actual execution uses C ABI for stability.
536 ///
537 /// # Supported Signatures
538 ///
539 /// Currently supports function signatures with 0-7 arguments:
540 /// - `fn() -> R`
541 /// - `fn(A1) -> R`
542 /// - `fn(A1, A2) -> R`
543 /// - ... up to `fn(A1, A2, A3, A4, A5, A6, A7) -> R`
544 ///
545 /// For unsupported signatures, use `RawCallableJitFunction` instead.
546 pub struct CallableJitFunction<F> {
547 _allocator: Box<JitAllocator>,
548 exec_ptr: *const u8,
549 _phantom: PhantomData<F>,
550 }
551
552 impl<F> CallableJitFunction<F> {
553 /// Create a new callable JIT function from instruction bytes
554 pub fn new(code: &[u8]) -> Result<Self, JitError> {
555 let mut allocator = JitAllocator::new(Default::default());
556 let (exec_ptr, mut_ptr) = allocator.alloc(code.len()).map_err(JitError::AllocationFailed)?;
557
558 unsafe {
559 std::ptr::copy_nonoverlapping(code.as_ptr(), mut_ptr, code.len());
560 }
561
562 Ok(CallableJitFunction {
563 _allocator: allocator,
564 exec_ptr,
565 _phantom: PhantomData,
566 })
567 }
568 }
569
570 /// Direct call methods based on function signature
571 ///
572 /// These methods provide type-safe calling with automatic ABI conversion from
573 /// Rust function types to C ABI. Currently supports 0-7 arguments.
574 ///
575 /// The conversion from `fn(...)` to `extern "C" fn(...)` is handled internally
576 /// for ABI stability across Rust versions.
577
578 impl<R> CallableJitFunction<fn() -> R> {
579 /// Call with no arguments - natural syntax: func.call()
580 pub fn call(&self) -> R {
581 let func: extern "C" fn() -> R = unsafe { std::mem::transmute_copy(&self.exec_ptr) };
582 func()
583 }
584 }
585
586 impl<A1, R> CallableJitFunction<fn(A1) -> R> {
587 /// Call with one argument - natural syntax: func.call(arg)
588 pub fn call(&self, arg1: A1) -> R {
589 let func: extern "C" fn(A1) -> R = unsafe { std::mem::transmute_copy(&self.exec_ptr) };
590 func(arg1)
591 }
592 }
593
594 impl<A1, A2, R> CallableJitFunction<fn(A1, A2) -> R> {
595 /// Call with two arguments - natural syntax: func.call(arg1, arg2)
596 pub fn call(&self, arg1: A1, arg2: A2) -> R {
597 let func: extern "C" fn(A1, A2) -> R = unsafe { std::mem::transmute_copy(&self.exec_ptr) };
598 func(arg1, arg2)
599 }
600 }
601
602 impl<A1, A2, A3, R> CallableJitFunction<fn(A1, A2, A3) -> R> {
603 /// Call with three arguments - natural syntax: func.call(arg1, arg2, arg3)
604 pub fn call(&self, arg1: A1, arg2: A2, arg3: A3) -> R {
605 let func: extern "C" fn(A1, A2, A3) -> R = unsafe { std::mem::transmute_copy(&self.exec_ptr) };
606 func(arg1, arg2, arg3)
607 }
608 }
609
610 impl<A1, A2, A3, A4, R> CallableJitFunction<fn(A1, A2, A3, A4) -> R> {
611 /// Call with four arguments - natural syntax: func.call(arg1, arg2, arg3, arg4)
612 pub fn call(&self, arg1: A1, arg2: A2, arg3: A3, arg4: A4) -> R {
613 let func: extern "C" fn(A1, A2, A3, A4) -> R = unsafe { std::mem::transmute_copy(&self.exec_ptr) };
614 func(arg1, arg2, arg3, arg4)
615 }
616 }
617
618 impl<A1, A2, A3, A4, A5, R> CallableJitFunction<fn(A1, A2, A3, A4, A5) -> R> {
619 /// Call with five arguments
620 pub fn call(&self, arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) -> R {
621 let func: extern "C" fn(A1, A2, A3, A4, A5) -> R = unsafe { std::mem::transmute_copy(&self.exec_ptr) };
622 func(arg1, arg2, arg3, arg4, arg5)
623 }
624 }
625
626 impl<A1, A2, A3, A4, A5, A6, R> CallableJitFunction<fn(A1, A2, A3, A4, A5, A6) -> R> {
627 /// Call with six arguments
628 pub fn call(&self, arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, arg6: A6) -> R {
629 let func: extern "C" fn(A1, A2, A3, A4, A5, A6) -> R = unsafe { std::mem::transmute_copy(&self.exec_ptr) };
630 func(arg1, arg2, arg3, arg4, arg5, arg6)
631 }
632 }
633
634 impl<A1, A2, A3, A4, A5, A6, A7, R> CallableJitFunction<fn(A1, A2, A3, A4, A5, A6, A7) -> R> {
635 /// Call with seven arguments
636 pub fn call(&self, arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, arg6: A6, arg7: A7) -> R {
637 let func: extern "C" fn(A1, A2, A3, A4, A5, A6, A7) -> R = unsafe { std::mem::transmute_copy(&self.exec_ptr) };
638 func(arg1, arg2, arg3, arg4, arg5, arg6, arg7)
639 }
640 }
641
642 // Note: For void return functions, we don't generate them here
643 // as they would need special handling with unit type ()
644
645 /// Errors that can occur during JIT execution
646 #[derive(Debug)]
647 pub enum JitError {
648 AllocationFailed(jit_allocator2::Error),
649 }
650
651 impl std::fmt::Display for JitError {
652 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
653 match self {
654 JitError::AllocationFailed(e) => write!(f, "Failed to allocate JIT memory: {:?}", e),
655 }
656 }
657 }
658
659 impl std::error::Error for JitError {}
660}