github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/backend/isa/amd64/lower_mem.go (about) 1 package amd64 2 3 import ( 4 "fmt" 5 6 "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" 7 "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" 8 "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" 9 ) 10 11 var addendsMatchOpcodes = [...]ssa.Opcode{ssa.OpcodeUExtend, ssa.OpcodeSExtend, ssa.OpcodeIadd, ssa.OpcodeIconst, ssa.OpcodeIshl} 12 13 type addend struct { 14 r regalloc.VReg 15 off int64 16 shift byte 17 } 18 19 func (a addend) String() string { 20 return fmt.Sprintf("addend{r=%s, off=%d, shift=%d}", a.r, a.off, a.shift) 21 } 22 23 // lowerToAddressMode converts a pointer to an addressMode that can be used as an operand for load/store instructions. 24 func (m *machine) lowerToAddressMode(ptr ssa.Value, offsetBase uint32) (am *amode) { 25 def := m.c.ValueDefinition(ptr) 26 27 if offsetBase&0x80000000 != 0 { 28 // Special casing the huge base offset whose MSB is set. In x64, the immediate is always 29 // sign-extended, but our IR semantics requires the offset base is always unsigned. 30 // Note that this should be extremely rare or even this shouldn't hit in the real application, 31 // therefore we don't need to optimize this case in my opinion. 32 33 a := m.lowerAddend(def) 34 off64 := a.off + int64(offsetBase) 35 offsetBaseReg := m.c.AllocateVReg(ssa.TypeI64) 36 m.lowerIconst(offsetBaseReg, uint64(off64), true) 37 if a.r != regalloc.VRegInvalid { 38 return m.newAmodeRegRegShift(0, offsetBaseReg, a.r, a.shift) 39 } else { 40 return m.newAmodeImmReg(0, offsetBaseReg) 41 } 42 } 43 44 if op := m.c.MatchInstrOneOf(def, addendsMatchOpcodes[:]); op == ssa.OpcodeIadd { 45 add := def.Instr 46 x, y := add.Arg2() 47 xDef, yDef := m.c.ValueDefinition(x), m.c.ValueDefinition(y) 48 ax := m.lowerAddend(xDef) 49 ay := m.lowerAddend(yDef) 50 add.MarkLowered() 51 return m.lowerAddendsToAmode(ax, ay, offsetBase) 52 } else { 53 // If it is not an Iadd, then we lower the one addend. 54 a := m.lowerAddend(def) 55 // off is always 0 if r is valid. 56 if a.r != regalloc.VRegInvalid { 57 if a.shift != 0 { 58 tmpReg := m.c.AllocateVReg(ssa.TypeI64) 59 m.lowerIconst(tmpReg, 0, true) 60 return m.newAmodeRegRegShift(offsetBase, tmpReg, a.r, a.shift) 61 } 62 return m.newAmodeImmReg(offsetBase, a.r) 63 } else { 64 off64 := a.off + int64(offsetBase) 65 tmpReg := m.c.AllocateVReg(ssa.TypeI64) 66 m.lowerIconst(tmpReg, uint64(off64), true) 67 return m.newAmodeImmReg(0, tmpReg) 68 } 69 } 70 } 71 72 func (m *machine) lowerAddendsToAmode(x, y addend, offBase uint32) *amode { 73 if x.r != regalloc.VRegInvalid && x.off != 0 || y.r != regalloc.VRegInvalid && y.off != 0 { 74 panic("invalid input") 75 } 76 77 u64 := uint64(x.off+y.off) + uint64(offBase) 78 if u64 != 0 { 79 if _, ok := asImm32(u64, false); !ok { 80 tmpReg := m.c.AllocateVReg(ssa.TypeI64) 81 m.lowerIconst(tmpReg, u64, true) 82 // Blank u64 as it has been already lowered. 83 u64 = 0 84 85 if x.r == regalloc.VRegInvalid { 86 x.r = tmpReg 87 } else if y.r == regalloc.VRegInvalid { 88 y.r = tmpReg 89 } else { 90 // We already know that either rx or ry is invalid, 91 // so we overwrite it with the temporary register. 92 panic("BUG") 93 } 94 } 95 } 96 97 u32 := uint32(u64) 98 switch { 99 // We assume rx, ry are valid iff offx, offy are 0. 100 case x.r != regalloc.VRegInvalid && y.r != regalloc.VRegInvalid: 101 switch { 102 case x.shift != 0 && y.shift != 0: 103 // Cannot absorb two shifted registers, must lower one to a shift instruction. 104 shifted := m.allocateInstr() 105 shifted.asShiftR(shiftROpShiftLeft, newOperandImm32(uint32(x.shift)), x.r, true) 106 m.insert(shifted) 107 108 return m.newAmodeRegRegShift(u32, x.r, y.r, y.shift) 109 case x.shift != 0 && y.shift == 0: 110 // Swap base and index. 111 x, y = y, x 112 fallthrough 113 default: 114 return m.newAmodeRegRegShift(u32, x.r, y.r, y.shift) 115 } 116 case x.r == regalloc.VRegInvalid && y.r != regalloc.VRegInvalid: 117 x, y = y, x 118 fallthrough 119 case x.r != regalloc.VRegInvalid && y.r == regalloc.VRegInvalid: 120 if x.shift != 0 { 121 zero := m.c.AllocateVReg(ssa.TypeI64) 122 m.lowerIconst(zero, 0, true) 123 return m.newAmodeRegRegShift(u32, zero, x.r, x.shift) 124 } 125 return m.newAmodeImmReg(u32, x.r) 126 default: // Both are invalid: use the offset. 127 tmpReg := m.c.AllocateVReg(ssa.TypeI64) 128 m.lowerIconst(tmpReg, u64, true) 129 return m.newAmodeImmReg(0, tmpReg) 130 } 131 } 132 133 func (m *machine) lowerAddend(x *backend.SSAValueDefinition) addend { 134 if x.IsFromBlockParam() { 135 return addend{x.BlkParamVReg, 0, 0} 136 } 137 // Ensure the addend is not referenced in multiple places; we will discard nested Iadds. 138 op := m.c.MatchInstrOneOf(x, addendsMatchOpcodes[:]) 139 if op != ssa.OpcodeInvalid && op != ssa.OpcodeIadd { 140 return m.lowerAddendFromInstr(x.Instr) 141 } 142 p := m.getOperand_Reg(x) 143 return addend{p.reg(), 0, 0} 144 } 145 146 // lowerAddendFromInstr takes an instruction returns a Vreg and an offset that can be used in an address mode. 147 // The Vreg is regalloc.VRegInvalid if the addend cannot be lowered to a register. 148 // The offset is 0 if the addend can be lowered to a register. 149 func (m *machine) lowerAddendFromInstr(instr *ssa.Instruction) addend { 150 instr.MarkLowered() 151 switch op := instr.Opcode(); op { 152 case ssa.OpcodeIconst: 153 u64 := instr.ConstantVal() 154 if instr.Return().Type().Bits() == 32 { 155 return addend{regalloc.VRegInvalid, int64(int32(u64)), 0} // sign-extend. 156 } else { 157 return addend{regalloc.VRegInvalid, int64(u64), 0} 158 } 159 case ssa.OpcodeUExtend, ssa.OpcodeSExtend: 160 input := instr.Arg() 161 inputDef := m.c.ValueDefinition(input) 162 if input.Type().Bits() != 32 { 163 panic("BUG: invalid input type " + input.Type().String()) 164 } 165 constInst := inputDef.IsFromInstr() && inputDef.Instr.Constant() 166 switch { 167 case constInst && op == ssa.OpcodeSExtend: 168 return addend{regalloc.VRegInvalid, int64(uint32(inputDef.Instr.ConstantVal())), 0} 169 case constInst && op == ssa.OpcodeUExtend: 170 return addend{regalloc.VRegInvalid, int64(int32(inputDef.Instr.ConstantVal())), 0} // sign-extend! 171 default: 172 r := m.getOperand_Reg(inputDef) 173 return addend{r.reg(), 0, 0} 174 } 175 case ssa.OpcodeIshl: 176 // If the addend is a shift, we can only handle it if the shift amount is a constant. 177 x, amount := instr.Arg2() 178 amountDef := m.c.ValueDefinition(amount) 179 if amountDef.IsFromInstr() && amountDef.Instr.Constant() && amountDef.Instr.ConstantVal() <= 3 { 180 r := m.getOperand_Reg(m.c.ValueDefinition(x)) 181 return addend{r.reg(), 0, uint8(amountDef.Instr.ConstantVal())} 182 } 183 r := m.getOperand_Reg(m.c.ValueDefinition(x)) 184 return addend{r.reg(), 0, 0} 185 } 186 panic("BUG: invalid opcode") 187 }