@@ -195,7 +195,9 @@ impl Assembler
195195 // This register is caller-saved (so we don't have to save it before using it)
196196 pub const SCRATCH_REG : Reg = X16_REG ;
197197 const SCRATCH0 : A64Opnd = A64Opnd :: Reg ( Assembler :: SCRATCH_REG ) ;
198+ const SCRATCH0_REG : Reg = Assembler :: SCRATCH_REG ;
198199 const SCRATCH1 : A64Opnd = A64Opnd :: Reg ( X17_REG ) ;
200+ const SCRATCH1_REG : Reg = X17_REG ;
199201
200202 /// Get the list of registers from which we will allocate on this platform
201203 pub fn get_alloc_regs ( ) -> Vec < Reg > {
@@ -648,31 +650,6 @@ impl Assembler
648650 * opnd = split_load_operand ( asm, * opnd) ;
649651 asm. push_insn ( insn) ;
650652 } ,
651- Insn :: Store { dest, src } => {
652- // The value being stored must be in a register, so if it's
653- // not already one we'll load it first.
654- let opnd1 = match src {
655- // If the first operand is zero, then we can just use
656- // the zero register.
657- Opnd :: UImm ( 0 ) | Opnd :: Imm ( 0 ) => Opnd :: Reg ( XZR_REG ) ,
658- // Otherwise we'll check if we need to load it first.
659- _ => split_load_operand ( asm, * src)
660- } ;
661-
662- match dest {
663- Opnd :: Reg ( _) => {
664- // Store does not support a register as a dest operand.
665- asm. mov ( * dest, opnd1) ;
666- }
667- _ => {
668- // The displacement for the STUR instruction can't be more
669- // than 9 bits long. If it's longer, we need to load the
670- // memory address into a register first.
671- let opnd0 = split_memory_address ( asm, * dest) ;
672- asm. store ( opnd0, opnd1) ;
673- }
674- }
675- } ,
676653 Insn :: Mul { left, right, .. } => {
677654 * left = split_load_operand ( asm, * left) ;
678655 * right = split_load_operand ( asm, * right) ;
@@ -839,6 +816,25 @@ impl Assembler
839816 }
840817 }
841818
819+ /// Load a VALUE to a register and remember it for GC marking and reference updating
820+ fn emit_load_gc_value ( cb : & mut CodeBlock , gc_offsets : & mut Vec < CodePtr > , dest : A64Opnd , value : VALUE ) {
821+ // We dont need to check if it's a special const
822+ // here because we only allow these operands to hit
823+ // this point if they're not a special const.
824+ assert ! ( !value. special_const_p( ) ) ;
825+
826+ // This assumes only load instructions can contain
827+ // references to GC'd Value operands. If the value
828+ // being loaded is a heap object, we'll report that
829+ // back out to the gc_offsets list.
830+ ldr_literal ( cb, dest, 2 . into ( ) ) ;
831+ b ( cb, InstructionOffset :: from_bytes ( 4 + ( SIZEOF_VALUE as i32 ) ) ) ;
832+ cb. write_bytes ( & value. as_u64 ( ) . to_le_bytes ( ) ) ;
833+
834+ let ptr_offset = cb. get_write_ptr ( ) . sub_bytes ( SIZEOF_VALUE ) ;
835+ gc_offsets. push ( ptr_offset) ;
836+ }
837+
842838 /// Emit a push instruction for the given operand by adding to the stack
843839 /// pointer and then storing the given value.
844840 fn emit_push ( cb : & mut CodeBlock , opnd : A64Opnd ) {
@@ -1009,12 +1005,84 @@ impl Assembler
10091005 Insn :: LShift { opnd, shift, out } => {
10101006 lsl ( cb, out. into ( ) , opnd. into ( ) , shift. into ( ) ) ;
10111007 } ,
1012- Insn :: Store { dest, src } => {
1008+ store_insn @ Insn :: Store { dest, src } => {
1009+ // With minor exceptions, as long as `dest` is a Mem, all forms of `src` are
1010+ // accepted. As a rule of thumb, avoid using Assembler::SCRATCH as a memory
1011+ // base register to gurantee things will work.
1012+ let & Opnd :: Mem ( Mem { num_bits : dest_num_bits, base : MemBase :: Reg ( base_reg_no) , disp } ) = dest else {
1013+ panic ! ( "Unexpected Insn::Store destination in arm64_emit: {dest:?}" ) ;
1014+ } ;
1015+
1016+ // This kind of tricky clobber can only happen for explicit use of SCRATCH_REG,
1017+ // so we panic to get the author to change their code.
1018+ #[ track_caller]
1019+ fn assert_no_clobber ( store_insn : & Insn , user_use : u8 , backend_use : Reg ) {
1020+ assert_ne ! (
1021+ backend_use. reg_no,
1022+ user_use,
1023+ "Emitting {store_insn:?} would clobber {user_use:?}, in conflict with its semantics"
1024+ ) ;
1025+ }
1026+
1027+ // Split src into SCRATCH0 if necessary
1028+ let src_reg: A64Reg = match src {
1029+ Opnd :: Reg ( reg) => * reg,
1030+ // Use zero register when possible
1031+ Opnd :: UImm ( 0 ) | Opnd :: Imm ( 0 ) => XZR_REG ,
1032+ // Immediates
1033+ & Opnd :: Imm ( imm) => {
1034+ assert_no_clobber ( store_insn, base_reg_no, Self :: SCRATCH0_REG ) ;
1035+ emit_load_value ( cb, Self :: SCRATCH0 , imm as u64 ) ;
1036+ Self :: SCRATCH0_REG
1037+ }
1038+ & Opnd :: UImm ( imm) => {
1039+ assert_no_clobber ( store_insn, base_reg_no, Self :: SCRATCH0_REG ) ;
1040+ emit_load_value ( cb, Self :: SCRATCH0 , imm) ;
1041+ Self :: SCRATCH0_REG
1042+ }
1043+ & Opnd :: Value ( value) => {
1044+ assert_no_clobber ( store_insn, base_reg_no, Self :: SCRATCH0_REG ) ;
1045+ emit_load_gc_value ( cb, & mut gc_offsets, Self :: SCRATCH0 , value) ;
1046+ Self :: SCRATCH0_REG
1047+ }
1048+ src_mem @ & Opnd :: Mem ( Mem { num_bits : src_num_bits, base : MemBase :: Reg ( src_base_reg_no) , disp : src_disp } ) => {
1049+ // For mem-to-mem store, load the source into SCRATCH0
1050+ assert_no_clobber ( store_insn, base_reg_no, Self :: SCRATCH0_REG ) ;
1051+ let src_mem = if mem_disp_fits_bits ( src_disp) {
1052+ src_mem. into ( )
1053+ } else {
1054+ // Split the load address into SCRATCH0 first if necessary
1055+ assert_no_clobber ( store_insn, src_base_reg_no, Self :: SCRATCH0_REG ) ;
1056+ load_effective_address ( cb, Self :: SCRATCH0 , src_base_reg_no, src_disp) ;
1057+ A64Opnd :: new_mem ( dest_num_bits, Self :: SCRATCH0 , 0 )
1058+ } ;
1059+ match src_num_bits {
1060+ 64 | 32 => ldur ( cb, Self :: SCRATCH0 , src_mem) ,
1061+ 16 => ldurh ( cb, Self :: SCRATCH0 , src_mem) ,
1062+ 8 => ldurb ( cb, Self :: SCRATCH0 , src_mem) ,
1063+ num_bits => panic ! ( "unexpected num_bits: {num_bits}" )
1064+ } ;
1065+ Self :: SCRATCH0_REG
1066+ }
1067+ src @ ( Opnd :: Mem ( _) | Opnd :: None | Opnd :: VReg { .. } ) => panic ! ( "Unexpected source operand during arm64_emit: {src:?}" )
1068+ } ;
1069+ let src = A64Opnd :: Reg ( src_reg) ;
1070+
1071+ // Split dest into SCRATCH1 if necessary.
1072+ let dest = if mem_disp_fits_bits ( disp) {
1073+ dest. into ( )
1074+ } else {
1075+ assert_no_clobber ( store_insn, src_reg. reg_no , Self :: SCRATCH1_REG ) ;
1076+ assert_no_clobber ( store_insn, base_reg_no, Self :: SCRATCH1_REG ) ;
1077+ load_effective_address ( cb, Self :: SCRATCH1 , base_reg_no, disp) ;
1078+ A64Opnd :: new_mem ( dest_num_bits, Self :: SCRATCH1 , 0 )
1079+ } ;
1080+
10131081 // This order may be surprising but it is correct. The way
10141082 // the Arm64 assembler works, the register that is going to
10151083 // be stored is first and the address is second. However in
10161084 // our IR we have the address first and the register second.
1017- match dest . rm_num_bits ( ) {
1085+ match dest_num_bits {
10181086 64 | 32 => stur ( cb, src. into ( ) , dest. into ( ) ) ,
10191087 16 => sturh ( cb, src. into ( ) , dest. into ( ) ) ,
10201088 num_bits => panic ! ( "unexpected dest num_bits: {} (src: {:#?}, dest: {:#?})" , num_bits, src, dest) ,
@@ -1041,21 +1109,7 @@ impl Assembler
10411109 } ;
10421110 } ,
10431111 Opnd :: Value ( value) => {
1044- // We dont need to check if it's a special const
1045- // here because we only allow these operands to hit
1046- // this point if they're not a special const.
1047- assert ! ( !value. special_const_p( ) ) ;
1048-
1049- // This assumes only load instructions can contain
1050- // references to GC'd Value operands. If the value
1051- // being loaded is a heap object, we'll report that
1052- // back out to the gc_offsets list.
1053- ldr_literal ( cb, out. into ( ) , 2 . into ( ) ) ;
1054- b ( cb, InstructionOffset :: from_bytes ( 4 + ( SIZEOF_VALUE as i32 ) ) ) ;
1055- cb. write_bytes ( & value. as_u64 ( ) . to_le_bytes ( ) ) ;
1056-
1057- let ptr_offset = cb. get_write_ptr ( ) . sub_bytes ( SIZEOF_VALUE ) ;
1058- gc_offsets. push ( ptr_offset) ;
1112+ emit_load_gc_value ( cb, & mut gc_offsets, out. into ( ) , value) ;
10591113 } ,
10601114 Opnd :: None => {
10611115 unreachable ! ( "Attempted to load from None operand" ) ;
@@ -1093,20 +1147,7 @@ impl Assembler
10931147 let & Opnd :: Mem ( Mem { num_bits : _, base : MemBase :: Reg ( base_reg_no) , disp } ) = opnd else {
10941148 panic ! ( "Unexpected Insn::Lea operand in arm64_emit: {opnd:?}" ) ;
10951149 } ;
1096- let out: A64Opnd = out. into ( ) ;
1097- let base_reg = A64Opnd :: Reg ( A64Reg { num_bits : 64 , reg_no : base_reg_no } ) ;
1098- assert_ne ! ( 31 , out. unwrap_reg( ) . reg_no, "Insn::Lea sp, [sp, #imm] not always encodable. Use add/sub instead." ) ;
1099-
1100- if ShiftedImmediate :: try_from ( disp. unsigned_abs ( ) as u64 ) . is_ok ( ) {
1101- // Use ADD/SUB if the displacement fits
1102- add ( cb, out, base_reg, A64Opnd :: new_imm ( disp. into ( ) ) ) ;
1103- } else {
1104- // Use add_extended() to interpret reg_no=31 as sp
1105- // since the base register is never the zero register.
1106- // Careful! Only the first two operands can refer to sp.
1107- emit_load_value ( cb, out, disp as u64 ) ;
1108- add_extended ( cb, out, base_reg, out) ;
1109- } ;
1150+ load_effective_address ( cb, out. into ( ) , base_reg_no, disp) ;
11101151 }
11111152 Insn :: LeaJumpTarget { out, target, .. } => {
11121153 if let Target :: Label ( label_idx) = target {
@@ -1337,6 +1378,22 @@ impl Assembler
13371378 }
13381379}
13391380
1381+ fn load_effective_address ( cb : & mut CodeBlock , out : A64Opnd , base_reg_no : u8 , disp : i32 ) {
1382+ let base_reg = A64Opnd :: Reg ( A64Reg { num_bits : 64 , reg_no : base_reg_no } ) ;
1383+ assert_ne ! ( 31 , out. unwrap_reg( ) . reg_no, "Lea sp, [sp, #imm] not always encodable. Use add/sub instead." ) ;
1384+
1385+ if ShiftedImmediate :: try_from ( disp. unsigned_abs ( ) as u64 ) . is_ok ( ) {
1386+ // Use ADD/SUB if the displacement fits
1387+ add ( cb, out, base_reg, A64Opnd :: new_imm ( disp. into ( ) ) ) ;
1388+ } else {
1389+ // Use add_extended() to interpret reg_no=31 as sp
1390+ // since the base register is never the zero register.
1391+ // Careful! Only the first two operands can refer to sp.
1392+ emit_load_value ( cb, out, disp as u64 ) ;
1393+ add_extended ( cb, out, base_reg, out) ;
1394+ } ;
1395+ }
1396+
13401397/// LIR Instructions that are lowered to an instruction that have 2 input registers and an output
13411398/// register can look to merge with a succeeding `Insn::Mov`.
13421399/// For example:
@@ -1625,6 +1682,64 @@ mod tests {
16251682 " ) ;
16261683 }
16271684
1685+ #[ test]
1686+ fn test_store ( ) {
1687+ let ( mut asm, mut cb) = setup_asm ( ) ;
1688+
1689+ // Large memory offsets in combinations of destination and source
1690+ let large_mem = Opnd :: mem ( 64 , NATIVE_STACK_PTR , -0x305 ) ;
1691+ let small_mem = Opnd :: mem ( 64 , C_RET_OPND , 0 ) ;
1692+ asm. store ( small_mem, large_mem) ;
1693+ asm. store ( large_mem, small_mem) ;
1694+ asm. store ( large_mem, large_mem) ;
1695+
1696+ asm. compile_with_num_regs ( & mut cb, 0 ) ;
1697+ assert_disasm ! ( cb, "f0170cd1100240f8100000f8100040f8f1170cd1300200f8f0170cd1100240f8f1170cd1300200f8" , "
1698+ 0x0: sub x16, sp, #0x305
1699+ 0x4: ldur x16, [x16]
1700+ 0x8: stur x16, [x0]
1701+ 0xc: ldur x16, [x0]
1702+ 0x10: sub x17, sp, #0x305
1703+ 0x14: stur x16, [x17]
1704+ 0x18: sub x16, sp, #0x305
1705+ 0x1c: ldur x16, [x16]
1706+ 0x20: sub x17, sp, #0x305
1707+ 0x24: stur x16, [x17]
1708+ " ) ;
1709+ }
1710+
1711+ #[ test]
1712+ fn test_store_value_without_split ( ) {
1713+ let ( mut asm, mut cb) = setup_asm ( ) ;
1714+
1715+ let imitation_heap_value = VALUE ( 0x1000 ) ;
1716+ assert ! ( imitation_heap_value. heap_object_p( ) ) ;
1717+ asm. store ( Opnd :: mem ( VALUE_BITS , SP , 0 ) , imitation_heap_value. into ( ) ) ;
1718+
1719+ // Side exit code are compiled without the split pass, so we directly call emit here to
1720+ // emulate that scenario.
1721+ let gc_offsets = asm. arm64_emit ( & mut cb) . unwrap ( ) ;
1722+ assert_eq ! ( 1 , gc_offsets. len( ) , "VALUE source operand should be reported as gc offset" ) ;
1723+
1724+ assert_disasm ! ( cb, "50000058030000140010000000000000b00200f8" , "
1725+ 0x0: ldr x16, #8
1726+ 0x4: b #0x10
1727+ 0x8: .byte 0x00, 0x10, 0x00, 0x00
1728+ 0xc: .byte 0x00, 0x00, 0x00, 0x00
1729+ 0x10: stur x16, [x21]
1730+ " ) ;
1731+ }
1732+
1733+ #[ test]
1734+ #[ should_panic]
1735+ fn test_store_unserviceable ( ) {
1736+ let ( mut asm, mut cb) = setup_asm ( ) ;
1737+ // This would put the source into SCRATCH_REG, messing up the destination
1738+ asm. store ( Opnd :: mem ( 64 , Opnd :: Reg ( Assembler :: SCRATCH_REG ) , 0 ) , 0x83902 . into ( ) ) ;
1739+
1740+ asm. compile_with_num_regs ( & mut cb, 0 ) ;
1741+ }
1742+
16281743 /*
16291744 #[test]
16301745 fn test_emit_lea_label() {
0 commit comments