package compiler import ( "fmt" "testing" "github.com/tetratelabs/wazero/internal/asm" "github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/wasm" "github.com/tetratelabs/wazero/internal/wazeroir" ) func Test_compileDropRange(t *testing.T) { t.Run("nop range", func(t *testing.T) { c := newCompiler() err := compileDropRange(c, wazeroir.NopInclusiveRange.AsU64()) require.NoError(t, err) }) t.Run("start at the top", func(t *testing.T) { c := newCompiler() c.Init(&wasm.FunctionType{}, nil, false) // Use up all unreserved registers. for _, reg := range unreservedGeneralPurposeRegisters { c.pushRuntimeValueLocationOnRegister(reg, runtimeValueTypeI32) } for i, vreg := range unreservedVectorRegisters { // Mix and match scalar float and vector values. if i%2 == 0 { c.pushVectorRuntimeValueLocationOnRegister(vreg) } else { c.pushRuntimeValueLocationOnRegister(vreg, runtimeValueTypeF32) } } unreservedRegisterTotal := len(unreservedGeneralPurposeRegisters) + len(unreservedVectorRegisters) ls := c.runtimeValueLocationStack() require.Equal(t, unreservedRegisterTotal, len(ls.usedRegisters.list())) // Drop all the values. err := compileDropRange(c, wazeroir.InclusiveRange{Start: 0, End: int32(ls.sp - 1)}.AsU64()) require.NoError(t, err) // All the registers must be marked unused. require.Equal(t, 0, len(ls.usedRegisters.list())) // Also, stack pointer must be zero. require.Equal(t, 0, int(ls.sp)) }) } func TestRuntimeValueLocationStack_dropsLivesForInclusiveRange(t *testing.T) { tests := []struct { v *runtimeValueLocationStack ir wazeroir.InclusiveRange lives, drops []runtimeValueLocation }{ { v: &runtimeValueLocationStack{ stack: []runtimeValueLocation{{register: 0}, {register: 1} /* drop target */, {register: 2}}, sp: 3, }, ir: wazeroir.InclusiveRange{Start: 1, End: 1}, drops: []runtimeValueLocation{{register: 1}}, lives: []runtimeValueLocation{{register: 2}}, }, { v: &runtimeValueLocationStack{ stack: []runtimeValueLocation{ {register: 0}, {register: 1}, {register: 2}, // drop target {register: 3}, // drop target {register: 4}, // drop target {register: 5}, {register: 6}, }, sp: 7, }, ir: wazeroir.InclusiveRange{Start: 2, End: 4}, drops: []runtimeValueLocation{{register: 2}, {register: 3}, {register: 4}}, lives: []runtimeValueLocation{{register: 5}, {register: 6}}, }, } for _, tc := range tests { actualDrops, actualLives := tc.v.dropsLivesForInclusiveRange(tc.ir) require.Equal(t, tc.drops, actualDrops) require.Equal(t, tc.lives, actualLives) } } func Test_getTemporariesForStackedLiveValues(t *testing.T) { t.Run("no stacked values", func(t *testing.T) { liveValues := []runtimeValueLocation{{register: 1}, {register: 2}} c := newCompiler() c.Init(&wasm.FunctionType{}, nil, false) gpTmp, vecTmp, err := getTemporariesForStackedLiveValues(c, liveValues) require.NoError(t, err) require.Equal(t, asm.NilRegister, gpTmp) require.Equal(t, asm.NilRegister, vecTmp) }) t.Run("general purpose needed", func(t *testing.T) { for _, freeRegisterExists := range []bool{false, true} { freeRegisterExists := freeRegisterExists t.Run(fmt.Sprintf("free register exists=%v", freeRegisterExists), func(t *testing.T) { liveValues := []runtimeValueLocation{ // Even multiple integer values are alive and on stack, // only one general purpose register should be chosen. {valueType: runtimeValueTypeI32}, {valueType: runtimeValueTypeI64}, } c := newCompiler() c.Init(&wasm.FunctionType{}, nil, false) if !freeRegisterExists { // Use up all the unreserved gp registers. for _, reg := range unreservedGeneralPurposeRegisters { c.pushRuntimeValueLocationOnRegister(reg, runtimeValueTypeI32) } // Ensures actually we used them up all. require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()), len(unreservedGeneralPurposeRegisters)) } gpTmp, vecTmp, err := getTemporariesForStackedLiveValues(c, liveValues) require.NoError(t, err) if !freeRegisterExists { // At this point, one register should be marked as unused. require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()), len(unreservedGeneralPurposeRegisters)-1) } require.NotEqual(t, asm.NilRegister, gpTmp) require.Equal(t, asm.NilRegister, vecTmp) }) } }) t.Run("vector needed", func(t *testing.T) { for _, freeRegisterExists := range []bool{false, true} { freeRegisterExists := freeRegisterExists t.Run(fmt.Sprintf("free register exists=%v", freeRegisterExists), func(t *testing.T) { liveValues := []runtimeValueLocation{ // Even multiple vectors are alive and on stack, // only one vector register should be chosen. {valueType: runtimeValueTypeF32}, {valueType: runtimeValueTypeV128Lo}, {valueType: runtimeValueTypeV128Hi}, {valueType: runtimeValueTypeV128Lo}, {valueType: runtimeValueTypeV128Hi}, } c := newCompiler() c.Init(&wasm.FunctionType{}, nil, false) if !freeRegisterExists { // Use up all the unreserved gp registers. for _, reg := range unreservedVectorRegisters { c.pushVectorRuntimeValueLocationOnRegister(reg) } // Ensures actually we used them up all. require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()), len(unreservedVectorRegisters)) } gpTmp, vecTmp, err := getTemporariesForStackedLiveValues(c, liveValues) require.NoError(t, err) if !freeRegisterExists { // At this point, one register should be marked as unused. require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()), len(unreservedVectorRegisters)-1) } require.Equal(t, asm.NilRegister, gpTmp) require.NotEqual(t, asm.NilRegister, vecTmp) }) } }) } func Test_migrateLiveValue(t *testing.T) { t.Run("v128.hi", func(t *testing.T) { migrateLiveValue(nil, &runtimeValueLocation{valueType: runtimeValueTypeV128Hi}, asm.NilRegister, asm.NilRegister) }) t.Run("already on register", func(t *testing.T) { // This case, we don't use tmp registers. c := newCompiler() c.Init(&wasm.FunctionType{}, nil, false) // Push the dummy values. for i := 0; i < 10; i++ { _ = c.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() } gpReg := unreservedGeneralPurposeRegisters[0] vReg := unreservedVectorRegisters[0] c.pushRuntimeValueLocationOnRegister(gpReg, runtimeValueTypeI64) c.pushVectorRuntimeValueLocationOnRegister(vReg) // Emulate the compileDrop ls := c.runtimeValueLocationStack() vLive, gpLive := ls.popV128(), ls.pop() const dropNum = 5 ls.sp -= dropNum // Migrate these two values. migrateLiveValue(c, gpLive, asm.NilRegister, asm.NilRegister) migrateLiveValue(c, vLive, asm.NilRegister, asm.NilRegister) // Check the new stack location. vectorMigrated, gpMigrated := ls.popV128(), ls.pop() require.Equal(t, uint64(5), gpMigrated.stackPointer) require.Equal(t, uint64(6), vectorMigrated.stackPointer) require.Equal(t, gpLive.register, gpMigrated.register) require.Equal(t, vLive.register, vectorMigrated.register) }) }