-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
--------- Signed-off-by: Glenn Lewis <[email protected]>
- Loading branch information
Showing
30 changed files
with
941 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
../../lib/pdk/*.v3 | ||
../../lib/util/*.v3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# apps/Extism | ||
|
||
Here are a few [Extism] plugins written using the Virgil programming language. | ||
|
||
[Extism]: https://extism.org/ | ||
|
||
* [greet](greet) | ||
* [count-vowels](count-vowels) | ||
* [http-get](http-get) | ||
|
||
## Build | ||
|
||
To build the examples, change your directory to this one (`apps/Extism`) | ||
and type: | ||
|
||
```bash | ||
$ ./build.sh | ||
``` | ||
|
||
## Run | ||
|
||
To run all the examples with the [Extism CLI], type: | ||
|
||
```bash | ||
$ ./run.sh | ||
``` | ||
|
||
[Extism CLI]: https://github.com/extism/cli |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
wasm |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// This is a simulated Extism SDK written in JavaScript in order to assist | ||
// in the debugging of the MoonBit Extism PDK. | ||
|
||
// Adapted from: https://dmitripavlutin.com/timeout-fetch-request/ | ||
export const fetchWithTimeout = async (resource, options = {}) => { | ||
const { timeout = 8000 } = options // 8000 ms = 8 seconds | ||
|
||
const controller = new AbortController() | ||
const id = setTimeout(() => controller.abort(), timeout) | ||
const response = await fetch(resource, { | ||
...options, | ||
signal: controller.signal, | ||
}) | ||
clearTimeout(id) | ||
return response | ||
} | ||
|
||
// `log` and `flust` are useful for debugging the wasm-gc or wasm targets with `println()`: | ||
export const [log, flush] = (() => { | ||
var buffer = [] | ||
function flush() { | ||
if (buffer.length > 0) { | ||
console.log(new TextDecoder("utf-16").decode(new Uint16Array(buffer).valueOf())) | ||
buffer = [] | ||
} | ||
} | ||
function log(ch) { | ||
if (ch == '\n'.charCodeAt(0)) { flush() } | ||
else if (ch == '\r'.charCodeAt(0)) { /* noop */ } | ||
else { buffer.push(ch) } | ||
} | ||
return [log, flush] | ||
})() | ||
|
||
const memory = new WebAssembly.Memory({ initial: 1, maximum: 1, shared: false }) | ||
const fakeAlloc = { offset: 0, buffers: {} } | ||
const alloc = (lengthBigInt) => { | ||
const offset = fakeAlloc.offset | ||
const length = Number(lengthBigInt) | ||
fakeAlloc.buffers[offset] = { | ||
offset, | ||
length, | ||
buffer: new Uint8Array(memory.buffer, offset, length), | ||
} | ||
fakeAlloc.offset += length | ||
return BigInt(offset) | ||
} | ||
const allocAndCopy = (str) => { | ||
const offsetBigInt = alloc(BigInt(str.length)) | ||
const offset = Number(offsetBigInt) | ||
const b = fakeAlloc.buffers[offset] | ||
for (let i = 0; i < str.length; i++) { b.buffer[i] = str.charCodeAt(i) } | ||
return offsetBigInt | ||
} | ||
const decodeOffset = (offset) => new TextDecoder().decode(fakeAlloc.buffers[offset].buffer) | ||
const lastHttpResponse = { statusCode: 0 } | ||
const http_request = async (reqOffsetBigInt, bodyOffsetBigInt) => { | ||
const req = JSON.parse(decodeOffset(reqOffsetBigInt)) | ||
const body = bodyOffsetBigInt ? decodeOffset(bodyOffsetBigInt) : '' | ||
console.log(`http_request: req=${JSON.stringify(req)}`) | ||
console.log(`http_request: body=${body}`) | ||
const fetchParams = { | ||
method: req.method, | ||
headers: req.header, | ||
} | ||
if (body) { fetchParams.body = body } | ||
const response = await fetchWithTimeout(req.url, fetchParams) | ||
const result = await response.text() | ||
console.log(`result=${result}`) | ||
lastHttpResponse.statusCode = response.status | ||
return allocAndCopy(result) | ||
} | ||
const http_status_code = () => lastHttpResponse.statusCode | ||
|
||
export const configs = {} // no configs to start with | ||
export const vars = {} // no vars to start with | ||
|
||
export const inputString = { value: '' } // allows for exporting | ||
|
||
export const importObject = { | ||
"extism:host/env": { | ||
alloc, | ||
config_get: (offsetBigInt) => { | ||
const offset = Number(offsetBigInt) | ||
const key = decodeOffset(offset) | ||
// console.log(`config_get(${offset}) = configs[${key}] = ${configs[key]}`) | ||
if (!configs[key]) { return BigInt(0) } | ||
return allocAndCopy(configs[key]) | ||
}, | ||
free: () => { }, // noop for now. | ||
http_request, | ||
http_status_code, | ||
input_length: () => BigInt(inputString.value.length), | ||
input_load_u8: (offsetBigInt) => { | ||
const offset = Number(offsetBigInt) | ||
if (offset < inputString.value.length) { return inputString.value.charCodeAt(offset) } | ||
console.error(`input_load_u8: wasm requested offset(${offset}) > inputString.value.length(${inputString.value.length})`) | ||
return 0 | ||
}, | ||
length: (offsetBigInt) => { | ||
const offset = Number(offsetBigInt) | ||
const b = fakeAlloc.buffers[offset] | ||
if (!b) { return BigInt(0) } | ||
// console.log(`length(${offset}) = ${b.length}`) | ||
return BigInt(b.length) | ||
}, | ||
load_u8: (offsetBigInt) => { | ||
const offset = Number(offsetBigInt) | ||
const bs = Object.keys(fakeAlloc.buffers).filter((key) => { | ||
const b = fakeAlloc.buffers[key] | ||
return (offset >= b.offset && offset < b.offset + b.length) | ||
}) | ||
if (bs.length !== 1) { | ||
console.error(`load_u8: offset ${offset} not found`) | ||
return 0 | ||
} | ||
const key = bs[0] | ||
const b = fakeAlloc.buffers[key] | ||
const byte = b.buffer[offset - key] | ||
// console.log(`load_u8(${offset}) = ${byte}`) | ||
return byte | ||
}, | ||
log_info: (offset) => console.info(`log_info: ${decodeOffset(offset)}`), | ||
log_debug: (offset) => console.log(`log_debug: ${decodeOffset(offset)}`), | ||
log_error: (offset) => console.error(`log_error: ${decodeOffset(offset)}`), | ||
log_warn: (offset) => console.warn(`log_warn: ${decodeOffset(offset)}`), | ||
output_set: (offset) => console.log(decodeOffset(offset)), | ||
store_u8: (offsetBigInt, byte) => { | ||
const offset = Number(offsetBigInt) | ||
Object.keys(fakeAlloc.buffers).forEach((key) => { | ||
const b = fakeAlloc.buffers[key] | ||
if (offset >= b.offset && offset < b.offset + b.length) { | ||
b.buffer[offset - key] = byte | ||
// console.log(`store_u8(${offset})=${byte}`) | ||
// if (offset == b.offset + b.length - 1) { | ||
// console.log(`store_u8 completed offset=${key}..${offset}, length=${b.length}: '${decodeOffset(key)}'`) | ||
// } | ||
} | ||
}) | ||
}, | ||
var_get: (offsetBigInt) => { | ||
const offset = Number(offsetBigInt) | ||
const key = decodeOffset(offset) | ||
// console.log(`var_get(${offset}) = vars[${key}] = ${vars[key]}`) | ||
if (!vars[key]) { return BigInt(0) } | ||
return vars[key] // BigInt | ||
}, | ||
var_set: (offsetBigInt, bufOffsetBigInt) => { | ||
const offset = Number(offsetBigInt) | ||
const key = decodeOffset(offset) | ||
// console.log(`var_set(${offset}, ${bufOffsetBigInt}) = vars[${key}]`) | ||
vars[key] = bufOffsetBigInt | ||
}, | ||
}, | ||
spectest: { print_char: log }, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#!/bin/bash -ex | ||
v3c \ | ||
-entry-export=_initialize \ | ||
-heap-size=500m \ | ||
-main-export=_initialize \ | ||
-output=greet \ | ||
-target=wasm \ | ||
greet/Greet.v3 $(cat DEPS) | ||
|
||
v3c \ | ||
-entry-export=_initialize \ | ||
-heap-size=500m \ | ||
-main-export=_initialize \ | ||
-output=count-vowels \ | ||
-target=wasm \ | ||
count-vowels/CountVowels.v3 $(cat DEPS) | ||
|
||
v3c \ | ||
-entry-export=_initialize \ | ||
-heap-size=500m \ | ||
-main-export=_initialize \ | ||
-output=http-get \ | ||
-target=wasm \ | ||
http-get/HttpGet.v3 $(cat DEPS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// `defaultVowels` represents the default set of vowels | ||
// if the host provides no "config.vowels" string. | ||
def defaultVowels = "aeiouAEIOU"; | ||
|
||
// `VowelReport` represents the JSON struct returned to the host. | ||
class VowelReport { | ||
var count: int; | ||
var total: int; | ||
var vowels: string; | ||
|
||
new(count, total, vowels) {} | ||
|
||
def toJson() -> string { | ||
var b = StringBuilder.new(); | ||
b.put1("{\"count\":%d,", count); | ||
b.put1("\"total\":%d,", total); | ||
b.put1("\"vowels\":\"%s\"}", vowels); | ||
return b.toString(); | ||
} | ||
} | ||
|
||
def getTotal() -> int { | ||
match (Var.getInt("total")) { | ||
Some(total) => return total; | ||
None => return 0; | ||
} | ||
} | ||
|
||
def storeTotal(total: int) { | ||
Var.setInt("total", total); | ||
} | ||
|
||
def getVowels() -> string { | ||
match (Config.get("vowels")) { | ||
Some(s) => return s; | ||
None => return defaultVowels; | ||
} | ||
} | ||
|
||
// Exported: `count_vowels` reads the input string from the host, reads the "vowels" | ||
// config from the host, then counts the number of vowels in the input | ||
// string and keeps a running total (over multiple iterations) | ||
// in the host's "total" var. | ||
// It sends the JSON `VowelReport` to the host via its output data channel. | ||
// It returns 0 to the host on success. | ||
def count_vowels() -> int { | ||
def input = Host.inputString(); | ||
// | ||
def vowels = getVowels(); | ||
def vowelsArr = Array<byte>.!(vowels); | ||
def count = Arrays.filter(input, Arrays.contains(vowelsArr, _)).length; | ||
// | ||
def total = getTotal() + count; | ||
storeTotal(total); | ||
// | ||
def jsonStr = VowelReport.new(count, total, vowels).toJson(); | ||
Host.outputString(jsonStr); | ||
return 0; | ||
} | ||
|
||
// Unused. | ||
def main() { | ||
} | ||
|
||
export count_vowels; | ||
|
||
// Hack to compile to wasm: provide Virgil identifiers that cannot be found: | ||
component System { | ||
def error(s1: string, s2: string) {} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# CountVowels | ||
|
||
The `CountVowels.wasm` plugin can be run from the top-level of the repo by | ||
typing: | ||
|
||
```bash | ||
$ ./build.sh | ||
$ ./scripts/python-server.sh | ||
``` | ||
|
||
Then open your browser window to: | ||
http://localhost:8080/examples/count-vowels |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
|
||
<head></head> | ||
|
||
<body> | ||
<script type="module"> | ||
import { configs, flush, importObject, inputString } from '/assets/simulatedExtismSdk.js' | ||
|
||
const wasmUnderTest = '/count-vowels/CountVowels.wasm' | ||
|
||
// WebAssembly.instantiateStreaming(fetch("/target/wasm-gc/release/build/examples/count-vowels/count-vowels.wasm"), importObject).then( | ||
WebAssembly.instantiateStreaming(fetch(wasmUnderTest), importObject).then( | ||
(obj) => { | ||
console.log('Using simulated Extism SDK...') | ||
obj.instance.exports._initialize() | ||
// configs.vowels = 'aeiouyAEIOUY' | ||
inputString.value = 'Once upon a dream' | ||
obj.instance.exports['count_vowels']() | ||
inputString.value = 'eight more vowels yo' | ||
obj.instance.exports['count_vowels']() | ||
flush() | ||
} | ||
) | ||
|
||
// Next, use the official Extism JavaScript SDK: | ||
// Read the JS SDK docs at: https://extism.github.io/js-sdk/ | ||
const extism = await import('https://esm.sh/@extism/extism') | ||
|
||
const plugin = await extism.createPlugin( | ||
fetch(wasmUnderTest), | ||
{ useWasi: true } | ||
) | ||
|
||
console.log('Using official Extism JavaScript SDK...') | ||
let out = await plugin.call('count_vowels', 'from official Extism JavaScript SDK') | ||
console.log(out.text()) | ||
out = await plugin.call('count_vowels', 'eight more vowels yo ho') | ||
console.log(out.text()); | ||
</script> | ||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Exported: `greet` reads the input string from the host and writes a | ||
// greeting to the host's output string. | ||
// It returns 0 to the host on success. | ||
def greet() -> int { | ||
def input = Host.inputString(); | ||
var b = StringBuilder.new(); | ||
b.puts("Hello, "); | ||
b.puts(input); | ||
b.puts("!"); | ||
Host.outputString(b.toString()); | ||
return 0; // success | ||
} | ||
|
||
// Unused. | ||
def main() { | ||
} | ||
|
||
export greet; | ||
|
||
// Hack to compile to wasm: provide Virgil identifiers that cannot be found: | ||
component System { | ||
def error(s1: string, s2: string) {} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Greet | ||
|
||
The `Greet.wasm` plugin can be run from the apps/Extism dir of the repo | ||
(the parent of this dir) by typing: | ||
|
||
```bash | ||
$ ./build.sh | ||
$ ./scripts/python-server.sh | ||
``` | ||
|
||
Then open your browser window to: | ||
http://localhost:8080/greet |
Oops, something went wrong.