github.com/taubyte/vm-wasm-utils@v1.0.2/host.go (about) 1 package wasm 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 ) 8 9 // HostFunc is a function with an inlined type, used for NewHostModule. 10 // Any corresponding FunctionType will be reused or added to the Module. 11 type HostFunc struct { 12 // ExportNames is equivalent to the same method on api.FunctionDefinition. 13 ExportNames []string 14 15 // Name is equivalent to the same method on api.FunctionDefinition. 16 Name string 17 18 // ParamTypes is equivalent to the same method on api.FunctionDefinition. 19 ParamTypes []ValueType 20 21 // ParamNames is equivalent to the same method on api.FunctionDefinition. 22 ParamNames []string 23 24 // ResultTypes is equivalent to the same method on api.FunctionDefinition. 25 ResultTypes []ValueType 26 27 // Code is the equivalent function in the SectionIDCode. 28 Code *Code 29 } 30 31 // NewGoFunc returns a HostFunc for the given parameters or panics. 32 func NewGoFunc(exportName string, name string, paramNames []string, fn interface{}) *HostFunc { 33 return (&HostFunc{ 34 ExportNames: []string{exportName}, 35 Name: name, 36 ParamNames: paramNames, 37 }).MustGoFunc(fn) 38 } 39 40 // MustGoFunc calls WithGoFunc or panics on error. 41 func (f *HostFunc) MustGoFunc(fn interface{}) *HostFunc { 42 if ret, err := f.WithGoFunc(fn); err != nil { 43 panic(err) 44 } else { 45 return ret 46 } 47 } 48 49 // WithGoFunc returns a copy of the function, replacing its Code.GoFunc. 50 func (f *HostFunc) WithGoFunc(fn interface{}) (*HostFunc, error) { 51 ret := *f 52 var err error 53 ret.ParamTypes, ret.ResultTypes, ret.Code, err = parseGoFunc(fn) 54 return &ret, err 55 } 56 57 // WithWasm returns a copy of the function, replacing its Code.Body. 58 func (f *HostFunc) WithWasm(body []byte) *HostFunc { 59 ret := *f 60 ret.Code = &Code{IsHostFunction: true, Body: body} 61 if f.Code != nil { 62 ret.Code.LocalTypes = f.Code.LocalTypes 63 } 64 return &ret 65 } 66 67 // NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small. 68 func NewHostModule( 69 moduleName string, 70 nameToGoFunc map[string]interface{}, 71 funcToNames map[string][]string, 72 nameToMemory map[string]*Memory, 73 nameToGlobal map[string]*Global, 74 enabledFeatures Features, 75 ) (m *Module, err error) { 76 if moduleName != "" { 77 m = &Module{NameSection: &NameSection{ModuleName: moduleName}} 78 } else { 79 m = &Module{} 80 } 81 82 funcCount := uint32(len(nameToGoFunc)) 83 memoryCount := uint32(len(nameToMemory)) 84 globalCount := uint32(len(nameToGlobal)) 85 exportCount := funcCount + memoryCount + globalCount 86 if exportCount > 0 { 87 m.ExportSection = make([]*Export, 0, exportCount) 88 } 89 90 // Check name collision as exports cannot collide on names, regardless of type. 91 for name := range nameToGoFunc { 92 // manually generate the error message as we don't have debug names yet. 93 if _, ok := nameToMemory[name]; ok { 94 return nil, fmt.Errorf("func[%s.%s] exports the same name as a memory", moduleName, name) 95 } 96 if _, ok := nameToGlobal[name]; ok { 97 return nil, fmt.Errorf("func[%s.%s] exports the same name as a global", moduleName, name) 98 } 99 } 100 for name := range nameToMemory { 101 if _, ok := nameToGlobal[name]; ok { 102 return nil, fmt.Errorf("memory[%s] exports the same name as a global", name) 103 } 104 } 105 106 if funcCount > 0 { 107 if err = addFuncs(m, nameToGoFunc, funcToNames, enabledFeatures); err != nil { 108 return 109 } 110 } 111 112 if memoryCount > 0 { 113 if err = addMemory(m, nameToMemory); err != nil { 114 return 115 } 116 } 117 118 // TODO: we can use enabledFeatures to fail early on things like mutable globals (once supported) 119 if globalCount > 0 { 120 if err = addGlobals(m, nameToGlobal); err != nil { 121 return 122 } 123 } 124 125 // Assigns the ModuleID by calculating sha256 on inputs as host modules do not have `wasm` to hash. 126 m.AssignModuleID([]byte(fmt.Sprintf("%s:%v:%v:%v:%v", 127 moduleName, nameToGoFunc, nameToMemory, nameToGlobal, enabledFeatures))) 128 m.BuildFunctionDefinitions() 129 return 130 } 131 132 func addFuncs( 133 m *Module, 134 nameToGoFunc map[string]interface{}, 135 funcToNames map[string][]string, 136 enabledFeatures Features, 137 ) (err error) { 138 if m.NameSection == nil { 139 m.NameSection = &NameSection{} 140 } 141 moduleName := m.NameSection.ModuleName 142 nameToFunc := make(map[string]*HostFunc, len(nameToGoFunc)) 143 sortedExportNames := make([]string, len(nameToFunc)) 144 for k := range nameToGoFunc { 145 sortedExportNames = append(sortedExportNames, k) 146 } 147 148 // Sort names for consistent iteration 149 sort.Strings(sortedExportNames) 150 151 funcNames := make([]string, len(nameToFunc)) 152 for _, k := range sortedExportNames { 153 v := nameToGoFunc[k] 154 if hf, ok := v.(*HostFunc); ok { 155 nameToFunc[hf.Name] = hf 156 funcNames = append(funcNames, hf.Name) 157 } else { 158 params, results, code, ftErr := parseGoFunc(v) 159 if ftErr != nil { 160 return fmt.Errorf("func[%s.%s] %w", moduleName, k, ftErr) 161 } 162 hf = &HostFunc{ 163 ExportNames: []string{k}, 164 Name: k, 165 ParamTypes: params, 166 ResultTypes: results, 167 Code: code, 168 } 169 if names := funcToNames[k]; names != nil { 170 namesLen := len(names) 171 if namesLen > 1 && namesLen-1 != len(params) { 172 return fmt.Errorf("func[%s.%s] has %d params, but %d param names", moduleName, k, namesLen-1, len(params)) 173 } 174 hf.Name = names[0] 175 hf.ParamNames = names[1:] 176 } 177 nameToFunc[k] = hf 178 funcNames = append(funcNames, k) 179 } 180 } 181 182 funcCount := uint32(len(nameToFunc)) 183 m.NameSection.FunctionNames = make([]*NameAssoc, 0, funcCount) 184 m.FunctionSection = make([]Index, 0, funcCount) 185 m.CodeSection = make([]*Code, 0, funcCount) 186 m.FunctionDefinitionSection = make([]*FunctionDefinition, 0, funcCount) 187 188 idx := Index(0) 189 for _, name := range funcNames { 190 hf := nameToFunc[name] 191 debugName := name // wasmdebug.FuncName(moduleName, name, idx) 192 typeIdx, typeErr := m.maybeAddType(hf.ParamTypes, hf.ResultTypes, enabledFeatures) 193 if typeErr != nil { 194 return fmt.Errorf("func[%s] %v", debugName, typeErr) 195 } 196 m.FunctionSection = append(m.FunctionSection, typeIdx) 197 m.CodeSection = append(m.CodeSection, hf.Code) 198 for _, export := range hf.ExportNames { 199 m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeFunc, Name: export, Index: idx}) 200 } 201 m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, &NameAssoc{Index: idx, Name: hf.Name}) 202 if len(hf.ParamNames) > 0 { 203 localNames := &NameMapAssoc{Index: idx} 204 for i, n := range hf.ParamNames { 205 localNames.NameMap = append(localNames.NameMap, &NameAssoc{Index: Index(i), Name: n}) 206 } 207 m.NameSection.LocalNames = append(m.NameSection.LocalNames, localNames) 208 } 209 idx++ 210 } 211 return nil 212 } 213 214 func addMemory(m *Module, nameToMemory map[string]*Memory) error { 215 memoryCount := uint32(len(nameToMemory)) 216 217 // Only one memory can be defined or imported 218 if memoryCount > 1 { 219 memoryNames := make([]string, 0, memoryCount) 220 for k := range nameToMemory { 221 memoryNames = append(memoryNames, k) 222 } 223 sort.Strings(memoryNames) // For consistent error messages 224 return fmt.Errorf("only one memory is allowed, but configured: %s", strings.Join(memoryNames, ", ")) 225 } 226 227 // Find the memory name to export. 228 var name string 229 for k, v := range nameToMemory { 230 name = k 231 if v.Min > v.Max { 232 return fmt.Errorf("memory[%s] min %d pages (%s) > max %d pages (%s)", name, v.Min, PagesToUnitOfBytes(v.Min), v.Max, PagesToUnitOfBytes(v.Max)) 233 } 234 m.MemorySection = v 235 } 236 237 m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeMemory, Name: name, Index: 0}) 238 return nil 239 } 240 241 func addGlobals(m *Module, globals map[string]*Global) error { 242 globalCount := len(globals) 243 m.GlobalSection = make([]*Global, 0, globalCount) 244 245 globalNames := make([]string, 0, globalCount) 246 for name := range globals { 247 globalNames = append(globalNames, name) 248 } 249 sort.Strings(globalNames) // For consistent iteration order 250 251 for i, name := range globalNames { 252 m.GlobalSection = append(m.GlobalSection, globals[name]) 253 m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeGlobal, Name: name, Index: Index(i)}) 254 } 255 return nil 256 } 257 258 func (m *Module) maybeAddType(params, results []ValueType, enabledFeatures Features) (Index, error) { 259 if len(results) > 1 { 260 // Guard >1.0 feature multi-value 261 if err := enabledFeatures.Require(FeatureMultiValue); err != nil { 262 return 0, fmt.Errorf("multiple result types invalid as %v", err) 263 } 264 } 265 for i, t := range m.TypeSection { 266 if t.EqualsSignature(params, results) { 267 return Index(i), nil 268 } 269 } 270 271 result := m.SectionElementCount(SectionIDType) 272 toAdd := &FunctionType{Params: params, Results: results} 273 m.TypeSection = append(m.TypeSection, toAdd) 274 return result, nil 275 }