github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/engine/wazevo/backend/isa/arm64/lower_mem_test.go (about)

     1  package arm64
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"strconv"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/bananabytelabs/wazero/internal/engine/wazevo/backend"
    11  	"github.com/bananabytelabs/wazero/internal/engine/wazevo/backend/regalloc"
    12  	"github.com/bananabytelabs/wazero/internal/engine/wazevo/ssa"
    13  	"github.com/bananabytelabs/wazero/internal/testing/require"
    14  )
    15  
    16  func TestAddressMode_format(t *testing.T) {
    17  	t.Run("addressModeKindRegScaledExtended", func(t *testing.T) {
    18  		require.Equal(t,
    19  			"[x1, w0, UXTW #0x1]",
    20  			addressMode{
    21  				kind:  addressModeKindRegScaledExtended,
    22  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
    23  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
    24  				extOp: extendOpUXTW,
    25  				imm:   0,
    26  			}.format(16),
    27  		)
    28  		require.Equal(t,
    29  			"[x1, w0, SXTW #0x1]",
    30  			addressMode{
    31  				kind:  addressModeKindRegScaledExtended,
    32  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
    33  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
    34  				extOp: extendOpSXTW,
    35  				imm:   0,
    36  			}.format(16),
    37  		)
    38  		require.Equal(t,
    39  			"[x1, w0, UXTW #0x2]",
    40  			addressMode{
    41  				kind:  addressModeKindRegScaledExtended,
    42  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
    43  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
    44  				extOp: extendOpUXTW,
    45  				imm:   0,
    46  			}.format(32),
    47  		)
    48  		require.Equal(t,
    49  			"[x1, w0, SXTW #0x2]",
    50  			addressMode{
    51  				kind:  addressModeKindRegScaledExtended,
    52  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
    53  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
    54  				extOp: extendOpSXTW,
    55  				imm:   0,
    56  			}.format(32),
    57  		)
    58  		require.Equal(t,
    59  			"[x1, w0, UXTW #0x3]",
    60  			addressMode{
    61  				kind:  addressModeKindRegScaledExtended,
    62  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
    63  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
    64  				extOp: extendOpUXTW,
    65  				imm:   0,
    66  			}.format(64),
    67  		)
    68  		require.Equal(t,
    69  			"[x1, w0, SXTW #0x3]",
    70  			addressMode{
    71  				kind:  addressModeKindRegScaledExtended,
    72  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
    73  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
    74  				extOp: extendOpSXTW,
    75  				imm:   0,
    76  			}.format(64),
    77  		)
    78  	})
    79  	t.Run("addressModeKindRegScaled", func(t *testing.T) {
    80  		require.Equal(t,
    81  			"[x1, w0, lsl #0x1]",
    82  			addressMode{
    83  				kind:  addressModeKindRegScaled,
    84  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
    85  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
    86  				extOp: extendOpUXTW,
    87  				imm:   0,
    88  			}.format(16),
    89  		)
    90  		require.Equal(t,
    91  			"[x1, w0, lsl #0x1]",
    92  			addressMode{
    93  				kind:  addressModeKindRegScaled,
    94  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
    95  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
    96  				extOp: extendOpSXTW,
    97  				imm:   0,
    98  			}.format(16),
    99  		)
   100  		require.Equal(t,
   101  			"[x1, w0, lsl #0x2]",
   102  			addressMode{
   103  				kind:  addressModeKindRegScaled,
   104  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   105  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
   106  				extOp: extendOpUXTW,
   107  				imm:   0,
   108  			}.format(32),
   109  		)
   110  		require.Equal(t,
   111  			"[x1, w0, lsl #0x2]",
   112  			addressMode{
   113  				kind:  addressModeKindRegScaled,
   114  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   115  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
   116  				extOp: extendOpSXTW,
   117  				imm:   0,
   118  			}.format(32),
   119  		)
   120  		require.Equal(t,
   121  			"[x1, w0, lsl #0x3]",
   122  			addressMode{
   123  				kind:  addressModeKindRegScaled,
   124  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   125  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
   126  				extOp: extendOpUXTW,
   127  				imm:   0,
   128  			}.format(64),
   129  		)
   130  		require.Equal(t,
   131  			"[x1, w0, lsl #0x3]",
   132  			addressMode{
   133  				kind:  addressModeKindRegScaled,
   134  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   135  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
   136  				extOp: extendOpSXTW,
   137  				imm:   0,
   138  			}.format(64),
   139  		)
   140  	})
   141  	t.Run("addressModeKindRegExtended", func(t *testing.T) {
   142  		require.Equal(t,
   143  			"[x1, w0, UXTW]",
   144  			addressMode{
   145  				kind:  addressModeKindRegExtended,
   146  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   147  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
   148  				extOp: extendOpUXTW,
   149  				imm:   0,
   150  			}.format(16),
   151  		)
   152  		require.Equal(t,
   153  			"[x1, w0, SXTW]",
   154  			addressMode{
   155  				kind:  addressModeKindRegExtended,
   156  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   157  				rm:    regalloc.FromRealReg(x0, regalloc.RegTypeInt),
   158  				extOp: extendOpSXTW,
   159  				imm:   0,
   160  			}.format(64),
   161  		)
   162  	})
   163  	t.Run("addressModeKindRegReg", func(t *testing.T) {
   164  		require.Equal(t,
   165  			"[x1, w29]",
   166  			addressMode{
   167  				kind:  addressModeKindRegReg,
   168  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   169  				rm:    regalloc.FromRealReg(x29, regalloc.RegTypeInt),
   170  				extOp: extendOpUXTW, // To indicate that the index reg is 32-bit.
   171  				imm:   0,
   172  			}.format(64),
   173  		)
   174  		require.Equal(t,
   175  			"[x1, x29]",
   176  			addressMode{
   177  				kind:  addressModeKindRegReg,
   178  				rn:    regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   179  				rm:    regalloc.FromRealReg(x29, regalloc.RegTypeInt),
   180  				extOp: extendOpUXTX, // To indicate that the index reg is 64-bit.
   181  				imm:   0,
   182  			}.format(64),
   183  		)
   184  	})
   185  	t.Run("addressModeKindRegSignedImm9", func(t *testing.T) {
   186  		require.Equal(t,
   187  			"[x1]",
   188  			addressMode{
   189  				kind: addressModeKindRegSignedImm9,
   190  				rn:   regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   191  				imm:  0,
   192  			}.format(64),
   193  		)
   194  		require.Equal(t,
   195  			"[x1, #-0x100]",
   196  			addressMode{
   197  				kind: addressModeKindRegSignedImm9,
   198  				rn:   regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   199  				imm:  math.MinInt8 << 1,
   200  			}.format(64),
   201  		)
   202  		require.Equal(t,
   203  			"[x1, #0xff]",
   204  			addressMode{
   205  				kind: addressModeKindRegSignedImm9,
   206  				rn:   regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   207  				imm:  (math.MaxInt8 << 1) + 1,
   208  			}.format(64),
   209  		)
   210  	})
   211  
   212  	t.Run("addressModeKindRegUnsignedImm12", func(t *testing.T) {
   213  		require.Equal(t,
   214  			"[x1]",
   215  			addressMode{
   216  				kind: addressModeKindRegUnsignedImm12,
   217  				rn:   regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   218  				imm:  0,
   219  			}.format(64),
   220  		)
   221  		require.Equal(t,
   222  			"[x1, #0xfff]",
   223  			addressMode{
   224  				kind: addressModeKindRegUnsignedImm12,
   225  				rn:   regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   226  				imm:  4095,
   227  			}.format(64),
   228  		)
   229  	})
   230  }
   231  
   232  func Test_offsetFitsInAddressModeKindRegUnsignedImm12(t *testing.T) {
   233  	for _, tc := range []struct {
   234  		dstSizeInBits byte
   235  		offset        int64
   236  		exp           bool
   237  	}{
   238  		{dstSizeInBits: 8, offset: -1, exp: false},
   239  		{dstSizeInBits: 8, offset: 0, exp: false},
   240  		{dstSizeInBits: 8, offset: 1, exp: true},
   241  		{dstSizeInBits: 8, offset: 2, exp: true},
   242  		{dstSizeInBits: 8, offset: 3, exp: true},
   243  		{dstSizeInBits: 8, offset: 4095, exp: true},
   244  		{dstSizeInBits: 8, offset: 4096, exp: false},
   245  		{dstSizeInBits: 16, offset: -2, exp: false},
   246  		{dstSizeInBits: 16, offset: -1, exp: false},
   247  		{dstSizeInBits: 16, offset: 0, exp: false},
   248  		{dstSizeInBits: 16, offset: 1, exp: false},
   249  		{dstSizeInBits: 16, offset: 2, exp: true},
   250  		{dstSizeInBits: 16, offset: 3, exp: false},
   251  		{dstSizeInBits: 16, offset: 4095, exp: false},
   252  		{dstSizeInBits: 16, offset: 4096, exp: true},
   253  		{dstSizeInBits: 16, offset: 4095 * 2, exp: true},
   254  		{dstSizeInBits: 16, offset: 4095*2 + 1, exp: false},
   255  		{dstSizeInBits: 32, offset: -4, exp: false},
   256  		{dstSizeInBits: 32, offset: -1, exp: false},
   257  		{dstSizeInBits: 32, offset: 0, exp: false},
   258  		{dstSizeInBits: 32, offset: 1, exp: false},
   259  		{dstSizeInBits: 32, offset: 2, exp: false},
   260  		{dstSizeInBits: 32, offset: 3, exp: false},
   261  		{dstSizeInBits: 32, offset: 4, exp: true},
   262  		{dstSizeInBits: 32, offset: 4095, exp: false},
   263  		{dstSizeInBits: 32, offset: 4096, exp: true},
   264  		{dstSizeInBits: 32, offset: 4095 * 4, exp: true},
   265  		{dstSizeInBits: 32, offset: 4095*4 + 1, exp: false},
   266  		{dstSizeInBits: 64, offset: -8, exp: false},
   267  		{dstSizeInBits: 64, offset: -1, exp: false},
   268  		{dstSizeInBits: 64, offset: 0, exp: false},
   269  		{dstSizeInBits: 64, offset: 1, exp: false},
   270  		{dstSizeInBits: 64, offset: 2, exp: false},
   271  		{dstSizeInBits: 64, offset: 3, exp: false},
   272  		{dstSizeInBits: 64, offset: 4, exp: false},
   273  		{dstSizeInBits: 64, offset: 8, exp: true},
   274  		{dstSizeInBits: 64, offset: 4095, exp: false},
   275  		{dstSizeInBits: 64, offset: 4096, exp: true},
   276  		{dstSizeInBits: 64, offset: 4095 * 8, exp: true},
   277  		{dstSizeInBits: 64, offset: 4095*8 + 1, exp: false},
   278  	} {
   279  		require.Equal(
   280  			t, tc.exp,
   281  			offsetFitsInAddressModeKindRegUnsignedImm12(tc.dstSizeInBits, tc.offset),
   282  			fmt.Sprintf("dstSizeInBits=%d, offset=%d", tc.dstSizeInBits, tc.offset),
   283  		)
   284  	}
   285  }
   286  
   287  func Test_offsetFitsInAddressModeKindRegSignedImm9(t *testing.T) {
   288  	require.Equal(t, true, offsetFitsInAddressModeKindRegSignedImm9(0))
   289  	require.Equal(t, false, offsetFitsInAddressModeKindRegSignedImm9(-257))
   290  	require.Equal(t, true, offsetFitsInAddressModeKindRegSignedImm9(-256))
   291  	require.Equal(t, true, offsetFitsInAddressModeKindRegSignedImm9(255))
   292  	require.Equal(t, false, offsetFitsInAddressModeKindRegSignedImm9(256))
   293  }
   294  
   295  func TestMachine_collectAddends(t *testing.T) {
   296  	v1000, v2000 := regalloc.VReg(1000).SetRegType(regalloc.RegTypeInt), regalloc.VReg(2000).SetRegType(regalloc.RegTypeInt)
   297  	addParam := func(ctx *mockCompiler, b ssa.Builder, typ ssa.Type) ssa.Value {
   298  		p := b.CurrentBlock().AddParam(b, typ)
   299  		ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: v1000}
   300  		return p
   301  	}
   302  	insertI32Const := func(m *mockCompiler, b ssa.Builder, v uint32) *ssa.Instruction {
   303  		inst := b.AllocateInstruction()
   304  		inst.AsIconst32(v)
   305  		b.InsertInstruction(inst)
   306  		m.definitions[inst.Return()] = &backend.SSAValueDefinition{Instr: inst}
   307  		return inst
   308  	}
   309  	insertI64Const := func(m *mockCompiler, b ssa.Builder, v uint64) *ssa.Instruction {
   310  		inst := b.AllocateInstruction()
   311  		inst.AsIconst64(v)
   312  		b.InsertInstruction(inst)
   313  		m.definitions[inst.Return()] = &backend.SSAValueDefinition{Instr: inst}
   314  		return inst
   315  	}
   316  	insertIadd := func(m *mockCompiler, b ssa.Builder, lhs, rhs ssa.Value) *ssa.Instruction {
   317  		inst := b.AllocateInstruction()
   318  		inst.AsIadd(lhs, rhs)
   319  		b.InsertInstruction(inst)
   320  		m.definitions[inst.Return()] = &backend.SSAValueDefinition{Instr: inst}
   321  		return inst
   322  	}
   323  	insertExt := func(m *mockCompiler, b ssa.Builder, v ssa.Value, from, to byte, signed bool) *ssa.Instruction {
   324  		inst := b.AllocateInstruction()
   325  		if signed {
   326  			inst.AsSExtend(v, from, to)
   327  		} else {
   328  			inst.AsUExtend(v, from, to)
   329  		}
   330  		b.InsertInstruction(inst)
   331  		m.definitions[inst.Return()] = &backend.SSAValueDefinition{Instr: inst}
   332  		return inst
   333  	}
   334  
   335  	for _, tc := range []struct {
   336  		name   string
   337  		setup  func(*mockCompiler, ssa.Builder, *machine) (ptr ssa.Value, verify func(t *testing.T))
   338  		exp32s []addend32
   339  		exp64s []regalloc.VReg
   340  		offset int64
   341  	}{
   342  		{
   343  			name: "non merged",
   344  			setup: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, verify func(t *testing.T)) {
   345  				ptr = addParam(ctx, b, ssa.TypeI64)
   346  				return ptr, func(t *testing.T) {}
   347  			},
   348  			exp64s: []regalloc.VReg{v1000},
   349  		},
   350  		{
   351  			name: "i32 constant folded",
   352  			setup: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, verify func(t *testing.T)) {
   353  				minus1 := int32(-1)
   354  				c1, c2, c3, c4 := insertI32Const(ctx, b, 1), insertI32Const(ctx, b, 2), insertI32Const(ctx, b, 3), insertI32Const(ctx, b, uint32(minus1))
   355  				iadd1, iadd2 := insertIadd(ctx, b, c1.Return(), c2.Return()), insertIadd(ctx, b, c3.Return(), c4.Return())
   356  				iadd3 := insertIadd(ctx, b, iadd1.Return(), iadd2.Return())
   357  				return iadd3.Return(), func(t *testing.T) {
   358  					for _, instr := range []*ssa.Instruction{iadd1, iadd2, iadd3} {
   359  						require.True(t, instr.Lowered())
   360  					}
   361  				}
   362  			},
   363  			offset: 1 + 2 + 3 - 1,
   364  		},
   365  		{
   366  			name: "i64 constant folded",
   367  			setup: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, verify func(t *testing.T)) {
   368  				minus1 := int32(-1)
   369  				c1, c2, c3, c4 := insertI64Const(ctx, b, 1), insertI64Const(ctx, b, 2), insertI64Const(ctx, b, 3), insertI64Const(ctx, b, uint64(minus1))
   370  				iadd1, iadd2 := insertIadd(ctx, b, c1.Return(), c2.Return()), insertIadd(ctx, b, c3.Return(), c4.Return())
   371  				iadd3 := insertIadd(ctx, b, iadd1.Return(), iadd2.Return())
   372  				return iadd3.Return(), func(t *testing.T) {
   373  					for _, instr := range []*ssa.Instruction{iadd1, iadd2, iadd3} {
   374  						require.True(t, instr.Lowered())
   375  					}
   376  				}
   377  			},
   378  			offset: 1 + 2 + 3 - 1,
   379  		},
   380  		{
   381  			name: "constant folded with one 32 value",
   382  			setup: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, verify func(t *testing.T)) {
   383  				param := addParam(ctx, b, ssa.TypeI32)
   384  				minus1 := int32(-1)
   385  				c1, c2, c3, c4 := insertI32Const(ctx, b, 1), insertI32Const(ctx, b, 2), insertI32Const(ctx, b, 3), insertI32Const(ctx, b, uint32(minus1))
   386  				iadd1, iadd2 := insertIadd(ctx, b, c1.Return(), c2.Return()), insertIadd(ctx, b, c3.Return(), c4.Return())
   387  				iadd3 := insertIadd(ctx, b, iadd1.Return(), iadd2.Return())
   388  				iadd4 := insertIadd(ctx, b, param, iadd3.Return())
   389  
   390  				return iadd4.Return(), func(t *testing.T) {
   391  					for _, instr := range []*ssa.Instruction{iadd1, iadd2, iadd3, iadd4} {
   392  						require.True(t, instr.Lowered())
   393  					}
   394  					// Param must be zero-extended.
   395  					require.Equal(t, "uxtw x1?, w1000?", formatEmittedInstructionsInCurrentBlock(m))
   396  				}
   397  			},
   398  			exp64s: []regalloc.VReg{regalloc.VReg(1).SetRegType(regalloc.RegTypeInt)},
   399  			offset: 1 + 2 + 3 - 1,
   400  		},
   401  		{
   402  			name: "one 64 value + sign-extended (32->64) instr",
   403  			setup: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, verify func(t *testing.T)) {
   404  				param := addParam(ctx, b, ssa.TypeI64)
   405  				c1, c2 := insertI32Const(ctx, b, 1), insertI32Const(ctx, b, 2)
   406  				iadd1 := insertIadd(ctx, b, c1.Return(), c2.Return())
   407  				ext := insertExt(ctx, b, iadd1.Return(), 32, 64, true)
   408  				ctx.vRegMap[ext.Arg()] = v2000
   409  				iadd4 := insertIadd(ctx, b, param, ext.Return())
   410  				return iadd4.Return(), func(t *testing.T) {
   411  					for _, instr := range []*ssa.Instruction{ext, iadd4} {
   412  						require.True(t, instr.Lowered())
   413  					}
   414  				}
   415  			},
   416  			exp64s: []regalloc.VReg{v1000 /* == param */},
   417  			exp32s: []addend32{{v2000, extendOpSXTW} /* sign-extended iadd1 */},
   418  		},
   419  		{
   420  			name: "one 64 value + sign-extended (32->64) const",
   421  			setup: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, verify func(t *testing.T)) {
   422  				param := addParam(ctx, b, ssa.TypeI64)
   423  				minus1 := int32(-1)
   424  				c1 := insertI32Const(ctx, b, uint32(minus1))
   425  				ext := insertExt(ctx, b, c1.Return(), 32, 64, true)
   426  				ctx.vRegMap[ext.Arg()] = v2000
   427  				iadd4 := insertIadd(ctx, b, param, ext.Return())
   428  				return iadd4.Return(), func(t *testing.T) {
   429  					for _, instr := range []*ssa.Instruction{ext, iadd4} {
   430  						require.True(t, instr.Lowered())
   431  					}
   432  				}
   433  			},
   434  			exp64s: []regalloc.VReg{v1000 /* == param */},
   435  			offset: -1,
   436  		},
   437  		{
   438  			name: "one 64 value + zero-extended (32->64) const",
   439  			setup: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, verify func(t *testing.T)) {
   440  				param := addParam(ctx, b, ssa.TypeI64)
   441  				minus1 := int32(-1)
   442  				c1 := insertI32Const(ctx, b, uint32(minus1))
   443  				ext := insertExt(ctx, b, c1.Return(), 32, 64, false)
   444  				ctx.vRegMap[ext.Arg()] = v2000
   445  				iadd4 := insertIadd(ctx, b, param, ext.Return())
   446  				return iadd4.Return(), func(t *testing.T) {
   447  					for _, instr := range []*ssa.Instruction{ext, iadd4} {
   448  						require.True(t, instr.Lowered())
   449  					}
   450  				}
   451  			},
   452  			exp64s: []regalloc.VReg{v1000 /* == param */},
   453  			offset: math.MaxUint32, // zero-extended -1
   454  		},
   455  		{
   456  			name: "one 64 value + redundant extension",
   457  			setup: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, verify func(t *testing.T)) {
   458  				param := addParam(ctx, b, ssa.TypeI64)
   459  				ext := insertExt(ctx, b, param, 64, 64, true)
   460  				ctx.vRegMap[ext.Arg()] = v2000
   461  				iadd4 := insertIadd(ctx, b, param, ext.Return())
   462  				return iadd4.Return(), func(t *testing.T) {
   463  					for _, instr := range []*ssa.Instruction{ext, iadd4} {
   464  						require.True(t, instr.Lowered())
   465  					}
   466  				}
   467  			},
   468  			exp64s: []regalloc.VReg{v1000, v1000},
   469  		},
   470  	} {
   471  		tc := tc
   472  		t.Run(tc.name, func(t *testing.T) {
   473  			ctx, b, m := newSetupWithMockContext()
   474  			ptr, verify := tc.setup(ctx, b, m)
   475  			actual32sQ, actual64sQ, actualOffset := m.collectAddends(ptr)
   476  			require.Equal(t, tc.exp32s, actual32sQ.data)
   477  			require.Equal(t, tc.exp64s, actual64sQ.data)
   478  			require.Equal(t, tc.offset, actualOffset)
   479  			verify(t)
   480  		})
   481  	}
   482  }
   483  
   484  func TestMachine_addConstToReg64(t *testing.T) {
   485  	const nextVRegID = 100
   486  	t.Run("positive imm12", func(t *testing.T) {
   487  		c := int64(0xaaa)
   488  		ctx, _, m := newSetupWithMockContext()
   489  		ctx.vRegCounter = nextVRegID - 1
   490  		m.addConstToReg64(regalloc.FromRealReg(x15, regalloc.RegTypeInt), c)
   491  		require.Equal(t, `add x100?, x15, #0xaaa`, formatEmittedInstructionsInCurrentBlock(m))
   492  	})
   493  	t.Run("positive imm12", func(t *testing.T) {
   494  		c := int64(-0xaaa)
   495  		ctx, _, m := newSetupWithMockContext()
   496  		ctx.vRegCounter = nextVRegID - 1
   497  		m.addConstToReg64(regalloc.FromRealReg(x15, regalloc.RegTypeInt), c)
   498  		require.Equal(t, `sub x100?, x15, #0xaaa`, formatEmittedInstructionsInCurrentBlock(m))
   499  	})
   500  	t.Run("non imm12", func(t *testing.T) {
   501  		c := int64(1<<32 | 1)
   502  		ctx, _, m := newSetupWithMockContext()
   503  		ctx.vRegCounter = nextVRegID - 1
   504  		m.addConstToReg64(regalloc.FromRealReg(x15, regalloc.RegTypeInt), c)
   505  		require.Equal(t, `movz x101?, #0x1, lsl 0
   506  movk x101?, #0x1, lsl 32
   507  add x100?, x15, x101?`, formatEmittedInstructionsInCurrentBlock(m))
   508  	})
   509  }
   510  
   511  func TestMachine_addReg64ToReg64(t *testing.T) {
   512  	const nextVRegID = 100
   513  	for _, tc := range []struct {
   514  		exp    string
   515  		rn, rm regalloc.VReg
   516  	}{
   517  		{
   518  			exp: "add x100?, x0, x1",
   519  			rn:  regalloc.FromRealReg(x0, regalloc.RegTypeInt),
   520  			rm:  regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   521  		},
   522  		{
   523  			exp: "add x100?, x10, x12",
   524  			rn:  regalloc.FromRealReg(x10, regalloc.RegTypeInt),
   525  			rm:  regalloc.FromRealReg(x12, regalloc.RegTypeInt),
   526  		},
   527  	} {
   528  		tc := tc
   529  		t.Run(tc.exp, func(t *testing.T) {
   530  			ctx, _, m := newSetupWithMockContext()
   531  			ctx.vRegCounter = nextVRegID - 1
   532  			rd := m.addReg64ToReg64(tc.rn, tc.rm)
   533  			require.Equal(t, tc.exp, formatEmittedInstructionsInCurrentBlock(m))
   534  			require.Equal(t, rd, regalloc.VReg(nextVRegID).SetRegType(regalloc.RegTypeInt))
   535  		})
   536  	}
   537  }
   538  
   539  func TestMachine_addRegToReg64Ext(t *testing.T) {
   540  	const nextVRegID = 100
   541  	for _, tc := range []struct {
   542  		exp    string
   543  		rn, rm regalloc.VReg
   544  		ext    extendOp
   545  	}{
   546  		{
   547  			exp: "add x100?, x0, w1 UXTW",
   548  			rn:  regalloc.FromRealReg(x0, regalloc.RegTypeInt),
   549  			rm:  regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   550  			ext: extendOpUXTW,
   551  		},
   552  		{
   553  			exp: "add x100?, x0, w1 SXTW",
   554  			rn:  regalloc.FromRealReg(x0, regalloc.RegTypeInt),
   555  			rm:  regalloc.FromRealReg(x1, regalloc.RegTypeInt),
   556  			ext: extendOpSXTW,
   557  		},
   558  	} {
   559  		tc := tc
   560  		t.Run(tc.exp, func(t *testing.T) {
   561  			ctx, _, m := newSetupWithMockContext()
   562  			ctx.vRegCounter = nextVRegID - 1
   563  			rd := m.addRegToReg64Ext(tc.rn, tc.rm, tc.ext)
   564  			require.Equal(t, tc.exp, formatEmittedInstructionsInCurrentBlock(m))
   565  			require.Equal(t, rd, regalloc.VReg(nextVRegID).SetRegType(regalloc.RegTypeInt))
   566  		})
   567  	}
   568  }
   569  
   570  func TestMachine_lowerToAddressModeFromAddends(t *testing.T) {
   571  	x1, x2, x3 := regalloc.FromRealReg(x1, regalloc.RegTypeInt), regalloc.FromRealReg(x2, regalloc.RegTypeInt), regalloc.FromRealReg(x3, regalloc.RegTypeInt)
   572  	x4, x5, x6 := regalloc.FromRealReg(x4, regalloc.RegTypeInt), regalloc.FromRealReg(x5, regalloc.RegTypeInt), regalloc.FromRealReg(x6, regalloc.RegTypeInt)
   573  
   574  	nextVReg, nextNextVeg := regalloc.VReg(100).SetRegType(regalloc.RegTypeInt), regalloc.VReg(101).SetRegType(regalloc.RegTypeInt)
   575  	for _, tc := range []struct {
   576  		name          string
   577  		a32s          []addend32
   578  		a64s          []regalloc.VReg
   579  		dstSizeInBits byte
   580  		offset        int64
   581  		exp           addressMode
   582  		insts         []string
   583  	}{
   584  		{
   585  			name:   "only offset",
   586  			offset: 4095,
   587  			insts:  []string{"orr x100?, xzr, #0xfff"},
   588  			exp:    addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextVReg, imm: 0},
   589  		},
   590  		{
   591  			name:   "only offset",
   592  			offset: 4095 << 12,
   593  			insts:  []string{"orr x100?, xzr, #0xfff000"},
   594  			exp:    addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextVReg, imm: 0},
   595  		},
   596  		{
   597  			name:          "one a64 with imm12",
   598  			a64s:          []regalloc.VReg{x1},
   599  			offset:        4095,
   600  			dstSizeInBits: 8,
   601  			exp:           addressMode{kind: addressModeKindRegUnsignedImm12, rn: x1, imm: 4095},
   602  		},
   603  		{
   604  			name:          "one a64 with imm12",
   605  			a64s:          []regalloc.VReg{x1},
   606  			offset:        4095 * 2,
   607  			dstSizeInBits: 16,
   608  			exp:           addressMode{kind: addressModeKindRegUnsignedImm12, rn: x1, imm: 4095 * 2},
   609  		},
   610  		{
   611  			name:          "one a64 with imm12",
   612  			a64s:          []regalloc.VReg{x1},
   613  			offset:        4095 * 4,
   614  			dstSizeInBits: 32,
   615  			exp:           addressMode{kind: addressModeKindRegUnsignedImm12, rn: x1, imm: 4095 * 4},
   616  		},
   617  		{
   618  			name:          "one a64 with imm12",
   619  			a64s:          []regalloc.VReg{x1},
   620  			offset:        4095 * 8,
   621  			dstSizeInBits: 64,
   622  			exp:           addressMode{kind: addressModeKindRegUnsignedImm12, rn: x1, imm: 4095 * 8},
   623  		},
   624  		{
   625  			name:          "one a64 with imm9",
   626  			a64s:          []regalloc.VReg{x1},
   627  			dstSizeInBits: 64,
   628  			exp:           addressMode{kind: addressModeKindRegSignedImm9, rn: x1, imm: 0},
   629  		},
   630  		{
   631  			name:          "one a64 with imm9",
   632  			a64s:          []regalloc.VReg{x1},
   633  			offset:        -256,
   634  			dstSizeInBits: 64,
   635  			exp:           addressMode{kind: addressModeKindRegSignedImm9, rn: x1, imm: -256},
   636  		},
   637  		{
   638  			name:          "one a64 with offset not fitting",
   639  			a64s:          []regalloc.VReg{x1},
   640  			offset:        1 << 16,
   641  			dstSizeInBits: 64,
   642  			insts:         []string{"add x100?, x1, #0x10000"},
   643  			exp:           addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextVReg, imm: 0},
   644  		},
   645  		{
   646  			name:          "two a64 with imm12",
   647  			a64s:          []regalloc.VReg{x1, x2},
   648  			offset:        4095,
   649  			dstSizeInBits: 8,
   650  			insts:         []string{"add x100?, x1, x2"},
   651  			exp:           addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextVReg, imm: 4095},
   652  		},
   653  		{
   654  			name:          "two a64 with imm12",
   655  			a64s:          []regalloc.VReg{x1, x2},
   656  			offset:        4095 * 2,
   657  			dstSizeInBits: 16,
   658  			insts:         []string{"add x100?, x1, x2"},
   659  			exp:           addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextVReg, imm: 4095 * 2},
   660  		},
   661  		{
   662  			name:          "two a64 with imm12",
   663  			a64s:          []regalloc.VReg{x1, x2},
   664  			offset:        4095 * 4,
   665  			dstSizeInBits: 32,
   666  			insts:         []string{"add x100?, x1, x2"},
   667  			exp:           addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextVReg, imm: 4095 * 4},
   668  		},
   669  		{
   670  			name:          "two a64 with imm12",
   671  			a64s:          []regalloc.VReg{x1, x2},
   672  			offset:        4095 * 8,
   673  			dstSizeInBits: 64,
   674  			insts:         []string{"add x100?, x1, x2"},
   675  			exp:           addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextVReg, imm: 4095 * 8},
   676  		},
   677  		{
   678  			name:          "two a64 with imm9",
   679  			a64s:          []regalloc.VReg{x1, x2},
   680  			dstSizeInBits: 64,
   681  			insts:         []string{"add x100?, x1, x2"},
   682  			exp:           addressMode{kind: addressModeKindRegSignedImm9, rn: nextVReg, imm: 0},
   683  		},
   684  		{
   685  			name:          "two a64 with imm9",
   686  			a64s:          []regalloc.VReg{x1, x2},
   687  			offset:        -256,
   688  			dstSizeInBits: 64,
   689  			insts:         []string{"add x100?, x1, x2"},
   690  			exp:           addressMode{kind: addressModeKindRegSignedImm9, rn: nextVReg, imm: -256},
   691  		},
   692  		{
   693  			name:          "two a64 with offset not fitting",
   694  			a64s:          []regalloc.VReg{x1, x2},
   695  			offset:        1 << 16,
   696  			dstSizeInBits: 64,
   697  			insts:         []string{"add x100?, x1, #0x10000"},
   698  			exp:           addressMode{kind: addressModeKindRegReg, rn: nextVReg, rm: x2, extOp: extendOpUXTX},
   699  		},
   700  		{
   701  			name:          "three a64 with imm12",
   702  			a64s:          []regalloc.VReg{x1, x2, x3},
   703  			offset:        4095 * 2,
   704  			dstSizeInBits: 16,
   705  			insts: []string{
   706  				"add x100?, x1, x2",
   707  				"add x101?, x100?, x3",
   708  			},
   709  			exp: addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextNextVeg, imm: 4095 * 2},
   710  		},
   711  		{
   712  			name:          "three a64 with imm12",
   713  			a64s:          []regalloc.VReg{x1, x2, x3},
   714  			offset:        4095 * 4,
   715  			dstSizeInBits: 32,
   716  			insts: []string{
   717  				"add x100?, x1, x2",
   718  				"add x101?, x100?, x3",
   719  			},
   720  			exp: addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextNextVeg, imm: 4095 * 4},
   721  		},
   722  		{
   723  			name:          "three a64 with imm12",
   724  			a64s:          []regalloc.VReg{x1, x2, x3},
   725  			offset:        4095 * 8,
   726  			dstSizeInBits: 64,
   727  			insts: []string{
   728  				"add x100?, x1, x2",
   729  				"add x101?, x100?, x3",
   730  			},
   731  			exp: addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextNextVeg, imm: 4095 * 8},
   732  		},
   733  		{
   734  			name:          "three a64 with imm9",
   735  			a64s:          []regalloc.VReg{x1, x2, x3},
   736  			dstSizeInBits: 64,
   737  			insts: []string{
   738  				"add x100?, x1, x2",
   739  				"add x101?, x100?, x3",
   740  			},
   741  			exp: addressMode{kind: addressModeKindRegSignedImm9, rn: nextNextVeg, imm: 0},
   742  		},
   743  		{
   744  			name:          "three a64 with imm9",
   745  			a64s:          []regalloc.VReg{x1, x2, x3},
   746  			offset:        -256,
   747  			dstSizeInBits: 64,
   748  			insts: []string{
   749  				"add x100?, x1, x2",
   750  				"add x101?, x100?, x3",
   751  			},
   752  			exp: addressMode{kind: addressModeKindRegSignedImm9, rn: nextNextVeg, imm: -256},
   753  		},
   754  		{
   755  			name:          "three a64 with offset not fitting",
   756  			a64s:          []regalloc.VReg{x1, x2, x3},
   757  			offset:        1 << 16,
   758  			dstSizeInBits: 64,
   759  			insts: []string{
   760  				"add x100?, x1, #0x10000",
   761  				"add x101?, x100?, x3",
   762  			},
   763  			exp: addressMode{kind: addressModeKindRegReg, rn: nextNextVeg, rm: x2, extOp: extendOpUXTX},
   764  		},
   765  		{
   766  			name:          "three a32/a64 with offset",
   767  			a64s:          []regalloc.VReg{x1, x2, x3},
   768  			a32s:          []addend32{{r: x4, ext: extendOpSXTW}, {r: x5, ext: extendOpUXTW}, {r: x6, ext: extendOpSXTW}},
   769  			offset:        1 << 16,
   770  			dstSizeInBits: 64,
   771  			insts: []string{
   772  				"add x100?, x1, #0x10000",
   773  				"add x101?, x100?, x2",
   774  				"add x102?, x101?, x3",
   775  				"add x103?, x102?, w5 UXTW",
   776  				"add x104?, x103?, w6 SXTW",
   777  			},
   778  			exp: addressMode{
   779  				kind: addressModeKindRegExtended,
   780  				rn:   regalloc.VReg(104).SetRegType(regalloc.RegTypeInt),
   781  				rm:   x4, extOp: extendOpSXTW,
   782  			},
   783  		},
   784  		{
   785  			name:   "one a32 with offset",
   786  			a32s:   []addend32{{r: x1, ext: extendOpSXTW}},
   787  			offset: 1 << 16,
   788  			insts: []string{
   789  				"sxtw x100?, w1",
   790  				"add x101?, x100?, #0x10000",
   791  			},
   792  			exp: addressMode{kind: addressModeKindRegUnsignedImm12, rn: nextNextVeg, imm: 0},
   793  		},
   794  		{
   795  			name:   "two a32s with offset",
   796  			a32s:   []addend32{{r: x1, ext: extendOpSXTW}, {r: x2, ext: extendOpUXTW}},
   797  			offset: 1 << 16,
   798  			insts: []string{
   799  				"sxtw x100?, w1",
   800  				"add x101?, x100?, #0x10000",
   801  			},
   802  			exp: addressMode{
   803  				kind:  addressModeKindRegExtended,
   804  				rn:    regalloc.VReg(101).SetRegType(regalloc.RegTypeInt),
   805  				rm:    x2,
   806  				imm:   0,
   807  				extOp: extendOpUXTW,
   808  			},
   809  		},
   810  	} {
   811  		t.Run(tc.name, func(t *testing.T) {
   812  			ctx, _, m := newSetupWithMockContext()
   813  			ctx.vRegCounter = int(nextVReg.ID()) - 1
   814  
   815  			var a32s queue[addend32]
   816  			var a64s queue[regalloc.VReg]
   817  			for _, a32 := range tc.a32s {
   818  				a32s.enqueue(a32)
   819  			}
   820  			for _, a64 := range tc.a64s {
   821  				a64s.enqueue(a64)
   822  			}
   823  			actual := m.lowerToAddressModeFromAddends(&a32s, &a64s, tc.dstSizeInBits, tc.offset)
   824  			require.Equal(t, strings.Join(tc.insts, "\n"), formatEmittedInstructionsInCurrentBlock(m))
   825  			require.Equal(t, tc.exp, actual, actual.format(tc.dstSizeInBits))
   826  		})
   827  	}
   828  }
   829  
   830  func Test_extLoadSizeSign(t *testing.T) {
   831  	for _, tc := range []struct {
   832  		op      ssa.Opcode
   833  		expSize byte
   834  		signed  bool
   835  	}{
   836  		{op: ssa.OpcodeUload8, expSize: 8, signed: false},
   837  		{op: ssa.OpcodeUload16, expSize: 16, signed: false},
   838  		{op: ssa.OpcodeUload32, expSize: 32, signed: false},
   839  		{op: ssa.OpcodeSload8, expSize: 8, signed: true},
   840  		{op: ssa.OpcodeSload16, expSize: 16, signed: true},
   841  		{op: ssa.OpcodeSload32, expSize: 32, signed: true},
   842  	} {
   843  		size, signed := extLoadSignSize(tc.op)
   844  		require.Equal(t, tc.expSize, size)
   845  		require.Equal(t, tc.signed, signed)
   846  	}
   847  }
   848  
   849  func Test_lowerLoadSplatFromAddressMode(t *testing.T) {
   850  	positiveTests := make(map[addressModeKind]bool)
   851  	nextVReg := regalloc.VReg(100).SetRegType(regalloc.RegTypeInt)
   852  
   853  	for _, tc := range []struct {
   854  		amode       addressMode
   855  		expected    string
   856  		expectPanic bool
   857  	}{
   858  		{
   859  			amode: addressMode{kind: addressModeKindRegReg, rn: x0VReg, rm: x1VReg},
   860  			expected: `
   861  add x100?, x0, x1
   862  ld1r {x10.4s}, [x100?]
   863  `,
   864  		},
   865  		{
   866  			amode: addressMode{kind: addressModeKindRegUnsignedImm12, rn: x0VReg, imm: 15616},
   867  			expected: `
   868  movz x101?, #0x3d00, lsl 0
   869  add x100?, x0, x101?
   870  ld1r {x10.4s}, [x100?]
   871  `,
   872  		},
   873  		{
   874  			amode: addressMode{kind: addressModeKindRegUnsignedImm12, rn: x15VReg, imm: 0},
   875  			expected: `
   876  ld1r {x10.4s}, [x15]
   877  `,
   878  		},
   879  		{
   880  			amode: addressMode{kind: addressModeKindRegSignedImm9, rn: x0VReg, imm: 42},
   881  			expected: `
   882  add x100?, x0, #0x2a
   883  ld1r {x10.4s}, [x100?]
   884  `,
   885  		},
   886  	} {
   887  		tc := tc
   888  		t.Run("address mode "+strconv.Itoa(int(tc.amode.kind)), func(t *testing.T) {
   889  			ctx, _, m := newSetupWithMockContext()
   890  			ctx.vRegCounter = int(nextVReg.ID()) - 1
   891  			positiveTests[tc.amode.kind] = true
   892  
   893  			m.lowerLoadSplatFromAddressMode(operandNR(x10VReg), tc.amode, ssa.VecLaneI32x4)
   894  			require.Equal(t, tc.expected, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
   895  		})
   896  	}
   897  
   898  	// Must panic for all other addressModeKinds.
   899  	for k := 0; k <= int(addressModeKindResultStackSpace); k++ {
   900  		amk := addressModeKind(k)
   901  		if positiveTests[amk] {
   902  			continue
   903  		}
   904  
   905  		ctx, _, m := newSetupWithMockContext()
   906  		ctx.vRegCounter = int(nextVReg.ID()) - 1
   907  
   908  		t.Run("address mode "+strconv.Itoa(k), func(t *testing.T) {
   909  			err := require.CapturePanic(func() {
   910  				m.lowerLoadSplatFromAddressMode(operandNR(x10VReg), addressMode{kind: amk}, ssa.VecLaneI32x4)
   911  			})
   912  			require.Contains(t, err.Error(), "unsupported address mode for LoadSplat")
   913  		})
   914  
   915  	}
   916  }