diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f4edc74 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: LLVM +IndentWidth: 2 diff --git a/README.md b/README.md index 752fac2..c5d8be8 100644 --- a/README.md +++ b/README.md @@ -22,21 +22,26 @@ Let's write a simple program that exports a `greet` function which will take a n ```c #include "extism-pdk.h" +#include -const char *greeting = "Hello, "; -uint64_t greetingLen = 7; +#define Greet_Max_Input 1024 +static const char Greeting[] = "Hello, "; -int32_t greet() { +int32_t EXTISM_EXPORTED_FUNCTION(greet) { uint64_t inputLen = extism_input_length(); + if (inputLen > Greet_Max_Input) { + inputLen = Greet_Max_Input; + } // Load input - uint8_t inputData[inputLen]; + static uint8_t inputData[Greet_Max_Input]; extism_load_input(inputData, inputLen); // Allocate a new offset used to store greeting and name - uint64_t outputLen = greetingLen + inputLen; + const uint64_t greetingLen = sizeof(Greeting) - 1; + const uint64_t outputLen = greetingLen + inputLen; ExtismPointer offs = extism_alloc(outputLen); - extism_store(offs, (const uint8_t *)greeting, greetingLen); + extism_store(offs, (const uint8_t *)Greeting, greetingLen); extism_store(offs + greetingLen, inputData, inputLen); // Set output @@ -45,20 +50,21 @@ int32_t greet() { } ``` +The `EXTISM_EXPORTED_FUNCTION` macro simplifies declaring an Extism function that will be exported to the host. + Since we don't need any system access for this, we can compile this directly with clang: ```shell -clang -o plugin.wasm --target=wasm32-unknown-unknown -nostdlib -Wl,--no-entry -Wl,--export=greet plugin.c +clang -o plugin.wasm --target=wasm32-unknown-unknown -nostdlib -Wl,--no-entry plugin.c ``` -To break this down a little: +The above command may fail if ran with system clang. It's highly recommended to use clang from the [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) instead. The `wasi-sdk` also includes a libc implementation targeting WASI, necessary for plugins that need the C standard library. + +Let's break down the command a little: - `--target=wasm32-unknown-unknown` configures the correct Webassembly target - `-nostdlib` tells the compiler not to link the standard library - `-Wl,--no-entry` is a linker flag to tell the linker there is no `_start` function -- `-Wl,--export=greet` is a linker flag used to export the `greet` function - -There is also [wasi-sdk](https://github.com/WebAssembly/wasi-sdk), a libc implementation targeting WASI, for plugins that need access to the C standard library. We can now test `plugin.wasm` using the [Extism CLI](https://github.com/extism/cli)'s `call` command: @@ -75,22 +81,27 @@ We catch any exceptions thrown and return them as errors to the host. Suppose we ```c #include "extism-pdk.h" #include +#include #include -const char *greeting = "Hello, "; -uint64_t greetingLen = 7; +#define Greet_Max_Input 1024 +static const char Greeting[] = "Hello, "; -bool is_benjamin(const char *name) { - size_t nameLen = strlen(name); - return strncasecmp(name, "benjamin", nameLen) == 0; +static bool is_benjamin(const char *name) { + return strcasecmp(name, "benjamin") == 0; } -int32_t greet() { +int32_t EXTISM_EXPORTED_FUNCTION(greet) { uint64_t inputLen = extism_input_length(); + const uint64_t greetMaxString = Greet_Max_Input - 1; + if (inputLen > greetMaxString) { + inputLen = greetMaxString; + } // Load input - uint8_t inputData[inputLen]; + static uint8_t inputData[Greet_Max_Input]; extism_load_input(inputData, inputLen); + inputData[inputLen] = '\0'; // Check if the input matches "benjamin", if it does // return an error @@ -101,9 +112,10 @@ int32_t greet() { } // Allocate a new offset used to store greeting and name - uint64_t outputLen = greetingLen + inputLen; + const uint64_t greetingLen = sizeof(Greeting) - 1; + const uint64_t outputLen = greetingLen + inputLen; ExtismPointer offs = extism_alloc(outputLen); - extism_store(offs, (const uint8_t *)greeting, greetingLen); + extism_store(offs, (const uint8_t *)Greeting, greetingLen); extism_store(offs + greetingLen, inputData, inputLen); // Set output @@ -116,7 +128,7 @@ This time we will compile our example using [wasi-sdk](https://github.com/WebAss `wasm32-wasi`, we will need to add the `-mexec-model=reactor` flag to be able to export specific functions instead of a single `_start` function: ```bash -$WASI_SDK_PATH/bin/clang -o plugin.wasm plugin.c -Wl,--export=greet -mexec-model=reactor +$WASI_SDK_PATH/bin/clang -o plugin.wasm plugin.c -mexec-model=reactor extism call plugin.wasm greet --input="Benjamin" --wasi # => Error: ERROR echo $? # print last status code @@ -134,11 +146,13 @@ plug-in. These can be useful to statically configure the plug-in with some data ```c #include "extism-pdk.h" +#include +#include -const char *greeting = "Hello, "; -uint64_t greetingLen = 7; +#define Greet_Max_Input 1024 +static const char Greeting[] = "Hello, "; -int32_t greet() { +int32_t EXTISM_EXPORTED_FUNCTION(greet) { ExtismPointer key = extism_alloc_string("user", 4); ExtismPointer value = extism_config_get(key); extism_free(key); @@ -149,17 +163,24 @@ int32_t greet() { return -1; } - uint64_t valueLen = extism_length(value); + const uint64_t valueLen = extism_length(value); // Load config value - uint8_t valueData[valueLen]; + uint8_t *valueData = malloc(valueLen); + if (valueData == NULL) { + ExtismPointer err = extism_alloc_string("OOM", 11); + extism_error_set(err); + return -1; + } extism_load(value, valueData, valueLen); // Allocate a new offset used to store greeting and name - uint64_t outputLen = greetingLen + valueLen; + const uint64_t greetingLen = sizeof(Greeting) - 1; + const uint64_t outputLen = greetingLen + valueLen; ExtismPointer offs = extism_alloc(outputLen); - extism_store(offs, (const uint8_t *)greeting, greetingLen); + extism_store(offs, (const uint8_t *)Greeting, greetingLen); extism_store(offs + greetingLen, valueData, valueLen); + free(valueData); // Set output extism_output_set(offs, outputLen); @@ -184,8 +205,9 @@ You can use `extism_var_get`, and `extism_var_set` to manipulate vars: ```c #include "extism-pdk.h" +#include -int32_t count() { +int32_t EXTISM_EXPORTED_FUNCTION(count) { ExtismPointer key = extism_alloc_string("count", 5); ExtismPointer value = extism_var_get(key); @@ -221,8 +243,9 @@ The `extism_log*` functions can be used to emit logs: ```c #include "extism-pdk.h" +#include -uint32_t log_stuff() { +int32_t EXTISM_EXPORTED_FUNCTION(log_stuff) { ExtismPointer msg = extism_alloc_string("Hello!", 6); extism_log_info(msg); extism_log_debug(msg); @@ -251,9 +274,10 @@ HTTP calls can be made using `extism_http_request`: ```c #include "extism-pdk.h" +#include #include -uint32_t call_http() { +int32_t EXTISM_EXPORTED_FUNCTION(call_http) { const char *reqStr = "{\ \"method\": \"GET\",\ \"url\": \"https://jsonplaceholder.typicode.com/todos/1\"\ @@ -271,7 +295,7 @@ uint32_t call_http() { } ``` -To test it you will need to pass `--allowed-host jsonplaceholder.typicode.com` to the `extism` CLI, otherwise the HTTP request will +To test it you will need to pass `--allow-host jsonplaceholder.typicode.com` to the `extism` CLI, otherwise the HTTP request will be rejected. ## Imports (Host Functions) @@ -304,8 +328,9 @@ To call this function, we pass an Extism pointer and receive one back: ```c #include "extism-pdk.h" +#include -int32_t hello_from_python(){ +int32_t EXTISM_EXPORTED_FUNCTION(hello_from_python) { ExtismPointer arg = extism_alloc_string("Hello!", 6); ExtismPointer res = a_python_func(arg); extism_free(arg); @@ -363,6 +388,34 @@ python3 app.py # => An argument to send to Python! ``` -### Reach Out! +## Exports (details) + +The `EXTISM_EXPORTED_FUNCTION` macro is not essential to create a plugin function and export it to the host. You may instead write a function and then export it when linking. For example, the first example may have the following signature instead: + +```c +int32_t greet(void) +``` + +Then, it can be built and linked with: + +```bash +$WASI_SDK_PATH/bin/clang -o plugin.wasm --target=wasm32-unknown-unknown -nostdlib -Wl,--no-entry -Wl,--export=greet plugin.c +``` + +Note the `-Wl,--export=greet` + +Exports names do not necessarily have to match the function name either. Going back to the first example again. Try: + +```c +EXTISM_EXPORT_AS("greet") int32_t internal_name_for_greet(void) +``` + +and build with: + +```bash +$WASI_SDK_PATH/bin/clang -o plugin.wasm --target=wasm32-unknown-unknown -nostdlib -Wl,--no-entry plugin.c +``` + +## Reach Out! Have a question or just want to drop in and say hi? [Hop on the Discord](https://extism.org/discord)! diff --git a/examples/count-vowels/Makefile b/examples/count-vowels/Makefile index 92af4c3..d61069d 100644 --- a/examples/count-vowels/Makefile +++ b/examples/count-vowels/Makefile @@ -2,7 +2,7 @@ WASI_SDK_PATH?=../../wasi-sdk .PHONY: count-vowels count-vowels: - $(WASI_SDK_PATH)/bin/clang -O2 -g -o count-vowels.wasm count-vowels.c -mexec-model=reactor -Wl,--export=count_vowels + $(WASI_SDK_PATH)/bin/clang -O2 -g -o count-vowels.wasm count-vowels.c -mexec-model=reactor -run: +run: count-vowels extism call ./count-vowels.wasm count_vowels --wasi --input "this is a test" \ No newline at end of file diff --git a/examples/count-vowels/count-vowels.c b/examples/count-vowels/count-vowels.c index 1e5b40d..afacf75 100644 --- a/examples/count-vowels/count-vowels.c +++ b/examples/count-vowels/count-vowels.c @@ -3,7 +3,7 @@ #include #include -int32_t count_vowels() { +int32_t EXTISM_EXPORTED_FUNCTION(count_vowels) { uint64_t count = 0; uint8_t ch = 0; uint64_t length = extism_input_length(); diff --git a/examples/count-vowels/count-vowels.wasm b/examples/count-vowels/count-vowels.wasm deleted file mode 100755 index 40c0256..0000000 Binary files a/examples/count-vowels/count-vowels.wasm and /dev/null differ diff --git a/examples/globals/Makefile b/examples/globals/Makefile index 346586d..f13ef06 100644 --- a/examples/globals/Makefile +++ b/examples/globals/Makefile @@ -1,8 +1,8 @@ WASI_SDK_PATH?=../../wasi-sdk .PHONY: globals -count-vowels: - $(WASI_SDK_PATH)/bin/clang -O2 -g -o globals.wasm globals.c -mexec-model=reactor -Wl,--export=globals +globals: + $(WASI_SDK_PATH)/bin/clang -O2 -g -o globals.wasm globals.c -mexec-model=reactor -run: - extism call ./globals.wasm globals --loop 100 +run: globals + extism call ./globals.wasm globals --loop 100 --wasi diff --git a/examples/globals/globals.c b/examples/globals/globals.c index eed2105..ca2cd96 100644 --- a/examples/globals/globals.c +++ b/examples/globals/globals.c @@ -4,7 +4,7 @@ uint64_t count = 0; -int32_t globals() { +int32_t EXTISM_EXPORTED_FUNCTION(globals) { char out[128]; int n = snprintf(out, 128, "{\"count\": %llu}", count); diff --git a/examples/globals/globals.wasm b/examples/globals/globals.wasm deleted file mode 100755 index 0b6c5e2..0000000 Binary files a/examples/globals/globals.wasm and /dev/null differ diff --git a/examples/host-functions/Makefile b/examples/host-functions/Makefile index be1451d..3a90686 100644 --- a/examples/host-functions/Makefile +++ b/examples/host-functions/Makefile @@ -2,7 +2,7 @@ WASI_SDK_PATH?=../../wasi-sdk .PHONY: host-functions host-functions: - $(WASI_SDK_PATH)/bin/clang -O2 -g -o host-functions.wasm host-functions.c -mexec-model=reactor -Wl,--export=count_vowels -Wl,--import-undefined + $(WASI_SDK_PATH)/bin/clang -O2 -g -o host-functions.wasm host-functions.c -mexec-model=reactor -Wl,--import-undefined # run: # extism call ./count-vowels.wasm count_vowels --wasi --input "this is a test" \ No newline at end of file diff --git a/examples/host-functions/host-functions.c b/examples/host-functions/host-functions.c index 43a3d00..d4af75e 100644 --- a/examples/host-functions/host-functions.c +++ b/examples/host-functions/host-functions.c @@ -5,7 +5,7 @@ IMPORT("extism:host/user", "hello_world") extern uint64_t hello_world(uint64_t); -int32_t count_vowels() { +int32_t EXTISM_EXPORTED_FUNCTION(count_vowels) { uint64_t length = extism_input_length(); if (length == 0) { diff --git a/examples/infinite-loop/Makefile b/examples/infinite-loop/Makefile index 197558d..7f0fa01 100644 --- a/examples/infinite-loop/Makefile +++ b/examples/infinite-loop/Makefile @@ -4,5 +4,5 @@ WASI_SDK_PATH?=../../wasi-sdk infinite-loop: $(WASI_SDK_PATH)/bin/clang -O2 -g -o infinite-loop.wasm infinite-loop.c -mexec-model=reactor -Wl,--export=infinite_loop -run: +run: infinite-loop extism call --manifest ./manifest.json infinite_loop --wasi diff --git a/examples/infinite-loop/infinite-loop.c b/examples/infinite-loop/infinite-loop.c index cdeb5fa..57247c6 100644 --- a/examples/infinite-loop/infinite-loop.c +++ b/examples/infinite-loop/infinite-loop.c @@ -1,6 +1,6 @@ #include -int32_t infinite_loop() { +int32_t infinite_loop(void) { unsigned int i = 0; while (1) { i += 1; diff --git a/examples/infinite-loop/infinite-loop.wasm b/examples/infinite-loop/infinite-loop.wasm deleted file mode 100755 index dc25439..0000000 Binary files a/examples/infinite-loop/infinite-loop.wasm and /dev/null differ diff --git a/tests/Makefile b/tests/Makefile index 2d4d429..ccb201b 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,10 +1,11 @@ +WASI_SDK_PATH?=../wasi-sdk test: test-alloc test-hello run-fail: test-fail build: alloc hello fail %: %.c - clang --target=wasm32-unknown-unknown -o $*.wasm $*.c -Wl,--no-entry -Wl,--export=run_test -nostdlib + $(WASI_SDK_PATH)/bin/clang --target=wasm32-unknown-unknown -o $*.wasm $*.c -Wl,--no-entry -nostdlib test-%: % extism call $*.wasm run_test diff --git a/tests/alloc.c b/tests/alloc.c index 8d93606..45aa47f 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -4,7 +4,7 @@ #define NPAGES1 5 #define PAGE1 65539 -int32_t run_test() { +int32_t EXTISM_EXPORTED_FUNCTION(run_test) { ExtismPointer buf[NPAGES0][NPAGES1]; for (int i = 0; i < NPAGES0; i++) { for (int j = 0; j < NPAGES1; j++) { diff --git a/tests/fail.c b/tests/fail.c index 17ed75c..f6b9f9a 100644 --- a/tests/fail.c +++ b/tests/fail.c @@ -1,6 +1,6 @@ #include "util.h" -int32_t run_test() { +int32_t EXTISM_EXPORTED_FUNCTION(run_test) { const char *msg = "Some error message"; ExtismPointer ptr = extism_alloc(strlen(msg)); extism_store(ptr, (const uint8_t *)msg, strlen(msg)); diff --git a/tests/hello.c b/tests/hello.c index 899bebb..8261751 100644 --- a/tests/hello.c +++ b/tests/hello.c @@ -1,6 +1,6 @@ #include "util.h" -int32_t run_test() { +EXTISM_EXPORT_AS("run_test") int32_t run_test(void) { const char *msg = "Hello, world!"; ExtismPointer ptr = extism_alloc(strlen(msg)); assert(ptr > 0);