host.go

  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}