提交 d93a368d 编写于 作者: chai2010's avatar chai2010

包装 wabt 和 wazero

上级 a21a3458
// 版权 @2023 凹语言 作者。保留所有权利。
package wabt
import (
"bytes"
"errors"
"os"
"os/exec"
"path/filepath"
"sync"
"wa-lang.org/wa/internal/config"
"wa-lang.org/wa/internal/logger"
"wa-lang.org/wabt-go"
)
var muWabt sync.Mutex
var wat2wasmPath string
func init() {
const baseName = "wa.wat2wasm.exe"
// 1. exe 同级目录存在 wat2wasm ?
wat2wasmPath = filepath.Join(curExeDir(), baseName)
if exeExists(wat2wasmPath) {
return
}
// 2. 当前目录存在 wat2wasm ?
cwd, _ := os.Getwd()
wat2wasmPath = filepath.Join(cwd, baseName)
if exeExists(wat2wasmPath) {
return
}
// 3. 本地系统存在 wat2wasm ?
if s, _ := exec.LookPath(baseName); s != "" {
wat2wasmPath = s
return
}
// 4. wat2wasm 安装到 exe 所在目录 ?
wat2wasmPath = filepath.Join(curExeDir(), baseName)
if err := os.WriteFile(wat2wasmPath, wabt.LoadWat2Wasm(), 0777); err != nil {
logger.Tracef(&config.EnableTrace_app, "install wat2wasm failed: %+v", err)
return
}
}
func Wat2Wasm(watBytes []byte) (wasmBytes []byte, err error) {
muWabt.Lock()
defer muWabt.Unlock()
if wat2wasmPath == "" {
logger.Tracef(&config.EnableTrace_app, "wat2wasm not found")
return nil, errors.New("wat2wasm not found")
}
var bufStdout bytes.Buffer
var bufStderr bytes.Buffer
// wat2wasm - --output=-
cmd := exec.Command(wat2wasmPath, "-", "--output=-")
cmd.Stdin = bytes.NewReader(watBytes)
cmd.Stdout = &bufStdout
cmd.Stderr = &bufStderr
err = cmd.Run()
wasmBytes = bufStdout.Bytes()
if err != nil && bufStderr.Len() > 0 {
err = errors.New(bufStderr.String())
}
return
}
// exe 文件存在
func exeExists(path string) bool {
fi, err := os.Lstat(path)
if err != nil {
return false
}
if !fi.Mode().IsRegular() {
return false
}
return true
}
// 当前执行程序所在目录
func curExeDir() string {
s, err := os.Executable()
if err != nil {
logger.Panicf("os.Executable() failed: %+v", err)
}
return filepath.Dir(s)
}
// 版权 @2023 凹语言 作者。保留所有权利。
package wabt_test
import (
"strings"
"testing"
"wa-lang.org/wa/api"
"wa-lang.org/wa/internal/wabt"
)
func TestWat2Wasm(t *testing.T) {
for i, tt := range watTests {
_, err := wabt.Wat2Wasm([]byte(tt.watCode))
if !tMatchErrMsg(err, tt.errMsg) {
t.Fatalf("%d: check failed: %v", i, err)
}
}
}
func BenchmarkWat2Wasm(b *testing.B) {
wat := tBuildWat(t_hello_wa)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := wabt.Wat2Wasm([]byte(wat)); err != nil {
b.Fatal(err)
}
}
}
func tMatchErrMsg(err error, errMsg string) bool {
if errMsg == "" {
return err == nil
}
return strings.Contains(err.Error(), errMsg)
}
func tBuildWat(waCode string) string {
watBytes, err := api.BuildFile(api.DefaultConfig(), "main.wa", waCode)
if err != nil {
return err.Error()
}
return string(watBytes)
}
var watTests = []struct {
watCode string
errMsg string
}{
{
watCode: ``,
errMsg: `error: unexpected token "EOF", expected a module field or a module.`,
},
{
watCode: `()`,
errMsg: `error: unexpected token ")", expected a module field or a module.`,
},
{
watCode: `(module)`,
errMsg: "",
},
{
watCode: `
(module
(func)
(memory 1)
)`,
errMsg: "",
},
{
watCode: t_hello_wasi,
errMsg: "",
},
{
watCode: tBuildWat(t_hello_wa),
errMsg: "",
},
}
const t_hello_wasi = `
(module $hello_wasi
;; type iov struct { iov_base, iov_len int32 }
;; func fd_write(id *iov, iovs_len int32, nwritten *int32) (written int32)
(import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
(memory 1)(export "memory" (memory 0))
;; 前 8 个字节保留给 iov 数组, 字符串从地址 8 开始
(data (i32.const 8) "hello world\n")
;; _start 类似 main 函数, 自动执行
(func $main (export "_start")
(i32.store (i32.const 0) (i32.const 8)) ;; iov.iov_base - 字符串地址为 8
(i32.store (i32.const 4) (i32.const 12)) ;; iov.iov_len - 字符串长度
(call $fd_write
(i32.const 1) ;; 1 对应 stdout
(i32.const 0) ;; *iovs - 前 8 个字节保留给 iov 数组
(i32.const 1) ;; len(iovs) - 只有1个字符串
(i32.const 20) ;; nwritten - 指针, 里面是要写到数据长度
)
drop ;; 忽略返回值
)
)
`
const t_hello_wa = `
// 版权 @2019 凹语言 作者。保留所有权利。
import "fmt"
import "runtime"
global year: i32 = 2023
func main {
println("你好,凹语言!", runtime.WAOS)
println(add(40, 2), year)
fmt.Println("1+1 =", 1+1)
}
func add(a: i32, b: i32) => i32 {
return a+b
}
`
// 版权 @2023 凹语言 作者。保留所有权利。
package wazero
import (
"bytes"
"context"
"crypto/rand"
"fmt"
"os"
"runtime"
"strings"
"sync"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"wa-lang.org/wa/internal/app/waruntime"
"wa-lang.org/wa/internal/config"
)
// wasm 模块, 可多次执行
type Module struct {
cfg *config.Config
wasmName string
wasmBytes []byte
wasmArgs []string
stdoutBuffer bytes.Buffer
stderrBuffer bytes.Buffer
wazeroOnce sync.Once
wazeroCtx context.Context
wazeroConf wazero.ModuleConfig
wazeroRuntime wazero.Runtime
wazeroCompileModule wazero.CompiledModule
wazeroModule api.Module
wazeroInitErr error
}
// 构建模块(会执行编译)
func BuildModule(
cfg *config.Config, wasmName string, wasmBytes []byte, wasmArgs ...string,
) (*Module, error) {
m := &Module{
cfg: cfg,
wasmName: wasmName,
wasmBytes: wasmBytes,
wasmArgs: wasmArgs,
}
if err := m.buildModule(); err != nil {
return nil, err
}
return m, nil
}
// 执行初始化, 仅执行一次
func (p *Module) RunInitOnce() (stdout, stderr []byte, err error) {
p.wazeroOnce.Do(func() { p.runInitFunc() })
stdout = p.stdoutBuffer.Bytes()
stderr = p.stderrBuffer.Bytes()
err = p.wazeroInitErr
return
}
// 执行指定函数(init会被强制执行一次)
func (p *Module) RunFunc(name string, args ...uint64) (result []uint64, stdout, stderr []byte, err error) {
stdout, stderr, err = p.RunInitOnce()
if err != nil {
return
}
p.stdoutBuffer.Reset()
p.stderrBuffer.Reset()
fn := p.wazeroModule.ExportedFunction(name)
if fn == nil {
err = fmt.Errorf("wazero: func %q not found", name)
return
}
result, err = fn.Call(p.wazeroCtx, args...)
stdout = p.stdoutBuffer.Bytes()
stderr = p.stderrBuffer.Bytes()
return
}
// 关闭模块
func (p *Module) Close() error {
var err error
if p.wazeroRuntime != nil {
err = p.wazeroRuntime.Close(p.wazeroCtx)
p.wazeroRuntime = nil
}
return err
}
func (p *Module) buildModule() error {
p.wazeroCtx = context.Background()
p.wazeroConf = wazero.NewModuleConfig().
WithStdout(&p.stdoutBuffer).
WithStderr(&p.stderrBuffer).
WithStdin(os.Stdin).
WithRandSource(rand.Reader).
WithSysNanosleep().
WithSysNanotime().
WithSysWalltime().
WithArgs(append([]string{p.wasmName}, p.wasmArgs...)...)
// TODO: Windows 可能导致异常, 临时屏蔽
if runtime.GOOS != "windows" {
for _, s := range os.Environ() {
var key, value string
if kv := strings.Split(s, "="); len(kv) >= 2 {
key = kv[0]
value = kv[1]
} else if len(kv) >= 1 {
key = kv[0]
}
p.wazeroConf = p.wazeroConf.WithEnv(key, value)
}
}
p.wazeroRuntime = wazero.NewRuntime(p.wazeroCtx)
var err error
p.wazeroCompileModule, err = p.wazeroRuntime.CompileModule(p.wazeroCtx, p.wasmBytes)
if err != nil {
p.wazeroInitErr = err
return err
}
switch p.cfg.WaOS {
case config.WaOS_arduino:
if _, err = waruntime.ArduinoInstantiate(p.wazeroCtx, p.wazeroRuntime); err != nil {
p.wazeroInitErr = err
return err
}
case config.WaOS_chrome:
if _, err = waruntime.ChromeInstantiate(p.wazeroCtx, p.wazeroRuntime); err != nil {
p.wazeroInitErr = err
return err
}
case config.WaOS_wasi:
if _, err = waruntime.WasiInstantiate(p.wazeroCtx, p.wazeroRuntime); err != nil {
p.wazeroInitErr = err
return err
}
}
return nil
}
func (p *Module) runInitFunc() {
if err := p.wazeroInitErr; err != nil {
return
}
if p.wazeroModule != nil {
return
}
p.wazeroModule, p.wazeroInitErr = p.wazeroRuntime.InstantiateModule(
p.wazeroCtx, p.wazeroCompileModule, p.wazeroConf,
)
}
// 版权 @2023 凹语言 作者。保留所有权利。
package wazero_test
import (
"strings"
"testing"
"wa-lang.org/wa/api"
"wa-lang.org/wa/internal/wabt"
"wa-lang.org/wa/internal/wazero"
)
func TestModule(t *testing.T) {
const wasmName = "main.wa"
const waCode = `
func main {
println("hello wazero")
}
func add(a:i32, b:i32) => i32 {
return a+b
}
`
wasmBytes := tBuildWasm(t, waCode)
m, err := wazero.BuildModule(
api.DefaultConfig(), wasmName, wasmBytes,
)
if err != nil {
t.Fatal(err)
}
defer m.Close()
// init+main 执行
stdout, _, err := m.RunInitOnce()
if err != nil {
t.Fatal(err)
}
if got := strings.TrimSpace(string(stdout)); got != "hello wazero" {
t.Fatalf("expect %q, got %q", "hello wazero", got)
}
// add 函数执行
result, _, _, err := m.RunFunc("__main__.add", 1, 2)
if err != nil {
t.Fatal(err)
}
if len(result) != 1 || result[0] != 3 {
t.Fatalf("expect %q, got %q", []uint64{3}, result)
}
}
func tBuildWasm(t *testing.T, waCode string) []byte {
watBytes, err := api.BuildFile(api.DefaultConfig(), "main.wa", waCode)
if err != nil {
t.Fatal(err)
}
wasmBytes, err := wabt.Wat2Wasm(watBytes)
if err != nil {
t.Fatal(err)
}
return wasmBytes
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册