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  }