summaryrefslogtreecommitdiff
path: root/yjit/src/utils.rs
diff options
context:
space:
mode:
Diffstat (limited to 'yjit/src/utils.rs')
-rw-r--r--yjit/src/utils.rs205
1 files changed, 205 insertions, 0 deletions
diff --git a/yjit/src/utils.rs b/yjit/src/utils.rs
new file mode 100644
index 0000000000..227e3e5f32
--- /dev/null
+++ b/yjit/src/utils.rs
@@ -0,0 +1,205 @@
+use crate::asm::x86_64::*;
+use crate::asm::*;
+use crate::cruby::*;
+use std::slice;
+
+/// Trait for casting to [usize] that allows you to say `.as_usize()`.
+/// Implementation conditional on the the cast preserving the numeric value on
+/// all inputs and being inexpensive.
+///
+/// [usize] is only guaranteed to be more than 16-bit wide, so we can't use
+/// `.into()` to cast an `u32` or an `u64` to a `usize` even though in all
+/// the platforms YJIT supports these two casts are pretty much no-ops.
+/// We could say `as usize` or `.try_convert().unwrap()` everywhere
+/// for those casts but they both have undesirable consequences if and when
+/// we decide to support 32-bit platforms. Unfortunately we can't implement
+/// [::core::convert::From] for [usize] since both the trait and the type are
+/// external. Naming the method `into()` also runs into naming conflicts.
+pub(crate) trait IntoUsize {
+ /// Convert to usize. Implementation conditional on width of [usize].
+ fn as_usize(self) -> usize;
+}
+
+#[cfg(target_pointer_width = "64")]
+impl IntoUsize for u64 {
+ fn as_usize(self) -> usize {
+ self as usize
+ }
+}
+
+#[cfg(target_pointer_width = "64")]
+impl IntoUsize for u32 {
+ fn as_usize(self) -> usize {
+ self as usize
+ }
+}
+
+impl IntoUsize for u16 {
+ /// Alias for `.into()`. For convenience so you could use the trait for
+ /// all unsgined types.
+ fn as_usize(self) -> usize {
+ self.into()
+ }
+}
+
+impl IntoUsize for u8 {
+ /// Alias for `.into()`. For convenience so you could use the trait for
+ /// all unsgined types.
+ fn as_usize(self) -> usize {
+ self.into()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn min_max_preserved_after_cast_to_usize() {
+ use crate::utils::IntoUsize;
+
+ let min: usize = u64::MIN.as_usize();
+ assert_eq!(min, u64::MIN.try_into().unwrap());
+ let max: usize = u64::MAX.as_usize();
+ assert_eq!(max, u64::MAX.try_into().unwrap());
+
+ let min: usize = u32::MIN.as_usize();
+ assert_eq!(min, u32::MIN.try_into().unwrap());
+ let max: usize = u32::MAX.as_usize();
+ assert_eq!(max, u32::MAX.try_into().unwrap());
+ }
+}
+
+// TODO: we may want to move this function into yjit.c, maybe add a convenient Rust-side wrapper
+/*
+// For debugging. Print the bytecode for an iseq.
+RBIMPL_ATTR_MAYBE_UNUSED()
+static void
+yjit_print_iseq(const rb_iseq_t *iseq)
+{
+ char *ptr;
+ long len;
+ VALUE disassembly = rb_iseq_disasm(iseq);
+ RSTRING_GETMEM(disassembly, ptr, len);
+ fprintf(stderr, "%.*s\n", (int)len, ptr);
+}
+*/
+
+// Save caller-save registers on the stack before a C call
+fn push_regs(cb: &mut CodeBlock) {
+ push(cb, RAX);
+ push(cb, RCX);
+ push(cb, RDX);
+ push(cb, RSI);
+ push(cb, RDI);
+ push(cb, R8);
+ push(cb, R9);
+ push(cb, R10);
+ push(cb, R11);
+ pushfq(cb);
+}
+
+// Restore caller-save registers from the after a C call
+fn pop_regs(cb: &mut CodeBlock) {
+ popfq(cb);
+ pop(cb, R11);
+ pop(cb, R10);
+ pop(cb, R9);
+ pop(cb, R8);
+ pop(cb, RDI);
+ pop(cb, RSI);
+ pop(cb, RDX);
+ pop(cb, RCX);
+ pop(cb, RAX);
+}
+
+pub fn print_int(cb: &mut CodeBlock, opnd: X86Opnd) {
+ extern "sysv64" fn print_int_fn(val: i64) {
+ println!("{}", val);
+ }
+
+ push_regs(cb);
+
+ match opnd {
+ X86Opnd::Mem(_) | X86Opnd::Reg(_) => {
+ // Sign-extend the value if necessary
+ if opnd.num_bits() < 64 {
+ movsx(cb, C_ARG_REGS[0], opnd);
+ } else {
+ mov(cb, C_ARG_REGS[0], opnd);
+ }
+ }
+ X86Opnd::Imm(_) | X86Opnd::UImm(_) => {
+ mov(cb, C_ARG_REGS[0], opnd);
+ }
+ _ => unreachable!(),
+ }
+
+ mov(cb, RAX, const_ptr_opnd(print_int_fn as *const u8));
+ call(cb, RAX);
+ pop_regs(cb);
+}
+
+/// Generate code to print a pointer
+pub fn print_ptr(cb: &mut CodeBlock, opnd: X86Opnd) {
+ extern "sysv64" fn print_ptr_fn(ptr: *const u8) {
+ println!("{:p}", ptr);
+ }
+
+ assert!(opnd.num_bits() == 64);
+
+ push_regs(cb);
+ mov(cb, C_ARG_REGS[0], opnd);
+ mov(cb, RAX, const_ptr_opnd(print_ptr_fn as *const u8));
+ call(cb, RAX);
+ pop_regs(cb);
+}
+
+/// Generate code to print a value
+pub fn print_value(cb: &mut CodeBlock, opnd: X86Opnd) {
+ extern "sysv64" fn print_value_fn(val: VALUE) {
+ unsafe { rb_obj_info_dump(val) }
+ }
+
+ assert!(opnd.num_bits() == 64);
+
+ push_regs(cb);
+
+ mov(cb, RDI, opnd);
+ mov(cb, RAX, const_ptr_opnd(print_value_fn as *const u8));
+ call(cb, RAX);
+
+ pop_regs(cb);
+}
+
+// Generate code to print constant string to stdout
+pub fn print_str(cb: &mut CodeBlock, str: &str) {
+ extern "sysv64" fn print_str_cfun(ptr: *const u8, num_bytes: usize) {
+ unsafe {
+ let slice = slice::from_raw_parts(ptr, num_bytes);
+ let str = std::str::from_utf8(slice).unwrap();
+ println!("{}", str);
+ }
+ }
+
+ let bytes = str.as_ptr();
+ let num_bytes = str.len();
+
+ push_regs(cb);
+
+ // Load the string address and jump over the string data
+ lea(cb, C_ARG_REGS[0], mem_opnd(8, RIP, 5));
+ jmp32(cb, num_bytes as i32);
+
+ // Write the string chars and a null terminator
+ for i in 0..num_bytes {
+ cb.write_byte(unsafe { *bytes.add(i) });
+ }
+
+ // Pass the string length as an argument
+ mov(cb, C_ARG_REGS[1], uimm_opnd(num_bytes as u64));
+
+ // Call the print function
+ mov(cb, RAX, const_ptr_opnd(print_str_cfun as *const u8));
+ call(cb, RAX);
+
+ pop_regs(cb);
+}