diff --git a/experiments/waforth2c/.gitignore b/experiments/waforth2c/.gitignore new file mode 100644 index 0000000..32dceeb --- /dev/null +++ b/experiments/waforth2c/.gitignore @@ -0,0 +1,4 @@ +waforth.gen +*.o +waforth_core.* +main diff --git a/experiments/waforth2c/Makefile b/experiments/waforth2c/Makefile new file mode 100644 index 0000000..44f7ef9 --- /dev/null +++ b/experiments/waforth2c/Makefile @@ -0,0 +1,32 @@ +CFLAGS= +CPPFLAGS=-I. -Iwaforth.gen -Iwasm2c + +.DEFAULT_GOAL := all + +-include waforth.gen/Makefile.inc + +OBJECTS=waforth_core.o main.o waforth.o wasm2c/wasm-rt-impl.o $(WAFORTH_MODULE_OBJECTS) + +.PHONY: all +all: main + +.PHONY: example +example: + ./waforth2c.js examples/sieve.f + $(MAKE) + ./main + +main: $(OBJECTS) + $(CC) -o $@ $(OBJECTS) + +clean: + -rm -rf $(OBJECTS) waforth_core.c waforth_core.h main $(WAFORTH_MODULE_HEADERS) $(WAFORTH_MODULE_SOURCES) + +waforth.o: waforth.gen/waforth_modules.h + +waforth_core.c: ../../src/waforth.wasm + wasm2c $< -o $@ + +waforth_core.o: waforth_core.c + $(CC) $(CPPFLAGS) $(CFLAGS) -DWASM_RT_MODULE_PREFIX=waforth_core_ -c $< -o $@ + diff --git a/experiments/waforth2c/README.md b/experiments/waforth2c/README.md new file mode 100644 index 0000000..bbd4764 --- /dev/null +++ b/experiments/waforth2c/README.md @@ -0,0 +1,24 @@ +# WAForth2C: Experiment to use WAForth to convert Forth to C + +Uses WAForth to generate WebAssembly modules, passes them to `wasm2c`, and +compiles and loads everything together into a native binary. + +## Usage + +1. Create a `.f` file with a `main` word defined (e.g. `example/sieve.f`) +2. Compile + + ./waforth2c.js examples/sieve.f + + This will generate `.wasm` files for all the defined words. + +3. Build + + make + + This will generate `.c` files from the `.wasm` files generated in 2., and build + them using the C compiler. + +4. Run + + ./main diff --git a/experiments/waforth2c/examples/sieve.f b/experiments/waforth2c/examples/sieve.f new file mode 100644 index 0000000..6d71fae --- /dev/null +++ b/experiments/waforth2c/examples/sieve.f @@ -0,0 +1,21 @@ +: prime? HERE + C@ 0= ; +: composite! HERE + 1 SWAP C! ; + +: sieve + HERE OVER ERASE + 2 + BEGIN + 2DUP DUP * > + WHILE + DUP prime? IF + 2DUP DUP * DO + I composite! + DUP +LOOP + THEN + 1+ + REPEAT + DROP + 1 SWAP 2 DO I prime? IF DROP I THEN LOOP . +; + +: main 10000 sieve ; diff --git a/experiments/waforth2c/main.c b/experiments/waforth2c/main.c new file mode 100644 index 0000000..dafc116 --- /dev/null +++ b/experiments/waforth2c/main.c @@ -0,0 +1,6 @@ +#include "waforth.h" + +int main(int argc, char** argv) { + waforth_init(); + return 0; +} diff --git a/experiments/waforth2c/waforth.c b/experiments/waforth2c/waforth.c new file mode 100644 index 0000000..f47c997 --- /dev/null +++ b/experiments/waforth2c/waforth.c @@ -0,0 +1,67 @@ +#include +#include +#include + +#define WASM_RT_MODULE_PREFIX waforth_core_ +#include "waforth_core.h" +#undef WASM_RT_MODULE_PREFIX +#include "waforth.gen/waforth_modules.h" + +void (*Z_shellZ_emitZ_vi)(u32); +u32 (*Z_shellZ_keyZ_iv)(void); +void (*Z_shellZ_loadZ_viii)(u32, u32, u32); +void (*Z_shellZ_debugZ_vi)(u32); +wasm_rt_table_t (*Z_envZ_table); +wasm_rt_memory_t (*Z_envZ_memory); + +static void shellEmit(u32 c) { + printf("%c", c); + fflush(stdout); +} + +static int bufferIndex; +char* buffer = "main\n"; + +static u32 shellKey() { + if (bufferIndex >= strlen(buffer)) { + return -1; + } + return buffer[bufferIndex++]; +} + +static void shellLoad(u32 offset, u32 length, u32 index) { + printf("Loading not supported!\n"); +} + +static void shellDebug(u32 c) { + fprintf(stderr, "%d\n", c); +} + +void wasm_rt_reallocate_table(wasm_rt_table_t* table, uint32_t elements, uint32_t max_elements) { + table->size = elements; + table->max_size = max_elements; + table->data = realloc(table->data, table->size * sizeof(wasm_rt_elem_t)); +} + +void waforth_init() { + waforth_core_init(); + + if (WAFORTH_TABLE_SIZE >= 0) { + wasm_rt_reallocate_table(waforth_core_Z_table, WAFORTH_TABLE_SIZE, waforth_core_Z_table->max_size); + } + + Z_shellZ_emitZ_vi = &shellEmit; + Z_shellZ_keyZ_iv = &shellKey; + Z_shellZ_loadZ_viii = &shellLoad; + Z_shellZ_debugZ_vi = &shellDebug; + + Z_envZ_table = waforth_core_Z_table; + Z_envZ_memory = waforth_core_Z_memory; + + waforth_core_Z_set_stateZ_vii(WAFORTH_LATEST, WAFORTH_HERE); + + waforth_modules_init(); + + bufferIndex = 0; + waforth_core_Z_interpretZ_iv(); +} diff --git a/experiments/waforth2c/waforth.h b/experiments/waforth2c/waforth.h new file mode 100644 index 0000000..4e7f64b --- /dev/null +++ b/experiments/waforth2c/waforth.h @@ -0,0 +1,3 @@ +#pragma once + +void waforth_init(); diff --git a/experiments/waforth2c/waforth2c.js b/experiments/waforth2c/waforth2c.js new file mode 100755 index 0000000..d5d3c6f --- /dev/null +++ b/experiments/waforth2c/waforth2c.js @@ -0,0 +1,160 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const process = require("process"); + +if (process.argv.length < 3) { + console.log("Expected input file"); + process.exit(1); +} +const input = fs.readFileSync(process.argv[2]) + ""; + +const coreWasm = fs.readFileSync("../../src/waforth.wasm"); + +let core, table, memory, memory32; +const buffer = []; +const modules = []; +let tableSize = -1; + +function run(s) { + const data = new TextEncoder().encode(s); + for (let i = data.length - 1; i >= 0; --i) { + buffer.push(data[i]); + } + return core.exports.interpret(); +} + +function latest() { + run("LATEST"); + const result = memory32[core.exports.tos() / 4 - 1]; + run("DROP"); + return result; +} + +function here() { + run("HERE"); + const result = memory32[core.exports.tos() / 4 - 1]; + run("DROP"); + return result; +} + +WebAssembly.instantiate(coreWasm, { + shell: { + emit: c => { + process.stdout.write(String.fromCharCode(c)); + }, + + key: () => { + if (buffer.length === 0) { + return -1; + } + return buffer.pop(); + }, + + debug: c => { + process.stderr.write(String.fromCharCode(c)); + }, + + load: (offset, length, index) => { + let data = new Uint8Array(core.exports.memory.buffer, offset, length); + if (index >= table.length) { + table.grow(table.length); + } + tableSize = index + 1; + var module = new WebAssembly.Module(data); + modules.push(new Uint8Array(Array.from(data))); + new WebAssembly.Instance(module, { + env: { table, memory, tos: -1 } + }); + } + } +}).then(instance => { + core = instance.instance; + table = core.exports.table; + memory = core.exports.memory; + memory32 = new Int32Array(core.exports.memory.buffer, 0, 0x30000); + const memory8 = new Uint8Array(core.exports.memory.buffer, 0, 0x30000); + + const dictionaryStart = latest(); + + // Load prelude + core.exports.loadPrelude(); + + // Load code + run(input); + + const savedLatest = latest(); + const savedHere = here(); + + //////////////////////////////////////////////////////////// + // Generate build files + //////////////////////////////////////////////////////////// + + if (!fs.existsSync("waforth.gen")) { + fs.mkdirSync("waforth.gen"); + } + + const make = [ + "waforth.gen/waforth_module_%.c waforth.gen/waforth_module_%.h: waforth.gen/waforth_module_%.wasm", + "\twasm2c $< -o $(subst .wasm,.c,$<)", + "", + "waforth.gen/waforth_module_%.wasm: waforth.gen/waforth_module_%.in.wasm", + "\twasm-dis $< -o $(subst .wasm,.wat,$@)", + "\twasm-as $(subst .wasm,.wat,$@) -o $@", + "" + ]; + const include = [ + "#pragma once", + "", + "#define WAFORTH_LATEST " + savedLatest + "\n", + "#define WAFORTH_HERE " + savedHere + "\n", + "#define WAFORTH_TABLE_SIZE " + tableSize + "\n", + "void waforth_modules_init();", + "#undef WASM_RT_MODULE_PREFIX" + ]; + const init = [ + '#include "waforth_modules.h"', + "void waforth_modules_init() {" + ]; + for (let i = dictionaryStart; i < savedHere; ++i) { + init.push("Z_envZ_memory->data[" + i + "] = " + memory8[i] + ";\n"); + } + const objects = ["waforth.gen/waforth_modules.o"]; + const moduleHeaders = []; + const moduleSources = []; + for (let i = 0; i < modules.length; ++i) { + fs.writeFileSync( + "waforth.gen/waforth_module_" + i + ".in.wasm", + modules[i] + ); + make.push( + "waforth.gen/waforth_module_" + + i + + ".o: waforth.gen/waforth_module_" + + i + + ".c" + ); + make.push( + "\t$(CC) $(CPPFLAGS) $(CFLAGS) -DWASM_RT_MODULE_PREFIX=waforth_module_" + + i + + "_ -c $< -o $@" + ); + make.push(""); + include.push("#define WASM_RT_MODULE_PREFIX waforth_module_" + i + "_"); + include.push('#include "waforth.gen/waforth_module_' + i + '.h"'); + include.push("#undef WASM_RT_MODULE_PREFIX"); + init.push("waforth_module_" + i + "_init();"); + objects.push("waforth.gen/waforth_module_" + i + ".o"); + moduleHeaders.push("waforth.gen/waforth_module_" + i + ".h"); + moduleSources.push("waforth.gen/waforth_module_" + i + ".c"); + } + include.push("#define WASM_RT_MODULE_PREFIX"); + make.push("WAFORTH_MODULE_OBJECTS = " + objects.join(" ") + "\n"); + make.push("WAFORTH_MODULE_HEADERS = " + moduleHeaders.join(" ") + "\n"); + make.push("WAFORTH_MODULE_SOURCES = " + moduleSources.join(" ") + "\n"); + make.push("waforth.gen/waforth_modules.h: $(WAFORTH_MODULE_HEADERS)"); + init.push("}"); + fs.writeFileSync("waforth.gen/Makefile.inc", make.join("\n") + "\n"); + fs.writeFileSync("waforth.gen/waforth_modules.h", include.join("\n") + "\n"); + fs.writeFileSync("waforth.gen/waforth_modules.c", init.join("\n") + "\n"); +}); diff --git a/experiments/waforth2c/wasm2c/wasm-rt-impl.c b/experiments/waforth2c/wasm2c/wasm-rt-impl.c new file mode 100644 index 0000000..0fcbcf9 --- /dev/null +++ b/experiments/waforth2c/wasm2c/wasm-rt-impl.c @@ -0,0 +1,122 @@ +/* + * Copyright 2018 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wasm-rt-impl.h" + +#include +#include +#include +#include +#include +#include + +#define PAGE_SIZE 65536 + +typedef struct FuncType { + wasm_rt_type_t* params; + wasm_rt_type_t* results; + uint32_t param_count; + uint32_t result_count; +} FuncType; + +uint32_t wasm_rt_call_stack_depth; +uint32_t g_saved_call_stack_depth; + +jmp_buf g_jmp_buf; +FuncType* g_func_types; +uint32_t g_func_type_count; + +void wasm_rt_trap(wasm_rt_trap_t code) { + assert(code != WASM_RT_TRAP_NONE); + wasm_rt_call_stack_depth = g_saved_call_stack_depth; + longjmp(g_jmp_buf, code); +} + +static bool func_types_are_equal(FuncType* a, FuncType* b) { + if (a->param_count != b->param_count || a->result_count != b->result_count) + return 0; + int i; + for (i = 0; i < a->param_count; ++i) + if (a->params[i] != b->params[i]) + return 0; + for (i = 0; i < a->result_count; ++i) + if (a->results[i] != b->results[i]) + return 0; + return 1; +} + +uint32_t wasm_rt_register_func_type(uint32_t param_count, + uint32_t result_count, + ...) { + FuncType func_type; + func_type.param_count = param_count; + func_type.params = malloc(param_count * sizeof(wasm_rt_type_t)); + func_type.result_count = result_count; + func_type.results = malloc(result_count * sizeof(wasm_rt_type_t)); + + va_list args; + va_start(args, result_count); + + uint32_t i; + for (i = 0; i < param_count; ++i) + func_type.params[i] = va_arg(args, wasm_rt_type_t); + for (i = 0; i < result_count; ++i) + func_type.results[i] = va_arg(args, wasm_rt_type_t); + va_end(args); + + for (i = 0; i < g_func_type_count; ++i) { + if (func_types_are_equal(&g_func_types[i], &func_type)) { + free(func_type.params); + free(func_type.results); + return i + 1; + } + } + + uint32_t idx = g_func_type_count++; + g_func_types = realloc(g_func_types, g_func_type_count * sizeof(FuncType)); + g_func_types[idx] = func_type; + return idx + 1; +} + +void wasm_rt_allocate_memory(wasm_rt_memory_t* memory, + uint32_t initial_pages, + uint32_t max_pages) { + memory->pages = initial_pages; + memory->max_pages = max_pages; + memory->size = initial_pages * PAGE_SIZE; + memory->data = calloc(memory->size, 1); +} + +uint32_t wasm_rt_grow_memory(wasm_rt_memory_t* memory, uint32_t delta) { + uint32_t old_pages = memory->pages; + uint32_t new_pages = memory->pages + delta; + if (new_pages < old_pages || new_pages > memory->max_pages) { + return (uint32_t)-1; + } + memory->pages = new_pages; + memory->size = new_pages * PAGE_SIZE; + memory->data = realloc(memory->data, memory->size); + memset(memory->data + old_pages * PAGE_SIZE, 0, delta * PAGE_SIZE); + return old_pages; +} + +void wasm_rt_allocate_table(wasm_rt_table_t* table, + uint32_t elements, + uint32_t max_elements) { + table->size = elements; + table->max_size = max_elements; + table->data = calloc(table->size, sizeof(wasm_rt_elem_t)); +} diff --git a/experiments/waforth2c/wasm2c/wasm-rt-impl.h b/experiments/waforth2c/wasm2c/wasm-rt-impl.h new file mode 100644 index 0000000..bdc4502 --- /dev/null +++ b/experiments/waforth2c/wasm2c/wasm-rt-impl.h @@ -0,0 +1,56 @@ +/* + * Copyright 2018 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WASM_RT_IMPL_H_ +#define WASM_RT_IMPL_H_ + +#include + +#include "wasm-rt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** A setjmp buffer used for handling traps. */ +extern jmp_buf g_jmp_buf; + +/** Saved call stack depth that will be restored in case a trap occurs. */ +extern uint32_t g_saved_call_stack_depth; + +/** Convenience macro to use before calling a wasm function. On first execution + * it will return `WASM_RT_TRAP_NONE` (i.e. 0). If the function traps, it will + * jump back and return the trap that occurred. + * + * ``` + * wasm_rt_trap_t code = wasm_rt_impl_try(); + * if (code != 0) { + * printf("A trap occurred with code: %d\n", code); + * ... + * } + * + * // Call the potentially-trapping function. + * my_wasm_func(); + * ``` + */ +#define wasm_rt_impl_try() \ + (g_saved_call_stack_depth = wasm_rt_call_stack_depth, setjmp(g_jmp_buf)) + +#ifdef __cplusplus +} +#endif + +#endif // WASM_RT_IMPL_H_ diff --git a/experiments/waforth2c/wasm2c/wasm-rt.h b/experiments/waforth2c/wasm2c/wasm-rt.h new file mode 100644 index 0000000..494fde1 --- /dev/null +++ b/experiments/waforth2c/wasm2c/wasm-rt.h @@ -0,0 +1,168 @@ +/* + * Copyright 2018 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WASM_RT_H_ +#define WASM_RT_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Maximum stack depth before trapping. This can be configured by defining + * this symbol before including wasm-rt when building the generated c files, + * for example: + * + * ``` + * cc -c -DWASM_RT_MAX_CALL_STACK_DEPTH=100 my_module.c -o my_module.o + * ``` + * */ +#ifndef WASM_RT_MAX_CALL_STACK_DEPTH +#define WASM_RT_MAX_CALL_STACK_DEPTH 500 +#endif + +/** Reason a trap occurred. Provide this to `wasm_rt_trap`. */ +typedef enum { + WASM_RT_TRAP_NONE, /** No error. */ + WASM_RT_TRAP_OOB, /** Out-of-bounds access in linear memory. */ + WASM_RT_TRAP_INT_OVERFLOW, /** Integer overflow on divide or truncation. */ + WASM_RT_TRAP_DIV_BY_ZERO, /** Integer divide by zero. */ + WASM_RT_TRAP_INVALID_CONVERSION, /** Conversion from NaN to integer. */ + WASM_RT_TRAP_UNREACHABLE, /** Unreachable instruction executed. */ + WASM_RT_TRAP_CALL_INDIRECT, /** Invalid call_indirect, for any reason. */ + WASM_RT_TRAP_EXHAUSTION, /** Call stack exhausted. */ +} wasm_rt_trap_t; + +/** Value types. Used to define function signatures. */ +typedef enum { + WASM_RT_I32, + WASM_RT_I64, + WASM_RT_F32, + WASM_RT_F64, +} wasm_rt_type_t; + +/** A function type for all `anyfunc` functions in a Table. All functions are + * stored in this canonical form, but must be cast to their proper signature to + * call. */ +typedef void (*wasm_rt_anyfunc_t)(void); + +/** A single element of a Table. */ +typedef struct { + /** The index as returned from `wasm_rt_register_func_type`. */ + uint32_t func_type; + /** The function. The embedder must know the actual C signature of the + * function and cast to it before calling. */ + wasm_rt_anyfunc_t func; +} wasm_rt_elem_t; + +/** A Memory object. */ +typedef struct { + /** The linear memory data, with a byte length of `size`. */ + uint8_t* data; + /** The current and maximum page count for this Memory object. If there is no + * maximum, `max_pages` is 0xffffffffu (i.e. UINT32_MAX). */ + uint32_t pages, max_pages; + /** The current size of the linear memory, in bytes. */ + uint32_t size; +} wasm_rt_memory_t; + +/** A Table object. */ +typedef struct { + /** The table element data, with an element count of `size`. */ + wasm_rt_elem_t* data; + /** The maximum element count of this Table object. If there is no maximum, + * `max_size` is 0xffffffffu (i.e. UINT32_MAX). */ + uint32_t max_size; + /** The current element count of the table. */ + uint32_t size; +} wasm_rt_table_t; + +/** Stop execution immediately and jump back to the call to `wasm_rt_try`. + * The result of `wasm_rt_try` will be the provided trap reason. + * + * This is typically called by the generated code, and not the embedder. */ +extern void wasm_rt_trap(wasm_rt_trap_t) __attribute__((noreturn)); + +/** Register a function type with the given signature. The returned function + * index is guaranteed to be the same for all calls with the same signature. + * The following varargs must all be of type `wasm_rt_type_t`, first the + * params` and then the `results`. + * + * ``` + * // Register (func (param i32 f32) (result i64)). + * wasm_rt_register_func_type(2, 1, WASM_RT_I32, WASM_RT_F32, WASM_RT_I64); + * => returns 1 + * + * // Register (func (result i64)). + * wasm_rt_register_func_type(0, 1, WASM_RT_I32); + * => returns 2 + * + * // Register (func (param i32 f32) (result i64)) again. + * wasm_rt_register_func_type(2, 1, WASM_RT_I32, WASM_RT_F32, WASM_RT_I64); + * => returns 1 + * ``` */ +extern uint32_t wasm_rt_register_func_type(uint32_t params, + uint32_t results, + ...); + +/** Initialize a Memory object with an initial page size of `initial_pages` and + * a maximum page size of `max_pages`. + * + * ``` + * wasm_rt_memory_t my_memory; + * // 1 initial page (65536 bytes), and a maximum of 2 pages. + * wasm_rt_allocate_memory(&my_memory, 1, 2); + * ``` */ +extern void wasm_rt_allocate_memory(wasm_rt_memory_t*, + uint32_t initial_pages, + uint32_t max_pages); + +/** Grow a Memory object by `pages`, and return the previous page count. If + * this new page count is greater than the maximum page count, the grow fails + * and 0xffffffffu (UINT32_MAX) is returned instead. + * + * ``` + * wasm_rt_memory_t my_memory; + * ... + * // Grow memory by 10 pages. + * uint32_t old_page_size = wasm_rt_grow_memory(&my_memory, 10); + * if (old_page_size == UINT32_MAX) { + * // Failed to grow memory. + * } + * ``` */ +extern uint32_t wasm_rt_grow_memory(wasm_rt_memory_t*, uint32_t pages); + +/** Initialize a Table object with an element count of `elements` and a maximum + * page size of `max_elements`. + * + * ``` + * wasm_rt_table_t my_table; + * // 5 elemnets and a maximum of 10 elements. + * wasm_rt_allocate_table(&my_table, 5, 10); + * ``` */ +extern void wasm_rt_allocate_table(wasm_rt_table_t*, + uint32_t elements, + uint32_t max_elements); + +/** Current call stack depth. */ +extern uint32_t wasm_rt_call_stack_depth; + +#ifdef __cplusplus +} +#endif + +#endif /* WASM_RT_H_ */