Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[x/programs] add program abstraction #660

Merged
merged 29 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c71e603
Move fixtures into tests directory
hexfusion Dec 14, 2023
903fefe
Abstract engine from runtime
hexfusion Dec 14, 2023
ff0adb6
React to changes
hexfusion Dec 14, 2023
cea5ea5
Nits
hexfusion Dec 15, 2023
f25a144
Review comments
hexfusion Dec 21, 2023
29d3bbe
Clarify debug mode
hexfusion Dec 21, 2023
a75af73
Nit
hexfusion Dec 21, 2023
3d03b4c
Add host abstraction
hexfusion Dec 15, 2023
2b7ba48
React to changes
hexfusion Dec 15, 2023
3ac1a3f
Cleanup
hexfusion Dec 21, 2023
195c96a
Move program from runtime
hexfusion Dec 22, 2023
af1201b
Add program abstraction
hexfusion Dec 22, 2023
597ff1a
Implement runtime instance and react to changes
hexfusion Dec 22, 2023
2fbdc8e
Nit
hexfusion Dec 22, 2023
fa5565d
Add RegisterImportWrapFn
hexfusion Dec 22, 2023
7485e7c
Update examples
hexfusion Dec 22, 2023
e1bbfc4
Show example of wrap import function impl
hexfusion Dec 22, 2023
8dbe3ed
Remove stale docs
hexfusion Dec 22, 2023
678a8cb
Impliment pstate with wrap functionality
hexfusion Dec 22, 2023
7e5f311
license header lint
samliok Dec 27, 2023
95aa598
remove hardcoded wrapper funcitons, fixed pstate bugs
samliok Jan 6, 2024
13ff0a6
lint
samliok Jan 6, 2024
cee952c
lint
samliok Jan 6, 2024
5c359d9
Merge branch 'dynamic_args' into refactor-statekey
samliok Jan 6, 2024
15b84b5
Revert "Merge branch 'dynamic_args' into refactor-statekey"
samliok Jan 6, 2024
3a23ebb
Revert "Revert "Merge branch 'dynamic_args' into refactor-statekey""
samliok Jan 6, 2024
7f323b2
lint
samliok Jan 6, 2024
b5e64fb
memory tests
samliok Jan 6, 2024
d175d2c
create WAT program instead of memory.wasm
samliok Jan 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement runtime instance and react to changes
Signed-off-by: Sam Batschelet <sam.batschelet@avalabs.org>
  • Loading branch information
hexfusion committed Dec 22, 2023
commit 597ff1a5a09326e6c70c8a7b782818821b362d4c
36 changes: 5 additions & 31 deletions x/programs/runtime/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ package runtime
import (
"context"

"github.com/bytecodealliance/wasmtime-go/v14"

"github.com/ava-labs/hypersdk/x/programs/engine"
"github.com/ava-labs/hypersdk/x/programs/program"
)

