1package wasm
2
3import (
4 "errors"
5 "fmt"
6
7 "github.com/tetratelabs/wazero/api"
8 "github.com/tetratelabs/wazero/internal/wasmdebug"
9)
10
11type 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.
17type 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.
41func (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.
48func 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
79func 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
162func (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}