extism / cpp-sdk

Extism C++ Host SDK
BSD 3-Clause "New" or "Revised" License
7 stars 3 forks source link

feature request: simplify host interface #21

Open konsumer opened 1 month ago

konsumer commented 1 month ago

I am not great with C/C++ so this may not be a reasonable expectation, but I think the WAMR host interface is simpler, as it is, than exposing a host-function with extism. Here is an example in wamr:

// host implementation, basically a thin-wrapper around null0_random_int that adds env param
static int32_t wamr_null0_random_int(wasm_exec_env_t exec_env, int32_t min, int32_t max) {
  return null0_random_int(min, max);
}

// later in setup
{"random_int", wamr_null0_random_int, "(ii)i"}

No raising/lowering of values into a shared-buffer, WAMR sees the function is (ii)i meaning "takes 2 i32 params, returns i32" and inserts those into the host function-call.

It does break down sometimes, like this function that returns a struct:

// Generate randomized preset sfxr params
static void wamr_null0_preset_sfx(wasm_exec_env_t exec_env, SfxParams* params, SfxPresetType type) {
  null0_preset_sfx(params, type);
}

{"preset_sfx", wamr_null0_preset_sfx, "(*i)"}

clang mangles the return in wasm, and inserts it as the first param, as a pointer in wasm-space. But otherwise, all the pointers in the host-function are already setup to work with the wasm memory, and so you just work with those pointers like they are plain old C pointers, in host-space.

I think it is similar in c-pdk, like to keep my simple user-api, I will need to wrap every function with extism stuff to raise/lower values into the buffer and then call user/host functions.

I am not good enough with C macros to know how to do it myself, but it'd be cool if I could just expose a function and have it work with no extra trouble, like this in host:

SfxParams null0_preset_sfx(SfxPresetType type) {
  // implemented here, just normal C function that can use pointers  that are pulled out alread
}

extism_expose_host(plugin, null0_preset_sfx, "null0.preset_sfx");

and this in wasm:

extism_import_host("null0.preset_sfx", SfxParams preset_sfx(SfxPresetType type));

so I can use the host-function without any wrapping or pulling/pushing pointers. Like here, it would look at null0_preset_sfx and turn it into extism stuff.

On host:

In wasm:

konsumer commented 1 month ago

Here is another simple example:

In wamr host

static void wamr_null0_trace(wasm_exec_env_t exec_env, char* str) {
  printf("%s\n", str);
}

// ... in setup function
{"trace", wamr_null0_trace, "($)"}

And in wasm:

#define NULL0_IMPORT(n) __attribute__((import_module("null0"), import_name(n)))

NULL0_IMPORT("trace")
void trace(char* str);

but in extism, I need to juggle the vars around:

In extism host

auto trace_in = std::vector<extism::ValType>{extism::ValType::ExtismValType_I64};
  auto null0_host_trace = extism::Function(
      "trace",
      trace_in,
      {},
      [](extism::CurrentPlugin plugin, void* user_data) {
        cout << plugin.inputStringView(0) << endl;
      },
      NULL,
      NULL);

This isn't too bad, since I can just call plugin.inputStringView(0) to get the string val, but if I could just point it directly at the plain function it'd be much simpler. Also, on the wasm-side, it's more complicated, because I need to raise the memory first, so I need an extra layer if I want to keep it simple for the user (no handles and storing):

EXTISM_IMPORT("extism:host/user", "trace")
extern void _null0_trace_real(ExtismHandle);

void trace(const char* null0_traceBuffer) {
  int l = strlen(null0_traceBuffer);
  ExtismHandle handle = extism_alloc(l);
  extism_store_to_handle(handle, 0, null0_traceBuffer, l);
  _null0_trace_real(handle);
}

What I think would be cool is this:



// wasm-side
EXTISM_IMPORT("extism:host/user", "trace")
extern void trace(const char* null0_traceBuffer);

// or extism_import_host style, above

// host-side
void null0_trace(char* message) {
  printf("%s\n", message);
}

extism_expose_host(plugin, "trace", null0_trace);

// or with lambda & string:
extism_expose_host(plugin, "trace", [](string message) {
  cout << message << endl;
});

// or maybe
plugin.expose("trace", [](string message) {
  cout << message << endl;
});
G4Vi commented 1 month ago

A more convenient host function interface would be nice, but I'm not sure how much can be done portability. Currently, I don't believe libextism exposes the linear memory of the plugin, but the plugin's (kernel) memory. This could probably be done with wasmtime and WAMR, but on the host side I'm not sure if the plugin's linear memory is always accessible, for example, in JS unless it's exported I don't think it is. Without this feature, the wasm must handle copying into the plugin's kernel memory unless it was exporting it's copy routines for the host function wrapper to use.

Still thought. on the host side potentially a host function interface could be created that allows the host function to take pointer parameters or string_view, etc (where the cpp-sdk handles converting inputs to host pointers). The old api might still be necessary for multi-value returns or maybe those can use std::tuple.

Please share if you experiment and get something working!