-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[x/programs] Add Engine and Store abstractions (#657)
* Move fixtures into tests directory Signed-off-by: Sam Batschelet <[email protected]> * Abstract engine from runtime Signed-off-by: Sam Batschelet <[email protected]> * React to changes Signed-off-by: Sam Batschelet <[email protected]> * Nits Signed-off-by: Sam Batschelet <[email protected]> * Review comments Signed-off-by: Sam Batschelet <[email protected]> * Clarify debug mode Signed-off-by: Sam Batschelet <[email protected]> * Nit Signed-off-by: Sam Batschelet <[email protected]> --------- Signed-off-by: Sam Batschelet <[email protected]>
- Loading branch information
Showing
22 changed files
with
1,044 additions
and
659 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package engine | ||
|
||
import ( | ||
"github.com/ava-labs/avalanchego/utils/units" | ||
|
||
"github.com/bytecodealliance/wasmtime-go/v14" | ||
) | ||
|
||
type CompileStrategy uint8 | ||
|
||
const ( | ||
// CompileWasm will compile the wasm module before instantiating it. | ||
CompileWasm CompileStrategy = iota | ||
// PrecompiledWasm accepts a precompiled wasm module serialized by an Engine. | ||
PrecompiledWasm | ||
) | ||
|
||
var ( | ||
DefaultMaxWasmStack = 256 * units.MiB // 256 MiB | ||
DefaultLimitMaxMemory = uint32(18 * 64 * units.KiB) // 18 pages | ||
DefaultSIMD = false | ||
DefaultEnableReferenceTypes = false | ||
DefaultEnableBulkMemory = false | ||
DefaultProfilingStrategy = wasmtime.ProfilingStrategyNone | ||
DefaultMultiValue = false | ||
DefaultCompileStrategy = CompileWasm | ||
|
||
defaultWasmThreads = false | ||
defaultFuelMetering = true | ||
defaultWasmMultiMemory = false | ||
defaultWasmMemory64 = false | ||
defaultCompilerStrategy = wasmtime.StrategyCranelift | ||
defaultEpochInterruption = true | ||
defaultNaNCanonicalization = "true" | ||
defaultCraneliftOptLevel = wasmtime.OptLevelSpeed | ||
defaultEnableCraneliftDebugVerifier = false | ||
defaultEnableDebugInfo = false | ||
) | ||
|
||
// NewConfig creates a new engine config with default settings | ||
func NewConfig() *Config { | ||
return &Config{ | ||
wasmConfig: DefaultWasmtimeConfig(), | ||
} | ||
} | ||
|
||
// Config is wrapper for wasmtime.Config | ||
type Config struct { | ||
wasmConfig *wasmtime.Config | ||
} | ||
|
||
// Get returns the underlying wasmtime config. | ||
func (c *Config) Get() *wasmtime.Config { | ||
return c.wasmConfig | ||
} | ||
|
||
// CacheConfigLoad enables compiled code caching for this `Config` using the | ||
// settings specified in the configuration file `path`. | ||
func (c *Config) CacheConfigLoad(path string) error { | ||
return c.wasmConfig.CacheConfigLoad(path) | ||
} | ||
|
||
// CacheConfigLoadDefault enables compiled code caching for this `Config` using | ||
// the default settings configuration can be found. | ||
func (c *Config) CacheConfigLoadDefault() error { | ||
return c.wasmConfig.CacheConfigLoadDefault() | ||
} | ||
|
||
// EnableCraneliftFlag enables a target-specific flag in Cranelift. | ||
func (c *Config) EnableCraneliftFlag(flag string) { | ||
c.wasmConfig.EnableCraneliftFlag(flag) | ||
} | ||
|
||
// SetConsumFuel configures whether fuel is enabled. | ||
func (c *Config) SetConsumeFuel(enabled bool) { | ||
c.wasmConfig.SetConsumeFuel(enabled) | ||
} | ||
|
||
// SetCraneliftDebugVerifier configures whether the cranelift debug verifier | ||
// will be active when cranelift is used to compile wasm code. | ||
func (c *Config) SetCraneliftDebugVerifier(enabled bool) { | ||
c.wasmConfig.SetCraneliftDebugVerifier(enabled) | ||
} | ||
|
||
// SetCraneliftFlag sets a target-specific flag in Cranelift to the specified value. | ||
func (c *Config) SetCraneliftFlag(name string, value string) { | ||
c.wasmConfig.SetCraneliftFlag(name, value) | ||
} | ||
|
||
// SetCraneliftOptLevel configures the cranelift optimization level for generated code. | ||
func (c *Config) SetCraneliftOptLevel(level wasmtime.OptLevel) { | ||
c.wasmConfig.SetCraneliftOptLevel(level) | ||
} | ||
|
||
// SetDebugInfo configures whether dwarf debug information for JIT code is enabled | ||
func (c *Config) SetDebugInfo(enabled bool) { | ||
c.wasmConfig.SetDebugInfo(enabled) | ||
} | ||
|
||
// SetEpochInterruption enables epoch-based instrumentation of generated code to | ||
// interrupt WebAssembly execution when the current engine epoch exceeds a | ||
// defined threshold. | ||
func (c *Config) SetEpochInterruption(enable bool) { | ||
c.wasmConfig.SetEpochInterruption(enable) | ||
} | ||
|
||
// SetMaxWasmStack configures the maximum stack size, in bytes, that JIT code can use. | ||
func (c *Config) SetMaxWasmStack(size int) { | ||
c.wasmConfig.SetMaxWasmStack(size) | ||
} | ||
|
||
// SetProfiler configures what profiler strategy to use for generated code. | ||
func (c *Config) SetProfiler(profiler wasmtime.ProfilingStrategy) { | ||
c.wasmConfig.SetProfiler(profiler) | ||
} | ||
|
||
// SetStrategy configures what compilation strategy is used to compile wasm code. | ||
func (c *Config) SetStrategy(strategy wasmtime.Strategy) { | ||
c.wasmConfig.SetStrategy(strategy) | ||
} | ||
|
||
// SetTarget configures the target triple that this configuration will produce machine code for. | ||
func (c *Config) SetTarget(target string) error { | ||
return c.wasmConfig.SetTarget(target) | ||
} | ||
|
||
// SetWasmBulkMemory configures whether the wasm bulk memory proposal is enabled. | ||
func (c *Config) SetWasmBulkMemory(enabled bool) { | ||
c.wasmConfig.SetWasmBulkMemory(enabled) | ||
} | ||
|
||
// SetWasmMemory64 configures whether the wasm memory64 proposal is enabled. | ||
func (c *Config) SetWasmMemory64(enabled bool) { | ||
c.wasmConfig.SetWasmMemory64(enabled) | ||
} | ||
|
||
// SetWasmMultiMemory configures whether the wasm multi memory proposal is enabled. | ||
func (c *Config) SetWasmMultiMemory(enabled bool) { | ||
c.wasmConfig.SetWasmMultiMemory(enabled) | ||
} | ||
|
||
// SetWasmMultiValue configures whether the wasm multi value proposal is enabled. | ||
func (c *Config) SetWasmMultiValue(enabled bool) { | ||
c.wasmConfig.SetWasmMultiValue(enabled) | ||
} | ||
|
||
// SetWasmReferenceTypes configures whether the wasm reference types proposal is enabled. | ||
func (c *Config) SetWasmReferenceTypes(enabled bool) { | ||
c.wasmConfig.SetWasmReferenceTypes(enabled) | ||
} | ||
|
||
// SetWasmSIMD configures whether the wasm SIMD proposal is enabled. | ||
func (c *Config) SetWasmSIMD(enabled bool) { | ||
c.wasmConfig.SetWasmSIMD(enabled) | ||
} | ||
|
||
// SetWasmThreads configures whether the wasm threads proposal is enabled. | ||
func (c *Config) SetWasmThreads(enabled bool) { | ||
c.wasmConfig.SetWasmThreads(enabled) | ||
} | ||
|
||
// DefaultWasmtimeConfig returns a new wasmtime config with default settings. | ||
func DefaultWasmtimeConfig() *wasmtime.Config { | ||
cfg := wasmtime.NewConfig() | ||
|
||
// non configurable defaults | ||
cfg.SetCraneliftOptLevel(defaultCraneliftOptLevel) | ||
cfg.SetConsumeFuel(defaultFuelMetering) | ||
cfg.SetWasmThreads(defaultWasmThreads) | ||
cfg.SetWasmMultiMemory(defaultWasmMultiMemory) | ||
cfg.SetWasmMemory64(defaultWasmMemory64) | ||
cfg.SetStrategy(defaultCompilerStrategy) | ||
cfg.SetEpochInterruption(defaultEpochInterruption) | ||
cfg.SetCraneliftFlag("enable_nan_canonicalization", defaultNaNCanonicalization) | ||
|
||
// TODO: expose these knobs for developers | ||
cfg.SetCraneliftDebugVerifier(defaultEnableCraneliftDebugVerifier) | ||
cfg.SetDebugInfo(defaultEnableDebugInfo) | ||
return cfg | ||
} | ||
|
||
// NewConfigBuilder returns a new engine configuration builder with default settings. | ||
// All instances of ConfigBuilder should be created with this constructor. | ||
func NewConfigBuilder() *ConfigBuilder { | ||
return &ConfigBuilder{ | ||
EnableBulkMemory: DefaultEnableBulkMemory, | ||
EnableWasmMultiValue: DefaultMultiValue, | ||
EnableWasmReferenceTypes: DefaultEnableReferenceTypes, | ||
EnableWasmSIMD: DefaultSIMD, | ||
MaxWasmStack: DefaultMaxWasmStack, | ||
ProfilingStrategy: DefaultProfilingStrategy, | ||
EnableDefaultCache: false, | ||
} | ||
} | ||
|
||
type ConfigBuilder struct { | ||
// Configures whether the WebAssembly bulk memory operations proposal will | ||
// be enabled for compilation. This feature gates items such as the | ||
// memory.copy instruction, passive data/table segments, etc, being in a | ||
// module. | ||
// This is false by default. | ||
EnableBulkMemory bool `yaml:"enable_bulk_memory,omitempty" json:"enableBulkMemory,omitempty"` | ||
// Configures whether the WebAssembly multi-value proposal will be enabled for compilation. | ||
// This feature gates functions and blocks returning multiple values in a module, for example. | ||
// This is false by default. | ||
EnableWasmMultiValue bool `yaml:"enable_wasm_multi_value,omitempty" json:"enableWasmMultiValue,omitempty"` | ||
// Configures whether the WebAssembly reference types proposal will be | ||
// enabled for compilation. This feature gates items such as the externref | ||
// and funcref types as well as allowing a module to define multiple tables. | ||
// Note that the reference types proposal depends on the bulk memory | ||
// proposal. | ||
// This is false by default. | ||
EnableWasmReferenceTypes bool `yaml:"enable_wasm_reference_types,omitempty" json:"enableWasmReferenceTypes,omitempty"` | ||
// Configures whether the WebAssembly SIMD proposal will be enabled for | ||
// compilation. The WebAssembly SIMD proposal. This feature gates items | ||
// such as the v128 type and all of its operators being in a module. Note | ||
// that this does not enable the relaxed simd proposal. | ||
// This is false by default. | ||
EnableWasmSIMD bool `yaml:"enable_wasm_simd,omitempty" json:"enableWasmSIMD,omitempty"` | ||
// EnableDefaultCache 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 | ||
// This is false by default. | ||
EnableDefaultCache bool `yaml:"enable_default_cache,omitempty" json:"enableDefaultCache,omitempty"` | ||
// SetMaxWasmStack configures the maximum stack size, in bytes, that JIT code can use. | ||
// The amount of stack space that wasm takes is always relative to the first invocation of wasm on the stack. | ||
// Recursive calls with host frames in the middle will all need to fit within this setting. | ||
// Note that this setting is not interpreted with 100% precision. | ||
// This is 256 MiB by default. | ||
MaxWasmStack int `yaml:"max_wasm_stack,omitempty" json:"maxWasmStack,omitempty"` | ||
// ProfilingStrategy decides what sort of profiling to enable, if any. | ||
// Default is `wasmtime.ProfilingStrategyNone`. | ||
ProfilingStrategy wasmtime.ProfilingStrategy | ||
} | ||
|
||
// WithMaxWasmStack defines the maximum amount of stack space available for | ||
// executing WebAssembly code. | ||
// | ||
// Default is 256 MiB. | ||
func (c *ConfigBuilder) WithMaxWasmStack(max int) *ConfigBuilder { | ||
c.MaxWasmStack = max | ||
return c | ||
} | ||
|
||
// WithMultiValue enables modules that can return multiple values. | ||
// ref. https://github.com/webassembly/multi-value | ||
// | ||
// Default is false. | ||
func (c *ConfigBuilder) WithMultiValue(enable bool) *ConfigBuilder { | ||
c.EnableWasmMultiValue = enable | ||
return c | ||
} | ||
|
||
// WithBulkMemory enables `memory.copy` instruction, tables and passive data. | ||
// ref. https://github.com/WebAssembly/bulk-memory-operations | ||
// | ||
// Default is false. | ||
func (c *ConfigBuilder) WithBulkMemory(enable bool) *ConfigBuilder { | ||
c.EnableBulkMemory = enable | ||
return c | ||
} | ||
|
||
// WithReferenceTypes Enables the `externref` and `funcref` types as well as | ||
// allowing a module to define multiple tables. | ||
// ref. https://github.com/webassembly/reference-types | ||
// | ||
// Note: depends on bulk memory being enabled. | ||
// | ||
// Default is false. | ||
func (c *ConfigBuilder) WithReferenceTypes(enable bool) *ConfigBuilder { | ||
c.EnableWasmReferenceTypes = enable | ||
return c | ||
} | ||
|
||
// WithSIMD enables SIMD instructions including v128. | ||
// ref. https://github.com/webassembly/simd | ||
// | ||
// Default is false. | ||
func (c *ConfigBuilder) WithSIMD(enable bool) *ConfigBuilder { | ||
c.EnableWasmSIMD = enable | ||
return c | ||
} | ||
|
||
// WithProfilingStrategy defines the profiling strategy used for defining the | ||
// default profiler. | ||
// | ||
// Default is `wasmtime.ProfilingStrategyNone`. | ||
func (c *ConfigBuilder) WithProfilingStrategy(strategy wasmtime.ProfilingStrategy) *ConfigBuilder { | ||
c.ProfilingStrategy = strategy | ||
return c | ||
} | ||
|
||
// WithDefaultCache enables the default caching strategy. | ||
// | ||
// Default is false. | ||
func (c *ConfigBuilder) WithDefaultCache(enabled bool) *ConfigBuilder { | ||
c.EnableDefaultCache = enabled | ||
return c | ||
} | ||
|
||
func (c *ConfigBuilder) Build() (*Config, error) { | ||
cfg := NewConfig() | ||
cfg.SetWasmBulkMemory(c.EnableBulkMemory) | ||
cfg.SetWasmMultiValue(c.EnableWasmMultiValue) | ||
cfg.SetWasmReferenceTypes(c.EnableWasmReferenceTypes) | ||
cfg.SetWasmSIMD(c.EnableWasmSIMD) | ||
cfg.SetMaxWasmStack(c.MaxWasmStack) | ||
cfg.SetProfiler(c.ProfilingStrategy) | ||
if c.EnableDefaultCache { | ||
if err := cfg.CacheConfigLoadDefault(); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
return cfg, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package engine | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/bytecodealliance/wasmtime-go/v14" | ||
) | ||
|
||
// Engine is a wrapper around a wasmtime.Engine and manages the lifecycle of a | ||
// programs execution. It is expected that a single engine can have multiple | ||
// stores. For example in the case of program to program calls. | ||
type Engine struct { | ||
wasmEngine *wasmtime.Engine | ||
} | ||
|
||
// New creates a new Wasm engine. | ||
func New(cfg *Config) *Engine { | ||
return &Engine{ | ||
wasmEngine: wasmtime.NewEngineWithConfig(cfg.Get()), | ||
} | ||
} | ||
|
||
// Stop will increase the current epoch number by 1 within the current | ||
// engine which will cause any connected stores to be interrupted. | ||
func (e *Engine) Stop() { | ||
e.wasmEngine.IncrementEpoch() | ||
} | ||
|
||
// PreCompileModule will deserialize a precompiled module. | ||
func (e *Engine) PreCompileModule(bytes []byte) (*wasmtime.Module, error) { | ||
// Note: that to deserialize successfully the bytes provided must have been | ||
// produced with an `Engine` that has the same compilation options as the | ||
// provided engine, and from the same version of this library. | ||
// | ||
// A precompile is not something we would store on chain. | ||
// Instead we would prefetch programs and precompile them. | ||
return wasmtime.NewModuleDeserialize(e.wasmEngine, bytes) | ||
} | ||
|
||
// CompileModule will compile a module. | ||
func (e *Engine) CompileModule(bytes []byte) (*wasmtime.Module, error) { | ||
return wasmtime.NewModule(e.wasmEngine, bytes) | ||
} | ||
|
||
// PreCompileWasm returns a precompiled wasm module. | ||
// | ||
// Note: these bytes can be deserialized by an `Engine` that has the same version. | ||
// For that reason precompiled wasm modules should not be stored on chain. | ||
func PreCompileWasmBytes(engine *Engine, programBytes []byte, limitMaxMemory uint32) ([]byte, error) { | ||
store := NewStore(engine, NewStoreConfig().SetLimitMaxMemory(limitMaxMemory)) | ||
module, err := wasmtime.NewModule(store.GetEngine(), programBytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return module.Serialize() | ||
} | ||
|
||
// NewModule creates a new wasmtime module and handles the Wasm bytes based on compile strategy. | ||
func NewModule(engine *Engine, bytes []byte, strategy CompileStrategy) (*wasmtime.Module, error) { | ||
switch strategy { | ||
case CompileWasm: | ||
return engine.CompileModule(bytes) | ||
case PrecompiledWasm: | ||
return engine.PreCompileModule(bytes) | ||
default: | ||
return nil, fmt.Errorf("unknown compile strategy") | ||
} | ||
} |
Oops, something went wrong.