WebAssembly / WebAssembly/wasi-sdk

Using C++ string results in `fd_close`, `fd_seek` & `fd_write`, even for BAREMETAL compile

Open
#220 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
CMake
Stars
1.6k
Forks
237
Avg merge
18h 26m
Merged PRs (30d)
5

Description

I am writing C++ code for the internet computer, and I am a bit stuck at the moment trying to use strings.

I studied the discussion in this issue in great detail, and I made good progress after I edited the WASI SDK’s Makefile to disable assertions while opting into baremetal mode:

LIBCXXABI_CMAKE_FLAGS = \
    # ... snip ...
    -DUNIX:BOOL=ON \
+   -DLIBCXXABI_BAREMETAL=ON \
+   -DLIBCXXABI_ENABLE_ASSERTIONS=OFF \
    --debug-trycompile

With this patch, I could get the following to work:

// file: ok.cpp
#include <stdint.h>
#include <string.h>

// See: https://lld.llvm.org/WebAssembly.html#imports
#define WASM_SYMBOL_IMPORTED(module, name) \
    __attribute__((import_module(module))) __attribute__((import_name(name)));

// See: https://lld.llvm.org/WebAssembly.html#exports
#define WASM_SYMBOL_EXPORTED(name) asm(name) __attribute__((visibility("default")));

namespace ic0
{
    void debug_print(uint32_t src, uint32_t size)
        WASM_SYMBOL_IMPORTED("ic0", "debug_print");
}

void debug_print(const char *message)
{
    ic0::debug_print((uint32_t)message, (uint32_t)strlen(message));
}

void api_empty_to_empty() WASM_SYMBOL_EXPORTED("canister_query api_empty_to_empty");
void api_empty_to_empty()
{

    // Verify that debug logs are working (only on local network)
    const char *pmessage = "Using const char* - Hello from C++ api_empty_to_empty!";
    debug_print(pmessage);

    debug_print("Using directly - Hello from C++ api_empty_to_empty!");
}

I compile it with:

<...>/build/wasi-sdk-14.<xxx>/bin/clang++ --target=wasm32-wasi -O3 -flto -fcxx-exceptions --sysroot <...>/build/wasi-sdk-14.<xxx>/share/wasi-sysroot -std=c++20 -nostartfiles -Wl,--no-entry -Wl,--lto-O3 -Wl,--strip-all -Wl,--strip-debug -Wl,--stack-first -Wl,--export-dynamic ok.cpp -o ok.wasm

When converting the ok.wat using wasm2wat, the only import function is the one I specify, and the file ok.wat is really tiny:

(module
  (type (;0;) (func (param i32 i32)))
  (type (;1;) (func))
  (import "ic0" "debug_print" (func (;0;) (type 0)))
  (func (;1;) (type 1))
  (func (;2;) (type 1)
    call 1
    call 1)
  (func (;3;) (type 1)
    i32.const 65588
    i32.const 54
    call 0
    i32.const 65536
    i32.const 51
    call 0)
  (func (;4;) (type 1)
    call 3
    call 2)
  (table (;0;) 1 1 funcref)
  (memory (;0;) 2)
  (global (;0;) (mut i32) (i32.const 65536))
  (export "memory" (memory 0))
  (export "canister_query api_empty_to_empty" (func 4))
  (data (;0;) (i32.const 65536) "Using directly - Hello from C++ api_empty_to_empty!\00Using const char* - Hello from C++ api_empty_to_empty!\00"))

This file ok.wasm could be deployed without any issues.

Then I tried to use a string, and that did not work:

// file: nok.cpp
#include <stdint.h>
#include <string>

// See: https://lld.llvm.org/WebAssembly.html#imports
#define WASM_SYMBOL_IMPORTED(module, name) \
    __attribute__((import_module(module))) __attribute__((import_name(name)));

// See: https://lld.llvm.org/WebAssembly.html#exports
#define WASM_SYMBOL_EXPORTED(name) asm(name) __attribute__((visibility("default")));

namespace ic0
{
    void debug_print(uint32_t src, uint32_t size)
        WASM_SYMBOL_IMPORTED("ic0", "debug_print");
}

void debug_print(const std::string message)
{
    ic0::debug_print((uint32_t)message.c_str(), (uint32_t)message.size());
}

void api_empty_to_empty() WASM_SYMBOL_EXPORTED("canister_query api_empty_to_empty");
void api_empty_to_empty()
{
    /*
   * This does NOT work, it creates the imports:
   * (import "wasi_snapshot_preview1" "fd_close" (func (;1;) (type 1)))
   * (import "wasi_snapshot_preview1" "fd_seek" (func (;2;) (type 12)))
   * (import "wasi_snapshot_preview1" "fd_write" (func (;3;) (type 7)))
   */
    std::string message1 = "Create string first - Hello from C++ api_empty_to_empty!";
    debug_print(message1);
}

I compile it in the same way, but now the file nok.wat is much bigger and contains imports for fd_close, fd_seek & fd_write:

(module
  (type (;0;) (func (param i32 i32) (result i32)))
  ...
  (type (;42;) (func (param i32 i64 i64 i64 i64)))
  (import "ic0" "debug_print" (func (;0;) (type 10)))
  (import "wasi_snapshot_preview1" "fd_close" (func (;1;) (type 1)))
  (import "wasi_snapshot_preview1" "fd_seek" (func (;2;) (type 11)))
  (import "wasi_snapshot_preview1" "fd_write" (func (;3;) (type 7)))

I tried to apply the techniques explained in the other issue to hunt down the reason and then patch it, but was not able to.

Any tips how to get rid of these imports would be greatly appreciated.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Compare the generated modules from ok.cpp and nok.cpp using the shown clang++ command, then inspect the WASI SDK Makefile settings for baremetal libc++abi. Trace which std::string-related dependency introduces fd_close, fd_seek, and fd_write, and confirm that the resolved configuration removes those wasi_snapshot_preview1 imports from nok.wasm.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, wasm
Domain
build-system, compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.