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  }