mirror of
https://github.com/remko/waforth
synced 2025-01-30 08:34:58 +01:00
Add documentation
This commit is contained in:
parent
05323af20a
commit
bbe5a4eec3
2 changed files with 76 additions and 46 deletions
116
README.md
116
README.md
|
@ -4,21 +4,26 @@ WAForth is a bootstrapping Forth interpreter and dynamic compiler for
|
||||||
[WebAssembly](https://webassembly.org). You can see it in a demo
|
[WebAssembly](https://webassembly.org). You can see it in a demo
|
||||||
[here](https://el-tramo.be/waforth/).
|
[here](https://el-tramo.be/waforth/).
|
||||||
|
|
||||||
It is (almost) entirely written in WebAssembly and Forth, and the compiler generates
|
It is (almost) entirely written in WebAssembly and Forth, and the compiler
|
||||||
WebAssembly code on the fly. The only parts for which it relies on external
|
generates WebAssembly code on the fly. The only parts for which it relies on
|
||||||
(JavaScript) code is the dynamic loader (since WebAssembly [doesn't support JIT
|
external (JavaScript) code is the dynamic loader (since WebAssembly [doesn't
|
||||||
yet](https://webassembly.org/docs/future-features/#platform-independent-just-in-time-jit-compilation)), and the I/O primitives to read and write a character.
|
support JIT
|
||||||
|
yet](https://webassembly.org/docs/future-features/#platform-independent-just-in-time-jit-compilation)),
|
||||||
|
and the I/O primitives to read and write a character.
|
||||||
|
|
||||||
The implementation was influenced by [jonesforth](http://git.annexia.org/?p=jonesforth.git;a=summary),
|
The implementation was influenced by
|
||||||
and I shamelessly stole the Forth implementation of some of its high-level words.
|
[jonesforth](http://git.annexia.org/?p=jonesforth.git;a=summary), and I
|
||||||
|
shamelessly stole the Forth implementation of some of its high-level words.
|
||||||
|
|
||||||
WAForth is still just an experiment, and doesn't implement all the ANS standard words yet.
|
WAForth is still just an experiment, and doesn't implement all the ANS standard
|
||||||
|
words yet.
|
||||||
|
|
||||||
## Install Dependencies
|
## Install Dependencies
|
||||||
|
|
||||||
The build uses [Racket](https://racket-lang.org) for processing the WebAssembly code,
|
The build uses [Racket](https://racket-lang.org) for processing the WebAssembly
|
||||||
the [WebAssembly Binary Toolkit](https://github.com/WebAssembly/wabt) for converting it in binary
|
code, the [WebAssembly Binary Toolkit](https://github.com/WebAssembly/wabt) for
|
||||||
format,and [Yarn](https://yarnpkg.com) for managing the dependencies of the shell.
|
converting it in binary format,and [Yarn](https://yarnpkg.com) for managing the
|
||||||
|
dependencies of the shell.
|
||||||
|
|
||||||
brew install wabt yarn minimal-racket
|
brew install wabt yarn minimal-racket
|
||||||
yarn
|
yarn
|
||||||
|
@ -42,67 +47,90 @@ The tests are served from `/tests`.
|
||||||
|
|
||||||
### The Macro Assembler
|
### The Macro Assembler
|
||||||
|
|
||||||
The WAForth core is written as [a single module](https://github.com/remko/waforth/blob/master/src/waforth.wat) in WebAssembly's [text format](https://webassembly.github.io/spec/core/text/index.html). The
|
The WAForth core is written as [a single
|
||||||
text format isn't really meant for writing code in, so it has no facilities like a real assembler
|
module](https://github.com/remko/waforth/blob/master/src/waforth.wat) in
|
||||||
(e.g. constant definitions, macro expansion, ...) However, since the text format uses S-expressions,
|
WebAssembly's [text
|
||||||
you can do some small modifications to make it extensible with Lisp-style macros.
|
format](https://webassembly.github.io/spec/core/text/index.html). The text
|
||||||
|
format isn't really meant for writing code in, so it has no facilities like a
|
||||||
|
real assembler (e.g. constant definitions, macro expansion, ...) However, since
|
||||||
|
the text format uses S-expressions, you can do some small modifications to make
|
||||||
|
it extensible with Lisp-style macros.
|
||||||
|
|
||||||
I added some Racket macros to the module definition, and implemented [a mini
|
I added some Racket macros to the module definition, and implemented [a mini
|
||||||
assembler](https://github.com/remko/waforth/blob/master/src/tools/assembler.rkt)
|
assembler](https://github.com/remko/waforth/blob/master/src/tools/assembler.rkt)
|
||||||
to print out the resulting s-expressions in the right format.
|
to print out the resulting s-expressions in the right format.
|
||||||
|
|
||||||
The result is something that looks like a standard WebAssembly module, but sprinkled with some
|
The result is something that looks like a standard WebAssembly module, but
|
||||||
macros for convenience.
|
sprinkled with some macros for convenience.
|
||||||
|
|
||||||
### The Interpreter
|
### The Interpreter
|
||||||
|
|
||||||
The interpreter runs a loop that processes commands, and switches to and from
|
The interpreter runs a loop that processes commands, and switches to and from
|
||||||
compiler mode.
|
compiler mode.
|
||||||
|
|
||||||
Contrary to some other Forth systems, this system doesn't use direct threading
|
Contrary to some other Forth systems, this system doesn't use direct threading
|
||||||
for executing code. WebAssembly doesn't allow unstructured jumps, let alone dynamic jumps.
|
for executing code. WebAssembly doesn't allow unstructured jumps, let alone
|
||||||
Instead, WAForth uses subroutine threading, where each word is implemented as a single
|
dynamic jumps. Instead, WAForth uses subroutine threading, where each word
|
||||||
WebAssembly function, and the system uses
|
is implemented as a single WebAssembly function, and the system uses calls
|
||||||
calls and indirect calls (see below) to execute words.
|
and indirect calls (see below) to execute words.
|
||||||
|
|
||||||
|
|
||||||
### The Compiler
|
### The Compiler
|
||||||
|
|
||||||
While in compile mode for a word, the compiler generates WebAssembly instructions in
|
While in compile mode for a word, the compiler generates WebAssembly
|
||||||
binary format (since there is no assembler infrastructure in the browser). Since WebAssembly
|
instructions in binary format (since there is no assembler infrastructure in
|
||||||
[doesn't support JIT compilation yet](https://webassembly.org/docs/future-features/#platform-independent-just-in-time-jit-compilation), a finished word is bundled into a separate binary WebAssembly module, and
|
the browser). Since WebAssembly [doesn't support JIT compilation
|
||||||
sent to the loader, which dynamically loads it and registers it with a shared
|
yet](https://webassembly.org/docs/future-features/#platform-independent-just-in-time-jit-compilation),
|
||||||
[function table](https://webassembly.github.io/spec/core/valid/modules.html#tables) at the
|
a finished word is bundled into a separate binary WebAssembly module, and sent
|
||||||
next offset, which in turn is recorded in the word dictionary.
|
to the loader, which dynamically loads it and registers it with a shared
|
||||||
|
[function
|
||||||
|
table](https://webassembly.github.io/spec/core/valid/modules.html#tables) at
|
||||||
|
the next offset, which in turn is recorded in the word dictionary.
|
||||||
|
|
||||||
Because words reside in different modules, all calls to and from the words need to happen as
|
Because words reside in different modules, all calls to and from the words need
|
||||||
indirect `call_indirect` calls through the shared function table. This of course introduces
|
to happen as indirect `call_indirect` calls through the shared function table.
|
||||||
some overhead, although it seems limited.
|
This of course introduces some overhead, although it seems limited.
|
||||||
|
|
||||||
As WebAssembly doesn't support unstructured jumps, control flow words (`IF/ELSE/THEN`,
|
As WebAssembly doesn't support unstructured jumps, control flow words
|
||||||
`LOOP`, `REPEAT`, ...) can't be implemented in terms of more basic words, unlike in jonesforth.
|
(`IF/ELSE/THEN`, `LOOP`, `REPEAT`, ...) can't be implemented in terms of more
|
||||||
However, since Forth only requires structured jumps, the compiler can easily be implemented
|
basic words, unlike in jonesforth. However, since Forth only requires
|
||||||
using the loop and branch instructions available in WebAssembly.
|
structured jumps, the compiler can easily be implemented using the loop and
|
||||||
|
branch instructions available in WebAssembly.
|
||||||
|
|
||||||
Finally, the compiler adds minimal debug information about the compiled word in
|
Finally, the compiler adds minimal debug information about the compiled word in
|
||||||
the [name section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#name-section), making it easier for doing some debugging in the browser.
|
the [name
|
||||||
|
section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#name-section),
|
||||||
|
making it easier for doing some debugging in the browser.
|
||||||
|
|
||||||
![Debugger view of a compiled word](https://el-tramo.be/blog/waforth/debugger.png "Debugger view of a compiled word")
|
![Debugger view of a compiled
|
||||||
|
word](https://el-tramo.be/blog/waforth/debugger.png "Debugger view of a
|
||||||
|
compiled word")
|
||||||
|
|
||||||
|
|
||||||
### The Loader
|
### The Loader
|
||||||
|
|
||||||
The loader is a small bit of JavaScript that uses the [WebAssembly JavaScript API](https://webassembly.github.io/spec/js-api/index.html) to dynamically load a compiled word (in the form of a WebAssembly module), and ensuring that the shared function table is large enough for the module to
|
The loader is a small bit of JavaScript that uses the [WebAssembly JavaScript
|
||||||
register itself.
|
API](https://webassembly.github.io/spec/js-api/index.html) to dynamically load
|
||||||
|
a compiled word (in the form of a WebAssembly module), and ensuring that the
|
||||||
|
shared function table is large enough for the module to register itself.
|
||||||
|
|
||||||
### The Shell
|
### The Shell
|
||||||
|
|
||||||
The shell is [a JavaScript class](https://github.com/remko/waforth/blob/master/src/shell/WAForth.js)
|
The shell is [a JavaScript
|
||||||
that wraps the WebAssembly module, and loads it in the browser.
|
class](https://github.com/remko/waforth/blob/master/src/shell/WAForth.js) that
|
||||||
It provides the I/O primitives to the WebAssembly module to read and write characters to a terminal,
|
wraps the WebAssembly module, and loads it in the browser. It provides the I/O
|
||||||
and externally provides a `run()` function to execute a fragment of Forth code.
|
primitives to the WebAssembly module to read and write characters to a
|
||||||
|
terminal, and externally provides a `run()` function to execute a fragment of
|
||||||
|
Forth code.
|
||||||
|
|
||||||
To tie everything together into an interactive system, there's a small console-based interface around this shell to type
|
To tie everything together into an interactive system, there's a small
|
||||||
Forth code, which you can see in action [here](https://el-tramo.be/waforth/).
|
console-based interface around this shell to type Forth code, which you can see
|
||||||
|
in action [here](https://el-tramo.be/waforth/).
|
||||||
|
|
||||||
![WAForth Console](https://el-tramo.be/waforth/console.gif "WAForth Console")
|
![WAForth Console](https://el-tramo.be/waforth/console.gif "WAForth Console")
|
||||||
|
|
||||||
|
### Misc notes
|
||||||
|
|
||||||
|
- The exposed return stack isn't used. Control flow is kept implicitly in the
|
||||||
|
code (e.g. through branches, indirect calls, ...). This also means that
|
||||||
|
control flow can't be influenced by code.
|
||||||
|
|
|
@ -1894,9 +1894,11 @@ EOF
|
||||||
;; - name (n bytes): Name characters. End is 4-byte aligned.
|
;; - name (n bytes): Name characters. End is 4-byte aligned.
|
||||||
;; - code pointer (4 bytes): Index into the function
|
;; - code pointer (4 bytes): Index into the function
|
||||||
;; table of code to execute
|
;; table of code to execute
|
||||||
;; - code argument (4 bytes) (optional): In case the function in the
|
;; - code argument (4 bytes) (optional): In case the function is
|
||||||
;; code pointer takes an argument (i.e. 'flags' has fData set).
|
;; pushDataValue (used by CONST), contains data used by the function.
|
||||||
;; - data (m bytes)
|
;; - data (m bytes)
|
||||||
|
;;
|
||||||
|
;; Execution tokens are addresses of dictionary entries
|
||||||
|
|
||||||
(data (i32.const !baseBase) "\u000A\u0000\u0000\u0000")
|
(data (i32.const !baseBase) "\u000A\u0000\u0000\u0000")
|
||||||
(data (i32.const !stateBase) "\u0000\u0000\u0000\u0000")
|
(data (i32.const !stateBase) "\u0000\u0000\u0000\u0000")
|
||||||
|
|
Loading…
Add table
Reference in a new issue