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

     1  package compiler
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"unsafe"
     7  
     8  	"github.com/wasilibs/wazerox/internal/asm"
     9  	"github.com/wasilibs/wazerox/internal/testing/require"
    10  	"github.com/wasilibs/wazerox/internal/wasm"
    11  	"github.com/wasilibs/wazerox/internal/wazeroir"
    12  )
    13  
    14  func TestCompiler_compileHostFunction(t *testing.T) {
    15  	env := newCompilerEnvironment()
    16  	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
    17  
    18  	err := compiler.compileGoDefinedHostFunction()
    19  	require.NoError(t, err)
    20  
    21  	// Get the location of caller function's location stored in the stack, which depends on the type.
    22  	// In this test, the host function has empty sig.
    23  	_, _, callerFuncLoc := compiler.runtimeValueLocationStack().getCallFrameLocations(&wasm.FunctionType{})
    24  
    25  	code := asm.CodeSegment{}
    26  	defer func() { require.NoError(t, code.Unmap()) }()
    27  
    28  	// Generate the machine code for the test.
    29  	_, err = compiler.compile(code.NextCodeSection())
    30  	require.NoError(t, err)
    31  
    32  	// Set the caller's function which always exists in the real usecase.
    33  	f := &function{moduleInstance: &wasm.ModuleInstance{}}
    34  	env.stack()[callerFuncLoc.stackPointer] = uint64(uintptr(unsafe.Pointer(f)))
    35  	env.exec(code.Bytes())
    36  
    37  	// On the return, the code must exit with the host call status.
    38  	require.Equal(t, nativeCallStatusCodeCallGoHostFunction, env.compilerStatus())
    39  	// Plus, the exitContext holds the caller's wasm.FunctionInstance.
    40  	require.Equal(t, f.moduleInstance, env.ce.exitContext.callerModuleInstance)
    41  
    42  	// Re-enter the return address.
    43  	require.NotEqual(t, uintptr(0), uintptr(env.ce.returnAddress))
    44  	nativecall(env.ce.returnAddress, env.callEngine(), env.module())
    45  
    46  	// After that, the code must exit with returned status.
    47  	require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
    48  }
    49  
    50  func TestCompiler_compileLabel(t *testing.T) {
    51  	label := wazeroir.NewLabel(wazeroir.LabelKindContinuation, 100)
    52  	for _, expectSkip := range []bool{false, true} {
    53  		expectSkip := expectSkip
    54  		t.Run(fmt.Sprintf("expect skip=%v", expectSkip), func(t *testing.T) {
    55  			env := newCompilerEnvironment()
    56  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
    57  
    58  			if expectSkip {
    59  				// If the initial stack is not set, compileLabel must return skip=true.
    60  				actual := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(label)))
    61  				require.True(t, actual)
    62  			} else {
    63  				err := compiler.compileBr(operationPtr(wazeroir.NewOperationBr(label)))
    64  				require.NoError(t, err)
    65  				actual := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(label)))
    66  				require.False(t, actual)
    67  			}
    68  		})
    69  	}
    70  }
    71  
    72  func TestCompiler_compileBrIf(t *testing.T) {
    73  	unreachableStatus, thenLabelExitStatus, elseLabelExitStatus := nativeCallStatusCodeUnreachable, nativeCallStatusCodeUnreachable+1, nativeCallStatusCodeUnreachable+2
    74  	thenBranchTarget := wazeroir.NewLabel(wazeroir.LabelKindHeader, 1)
    75  	elseBranchTarget := wazeroir.NewLabel(wazeroir.LabelKindHeader, 2)
    76  
    77  	tests := []struct {
    78  		name      string
    79  		setupFunc func(t *testing.T, compiler compilerImpl, shouldGoElse bool)
    80  	}{
    81  		{
    82  			name: "cond on register",
    83  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
    84  				val := uint32(1)
    85  				if shouldGoElse {
    86  					val = 0
    87  				}
    88  				err := compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(val)))
    89  				require.NoError(t, err)
    90  			},
    91  		},
    92  		{
    93  			name: "LS",
    94  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
    95  				x1, x2 := uint32(1), uint32(2)
    96  				if shouldGoElse {
    97  					x2, x1 = x1, x2
    98  				}
    99  				requirePushTwoInt32Consts(t, x1, x2, compiler)
   100  				// Le on unsigned integer produces the value on COND_LS register.
   101  				err := compiler.compileLe(operationPtr(wazeroir.NewOperationLe(wazeroir.SignedTypeUint32)))
   102  				require.NoError(t, err)
   103  			},
   104  		},
   105  		{
   106  			name: "LE",
   107  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
   108  				x1, x2 := uint32(1), uint32(2)
   109  				if shouldGoElse {
   110  					x2, x1 = x1, x2
   111  				}
   112  				requirePushTwoInt32Consts(t, x1, x2, compiler)
   113  				// Le on signed integer produces the value on COND_LE register.
   114  				err := compiler.compileLe(operationPtr(wazeroir.NewOperationLe(wazeroir.SignedTypeInt32)))
   115  				require.NoError(t, err)
   116  			},
   117  		},
   118  		{
   119  			name: "HS",
   120  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
   121  				x1, x2 := uint32(2), uint32(1)
   122  				if shouldGoElse {
   123  					x2, x1 = x1, x2
   124  				}
   125  				requirePushTwoInt32Consts(t, x1, x2, compiler)
   126  				// Ge on unsigned integer produces the value on COND_HS register.
   127  				err := compiler.compileGe(operationPtr(wazeroir.NewOperationGe(wazeroir.SignedTypeUint32)))
   128  				require.NoError(t, err)
   129  			},
   130  		},
   131  		{
   132  			name: "GE",
   133  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
   134  				x1, x2 := uint32(2), uint32(1)
   135  				if shouldGoElse {
   136  					x2, x1 = x1, x2
   137  				}
   138  				requirePushTwoInt32Consts(t, x1, x2, compiler)
   139  				// Ge on signed integer produces the value on COND_GE register.
   140  				err := compiler.compileGe(operationPtr(wazeroir.NewOperationGe(wazeroir.SignedTypeInt32)))
   141  				require.NoError(t, err)
   142  			},
   143  		},
   144  		{
   145  			name: "HI",
   146  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
   147  				x1, x2 := uint32(2), uint32(1)
   148  				if shouldGoElse {
   149  					x2, x1 = x1, x2
   150  				}
   151  				requirePushTwoInt32Consts(t, x1, x2, compiler)
   152  				// Gt on unsigned integer produces the value on COND_HI register.
   153  				err := compiler.compileGt(operationPtr(wazeroir.NewOperationGt(wazeroir.SignedTypeUint32)))
   154  				require.NoError(t, err)
   155  			},
   156  		},
   157  		{
   158  			name: "GT",
   159  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
   160  				x1, x2 := uint32(2), uint32(1)
   161  				if shouldGoElse {
   162  					x2, x1 = x1, x2
   163  				}
   164  				requirePushTwoInt32Consts(t, x1, x2, compiler)
   165  				// Gt on signed integer produces the value on COND_GT register.
   166  				err := compiler.compileGt(operationPtr(wazeroir.NewOperationGt(wazeroir.SignedTypeInt32)))
   167  				require.NoError(t, err)
   168  			},
   169  		},
   170  		{
   171  			name: "LO",
   172  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
   173  				x1, x2 := uint32(1), uint32(2)
   174  				if shouldGoElse {
   175  					x2, x1 = x1, x2
   176  				}
   177  				requirePushTwoInt32Consts(t, x1, x2, compiler)
   178  				// Lt on unsigned integer produces the value on COND_LO register.
   179  				err := compiler.compileLt(operationPtr(wazeroir.NewOperationLt(wazeroir.SignedTypeUint32)))
   180  				require.NoError(t, err)
   181  			},
   182  		},
   183  		{
   184  			name: "LT",
   185  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
   186  				x1, x2 := uint32(1), uint32(2)
   187  				if shouldGoElse {
   188  					x2, x1 = x1, x2
   189  				}
   190  				requirePushTwoInt32Consts(t, x1, x2, compiler)
   191  				// Lt on signed integer produces the value on COND_LT register.
   192  				err := compiler.compileLt(operationPtr(wazeroir.NewOperationLt(wazeroir.SignedTypeInt32)))
   193  				require.NoError(t, err)
   194  			},
   195  		},
   196  		{
   197  			name: "MI",
   198  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
   199  				x1, x2 := float32(1), float32(2)
   200  				if shouldGoElse {
   201  					x2, x1 = x1, x2
   202  				}
   203  				requirePushTwoFloat32Consts(t, x1, x2, compiler)
   204  				// Lt on floats produces the value on COND_MI register.
   205  				err := compiler.compileLt(operationPtr(wazeroir.NewOperationLt(wazeroir.SignedTypeFloat32)))
   206  				require.NoError(t, err)
   207  			},
   208  		},
   209  		{
   210  			name: "EQ",
   211  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
   212  				x1, x2 := uint32(1), uint32(1)
   213  				if shouldGoElse {
   214  					x2++
   215  				}
   216  				requirePushTwoInt32Consts(t, x1, x2, compiler)
   217  				err := compiler.compileEq(operationPtr(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32)))
   218  				require.NoError(t, err)
   219  			},
   220  		},
   221  		{
   222  			name: "NE",
   223  			setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
   224  				x1, x2 := uint32(1), uint32(2)
   225  				if shouldGoElse {
   226  					x2 = x1
   227  				}
   228  				requirePushTwoInt32Consts(t, x1, x2, compiler)
   229  				err := compiler.compileNe(operationPtr(wazeroir.NewOperationNe(wazeroir.UnsignedTypeI32)))
   230  				require.NoError(t, err)
   231  			},
   232  		},
   233  	}
   234  
   235  	for _, tt := range tests {
   236  		tc := tt
   237  		t.Run(tc.name, func(t *testing.T) {
   238  			for _, shouldGoToElse := range []bool{false, true} {
   239  				shouldGoToElse := shouldGoToElse
   240  				t.Run(fmt.Sprintf("should_goto_else=%v", shouldGoToElse), func(t *testing.T) {
   241  					env := newCompilerEnvironment()
   242  					compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
   243  					err := compiler.compilePreamble()
   244  					require.NoError(t, err)
   245  
   246  					tc.setupFunc(t, compiler, shouldGoToElse)
   247  					requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
   248  
   249  					err = compiler.compileBrIf(operationPtr(wazeroir.NewOperationBrIf(thenBranchTarget, elseBranchTarget, wazeroir.NopInclusiveRange)))
   250  					require.NoError(t, err)
   251  					compiler.compileExitFromNativeCode(unreachableStatus)
   252  
   253  					// Emit code for .then label.
   254  					skip := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(thenBranchTarget)))
   255  					require.False(t, skip)
   256  					compiler.compileExitFromNativeCode(thenLabelExitStatus)
   257  
   258  					// Emit code for .else label.
   259  					skip = compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(elseBranchTarget)))
   260  					require.False(t, skip)
   261  					compiler.compileExitFromNativeCode(elseLabelExitStatus)
   262  
   263  					code := asm.CodeSegment{}
   264  					defer func() { require.NoError(t, code.Unmap()) }()
   265  
   266  					_, err = compiler.compile(code.NextCodeSection())
   267  					require.NoError(t, err)
   268  
   269  					// The generated code looks like this:
   270  					//
   271  					//    ... code from compilePreamble()
   272  					//    ... code from tc.setupFunc()
   273  					//    br_if .then, .else
   274  					//    exit $unreachableStatus
   275  					// .then:
   276  					//    exit $thenLabelExitStatus
   277  					// .else:
   278  					//    exit $elseLabelExitStatus
   279  					//
   280  					// Therefore, if we start executing from the top, we must end up exiting with an appropriate status.
   281  					env.exec(code.Bytes())
   282  					require.NotEqual(t, unreachableStatus, env.compilerStatus())
   283  					if shouldGoToElse {
   284  						require.Equal(t, elseLabelExitStatus, env.compilerStatus())
   285  					} else {
   286  						require.Equal(t, thenLabelExitStatus, env.compilerStatus())
   287  					}
   288  				})
   289  			}
   290  		})
   291  	}
   292  }
   293  
   294  func TestCompiler_compileBrTable(t *testing.T) {
   295  	requireRunAndExpectedValueReturned := func(t *testing.T, env *compilerEnv, c compilerImpl, expValue uint32) {
   296  		// Emit code for each label which returns the frame ID.
   297  		for returnValue := uint32(0); returnValue < 7; returnValue++ {
   298  			label := wazeroir.NewLabel(wazeroir.LabelKindHeader, returnValue)
   299  			err := c.compileBr(operationPtr(wazeroir.NewOperationBr(label)))
   300  			require.NoError(t, err)
   301  			_ = c.compileLabel(operationPtr(wazeroir.NewOperationLabel(label)))
   302  			_ = c.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(label.FrameID()))))
   303  			err = c.compileReturnFunction()
   304  			require.NoError(t, err)
   305  		}
   306  
   307  		code := asm.CodeSegment{}
   308  		defer func() { require.NoError(t, code.Unmap()) }()
   309  
   310  		// Generate the code under test and run.
   311  		_, err := c.compile(code.NextCodeSection())
   312  		require.NoError(t, err)
   313  		env.exec(code.Bytes())
   314  
   315  		// Check the returned value.
   316  		require.Equal(t, uint64(1), env.stackPointer())
   317  		require.Equal(t, expValue, env.stackTopAsUint32())
   318  	}
   319  
   320  	getBranchLabelFromFrameID := func(frameid uint32) uint64 {
   321  		return uint64(wazeroir.NewLabel(wazeroir.LabelKindHeader, frameid))
   322  	}
   323  
   324  	tests := []struct {
   325  		name          string
   326  		index         int64
   327  		o             *wazeroir.UnionOperation
   328  		expectedValue uint32
   329  	}{
   330  		{
   331  			name: "only default with index 0",
   332  			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
   333  				getBranchLabelFromFrameID(6),
   334  				wazeroir.NopInclusiveRange.AsU64(),
   335  			})),
   336  			index:         0,
   337  			expectedValue: 6,
   338  		},
   339  		{
   340  			name: "only default with index 100",
   341  			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
   342  				getBranchLabelFromFrameID(6),
   343  				wazeroir.NopInclusiveRange.AsU64(),
   344  			})),
   345  			index:         100,
   346  			expectedValue: 6,
   347  		},
   348  		{
   349  			name: "select default with targets and good index",
   350  			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
   351  				getBranchLabelFromFrameID(1),
   352  				wazeroir.NopInclusiveRange.AsU64(),
   353  				getBranchLabelFromFrameID(2),
   354  				wazeroir.NopInclusiveRange.AsU64(),
   355  				getBranchLabelFromFrameID(6), // default
   356  				wazeroir.NopInclusiveRange.AsU64(),
   357  			})),
   358  			index:         3,
   359  			expectedValue: 6,
   360  		},
   361  		{
   362  			name: "select default with targets and huge index",
   363  			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
   364  				getBranchLabelFromFrameID(1),
   365  				wazeroir.NopInclusiveRange.AsU64(),
   366  				getBranchLabelFromFrameID(2),
   367  				wazeroir.NopInclusiveRange.AsU64(),
   368  				getBranchLabelFromFrameID(6), // default
   369  				wazeroir.NopInclusiveRange.AsU64(),
   370  			},
   371  			)),
   372  			index:         100000,
   373  			expectedValue: 6,
   374  		},
   375  		{
   376  			name: "select first with two targets",
   377  			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
   378  				getBranchLabelFromFrameID(1),
   379  				wazeroir.NopInclusiveRange.AsU64(),
   380  				getBranchLabelFromFrameID(2),
   381  				wazeroir.NopInclusiveRange.AsU64(),
   382  				getBranchLabelFromFrameID(5), // default
   383  				wazeroir.NopInclusiveRange.AsU64(),
   384  			})),
   385  			index:         0,
   386  			expectedValue: 1,
   387  		},
   388  		{
   389  			name: "select last with two targets",
   390  			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
   391  				getBranchLabelFromFrameID(1),
   392  				wazeroir.NopInclusiveRange.AsU64(),
   393  				getBranchLabelFromFrameID(2),
   394  				wazeroir.NopInclusiveRange.AsU64(),
   395  				getBranchLabelFromFrameID(6), // default
   396  				wazeroir.NopInclusiveRange.AsU64(),
   397  			})),
   398  			index:         1,
   399  			expectedValue: 2,
   400  		},
   401  		{
   402  			name: "select first with five targets",
   403  			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
   404  				getBranchLabelFromFrameID(1),
   405  				wazeroir.NopInclusiveRange.AsU64(),
   406  				getBranchLabelFromFrameID(2),
   407  				wazeroir.NopInclusiveRange.AsU64(),
   408  				getBranchLabelFromFrameID(3),
   409  				wazeroir.NopInclusiveRange.AsU64(),
   410  				getBranchLabelFromFrameID(4),
   411  				wazeroir.NopInclusiveRange.AsU64(),
   412  				getBranchLabelFromFrameID(5),
   413  				wazeroir.NopInclusiveRange.AsU64(),
   414  				getBranchLabelFromFrameID(5), // default
   415  				wazeroir.NopInclusiveRange.AsU64(),
   416  			})),
   417  			index:         0,
   418  			expectedValue: 1,
   419  		},
   420  		{
   421  			name: "select middle with five targets",
   422  			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
   423  				getBranchLabelFromFrameID(1),
   424  				wazeroir.NopInclusiveRange.AsU64(),
   425  				getBranchLabelFromFrameID(2),
   426  				wazeroir.NopInclusiveRange.AsU64(),
   427  				getBranchLabelFromFrameID(3),
   428  				wazeroir.NopInclusiveRange.AsU64(),
   429  				getBranchLabelFromFrameID(4),
   430  				wazeroir.NopInclusiveRange.AsU64(),
   431  				getBranchLabelFromFrameID(5),
   432  				wazeroir.NopInclusiveRange.AsU64(),
   433  				getBranchLabelFromFrameID(5), // default
   434  				wazeroir.NopInclusiveRange.AsU64(),
   435  			})),
   436  			index:         2,
   437  			expectedValue: 3,
   438  		},
   439  		{
   440  			name: "select last with five targets",
   441  			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
   442  				getBranchLabelFromFrameID(1),
   443  				wazeroir.NopInclusiveRange.AsU64(),
   444  				getBranchLabelFromFrameID(2),
   445  				wazeroir.NopInclusiveRange.AsU64(),
   446  				getBranchLabelFromFrameID(3),
   447  				wazeroir.NopInclusiveRange.AsU64(),
   448  				getBranchLabelFromFrameID(4),
   449  				wazeroir.NopInclusiveRange.AsU64(),
   450  				getBranchLabelFromFrameID(5),
   451  				wazeroir.NopInclusiveRange.AsU64(),
   452  				getBranchLabelFromFrameID(5), // default
   453  				wazeroir.NopInclusiveRange.AsU64(),
   454  			})),
   455  			index:         4,
   456  			expectedValue: 5,
   457  		},
   458  	}
   459  
   460  	for _, tt := range tests {
   461  		tc := tt
   462  		t.Run(tc.name, func(t *testing.T) {
   463  			env := newCompilerEnvironment()
   464  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
   465  
   466  			err := compiler.compilePreamble()
   467  			require.NoError(t, err)
   468  
   469  			err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(tc.index))))
   470  			require.NoError(t, err)
   471  
   472  			err = compiler.compileBrTable(tc.o)
   473  			require.NoError(t, err)
   474  
   475  			require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
   476  
   477  			requireRunAndExpectedValueReturned(t, env, compiler, tc.expectedValue)
   478  		})
   479  	}
   480  }
   481  
   482  func requirePushTwoInt32Consts(t *testing.T, x1, x2 uint32, compiler compilerImpl) {
   483  	err := compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(x1)))
   484  	require.NoError(t, err)
   485  	err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(x2)))
   486  	require.NoError(t, err)
   487  }
   488  
   489  func requirePushTwoFloat32Consts(t *testing.T, x1, x2 float32, compiler compilerImpl) {
   490  	err := compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(x1)))
   491  	require.NoError(t, err)
   492  	err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(x2)))
   493  	require.NoError(t, err)
   494  }
   495  
   496  func TestCompiler_compileBr(t *testing.T) {
   497  	t.Run("return", func(t *testing.T) {
   498  		env := newCompilerEnvironment()
   499  		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
   500  		err := compiler.compilePreamble()
   501  		require.NoError(t, err)
   502  
   503  		// Branch into nil label is interpreted as return. See BranchTarget.IsReturnTarget
   504  		err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(wazeroir.NewLabel(wazeroir.LabelKindReturn, 0))))
   505  		require.NoError(t, err)
   506  
   507  		code := asm.CodeSegment{}
   508  		defer func() { require.NoError(t, code.Unmap()) }()
   509  
   510  		// Compile and execute the code under test.
   511  		// Note: we don't invoke "compiler.return()" as the code emitted by compilerBr is enough to exit.
   512  		_, err = compiler.compile(code.NextCodeSection())
   513  		require.NoError(t, err)
   514  		env.exec(code.Bytes())
   515  
   516  		require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   517  	})
   518  	t.Run("back-and-forth br", func(t *testing.T) {
   519  		env := newCompilerEnvironment()
   520  		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
   521  		err := compiler.compilePreamble()
   522  		require.NoError(t, err)
   523  
   524  		// Emit the forward br, meaning that handle Br instruction where the target label hasn't been compiled yet.
   525  		forwardLabel := wazeroir.NewLabel(wazeroir.LabelKindHeader, 0)
   526  		err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(forwardLabel)))
   527  		require.NoError(t, err)
   528  
   529  		// We must not reach the code after Br, so emit the code exiting with Unreachable status.
   530  		compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable)
   531  		require.NoError(t, err)
   532  
   533  		exitLabel := wazeroir.NewLabel(wazeroir.LabelKindHeader, 1)
   534  		err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(exitLabel)))
   535  		require.NoError(t, err)
   536  
   537  		// Emit code for the exitLabel.
   538  		skip := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(exitLabel)))
   539  		require.False(t, skip)
   540  		compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned)
   541  		require.NoError(t, err)
   542  
   543  		// Emit code for the forwardLabel.
   544  		skip = compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(forwardLabel)))
   545  		require.False(t, skip)
   546  		err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(exitLabel)))
   547  		require.NoError(t, err)
   548  
   549  		code := asm.CodeSegment{}
   550  		defer func() { require.NoError(t, code.Unmap()) }()
   551  
   552  		_, err = compiler.compile(code.NextCodeSection())
   553  		require.NoError(t, err)
   554  
   555  		// The generated code looks like this:)
   556  		//
   557  		//    ... code from compilePreamble()
   558  		//    br .forwardLabel
   559  		//    exit nativeCallStatusCodeUnreachable  // must not be reached
   560  		//    br .exitLabel                      // must not be reached
   561  		// .exitLabel:
   562  		//    exit nativeCallStatusCodeReturned
   563  		// .forwardLabel:
   564  		//    br .exitLabel
   565  		//
   566  		// Therefore, if we start executing from the top, we must end up exiting nativeCallStatusCodeReturned.
   567  		env.exec(code.Bytes())
   568  		require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   569  	})
   570  }
   571  
   572  func TestCompiler_compileCallIndirect(t *testing.T) {
   573  	t.Run("out of bounds", func(t *testing.T) {
   574  		env := newCompilerEnvironment()
   575  		env.addTable(&wasm.TableInstance{References: make([]wasm.Reference, 10)})
   576  		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   577  			Types:    []wasm.FunctionType{{}},
   578  			HasTable: true,
   579  		})
   580  		err := compiler.compilePreamble()
   581  		require.NoError(t, err)
   582  
   583  		targetOperation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0))
   584  
   585  		// Place the offset value.
   586  		err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(10)))
   587  		require.NoError(t, err)
   588  
   589  		err = compiler.compileCallIndirect(targetOperation)
   590  		require.NoError(t, err)
   591  
   592  		// We expect to exit from the code in callIndirect so the subsequent code must be unreachable.
   593  		compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable)
   594  
   595  		code := asm.CodeSegment{}
   596  		defer func() { require.NoError(t, code.Unmap()) }()
   597  
   598  		// Generate the code under test and run.
   599  		_, err = compiler.compile(code.NextCodeSection())
   600  		require.NoError(t, err)
   601  		env.exec(code.Bytes())
   602  
   603  		require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
   604  	})
   605  
   606  	t.Run("uninitialized", func(t *testing.T) {
   607  		env := newCompilerEnvironment()
   608  		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   609  			Types:    []wasm.FunctionType{{}},
   610  			HasTable: true,
   611  		})
   612  		err := compiler.compilePreamble()
   613  		require.NoError(t, err)
   614  
   615  		targetOperation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0))
   616  		targetOffset := operationPtr(wazeroir.NewOperationConstI32(uint32(0)))
   617  
   618  		// and the typeID doesn't match the table[targetOffset]'s type ID.
   619  		table := make([]wasm.Reference, 10)
   620  		env.addTable(&wasm.TableInstance{References: table})
   621  		env.module().TypeIDs = make([]wasm.FunctionTypeID, 10)
   622  
   623  		// Place the offset value.
   624  		err = compiler.compileConstI32(targetOffset)
   625  		require.NoError(t, err)
   626  		err = compiler.compileCallIndirect(targetOperation)
   627  		require.NoError(t, err)
   628  
   629  		// We expect to exit from the code in callIndirect so the subsequent code must be unreachable.
   630  		compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable)
   631  		require.NoError(t, err)
   632  
   633  		code := asm.CodeSegment{}
   634  		defer func() { require.NoError(t, code.Unmap()) }()
   635  
   636  		// Generate the code under test and run.
   637  		_, err = compiler.compile(code.NextCodeSection())
   638  		require.NoError(t, err)
   639  		env.exec(code.Bytes())
   640  
   641  		require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
   642  	})
   643  
   644  	t.Run("type not match", func(t *testing.T) {
   645  		env := newCompilerEnvironment()
   646  		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   647  			Types:    []wasm.FunctionType{{}},
   648  			HasTable: true,
   649  		})
   650  		err := compiler.compilePreamble()
   651  		require.NoError(t, err)
   652  
   653  		targetOperation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0))
   654  		targetOffset := operationPtr(wazeroir.NewOperationConstI32(uint32(0)))
   655  		env.module().TypeIDs = []wasm.FunctionTypeID{1000}
   656  		// Ensure that the module instance has the type information for targetOperation.TypeIndex,
   657  		// and the typeID doesn't match the table[targetOffset]'s type ID.
   658  		table := make([]wasm.Reference, 10)
   659  		env.addTable(&wasm.TableInstance{References: table})
   660  
   661  		cf := &function{typeID: 50}
   662  		table[0] = uintptr(unsafe.Pointer(cf))
   663  
   664  		// Place the offset value.
   665  		err = compiler.compileConstI32(targetOffset)
   666  		require.NoError(t, err)
   667  
   668  		// Now emit the code.
   669  		require.NoError(t, compiler.compileCallIndirect(targetOperation))
   670  
   671  		// We expect to exit from the code in callIndirect so the subsequent code must be unreachable.
   672  		compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable)
   673  		require.NoError(t, err)
   674  
   675  		code := asm.CodeSegment{}
   676  		defer func() { require.NoError(t, code.Unmap()) }()
   677  
   678  		// Generate the code under test and run.
   679  		_, err = compiler.compile(code.NextCodeSection())
   680  		require.NoError(t, err)
   681  		env.exec(code.Bytes())
   682  
   683  		require.Equal(t, nativeCallStatusCodeTypeMismatchOnIndirectCall.String(), env.compilerStatus().String())
   684  	})
   685  
   686  	t.Run("ok", func(t *testing.T) {
   687  		targetType := wasm.FunctionType{
   688  			Results:           []wasm.ValueType{wasm.ValueTypeI32},
   689  			ResultNumInUint64: 1,
   690  		}
   691  		const typeIndex = 0
   692  		targetTypeID := wasm.FunctionTypeID(10)
   693  		operation := operationPtr(wazeroir.NewOperationCallIndirect(typeIndex, 0))
   694  
   695  		table := make([]wasm.Reference, 10)
   696  		env := newCompilerEnvironment()
   697  		env.addTable(&wasm.TableInstance{References: table})
   698  
   699  		// Ensure that the module instance has the type information for targetOperation.TypeIndex,
   700  		// and the typeID matches the table[targetOffset]'s type ID.
   701  		env.module().TypeIDs = make([]wasm.FunctionTypeID, 100)
   702  		env.module().TypeIDs[typeIndex] = targetTypeID
   703  		env.module().Engine = &moduleEngine{functions: []function{}}
   704  
   705  		me := env.moduleEngine()
   706  		me.functions = make([]function, len(table))
   707  		for i := 0; i < len(table); i++ {
   708  			// First, we create the call target function for the table element i.
   709  			// To match its function type, it must return one value.
   710  			expectedReturnValue := uint32(i * 1000)
   711  
   712  			compiler := env.requireNewCompiler(t, &targetType, newCompiler, &wazeroir.CompilationResult{})
   713  			err := compiler.compilePreamble()
   714  			require.NoError(t, err)
   715  			err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(expectedReturnValue)))
   716  			require.NoError(t, err)
   717  
   718  			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
   719  			// The function result value must be set at the bottom of the stack.
   720  			err = compiler.compileSet(operationPtr(wazeroir.NewOperationSet(int(compiler.runtimeValueLocationStack().sp-1), false)))
   721  			require.NoError(t, err)
   722  			err = compiler.compileReturnFunction()
   723  			require.NoError(t, err)
   724  
   725  			code := asm.CodeSegment{}
   726  			defer func() { require.NoError(t, code.Unmap()) }()
   727  
   728  			_, err = compiler.compile(code.NextCodeSection())
   729  			require.NoError(t, err)
   730  
   731  			makeExecutable(code.Bytes())
   732  
   733  			// Now that we've generated the code for this function,
   734  			// add it to the module engine and assign its pointer to the table index.
   735  			me.functions[i] = function{
   736  				codeInitialAddress: uintptr(unsafe.Pointer(&code.Bytes()[0])),
   737  				moduleInstance:     env.moduleInstance,
   738  				typeID:             targetTypeID,
   739  			}
   740  			table[i] = uintptr(unsafe.Pointer(&me.functions[i]))
   741  		}
   742  
   743  		// Test to ensure that we can call all the functions stored in the table.
   744  		for i := 1; i < len(table); i++ {
   745  			expectedReturnValue := uint32(i * 1000)
   746  			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   747  				compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
   748  					&wazeroir.CompilationResult{
   749  						Types:    []wasm.FunctionType{targetType},
   750  						HasTable: true,
   751  					},
   752  				)
   753  				err := compiler.compilePreamble()
   754  				require.NoError(t, err)
   755  
   756  				// Place the offset value. Here we try calling a function of functionaddr == table[i].FunctionIndex.
   757  				err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(i))))
   758  				require.NoError(t, err)
   759  
   760  				// At this point, we should have one item (offset value) on the stack.
   761  				requireRuntimeLocationStackPointerEqual(t, 1, compiler)
   762  
   763  				require.NoError(t, compiler.compileCallIndirect(operation))
   764  
   765  				// At this point, we consumed the offset value, but the function returns one value,
   766  				// so the stack pointer results in the same.
   767  				requireRuntimeLocationStackPointerEqual(t, 1, compiler)
   768  
   769  				err = compiler.compileReturnFunction()
   770  				require.NoError(t, err)
   771  
   772  				code := asm.CodeSegment{}
   773  				defer func() { require.NoError(t, code.Unmap()) }()
   774  
   775  				// Generate the code under test and run.
   776  				_, err = compiler.compile(code.NextCodeSection())
   777  				require.NoError(t, err)
   778  				env.exec(code.Bytes())
   779  
   780  				require.Equal(t, nativeCallStatusCodeReturned.String(), env.compilerStatus().String())
   781  				require.Equal(t, uint64(1), env.stackPointer())
   782  				require.Equal(t, expectedReturnValue, uint32(env.ce.popValue()))
   783  			})
   784  		}
   785  	})
   786  }
   787  
   788  // TestCompiler_callIndirect_largeTypeIndex ensures that non-trivial large type index works well during call_indirect.
   789  // Note: any index larger than 8-bit range is considered as large for arm64 compiler.
   790  func TestCompiler_callIndirect_largeTypeIndex(t *testing.T) {
   791  	env := newCompilerEnvironment()
   792  	table := make([]wasm.Reference, 1)
   793  	env.addTable(&wasm.TableInstance{References: table})
   794  	// Ensure that the module instance has the type information for targetOperation.TypeIndex,
   795  	// and the typeID  matches the table[targetOffset]'s type ID.
   796  	const typeIndex, typeID = 12345, 0
   797  	operation := operationPtr(wazeroir.NewOperationCallIndirect(typeIndex, 0))
   798  	env.module().TypeIDs = make([]wasm.FunctionTypeID, typeIndex+1)
   799  	env.module().TypeIDs[typeIndex] = typeID
   800  	env.module().Engine = &moduleEngine{functions: []function{}}
   801  
   802  	types := make([]wasm.FunctionType, typeIndex+1)
   803  	types[typeIndex] = wasm.FunctionType{}
   804  
   805  	code1 := asm.CodeSegment{}
   806  	code2 := asm.CodeSegment{}
   807  	defer func() {
   808  		require.NoError(t, code1.Unmap())
   809  		require.NoError(t, code2.Unmap())
   810  	}()
   811  
   812  	me := env.moduleEngine()
   813  	{ // Compiling call target.
   814  		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
   815  		err := compiler.compilePreamble()
   816  		require.NoError(t, err)
   817  		err = compiler.compileReturnFunction()
   818  		require.NoError(t, err)
   819  
   820  		_, err = compiler.compile(code1.NextCodeSection())
   821  		require.NoError(t, err)
   822  
   823  		makeExecutable(code1.Bytes())
   824  		f := function{
   825  			parent:             &compiledFunction{parent: &compiledCode{executable: code1}},
   826  			codeInitialAddress: uintptr(unsafe.Pointer(&code1.Bytes()[0])),
   827  			moduleInstance:     env.moduleInstance,
   828  		}
   829  		me.functions = append(me.functions, f)
   830  		table[0] = uintptr(unsafe.Pointer(&f))
   831  	}
   832  
   833  	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   834  		Types:    types,
   835  		HasTable: true,
   836  	})
   837  	err := compiler.compilePreamble()
   838  	require.NoError(t, err)
   839  
   840  	err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(0)))
   841  	require.NoError(t, err)
   842  
   843  	require.NoError(t, compiler.compileCallIndirect(operation))
   844  
   845  	err = compiler.compileReturnFunction()
   846  	require.NoError(t, err)
   847  
   848  	// Generate the code under test and run.
   849  	_, err = compiler.compile(code2.NextCodeSection())
   850  	require.NoError(t, err)
   851  	env.exec(code2.Bytes())
   852  }
   853  
   854  func TestCompiler_compileCall(t *testing.T) {
   855  	env := newCompilerEnvironment()
   856  	me := env.moduleEngine()
   857  	expectedValue := uint32(0)
   858  
   859  	// Emit the call target function.
   860  	const numCalls = 3
   861  	targetFunctionType := wasm.FunctionType{
   862  		Params:           []wasm.ValueType{wasm.ValueTypeI32},
   863  		Results:          []wasm.ValueType{wasm.ValueTypeI32},
   864  		ParamNumInUint64: 1, ResultNumInUint64: 1,
   865  	}
   866  
   867  	for i := 0; i < numCalls; i++ {
   868  		// Each function takes one argument, adds the value with 100 + i and returns the result.
   869  		addTargetValue := uint32(100 + i)
   870  		expectedValue += addTargetValue
   871  		compiler := env.requireNewCompiler(t, &targetFunctionType, newCompiler, &wazeroir.CompilationResult{})
   872  
   873  		err := compiler.compilePreamble()
   874  		require.NoError(t, err)
   875  
   876  		err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(addTargetValue)))
   877  		require.NoError(t, err)
   878  		// Picks the function argument placed at the bottom of the stack.
   879  		err = compiler.compilePick(operationPtr(wazeroir.NewOperationPick(int(compiler.runtimeValueLocationStack().sp-1), false)))
   880  		require.NoError(t, err)
   881  		// Adds the const to the picked value.
   882  		err = compiler.compileAdd(operationPtr(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI32)))
   883  		require.NoError(t, err)
   884  		// Then store the added result into the bottom of the stack (which is treated as the result of the function).
   885  		err = compiler.compileSet(operationPtr(wazeroir.NewOperationSet(int(compiler.runtimeValueLocationStack().sp-1), false)))
   886  		require.NoError(t, err)
   887  
   888  		err = compiler.compileReturnFunction()
   889  		require.NoError(t, err)
   890  
   891  		code := asm.CodeSegment{}
   892  		defer func() { require.NoError(t, code.Unmap()) }()
   893  
   894  		_, err = compiler.compile(code.NextCodeSection())
   895  		require.NoError(t, err)
   896  
   897  		makeExecutable(code.Bytes())
   898  		me.functions = append(me.functions, function{
   899  			parent:             &compiledFunction{parent: &compiledCode{executable: code}},
   900  			codeInitialAddress: uintptr(unsafe.Pointer(&code.Bytes()[0])),
   901  			moduleInstance:     env.moduleInstance,
   902  		})
   903  	}
   904  
   905  	// Now we start building the caller's code.
   906  	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   907  		Functions: make([]uint32, numCalls),
   908  		Types:     []wasm.FunctionType{targetFunctionType},
   909  	})
   910  
   911  	err := compiler.compilePreamble()
   912  	require.NoError(t, err)
   913  
   914  	const initialValue = 100
   915  	expectedValue += initialValue
   916  	err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(1234))) // Dummy value so the base pointer would be non-trivial for callees.
   917  	require.NoError(t, err)
   918  	err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(initialValue)))
   919  	require.NoError(t, err)
   920  
   921  	// Call all the built functions.
   922  	for i := 0; i < numCalls; i++ {
   923  		err = compiler.compileCall(operationPtr(wazeroir.NewOperationCall(1)))
   924  		require.NoError(t, err)
   925  	}
   926  
   927  	// Set the result slot
   928  	err = compiler.compileReturnFunction()
   929  	require.NoError(t, err)
   930  
   931  	code := asm.CodeSegment{}
   932  	defer func() { require.NoError(t, code.Unmap()) }()
   933  
   934  	_, err = compiler.compile(code.NextCodeSection())
   935  	require.NoError(t, err)
   936  	env.exec(code.Bytes())
   937  
   938  	// Check status and returned values.
   939  	require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   940  	require.Equal(t, uint64(0), env.stackBasePointer())
   941  	require.Equal(t, uint64(2), env.stackPointer()) // Must be 2 (dummy value + the calculation results)
   942  	require.Equal(t, expectedValue, env.stackTopAsUint32())
   943  }