diff options
Diffstat (limited to 'yjit/src/asm/arm64/mod.rs')
| -rw-r--r-- | yjit/src/asm/arm64/mod.rs | 198 |
1 files changed, 188 insertions, 10 deletions
diff --git a/yjit/src/asm/arm64/mod.rs b/yjit/src/asm/arm64/mod.rs index 24f349d589..ced8b262c5 100644 --- a/yjit/src/asm/arm64/mod.rs +++ b/yjit/src/asm/arm64/mod.rs @@ -6,12 +6,15 @@ mod arg; mod inst; mod opnd; -use arg::*; use inst::*; -use opnd::*; + +// We're going to make these public to make using these things easier in the +// backend (so they don't have to have knowledge about the submodule). +pub use arg::*; +pub use opnd::*; /// Checks that a signed value fits within the specified number of bits. -const fn imm_fits_bits(imm: i64, num_bits: u8) -> bool { +pub const fn imm_fits_bits(imm: i64, num_bits: u8) -> bool { let minimum = if num_bits == 64 { i64::MIN } else { -2_i64.pow((num_bits as u32) - 1) }; let maximum = if num_bits == 64 { i64::MAX } else { 2_i64.pow((num_bits as u32) - 1) - 1 }; @@ -19,7 +22,7 @@ const fn imm_fits_bits(imm: i64, num_bits: u8) -> bool { } /// Checks that an unsigned value fits within the specified number of bits. -const fn uimm_fits_bits(uimm: u64, num_bits: u8) -> bool { +pub const fn uimm_fits_bits(uimm: u64, num_bits: u8) -> bool { let maximum = if num_bits == 64 { u64::MAX } else { 2_u64.pow(num_bits as u32) - 1 }; uimm <= maximum @@ -115,12 +118,39 @@ pub fn ands(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { cb.write_bytes(&bytes); } +/// Whether or not the offset between two instructions fits into the branch with +/// or without link instruction. If it doesn't, then we have to load the value +/// into a register first. +pub const fn b_offset_fits_bits(offset: i64) -> bool { + imm_fits_bits(offset, 26) +} + +/// B - branch without link (offset is number of instructions to jump) +pub fn b(cb: &mut CodeBlock, imm26: A64Opnd) { + let bytes: [u8; 4] = match imm26 { + A64Opnd::Imm(imm26) => { + assert!(b_offset_fits_bits(imm26), "The immediate operand must be 26 bits or less."); + + Call::b(imm26 as i32).into() + }, + _ => panic!("Invalid operand combination to b instruction.") + }; + + cb.write_bytes(&bytes); +} + +/// Whether or not the offset between two instructions fits into the b.cond +/// instruction. If it doesn't, then we have to load the value into a register +/// first, then use the b.cond instruction to skip past a direct jump. +pub const fn bcond_offset_fits_bits(offset: i64) -> bool { + imm_fits_bits(offset, 21) && (offset & 0b11 == 0) +} + /// B.cond - branch to target if condition is true pub fn bcond(cb: &mut CodeBlock, cond: Condition, byte_offset: A64Opnd) { let bytes: [u8; 4] = match byte_offset { A64Opnd::Imm(imm) => { - assert!(imm_fits_bits(imm, 21), "The immediate operand must be 21 bits or less."); - assert!(imm & 0b11 == 0, "The immediate operand must be aligned to a 2-bit boundary."); + assert!(bcond_offset_fits_bits(imm), "The immediate operand must be 21 bits or less and be aligned to a 2-bit boundary."); BranchCond::bcond(cond, imm as i32).into() }, @@ -134,7 +164,7 @@ pub fn bcond(cb: &mut CodeBlock, cond: Condition, byte_offset: A64Opnd) { pub fn bl(cb: &mut CodeBlock, imm26: A64Opnd) { let bytes: [u8; 4] = match imm26 { A64Opnd::Imm(imm26) => { - assert!(imm_fits_bits(imm26, 26), "The immediate operand must be 26 bits or less."); + assert!(b_offset_fits_bits(imm26), "The immediate operand must be 26 bits or less."); Call::bl(imm26 as i32).into() }, @@ -154,6 +184,20 @@ pub fn br(cb: &mut CodeBlock, rn: A64Opnd) { cb.write_bytes(&bytes); } +/// BRK - create a breakpoint +pub fn brk(cb: &mut CodeBlock, imm16: A64Opnd) { + let bytes: [u8; 4] = match imm16 { + A64Opnd::None => Breakpoint::brk(0).into(), + A64Opnd::UImm(imm16) => { + assert!(uimm_fits_bits(imm16, 16), "The immediate operand must be 16 bits or less."); + Breakpoint::brk(imm16 as u16).into() + }, + _ => panic!("Invalid operand combination to brk instruction.") + }; + + cb.write_bytes(&bytes); +} + /// CMP - compare rn and rm, update flags pub fn cmp(cb: &mut CodeBlock, rn: A64Opnd, rm: A64Opnd) { let bytes: [u8; 4] = match (rn, rm) { @@ -196,6 +240,11 @@ pub fn ldaddal(cb: &mut CodeBlock, rs: A64Opnd, rt: A64Opnd, rn: A64Opnd) { /// LDUR - load a memory address into a register pub fn ldur(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { let bytes: [u8; 4] = match (rt, rn) { + (A64Opnd::Reg(rt), A64Opnd::Reg(rn)) => { + assert!(rt.num_bits == rn.num_bits, "All operands must be of the same size."); + + Load::ldur(rt.reg_no, rn.reg_no, 0, rt.num_bits).into() + }, (A64Opnd::Reg(rt), A64Opnd::Mem(rn)) => { assert!(rt.num_bits == rn.num_bits, "Expected registers to be the same size"); assert!(imm_fits_bits(rn.disp.into(), 9), "Expected displacement to be 9 bits or less"); @@ -238,6 +287,23 @@ pub fn lsr(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, shift: A64Opnd) { cb.write_bytes(&bytes); } +/// MOV - move a value in a register to another register +pub fn mov(cb: &mut CodeBlock, rd: A64Opnd, rm: A64Opnd) { + let bytes: [u8; 4] = match (rd, rm) { + (A64Opnd::Reg(rd), A64Opnd::Reg(rm)) => { + assert!(rd.num_bits == rm.num_bits, "Expected registers to be the same size"); + + LogicalReg::mov(rd.reg_no, rm.reg_no, rd.num_bits).into() + }, + (A64Opnd::Reg(rd), A64Opnd::UImm(imm)) => { + LogicalImm::mov(rd.reg_no, imm.try_into().unwrap(), rd.num_bits).into() + }, + _ => panic!("Invalid operand combination to mov instruction") + }; + + cb.write_bytes(&bytes); +} + /// MOVK - move a 16 bit immediate into a register, keep the other bits in place pub fn movk(cb: &mut CodeBlock, rd: A64Opnd, imm16: A64Opnd, shift: u8) { let bytes: [u8; 4] = match (rd, imm16) { @@ -266,6 +332,63 @@ pub fn movz(cb: &mut CodeBlock, rd: A64Opnd, imm16: A64Opnd, shift: u8) { cb.write_bytes(&bytes); } +/// MVN - move a value in a register to another register, negating it +pub fn mvn(cb: &mut CodeBlock, rd: A64Opnd, rm: A64Opnd) { + let bytes: [u8; 4] = match (rd, rm) { + (A64Opnd::Reg(rd), A64Opnd::Reg(rm)) => { + assert!(rd.num_bits == rm.num_bits, "Expected registers to be the same size"); + + LogicalReg::mvn(rd.reg_no, rm.reg_no, rd.num_bits).into() + }, + _ => panic!("Invalid operand combination to mvn instruction") + }; + + cb.write_bytes(&bytes); +} + +/// NOP - no-operation, used for alignment purposes +pub fn nop(cb: &mut CodeBlock) { + let bytes: [u8; 4] = Nop::nop().into(); + + cb.write_bytes(&bytes); +} + +/// ORN - perform a bitwise OR of rn and NOT rm, put the result in rd, don't update flags +pub fn orn(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { + let bytes: [u8; 4] = match (rd, rn, rm) { + (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::Reg(rm)) => { + assert!(rd.num_bits == rn.num_bits && rn.num_bits == rm.num_bits, "Expected registers to be the same size"); + + LogicalReg::orn(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() + }, + _ => panic!("Invalid operand combination to orn instruction.") + }; + + cb.write_bytes(&bytes); +} + +/// ORR - perform a bitwise OR of rn and rm, put the result in rd, don't update flags +pub fn orr(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { + let bytes: [u8; 4] = match (rd, rn, rm) { + (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::Reg(rm)) => { + assert!( + rd.num_bits == rn.num_bits && rn.num_bits == rm.num_bits, + "All operands must be of the same size." + ); + + LogicalReg::orr(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() + }, + (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::UImm(imm)) => { + assert!(rd.num_bits == rn.num_bits, "rd and rn must be of the same size."); + + LogicalImm::orr(rd.reg_no, rn.reg_no, imm.try_into().unwrap(), rd.num_bits).into() + }, + _ => panic!("Invalid operand combination to orr instruction."), + }; + + cb.write_bytes(&bytes); +} + /// STUR - store a value in a register at a memory address pub fn stur(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { let bytes: [u8; 4] = match (rt, rn) { @@ -435,6 +558,11 @@ mod tests { } #[test] + fn test_b() { + check_bytes("00040014", |cb| b(cb, A64Opnd::new_imm(1024))); + } + + #[test] fn test_bl() { check_bytes("00040094", |cb| bl(cb, A64Opnd::new_imm(1024))); } @@ -445,6 +573,16 @@ mod tests { } #[test] + fn test_brk_none() { + check_bytes("000020d4", |cb| brk(cb, A64Opnd::None)); + } + + #[test] + fn test_brk_uimm() { + check_bytes("c00120d4", |cb| brk(cb, A64Opnd::new_uimm(14))); + } + + #[test] fn test_cmp_register() { check_bytes("5f010beb", |cb| cmp(cb, X10, X11)); } @@ -460,8 +598,13 @@ mod tests { } #[test] - fn test_ldur() { - check_bytes("20b047f8", |cb| ldur(cb, X0, A64Opnd::new_mem(X1, 123))); + fn test_ldur_memory() { + check_bytes("20b047f8", |cb| ldur(cb, X0, A64Opnd::new_mem(64, X1, 123))); + } + + #[test] + fn test_ldur_register() { + check_bytes("200040f8", |cb| ldur(cb, X0, X1)); } #[test] @@ -475,6 +618,16 @@ mod tests { } #[test] + fn test_mov_registers() { + check_bytes("ea030baa", |cb| mov(cb, X10, X11)); + } + + #[test] + fn test_mov_immediate() { + check_bytes("eaf300b2", |cb| mov(cb, X10, A64Opnd::new_uimm(0x5555555555555555))); + } + + #[test] fn test_movk() { check_bytes("600fa0f2", |cb| movk(cb, X0, A64Opnd::new_uimm(123), 16)); } @@ -485,6 +638,31 @@ mod tests { } #[test] + fn test_mvn() { + check_bytes("ea032baa", |cb| mvn(cb, X10, X11)); + } + + #[test] + fn test_nop() { + check_bytes("1f2003d5", |cb| nop(cb)); + } + + #[test] + fn test_orn() { + check_bytes("6a012caa", |cb| orn(cb, X10, X11, X12)); + } + + #[test] + fn test_orr_register() { + check_bytes("6a010caa", |cb| orr(cb, X10, X11, X12)); + } + + #[test] + fn test_orr_immediate() { + check_bytes("6a0940b2", |cb| orr(cb, X10, X11, A64Opnd::new_uimm(7))); + } + + #[test] fn test_ret_none() { check_bytes("c0035fd6", |cb| ret(cb, A64Opnd::None)); } @@ -496,7 +674,7 @@ mod tests { #[test] fn test_stur() { - check_bytes("6a0108f8", |cb| stur(cb, X10, A64Opnd::new_mem(X11, 128))); + check_bytes("6a0108f8", |cb| stur(cb, X10, A64Opnd::new_mem(64, X11, 128))); } #[test] |
