From 0751ed383a270fe448d82de58652c43f43ead1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remko=20Tron=C3=A7on?= Date: Tue, 15 Nov 2022 18:50:52 +0100 Subject: [PATCH] standalone: Add wabt version --- src/standalone/wabt/.gitignore | 5 + src/standalone/wabt/Makefile | 80 ++++++++++++ src/standalone/wabt/README.md | 2 + src/standalone/wabt/main.cpp | 229 +++++++++++++++++++++++++++++++++ 4 files changed, 316 insertions(+) create mode 100644 src/standalone/wabt/.gitignore create mode 100644 src/standalone/wabt/Makefile create mode 100644 src/standalone/wabt/README.md create mode 100644 src/standalone/wabt/main.cpp diff --git a/src/standalone/wabt/.gitignore b/src/standalone/wabt/.gitignore new file mode 100644 index 0000000..290c0e5 --- /dev/null +++ b/src/standalone/wabt/.gitignore @@ -0,0 +1,5 @@ +/waforth +/waforth_core.h +/wasm-micro-runtime +/wasmtime-* +/*.tgz diff --git a/src/standalone/wabt/Makefile b/src/standalone/wabt/Makefile new file mode 100644 index 0000000..34fe5c8 --- /dev/null +++ b/src/standalone/wabt/Makefile @@ -0,0 +1,80 @@ +.DEFAULT_GOAL := all + +VERSION?=$(shell cat ../../../package.json | grep '"version"' | sed -e 's/.*:.*"\(.*\)".*/\1/') + +ifeq ($(OS),Windows_NT) +CC=gcc +LIBS= +else +ifeq (, $(shell which gtar)) +TAR := tar +else +# bsd-tar corrupts files on GitHub: https://github.com/actions/virtual-environments/issues/2619 +TAR := gtar +endif +UNAME_S=$(shell uname -s) +UNAME_P=$(shell uname -p) +WABT_DIR = /usr/local/wabt +ifeq ($(UNAME_S),Darwin) +CXXFLAGS = -std=c++17 +ifeq ($(UNAME_P),arm) +PACKAGE_SUFFIX=arm64-macos +else +PACKAGE_SUFFIX=x86_64-macos +endif +else +LIBS=-lpthread -lm -ldl +PACKAGE_SUFFIX=x86_64-linux +endif +package: waforth + $(TAR) czf waforth-v$(VERSION)-$(PACKAGE_SUFFIX).tgz waforth +endif +WABT_INCLUDE_DIR := $(WABT_DIR)/include +WABT_LIB_DIR := $(WABT_DIR)/lib +CXXFLAGS:= -I$(WABT_INCLUDE_DIR) $(CXXFLAGS) +LIBS:=$(WABT_LIB_DIR)/libwabt.a $(LIBS) + +ifeq ($(DEBUG),1) +CXXFLAGS := $(CXXFLAGS) -g +else +CXXFLAGS := $(CXXFLAGS) -O2 +endif + +################################################ + +BIN2H=../../../scripts/bin2h +WAT2WASM=wat2wasm +WAT2WASM_FLAGS=--debug-names + +CXXFLAGS:=-DVERSION='"$(VERSION)"' $(CXXFLAGS) +OBJECTS=main.o $(RESOURCE_OBJECTS) + +all: waforth + +waforth: $(OBJECTS) + $(CXX) $(CXXFLAGS) -o $@ $(OBJECTS) $(LDFLAGS) $(LIBS) + +main.o: waforth_core.h + +waforth_core.wasm: ../../waforth.wat + $(WAT2WASM) $(WAT2WASM_FLAGS) -o $@ $< + +waforth_core.h: waforth_core.wasm + $(BIN2H) $< $@ + +.PHONY: package + +.PHONY: check +check: + -rm -f test.out + echo ": MAIN 1 2 3 + .S ; MAIN" | ./waforth | tee test.out + grep "1 5 ok" test.out + rm test.out + ./waforth ../../examples/hello.fs | tee test.out + grep "Hello, Forth" test.out + rm test.out + + +.PHONY: clean +clean: + -rm -f waforth_core.wasm waforth_core.h $(OBJECTS) waforth *.exe *.tgz *.zip test.out diff --git a/src/standalone/wabt/README.md b/src/standalone/wabt/README.md new file mode 100644 index 0000000..342b7be --- /dev/null +++ b/src/standalone/wabt/README.md @@ -0,0 +1,2 @@ +This is an experimental version of the standalone shell that uses the WABT interpreter. +When WABT implements the most recent version of the WASM C API, this can go away. diff --git a/src/standalone/wabt/main.cpp b/src/standalone/wabt/main.cpp new file mode 100644 index 0000000..690a53f --- /dev/null +++ b/src/standalone/wabt/main.cpp @@ -0,0 +1,229 @@ +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +#include +#else +#include +#endif + +#include + +#include +#include +#include +#include +#include + +#include "waforth_core.h" + +namespace wabti = wabt::interp; + +#ifndef VERSION +#define VERSION "dev" +#endif + +#define CORE_RUN_EXPORT_INDEX 0 +#define CORE_TABLE_EXPORT_INDEX 1 +#define CORE_MEMORY_EXPORT_INDEX 2 +#define CORE_ERROR_EXPORT_INDEX 9 + +#define ERR_UNKNOWN 0x1 +#define ERR_QUIT 0x2 +#define ERR_ABORT 0x3 +#define ERR_EOI 0x4 +#define ERR_BYE 0x5 + +static wabti::Store store; +static std::unique_ptr stderrStream; +static wabt::Features features; +static wabti::Memory::Ptr memory; +static wabti::Table::Ptr table; +static wabt::Errors errors; + +FILE *input; + +wabt::Result emit_cb(wabti::Thread &thread, const wabti::Values ¶ms, wabti::Values &results, wabti::Trap::Ptr *trap) { + putchar(params[0].Get()); + return wabt::Result::Ok; +} + +wabt::Result key_cb(wabti::Thread &thread, const wabti::Values ¶ms, wabti::Values &results, wabti::Trap::Ptr *trap) { +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + HANDLE h = GetStdHandle(STD_INPUT_HANDLE); + if (h == NULL) { + return trap_from_string("no console"); + } + DWORD mode; + GetConsoleMode(h, &mode); + SetConsoleMode(h, mode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)); + TCHAR ch = 0; + DWORD cc; + ReadConsole(h, &ch, 1, &cc, NULL); + SetConsoleMode(h, mode); +#else + struct termios old, current; + tcgetattr(0, &old); + current = old; + current.c_lflag &= ~ICANON; + current.c_lflag &= ~ECHO; + tcsetattr(0, TCSANOW, ¤t); + char ch = getchar(); + tcsetattr(0, TCSANOW, &old); +#endif + results[0].Set((wabti::u32)ch); + return wabt::Result::Ok; +} + +wabt::Result read_cb(wabti::Thread &thread, const wabti::Values ¶ms, wabti::Values &results, wabti::Trap::Ptr *trap) { + auto addr = (char *)memory->UnsafeData() + params[0].Get(); + auto size = params[1].Get(); + *addr = 0; + fgets(addr, size, input); + int n = strlen(addr); + results[0].Set((wabti::u32)n); + return wabt::Result::Ok; +} + +wabt::Result load_cb(wabti::Thread &thread, const wabti::Values ¶ms, wabti::Values &results, wabti::Trap::Ptr *trap) { + auto addr = params[0].Get(); + auto size = params[1].Get(); + wabti::ModuleDesc desc; + CHECK_RESULT(wabti::ReadBinaryInterp("word.wasm", memory->UnsafeData() + addr, size, wabt::ReadBinaryOptions(features, nullptr, true, true, true), + &errors, &desc)); + auto mod = wabti::Module::New(store, desc); + wabti::RefVec imports = {table.ref(), memory.ref()}; + auto modi = wabti::Instance::Instantiate(store, mod.ref(), imports, trap); + if (!modi) { + printf("error instantiating word module\n"); + return wabt::Result::Error; + ; + } + return wabt::Result::Ok; +} + +wabt::Result call_cb(wabti::Thread &thread, const wabti::Values ¶ms, wabti::Values &results, wabti::Trap::Ptr *trap) { + printf("`call` is not available in standalone\n"); + return wabt::Result::Error; +} + +wabt::Result run(bool interactive) { + stderrStream = wabt::FileStream::CreateStderr(); + + // Load core module + wabti::ModuleDesc desc; + CHECK_RESULT(wabti::ReadBinaryInterp("waforth.wasm", waforth_core, sizeof(waforth_core), + wabt::ReadBinaryOptions(features, nullptr, true, true, true), &errors, &desc)); + auto core = wabti::Module::New(store, desc); + + // Core Exports + wabti::Func::Ptr errorFn; + wabti::Func::Ptr runFn; + + // Bind core imports + wabti::RefVec imports; + for (auto &&import : core->desc().imports) { + if (import.type.type->kind == wabti::ExternKind::Func && import.type.module == "shell") { + auto ft = *wabt::cast(import.type.type.get()); + wabti::HostFunc::Callback cb; + if (import.type.name == "emit") { + cb = emit_cb; + } else if (import.type.name == "read") { + cb = read_cb; + } else if (import.type.name == "key") { + cb = key_cb; + } else if (import.type.name == "load") { + cb = load_cb; + } else if (import.type.name == "call") { + cb = call_cb; + } else { + printf("Unknown import: %s\n", import.type.name.c_str()); + return wabt::Result::Error; + } + auto func = wabti::HostFunc::New(store, ft, cb); + imports.push_back(func.ref()); + continue; + } + imports.push_back(wabti::Ref::Null); + } + + // Instantiate module + wabti::Trap::Ptr trap; + auto corei = wabti::Instance::Instantiate(store, core.ref(), imports, &trap); + if (!corei) { + printf("error instantiating module\n"); + if (trap) { + wabti::WriteTrap(stderrStream.get(), " error ", trap); + } + return wabt::Result::Error; + } + + // Load exports + for (auto &&export_ : core->desc().exports) { + if (export_.type.type->kind == wabt::ExternalKind::Memory) { + memory = store.UnsafeGet(corei->memories()[export_.index]); + } else if (export_.type.type->kind == wabt::ExternalKind::Table) { + table = store.UnsafeGet(corei->tables()[export_.index]); + } else if (export_.type.name == "run") { + runFn = store.UnsafeGet(corei->funcs()[export_.index]); + } else if (export_.type.name == "error") { + errorFn = store.UnsafeGet(corei->funcs()[export_.index]); + } + } + + // Run + wabti::Values runParams = {wabti::Value::Make(interactive ? 0 : 1)}; + wabti::Values runResults; + wabti::Values errorParams; + wabti::Values errorResults; + for (int stopped = false; !stopped;) { + auto runRes = runFn->Call(store, runParams, runResults, &trap, nullptr); + CHECK_RESULT(errorFn->Call(store, errorParams, errorResults, &trap, nullptr)); + switch (errorResults[0].Get()) { + case ERR_QUIT: + case ERR_ABORT: + assert(!Succeeded(runRes)); + break; + case ERR_EOI: + assert(Succeeded(runRes)); + stopped = true; + break; + case ERR_BYE: + assert(!Succeeded(runRes)); + stopped = true; + break; + case ERR_UNKNOWN: + assert(!Succeeded(runRes)); + if (trap) { + wabti::WriteTrap(stderrStream.get(), " error ", trap); + } else { + printf("unknown error\n"); + } + break; + default: + printf("unknown error code\n"); + if (trap) { + wabti::WriteTrap(stderrStream.get(), " error ", trap); + } + assert(false); + } + } + return wabt::Result::Ok; +} + +int main(int argc, char *argv[]) { + if (argc >= 2) { + input = fopen(argv[1], "r"); + } else { + input = stdin; + } + + if (input == stdin) { + printf("WAForth (" VERSION ")\n"); + } + + auto result = run(input == stdin); + + if (input != stdin) { + fclose(input); + } + + return Succeeded(result) ? 0 : 1; +} \ No newline at end of file