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

     1  package arm64
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/bananabytelabs/wazero/internal/engine/wazevo/backend"
     8  	"github.com/bananabytelabs/wazero/internal/engine/wazevo/backend/regalloc"
     9  	"github.com/bananabytelabs/wazero/internal/engine/wazevo/ssa"
    10  	"github.com/bananabytelabs/wazero/internal/testing/require"
    11  )
    12  
    13  func Test_asImm12(t *testing.T) {
    14  	v, shift, ok := asImm12(0xfff)
    15  	require.True(t, ok)
    16  	require.True(t, shift == 0)
    17  	require.Equal(t, uint16(0xfff), v)
    18  
    19  	_, _, ok = asImm12(0xfff << 1)
    20  	require.False(t, ok)
    21  
    22  	v, shift, ok = asImm12(0xabc << 12)
    23  	require.True(t, ok)
    24  	require.True(t, shift == 1)
    25  	require.Equal(t, uint16(0xabc), v)
    26  
    27  	_, _, ok = asImm12(0xabc<<12 | 0b1)
    28  	require.False(t, ok)
    29  }
    30  
    31  func TestMachine_getOperand_NR(t *testing.T) {
    32  	for _, tc := range []struct {
    33  		name         string
    34  		setup        func(*mockCompiler, ssa.Builder, *machine) (def *backend.SSAValueDefinition, mode extMode)
    35  		exp          operand
    36  		instructions []string
    37  	}{
    38  		{
    39  			name: "block param - no extend",
    40  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
    41  				blk := builder.CurrentBlock()
    42  				v := blk.AddParam(builder, ssa.TypeI64)
    43  				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
    44  				return def, extModeZeroExtend64
    45  			},
    46  			exp: operandNR(regToVReg(x4)),
    47  		},
    48  		{
    49  			name: "block param - zero extend",
    50  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
    51  				blk := builder.CurrentBlock()
    52  				v := blk.AddParam(builder, ssa.TypeI32)
    53  				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
    54  				return def, extModeZeroExtend64
    55  			},
    56  			exp:          operandNR(regalloc.VReg(1).SetRegType(regalloc.RegTypeInt)),
    57  			instructions: []string{"uxtw x1?, w4"},
    58  		},
    59  		{
    60  			name: "block param - sign extend",
    61  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
    62  				blk := builder.CurrentBlock()
    63  				v := blk.AddParam(builder, ssa.TypeI32)
    64  				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
    65  				return def, extModeSignExtend64
    66  			},
    67  			exp:          operandNR(regalloc.VReg(1).SetRegType(regalloc.RegTypeInt)),
    68  			instructions: []string{"sxtw x1?, w4"},
    69  		},
    70  		{
    71  			name: "const instr",
    72  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
    73  				instr := builder.AllocateInstruction()
    74  				instr.AsIconst32(0xf00000f)
    75  				builder.InsertInstruction(instr)
    76  				def = &backend.SSAValueDefinition{Instr: instr, N: 0}
    77  				ctx.vRegCounter = 99
    78  				return
    79  			},
    80  			exp: operandNR(regalloc.VReg(100).SetRegType(regalloc.RegTypeInt)),
    81  			instructions: []string{
    82  				"movz w100?, #0xf, lsl 0",
    83  				"movk w100?, #0xf00, lsl 16",
    84  			},
    85  		},
    86  		{
    87  			name: "non const instr",
    88  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
    89  				c := builder.AllocateInstruction()
    90  				sig := &ssa.Signature{Results: []ssa.Type{ssa.TypeI64, ssa.TypeF64, ssa.TypeF64}}
    91  				builder.DeclareSignature(sig)
    92  				c.AsCall(ssa.FuncRef(0), sig, nil)
    93  				builder.InsertInstruction(c)
    94  				_, rs := c.Returns()
    95  				ctx.vRegMap[rs[1]] = regalloc.VReg(50)
    96  				def = &backend.SSAValueDefinition{Instr: c, N: 2}
    97  				return
    98  			},
    99  			exp: operandNR(regalloc.VReg(50)),
   100  		},
   101  	} {
   102  		t.Run(tc.name, func(t *testing.T) {
   103  			ctx, b, m := newSetupWithMockContext()
   104  			def, mode := tc.setup(ctx, b, m)
   105  			actual := m.getOperand_NR(def, mode)
   106  			require.Equal(t, tc.exp, actual)
   107  			require.Equal(t, strings.Join(tc.instructions, "\n"), formatEmittedInstructionsInCurrentBlock(m))
   108  		})
   109  	}
   110  }
   111  
   112  func TestMachine_getOperand_SR_NR(t *testing.T) {
   113  	ishlWithConstAmount := func(
   114  		ctx *mockCompiler, builder ssa.Builder, m *machine,
   115  	) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
   116  		blk := builder.CurrentBlock()
   117  		// (p1+p2) << amount
   118  		p1 := blk.AddParam(builder, ssa.TypeI64)
   119  		p2 := blk.AddParam(builder, ssa.TypeI64)
   120  		add := builder.AllocateInstruction()
   121  		add.AsIadd(p1, p2)
   122  		builder.InsertInstruction(add)
   123  		addResult := add.Return()
   124  
   125  		amount := builder.AllocateInstruction()
   126  		amount.AsIconst32(14)
   127  		builder.InsertInstruction(amount)
   128  
   129  		amountVal := amount.Return()
   130  
   131  		ishl := builder.AllocateInstruction()
   132  		ishl.AsIshl(addResult, amountVal)
   133  		builder.InsertInstruction(ishl)
   134  
   135  		ctx.definitions[p1] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(1), BlockParamValue: p1}
   136  		ctx.definitions[p2] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(2), BlockParamValue: p2}
   137  		ctx.definitions[addResult] = &backend.SSAValueDefinition{Instr: add, N: 0}
   138  		ctx.definitions[amountVal] = &backend.SSAValueDefinition{Instr: amount, N: 0}
   139  
   140  		ctx.vRegMap[addResult] = regalloc.VReg(1234)
   141  		ctx.vRegMap[ishl.Return()] = regalloc.VReg(10)
   142  		def = &backend.SSAValueDefinition{Instr: ishl, N: 0}
   143  		mode = extModeNone
   144  		verify = func(t *testing.T) {
   145  			require.True(t, ishl.Lowered())
   146  			require.True(t, amount.Lowered())
   147  		}
   148  		return
   149  	}
   150  
   151  	for _, tc := range []struct {
   152  		name         string
   153  		setup        func(*mockCompiler, ssa.Builder, *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T))
   154  		exp          operand
   155  		instructions []string
   156  	}{
   157  		{
   158  			name: "block param",
   159  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
   160  				blk := builder.CurrentBlock()
   161  				v := blk.AddParam(builder, ssa.TypeI64)
   162  				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
   163  				return def, extModeNone, func(t *testing.T) {}
   164  			},
   165  			exp: operandNR(regToVReg(x4)),
   166  		},
   167  		{
   168  			name: "ishl but not const amount",
   169  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
   170  				blk := builder.CurrentBlock()
   171  				// (p1+p2) << p3
   172  				p1 := blk.AddParam(builder, ssa.TypeI64)
   173  				p2 := blk.AddParam(builder, ssa.TypeI64)
   174  				p3 := blk.AddParam(builder, ssa.TypeI64)
   175  				add := builder.AllocateInstruction()
   176  				add.AsIadd(p1, p2)
   177  				builder.InsertInstruction(add)
   178  				addResult := add.Return()
   179  
   180  				ishl := builder.AllocateInstruction()
   181  				ishl.AsIshl(addResult, p3)
   182  				builder.InsertInstruction(ishl)
   183  
   184  				ctx.definitions[p1] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(1), BlockParamValue: p1}
   185  				ctx.definitions[p2] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(2), BlockParamValue: p2}
   186  				ctx.definitions[p3] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(3), BlockParamValue: p3}
   187  				ctx.definitions[addResult] = &backend.SSAValueDefinition{Instr: add, N: 0}
   188  				ctx.vRegMap[addResult] = regalloc.VReg(1234) // whatever is fine.
   189  				ctx.vRegMap[ishl.Return()] = regalloc.VReg(10)
   190  				def = &backend.SSAValueDefinition{Instr: ishl, N: 0}
   191  				return def, extModeNone, func(t *testing.T) {}
   192  			},
   193  			exp: operandNR(regalloc.VReg(10)),
   194  		},
   195  		{
   196  			name:  "ishl with const amount",
   197  			setup: ishlWithConstAmount,
   198  			exp:   operandSR(regalloc.VReg(1234), 14, shiftOpLSL),
   199  		},
   200  		{
   201  			name: "ishl with const amount with i32",
   202  			setup: func(
   203  				ctx *mockCompiler, builder ssa.Builder, m *machine,
   204  			) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
   205  				blk := builder.CurrentBlock()
   206  				// (p1+p2) << amount
   207  				p1 := blk.AddParam(builder, ssa.TypeI32)
   208  				p2 := blk.AddParam(builder, ssa.TypeI32)
   209  				add := builder.AllocateInstruction()
   210  				add.AsIadd(p1, p2)
   211  				builder.InsertInstruction(add)
   212  				addResult := add.Return()
   213  
   214  				amount := builder.AllocateInstruction()
   215  				amount.AsIconst32(45) // should be taken modulo by 31.
   216  				builder.InsertInstruction(amount)
   217  
   218  				amountVal := amount.Return()
   219  
   220  				ishl := builder.AllocateInstruction()
   221  				ishl.AsIshl(addResult, amountVal)
   222  				builder.InsertInstruction(ishl)
   223  
   224  				ctx.definitions[p1] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(1), BlockParamValue: p1}
   225  				ctx.definitions[p2] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(2), BlockParamValue: p2}
   226  				ctx.definitions[addResult] = &backend.SSAValueDefinition{Instr: add, N: 0}
   227  				ctx.definitions[amountVal] = &backend.SSAValueDefinition{Instr: amount, N: 0}
   228  
   229  				ctx.vRegMap[addResult] = regalloc.VReg(1234)
   230  				ctx.vRegMap[ishl.Return()] = regalloc.VReg(10)
   231  				def = &backend.SSAValueDefinition{Instr: ishl, N: 0}
   232  				mode = extModeNone
   233  				verify = func(t *testing.T) {
   234  					require.True(t, ishl.Lowered())
   235  					require.True(t, amount.Lowered())
   236  				}
   237  				return
   238  			},
   239  			exp: operandSR(regalloc.VReg(1234), 13, shiftOpLSL),
   240  		},
   241  		{
   242  			name: "ishl with const amount with const shift target",
   243  			setup: func(
   244  				ctx *mockCompiler, builder ssa.Builder, m *machine,
   245  			) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
   246  				const nextVReg = 100
   247  				ctx.vRegCounter = nextVReg - 1
   248  
   249  				target := builder.AllocateInstruction().AsIconst64(0xff).Insert(builder)
   250  				amount := builder.AllocateInstruction().AsIconst32(14).Insert(builder)
   251  				targetVal, amountVal := target.Return(), amount.Return()
   252  
   253  				ishl := builder.AllocateInstruction()
   254  				ishl.AsIshl(target.Return(), amountVal)
   255  				builder.InsertInstruction(ishl)
   256  
   257  				ctx.definitions[targetVal] = &backend.SSAValueDefinition{Instr: target, N: 0}
   258  				ctx.definitions[amountVal] = &backend.SSAValueDefinition{Instr: amount, N: 0}
   259  				ctx.vRegMap[targetVal] = regalloc.VReg(1234)
   260  				ctx.vRegMap[ishl.Return()] = regalloc.VReg(10)
   261  				def = &backend.SSAValueDefinition{Instr: ishl, N: 0}
   262  				mode = extModeNone
   263  				verify = func(t *testing.T) {
   264  					require.True(t, ishl.Lowered())
   265  					require.True(t, amount.Lowered())
   266  				}
   267  				return
   268  			},
   269  			exp:          operandSR(regalloc.VReg(100).SetRegType(regalloc.RegTypeInt), 14, shiftOpLSL),
   270  			instructions: []string{"orr x100?, xzr, #0xff"},
   271  		},
   272  		{
   273  			name: "ishl with const amount but group id is different",
   274  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
   275  				def, mode, _ = ishlWithConstAmount(ctx, builder, m)
   276  				ctx.currentGID = 1230
   277  				return
   278  			},
   279  			exp: operandNR(regalloc.VReg(10)),
   280  		},
   281  		{
   282  			name: "ishl with const amount but ref count is larger than 1",
   283  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
   284  				def, mode, _ = ishlWithConstAmount(ctx, builder, m)
   285  				def.RefCount = 10
   286  				return
   287  			},
   288  			exp: operandNR(regalloc.VReg(10)),
   289  		},
   290  	} {
   291  		t.Run(tc.name, func(t *testing.T) {
   292  			ctx, b, m := newSetupWithMockContext()
   293  			def, mode, verify := tc.setup(ctx, b, m)
   294  			actual := m.getOperand_SR_NR(def, mode)
   295  			require.Equal(t, tc.exp, actual)
   296  			if verify != nil {
   297  				verify(t)
   298  			}
   299  			require.Equal(t, strings.Join(tc.instructions, "\n"), formatEmittedInstructionsInCurrentBlock(m))
   300  		})
   301  	}
   302  }
   303  
   304  func TestMachine_getOperand_ER_SR_NR(t *testing.T) {
   305  	const nextVReg = 100
   306  	type testCase struct {
   307  		setup        func(*mockCompiler, ssa.Builder, *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T))
   308  		exp          operand
   309  		instructions []string
   310  	}
   311  	runner := func(tc testCase) {
   312  		ctx, b, m := newSetupWithMockContext()
   313  		ctx.vRegCounter = nextVReg - 1
   314  		def, mode, verify := tc.setup(ctx, b, m)
   315  		actual := m.getOperand_ER_SR_NR(def, mode)
   316  		require.Equal(t, tc.exp, actual)
   317  		verify(t)
   318  		require.Equal(t, strings.Join(tc.instructions, "\n"), formatEmittedInstructionsInCurrentBlock(m))
   319  	}
   320  
   321  	t.Run("block param", func(t *testing.T) {
   322  		runner(testCase{
   323  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
   324  				blk := builder.CurrentBlock()
   325  				v := blk.AddParam(builder, ssa.TypeI64)
   326  				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
   327  				return def, extModeZeroExtend64, func(t *testing.T) {}
   328  			},
   329  			exp: operandNR(regToVReg(x4)),
   330  		})
   331  	})
   332  
   333  	t.Run("mode none", func(t *testing.T) {
   334  		for _, c := range []struct {
   335  			from, to byte
   336  			signed   bool
   337  			exp      operand
   338  		}{
   339  			{from: 8, to: 32, signed: true, exp: operandER(regalloc.VReg(10), extendOpSXTB, 32)},
   340  			{from: 16, to: 32, signed: true, exp: operandER(regalloc.VReg(10), extendOpSXTH, 32)},
   341  			{from: 8, to: 32, signed: false, exp: operandER(regalloc.VReg(10), extendOpUXTB, 32)},
   342  			{from: 16, to: 32, signed: false, exp: operandER(regalloc.VReg(10), extendOpUXTH, 32)},
   343  			{from: 8, to: 64, signed: true, exp: operandER(regalloc.VReg(10), extendOpSXTB, 64)},
   344  			{from: 16, to: 64, signed: true, exp: operandER(regalloc.VReg(10), extendOpSXTH, 64)},
   345  			{from: 32, to: 64, signed: true, exp: operandER(regalloc.VReg(10), extendOpSXTW, 64)},
   346  			{from: 8, to: 64, signed: false, exp: operandER(regalloc.VReg(10), extendOpUXTB, 64)},
   347  			{from: 16, to: 64, signed: false, exp: operandER(regalloc.VReg(10), extendOpUXTH, 64)},
   348  			{from: 32, to: 64, signed: false, exp: operandER(regalloc.VReg(10), extendOpUXTW, 64)},
   349  		} {
   350  			runner(testCase{
   351  				setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
   352  					blk := builder.CurrentBlock()
   353  					v := blk.AddParam(builder, ssa.TypeI64)
   354  					ext := builder.AllocateInstruction()
   355  					if c.signed {
   356  						ext.AsSExtend(v, c.from, c.to)
   357  					} else {
   358  						ext.AsUExtend(v, c.from, c.to)
   359  					}
   360  					builder.InsertInstruction(ext)
   361  					extArg := ext.Arg()
   362  					ctx.vRegMap[ext.Arg()] = regalloc.VReg(10)
   363  					ctx.definitions[v] = &backend.SSAValueDefinition{BlkParamVReg: regalloc.VReg(10), BlockParamValue: extArg}
   364  					def = &backend.SSAValueDefinition{Instr: ext, N: 0}
   365  					return def, extModeNone, func(t *testing.T) {
   366  						require.True(t, ext.Lowered())
   367  					}
   368  				},
   369  				exp: c.exp,
   370  			})
   371  		}
   372  	})
   373  
   374  	t.Run("valid mode", func(t *testing.T) {
   375  		const argVReg, resultVReg = regalloc.VReg(10), regalloc.VReg(11)
   376  		for _, c := range []struct {
   377  			name         string
   378  			from, to     byte
   379  			signed       bool
   380  			mode         extMode
   381  			exp          operand
   382  			lowered      bool
   383  			extArgConst  bool
   384  			instructions []string
   385  		}{
   386  			{
   387  				name: "8->16->32: signed",
   388  				from: 8, to: 16, signed: true, mode: extModeSignExtend32,
   389  				exp:     operandER(argVReg, extendOpSXTB, 32),
   390  				lowered: true,
   391  			},
   392  			{
   393  				name: "8->16->32: unsigned",
   394  				from: 8, to: 16, signed: false, mode: extModeZeroExtend32,
   395  				exp:     operandER(argVReg, extendOpUXTB, 32),
   396  				lowered: true,
   397  			},
   398  			{
   399  				name: "8->32->64: signed",
   400  				from: 8, to: 32, signed: true, mode: extModeSignExtend64,
   401  				exp:     operandER(argVReg, extendOpSXTB, 64),
   402  				lowered: true,
   403  			},
   404  			{
   405  				name: "16->32->64: signed",
   406  				from: 16, to: 32, signed: true, mode: extModeSignExtend64,
   407  				exp:     operandER(argVReg, extendOpSXTH, 64),
   408  				lowered: true,
   409  			},
   410  			{
   411  				name: "8->32->64: unsigned",
   412  				from: 8, to: 32, signed: false, mode: extModeZeroExtend64,
   413  				exp:     operandER(argVReg, extendOpUXTB, 64),
   414  				lowered: true,
   415  			},
   416  			{
   417  				name: "16->32->64: unsigned",
   418  				from: 16, to: 32, signed: false, mode: extModeZeroExtend64,
   419  				exp:     operandER(argVReg, extendOpUXTH, 64),
   420  				lowered: true,
   421  			},
   422  			{
   423  				name: "8->16->64: signed",
   424  				from: 8, to: 16, signed: true, mode: extModeSignExtend64,
   425  				exp:     operandER(argVReg, extendOpSXTB, 64),
   426  				lowered: true,
   427  			},
   428  			{
   429  				name: "8->16->64: unsigned",
   430  				from: 8, to: 16, signed: false, mode: extModeZeroExtend64,
   431  				exp:     operandER(argVReg, extendOpUXTB, 64),
   432  				lowered: true,
   433  			},
   434  			{
   435  				name: "8(VReg)->64->64: unsigned",
   436  				from: 8, to: 64, signed: false, mode: extModeZeroExtend64,
   437  				exp:     operandER(argVReg, extendOpUXTB, 64),
   438  				lowered: true,
   439  			},
   440  			{
   441  				name: "8(VReg,Const)->64->64: unsigned",
   442  				from: 8, to: 64, signed: false, mode: extModeZeroExtend64,
   443  				exp:          operandER(regalloc.VReg(nextVReg).SetRegType(regalloc.RegTypeInt), extendOpUXTB, 64),
   444  				lowered:      true,
   445  				extArgConst:  true,
   446  				instructions: []string{"movz w100?, #0xffff, lsl 0"},
   447  			},
   448  			{
   449  				name: "16(VReg)->64->64: unsigned",
   450  				from: 16, to: 64, signed: false, mode: extModeZeroExtend64,
   451  				exp:     operandER(argVReg, extendOpUXTH, 64),
   452  				lowered: true,
   453  			},
   454  			{
   455  				name: "16(VReg,Const)->64->64: unsigned",
   456  				from: 16, to: 64, signed: false, mode: extModeZeroExtend64,
   457  				exp:          operandER(regalloc.VReg(nextVReg).SetRegType(regalloc.RegTypeInt), extendOpUXTH, 64),
   458  				lowered:      true,
   459  				extArgConst:  true,
   460  				instructions: []string{"movz w100?, #0xffff, lsl 0"},
   461  			},
   462  			{
   463  				name: "32(VReg)->64->64: unsigned",
   464  				from: 32, to: 64, signed: false, mode: extModeZeroExtend64,
   465  				exp:     operandER(argVReg, extendOpUXTW, 64),
   466  				lowered: true,
   467  			},
   468  			{
   469  				name: "32(VReg,Const)->64->64: unsigned",
   470  				from: 32, to: 64, signed: false, mode: extModeZeroExtend64,
   471  				exp:          operandER(regalloc.VReg(nextVReg).SetRegType(regalloc.RegTypeInt), extendOpUXTW, 64),
   472  				lowered:      true,
   473  				extArgConst:  true,
   474  				instructions: []string{"movz w100?, #0xffff, lsl 0"},
   475  			},
   476  			{
   477  				name: "8(VReg)->64->64: signed",
   478  				from: 8, to: 64, signed: true, mode: extModeZeroExtend64,
   479  				exp:     operandER(argVReg, extendOpSXTB, 64),
   480  				lowered: true,
   481  			},
   482  			{
   483  				name: "8(VReg,Const)->64->64: signed",
   484  				from: 8, to: 64, signed: true, mode: extModeZeroExtend64,
   485  				exp:          operandER(regalloc.VReg(nextVReg).SetRegType(regalloc.RegTypeInt), extendOpSXTB, 64),
   486  				lowered:      true,
   487  				extArgConst:  true,
   488  				instructions: []string{"movz w100?, #0xffff, lsl 0"},
   489  			},
   490  			{
   491  				name: "16(VReg)->64->64: signed",
   492  				from: 16, to: 64, signed: true, mode: extModeZeroExtend64,
   493  				exp:     operandER(argVReg, extendOpSXTH, 64),
   494  				lowered: true,
   495  			},
   496  			{
   497  				name: "16(VReg,Const)->64->64: signed",
   498  				from: 16, to: 64, signed: true, mode: extModeZeroExtend64,
   499  				exp:          operandER(regalloc.VReg(nextVReg).SetRegType(regalloc.RegTypeInt), extendOpSXTH, 64),
   500  				lowered:      true,
   501  				extArgConst:  true,
   502  				instructions: []string{"movz w100?, #0xffff, lsl 0"},
   503  			},
   504  			{
   505  				name: "32(VReg)->64->64: signed",
   506  				from: 32, to: 64, signed: true, mode: extModeZeroExtend64,
   507  				exp:     operandER(argVReg, extendOpSXTW, 64),
   508  				lowered: true,
   509  			},
   510  			{
   511  				name: "32(VReg,Const)->64->64: signed",
   512  				from: 32, to: 64, signed: true, mode: extModeZeroExtend64,
   513  				exp:          operandER(regalloc.VReg(nextVReg).SetRegType(regalloc.RegTypeInt), extendOpSXTW, 64),
   514  				lowered:      true,
   515  				extArgConst:  true,
   516  				instructions: []string{"movz w100?, #0xffff, lsl 0"},
   517  			},
   518  
   519  			// Not lowered cases.
   520  			{
   521  				name: "8-signed->16-zero->64",
   522  				from: 8, to: 16, signed: true, mode: extModeZeroExtend64,
   523  				exp:     operandER(resultVReg, extendOpUXTH, 64),
   524  				lowered: false,
   525  			},
   526  			{
   527  				name: "8-signed->32-zero->64",
   528  				from: 8, to: 32, signed: true, mode: extModeZeroExtend64,
   529  				exp:     operandER(resultVReg, extendOpUXTW, 64),
   530  				lowered: false,
   531  			},
   532  			{
   533  				name: "16-signed->32-zero->64",
   534  				from: 16, to: 32, signed: true, mode: extModeZeroExtend64,
   535  				exp:     operandER(resultVReg, extendOpUXTW, 64),
   536  				lowered: false,
   537  			},
   538  			{
   539  				name: "8-zero->16-signed->64",
   540  				from: 8, to: 16, signed: false, mode: extModeSignExtend64,
   541  				exp:     operandER(resultVReg, extendOpSXTH, 64),
   542  				lowered: false,
   543  			},
   544  			{
   545  				name: "8-zero->32-signed->64",
   546  				from: 8, to: 32, signed: false, mode: extModeSignExtend64,
   547  				exp:     operandER(resultVReg, extendOpSXTW, 64),
   548  				lowered: false,
   549  			},
   550  			{
   551  				name: "16-zero->32-signed->64",
   552  				from: 16, to: 32, signed: false, mode: extModeSignExtend64,
   553  				exp:     operandER(resultVReg, extendOpSXTW, 64),
   554  				lowered: false,
   555  			},
   556  		} {
   557  			t.Run(c.name, func(t *testing.T) {
   558  				runner(testCase{
   559  					setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode, verify func(t *testing.T)) {
   560  						blk := builder.CurrentBlock()
   561  						v := blk.AddParam(builder, ssa.TypeI64)
   562  						ext := builder.AllocateInstruction()
   563  						if c.signed {
   564  							ext.AsSExtend(v, c.from, c.to)
   565  						} else {
   566  							ext.AsUExtend(v, c.from, c.to)
   567  						}
   568  						builder.InsertInstruction(ext)
   569  						extArg := ext.Arg()
   570  						ctx.vRegMap[extArg] = argVReg
   571  						ctx.vRegMap[ext.Return()] = resultVReg
   572  						if c.extArgConst {
   573  							iconst := builder.AllocateInstruction().AsIconst32(0xffff).Insert(builder)
   574  							m.compiler.(*mockCompiler).definitions[extArg] = &backend.SSAValueDefinition{
   575  								Instr: iconst,
   576  							}
   577  						} else {
   578  							m.compiler.(*mockCompiler).definitions[extArg] = &backend.SSAValueDefinition{
   579  								BlkParamVReg:    argVReg,
   580  								BlockParamValue: extArg,
   581  							}
   582  						}
   583  						def = &backend.SSAValueDefinition{Instr: ext, N: 0}
   584  						return def, c.mode, func(t *testing.T) {
   585  							require.Equal(t, c.lowered, ext.Lowered())
   586  						}
   587  					},
   588  					exp:          c.exp,
   589  					instructions: c.instructions,
   590  				})
   591  			})
   592  		}
   593  	})
   594  }
   595  
   596  func TestMachine_getOperand_Imm12_ER_SR_NR(t *testing.T) {
   597  	for _, tc := range []struct {
   598  		name         string
   599  		setup        func(*mockCompiler, ssa.Builder, *machine) (def *backend.SSAValueDefinition, mode extMode)
   600  		exp          operand
   601  		instructions []string
   602  	}{
   603  		{
   604  			name: "block param",
   605  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
   606  				blk := builder.CurrentBlock()
   607  				v := blk.AddParam(builder, ssa.TypeI64)
   608  				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
   609  				return def, extModeZeroExtend64
   610  			},
   611  			exp: operandNR(regToVReg(x4)),
   612  		},
   613  		{
   614  			name: "const imm 12 no shift",
   615  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
   616  				cinst := builder.AllocateInstruction()
   617  				cinst.AsIconst32(0xfff)
   618  				builder.InsertInstruction(cinst)
   619  				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
   620  				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
   621  				return
   622  			},
   623  			exp: operandImm12(0xfff, 0),
   624  		},
   625  		{
   626  			name: "const imm 12 with shift bit",
   627  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
   628  				cinst := builder.AllocateInstruction()
   629  				cinst.AsIconst32(0xabc_000)
   630  				builder.InsertInstruction(cinst)
   631  				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
   632  				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
   633  				return
   634  			},
   635  			exp: operandImm12(0xabc, 1),
   636  		},
   637  	} {
   638  		t.Run(tc.name, func(t *testing.T) {
   639  			ctx, b, m := newSetupWithMockContext()
   640  			def, mode := tc.setup(ctx, b, m)
   641  			actual := m.getOperand_Imm12_ER_SR_NR(def, mode)
   642  			require.Equal(t, tc.exp, actual)
   643  			require.Equal(t, strings.Join(tc.instructions, "\n"), formatEmittedInstructionsInCurrentBlock(m))
   644  		})
   645  	}
   646  }
   647  
   648  func TestMachine_getOperand_MaybeNegatedImm12_ER_SR_NR(t *testing.T) {
   649  	for _, tc := range []struct {
   650  		name         string
   651  		setup        func(*mockCompiler, ssa.Builder, *machine) (def *backend.SSAValueDefinition, mode extMode)
   652  		exp          operand
   653  		expNegated   bool
   654  		instructions []string
   655  	}{
   656  		{
   657  			name: "block param",
   658  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
   659  				blk := builder.CurrentBlock()
   660  				v := blk.AddParam(builder, ssa.TypeI64)
   661  				def = &backend.SSAValueDefinition{BlkParamVReg: regToVReg(x4), BlockParamValue: v}
   662  				return def, extModeZeroExtend64
   663  			},
   664  			exp: operandNR(regToVReg(x4)),
   665  		},
   666  		{
   667  			name: "const imm 12 no shift",
   668  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
   669  				cinst := builder.AllocateInstruction()
   670  				cinst.AsIconst32(0xfff)
   671  				builder.InsertInstruction(cinst)
   672  				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
   673  				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
   674  				return
   675  			},
   676  			exp: operandImm12(0xfff, 0),
   677  		},
   678  		{
   679  			name: "const negative imm 12 no shift",
   680  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
   681  				c := int32(-1)
   682  				cinst := builder.AllocateInstruction()
   683  				cinst.AsIconst32(uint32(c))
   684  				builder.InsertInstruction(cinst)
   685  				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
   686  				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
   687  				return
   688  			},
   689  			exp:        operandImm12(1, 0),
   690  			expNegated: true,
   691  		},
   692  		{
   693  			name: "const imm 12 with shift bit",
   694  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
   695  				cinst := builder.AllocateInstruction()
   696  				cinst.AsIconst32(0xabc_000)
   697  				builder.InsertInstruction(cinst)
   698  				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
   699  				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
   700  				return
   701  			},
   702  			exp: operandImm12(0xabc, 1),
   703  		},
   704  		{
   705  			name: "const negated imm 12 with shift bit",
   706  			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (def *backend.SSAValueDefinition, mode extMode) {
   707  				c := int32(-0xabc_000)
   708  				cinst := builder.AllocateInstruction()
   709  				cinst.AsIconst32(uint32(c))
   710  				builder.InsertInstruction(cinst)
   711  				def = &backend.SSAValueDefinition{Instr: cinst, N: 0}
   712  				ctx.currentGID = 0xff // const can be merged anytime, regardless of the group id.
   713  				return
   714  			},
   715  			exp:        operandImm12(0xabc, 1),
   716  			expNegated: true,
   717  		},
   718  	} {
   719  		t.Run(tc.name, func(t *testing.T) {
   720  			ctx, b, m := newSetupWithMockContext()
   721  			def, mode := tc.setup(ctx, b, m)
   722  			actual, negated := m.getOperand_MaybeNegatedImm12_ER_SR_NR(def, mode)
   723  			require.Equal(t, tc.exp, actual)
   724  			require.Equal(t, tc.expNegated, negated)
   725  			require.Equal(t, strings.Join(tc.instructions, "\n"), formatEmittedInstructionsInCurrentBlock(m))
   726  		})
   727  	}
   728  }