github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/compiler/compiler_global_test.go (about)

     1  package compiler
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/wasilibs/wazerox/internal/asm"
     7  	"github.com/wasilibs/wazerox/internal/testing/require"
     8  	"github.com/wasilibs/wazerox/internal/wasm"
     9  	"github.com/wasilibs/wazerox/internal/wazeroir"
    10  )
    11  
    12  func TestCompiler_compileGlobalGet(t *testing.T) {
    13  	const globalValue uint64 = 12345
    14  	for _, tp := range []wasm.ValueType{
    15  		wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueTypeI32, wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref,
    16  	} {
    17  		tp := tp
    18  		t.Run(wasm.ValueTypeName(tp), func(t *testing.T) {
    19  			env := newCompilerEnvironment()
    20  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
    21  				Globals: []wasm.GlobalType{{}, {ValType: tp}},
    22  			})
    23  
    24  			// Setup the global. (start with nil as a dummy so that global index can be non-trivial.)
    25  			globals := []*wasm.GlobalInstance{nil, {Val: globalValue, Type: wasm.GlobalType{ValType: tp}}}
    26  			env.addGlobals(globals...)
    27  
    28  			// Emit the code.
    29  			err := compiler.compilePreamble()
    30  			require.NoError(t, err)
    31  			op := operationPtr(wazeroir.NewOperationGlobalGet(1))
    32  			err = compiler.compileGlobalGet(op)
    33  			require.NoError(t, err)
    34  
    35  			// At this point, the top of stack must be the retrieved global on a register.
    36  			global := compiler.runtimeValueLocationStack().peek()
    37  			require.True(t, global.onRegister())
    38  			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
    39  			switch tp {
    40  			case wasm.ValueTypeF32, wasm.ValueTypeF64:
    41  				require.True(t, isVectorRegister(global.register))
    42  			case wasm.ValueTypeI32, wasm.ValueTypeI64:
    43  				require.True(t, isGeneralPurposeRegister(global.register))
    44  			}
    45  			err = compiler.compileReturnFunction()
    46  			require.NoError(t, err)
    47  
    48  			code := asm.CodeSegment{}
    49  			defer func() { require.NoError(t, code.Unmap()) }()
    50  
    51  			// Generate the code under test.
    52  			_, err = compiler.compile(code.NextCodeSection())
    53  			require.NoError(t, err)
    54  
    55  			// Run the code assembled above.
    56  			env.exec(code.Bytes())
    57  
    58  			// Since we call global.get, the top of the stack must be the global value.
    59  			require.Equal(t, globalValue, env.stackTopAsUint64())
    60  			// Plus as we push the value, the stack pointer must be incremented.
    61  			require.Equal(t, uint64(1), env.stackPointer())
    62  		})
    63  	}
    64  }
    65  
    66  func TestCompiler_compileGlobalGet_v128(t *testing.T) {
    67  	const v128Type = wasm.ValueTypeV128
    68  	env := newCompilerEnvironment()
    69  	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
    70  		Globals: []wasm.GlobalType{{}, {ValType: v128Type}},
    71  	})
    72  
    73  	// Setup the global. (start with nil as a dummy so that global index can be non-trivial.)
    74  	globals := []*wasm.GlobalInstance{nil, {Val: 12345, ValHi: 6789, Type: wasm.GlobalType{ValType: v128Type}}}
    75  	env.addGlobals(globals...)
    76  
    77  	// Emit the code.
    78  	err := compiler.compilePreamble()
    79  	require.NoError(t, err)
    80  	op := operationPtr(wazeroir.NewOperationGlobalGet(1))
    81  	err = compiler.compileGlobalGet(op)
    82  	require.NoError(t, err)
    83  
    84  	// At this point, the top of stack must be the retrieved global on a register.
    85  	global := compiler.runtimeValueLocationStack().peek()
    86  	require.True(t, global.onRegister())
    87  	require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
    88  	require.True(t, isVectorRegister(global.register))
    89  	err = compiler.compileReturnFunction()
    90  	require.NoError(t, err)
    91  
    92  	code := asm.CodeSegment{}
    93  	defer func() { require.NoError(t, code.Unmap()) }()
    94  
    95  	// Generate the code under test.
    96  	_, err = compiler.compile(code.NextCodeSection())
    97  	require.NoError(t, err)
    98  
    99  	// Run the code assembled above.
   100  	env.exec(code.Bytes())
   101  
   102  	require.Equal(t, uint64(2), env.stackPointer())
   103  	require.Equal(t, nativeCallStatusCodeReturned, env.callEngine().statusCode)
   104  
   105  	// Since we call global.get, the top of the stack must be the global value.
   106  	actual := globals[1]
   107  	sp := env.ce.stackContext.stackPointer
   108  	stack := env.stack()
   109  	require.Equal(t, actual.Val, stack[sp-2])
   110  	require.Equal(t, actual.ValHi, stack[sp-1])
   111  }
   112  
   113  func TestCompiler_compileGlobalSet(t *testing.T) {
   114  	const valueToSet uint64 = 12345
   115  	for _, tp := range []wasm.ValueType{
   116  		wasm.ValueTypeF32, wasm.ValueTypeF64,
   117  		wasm.ValueTypeI32, wasm.ValueTypeI64,
   118  		wasm.ValueTypeExternref, wasm.ValueTypeFuncref,
   119  	} {
   120  		tp := tp
   121  		t.Run(wasm.ValueTypeName(tp), func(t *testing.T) {
   122  			env := newCompilerEnvironment()
   123  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   124  				Globals: []wasm.GlobalType{{}, {ValType: tp}},
   125  			})
   126  
   127  			// Setup the global. (start with nil as a dummy so that global index can be non-trivial.)
   128  			env.addGlobals(nil, &wasm.GlobalInstance{Val: 40, Type: wasm.GlobalType{ValType: tp}})
   129  
   130  			err := compiler.compilePreamble()
   131  			require.NoError(t, err)
   132  
   133  			// Place the set target value.
   134  			loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   135  			switch tp {
   136  			case wasm.ValueTypeI32:
   137  				loc.valueType = runtimeValueTypeI32
   138  			case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
   139  				loc.valueType = runtimeValueTypeI64
   140  			case wasm.ValueTypeF32:
   141  				loc.valueType = runtimeValueTypeF32
   142  			case wasm.ValueTypeF64:
   143  				loc.valueType = runtimeValueTypeF64
   144  			}
   145  			env.stack()[loc.stackPointer] = valueToSet
   146  
   147  			const index = 1
   148  			op := operationPtr(wazeroir.NewOperationGlobalSet(index))
   149  			err = compiler.compileGlobalSet(op)
   150  			requireRuntimeLocationStackPointerEqual(t, 0, compiler)
   151  
   152  			require.NoError(t, err)
   153  
   154  			err = compiler.compileReturnFunction()
   155  			require.NoError(t, err)
   156  
   157  			code := asm.CodeSegment{}
   158  			defer func() { require.NoError(t, code.Unmap()) }()
   159  
   160  			// Generate the code under test.
   161  			_, err = compiler.compile(code.NextCodeSection())
   162  			require.NoError(t, err)
   163  			env.exec(code.Bytes())
   164  
   165  			// The global value should be set to valueToSet.
   166  			actual := env.globals()[index]
   167  			require.Equal(t, valueToSet, actual.Val)
   168  			// Plus we consumed the top of the stack, the stack pointer must be decremented.
   169  			require.Equal(t, uint64(0), env.stackPointer())
   170  		})
   171  	}
   172  }
   173  
   174  func TestCompiler_compileGlobalSet_v128(t *testing.T) {
   175  	const v128Type = wasm.ValueTypeV128
   176  	const valueToSetLo, valueToSetHi uint64 = 0xffffff, 1
   177  
   178  	env := newCompilerEnvironment()
   179  	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   180  		Globals: []wasm.GlobalType{{}, {ValType: v128Type}},
   181  	})
   182  
   183  	// Setup the global. (start with nil as a dummy so that global index can be non-trivial.)
   184  	env.addGlobals(nil, &wasm.GlobalInstance{Val: 0, ValHi: 0, Type: wasm.GlobalType{ValType: v128Type}})
   185  
   186  	err := compiler.compilePreamble()
   187  	require.NoError(t, err)
   188  
   189  	// Place the set target value.
   190  	lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   191  	lo.valueType = runtimeValueTypeV128Lo
   192  	env.stack()[lo.stackPointer] = valueToSetLo
   193  	hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   194  	hi.valueType = runtimeValueTypeV128Hi
   195  	env.stack()[hi.stackPointer] = valueToSetHi
   196  
   197  	const index = 1
   198  	op := operationPtr(wazeroir.NewOperationGlobalSet(index))
   199  	err = compiler.compileGlobalSet(op)
   200  	requireRuntimeLocationStackPointerEqual(t, 0, compiler)
   201  	require.NoError(t, err)
   202  
   203  	err = compiler.compileReturnFunction()
   204  	require.NoError(t, err)
   205  
   206  	code := asm.CodeSegment{}
   207  	defer func() { require.NoError(t, code.Unmap()) }()
   208  
   209  	// Generate the code under test.
   210  	_, err = compiler.compile(code.NextCodeSection())
   211  	require.NoError(t, err)
   212  	env.exec(code.Bytes())
   213  
   214  	require.Equal(t, uint64(0), env.stackPointer())
   215  	require.Equal(t, nativeCallStatusCodeReturned, env.callEngine().statusCode)
   216  
   217  	// The global value should be set to valueToSet.
   218  	actual := env.globals()[index]
   219  	require.Equal(t, valueToSetLo, actual.Val)
   220  	require.Equal(t, valueToSetHi, actual.ValHi)
   221  }