wasm-c-api icon indicating copy to clipboard operation
wasm-c-api copied to clipboard

Exposing "regular" C/C++ functions?

Open jakobkummerow opened this issue 5 years ago • 5 comments

I was wondering whether there was any appetite for exposing "regular" C/C++ functions to WebAssembly, e.g.

int function_I_have_anyway(int32_t a, int64_t b, double c, float d);

(as opposed to functions specifically coded against the (void* env, Val args[], Val results[]) API).

This would obviously have to be limited to functions whose signatures are a subset of valid Wasm signatures.

The primary benefit would be performance. Calling such functions directly would avoid one layer of indirection (=copying parameters and return values back and forth) -- on the assumption that such a function is where the real work happens anyway, and with the current state of things it would have to be wrapped into Val[] args, Val[] returns style.

Another benefit would be convenience, because it would no longer be necessary to write such a wrapper. For the C++ version of the API, we might even be able to use template magic to derive the signature automatically; for the C version I don't think that is possible, so the signature would still have to be specified manually.

A drawback I can see is that this would make it difficult to support features like multiple return values and traps/exceptions. The former should not be an issue, if the envisioned use case is to expose existing functions to Wasm -- they only return one value anyway. I'm not sure how much of a problem the latter is.

I don't see a way for such functions to retain a ::call method, for lack of a way of specifying it. Functionality-wise that's probably be fine, because they can always be called directly; but this means that a separate type (as in: not Func) would probably have to be introduced for them.

WDYT? Are lower-overhead calls from WebAssembly into C/C++ worth the effort of exploring this direction? Or would the cases where this actually helps be too narrow to be useful?

jakobkummerow avatar May 03 '19 13:05 jakobkummerow

A more efficient and direct call interface (in both directions) is something we have discussed. It clearly is desirable, but it is less clear how to provide it or implement it generically. That seems impossible to do in plain C/C++ or without generating some stub code at runtime, which would exclude interpreter implementations like e.g. Wabt.

Consequently, current thinking is that this is (1) post-MVP, and (2) probably gonna be some kind of "optional" feature, details TBD.

rossberg avatar May 06 '19 08:05 rossberg

I think I see a way to do this with C++ template magic, even for interpreters[*], but I agree this is a post-MVP feature.

[*] provided the set of C++ signatures is known at the time that wasm.hh is built, which doesn't work for dynamically linked C++ programs.

titzer avatar May 06 '19 11:05 titzer

FWIW, we're using template magic and std::apply() (well, absl::apply()) to convert between wasm::Val[] and C++ arguments in a generic way, when calling host functions and module functions.

PiotrSikora avatar May 20 '19 20:05 PiotrSikora

@PiotrSikora, yes, but all such template magic has two problems: (1) it most likely breaks the goal of link-time compatibility between different implementations, and (2) it does not extend to the C API.

I think as a basis we need something that works in plain C as well, and then we can have (pure header-file) template magic on top that makes that more convenient for the C++ API.

rossberg avatar May 21 '19 08:05 rossberg

A more efficient and direct call interface (in both directions) is something we have discussed. It clearly is desirable, but it is less clear how to provide it or implement it generically. That seems impossible to do in plain C/C++ or without generating some stub code at runtime, which would exclude interpreter implementations like e.g. Wabt.

For the record, it's possible to directly call arbitrary C functions with the help of an assembly thunk and ABI-specific code. e.g. for the Sys V x86-64 ABI:

struct CCRegs_SysV_X86_64
{
    uint64_t rdi;
    uint64_t rsi;
    uint64_t rdx;
    uint64_t rcx;
    uint64_t r8;
    uint64_t r9;
    uint64_t rax;
    __m128 xmm[7];
};

// low_level_call_sysv_x86_64 is a function implemented in assembly that:
// 1) reads stack_args_size bytes from stack_args and pushes it on the stack
// 2) sets the argument registers (rdi/rsi/rdx/rcx/r8/r9/xmm0-xmm7) from the regs struct
// 3) calls func
// 4) copies the result registers (rax/rdx/xmm0/xmm1) back to the regs struct
// 5) pops stack_args_size bytes off the stack
extern "C" void low_level_call_sysv_x86_64(
    void* func,
    CCRegs_SysV_X86_64* regs,
    void* stack_args,
    size_t stack_args_size);

I haven't tried implementing this, but I'm convinced that it's possible for a non-JIT runtime to call the subset of host C functions that map to WASM function types.

The same strategy could be used to expose passing arguments directly to a WASM function. However, to allow it to work in the non-JIT scenario, an extra argument would be necessary to call different WASM functions through the same thunk.

AndrewScheidecker avatar Sep 26 '19 20:09 AndrewScheidecker