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

Is aot support host function call? #35

Open
Celebraty opened this issue Jan 18, 2023 · 8 comments
Open

Is aot support host function call? #35

Celebraty opened this issue Jan 18, 2023 · 8 comments

Comments

@Celebraty
Copy link

Celebraty commented Jan 18, 2023

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 Celebraty changed the title How to call a defined in golang function in wasm Is aot support host function call? Jan 31, 2023
@q82419
Copy link
Member

q82419 commented Mar 13, 2023

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 AOT bug WasmEdge/WasmEdge#2061 and Optimizing wasmedge_quickjs with wasmedgec (AOT) fails WasmEdge/WasmEdge#2319

@Celebraty
Copy link
Author

@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

@q82419
Copy link
Member

q82419 commented Mar 14, 2023

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.

@Celebraty
Copy link
Author

@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=&{[]}

@q82419
Copy link
Member

q82419 commented Mar 30, 2023

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).

@Celebraty
Copy link
Author

@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
Copy link
Author

Celebraty commented May 24, 2023

@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

@Victor-mqz
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants