diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bac13bd..f9beaeb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -72,6 +72,10 @@ jobs: shell: bash run: ./ci/test-vendoring.sh if: runner.os != 'Windows' + - name: Test the minimal runtime + shell: bash + run: ./ci/test-minimal-runtime/test.sh + if: runner.os != 'Windows' coverage: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index d6f1347..68a99a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build /bazel-* -test-vendoring-project \ No newline at end of file +test-vendoring-project +ci/test-minimal-runtime/module.cwasm \ No newline at end of file diff --git a/ci/download-wasmtime.py b/ci/download-wasmtime.py index 459df58..a203b34 100644 --- a/ci/download-wasmtime.py +++ b/ci/download-wasmtime.py @@ -10,7 +10,7 @@ import glob -version = 'v42.0.0' +version = 'v43.0.0' urls = [ ['wasmtime-{}-x86_64-mingw-c-api.zip', 'windows-x86_64'], ['wasmtime-{}-x86_64-linux-c-api.tar.xz', 'linux-x86_64'], @@ -51,6 +51,7 @@ shutil.copytree(src + '/include', 'build/include', dirs_exist_ok=True) shutil.copytree(src + '/lib', 'build/' + dirname, dirs_exist_ok=True) + shutil.copytree(src + '/min/lib', 'build/' + dirname + '-min', dirs_exist_ok=True) shutil.rmtree(src) for dylib in glob.glob("build/**/*.dll"): diff --git a/ci/test-minimal-runtime/create_cwasm.go b/ci/test-minimal-runtime/create_cwasm.go new file mode 100644 index 0000000..1372665 --- /dev/null +++ b/ci/test-minimal-runtime/create_cwasm.go @@ -0,0 +1,35 @@ +//go:build ignore + +package main + +import ( + "log" + "os" + + "github.com/bytecodealliance/wasmtime-go/v42" +) + +func main() { + wasm, err := wasmtime.Wat2Wasm(`(module (func (export "test") (result i32) (i32.const 1)))`) + check(err) + + cfg := wasmtime.NewConfig() + cfg.SetGCSupport(false) + cfg.SetWasmThreads(false) + cfg.SetWasmComponentModel(false) + engine := wasmtime.NewEngineWithConfig(cfg) + module, err := wasmtime.NewModule(engine, wasm) + check(err) + defer module.Close() + + artifact, err := module.Serialize() + check(err) + + check(os.WriteFile("module.cwasm", artifact, 0o644)) +} + +func check(err error) { + if err != nil { + log.Fatal(err) + } +} diff --git a/ci/test-minimal-runtime/min_test.go b/ci/test-minimal-runtime/min_test.go new file mode 100644 index 0000000..eb8db96 --- /dev/null +++ b/ci/test-minimal-runtime/min_test.go @@ -0,0 +1,31 @@ +//go:build min + +package testminimalruntime_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/bytecodealliance/wasmtime-go/v42" +) + +func TestMinimalRuntime(t *testing.T) { + cfg := wasmtime.NewConfig() + cfg.SetGCSupport(false) + engine := wasmtime.NewEngineWithConfig(cfg) + module, err := wasmtime.NewModuleDeserializeFile(engine, "module.cwasm") + require.NoError(t, err) + defer module.Close() + + store := wasmtime.NewStore(engine) + instance, err := wasmtime.NewInstance(store, module, []wasmtime.AsExtern{}) + require.NoError(t, err) + + fn := instance.GetFunc(store, "test") + require.NotNil(t, fn) + + result, err := fn.Call(store) + require.NoError(t, err) + require.Equal(t, int32(1), result) +} diff --git a/ci/test-minimal-runtime/test.sh b/ci/test-minimal-runtime/test.sh new file mode 100755 index 0000000..69df19a --- /dev/null +++ b/ci/test-minimal-runtime/test.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" + +rm -f module.cwasm +go run create_cwasm.go +go test -count=1 -tags min . diff --git a/config.go b/config.go index 8e93558..b7b5b35 100644 --- a/config.go +++ b/config.go @@ -71,12 +71,6 @@ func (cfg *Config) SetMaxWasmStack(size int) { runtime.KeepAlive(cfg) } -// SetWasmThreads configures whether the wasm threads proposal is enabled -func (cfg *Config) SetWasmThreads(enabled bool) { - C.wasmtime_config_wasm_threads_set(cfg.ptr(), C.bool(enabled)) - runtime.KeepAlive(cfg) -} - // SetWasmReferenceTypes configures whether the wasm reference types proposal is enabled func (cfg *Config) SetWasmReferenceTypes(enabled bool) { C.wasmtime_config_wasm_reference_types_set(cfg.ptr(), C.bool(enabled)) @@ -143,10 +137,10 @@ func (cfg *Config) SetWasmGC(enabled bool) { runtime.KeepAlive(cfg) } -// SetWasmComponentModel configures whether the wasm component model proposal is -// enabled. -func (cfg *Config) SetWasmComponentModel(enabled bool) { - C.wasmtime_config_wasm_component_model_set(cfg.ptr(), C.bool(enabled)) +// SetGCSupport configures whether GC support is enabled in Wasmtime at all. +// When false, engines can be used without a GC collector (e.g. minimal Wasmtime builds). +func (cfg *Config) SetGCSupport(enabled bool) { + C.wasmtime_config_gc_support_set(cfg.ptr(), C.bool(enabled)) runtime.KeepAlive(cfg) } @@ -162,24 +156,6 @@ func (cfg *Config) SetConsumeFuel(enabled bool) { runtime.KeepAlive(cfg) } -// SetParallelCompilation configures whether compilation should use multiple threads -func (cfg *Config) SetParallelCompilation(enabled bool) { - C.wasmtime_config_parallel_compilation_set(cfg.ptr(), C.bool(enabled)) - runtime.KeepAlive(cfg) -} - -// SetCraneliftNanCanonicalization configures whether whether Cranelift should perform a -// NaN-canonicalization pass. -// -// When Cranelift is used as a code generation backend this will configure it to replace NaNs with a single -// canonical value. This is useful for users requiring entirely deterministic WebAssembly computation. -// -// This is not required by the WebAssembly spec, so it is not enabled by default. -func (cfg *Config) SetCraneliftNanCanonicalization(enabled bool) { - C.wasmtime_config_cranelift_nan_canonicalization_set(cfg.ptr(), C.bool(enabled)) - runtime.KeepAlive(cfg) -} - // SetNativeUnwindInfo whether to generate native unwind information (e.g. .eh_frame on Linux). func (cfg *Config) SetNativeUnwindInfo(enabled bool) { C.wasmtime_config_native_unwind_info_set(cfg.ptr(), C.bool(enabled)) @@ -205,61 +181,12 @@ func (cfg *Config) SetMemoryInitCOWSet(enabled bool) { runtime.KeepAlive(cfg) } -// SetStrategy configures what compilation strategy is used to compile wasm code -func (cfg *Config) SetStrategy(strat Strategy) { - C.wasmtime_config_strategy_set(cfg.ptr(), C.wasmtime_strategy_t(strat)) - runtime.KeepAlive(cfg) -} - -// SetCraneliftDebugVerifier configures whether the cranelift debug verifier will be active when -// cranelift is used to compile wasm code. -func (cfg *Config) SetCraneliftDebugVerifier(enabled bool) { - C.wasmtime_config_cranelift_debug_verifier_set(cfg.ptr(), C.bool(enabled)) - runtime.KeepAlive(cfg) -} - -// SetCraneliftOptLevel configures the cranelift optimization level for generated code -func (cfg *Config) SetCraneliftOptLevel(level OptLevel) { - C.wasmtime_config_cranelift_opt_level_set(cfg.ptr(), C.wasmtime_opt_level_t(level)) - runtime.KeepAlive(cfg) -} - // SetProfiler configures what profiler strategy to use for generated code func (cfg *Config) SetProfiler(profiler ProfilingStrategy) { C.wasmtime_config_profiler_set(cfg.ptr(), C.wasmtime_profiling_strategy_t(profiler)) runtime.KeepAlive(cfg) } -// CacheConfigLoadDefault enables compiled code caching for this `Config` using the default settings -// configuration can be found. -// -// For more information about caching see -// https://bytecodealliance.github.io/wasmtime/cli-cache.html -func (cfg *Config) CacheConfigLoadDefault() error { - err := C.wasmtime_config_cache_config_load(cfg.ptr(), nil) - runtime.KeepAlive(cfg) - if err != nil { - return mkError(err) - } - return nil -} - -// CacheConfigLoad enables compiled code caching for this `Config` using the settings specified -// in the configuration file `path`. -// -// For more information about caching and configuration options see -// https://bytecodealliance.github.io/wasmtime/cli-cache.html -func (cfg *Config) CacheConfigLoad(path string) error { - cstr := C.CString(path) - err := C.wasmtime_config_cache_config_load(cfg.ptr(), cstr) - C.free(unsafe.Pointer(cstr)) - runtime.KeepAlive(cfg) - if err != nil { - return mkError(err) - } - return nil -} - // SetEpochInterruption enables epoch-based instrumentation of generated code to // interrupt WebAssembly execution when the current engine epoch exceeds a // defined threshold. @@ -289,36 +216,6 @@ func (cfg *Config) SetTarget(target string) error { return nil } -// EnableCraneliftFlag enables a target-specific flag in Cranelift. -// -// This can be used, for example, to enable SSE4.2 on x86_64 hosts. Settings can -// be explored with `wasmtime settings` on the CLI. -// -// For more information see the Rust documentation at -// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.cranelift_flag_enable -func (cfg *Config) EnableCraneliftFlag(flag string) { - cstr := C.CString(flag) - C.wasmtime_config_cranelift_flag_enable(cfg.ptr(), cstr) - C.free(unsafe.Pointer(cstr)) - runtime.KeepAlive(cfg) -} - -// SetCraneliftFlag sets a target-specific flag in Cranelift to the specified value. -// -// This can be used, for example, to enable SSE4.2 on x86_64 hosts. Settings can -// be explored with `wasmtime settings` on the CLI. -// -// For more information see the Rust documentation at -// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.cranelift_flag_set -func (cfg *Config) SetCraneliftFlag(name string, value string) { - cstrName := C.CString(name) - cstrValue := C.CString(value) - C.wasmtime_config_cranelift_flag_set(cfg.ptr(), cstrName, cstrValue) - C.free(unsafe.Pointer(cstrName)) - C.free(unsafe.Pointer(cstrValue)) - runtime.KeepAlive(cfg) -} - // See comments in `ffi.go` for what's going on here func (cfg *Config) ptr() *C.wasm_config_t { ret := cfg._ptr diff --git a/config_feat_cache.go b/config_feat_cache.go new file mode 100644 index 0000000..eba127a --- /dev/null +++ b/config_feat_cache.go @@ -0,0 +1,41 @@ +//go:build !min + +package wasmtime + +// #include +// #include +import "C" +import ( + "runtime" + "unsafe" +) + +// CacheConfigLoadDefault enables compiled code caching for this `Config` using the default settings +// configuration can be found. +// +// For more information about caching see +// https://bytecodealliance.github.io/wasmtime/cli-cache.html +func (cfg *Config) CacheConfigLoadDefault() error { + err := C.wasmtime_config_cache_config_load(cfg.ptr(), nil) + runtime.KeepAlive(cfg) + if err != nil { + return mkError(err) + } + return nil +} + +// CacheConfigLoad enables compiled code caching for this `Config` using the settings specified +// in the configuration file `path`. +// +// For more information about caching and configuration options see +// https://bytecodealliance.github.io/wasmtime/cli-cache.html +func (cfg *Config) CacheConfigLoad(path string) error { + cstr := C.CString(path) + err := C.wasmtime_config_cache_config_load(cfg.ptr(), cstr) + C.free(unsafe.Pointer(cstr)) + runtime.KeepAlive(cfg) + if err != nil { + return mkError(err) + } + return nil +} diff --git a/config_feat_component_model.go b/config_feat_component_model.go new file mode 100644 index 0000000..371e12c --- /dev/null +++ b/config_feat_component_model.go @@ -0,0 +1,14 @@ +//go:build !min + +package wasmtime + +// #include +import "C" +import "runtime" + +// SetWasmComponentModel configures whether the wasm component model proposal is +// enabled. +func (cfg *Config) SetWasmComponentModel(enabled bool) { + C.wasmtime_config_wasm_component_model_set(cfg.ptr(), C.bool(enabled)) + runtime.KeepAlive(cfg) +} diff --git a/config_feat_cranelift.go b/config_feat_cranelift.go new file mode 100644 index 0000000..8dcd5d9 --- /dev/null +++ b/config_feat_cranelift.go @@ -0,0 +1,72 @@ +//go:build !min + +package wasmtime + +// #include +// #include +import "C" +import ( + "runtime" + "unsafe" +) + +// SetStrategy configures what compilation strategy is used to compile wasm code +func (cfg *Config) SetStrategy(strat Strategy) { + C.wasmtime_config_strategy_set(cfg.ptr(), C.wasmtime_strategy_t(strat)) + runtime.KeepAlive(cfg) +} + +// SetCraneliftDebugVerifier configures whether the cranelift debug verifier will be active when +// cranelift is used to compile wasm code. +func (cfg *Config) SetCraneliftDebugVerifier(enabled bool) { + C.wasmtime_config_cranelift_debug_verifier_set(cfg.ptr(), C.bool(enabled)) + runtime.KeepAlive(cfg) +} + +// SetCraneliftOptLevel configures the cranelift optimization level for generated code +func (cfg *Config) SetCraneliftOptLevel(level OptLevel) { + C.wasmtime_config_cranelift_opt_level_set(cfg.ptr(), C.wasmtime_opt_level_t(level)) + runtime.KeepAlive(cfg) +} + +// SetCraneliftNanCanonicalization configures whether whether Cranelift should perform a +// NaN-canonicalization pass. +// +// When Cranelift is used as a code generation backend this will configure it to replace NaNs with a single +// canonical value. This is useful for users requiring entirely deterministic WebAssembly computation. +// +// This is not required by the WebAssembly spec, so it is not enabled by default. +func (cfg *Config) SetCraneliftNanCanonicalization(enabled bool) { + C.wasmtime_config_cranelift_nan_canonicalization_set(cfg.ptr(), C.bool(enabled)) + runtime.KeepAlive(cfg) +} + +// EnableCraneliftFlag enables a target-specific flag in Cranelift. +// +// This can be used, for example, to enable SSE4.2 on x86_64 hosts. Settings can +// be explored with `wasmtime settings` on the CLI. +// +// For more information see the Rust documentation at +// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.cranelift_flag_enable +func (cfg *Config) EnableCraneliftFlag(flag string) { + cstr := C.CString(flag) + C.wasmtime_config_cranelift_flag_enable(cfg.ptr(), cstr) + C.free(unsafe.Pointer(cstr)) + runtime.KeepAlive(cfg) +} + +// SetCraneliftFlag sets a target-specific flag in Cranelift to the specified value. +// +// This can be used, for example, to enable SSE4.2 on x86_64 hosts. Settings can +// be explored with `wasmtime settings` on the CLI. +// +// For more information see the Rust documentation at +// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.cranelift_flag_set +func (cfg *Config) SetCraneliftFlag(name string, value string) { + cstrName := C.CString(name) + cstrValue := C.CString(value) + C.wasmtime_config_cranelift_flag_set(cfg.ptr(), cstrName, cstrValue) + C.free(unsafe.Pointer(cstrName)) + C.free(unsafe.Pointer(cstrValue)) + runtime.KeepAlive(cfg) +} diff --git a/config_feat_parallel_compilation.go b/config_feat_parallel_compilation.go new file mode 100644 index 0000000..c76492e --- /dev/null +++ b/config_feat_parallel_compilation.go @@ -0,0 +1,13 @@ +//go:build !min + +package wasmtime + +// #include +import "C" +import "runtime" + +// SetParallelCompilation configures whether compilation should use multiple threads +func (cfg *Config) SetParallelCompilation(enabled bool) { + C.wasmtime_config_parallel_compilation_set(cfg.ptr(), C.bool(enabled)) + runtime.KeepAlive(cfg) +} diff --git a/config_feat_threads.go b/config_feat_threads.go new file mode 100644 index 0000000..9816bfb --- /dev/null +++ b/config_feat_threads.go @@ -0,0 +1,13 @@ +//go:build !min + +package wasmtime + +// #include +import "C" +import "runtime" + +// SetWasmThreads configures whether the wasm threads proposal is enabled +func (cfg *Config) SetWasmThreads(enabled bool) { + C.wasmtime_config_wasm_threads_set(cfg.ptr(), C.bool(enabled)) + runtime.KeepAlive(cfg) +} diff --git a/config_test.go b/config_test.go index cb29f74..e878075 100644 --- a/config_test.go +++ b/config_test.go @@ -21,6 +21,7 @@ func TestConfig(t *testing.T) { NewConfig().SetWasmTailCall(true) NewConfig().SetWasmFunctionReferences(true) NewConfig().SetWasmGC(true) + NewConfig().SetGCSupport(true) NewConfig().SetWasmComponentModel(true) NewConfig().SetWasmWideArithmetic(true) NewConfig().SetConsumeFuel(true) diff --git a/doc.go b/doc.go index a37191b..df063f8 100644 --- a/doc.go +++ b/doc.go @@ -15,5 +15,14 @@ https://github.com/bytecodealliance/wasmtime-go/issues/new. It's also worth pointing out that the authors of this package up to this point primarily work in Rust, so if you've got suggestions of how to make this package more idiomatic for Go we'd love to hear your thoughts! + +# Full vs minimal Go API + +A plain `go build` includes all Go bindings, useful if using the full Wasmtime +binary. + +`go build -tags min` omits Go bindings for optional Wasmtime features. This is +necessary if using the minimal Wasmtime binary, since otherwise compilation +fails due to the missing C functions. */ package wasmtime diff --git a/ffi.go b/ffi.go index 7d47d7b..fd7ea63 100644 --- a/ffi.go +++ b/ffi.go @@ -4,11 +4,16 @@ package wasmtime // #cgo !windows LDFLAGS:-lwasmtime -lm -ldl -pthread // #cgo windows CFLAGS:-DWASM_API_EXTERN= -DWASI_API_EXTERN= // #cgo windows LDFLAGS:-lwasmtime -luserenv -lole32 -lntdll -lws2_32 -lkernel32 -lbcrypt -// #cgo linux,amd64 LDFLAGS:-L${SRCDIR}/build/linux-x86_64 -// #cgo linux,arm64 LDFLAGS:-L${SRCDIR}/build/linux-aarch64 -// #cgo darwin,amd64 LDFLAGS:-L${SRCDIR}/build/macos-x86_64 -// #cgo darwin,arm64 LDFLAGS:-L${SRCDIR}/build/macos-aarch64 -// #cgo windows,amd64 LDFLAGS:-L${SRCDIR}/build/windows-x86_64 +// #cgo linux,amd64,!min LDFLAGS:-L${SRCDIR}/build/linux-x86_64 +// #cgo linux,amd64,min LDFLAGS:-L${SRCDIR}/build/linux-x86_64-min +// #cgo linux,arm64,!min LDFLAGS:-L${SRCDIR}/build/linux-aarch64 +// #cgo linux,arm64,min LDFLAGS:-L${SRCDIR}/build/linux-aarch64-min +// #cgo darwin,amd64,!min LDFLAGS:-L${SRCDIR}/build/macos-x86_64 +// #cgo darwin,amd64,min LDFLAGS:-L${SRCDIR}/build/macos-x86_64-min +// #cgo darwin,arm64,!min LDFLAGS:-L${SRCDIR}/build/macos-aarch64 +// #cgo darwin,arm64,min LDFLAGS:-L${SRCDIR}/build/macos-aarch64-min +// #cgo windows,amd64,!min LDFLAGS:-L${SRCDIR}/build/windows-x86_64 +// #cgo windows,amd64,min LDFLAGS:-L${SRCDIR}/build/windows-x86_64-min // #include import "C" import ( diff --git a/linker.go b/linker.go index a7eb8ef..0e2f4b1 100644 --- a/linker.go +++ b/linker.go @@ -197,20 +197,6 @@ func (l *Linker) DefineModule(store Storelike, name string, module *Module) erro return mkError(err) } -// DefineWasi links a WASI module into this linker, ensuring that all exported functions -// are available for linking. -// -// Returns an error if shadowing is disabled and names are already defined. -func (l *Linker) DefineWasi() error { - err := C.wasmtime_linker_define_wasi(l.ptr()) - runtime.KeepAlive(l) - if err == nil { - return nil - } - - return mkError(err) -} - // Instantiate instantiates a module with all imports defined in this linker. // // Returns an error if the instance's imports couldn't be satisfied, had the diff --git a/linker_feat_wasi.go b/linker_feat_wasi.go new file mode 100644 index 0000000..8ad9b3e --- /dev/null +++ b/linker_feat_wasi.go @@ -0,0 +1,21 @@ +//go:build !min + +package wasmtime + +// #include +import "C" +import "runtime" + +// DefineWasi links a WASI module into this linker, ensuring that all exported functions +// are available for linking. +// +// Returns an error if shadowing is disabled and names are already defined. +func (l *Linker) DefineWasi() error { + err := C.wasmtime_linker_define_wasi(l.ptr()) + runtime.KeepAlive(l) + if err == nil { + return nil + } + + return mkError(err) +} diff --git a/module.go b/module.go index a92f05a..187a69c 100644 --- a/module.go +++ b/module.go @@ -5,7 +5,6 @@ package wasmtime import "C" import ( - "os" "runtime" "unsafe" ) @@ -17,67 +16,6 @@ type Module struct { _ptr *C.wasmtime_module_t } -// NewModule compiles a new `Module` from the `wasm` provided with the given configuration -// in `engine`. -func NewModule(engine *Engine, wasm []byte) (*Module, error) { - // We can't create the `wasm_byte_vec_t` here and pass it in because - // that runs into the error of "passed a pointer to a pointer" because - // the vec itself is passed by pointer and it contains a pointer to - // `wasm`. To work around this we insert some C shims above and call - // them. - var wasmPtr *C.uint8_t - if len(wasm) > 0 { - wasmPtr = (*C.uint8_t)(unsafe.Pointer(&wasm[0])) - } - var ptr *C.wasmtime_module_t - err := C.wasmtime_module_new(engine.ptr(), wasmPtr, C.size_t(len(wasm)), &ptr) - runtime.KeepAlive(engine) - runtime.KeepAlive(wasm) - - if err != nil { - return nil, mkError(err) - } - - return mkModule(ptr), nil -} - -// NewModuleFromFile reads the contents of the `file` provided and interprets them as either the -// text format or the binary format for WebAssembly. -// -// Afterwards delegates to the `NewModule` constructor with the contents read. -func NewModuleFromFile(engine *Engine, file string) (*Module, error) { - wasm, err := os.ReadFile(file) - if err != nil { - return nil, err - } - // If this wasm isn't actually wasm, treat it as the text format and - // parse it as such. - if len(wasm) > 0 && wasm[0] != 0 { - wasm, err = Wat2Wasm(string(wasm)) - if err != nil { - return nil, err - } - } - return NewModule(engine, wasm) -} - -// ModuleValidate validates whether `wasm` would be a valid wasm module according to the -// configuration in `store` -func ModuleValidate(engine *Engine, wasm []byte) error { - var wasmPtr *C.uint8_t - if len(wasm) > 0 { - wasmPtr = (*C.uint8_t)(unsafe.Pointer(&wasm[0])) - } - err := C.wasmtime_module_validate(engine.ptr(), wasmPtr, C.size_t(len(wasm))) - runtime.KeepAlive(engine) - runtime.KeepAlive(wasm) - if err == nil { - return nil - } - - return mkError(err) -} - func mkModule(ptr *C.wasmtime_module_t) *Module { module := &Module{_ptr: ptr} runtime.SetFinalizer(module, func(module *Module) { @@ -216,23 +154,3 @@ func NewModuleDeserializeFile(engine *Engine, path string) (*Module, error) { return mkModule(ptr), nil } - -// Serialize will convert this in-memory compiled module into a list of bytes. -// -// The purpose of this method is to extract an artifact which can be stored -// elsewhere from this `Module`. The returned bytes can, for example, be stored -// on disk or in an object store. The `NewModuleDeserialize` function can be -// used to deserialize the returned bytes at a later date to get the module -// back. -func (m *Module) Serialize() ([]byte, error) { - retVec := C.wasm_byte_vec_t{} - err := C.wasmtime_module_serialize(m.ptr(), &retVec) - runtime.KeepAlive(m) - - if err != nil { - return nil, mkError(err) - } - ret := C.GoBytes(unsafe.Pointer(retVec.data), C.int(retVec.size)) - C.wasm_byte_vec_delete(&retVec) - return ret, nil -} diff --git a/module_feat_cranelift.go b/module_feat_cranelift.go new file mode 100644 index 0000000..0e87e77 --- /dev/null +++ b/module_feat_cranelift.go @@ -0,0 +1,72 @@ +//go:build !min + +package wasmtime + +// #include "shims.h" +import "C" + +import ( + "runtime" + "unsafe" +) + +// NewModule compiles a new `Module` from the `wasm` provided with the given configuration +// in `engine`. +func NewModule(engine *Engine, wasm []byte) (*Module, error) { + // We can't create the `wasm_byte_vec_t` here and pass it in because + // that runs into the error of "passed a pointer to a pointer" because + // the vec itself is passed by pointer and it contains a pointer to + // `wasm`. To work around this we insert some C shims above and call + // them. + var wasmPtr *C.uint8_t + if len(wasm) > 0 { + wasmPtr = (*C.uint8_t)(unsafe.Pointer(&wasm[0])) + } + var ptr *C.wasmtime_module_t + err := C.wasmtime_module_new(engine.ptr(), wasmPtr, C.size_t(len(wasm)), &ptr) + runtime.KeepAlive(engine) + runtime.KeepAlive(wasm) + + if err != nil { + return nil, mkError(err) + } + + return mkModule(ptr), nil +} + +// ModuleValidate validates whether `wasm` would be a valid wasm module according to the +// configuration in `store` +func ModuleValidate(engine *Engine, wasm []byte) error { + var wasmPtr *C.uint8_t + if len(wasm) > 0 { + wasmPtr = (*C.uint8_t)(unsafe.Pointer(&wasm[0])) + } + err := C.wasmtime_module_validate(engine.ptr(), wasmPtr, C.size_t(len(wasm))) + runtime.KeepAlive(engine) + runtime.KeepAlive(wasm) + if err == nil { + return nil + } + + return mkError(err) +} + +// Serialize will convert this in-memory compiled module into a list of bytes. +// +// The purpose of this method is to extract an artifact which can be stored +// elsewhere from this `Module`. The returned bytes can, for example, be stored +// on disk or in an object store. The `NewModuleDeserialize` function can be +// used to deserialize the returned bytes at a later date to get the module +// back. +func (m *Module) Serialize() ([]byte, error) { + retVec := C.wasm_byte_vec_t{} + err := C.wasmtime_module_serialize(m.ptr(), &retVec) + runtime.KeepAlive(m) + + if err != nil { + return nil, mkError(err) + } + ret := C.GoBytes(unsafe.Pointer(retVec.data), C.int(retVec.size)) + C.wasm_byte_vec_delete(&retVec) + return ret, nil +} diff --git a/module_feats_cranelift_wat.go b/module_feats_cranelift_wat.go new file mode 100644 index 0000000..4db0e9b --- /dev/null +++ b/module_feats_cranelift_wat.go @@ -0,0 +1,25 @@ +//go:build !min + +package wasmtime + +import "os" + +// NewModuleFromFile reads the contents of the `file` provided and interprets them as either the +// text format or the binary format for WebAssembly. +// +// Afterwards delegates to the `NewModule` constructor with the contents read. +func NewModuleFromFile(engine *Engine, file string) (*Module, error) { + wasm, err := os.ReadFile(file) + if err != nil { + return nil, err + } + // If this wasm isn't actually wasm, treat it as the text format and + // parse it as such. + if len(wasm) > 0 && wasm[0] != 0 { + wasm, err = Wat2Wasm(string(wasm)) + if err != nil { + return nil, err + } + } + return NewModule(engine, wasm) +} diff --git a/store.go b/store.go index 3340fa5..fef28c4 100644 --- a/store.go +++ b/store.go @@ -136,19 +136,6 @@ func (store *Store) GC() { runtime.KeepAlive(store) } -// SetWasi will configure the WASI state to use for instances within this -// `Store`. -// -// The `wasi` argument cannot be reused for another `Store`, it's consumed by -// this function. -func (store *Store) SetWasi(wasi *WasiConfig) { - runtime.SetFinalizer(wasi, nil) - ptr := wasi.ptr() - wasi._ptr = nil - C.wasmtime_context_set_wasi(store.Context(), ptr) - runtime.KeepAlive(store) -} - // Implementation of the `Storelike` interface func (store *Store) Context() *C.wasmtime_context_t { ret := C.wasmtime_store_context(store.ptr()) diff --git a/store_feat_wasi.go b/store_feat_wasi.go new file mode 100644 index 0000000..4fdc366 --- /dev/null +++ b/store_feat_wasi.go @@ -0,0 +1,20 @@ +//go:build !min + +package wasmtime + +// #include +import "C" +import "runtime" + +// SetWasi will configure the WASI state to use for instances within this +// `Store`. +// +// The `wasi` argument cannot be reused for another `Store`, it's consumed by +// this function. +func (store *Store) SetWasi(wasi *WasiConfig) { + runtime.SetFinalizer(wasi, nil) + ptr := wasi.ptr() + wasi._ptr = nil + C.wasmtime_context_set_wasi(store.Context(), ptr) + runtime.KeepAlive(store) +} diff --git a/wasi.go b/wasi.go index da4bc61..d27eda4 100644 --- a/wasi.go +++ b/wasi.go @@ -1,3 +1,5 @@ +//go:build !min + package wasmtime // #include diff --git a/wat2wasm.go b/wat2wasm.go index 735db5c..ea618bc 100644 --- a/wat2wasm.go +++ b/wat2wasm.go @@ -1,3 +1,5 @@ +//go:build !min + package wasmtime // #include