@@ -293,38 +293,11 @@ impl Assembler
293293
294294 asm. push_insn ( insn) ;
295295 } ,
296- Insn :: Mov { dest, src } | Insn :: Store { dest, src } => {
297- match ( & dest, & src) {
298- ( Opnd :: Mem ( _) , Opnd :: Mem ( _) ) => {
299- // We load opnd1 because for mov, opnd0 is the output
300- let opnd1 = asm. load ( * src) ;
301- asm. mov ( * dest, opnd1) ;
302- } ,
303- ( Opnd :: Mem ( Mem { num_bits, .. } ) , Opnd :: UImm ( value) ) => {
304- // For 64 bit destinations, 32-bit values will be sign-extended
305- if * num_bits == 64 && imm_num_bits ( * value as i64 ) > 32 {
306- let opnd1 = asm. load ( * src) ;
307- asm. mov ( * dest, opnd1) ;
308- } else {
309- asm. mov ( * dest, * src) ;
310- }
311- } ,
312- ( Opnd :: Mem ( Mem { num_bits, .. } ) , Opnd :: Imm ( value) ) => {
313- // For 64 bit destinations, 32-bit values will be sign-extended
314- if * num_bits == 64 && imm_num_bits ( * value) > 32 {
315- let opnd1 = asm. load ( * src) ;
316- asm. mov ( * dest, opnd1) ;
317- } else if uimm_num_bits ( * value as u64 ) <= * num_bits {
318- // If the bit string is short enough for the destination, use the unsigned representation.
319- // Note that 64-bit and negative values are ruled out.
320- asm. mov ( * dest, Opnd :: UImm ( * value as u64 ) ) ;
321- } else {
322- asm. mov ( * dest, * src) ;
323- }
324- } ,
325- _ => {
326- asm. mov ( * dest, * src) ;
327- }
296+ Insn :: Mov { dest, src } => {
297+ if let Opnd :: Mem ( _) = dest {
298+ asm. store ( * dest, * src) ;
299+ } else {
300+ asm. mov ( * dest, * src) ;
328301 }
329302 } ,
330303 Insn :: Not { opnd, .. } => {
@@ -440,6 +413,14 @@ impl Assembler
440413 }
441414 }
442415
416+ fn emit_load_gc_value ( cb : & mut CodeBlock , gc_offsets : & mut Vec < CodePtr > , dest_reg : X86Opnd , value : VALUE ) {
417+ // Using movabs because mov might write value in 32 bits
418+ movabs ( cb, dest_reg, value. 0 as _ ) ;
419+ // The pointer immediate is encoded as the last part of the mov written out
420+ let ptr_offset = cb. get_write_ptr ( ) . sub_bytes ( SIZEOF_VALUE ) ;
421+ gc_offsets. push ( ptr_offset) ;
422+ }
423+
443424 // List of GC offsets
444425 let mut gc_offsets: Vec < CodePtr > = Vec :: new ( ) ;
445426
@@ -552,20 +533,71 @@ impl Assembler
552533 shr ( cb, opnd. into ( ) , shift. into ( ) )
553534 } ,
554535
555- Insn :: Store { dest, src } => {
556- mov ( cb, dest. into ( ) , src. into ( ) ) ;
557- } ,
536+ store_insn @ Insn :: Store { dest, src } => {
537+ let & Opnd :: Mem ( Mem { num_bits, base : MemBase :: Reg ( base_reg_no) , disp : _ } ) = dest else {
538+ panic ! ( "Unexpected Insn::Store destination in x64_emit: {dest:?}" ) ;
539+ } ;
540+
541+ // This kind of tricky clobber can only happen for explicit use of SCRATCH_REG,
542+ // so we panic to get the author to change their code.
543+ #[ track_caller]
544+ fn assert_no_clobber ( store_insn : & Insn , user_use : u8 , backend_use : Reg ) {
545+ assert_ne ! (
546+ backend_use. reg_no,
547+ user_use,
548+ "Emitting {store_insn:?} would clobber {user_use:?}, in conflict with its semantics"
549+ ) ;
550+ }
551+
552+ let scratch = X86Opnd :: Reg ( Self :: SCRATCH_REG ) ;
553+ let src = match src {
554+ Opnd :: Reg ( _) => src. into ( ) ,
555+ & Opnd :: Mem ( _) => {
556+ assert_no_clobber ( store_insn, base_reg_no, Self :: SCRATCH_REG ) ;
557+ mov ( cb, scratch, src. into ( ) ) ;
558+ scratch
559+ }
560+ & Opnd :: Imm ( imm) => {
561+ // For 64 bit destinations, 32-bit values will be sign-extended
562+ if num_bits == 64 && imm_num_bits ( imm) > 32 {
563+ assert_no_clobber ( store_insn, base_reg_no, Self :: SCRATCH_REG ) ;
564+ mov ( cb, scratch, src. into ( ) ) ;
565+ scratch
566+ } else if uimm_num_bits ( imm as u64 ) <= num_bits {
567+ // If the bit string is short enough for the destination, use the unsigned representation.
568+ // Note that 64-bit and negative values are ruled out.
569+ uimm_opnd ( imm as u64 )
570+ } else {
571+ src. into ( )
572+ }
573+ }
574+ & Opnd :: UImm ( imm) => {
575+ // For 64 bit destinations, 32-bit values will be sign-extended
576+ if num_bits == 64 && imm_num_bits ( imm as i64 ) > 32 {
577+ assert_no_clobber ( store_insn, base_reg_no, Self :: SCRATCH_REG ) ;
578+ mov ( cb, scratch, src. into ( ) ) ;
579+ scratch
580+ } else {
581+ src. into ( )
582+ }
583+ }
584+ & Opnd :: Value ( value) => {
585+ assert_no_clobber ( store_insn, base_reg_no, Self :: SCRATCH_REG ) ;
586+ emit_load_gc_value ( cb, & mut gc_offsets, scratch, value) ;
587+ scratch
588+ }
589+ src @ ( Opnd :: None | Opnd :: VReg { .. } ) => panic ! ( "Unexpected source operand during x86_emit: {src:?}" )
590+
591+ } ;
592+ mov ( cb, dest. into ( ) , src) ;
593+ }
558594
559595 // This assumes only load instructions can contain references to GC'd Value operands
560596 Insn :: Load { opnd, out } |
561597 Insn :: LoadInto { dest : out, opnd } => {
562598 match opnd {
563599 Opnd :: Value ( val) if val. heap_object_p ( ) => {
564- // Using movabs because mov might write value in 32 bits
565- movabs ( cb, out. into ( ) , val. 0 as _ ) ;
566- // The pointer immediate is encoded as the last part of the mov written out
567- let ptr_offset = cb. get_write_ptr ( ) . sub_bytes ( SIZEOF_VALUE ) ;
568- gc_offsets. push ( ptr_offset) ;
600+ emit_load_gc_value ( cb, & mut gc_offsets, out. into ( ) , * val) ;
569601 }
570602 _ => mov ( cb, out. into ( ) , opnd. into ( ) )
571603 }
@@ -1352,4 +1384,16 @@ mod tests {
13521384 0x29: pop rbp
13531385 " } ) ;
13541386 }
1387+
1388+ #[ test]
1389+ fn test_store_value_without_split ( ) {
1390+ let ( mut asm, mut cb) = setup_asm ( ) ;
1391+
1392+ let imitation_heap_value = VALUE ( 0x1000 ) ;
1393+ assert ! ( imitation_heap_value. heap_object_p( ) ) ;
1394+ asm. store ( Opnd :: mem ( VALUE_BITS , SP , 0 ) , imitation_heap_value. into ( ) ) ;
1395+
1396+ let gc_offsets = asm. x86_emit ( & mut cb) . unwrap ( ) ;
1397+ assert_eq ! ( 1 , gc_offsets. len( ) , "VALUE source operand should be reported as gc offset" ) ;
1398+ }
13551399}
0 commit comments