WasmEdge-go icon indicating copy to clipboard operation
WasmEdge-go copied to clipboard

Is aot support host function call?

Open Celebraty opened this issue 3 years ago • 8 comments

What i want

just like wasmer, i declare some wasi functions in C++, and call them in C++

extern "C" {
    EMSCRIPTEN_KEEPALIVE void *get_request_data(int field);

EMSCRIPTEN_KEEPALIVE char* calculate_score_wasi(int length) {
    double *prices = (double*)get_request_data(1);
    ...

then, i defined them in golang, and register them to wasi module, it can run correctly

importObject, err = wasiEnv.GenerateImportObject(store, module)
...
funcMap["get_request_data"] = wasmer.NewFunction(store, wasmer.NewFunctionType([]*wasmer.ValueType{wasmer.NewValueType(wasmer.I32)},
				[]*wasmer.ValueType{wasmer.NewValueType(wasmer.I32)}), [mydefinedfunc])
importObject.Register("env", funcMap)
...

What's the problem

when i try host function in wasmedge, my register code is

wasmedge.SetLogDebugLevel()
wasmPath = compileWasm(wasmPath) // compile my demo.wasm by aot

var (
	conf    = wasmedge.NewConfigure(wasmedge.WASI)
	err     error
	vm      = wasmedge.NewVMWithConfig(conf)
	wasiobj = vm.GetImportModule(wasmedge.WASI)
)

wasiobj.InitWasi(
	os.Args[1:],     // The args
	os.Environ(),    // The envs
	[]string{".:."}, // The mapping preopens
)

importOjb := wasmedge.NewModule("env")
for name, function := range allWasiFunctionDefined {
	temp := wasmedge.NewFunction(hostFunctionType, function, nil, 20)
	importOjb.AddFunction(name, temp)
}

err = vm.RegisterModule(importOjb)
if err != nil {
	fmt.Printf("failed to import function module\n")
	return nil
}

err = vm.LoadWasmFile(wasmPath)
if err != nil {
	fmt.Printf("failed to load wasm, err=%v\n", err)
	return nil
}
err = vm.Validate()
if err != nil {
	fmt.Printf("Validate err=%v\n", err)
	return nil
}
err = vm.Instantiate()
if err != nil {
	fmt.Printf("Instantiate err=%v\n", err)
	return nil
}

then i call the calculate_score_wasi function, get the error

[error] execution failed: out of bounds memory access, Code: 0x88
[error]     When executing function name: "calculate_score_wasi"

so, is my fault or the wasmedge hasn't support ? please help, thanks a lot

Celebraty avatar Jan 18 '23 03:01 Celebraty

AOT mode supports the host function call.

  1. Whats is the hostFunctionType? Is the function type all correct?
  2. It may be the issue in https://github.com/WasmEdge/WasmEdge/issues/2061 and https://github.com/WasmEdge/WasmEdge/issues/2319

q82419 avatar Mar 13 '23 07:03 q82419

@q82419

  1. my hostfunctiontype : wasmedge.NewFunctionType([]wasmedge.ValType{wasmedge.ValType_I32}, []wasmedge.ValType{wasmedge.ValType_I32}); and i think it is correct, because i can run it correctly without aot compile
  2. i think it is not same as them because i reproduce the issue only in hostfunction

Celebraty avatar Mar 13 '23 07:03 Celebraty

Can you help to provide your wasm file and a simple go code to reproduce it? In this situation, it should pass or there are bugs. Thanks.

q82419 avatar Mar 14 '23 12:03 q82419

@q82419

cmd/wasm_edge.go

package main

import (
	"encoding/binary"
	"fmt"
	"math"
	"os"

	"github.com/second-state/WasmEdge-go/wasmedge"
)

var hostFunctionType = wasmedge.NewFunctionType([]wasmedge.ValType{wasmedge.ValType_I32}, []wasmedge.ValType{wasmedge.ValType_I32})

var allWasiFunction = map[string]func(
	data interface{}, frame *wasmedge.CallingFrame, params []interface{}) ([]interface{}, wasmedge.Result){
	"get_prerank_request_data": PrerankGetRequestValue,
	"update_prerank_response":  PrerankUpdateResponse,
}

func compileWasm(wasmPath string) string {
	// Create Configure
	conf := wasmedge.NewConfigure(wasmedge.THREADS, wasmedge.EXTENDED_CONST, wasmedge.TAIL_CALL, wasmedge.WASI)

	// Create Compiler
	compiler := wasmedge.NewCompilerWithConfig(conf)

	outputPath := wasmPath + ".so"
	// Compile WASM AOT
	err := compiler.Compile(wasmPath, outputPath)
	if err != nil {
		fmt.Println("Go: Compile WASM to AOT mode Failed!!")
	}

	conf.Release()
	compiler.Release()
	return outputPath
}

func GetVm(wasmPath string) *wasmedge.VM {
	wasmedge.SetLogErrorLevel()
	wasmPath = compileWasm(wasmPath)  // if i delete the line, everything went ok

	var (
		conf    = wasmedge.NewConfigure(wasmedge.WASI)
		err     error
		vm      = wasmedge.NewVMWithConfig(conf)
		wasiobj = vm.GetImportModule(wasmedge.WASI)
	)

	wasiobj.InitWasi(
		os.Args[1:],     // The args
		os.Environ(),    // The envs
		[]string{".:."}, // The mapping preopens
	)

	module := wasmedge.NewModule("env")
	for name, function := range allWasiFunction {
		temp := wasmedge.NewFunction(hostFunctionType, function, nil, 20)
		module.AddFunction(name, temp)
	}

	err = vm.LoadWasmFile(wasmPath)
	if err != nil {
		fmt.Printf("failed to load wasm, err=%v\n", err)
		return nil
	}
	vm.RegisterModule(module)
	err = vm.Validate()
	if err != nil {
		fmt.Printf("Validate err=%v\n", err)
		return nil
	}
	err = vm.Instantiate()
	if err != nil {
		fmt.Printf("Instantiate err=%v\n", err)
		return nil
	}
	conf.Release()
	wasiobj.Release()
	return vm
}

var curPrerankRequest *PreRankColumnRequest
var curPrerankResponse *RankResponse
var curPrerankVm *wasmedge.VM

type PreRankColumnRequest struct {
	Country   string    `json:"country"`
	PpCtrs    []float64 `json:"pp_ctrs"`
	BidPrices []int64   `json:"bid_prices"`
}

type RankResponse struct {
	Score []float64 `json:"score"`
}

func PrerankUpdateResponse(data interface{}, callframe *wasmedge.CallingFrame, params []interface{}) ([]interface{}, wasmedge.Result) {
	curPrerankResponse.Score = make([]float64, len(curPrerankRequest.BidPrices))
	offset := params[0].(int32)
	mem := callframe.GetMemoryByIndex(0)
	buf, _ := mem.GetData(uint(offset), uint(8*len(curPrerankRequest.BidPrices)))

	curpos := 0
	for idx := range curPrerankResponse.Score {
		curPrerankResponse.Score[idx] = ReadFloat64(&buf, &curpos)
	}
	return []interface{}{0}, wasmedge.Result_Success
}

func PrerankGetRequestValue(data interface{}, callframe *wasmedge.CallingFrame, params []interface{}) ([]interface{}, wasmedge.Result) {
	if len(params) != 1 {
		return nil, wasmedge.Result_Fail
	}

	dataLen := uint(len(curPrerankRequest.BidPrices) * 8)

	pointerBuf, _ := callframe.GetMemoryByIndex(0).GetData(uint(params[0].(int32)), 4)
	pos := 0
	pointer, _ := curPrerankVm.Execute("allocMem", int32(dataLen))
	requestType := ReadInt32(&pointerBuf, &pos)
	pos = 0

	buf, _ := callframe.GetMemoryByIndex(0).GetData(uint(pointer[0].(int32)), dataLen)

	switch requestType {
	case 0:
		for _, val := range curPrerankRequest.PpCtrs {
			PutFloat64(&buf, val, &pos)
		}
	case 1:
		for _, val := range curPrerankRequest.BidPrices {
			PutInt64(&buf, val, &pos)
		}
	}
	pos = 0
	PutInt32(&pointerBuf, pointer[0].(int32), &pos)

	return []interface{}{0}, wasmedge.Result_Success
}

func ExecutePrerankWasi(vm *wasmedge.VM, input *PreRankColumnRequest, funcName string) *RankResponse {
	// 为 subject 分配内存,并获得其指针
	// 包括一个字节,用于我们在下面添加的 NULL 结束符
	response := &RankResponse{}
	curPrerankRequest = input
	curPrerankVm = vm
	curPrerankResponse = response

	resp, err := vm.Execute(funcName, int32(len(input.BidPrices)))

	if err != nil {
		fmt.Printf("prerank with wasi err=%v, resp=%v\n", err, resp)
		return nil
	}

	return response
}

func PutFloat64(bytes *[]byte, f float64, curPos *int) {
	bits := math.Float64bits(f)
	binary.LittleEndian.PutUint64((*bytes)[*curPos:], bits)
	*curPos += 8
}

func PutInt64(bytes *[]byte, i int64, curPos *int) {
	binary.LittleEndian.PutUint64((*bytes)[*curPos:], uint64(i))
	*curPos += 8
}

func PutInt32(bytes *[]byte, i int32, curPos *int) {
	binary.LittleEndian.PutUint32((*bytes)[*curPos:], uint32(i))
	*curPos += 4
}

func ReadInt32(bytes *[]byte, curPos *int) int32 {
	bits := binary.LittleEndian.Uint32((*bytes)[*curPos : *curPos+4])
	*curPos += 4
	return int32(bits)
}

func ReadFloat64(bytes *[]byte, curPos *int) float64 {
	bits := binary.LittleEndian.Uint64((*bytes)[*curPos : *curPos+8])
	ret := math.Float64frombits(bits)
	*curPos += 8
	return ret
}

func main() {
	vm := GetVm("output/bench.wasm")
	response := ExecutePrerankWasi(vm, &PreRankColumnRequest{}, "prerank_with_wasi")
	fmt.Printf("response=%v\n", response)
}

cpp/wasm_edge.cpp

#include "emscripten/emscripten.h"
#include <stdio.h>
#include <unistd.h>

extern "C" {
    int update_prerank_response(void *request);
    void *get_prerank_request_data(int field);
}

char* prerank_wasi(int length) {
    double *ppctrs = (double *)get_prerank_request_data(0);
    long long *bidPrices = (long long*)get_prerank_request_data(1);

    void *result = malloc(length * 8);
    double *ptr = (double *)result;
    for (int i = 0; i < length; ++i) {
        *ptr = ppctrs[i] * bidPrices[i];
        ++ptr;
    }

    update_prerank_response(result);
    free(ppctrs);
    free(bidPrices);
    free(result);
    return nullptr;
}

build wasm command

mkdir -p output
emcc -g -O3 -o output/bench.html cpp/wasm_edge.cpp -s STANDALONE_WASM -sERROR_ON_UNDEFINED_SYMBOLS=0

reproduce

by the command directly

go run cmd/wasm_edge.go

and then get the err:

[2023-03-15 10:41:10.462] [error] execution failed: out of bounds memory access, Code: 0x88
[2023-03-15 10:41:10.462] [error]     When executing function name: "prerank_with_wasi"
prerank with wasi err=out of bounds memory access, resp=[]

some others

if i delete the wasm_edge.go code:

wasmPath = compileWasm(wasmPath)

then i can get the correct response:

response=&{[]}

Celebraty avatar Mar 15 '23 02:03 Celebraty

Hi @Celebraty ,

As your code above, this may be the AOT issue I mentioned, not the host functions. Because the error occurred in the WASM, not in host functions.

From your code, I have additional suggestions in the GetVM function:

  1. You should keep your module object which registered into the VM, and call release() after finishing the VM. https://wasmedge.org/book/en/sdk/go/ref.html#host-module-registrations
  2. The module instances get from the vm.GetImportModule() API should not be released (although this doesn't matter, because the object didn't get the ownership of the module instance context).

q82419 avatar Mar 30 '23 09:03 q82419

@q82419 thanks for your help, i have tried to add a response for the GetVM function, it can return the module which registered into the VM, and the new error occur :

libc++abi: terminating with uncaught exception of type std::__1::system_error: mutex lock failed: Invalid argument
signal: abort trap

do you have any idea how to resolve the problem

Celebraty avatar Mar 30 '23 11:03 Celebraty

@q82419 After more attempts, it was found that the following two errors appeared randomly

first(just as the err before):

[error] execution failed: out of bounds memory access, Code: 0x88
[error]     When executing function name: "prerank_with_wasi"

second:

libc++abi: terminating with uncaught exception of type std::__1::system_error: mutex lock failed: Invalid argument
signal: abort trap

Celebraty avatar May 24 '23 09:05 Celebraty

finally,i found that this issue happens only on mac;there aren't any issue occured on linux

Victor-mqz avatar Feb 29 '24 12:02 Victor-mqz