wa-lang.org/wazero@v1.0.2/internal/gojs/spfunc/spfunc.go (about)

     1  package spfunc
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"wa-lang.org/wazero/api"
     8  	"wa-lang.org/wazero/internal/leb128"
     9  	"wa-lang.org/wazero/internal/wasm"
    10  )
    11  
    12  const debugMode = false
    13  
    14  func MustCallFromSP(expectSP bool, proxied *wasm.HostFunc) *wasm.ProxyFunc {
    15  	if ret, err := callFromSP(expectSP, proxied); err != nil {
    16  		panic(err)
    17  	} else {
    18  		return ret
    19  	}
    20  }
    21  
    22  // callFromSP generates code to call a function with the provided signature.
    23  // The returned function has a single api.ValueTypeI32 parameter of SP. Each
    24  // parameter is read at 8 byte offsets after that, and each result is written
    25  // at 8 byte offsets after the parameters.
    26  //
    27  // # Parameters
    28  //
    29  //   - expectSP: true if a constructor or method invocation. The last result is
    30  //     an updated SP value (i32), which affects the result memory offsets.
    31  func callFromSP(expectSP bool, proxied *wasm.HostFunc) (*wasm.ProxyFunc, error) {
    32  	params := proxied.ParamTypes
    33  	results := proxied.ResultTypes
    34  	if (8+len(params)+len(results))*8 > 255 {
    35  		return nil, errors.New("TODO: memory offset larger than one byte")
    36  	}
    37  
    38  	if debugMode {
    39  		fmt.Printf("(func $%s.proxy (param $%s %s)", proxied.Name, "sp", wasm.ValueTypeName(wasm.ValueTypeI32))
    40  	}
    41  
    42  	var localTypes []api.ValueType
    43  
    44  	resultSpOffset := 8 + len(params)*8
    45  	resultSPIndex := byte(0)
    46  	if len(results) > 0 {
    47  		if debugMode {
    48  			fmt.Printf(" (local %s %s)", wasm.ValueTypeName(wasm.ValueTypeI32), wasm.ValueTypeName(wasm.ValueTypeI64))
    49  		}
    50  		localTypes = append(localTypes, api.ValueTypeI32, api.ValueTypeI64)
    51  		if expectSP {
    52  			if debugMode {
    53  				fmt.Printf(" (local %s)", wasm.ValueTypeName(wasm.ValueTypeI32))
    54  			}
    55  			resultSPIndex = 3
    56  			resultSpOffset += 8
    57  			localTypes = append(localTypes, api.ValueTypeI32)
    58  		}
    59  	}
    60  
    61  	// Load all parameters onto the stack.
    62  	var code []byte
    63  	for i, t := range params {
    64  		if debugMode {
    65  			fmt.Printf("\n;; param[%d]=%s\n", i, wasm.ValueTypeName(t))
    66  		}
    67  
    68  		// First, add the memory offset to load onto the stack.
    69  		offset := 8 + int(i*8)
    70  		code = compileAddOffsetToSP(code, 0, offset)
    71  
    72  		// Next, load stack parameter $i from memory at that offset.
    73  		switch t {
    74  		case api.ValueTypeI32:
    75  			if debugMode {
    76  				fmt.Println(wasm.OpcodeI32LoadName)
    77  			}
    78  			code = append(code, wasm.OpcodeI32Load, 0x2, 0x0) // alignment=2 (natural alignment) staticOffset=0
    79  		case api.ValueTypeI64:
    80  			if debugMode {
    81  				fmt.Println(wasm.OpcodeI64LoadName)
    82  			}
    83  			code = append(code, wasm.OpcodeI64Load, 0x3, 0x0) // alignment=3 (natural alignment) staticOffset=0
    84  		default:
    85  			panic(errors.New("TODO: param " + api.ValueTypeName(t)))
    86  		}
    87  	}
    88  
    89  	// Now that all parameters are on the stack, call the function
    90  	callFuncPos := len(code) + 1
    91  	if debugMode {
    92  		fmt.Printf("\n%s 0\n", wasm.OpcodeCallName)
    93  	}
    94  
    95  	// Call index zero is a placeholder as it is replaced later.
    96  	code = append(code, wasm.OpcodeCall, 0)
    97  
    98  	// The stack may now have results. Iterate backwards.
    99  	i := len(results) - 1
   100  	if expectSP {
   101  		if debugMode {
   102  			fmt.Printf("%s %d ;; refresh SP\n", wasm.OpcodeLocalSetName, resultSPIndex)
   103  		}
   104  		code = append(code, wasm.OpcodeLocalSet, resultSPIndex)
   105  		i--
   106  	}
   107  	for ; i >= 0; i-- {
   108  		// pop current result from stack
   109  		t := results[i]
   110  		if debugMode {
   111  			fmt.Printf("\n;; result[%d]=%s\n", i, wasm.ValueTypeName(t))
   112  		}
   113  
   114  		var typeIndex byte
   115  		switch t {
   116  		case api.ValueTypeI32:
   117  			typeIndex = 1
   118  		case api.ValueTypeI64:
   119  			typeIndex = 2
   120  		default:
   121  			panic(errors.New("TODO: result " + api.ValueTypeName(t)))
   122  		}
   123  
   124  		if debugMode {
   125  			fmt.Printf("%s %d ;; next result\n", wasm.OpcodeLocalSetName, typeIndex)
   126  		}
   127  		code = append(code, wasm.OpcodeLocalSet, typeIndex)
   128  
   129  		offset := resultSpOffset + i*8
   130  		code = compileAddOffsetToSP(code, resultSPIndex, offset)
   131  
   132  		if debugMode {
   133  			fmt.Printf("%s %d ;; store next result\n", wasm.OpcodeLocalGetName, typeIndex)
   134  		}
   135  		code = append(code, wasm.OpcodeLocalGet, typeIndex)
   136  
   137  		switch t {
   138  		case api.ValueTypeI32:
   139  			if debugMode {
   140  				fmt.Println(wasm.OpcodeI32StoreName)
   141  			}
   142  			code = append(code, wasm.OpcodeI32Store, 0x2, 0x0) // alignment=2 (natural alignment) staticOffset=0
   143  		case api.ValueTypeI64:
   144  			if debugMode {
   145  				fmt.Println(wasm.OpcodeI64StoreName)
   146  			}
   147  			code = append(code, wasm.OpcodeI64Store, 0x3, 0x0) // alignment=3 (natural alignment) staticOffset=0
   148  		default:
   149  			panic(errors.New("TODO: result " + api.ValueTypeName(t)))
   150  		}
   151  
   152  	}
   153  	if debugMode {
   154  		fmt.Println("\n)")
   155  	}
   156  	code = append(code, wasm.OpcodeEnd)
   157  	return &wasm.ProxyFunc{
   158  		Proxy: &wasm.HostFunc{
   159  			ExportNames: proxied.ExportNames,
   160  			Name:        proxied.Name + ".proxy",
   161  			ParamTypes:  []api.ValueType{api.ValueTypeI32},
   162  			ParamNames:  []string{"sp"},
   163  			Code:        &wasm.Code{IsHostFunction: true, LocalTypes: localTypes, Body: code},
   164  		},
   165  		Proxied: &wasm.HostFunc{
   166  			Name:        proxied.Name,
   167  			ParamTypes:  proxied.ParamTypes,
   168  			ResultTypes: proxied.ResultTypes,
   169  			ParamNames:  proxied.ParamNames,
   170  			Code:        proxied.Code,
   171  		},
   172  		CallBodyPos: callFuncPos,
   173  	}, nil
   174  }
   175  
   176  func compileAddOffsetToSP(code []byte, spLocalIndex byte, offset int) []byte {
   177  	if debugMode {
   178  		fmt.Printf("%s %d ;; SP\n", wasm.OpcodeLocalGetName, spLocalIndex)
   179  		fmt.Printf("%s %d ;; offset\n", wasm.OpcodeI32ConstName, offset)
   180  		fmt.Printf("%s\n", wasm.OpcodeI32AddName)
   181  	}
   182  	code = append(code, wasm.OpcodeLocalGet, spLocalIndex)
   183  	// See /RATIONALE.md we can't tell the signed interpretation of a constant, so default to signed.
   184  	code = append(code, wasm.OpcodeI32Const)
   185  	code = append(code, leb128.EncodeInt32(int32(offset))...)
   186  	code = append(code, wasm.OpcodeI32Add)
   187  	return code
   188  }