wa-lang.org/wazero@v1.0.2/internal/engine/compiler/compiler_controlflow_test.go (about)

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