Stream: general

Topic: using wasmetime in C++


view this post on Zulip Aleksandr Lykhouzov (Oct 17 2022 at 11:58):

disclaimer: I am not a c++ developer at all. actually
The problem: I am currently working on a module for azerothcore which will use wasm files to extend functionality, that is, some kind of wasm plugin. Since I am not familiar with c++, i've decided to test posibilities with simple example emulating calls of module. I was able to make an example work in very hardcoded way, copying whole example from wasmtime repo. But now I want to separate variables and re-use them in different method calls. I've decided to save as a class field instance, store, and engine vars. see an example

#include <fstream>
#include <iostream>
#include <sstream>
#include <wasmtime.hh>
#include <Player.h>
#include <span>
using namespace wasmtime;
Span<uint8_t> readWasmFile(const char *filename)
{
    FILE *file;
    file = fopen(filename, "rb");
    if (!file)
    {
        printf("> Error loading module!\n");
        fclose(file);
    }
    fseek(file, 0L, SEEK_END);
    size_t file_size = ftell(file);
    fseek(file, 0L, SEEK_SET);
    printf("File was read...\n");
    wasm_byte_vec_t wasm_bytes;
    wasm_byte_vec_new_uninitialized(&wasm_bytes, file_size);
    if (fread(wasm_bytes.data, file_size, 1, file) != 1)
    {
        printf("> Error loading module!\n");
    }
    fclose(file);
    std::vector<uint8_t> vec;
    Span<uint8_t> raw(reinterpret_cast<uint8_t *>(wasm_bytes.data), wasm_bytes.size);
    vec.assign(raw.begin(), raw.end());
    wasm_byte_vec_delete(&wasm_bytes);
    return vec;
}

class Wasm_Script
{
    Engine engine;
    Store store;
    wasmtime::Instance *instance;

public:
    Wasm_Script() : store(engine)
    {
        auto buf = readWasmFile("wasm_modules/rust_wasm_app.wasm");
        auto module = Module::compile(engine, buf).unwrap();
        std::cout << "Initializing...\n";
        std::cout << "Creating callbacks...\n";
        Func host_func = Func::wrap(&store, []()
                                    { std::cout << "Calling back...\n"; });
        Func send_sys_message(
            &store, FuncType({ValKind::I32, ValKind::I32}, {}),
            [](auto caller, auto params, auto results)
            {
                // caller.context().gc();
                auto ptr = params[0].i32();
                auto len = params[1].i32();
                std::cout << "PTR = " << ptr << "\n";
                Memory mem = std::get<Memory>(*caller.get_export("memory"));
                wasmtime::Span<uint8_t> d = mem.data(caller);
                std::string msg = "";
                for (auto i = ptr; i < ptr + len; i++)
                {
                    msg += d[i];
                }
                std::cout << msg << std::endl;

                return std::monostate();
            });
        std::cout << "Instantiating module...\n";
        Instance instance = Instance::create(store, module, {host_func, send_sys_message}).unwrap();
        this->instance = &instance;
    }
    void OnLogin(Player *player)
    {
        std::cout << "Extracting export...\n";
        auto fn = instance->get(store, "on_player_login");
        auto on_player_login = std::get<Func>(*fn);
        std::cout << "Calling export...\n";
        auto r = on_player_login.call(store, {}).unwrap();
        std::cout << "Result from `on_player_login` ";
        std::cout << r[0].i32();
        std::cout << "\n\n";
        std::cout << "Done\n";
    }
};
int main()
{
    auto player = new Player();
    auto script = new Wasm_Script();
    script->OnLogin(player);
    return 0;
}

if I run it i get a panic form rust lib :)
thread '<unnamed>' panicked at 'object used with the wrong store', crates/wasmtime/src/store/data.rs:258:5

Could you help me to understand why it does not want to work
PS: if I move everything in constructor to OnLogin method - everything works fine.

view this post on Zulip Alex Crichton (Oct 17 2022 at 14:35):

I believe your code has a use-after free in this->instance = &instance; since after the method returns the instance pointer is no longer valid.

view this post on Zulip Aleksandr Lykhouzov (Oct 17 2022 at 14:57):

I wouldn't agree.
if I add in constructor

        this->instance = &instance;
        std::cout << this->instance <<"\n";

and then call it in OnLogin method

        std::cout << "Extracting export...\n";
        std::cout << instance <<"\n";

I have exactly the same address(pointer)

File was read...
Initializing...
Creating callbacks...
Instantiating module...
0x7ffee90abf98
Extracting export...
0x7ffee90abf98
thread '<unnamed>' panicked at 'object used with the wrong store', crates/wasmtime/src/store/data.rs:258:5

If i do the same with store - i will get the same store address. so, that is why. I am a little bit stuck with it and asked help here.

view this post on Zulip Alex Crichton (Oct 17 2022 at 14:58):

oh ok, in that case I'm probably not familiar with C++ enough to help

view this post on Zulip Dan Gohman (Oct 17 2022 at 15:06):

That debug output just shows that you have the same pointer value. But the Instance that pointer is pointing to has gone out of scope.

view this post on Zulip Aleksandr Lykhouzov (Oct 17 2022 at 15:16):

@Dan Gohman , you know, I've just was thinking about that pointer is not a proof :) and you just confirmed that the address cold be the same but the value of it could be different.
Then, next question would be how i could "save" the instance in c++ class to use it in its methods?
I am sorry, it could be a stupid/newbie question, but i cannot find the answer to it :(

PS: actually, i use a pointer for the instnace because if I use it as wasmtime::Instance instance; it will tell me that the Instance does not have a default constructor and you much explicitly tell it how to use it.

view this post on Zulip Dan Gohman (Oct 17 2022 at 15:17):

Would std::shared_ptr work?

view this post on Zulip Aleksandr Lykhouzov (Oct 17 2022 at 15:19):

Dan Gohman ha dicho:

Would std::shared_ptr work?

let search how to use it :)
As I've told - i am super newbie in c++, always tried to avoid it :) but now is the time

