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  }