Search code examples
webassemblyemscriptenwasmtime

Compile a protobuf c++ program to wasm(Webassembly)


I want to compile a c++ program to wasm(webassembly) and run it by wasmtime which is a wasm runtime but failed.

The c++ program utilize protobuf which is already compiled from src code using Emscripten. The program is build using Emscripten successfully but cannot run with wasmtime, neither converting to .wat file using wasm2wat.

Now the problem is:

  1. The error message when running with wasmtime is:
# wasmtime person.wasm
Error: failed to run main module `person.wasm`

Caused by:
    0: if you're trying to run a precompiled module, pass --allow-precompiled
    1: failed to compile wasm function 79 at offset 0x293c
    2: WebAssembly translation error
    3: Invalid input WebAssembly code at offset 10632: threads support is not enabled
  1. When converting to .wat format, comes another error:
# wasm2wat person.wasm
000298a: error: unexpected opcode: 0xfe 0x10

Here is the file tree:

protobuf_wasm_test/
  -lib/
    -libprotobuf-lite.a
    -libprotobuf.a
    -libprotoc.a
  -pb/
    -person.pb.cc
    -person.pb.h
  -CMakeLists.txt
  -person.cpp

The pb directory contains protofiles which define the message format, the person.proto file is:

syntax = "proto3";
message Person {
    string id = 1;
    string name = 2;
}

The person.cpp is:

#include <iostream>
#include "./pb/person.pb.h"

int main()
{
    Person *person = (Person*)malloc(sizeof(Person));
    person->set_id("12345");
    person->set_name("abc");
    std::cout<<"id: "<<person->id()<<", name: "<<person->name()<<std::endl;
    free(person);
    return 0;
}

The CMakeList.txt is:

cmake_minimum_required(VERSION 3.5)
project(protobuf_wasm_test)

add_compile_options(-sSTANDALONE_WASM -Wno-unused-command-line-argument)

include_directories("${CMAKE_CURRENT_BINARY_DIR}/../pb")
include_directories("/home/hzb/protobuf_wasm/protobuf_src/src")

link_directories("${CMAKE_CURRENT_BINARY_DIR}/../lib")


set(proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/../pb/person.pb.cc")
set(proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/../pb/person.pb.h")

add_library(proto
    ${proto_srcs}
    ${proto_hdrs})


add_executable(person
    person.cpp)

target_link_libraries(person
    proto
    protobuf)

I rebuild the protobuf source code with Emcsripten and generate the libprotobuf.a in ./lib/ directory,

Build successfully:

emcmake ..
emmake make
configure: cmake .. -DCMAKE_TOOLCHAIN_FILE=/home/hzb/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_CROSSCOMPILING_EMULATOR=/home/hzb/emsdk/node/14.18.2_64bit/bin/node;--experimental-wasm-bulk-memory;--experimental-wasm-threads
-- Configuring done
-- Generating done
-- Build files have been written to: /home/hzb/protobuf_wasm/protobuf_wasm_test/build
make: make
[ 25%] Building CXX object CMakeFiles/proto.dir/pb/person.pb.cc.o
[ 50%] Linking CXX static library libproto.a
[ 50%] Built target proto
[ 75%] Building CXX object CMakeFiles/person.dir/person.cpp.o
[100%] Linking CXX executable person.js
[100%] Built target person

Here is another question: I also want to know how to compile a complicate c++ program(with thirdparty libraries) to wasm and run it with wasmtime or other wasm runtime(non-web)? Does compiling each library to wasm(libxxx.a) and link them to the target work?


Solution

  • Noticed that the error message said "threads support is not enabled" and I tried to add --wasm-feature threads when run wasmtime(link) like wasmtime --wasm-feature threads persom.wasm. But still didn't work.

    Besides, since I want to generate a stand alone wasm file, I added a compile parameter -sSTANDALONE_WASM. From this page said that

    emcc ... -o output.wasm omits generating either JavaScript or HTML launcher file, and produces a single Wasm file built in standalone mode as if the -sSTANDALONE_WASM settting had been used.

    which means I should specify both standalone_wasm and -o output.wasm at the same time.

    So I Specified the type of output file through set(CMAKE_EXECUTABLE_SUFFIX ".wasm"). It works finally!

    But I don't understand why I need to enable thread for wasmtime since there is no thread code in the cpp.

    Thanks for the comment from @yeputons, the thread option may caused by protobuf atomic variables.