view this post on Zulip Aleksandr Lykhouzov (Oct 17 2022 at 15:47):

i found another solution, but I am not sure if it is good one.

this->instance = new Instance(Instance::create(store, module, {host_func, send_sys_message}).unwrap());

so, i've used a new Instance to create a pointer to class' field.
For me it looks a little bit dirty, but it works for now.
Just in case someone will face the sam issue or similar here is full cpp

#include <fstream>
#include <iostream>
#include <sstream>
#include <wasmtime.hh>
#include <Player.h>
#include <span>
using namespace wasmtime;
Span<uint8_t> readWasmFile(const char *filename)
{
    FILE *file;
    file = fopen(filename, "rb");
    if (!file)
    {
        printf("> Error loading module!\n");
        fclose(file);
    }
    fseek(file, 0L, SEEK_END);
    size_t file_size = ftell(file);
    fseek(file, 0L, SEEK_SET);
    printf("File was read...\n");
    wasm_byte_vec_t wasm_bytes;
    wasm_byte_vec_new_uninitialized(&wasm_bytes, file_size);
    if (fread(wasm_bytes.data, file_size, 1, file) != 1)
    {
        printf("> Error loading module!\n");
    }
    fclose(file);
    std::vector<uint8_t> vec;
    Span<uint8_t> raw(reinterpret_cast<uint8_t *>(wasm_bytes.data), wasm_bytes.size);
    vec.assign(raw.begin(), raw.end());
    wasm_byte_vec_delete(&wasm_bytes);
    return vec;
}

class Wasm_Script
{
    Engine engine;
    Store store;
    Instance *instance;

public:
    Wasm_Script() : store(engine)
    {
        auto buf = readWasmFile("wasm_modules/rust_wasm_app.wasm");
        auto module = Module::compile(engine, buf).unwrap();
        std::cout << "Initializing...\n";
        std::cout << "Creating callbacks...\n";
        Func host_func = Func::wrap(&store, []()
                                    { std::cout << "Calling back...\n"; });
        Func send_sys_message(
            store, FuncType({ValKind::I32, ValKind::I32}, {}),
            [](auto caller, auto params, auto results)
            {
                auto ptr = params[0].i32();
                auto len = params[1].i32();
                std::cout << "PTR = " << ptr << "\n";
                Memory mem = std::get<Memory>(*caller.get_export("memory"));
                wasmtime::Span<uint8_t> d = mem.data(caller);
                std::string msg = "";
                for (auto i = ptr; i < ptr + len; i++)
                {
                    msg += d[i];
                }
                std::cout << msg << std::endl;

                return std::monostate();
            });
        std::cout << "Instantiating module...\n";
        instance = new Instance(Instance::create(store, module, {host_func, send_sys_message}).unwrap());
    }
    void OnLogin(Player *player)
    {
        std::cout << "Extracting export...\n";
        auto fn = instance->get(store, "on_player_login");
        auto on_player_login = std::get<Func>(*fn);
        std::cout << "Calling export...\n";
        auto r = on_player_login.call(store, {}).unwrap();
        std::cout << "Result from `on_player_login` ";
        std::cout << r[0].i32();
        std::cout << "\n\n";
        std::cout << "Done\n";
    }
};
int main()
{
    auto player = new Player();
    auto script = new Wasm_Script();
    script->OnLogin(player);
    return 0;
}

as for me it is not clean, bu tit works and result is

./main
File was read...
Initializing...
Creating callbacks...
Instantiating module...
Extracting export...
Calling export...
PTR = 1114120
This is chat message from WASM
Result from `on_player_login` 1

Thank you for the help

view this post on Zulip Aleksandr Lykhouzov (Oct 17 2022 at 17:21):

Ok. it looks like that issue of mine we resolved. But i faced new one.
How to pass and retrieve data to/from the store in CPP variant of wasmtime?
There is no much examples to understand it.
Let me describe it more clearly:

  1. I have callback in c++ which is called inside wasm module.
  2. I need somehow to get a variable(user session id) in cpp during the call of the callback.
  3. in rust examples, it is done by passing data to store.
  4. in my c++ example i am trying to reproduce the same behaviour but it fails.
    here is an example
/// CALLBACK
Func send_sys_message(
            store, FuncType({ValKind::I32, ValKind::I32}, {}),
            [](auto caller, auto params, auto results)
            {
                auto mystore = caller.context().get_data(); // this is how I try to get AppState class instance
                std::cout << &mystore << "\n\n";
// ? how I can call a method from mystore.get_session();???
                auto ptr = params[0].i32();
                auto len = params[1].i32();
                Memory mem = std::get<Memory>(*caller.get_export("memory"));
                wasmtime::Span<uint8_t> d = mem.data(caller);
                std::string msg = "";
                for (auto i = ptr; i < ptr + len; i++)
                {
                    msg += d[i];
                }
                std::cout << msg << std::endl;
                return std::monostate();
            });
/// actuall call

        store.context().set_data(new AppState("My User Session"));
        auto fn = instance->get(store, "on_player_login");
        auto on_player_login = std::get<Func>(*fn);
        auto r = on_player_login.call(store, {}).unwrap();

Or you may provide better way to pass that "session" into callback on a moment of call.

Thank you.


Last updated: Dec 23 2024 at 12:05 UTC