Learning WASM #6
February 12, 2022•343 words
前回はqjsからhostの関数を呼び出した。
今回は、実際に通信できるように read
と write
を生やしてみる。
C側は puts
や getLine
を参考にこんな風に実装。dotnet側には1byteずつread writeする関数を生やして、jsモジュールとしては改行コードもしくはEOFまで書き込み読み込みし続けるreadLine
、writeLine
として実装する。
__attribute__((import_name("read")))extern int host_read();
__attribute__((import_name("write")))extern void host_write(int c);
static JSValue js_host_read_line(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
int c;
DynBuf dbuf;
JSValue obj;
js_std_dbuf_init(ctx, &dbuf);
for(;;) {
c = host_read();
if (c == EOF) {
if (dbuf.size == 0) {
/* EOF */
dbuf_free(&dbuf);
return JS_NULL;
} else {
break;
}
}
if (c == '\n')
break;
if (dbuf_putc(&dbuf, c)) {
dbuf_free(&dbuf);
return JS_ThrowOutOfMemory(ctx);
}
}
obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size);
dbuf_free(&dbuf);
return obj;
}
static JSValue js_host_write_line(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
int i,j;
size_t len;
const char *str;
for(i = 0; i < argc; i++) {
str = JS_ToCStringLen(ctx, &len, argv[i]);
if (!str)
return JS_EXCEPTION;
for (j = 0; j < len; j++) {
host_write(str[j]);
}
host_write('\n');
JS_FreeCString(ctx, str);
}
return JS_UNDEFINED;
}
static const JSCFunctionListEntry js_host_funcs[] = {
JS_CFUNC_DEF("readLine", 0, js_host_read_line),
JS_CFUNC_DEF("writeLine", 1, js_host_write_line),
};
static int js_host_init(JSContext *ctx, JSModuleDef *m)
{
return JS_SetModuleExportList(ctx, m, js_host_funcs,
countof(js_host_funcs));
}
JSModuleDef *js_init_module_host(JSContext *ctx, const char *module_name)
{
JSModuleDef *m;
m = JS_NewCModule(ctx, module_name, js_host_init);
if (!m)
return NULL;
JS_AddModuleExportList(ctx, m, js_host_funcs, countof(js_host_funcs));
return m;
}
dotnet側はとりあえずStream
に直結させるリングバッファとか考えたけどこれが手っ取り早い
using System;
using System.IO;
using System.Text;
using Wasmtime;
var dir = AppDomain.CurrentDomain.BaseDirectory;
var script =
@"
import * as host from 'host';
console.log('-- start js');
host.writeLine('Hello, I\'m js!');
const str = host.readLine();
console.log(str);
console.log('-- end js');
";
File.WriteAllText(Path.Combine(dir, "main.js"), script);
using var rbuf = new MemoryStream(Encoding.UTF8.GetBytes("Hello, I'm dotnet!\n"));
using var wbuf = new MemoryStream();
using var engine = new Engine();
using var module = Module.FromFile(engine, "qjs.wasm");
using var linker = new Linker(engine);
using var store = new Store(engine);
linker.DefineFunction("env", "read", () => {
return (int)rbuf.ReadByte();
});
linker.DefineFunction("env", "write", (int c) => {
wbuf.WriteByte((byte)c);
});
linker.DefineWasi();
store.SetWasiConfiguration(new WasiConfiguration()
.WithPreopenedDirectory(dir, ".")
.WithInheritedStandardOutput()
.WithArgs("--", "main.js"));
var instance = linker.Instantiate(store, module);
var run = instance.GetFunction(store, "_start");
run.Invoke(store);
wbuf.Seek(0, SeekOrigin.Begin);
var str = Encoding.UTF8.GetString(wbuf.ToArray());
Console.WriteLine(str);
実行結果はこんな感じ。 qjs内でdotnetから読み込んだ文字列を標準出力して、実行完了後、dotnet側でjsから受け取った文字列を標準出力してる。
-- start js
Hello, I'm dotnet!
-- end js
Hello, I'm js!
これで、文字列のやり取りができるようになったので、JSON RPCはできそう。
ただ、ループで回してたりするからjsからdotnetにRPCしている中でdotnetからjsにRPCするような、行ったり来たりするRPCはできそうにない。
まあそれは出来なくても大丈夫なはず。
次はJSON RPCを組んでみようと思う。