github.com/tetratelabs/wazero@v1.7.1/internal/engine/wazevo/backend/isa/amd64/machine_test.go (about) 1 package amd64 2 3 import ( 4 "fmt" 5 "runtime" 6 "strings" 7 "testing" 8 9 "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" 10 "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" 11 "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" 12 "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" 13 "github.com/tetratelabs/wazero/internal/platform" 14 "github.com/tetratelabs/wazero/internal/testing/require" 15 ) 16 17 func Test_asImm32(t *testing.T) { 18 v, ok := asImm32(0xffffffff, true) 19 require.True(t, ok) 20 require.Equal(t, uint32(0xffffffff), v) 21 22 v, ok = asImm32(0xffffffff, false) 23 require.False(t, ok) 24 require.Equal(t, uint32(0), v) 25 26 v, ok = asImm32(0x80000000, true) 27 require.True(t, ok) 28 require.Equal(t, uint32(0x80000000), v) 29 30 v, ok = asImm32(0x80000000, false) 31 require.False(t, ok) 32 require.Equal(t, uint32(0), v) 33 34 v, ok = asImm32(0xffffffff<<1, true) 35 require.False(t, ok) 36 require.Equal(t, uint32(0), v) 37 } 38 39 func TestMachine_getOperand_Reg(t *testing.T) { 40 for _, tc := range []struct { 41 name string 42 setup func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition 43 exp operand 44 instructions []string 45 }{ 46 { 47 name: "block param", 48 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 49 return &backend.SSAValueDefinition{BlkParamVReg: raxVReg, Instr: nil, N: 0} 50 }, 51 exp: newOperandReg(raxVReg), 52 }, 53 54 { 55 name: "const instr", 56 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 57 instr := builder.AllocateInstruction() 58 instr.AsIconst32(0xf00000f) 59 builder.InsertInstruction(instr) 60 ctx.vRegCounter = 99 61 return &backend.SSAValueDefinition{Instr: instr, N: 0} 62 }, 63 exp: newOperandReg(regalloc.VReg(100).SetRegType(regalloc.RegTypeInt)), 64 instructions: []string{"movl $251658255, %r100d?"}, 65 }, 66 { 67 name: "non const instr (single-return)", 68 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 69 c := builder.AllocateInstruction() 70 sig := &ssa.Signature{Results: []ssa.Type{ssa.TypeI64}} 71 builder.DeclareSignature(sig) 72 c.AsCall(ssa.FuncRef(0), sig, ssa.ValuesNil) 73 builder.InsertInstruction(c) 74 r := c.Return() 75 ctx.vRegMap[r] = regalloc.VReg(50) 76 return &backend.SSAValueDefinition{Instr: c, N: 0} 77 }, 78 exp: newOperandReg(regalloc.VReg(50)), 79 }, 80 { 81 name: "non const instr (multi-return)", 82 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 83 c := builder.AllocateInstruction() 84 sig := &ssa.Signature{Results: []ssa.Type{ssa.TypeI64, ssa.TypeF64, ssa.TypeF64}} 85 builder.DeclareSignature(sig) 86 c.AsCall(ssa.FuncRef(0), sig, ssa.ValuesNil) 87 builder.InsertInstruction(c) 88 _, rs := c.Returns() 89 ctx.vRegMap[rs[1]] = regalloc.VReg(50) 90 return &backend.SSAValueDefinition{Instr: c, N: 2} 91 }, 92 exp: newOperandReg(regalloc.VReg(50)), 93 }, 94 } { 95 t.Run(tc.name, func(t *testing.T) { 96 ctx, b, m := newSetupWithMockContext() 97 def := tc.setup(ctx, b, m) 98 actual := m.getOperand_Reg(def) 99 require.Equal(t, tc.exp, actual) 100 require.Equal(t, strings.Join(tc.instructions, "\n"), formatEmittedInstructionsInCurrentBlock(m)) 101 }) 102 } 103 } 104 105 func TestMachine_getOperand_Imm32_Reg(t *testing.T) { 106 for _, tc := range []struct { 107 name string 108 setup func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition 109 exp operand 110 instructions []string 111 }{ 112 { 113 name: "block param falls back to getOperand_Reg", 114 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 115 return &backend.SSAValueDefinition{BlkParamVReg: raxVReg, Instr: nil, N: 0} 116 }, 117 exp: newOperandReg(raxVReg), 118 }, 119 { 120 name: "const imm 32", 121 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 122 instr := builder.AllocateInstruction() 123 instr.AsIconst32(0xf00000f) 124 builder.InsertInstruction(instr) 125 ctx.vRegCounter = 99 126 ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id. 127 return &backend.SSAValueDefinition{Instr: instr, N: 0} 128 }, 129 exp: newOperandImm32(0xf00000f), 130 }, 131 } { 132 t.Run(tc.name, func(t *testing.T) { 133 ctx, b, m := newSetupWithMockContext() 134 def := tc.setup(ctx, b, m) 135 actual := m.getOperand_Imm32_Reg(def) 136 require.Equal(t, tc.exp, actual) 137 require.Equal(t, strings.Join(tc.instructions, "\n"), formatEmittedInstructionsInCurrentBlock(m)) 138 }) 139 } 140 } 141 142 func Test_machine_getOperand_Mem_Imm32_Reg(t *testing.T) { 143 _, _, m := newSetupWithMockContext() 144 defer func() { 145 runtime.KeepAlive(m) 146 }() 147 newAmodeImmReg := m.newAmodeImmReg 148 149 for _, tc := range []struct { 150 name string 151 setup func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition 152 exp operand 153 instructions []string 154 }{ 155 { 156 name: "block param falls back to getOperand_Imm32_Reg", 157 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 158 return &backend.SSAValueDefinition{BlkParamVReg: raxVReg, Instr: nil, N: 0} 159 }, 160 exp: newOperandReg(raxVReg), 161 }, 162 { 163 name: "amode with block param", 164 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 165 blk := builder.CurrentBlock() 166 ptr := blk.AddParam(builder, ssa.TypeI64) 167 ctx.definitions[ptr] = &backend.SSAValueDefinition{BlockParamValue: ptr, BlkParamVReg: raxVReg} 168 instr := builder.AllocateInstruction() 169 instr.AsLoad(ptr, 123, ssa.TypeI64).Insert(builder) 170 return &backend.SSAValueDefinition{Instr: instr, N: 0} 171 }, 172 exp: newOperandMem(newAmodeImmReg(123, raxVReg)), 173 }, 174 { 175 name: "amode with iconst", 176 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 177 iconst := builder.AllocateInstruction().AsIconst64(456).Insert(builder) 178 instr := builder.AllocateInstruction() 179 instr.AsLoad(iconst.Return(), 123, ssa.TypeI64).Insert(builder) 180 ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst} 181 return &backend.SSAValueDefinition{Instr: instr, N: 0} 182 }, 183 instructions: []string{ 184 "movabsq $579, %r1?", // r1 := 123+456 185 }, 186 exp: newOperandMem(newAmodeImmReg(0, regalloc.VReg(1).SetRegType(regalloc.RegTypeInt))), 187 }, 188 { 189 name: "amode with iconst and extend", 190 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 191 iconst := builder.AllocateInstruction().AsIconst32(0xffffff).Insert(builder) 192 uextend := builder.AllocateInstruction().AsUExtend(iconst.Return(), 32, 64).Insert(builder) 193 194 instr := builder.AllocateInstruction() 195 instr.AsLoad(uextend.Return(), 123, ssa.TypeI64).Insert(builder) 196 197 ctx.definitions[uextend.Return()] = &backend.SSAValueDefinition{Instr: uextend} 198 ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst} 199 200 return &backend.SSAValueDefinition{Instr: instr, N: 0} 201 }, 202 instructions: []string{ 203 fmt.Sprintf("movabsq $%d, %%r1?", 0xffffff+123), 204 }, 205 exp: newOperandMem(newAmodeImmReg(0, regalloc.VReg(1).SetRegType(regalloc.RegTypeInt))), 206 }, 207 { 208 name: "amode with iconst and extend", 209 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 210 iconst := builder.AllocateInstruction().AsIconst32(456).Insert(builder) 211 uextend := builder.AllocateInstruction().AsUExtend(iconst.Return(), 32, 64).Insert(builder) 212 213 instr := builder.AllocateInstruction() 214 instr.AsLoad(uextend.Return(), 123, ssa.TypeI64).Insert(builder) 215 216 ctx.definitions[uextend.Return()] = &backend.SSAValueDefinition{Instr: uextend} 217 ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst} 218 219 return &backend.SSAValueDefinition{Instr: instr, N: 0} 220 }, 221 instructions: []string{ 222 fmt.Sprintf("movabsq $%d, %%r1?", 456+123), 223 }, 224 exp: newOperandMem(newAmodeImmReg(0, regalloc.VReg(1).SetRegType(regalloc.RegTypeInt))), 225 }, 226 { 227 name: "amode with iconst and add", 228 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 229 p := builder.CurrentBlock().AddParam(builder, ssa.TypeI64) 230 iconst := builder.AllocateInstruction().AsIconst64(456).Insert(builder) 231 iadd := builder.AllocateInstruction().AsIadd(iconst.Return(), p).Insert(builder) 232 233 instr := builder.AllocateInstruction() 234 instr.AsLoad(iadd.Return(), 789, ssa.TypeI64).Insert(builder) 235 236 ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg} 237 ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst} 238 ctx.definitions[iadd.Return()] = &backend.SSAValueDefinition{Instr: iadd} 239 240 return &backend.SSAValueDefinition{Instr: instr, N: 0} 241 }, 242 exp: newOperandMem(newAmodeImmReg(456+789, raxVReg)), 243 }, 244 { 245 name: "amode with iconst, block param and add", 246 setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition { 247 iconst1 := builder.AllocateInstruction().AsIconst64(456).Insert(builder) 248 iconst2 := builder.AllocateInstruction().AsIconst64(123).Insert(builder) 249 iadd := builder.AllocateInstruction().AsIadd(iconst1.Return(), iconst2.Return()).Insert(builder) 250 251 instr := builder.AllocateInstruction() 252 instr.AsLoad(iadd.Return(), 789, ssa.TypeI64).Insert(builder) 253 254 ctx.definitions[iconst1.Return()] = &backend.SSAValueDefinition{Instr: iconst1} 255 ctx.definitions[iconst2.Return()] = &backend.SSAValueDefinition{Instr: iconst2} 256 ctx.definitions[iadd.Return()] = &backend.SSAValueDefinition{Instr: iadd} 257 258 return &backend.SSAValueDefinition{Instr: instr, N: 0} 259 }, 260 instructions: []string{ 261 fmt.Sprintf("movabsq $%d, %%r1?", 123+456+789), 262 }, 263 exp: newOperandMem(newAmodeImmReg(0, regalloc.VReg(1).SetRegType(regalloc.RegTypeInt))), 264 }, 265 } { 266 t.Run(tc.name, func(t *testing.T) { 267 ctx, b, m := newSetupWithMockContext() 268 def := tc.setup(ctx, b, m) 269 actual := m.getOperand_Mem_Imm32_Reg(def) 270 if tc.exp.kind == operandKindMem { 271 require.Equal(t, tc.exp.addressMode(), actual.addressMode()) 272 } else { 273 require.Equal(t, tc.exp, actual) 274 } 275 require.Equal(t, strings.Join(tc.instructions, "\n"), formatEmittedInstructionsInCurrentBlock(m)) 276 }) 277 } 278 } 279 280 func TestMachine_lowerExitWithCode(t *testing.T) { 281 _, _, m := newSetupWithMockContext() 282 m.lowerExitWithCode(r15VReg, wazevoapi.ExitCodeUnreachable) 283 m.insert(m.allocateInstr().asUD2()) 284 m.ectx.FlushPendingInstructions() 285 m.ectx.RootInstr = m.ectx.PerBlockHead 286 require.Equal(t, ` 287 mov.q %rsp, 56(%r15) 288 mov.q %rbp, 1152(%r15) 289 movl $3, %ebp 290 mov.l %rbp, (%r15) 291 L1: 292 lea L1, %rbp 293 mov.q %rbp, 48(%r15) 294 exit_sequence %r15 295 L2: 296 ud2 297 `, m.Format()) 298 } 299 300 func Test_machine_lowerClz(t *testing.T) { 301 for _, tc := range []struct { 302 name string 303 setup func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition 304 cpuFlags platform.CpuFeatureFlags 305 typ ssa.Type 306 exp string 307 }{ 308 { 309 name: "no extra flags (64)", 310 cpuFlags: &mockCpuFlags{}, 311 typ: ssa.TypeI64, 312 exp: ` 313 testq %rax, %rax 314 jnz L1 315 movabsq $64, %r1? 316 jmp L2 317 L1: 318 bsrq %rax, %r1? 319 xor $63, %r1? 320 L2: 321 movq %r1?, %rcx 322 `, 323 }, 324 { 325 name: "ABM (64)", 326 cpuFlags: &mockCpuFlags{extraFlags: platform.CpuExtraFeatureAmd64ABM}, 327 typ: ssa.TypeI64, 328 exp: ` 329 lzcntq %rax, %rcx 330 `, 331 }, 332 { 333 name: "no extra flags (32)", 334 cpuFlags: &mockCpuFlags{}, 335 typ: ssa.TypeI32, 336 exp: ` 337 testl %eax, %eax 338 jnz L1 339 movl $32, %r1d? 340 jmp L2 341 L1: 342 bsrl %eax, %r1d? 343 xor $31, %r1d? 344 L2: 345 movq %r1?, %rcx 346 `, 347 }, 348 { 349 name: "ABM (32)", 350 cpuFlags: &mockCpuFlags{extraFlags: platform.CpuExtraFeatureAmd64ABM}, 351 typ: ssa.TypeI32, 352 exp: ` 353 lzcntl %eax, %ecx 354 `, 355 }, 356 } { 357 t.Run(tc.name, func(t *testing.T) { 358 ctx, b, m := newSetupWithMockContext() 359 p := b.CurrentBlock().AddParam(b, tc.typ) 360 m.cpuFeatures = tc.cpuFlags 361 362 ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg} 363 ctx.vRegMap[0] = rcxVReg 364 instr := &ssa.Instruction{} 365 instr.AsClz(p) 366 m.lowerClz(instr) 367 m.ectx.FlushPendingInstructions() 368 m.ectx.RootInstr = m.ectx.PerBlockHead 369 require.Equal(t, tc.exp, m.Format()) 370 }) 371 } 372 } 373 374 func TestMachine_lowerCtz(t *testing.T) { 375 for _, tc := range []struct { 376 name string 377 setup func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition 378 cpuFlags platform.CpuFeatureFlags 379 typ ssa.Type 380 exp string 381 }{ 382 { 383 name: "no extra flags (64)", 384 cpuFlags: &mockCpuFlags{}, 385 typ: ssa.TypeI64, 386 exp: ` 387 testq %rax, %rax 388 jnz L1 389 movabsq $64, %r1? 390 jmp L2 391 L1: 392 bsfq %rax, %r1? 393 L2: 394 movq %r1?, %rcx 395 `, 396 }, 397 { 398 name: "ABM (64)", 399 cpuFlags: &mockCpuFlags{extraFlags: platform.CpuExtraFeatureAmd64ABM}, 400 typ: ssa.TypeI64, 401 exp: ` 402 tzcntq %rax, %rcx 403 `, 404 }, 405 { 406 name: "no extra flags (32)", 407 cpuFlags: &mockCpuFlags{}, 408 typ: ssa.TypeI32, 409 exp: ` 410 testl %eax, %eax 411 jnz L1 412 movl $32, %r1d? 413 jmp L2 414 L1: 415 bsfl %eax, %r1d? 416 L2: 417 movq %r1?, %rcx 418 `, 419 }, 420 { 421 name: "ABM (32)", 422 cpuFlags: &mockCpuFlags{extraFlags: platform.CpuExtraFeatureAmd64ABM}, 423 typ: ssa.TypeI32, 424 exp: ` 425 tzcntl %eax, %ecx 426 `, 427 }, 428 } { 429 t.Run(tc.name, func(t *testing.T) { 430 ctx, b, m := newSetupWithMockContext() 431 p := b.CurrentBlock().AddParam(b, tc.typ) 432 m.cpuFeatures = tc.cpuFlags 433 434 ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg} 435 ctx.vRegMap[0] = rcxVReg 436 instr := &ssa.Instruction{} 437 instr.AsCtz(p) 438 m.lowerCtz(instr) 439 m.ectx.FlushPendingInstructions() 440 m.ectx.RootInstr = m.ectx.PerBlockHead 441 require.Equal(t, tc.exp, m.Format()) 442 }) 443 } 444 } 445 446 // mockCpuFlags implements platform.CpuFeatureFlags 447 type mockCpuFlags struct { 448 flags platform.CpuFeature 449 extraFlags platform.CpuFeature 450 } 451 452 // Has implements the method of the same name in platform.CpuFeatureFlags 453 func (f *mockCpuFlags) Has(flag platform.CpuFeature) bool { 454 return (f.flags & flag) != 0 455 } 456 457 // HasExtra implements the method of the same name in platform.CpuFeatureFlags 458 func (f *mockCpuFlags) HasExtra(flag platform.CpuFeature) bool { 459 return (f.extraFlags & flag) != 0 460 }