type Runtime interface {
Expand All @@ -19,36 +18,11 @@ type Runtime interface {
// Call invokes an exported guest function with the given parameters.
// Returns the results of the call or an error if the call failed.
// If the function called does not return a result this value is set to nil.
Call(context.Context, string, ...SmartPtr) ([]int64, error)
// Memory returns the runtime memory.
Memory() Memory
// Meter returns the runtime meter.
Call(context.Context, string, ...program.SmartPtr) ([]int64, error)
// Memory returns the program memory.
Memory() (*program.Memory, error)
// Meter returns the engine meter.
Meter() *engine.Meter
// Stop stops the runtime.
Stop()
}

// TODO: abstract client interface so that the client doesn't need to be runtime specific/dependent.
type WasmtimeExportClient interface {
// GetExportedFunction returns a function exported by the guest module.
ExportedFunction(string) (*wasmtime.Func, error)
// GetExportedMemory returns the memory exported by the guest module.
GetMemory() (*wasmtime.Memory, error)
// GetExportedTable returns the store exported by the guest module.
Store() wasmtime.Storelike
}

// Memory defines the interface for interacting with memory.
type Memory interface {
// Range returns an owned slice of data from a specified offset.
Range(uint64, uint64) ([]byte, error)
// Alloc allocates a block of memory and returns a pointer
// (offset) to its location on the stack.
Alloc(uint64) (uint64, error)
// Write writes the given data to the memory at the given offset.
Write(uint64, []byte) error
// Len returns the length of this memory in bytes.
Len() (uint64, error)
// Grow increases the size of the memory pages by delta.
Grow(uint64) (uint64, error)
}
75 changes: 75 additions & 0 deletions x/programs/runtime/instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package runtime

import (
"fmt"

"github.com/ava-labs/hypersdk/x/programs/engine"
"github.com/ava-labs/hypersdk/x/programs/program"
"github.com/bytecodealliance/wasmtime-go/v14"
)

var _ program.Instance = (*Instance)(nil)

// NewInstance creates a new instance wrapper.
func NewInstance(store *engine.Store, inner *wasmtime.Instance) *Instance {
return &Instance{
inner: inner,
store: store.Get(),
}
}

// Instance is a wrapper around a wasmtime.Instance
type Instance struct {
inner *wasmtime.Instance
store wasmtime.Storelike
}

func (i *Instance) GetFunc(name string) (*program.Func, error) {
exp, err := i.GetExport(program.FuncName(name))
if err != nil {
return nil, err
}

fn, err := exp.Func()
if err != nil {
return nil, err
}

return program.NewFunc(fn, i.store), nil
}

func (i *Instance) GetExport(name string) (*program.Export, error) {
exp := i.inner.GetExport(i.store, name)
if exp == nil {
return nil, fmt.Errorf("failed to create export %w: %s", program.ErrInvalidType, name)
}

return program.NewExport(exp), nil
}

func (i *Instance) Memory() (*program.Memory, error) {
exp, err := i.GetExport(program.MemoryFnName)
if err != nil {
return nil, err
}

mem, err := exp.Memory()
if err != nil {
return nil, err
}

exp, err = i.GetExport(program.AllocFnName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we require the instance to export an alloc function here, might make sense to make this optional

if err != nil {
return nil, err
}

allocFn, err := exp.Func()
if err != nil {
return nil, err
}

return program.NewMemory(mem, allocFn, i.store), nil
}
101 changes: 20 additions & 81 deletions x/programs/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ package runtime

import (
"context"
"fmt"
"sync"

"github.com/bytecodealliance/wasmtime-go/v14"

"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/hypersdk/x/programs/engine"
"github.com/ava-labs/hypersdk/x/programs/host"
"github.com/ava-labs/hypersdk/x/programs/program"
)

var _ Runtime = &WasmRuntime{}
Expand All @@ -33,19 +33,15 @@ func New(
}

type WasmRuntime struct {
engine *engine.Engine
store *engine.Store
inst *wasmtime.Instance
mod *wasmtime.Module
exp WasmtimeExportClient
meter *engine.Meter
cfg *Config
engine *engine.Engine
meter *engine.Meter
inst program.Instance
imports host.SupportedImports
cfg *Config

once sync.Once
cancelFn context.CancelFunc

imports host.SupportedImports

log logging.Logger
}

Expand All @@ -60,96 +56,60 @@ func (r *WasmRuntime) Initialize(ctx context.Context, programBytes []byte, maxUn
// create store
cfg := engine.NewStoreConfig()
cfg.SetLimitMaxMemory(r.cfg.LimitMaxMemory)
r.store = engine.NewStore(r.engine, cfg)
store := engine.NewStore(r.engine, cfg)

// enable wasi logging support only in debug mode
if r.cfg.EnableDebugMode {
wasiConfig := wasmtime.NewWasiConfig()
wasiConfig.InheritStderr()
wasiConfig.InheritStdout()
r.store.SetWasi(wasiConfig)
store.SetWasi(wasiConfig)
}

// add metered units to the store
err = r.store.AddUnits(maxUnits)
err = store.AddUnits(maxUnits)
if err != nil {
return err
}

// setup metering
r.meter, err = engine.NewMeter(r.store)
r.meter, err = engine.NewMeter(store)
if err != nil {
return err
}

// create module
r.mod, err = engine.NewModule(r.engine, programBytes, r.cfg.CompileStrategy)
mod, err := engine.NewModule(r.engine, programBytes, r.cfg.CompileStrategy)
if err != nil {
return err
}

// create linker
link := host.NewLink(r.log, r.store.GetEngine(), r.imports, r.meter, r.cfg.EnableDebugMode)
link := host.NewLink(r.log, store.GetEngine(), r.imports, r.meter, r.cfg.EnableDebugMode)

// instantiate the module with all of the imports defined by the linker
r.inst, err = link.Instantiate(r.store, r.mod, r.cfg.ImportFnCallback)
inst, err := link.Instantiate(store, mod, r.cfg.ImportFnCallback)
if err != nil {
return err
}

// setup client capable of calling exported functions
r.exp = newExportClient(r.inst, r.store.Get())
// set the instance
r.inst = NewInstance(store, inst)

return nil
}

func (r *WasmRuntime) Call(_ context.Context, name string, params ...SmartPtr) ([]int64, error) {
var fnName string
switch name {
case AllocFnName, DeallocFnName, MemoryFnName:
fnName = name
default:
// the SDK will append the guest suffix to the function name
fnName = name + guestSuffix
}

fn := r.inst.GetFunc(r.store.Get(), fnName)
if fn == nil {
return nil, fmt.Errorf("%w: %s", ErrMissingExportedFunction, name)
}

fnParams := fn.Type(r.store.Get()).Params()
if len(params) != len(fnParams) {
return nil, fmt.Errorf("%w for function %s: %d expected: %d", ErrInvalidParamCount, name, len(params), len(fnParams))
}

callParams, err := mapFunctionParams(params, fnParams)
func (r *WasmRuntime) Call(_ context.Context, name string, params ...program.SmartPtr) ([]int64, error) {
fn, err := r.inst.GetFunc(name)
if err != nil {
return nil, err
}

result, err := fn.Call(r.store.Get(), callParams...)
if err != nil {
return nil, fmt.Errorf("export function call failed %s: %w", name, handleTrapError(err))
}

switch v := result.(type) {
case int32:
value := int64(result.(int32))
return []int64{value}, nil
case int64:
value := result.(int64)
return []int64{value}, nil
case nil:
// the function had no return values
return nil, nil
default:
return nil, fmt.Errorf("invalid result type: %v", v)
}
return fn.Call(params...)
}

func (r *WasmRuntime) Memory() Memory {
return NewMemory(newExportClient(r.inst, r.store.Get()))
func (r *WasmRuntime) Memory() (*program.Memory, error) {
return r.inst.Memory()
}

func (r *WasmRuntime) Meter() *engine.Meter {
Expand All @@ -164,24 +124,3 @@ func (r *WasmRuntime) Stop() {
r.cancelFn()
})
}

// mapFunctionParams maps call input to the expected wasm function params.
func mapFunctionParams(input []SmartPtr, values []*wasmtime.ValType) ([]interface{}, error) {
params := make([]interface{}, len(values))
for i, v := range values {
switch v.Kind() {
case wasmtime.KindI32:
// ensure this value is within the range of an int32
if !EnsureIntToInt32(int(input[i])) {
return nil, fmt.Errorf("%w: %d", ErrIntegerConversionOverflow, input[i])
}
params[i] = int32(input[i])
case wasmtime.KindI64:
params[i] = int64(input[i])
default:
return nil, fmt.Errorf("%w: %v", ErrInvalidParamType, v.Kind())
}
}

return params, nil
}
Loading
Loading