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 }