Skip to content

Commit

Permalink
docs/test/build: use EXTISM_EXPORTED_FUNCTION (#13)
Browse files Browse the repository at this point in the history
* docs: use EXTISM_EXPORTED_FUNCTION, avoid stack allocated VLAs, add Exports details section

* build: remove example wasm files from git

* test/examples: update most of them to use EXTISM_EXPORTED_FUNCTION, fix tests Makefile to use WASI_SDK_PATH

* docs: always check the return value of malloc

* build: add .clang-format

* tests: use clang by default instead, override with CLANG

* examples: fix run

* Revert "tests: use clang by default instead, override with CLANG"

This reverts commit c1401b8.

* tests: fix wasi_sdk_path

* test: switch tests to use export macros

* docs: fix typo

Co-authored-by: zach <[email protected]>

---------

Co-authored-by: zach <[email protected]>
  • Loading branch information
G4Vi and zshipko committed Nov 30, 2023
1 parent 5acb74f commit 04c7f05
Show file tree
Hide file tree
Showing 17 changed files with 106 additions and 50 deletions.
2 changes: 2 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
BasedOnStyle: LLVM
IndentWidth: 2
121 changes: 87 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <stdint.h>

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
Expand All @@ -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:
Expand All @@ -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 <stdbool.h>
#include <stdint.h>
#include <string.h>

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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 <stdint.h>
#include <stdlib.h>

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);
Expand All @@ -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);
Expand All @@ -184,8 +205,9 @@ You can use `extism_var_get`, and `extism_var_set` to manipulate vars:

```c
#include "extism-pdk.h"
#include <stdint.h>

int32_t count() {
int32_t EXTISM_EXPORTED_FUNCTION(count) {
ExtismPointer key = extism_alloc_string("count", 5);
ExtismPointer value = extism_var_get(key);

Expand Down Expand Up @@ -221,8 +243,9 @@ The `extism_log*` functions can be used to emit logs:
```c
#include "extism-pdk.h"
#include <stdint.h>
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);
Expand Down Expand Up @@ -251,9 +274,10 @@ HTTP calls can be made using `extism_http_request`:

```c
#include "extism-pdk.h"
#include <stdint.h>
#include <string.h>

uint32_t call_http() {
int32_t EXTISM_EXPORTED_FUNCTION(call_http) {
const char *reqStr = "{\
\"method\": \"GET\",\
\"url\": \"https://jsonplaceholder.typicode.com/todos/1\"\
Expand All @@ -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)
Expand Down Expand Up @@ -304,8 +328,9 @@ To call this function, we pass an Extism pointer and receive one back:
```c
#include "extism-pdk.h"
#include <stdint.h>
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);
Expand Down Expand Up @@ -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)!
4 changes: 2 additions & 2 deletions examples/count-vowels/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion examples/count-vowels/count-vowels.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <stdio.h>
#include <string.h>

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();
Expand Down
Binary file removed examples/count-vowels/count-vowels.wasm
Binary file not shown.
8 changes: 4 additions & 4 deletions examples/globals/Makefile
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion examples/globals/globals.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Binary file removed examples/globals/globals.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion examples/host-functions/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion examples/host-functions/host-functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion examples/infinite-loop/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion examples/infinite-loop/infinite-loop.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <stdint.h>

int32_t infinite_loop() {
int32_t infinite_loop(void) {
unsigned int i = 0;
while (1) {
i += 1;
Expand Down
Binary file removed examples/infinite-loop/infinite-loop.wasm
Binary file not shown.
3 changes: 2 additions & 1 deletion tests/Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down
2 changes: 1 addition & 1 deletion tests/fail.c
Original file line number Diff line number Diff line change
@@ -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));
Expand Down
2 changes: 1 addition & 1 deletion tests/hello.c
Original file line number Diff line number Diff line change
@@ -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);
Expand Down

0 comments on commit 04c7f05

Please sign in to comment.