Skip to content

Commit

Permalink
pack guest return data (#21)
Browse files Browse the repository at this point in the history
* pack guest return data

* fix guest func return

* code cleanup

* update README

* change Read signature

* rename packeDataArray

* MultiPackedData

* packedDataArray

* simplified mdk

* naming refactoring

* refactoring

* remove auto freeing memories from

* remove allocationMAps from hf

* remove allocationMAp pkg

* test

* comment tests

* add xPack methods

* fix tests

* update README
  • Loading branch information
LukaGiorgadze authored Nov 4, 2023
1 parent 4901623 commit 1157257
Show file tree
Hide file tree
Showing 19 changed files with 978 additions and 849 deletions.
99 changes: 68 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

wasify simplifies communication with the WebAssembly System Interface (WASI), eliminating the need for developers to delve into intricate details or communicate using numbers, which is the standard method of interaction with modules. This library significantly eases the process of running and working with wasm modules, which has traditionally been a less straightforward task.


---

> [!WARNING]
> - **The project is still being worked on, and there might be significant changes coming;**
> - Requires Go v1.21
>
> - **The project is still being worked on, and there might be significant changes coming;**
> - Requires Go v1.21
---

## Installation

```bash
Expand Down Expand Up @@ -46,34 +48,56 @@ func main() {
defer runtime.Close(ctx)

module, _ := runtime.NewModule(ctx, &wasify.ModuleConfig{
Name: "myEnv",
Name: "host_all_available_types",
Wasm: wasify.Wasm{
Binary: moduleData,
},
HostFunctions: []wasify.HostFunction{
{
Name: "hostFunc",
Callback: func(ctx context.Context, m wasify.ModuleProxy, params wasify.Params) *wasify.Results {

fmt.Println("Host func param 0: ", params[0].Value)
fmt.Println("Host func param 1: ", params[1].Value)

return m.Return(
[]byte("Hello"),
uint32(1234),
)
Name: "hostTest",
Callback: func(ctx context.Context, m *wasify.ModuleProxy, params []wasify.PackedData) wasify.MultiPackedData {

bytes, _ := m.Memory.ReadBytesPack(params[0])
fmt.Println("Param 1: ", bytes)
// ...

return m.Memory.WriteMultiPack(
m.Memory.WriteBytesPack([]byte("Some")),
m.Memory.WriteBytePack(1),
m.Memory.WriteUint32Pack(11),
m.Memory.WriteUint64Pack(2023),
m.Memory.WriteFloat32Pack(11.1),
m.Memory.WriteFloat64Pack(11.2023),
m.Memory.WriteStringPack("Host: Wasify."),
)

},
Params: []wasify.ValueType{wasify.ValueTypeString, wasify.ValueTypeI32},
Results: []wasify.ValueType{wasify.ValueTypeBytes, wasify.ValueTypeI32},
Params: []wasify.ValueType{
wasify.ValueTypeBytes,
wasify.ValueTypeByte,
wasify.ValueTypeI32,
wasify.ValueTypeI64,
wasify.ValueTypeF32,
wasify.ValueTypeF64,
wasify.ValueTypeString,
},
Results: []wasify.ValueType{
wasify.ValueTypeBytes,
wasify.ValueTypeByte,
wasify.ValueTypeI32,
wasify.ValueTypeI64,
wasify.ValueTypeF32,
wasify.ValueTypeF64,
wasify.ValueTypeString,
},
},
},
})

defer module.Close(ctx)

// Call guest function
module.GuestFunction(ctx, "greet").Invoke()
module.GuestFunction(ctx, "greet").Invoke("example arg", 2023)

}
```
Expand All @@ -83,26 +107,40 @@ func main() {
```go
package main

import "github.com/wasify-io/wasify-go/mdk"
import (
"github.com/wasify-io/wasify-go/mdk"
)

func main() {}

//go:wasmimport myEnv hostFunc
func hostFunc(mdk.ArgData, mdk.ArgData) mdk.ResultOffset

//export greet
func greet() {
resultOffset := hostFunc(mdk.Arg("Hello"), mdk.Arg(uint32(2023)))

results := mdk.ReadResults(resultOffset)

for i, result := range results {
mdk.Log("Guest func result %d: %s", i, result.Data)
}
//go:wasmimport host_all_available_types hostTest
func hostTest(
mdk.PackedData,
mdk.PackedData,
mdk.PackedData,
mdk.PackedData,
mdk.PackedData,
mdk.PackedData,
mdk.PackedData,
) mdk.MultiPackedData

//export guestTest
func _guestTest() {
hostTest(
mdk.WriteBytesPack([]byte("Guest: Wello Wasify!")),
mdk.WriteBytePack(byte(1)),
mdk.WriteUint32Pack(uint32(11)),
mdk.WriteUint64Pack(uint64(2023)),
mdk.WriteFloat32Pack(float32(11.1)),
mdk.WriteFloat64Pack(float64(11.2023)),
mdk.WriteStringPack("Guest: Wasify."),
)
}

```

Build module using [TinyGo](https://tinygo.org/)

```
tinygo build -o ./module/example.wasm -target wasi ./module/example.go
```
Expand All @@ -112,4 +150,3 @@ Run main.go `go run .`
## Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

55 changes: 55 additions & 0 deletions guest_function.go
Original file line number Diff line number Diff line change
@@ -1 +1,56 @@
package wasify

import (
"errors"
"fmt"

"github.com/wasify-io/wasify-go/internal/types"
"github.com/wasify-io/wasify-go/internal/utils"
)

type GuestFunctionResult struct {
multiPackedData uint64
memory Memory
}

// ReadPacks decodes the packedData from a GuestFunctionResult instance and retrieves a sequence of packed datas.
// NOTE: Frees multiPackedData, which means ReadPacks should be called once.
func (r GuestFunctionResult) ReadPacks() ([]PackedData, error) {

if r.multiPackedData == 0 {
return nil, errors.New("packedData is empty")
}

t, offsetU32, size := utils.UnpackUI64(uint64(r.multiPackedData))

if t != types.ValueTypePack {
err := fmt.Errorf("Can't unpack host data, the type is not a valueTypePack. expected %d, got %d", types.ValueTypePack, t)
return nil, err
}

bytes, err := r.memory.ReadBytes(offsetU32, size)
if err != nil {
err := errors.Join(errors.New("ReadPacks error, can't read bytes:"), err)
return nil, err
}

err = r.memory.FreePack(PackedData(r.multiPackedData))
if err != nil {
err := errors.Join(errors.New("ReadPacks error, can't free multiPackedData:"), err)
return nil, err
}

packedDataArray := utils.BytesToUint64Array(bytes)

// calculate the number of elements in the array
count := size / 8

results := make([]PackedData, count)

// Iterate over the packedData, unpack and read data of each element into a Result
for i, pd := range packedDataArray {
results[i] = PackedData(pd)
}

return results, nil
}
45 changes: 27 additions & 18 deletions guest_function_wazero.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,24 @@ func (gf *wazeroGuestFunction) call(params ...uint64) (uint64, error) {
return stack[0], nil
}

// Invoke calls the guest function with the provided parameters, taking care of
// all the necessary memory management (except memory free) and data conversion.
// Ensures the data types are compatible, writes data into the WebAssembly memory,
// and invokes the guest function.
// Each parameter is converted to its corresponding packedData format, which
// contains a compact representation of its memory offset, size, and type information.
// This packedData is written into the WebAssembly memory, enabling the
// guest function to correctly interpret and utilize the data. Post invocation,
// Invoke calls a specified guest function with the provided parameters. It ensures proper memory management,
// data conversion, and compatibility with data types. Each parameter is converted to its packedData format,
// which provides a compact representation of its memory offset, size, and type information. This packedData
// is written into the WebAssembly memory, allowing the guest function to correctly interpret and use the data.
//
// While the method takes care of memory allocation for the parameters and writing them to memory, it does
// not handle freeing the allocated memory. If an error occurs at any step, from data conversion to memory
// allocation, or during the guest function invocation, the error is logged, and the function returns with an error.
//
// Example:
//
// res, err := module.GuestFunction(ctx, "guestTest").Invoke([]byte("bytes!"), uint32(32), float32(32.0), "Wasify")
func (gf *wazeroGuestFunction) Invoke(params ...any) (uint64, error) {
//
// params ...any: A variadic list of parameters of any type that the user wants to pass to the guest function.
//
// Return value: The result of invoking the guest function in the form of a GuestFunctionResult pointer,
// or an error if any step in the process fails.
func (gf *wazeroGuestFunction) Invoke(params ...any) (*GuestFunctionResult, error) {

var err error

Expand All @@ -66,37 +71,41 @@ func (gf *wazeroGuestFunction) Invoke(params ...any) (uint64, error) {
valueType, offsetSize, err := types.GetOffsetSizeAndDataTypeByConversion(p)
if err != nil {
err = errors.Join(fmt.Errorf("Can't convert guest func param %s", gf.name), err)
return 0, err
return nil, err
}

// allocate memory for each value
offsetI32, err := gf.memory.Malloc(offsetSize)
if err != nil {
err = errors.Join(fmt.Errorf("An error occurred while attempting to alloc memory for guest func param in: %s", gf.name), err)
gf.moduleConfig.log.Error(err.Error())
return 0, err
return nil, err
}

err = gf.memory.Write(offsetI32, p)
err = gf.memory.WriteAny(offsetI32, p)
if err != nil {
err = errors.Join(errors.New("can't write arg to"), err)
return 0, err
err = errors.Join(errors.New("Can't write arg to"), err)
return nil, err
}

stack[i], err = utils.PackUI64(valueType, offsetI32, offsetSize)
if err != nil {
err = errors.Join(fmt.Errorf("An error occurred while attempting to pack data for guest func param in: %s", gf.name), err)
gf.moduleConfig.log.Error(err.Error())
return 0, err
return nil, err
}

}

res, err := gf.call(stack...)
multiPackedData, err := gf.call(stack...)
if err != nil {
err = errors.Join(fmt.Errorf("An error occurred while attempting to invoke the guest function: %s", gf.name), err)
gf.moduleConfig.log.Error(err.Error())
return 0, err
return nil, err
}

res := &GuestFunctionResult{
multiPackedData: multiPackedData,
memory: gf.memory,
}

return res, err
Expand Down
Loading

0 comments on commit 1157257

Please sign in to comment.