-
Notifications
You must be signed in to change notification settings - Fork 824
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[c-api] add more functions for stdin/stderr/stdout control #2334
Comments
I agree we must improve the API for |
This issue Needs API design doc before working on the PR |
This issue should be resolved on Go to stdin/stdout tests to see how things are done and think how to expose in C |
The proposed C API looks sane to me, it's the equivalent to what we already provide for |
This has been fixed by #3032 #include "wasmer.h"
#include "string.h"
#include "stdio.h"
typedef struct {
int invocation;
} CustomWasiStdin;
long CustomWasiStdin_destructor(
const void* env,
__u_long sz,
__u_long ao
) {
(void)env;
(void)sz;
(void)ao;
return 0;
}
long CustomWasiStdin_onStdIn(
const void* env,
__u_long sz,
__u_long ao,
__u_long maxwrite,
wasi_console_stdin_response_t* in
) {
CustomWasiStdin* ptr = (CustomWasiStdin*)env;
(void)sz;
(void)ao;
(void)maxwrite;
if (ptr->invocation == 0) {
wasi_console_stdin_response_write_str(in, "hello");
ptr->invocation += 1;
return 5; // sizeof("hello")
} else {
return 0;
}
}
int main() {
wasm_engine_t* engine = wasm_engine_new();
if (!engine) {
printf("> Error loading engine!\n");
return 1;
}
wasm_store_t* store = wasm_store_new(engine);
if (!store) {
printf("> Error loading store!\n");
return 1;
}
wasi_config_t* config = wasi_config_new("example_program");
if (!config) {
printf("> Error loading config!\n");
return 1;
}
wasi_console_out_t* override_stdout = wasi_console_out_new_memory();
if (!override_stdout) {
printf("> Error loading override_stdout!\n");
return 1;
}
wasi_console_out_t* override_stderr = wasi_console_out_new_memory();
if (!override_stderr) {
printf("> Error loading override_stderr!\n");
return 1;
}
CustomWasiStdin stdin = { .invocation = 0 };
wasi_console_stdin_t* override_stdin = wasi_console_stdin_new(
CustomWasiStdin_onStdIn,
CustomWasiStdin_destructor,
&stdin,
sizeof(stdin),
8 // alignof(stdin)
);
// Cloning the `wasi_console_out_t` does not deep-clone the
// internal stream, since that is locked behind an Arc<Mutex<T>>.
wasi_console_out_t* stdout_receiver = wasi_console_out_clone(override_stdout);
wasi_console_out_t* stderr_receiver = wasi_console_out_clone(override_stderr);
// The override_stdin ownership is moved to the config
wasi_config_overwrite_stdin(config, override_stdin);
wasi_config_overwrite_stdout(config, override_stdout);
wasi_config_overwrite_stderr(config, override_stderr);
// Load binary.
FILE* file = fopen("stdio.wasm", "rb");
if (!file) {
printf("> Error loading module!\n");
return 1;
}
fseek(file, 0L, SEEK_END);
size_t file_size = ftell(file);
fseek(file, 0L, SEEK_SET);
wasm_byte_vec_t binary;
wasm_byte_vec_new_uninitialized(&binary, file_size);
if (fread(binary.data, file_size, 1, file) != 1) {
printf("> Error loading module!\n");
return 1;
}
fclose(file);
wasm_module_t* module = wasm_module_new(store, &binary);
if (!module) {
printf("> Error compiling module!\n");
return 1;
}
// The env now has ownership of the config (using the custom stdout / stdin channels)
wasi_env_t *wasi_env = wasi_env_new(store, config);
if (!wasi_env) {
printf("> Error building WASI env!\n");
return 1;
}
wasm_importtype_vec_t import_types;
wasm_module_imports(module, &import_types);
wasm_extern_vec_t imports;
wasm_extern_vec_new_uninitialized(&imports, import_types.size);
wasm_importtype_vec_delete(&import_types);
bool get_imports_result = wasi_get_imports(store, wasi_env, module, &imports);
if (!get_imports_result) {
printf("Error getting WASI imports!\n");
return 1;
}
// The program should wait for a stdin, then print "stdout: $1" to stdout
// and "stderr: $1" to stderr and exit.
// Instantiate the module
wasm_instance_t *instance = wasm_instance_new(store, module, &imports, NULL);
if (!instance) {
printf("> Error instantiating module!\n");
return -1;
}
// Read the exports.
wasm_extern_vec_t exports;
wasm_instance_exports(instance, &exports);
wasm_memory_t* mem = NULL;
for (size_t i = 0; i < exports.size; i++) {
mem = wasm_extern_as_memory(exports.data[i]);
if (mem) {
break;
}
}
if (!mem) {
printf("Failed to create instance: Could not find memory in exports\n");
return -1;
}
wasi_env_set_memory(wasi_env, mem);
// Get the _start function
wasm_func_t* run_func = wasi_get_start_function(instance);
if (run_func == NULL) {
printf("> Error accessing export!\n");
return 1;
}
// Run the _start function
// Running the program should trigger the stdin to write "hello" to the stdin
wasm_val_vec_t args = WASM_EMPTY_VEC;
wasm_val_vec_t res = WASM_EMPTY_VEC;
if (wasm_func_call(run_func, &args, &res)) {
printf("> Error calling function!\n");
return 1;
}
// Verify that the stdout / stderr worked as expected
char* out;
wasi_console_out_read_str(stdout_receiver, &out);
assert(strcmp(out, "stdout: hello") == 0);
wasi_console_out_delete_str(out);
char* out2;
wasi_console_out_read_str(stdout_receiver, &out2);
assert(strcmp(out2, "") == 0);
wasi_console_out_delete_str(out2);
char* out3;
wasi_console_out_read_str(stderr_receiver, &out3);
assert(strcmp(out3, "stderr: hello") == 0);
wasi_console_out_delete_str(out3);
char* out4;
wasi_console_out_read_str(stderr_receiver, &out4);
assert(strcmp(out4, "") == 0);
wasi_console_out_delete_str(out4);
wasi_console_out_delete(stdout_receiver);
wasi_console_out_delete(stderr_receiver);
wasm_byte_vec_delete(&binary);
wasm_module_delete(module);
wasm_func_delete(run_func);
wasi_env_delete(wasi_env);
wasm_store_delete(store);
wasm_engine_delete(engine);
return 0;
} |
This is implemented/done in #3145 |
It looks like the added functions where later removed in #3344, can we reopen? |
Motivation
Currently the c-api propose a limited set of possibilities when it comes to WASI and redirection over stdin/stderr/stdout.
As I understand it, the
wasi_config_t
will always inheritstdin
from the host program (see #2059).Proposed solution
Since the
wasi_config_t
has access to aWasmStateBuilder
, one could replace any of the program pipes with a validWasiFile
. This could be aHostFile
or aPipe
.stderr
andstdout
can already be captured using thewasi_config_capture_{stdout,stderr}
functions, but there is no way to replace the inheritedstdin
.A function could be added to replace the stdin from the
WasiStateBuilder
with aPipe
, example:wasmer/lib/c-api/src/wasm_c_api/wasi/mod.rs
Lines 156 to 160 in 4b24038
A function could be added to then write to this stdin (with less unwraps):
wasmer/lib/c-api/src/wasm_c_api/wasi/mod.rs
Lines 277 to 288 in 4b24038
This would allow the instantiation of the same module with different inputs. The code above is from this commit on my fork, I can open a PR if the proposed change is accepted.
Alternatives
Since any
WasiFile
is valid, one could also provide file-backed outputs and inputs, similar to wasmtime'swasi_config_set_stdin_file(wasi_config_t *config, const char *path)
Additional context
An example usage where I control the stdin input given to cowsay.wasm using a wrapper written in Julia:
The text was updated successfully, but these errors were encountered: