github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/wasm/host.go (about) 1 package wasm 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/wasilibs/wazerox/api" 8 "github.com/wasilibs/wazerox/internal/wasmdebug" 9 ) 10 11 type HostFuncExporter interface { 12 ExportHostFunc(*HostFunc) 13 } 14 15 // HostFunc is a function with an inlined type, used for NewHostModule. 16 // Any corresponding FunctionType will be reused or added to the Module. 17 type HostFunc struct { 18 // ExportName is the only value returned by api.FunctionDefinition. 19 ExportName string 20 21 // Name is equivalent to the same method on api.FunctionDefinition. 22 Name string 23 24 // ParamTypes is equivalent to the same method on api.FunctionDefinition. 25 ParamTypes []ValueType 26 27 // ParamNames is equivalent to the same method on api.FunctionDefinition. 28 ParamNames []string 29 30 // ResultTypes is equivalent to the same method on api.FunctionDefinition. 31 ResultTypes []ValueType 32 33 // ResultNames is equivalent to the same method on api.FunctionDefinition. 34 ResultNames []string 35 36 // Code is the equivalent function in the SectionIDCode. 37 Code Code 38 } 39 40 // WithGoModuleFunc returns a copy of the function, replacing its Code.GoFunc. 41 func (f *HostFunc) WithGoModuleFunc(fn api.GoModuleFunc) *HostFunc { 42 ret := *f 43 ret.Code.GoFunc = fn 44 return &ret 45 } 46 47 // NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small. 48 func NewHostModule( 49 moduleName string, 50 exportNames []string, 51 nameToHostFunc map[string]*HostFunc, 52 enabledFeatures api.CoreFeatures, 53 ) (m *Module, err error) { 54 if moduleName != "" { 55 m = &Module{NameSection: &NameSection{ModuleName: moduleName}} 56 } else { 57 return nil, errors.New("a module name must not be empty") 58 } 59 60 if exportCount := uint32(len(nameToHostFunc)); exportCount > 0 { 61 m.ExportSection = make([]Export, 0, exportCount) 62 m.Exports = make(map[string]*Export, exportCount) 63 if err = addFuncs(m, exportNames, nameToHostFunc, enabledFeatures); err != nil { 64 return 65 } 66 } 67 68 m.IsHostModule = true 69 // Uses the address of *wasm.Module as the module ID so that host functions can have each state per compilation. 70 // Downside of this is that compilation cache on host functions (trampoline codes for Go functions and 71 // Wasm codes for Wasm-implemented host functions) are not available and compiles each time. On the other hand, 72 // compilation of host modules is not costly as it's merely small trampolines vs the real-world native Wasm binary. 73 // TODO: refactor engines so that we can properly cache compiled machine codes for host modules. 74 m.AssignModuleID([]byte(fmt.Sprintf("@@@@@@@@%p", m)), // @@@@@@@@ = any 8 bytes different from Wasm header. 75 nil, false) 76 return 77 } 78 79 func addFuncs( 80 m *Module, 81 exportNames []string, 82 nameToHostFunc map[string]*HostFunc, 83 enabledFeatures api.CoreFeatures, 84 ) (err error) { 85 if m.NameSection == nil { 86 m.NameSection = &NameSection{} 87 } 88 moduleName := m.NameSection.ModuleName 89 90 for _, k := range exportNames { 91 hf := nameToHostFunc[k] 92 if hf.Name == "" { 93 hf.Name = k // default name to export name 94 } 95 switch hf.Code.GoFunc.(type) { 96 case api.GoModuleFunction, api.GoFunction: 97 continue // already parsed 98 } 99 100 // Resolve the code using reflection 101 hf.ParamTypes, hf.ResultTypes, hf.Code, err = parseGoReflectFunc(hf.Code.GoFunc) 102 if err != nil { 103 return fmt.Errorf("func[%s.%s] %w", moduleName, k, err) 104 } 105 106 // Assign names to the function, if they exist. 107 params := hf.ParamTypes 108 if paramNames := hf.ParamNames; paramNames != nil { 109 if paramNamesLen := len(paramNames); paramNamesLen != len(params) { 110 return fmt.Errorf("func[%s.%s] has %d params, but %d params names", moduleName, k, paramNamesLen, len(params)) 111 } 112 } 113 114 results := hf.ResultTypes 115 if resultNames := hf.ResultNames; resultNames != nil { 116 if resultNamesLen := len(resultNames); resultNamesLen != len(results) { 117 return fmt.Errorf("func[%s.%s] has %d results, but %d results names", moduleName, k, resultNamesLen, len(results)) 118 } 119 } 120 } 121 122 funcCount := uint32(len(exportNames)) 123 m.NameSection.FunctionNames = make([]NameAssoc, 0, funcCount) 124 m.FunctionSection = make([]Index, 0, funcCount) 125 m.CodeSection = make([]Code, 0, funcCount) 126 127 idx := Index(0) 128 for _, name := range exportNames { 129 hf := nameToHostFunc[name] 130 debugName := wasmdebug.FuncName(moduleName, name, idx) 131 typeIdx, typeErr := m.maybeAddType(hf.ParamTypes, hf.ResultTypes, enabledFeatures) 132 if typeErr != nil { 133 return fmt.Errorf("func[%s] %v", debugName, typeErr) 134 } 135 m.FunctionSection = append(m.FunctionSection, typeIdx) 136 m.CodeSection = append(m.CodeSection, hf.Code) 137 138 export := hf.ExportName 139 m.ExportSection = append(m.ExportSection, Export{Type: ExternTypeFunc, Name: export, Index: idx}) 140 m.Exports[export] = &m.ExportSection[len(m.ExportSection)-1] 141 m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, NameAssoc{Index: idx, Name: hf.Name}) 142 143 if len(hf.ParamNames) > 0 { 144 localNames := NameMapAssoc{Index: idx} 145 for i, n := range hf.ParamNames { 146 localNames.NameMap = append(localNames.NameMap, NameAssoc{Index: Index(i), Name: n}) 147 } 148 m.NameSection.LocalNames = append(m.NameSection.LocalNames, localNames) 149 } 150 if len(hf.ResultNames) > 0 { 151 resultNames := NameMapAssoc{Index: idx} 152 for i, n := range hf.ResultNames { 153 resultNames.NameMap = append(resultNames.NameMap, NameAssoc{Index: Index(i), Name: n}) 154 } 155 m.NameSection.ResultNames = append(m.NameSection.ResultNames, resultNames) 156 } 157 idx++ 158 } 159 return nil 160 } 161 162 func (m *Module) maybeAddType(params, results []ValueType, enabledFeatures api.CoreFeatures) (Index, error) { 163 if len(results) > 1 { 164 // Guard >1.0 feature multi-value 165 if err := enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil { 166 return 0, fmt.Errorf("multiple result types invalid as %v", err) 167 } 168 } 169 for i := range m.TypeSection { 170 t := &m.TypeSection[i] 171 if t.EqualsSignature(params, results) { 172 return Index(i), nil 173 } 174 } 175 176 result := m.SectionElementCount(SectionIDType) 177 m.TypeSection = append(m.TypeSection, FunctionType{Params: params, Results: results}) 178 return result, nil 179 }