# StoneKnifeForth

**Source:** https://github.com/kragen/stoneknifeforth

GitHub - kragen/stoneknifeforth: a tiny self-hosted Forth implementation



[Skip to content](#start-of-content)













## Navigation Menu

Toggle navigation

[Sign in](/login?return_to=https%3A%2F%2Fgithub.com%2Fkragen%2Fstoneknifeforth)

Appearance settings

* Platform

  + AI CODE CREATION
    - [GitHub CopilotWrite better code with AI](https://github.com/features/copilot)
    - [GitHub SparkBuild and deploy intelligent apps](https://github.com/features/spark)
    - [GitHub ModelsManage and compare prompts](https://github.com/features/models)
    - [MCP RegistryNewIntegrate external tools](https://github.com/mcp)
  + DEVELOPER WORKFLOWS
    - [ActionsAutomate any workflow](https://github.com/features/actions)
    - [CodespacesInstant dev environments](https://github.com/features/codespaces)
    - [IssuesPlan and track work](https://github.com/features/issues)
    - [Code ReviewManage code changes](https://github.com/features/code-review)
  + APPLICATION SECURITY
    - [GitHub Advanced SecurityFind and fix vulnerabilities](https://github.com/security/advanced-security)
    - [Code securitySecure your code as you build](https://github.com/security/advanced-security/code-security)
    - [Secret protectionStop leaks before they start](https://github.com/security/advanced-security/secret-protection)
  + EXPLORE
    - [Why GitHub](https://github.com/why-github)
    - [Documentation](https://docs.github.com)
    - [Blog](https://github.blog)
    - [Changelog](https://github.blog/changelog)
    - [Marketplace](https://github.com/marketplace)

  [View all features](https://github.com/features)
* Solutions

  + BY COMPANY SIZE
    - [Enterprises](https://github.com/enterprise)
    - [Small and medium teams](https://github.com/team)
    - [Startups](https://github.com/enterprise/startups)
    - [Nonprofits](https://github.com/solutions/industry/nonprofits)
  + BY USE CASE
    - [App Modernization](https://github.com/solutions/use-case/app-modernization)
    - [DevSecOps](https://github.com/solutions/use-case/devsecops)
    - [DevOps](https://github.com/solutions/use-case/devops)
    - [CI/CD](https://github.com/solutions/use-case/ci-cd)
    - [View all use cases](https://github.com/solutions/use-case)
  + BY INDUSTRY
    - [Healthcare](https://github.com/solutions/industry/healthcare)
    - [Financial services](https://github.com/solutions/industry/financial-services)
    - [Manufacturing](https://github.com/solutions/industry/manufacturing)
    - [Government](https://github.com/solutions/industry/government)
    - [View all industries](https://github.com/solutions/industry)

  [View all solutions](https://github.com/solutions)
* Resources

  + EXPLORE BY TOPIC
    - [AI](https://github.com/resources/articles?topic=ai)
    - [Software Development](https://github.com/resources/articles?topic=software-development)
    - [DevOps](https://github.com/resources/articles?topic=devops)
    - [Security](https://github.com/resources/articles?topic=security)
    - [View all topics](https://github.com/resources/articles)
  + EXPLORE BY TYPE
    - [Customer stories](https://github.com/customer-stories)
    - [Events & webinars](https://github.com/resources/events)
    - [Ebooks & reports](https://github.com/resources/whitepapers)
    - [Business insights](https://github.com/solutions/executive-insights)
    - [GitHub Skills](https://skills.github.com)
  + SUPPORT & SERVICES
    - [Documentation](https://docs.github.com)
    - [Customer support](https://support.github.com)
    - [Community forum](https://github.com/orgs/community/discussions)
    - [Trust center](https://github.com/trust-center)
    - [Partners](https://github.com/partners)
* Open Source

  + COMMUNITY
    - [GitHub SponsorsFund open source developers](https://github.com/sponsors)
  + PROGRAMS
    - [Security Lab](https://securitylab.github.com)
    - [Maintainer Community](https://maintainers.github.com)
    - [Accelerator](https://github.com/accelerator)
    - [Archive Program](https://archiveprogram.github.com)
  + REPOSITORIES
    - [Topics](https://github.com/topics)
    - [Trending](https://github.com/trending)
    - [Collections](https://github.com/collections)
* Enterprise

  + ENTERPRISE SOLUTIONS
    - [Enterprise platformAI-powered developer platform](https://github.com/enterprise)
  + AVAILABLE ADD-ONS
    - [GitHub Advanced SecurityEnterprise-grade security features](https://github.com/security/advanced-security)
    - [Copilot for BusinessEnterprise-grade AI features](https://github.com/features/copilot/copilot-business)
    - [Premium SupportEnterprise-grade 24/7 support](https://github.com/premium-support)
* [Pricing](https://github.com/pricing)

Search or jump to...


# Search code, repositories, users, issues, pull requests...

Search

Clear

[Search syntax tips](https://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax)

# Provide feedback

We read every piece of feedback, and take your input very seriously.


Include my email address so I can be contacted

Cancel
 Submit feedback





# Saved searches

## Use saved searches to filter your results more quickly

Name

Query

To see all available qualifiers, see our [documentation](https://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax).

Cancel
 Create saved search

[Sign in](/login?return_to=https%3A%2F%2Fgithub.com%2Fkragen%2Fstoneknifeforth)

[Sign up](/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E&source=header-repo&source_repo=kragen%2Fstoneknifeforth)

Appearance settings

Resetting focus

You signed in with another tab or window. Reload to refresh your session.
You signed out in another tab or window. Reload to refresh your session.
You switched accounts on another tab or window. Reload to refresh your session.
 


Dismiss alert

{{ message }}

[kragen](/kragen) 
/
**[stoneknifeforth](/kragen/stoneknifeforth)**
Public

* [Notifications](/login?return_to=%2Fkragen%2Fstoneknifeforth) You must be signed in to change notification settings
* [Fork
  24](/login?return_to=%2Fkragen%2Fstoneknifeforth)
* [Star
   436](/login?return_to=%2Fkragen%2Fstoneknifeforth)

a tiny self-hosted Forth implementation

### License

[CC0-1.0 license](/kragen/stoneknifeforth/blob/master/LICENSE.md)

[436
stars](/kragen/stoneknifeforth/stargazers) [24
forks](/kragen/stoneknifeforth/forks) [Branches](/kragen/stoneknifeforth/branches) [Tags](/kragen/stoneknifeforth/tags) [Activity](/kragen/stoneknifeforth/activity)

[Star](/login?return_to=%2Fkragen%2Fstoneknifeforth)

[Notifications](/login?return_to=%2Fkragen%2Fstoneknifeforth) You must be signed in to change notification settings

* [Code](/kragen/stoneknifeforth)
* [Issues
  2](/kragen/stoneknifeforth/issues)
* [Pull requests
  2](/kragen/stoneknifeforth/pulls)
* [Actions](/kragen/stoneknifeforth/actions)
* [Projects
  0](/kragen/stoneknifeforth/projects)
* [Wiki](/kragen/stoneknifeforth/wiki)
* [Security
  0](/kragen/stoneknifeforth/security)
* [Insights](/kragen/stoneknifeforth/pulse)

Additional navigation options


* [Code](/kragen/stoneknifeforth)
* [Issues](/kragen/stoneknifeforth/issues)
* [Pull requests](/kragen/stoneknifeforth/pulls)
* [Actions](/kragen/stoneknifeforth/actions)
* [Projects](/kragen/stoneknifeforth/projects)
* [Wiki](/kragen/stoneknifeforth/wiki)
* [Security](/kragen/stoneknifeforth/security)
* [Insights](/kragen/stoneknifeforth/pulse)

# kragen/stoneknifeforth

master

[Branches](/kragen/stoneknifeforth/branches)[Tags](/kragen/stoneknifeforth/tags)

Go to file

Code

Open more actions menu

## Folders and files

| Name | | Name | Last commit message | Last commit date |
| --- | --- | --- | --- | --- |
| Latest commit   History[113 Commits](/kragen/stoneknifeforth/commits/master/)   113 Commits | | |
| [.gitattributes](/kragen/stoneknifeforth/blob/master/.gitattributes ".gitattributes") | | [.gitattributes](/kragen/stoneknifeforth/blob/master/.gitattributes ".gitattributes") |  |  |
| [.gitignore](/kragen/stoneknifeforth/blob/master/.gitignore ".gitignore") | | [.gitignore](/kragen/stoneknifeforth/blob/master/.gitignore ".gitignore") |  |  |
| [386.c](/kragen/stoneknifeforth/blob/master/386.c "386.c") | | [386.c](/kragen/stoneknifeforth/blob/master/386.c "386.c") |  |  |
| [LICENSE.md](/kragen/stoneknifeforth/blob/master/LICENSE.md "LICENSE.md") | | [LICENSE.md](/kragen/stoneknifeforth/blob/master/LICENSE.md "LICENSE.md") |  |  |
| [Makefile](/kragen/stoneknifeforth/blob/master/Makefile "Makefile") | | [Makefile](/kragen/stoneknifeforth/blob/master/Makefile "Makefile") |  |  |
| [Q.tbf1](/kragen/stoneknifeforth/blob/master/Q.tbf1 "Q.tbf1") | | [Q.tbf1](/kragen/stoneknifeforth/blob/master/Q.tbf1 "Q.tbf1") |  |  |
| [README.md](/kragen/stoneknifeforth/blob/master/README.md "README.md") | | [README.md](/kragen/stoneknifeforth/blob/master/README.md "README.md") |  |  |
| [TODO](/kragen/stoneknifeforth/blob/master/TODO "TODO") | | [TODO](/kragen/stoneknifeforth/blob/master/TODO "TODO") |  |  |
| [build](/kragen/stoneknifeforth/blob/master/build "build") | | [build](/kragen/stoneknifeforth/blob/master/build "build") |  |  |
| [cat.tbf1](/kragen/stoneknifeforth/blob/master/cat.tbf1 "cat.tbf1") | | [cat.tbf1](/kragen/stoneknifeforth/blob/master/cat.tbf1 "cat.tbf1") |  |  |
| [cat1.tbf1](/kragen/stoneknifeforth/blob/master/cat1.tbf1 "cat1.tbf1") | | [cat1.tbf1](/kragen/stoneknifeforth/blob/master/cat1.tbf1 "cat1.tbf1") |  |  |
| [disassemble](/kragen/stoneknifeforth/blob/master/disassemble "disassemble") | | [disassemble](/kragen/stoneknifeforth/blob/master/disassemble "disassemble") |  |  |
| [hello42.tbf1](/kragen/stoneknifeforth/blob/master/hello42.tbf1 "hello42.tbf1") | | [hello42.tbf1](/kragen/stoneknifeforth/blob/master/hello42.tbf1 "hello42.tbf1") |  |  |
| [hi.tbf1](/kragen/stoneknifeforth/blob/master/hi.tbf1 "hi.tbf1") | | [hi.tbf1](/kragen/stoneknifeforth/blob/master/hi.tbf1 "hi.tbf1") |  |  |
| [onescreen.tbf1](/kragen/stoneknifeforth/blob/master/onescreen.tbf1 "onescreen.tbf1") | | [onescreen.tbf1](/kragen/stoneknifeforth/blob/master/onescreen.tbf1 "onescreen.tbf1") |  |  |
| [star.tbf1](/kragen/stoneknifeforth/blob/master/star.tbf1 "star.tbf1") | | [star.tbf1](/kragen/stoneknifeforth/blob/master/star.tbf1 "star.tbf1") |  |  |
| [tiny.asm](/kragen/stoneknifeforth/blob/master/tiny.asm "tiny.asm") | | [tiny.asm](/kragen/stoneknifeforth/blob/master/tiny.asm "tiny.asm") |  |  |
| [tinyboot.py](/kragen/stoneknifeforth/blob/master/tinyboot.py "tinyboot.py") | | [tinyboot.py](/kragen/stoneknifeforth/blob/master/tinyboot.py "tinyboot.py") |  |  |
| [tinyboot.s](/kragen/stoneknifeforth/blob/master/tinyboot.s "tinyboot.s") | | [tinyboot.s](/kragen/stoneknifeforth/blob/master/tinyboot.s "tinyboot.s") |  |  |
| [tinyboot1.tbf1](/kragen/stoneknifeforth/blob/master/tinyboot1.tbf1 "tinyboot1.tbf1") | | [tinyboot1.tbf1](/kragen/stoneknifeforth/blob/master/tinyboot1.tbf1 "tinyboot1.tbf1") |  |  |
| [trace.py](/kragen/stoneknifeforth/blob/master/trace.py "trace.py") | | [trace.py](/kragen/stoneknifeforth/blob/master/trace.py "trace.py") |  |  |
| [trim.py](/kragen/stoneknifeforth/blob/master/trim.py "trim.py") | | [trim.py](/kragen/stoneknifeforth/blob/master/trim.py "trim.py") |  |  |
| View all files | | |

## Repository files navigation

* [README](#)
* [CC0-1.0 license](#)

# StoneKnifeForth

This is StoneKnifeForth, a very simple language inspired by Forth. It
is not expected to be useful; instead, its purpose is to show how
simple a compiler can be. The compiler is a bit under two pages of
code when the comments are removed.

This package includes a “metacircular compiler” which is written in
StoneKnifeForth and compiles StoneKnifeForth to an x86 Linux ELF
executable.

There is also a StoneKnifeForth interpreter written in Python (tested
with Python 2.4). It seems to be about 100× slower than code emitted
by the compiler.

(All of the measurements below may be a bit out of date.)

On my 700MHz laptop, measuring wall-clock time:

* compiling the compiler, using the compiler running in the interpreter,
  takes about 10 seconds;
* compiling the compiler, using the compiler compiled with the compiler,
  takes about 0.1 seconds;
* compiling a version of the compiler from which comments and extra
  whitespace have been “trimmed”, using the compiler compiled with the
  compiler, takes about 0.02 seconds.

So this is a programming language implementation that can recompile
itself from source twice per 24fps movie frame. The entire “trimmed”
source code is 1902 bytes, which is less than half the size of the
nearest comparable project that I’m aware of, `otccelf`, which is 4748
bytes.

As demonstrated by the interpreter written in Python, the programming
language itself is essentially machine-independent, with very few
x86 quirks:

* items on the stack are 32 bits in size;
* arithmetic is 32-bit;
* stack items stored in memory not only take up 4 bytes, but they are
  little-endian.

(It would be fairly easy to make a tiny “compiler” if the source
language were, say, x86 machine code.)

The output executable is 4063 bytes, containing about 1400
instructions. `valgrind` reports that it takes 1,813,395 instructions
to compile itself. (So you would think that it could compile itself
in 2.6 ms. The long runtimes are a result of reading its input one
byte at at time.)

## Why? To Know What I’m Doing

A year and a half ago, I wrote a [metacircular Bicicleta-language
interpreter](http://lists.canonical.org/pipermail/kragen-hacks/2007-February/000450.html), and I said:

> Alan Kay frequently expresses enthusiasm over the metacircular Lisp
> interpreter in the Lisp 1.5 Programmer’s Manual. For example, in
> <http://acmqueue.com/modules.php?name=Content&pa=showpage&pid=273&page=4>
> he writes:
>
> > ```
> > Yes, that was the big revelation to me when I was in graduate
> > school — when I finally understood that the half page of code on
> > the bottom of page 13 of the Lisp 1.5 manual was Lisp in
> > itself. These were “Maxwell’s Equations of Software!” This is the
> > whole world of programming in a few lines that I can put my hand
> > over.
> >
> > I realized that anytime I want to know what I’m doing, I can just
> > write down the kernel of this thing in a half page and it’s not
> > going to lose any power. In fact, it’s going to gain power by
> > being able to reenter itself much more readily than most systems
> > done the other way can possibly do.
> > ```

But if you try to implement a Lisp interpreter in a low-level language
by translating that metacircular interpreter into it (as [I did later
that year](http://lists.canonical.org/pipermail/kragen-hacks/2007-September/000464.html)) you run into a problem. The metacircular interpreter
glosses over a large number of things that turn out to be nontrivial
to implement — indeed, about half of the code is devoted to things
outside of the scope of the metacircular interpreter. Here’s a list
of issues that the Lisp 1.5 metacircular interpreter neglects, some
semantic and some merely implementational:

* memory management
* argument evaluation order
* laziness vs. strictness
* other aspects of control flow
* representation and comparison of atoms
* representation of pairs
* parsing
* type checking and type testing (atom)
* recursive function call and return
* tail-calls
* lexical vs. dynamic scoping

In John C. Reynolds’s paper, “[Definitional Interpreters
Revisited](http://www.brics.dk/~hosc/local/HOSC-11-4-pp355-361.pdf)”, Higher-Order and Symbolic Computation, 11, 355–361
(1998), he says:

> In the fourth and third paragraphs before the end of Section 5, I
> should have emphasized the fact that a metacircular interpreter is
> not really a deﬁnition, since it is trivial when the deﬁning
> language is understood, and otherwise it is ambiguous. In
> particular, Interpreters I and II say nothing about order of
> application, while Interpreters I and III say little about
> higher-order functions. Jim Morris put the matter more strongly:
>
> > The activity of deﬁning features in terms of themselves is highly
> > suspect, especially when they are as subtle as functional
> > objects. It is a fad that should be debunked, in my opinion. A
> > real signiﬁcance of [a self-deﬁned] interpreter . . . is that it
> > displays a simple universal function for the language in
> > question.
>
> On the other hand, I clearly remember that John McCarthy’s
> deﬁnition of LISP [1DI], which is a deﬁnitional interpreter in the
> style of II, was a great help when I ﬁrst learned that language. But
> it was not the sole support of my understanding.

(For what it’s worth, it may not even the case that self-defined
interpreters are necessarily Turing-complete; it might be possible to
write a non-Turing-complete metacircular interpreter for a
non-Turing-complete language, such as David Turner’s Total Functional
Programming systems.)

A metacircular compiler forces you to confront this extra complexity.
Moreover, metacircular compilers are self-sustaining in a way that
interpreters aren’t; once you have the compiler running, you are free
to add features to the language it supports, then take advantage of
those features in the compiler.

So this is a “stone knife” programming tool: bootstrapped out of
almost nothing as quickly as possible.

## Why? To Develop a Compiler Incrementally

When I wrote Ur-Scheme, my thought was to see if I could figure out
how to develop a compiler incrementally, starting by building a small
working metacircular compiler in less than a day, and adding features
from there. I pretty much failed; it took me two and a half weeks to
get it to compile itself successfully.

Part of the problem is that a minimal subset of R5RS Scheme powerful
enough to write a compiler in — without making the compiler even
larger due to writing it in a low-level language — is still a
relatively large language. Ur-Scheme doesn’t have much arithmetic,
but it does have integers, dynamic typing, closures, characters,
strings, lists, recursion, booleans, variadic functions, `let` to
introduce local variables, character and string literals, a sort of
crude macro system, five different conditional forms (if, cond, case,
and, or), quotation, tail-call optimization, function argument count
verification, bounds checking, symbols, buffered input to keep it from
taking multiple seconds to compile itself, and a library of functions
for processing lists, strings, and characters. And each of those
things was added because it was necessary to get the compiler to be
able to compile itself. The end result was that the compiler is 90
kilobytes of source code, about 1600 lines if you leave out the
comments.

Now, maybe you can write 1600 lines of working Scheme in a day, but I
sure as hell can’t. It’s still not a very large compiler, as
compilers go, but it’s a lot bigger than `otccelf`. So I hypothesized
that maybe a simpler language, without a requirement for compatibility
with something else, would enable me to get a compiler bootstrapped
more easily.

So StoneKnifeForth was born. It’s inspired by Forth, so it inherits
most of Forth’s traditional simplifications:

* no expressions;
* no statements;
* no types, dynamic or static;
* no floating-point (although Ur-Scheme doesn’t have floating-point either);
* no scoping, lexical or dynamic;
* no dynamic memory allocation;

And it added a few of its own:

* no names of more than one byte;
* no user-defined IMMEDIATE words (the Forth equivalent of Lisp macros);
* no interpretation state, so no compile-time evaluation at all;
* no interactive REPL;
* no `do` loops;
* no `else` on `if` statements;
* no recursion;
* no access to the return stack;
* no access to the filesystem, just stdin and stdout;
* no multithreading.

Surprisingly, the language that results is still almost bearable to
write a compiler in, although it definitely has the flavor of an
assembler.

Unfortunately, I still totally failed to get it done in a day. It was
15 days from when I first started scribbling about it in my notebook
until it was able to compile itself successfully, although `git` only
shows active development happening on six of those days (including
some help from my friend Aristotle). So that’s an improvement, but
not as much of an improvement as I would like. At that point, it was
13k of source, 114 non-comment lines of code, which is definitely a
lot smaller than Ur-Scheme’s 90k and 1600 lines. (Although there are
another 181 lines of Python for the bootstrap interpreter.)

It’s possible to imagine writing and debugging 114 lines of code in a
day, or even 300 lines. It’s still maybe a bit optimistic to think I could
do that in a day, so maybe I need to find a way to increase
incrementality further.

My theory was that once I had a working compiler, I could add features
to the language incrementally and test them as I went. So far I
haven’t gotten to that part.

## Why? Wirth envy

[Michael Franz writes](http://www.ics.uci.edu/~franz/Site/pubs-pdf/BC03.pdf "Oberon — the overlooked jewel"):

> In order to find the optimal cost/benefit ratio, Wirth used a highly
> intuitive metric, the origin of which is unknown to me but that may
> very well be Wirth’s own invention. He used the compiler’s
> self-compilation speed as a measure of the compiler’s
> quality. Considering that Wirth’s compilers were written in the
> languages they compiled, and that compilers are substantial and
> non-trivial pieces of software in their own right, this introduced a
> highly practical benchmark that directly contested a compiler’s
> complexity against its performance. Under the self-compilation speed
> benchmark, only those optimizations were allowed to be incorporated
> into a compiler that accelerated it by so much that the intrinsic
> cost of the new code addition was fully compensated.

Wirth is clearly one of the great apostles of simplicity in
programming, together with with Edsger Dijkstra and Chuck Moore. But
I doubt very much that the Oberon compiler could ever compile itself
in 2 million instructions, given the complexity of the Oberon
language.

R. Kent Dybvig used the same criterion; speaking of the 1985–1987
development of Chez Scheme, [he writes](http://www.cs.indiana.edu/~dyb/pubs/hocs.pdf "The Development of Chez Scheme"):

> At some point we actually instituted the following rule to keep a
> lid on compilation overhead: if an optimization doesn’t make the
> compiler itself enough faster to make up for the cost of doing the
> optimization, the optimization is discarded. This ruled out several
> optimizations we tried, including an early attempt at a source
> optimizer.

## Far-Fetched Ways This Code Could Actually be Useful

The obvious way that it could be useful is that you could read it and
learn things from it, then put them to use in actually useful
software. This section is about the far-fetched ways instead.

If you want to counter Ken Thompson’s “Trusting Trust” attack, you
would want to start with a minimal compiler on a minimal chip;
StoneKnifeForth might be a good approach.

## Blind Alleys

Here are some things I thought about but didn’t do.

### Making More Things Into Primitives

There are straightforward changes to reduce the executable size
further, but they would make the compiler more complicated, not
simpler. Some of the most-referenced routines should be open-coded,
which should also speed it up, as well as making them available to
other programs compiled with the same compiler. Here are the routines
that were called in more than 10 places some time ago:

```
 11 0x169  xchg
 13 0xc20  Lit
 22 0x190  -  (now replaced by +, which is only used in 25 places)
 25 0x15b  pop
 26 0x1bc  =
 35 0x13d  dup
 60 0x286  .
```

Of these, `xchg`, `pop`, `-`, `=`, and `dup` could all be open-coded
at zero or negative cost at the call sites, and then their definitions
and temporary variables could be removed.

I tried out open-coding `xchg`, `pop`, `dup`, and `+`. The executable
shrank by 346 bytes (from 4223 bytes to 3877 bytes, an 18% reduction;
it also executed 42% fewer instructions to compile itself, from
1,492,993 down to 870,863 on the “trimmed” version of itself), and the
source code stayed almost exactly the same size, at 119 non-comment
lines; the machine-code definitions were one line each. They look
like this:

```
dup 'd = [ pop 80 . ; ]               ( dup is `push %eax` )
dup 'p = [ pop 88 . ; ]               ( pop is `pop %eax` )
dup 'x = [ pop 135 . 4 . 36 . ; ]     ( xchg is xchg %eax, (%esp)
dup '+ = [ pop 3 . 4 . 36 . 89 . ; ]  ( `add [%esp], %eax; pop %ecx` )
```

However, I decided not to do this. The current compiler already
contains 58 bytes of machine code, and this would add another 9 bytes
to that. The high-level Forth definitions (`: dup X ! ;` and the
like) are, I think, easier to understand and verify the correctness
of; and they don’t depend on lower-level details like what
architecture we’re compiling for, or how we represent the stacks.
Additionally, it adds another step to the bootstrap process.

### Putting the Operand Stack on `%edi`

Forth uses two stacks: one for procedure nesting (the “return stack”)
and one for parameter passing (the “data stack” or “operand stack”).
This arrangement is shared by other Forth-like languages such as
PostScript and HP calculator programs. Normally, in Forth, unlike in
these other languages, the “return stack” is directly accessible to
the user.

Right now, StoneKnifeForth stores these two stacks mostly in memory,
although it keeps the top item of the operand stack in `%eax`. The
registers `%esp` and `%ebp` point to the locations of the stacks in
memory; the one that’s currently being used is in `%esp`, and the
other one is in `%ebp`. So the compiler has to emit an `xchg %esp, %ebp` instruction whenever it switches between the two stacks. As a
result, when compiling itself, something like 30% of the instructions
it emits are `xchg %esp, %ebp`.

Inspired, I think, by colorForth, I considered just keeping the
operand stack pointer in `%edi` and using it directly from there,
rather than swapping it into `%esp`. The x86 has a `stosd`
instruction (GCC calls it `stosl`) which will write a 32-bit value in
`%eax` into memory at `%edi` and increment (or decrement) `%edi` by 4,
which is ideal for pushing values from `%eax` (as in `Lit`, to make
room for the new value). Popping values off the stack, though,
becomes somewhat hairier. The `lodsd` or `lodsl` instruction that
corresponds to `stosl` uses `%esi` instead of `%edi`, you have to set
“DF”, the direction flag, to get it to decrement instead of
incrementing, and like `stosl`, it accesses memory *before* updating
the index register, not after.

So, although we would eliminate a lot of redundant and ugly `xchg`
instructions in the output, as well as the `Restack`, `u`, `U`, and
`%flush` functions, a bunch of the relatively simple instruction
sequences currently emitted by the compiler would become hairier.
I think the changes are more or less as follows:

* `!` is currently `pop (%eax); pop %eax`, which is three bytes; this
  occurs 17 times in the output. The new code would be:
  sub $8, %edi
  mov 4(%edi), %ecx
  mov %ecx, (%eax)
  mov (%edi), %eax
  This is ten bytes. `store`, the byte version of `!`, is similar.
* `-` is currently `sub %eax, (%esp); pop %eax`, which is four bytes;
  this occurs 23 times in the output. The new code would be seven
  bytes:
  sub $4, %edi
  sub %eax, (%edi)
  mov (%edi), %eax
  There's something analogous in `<`, although it only occurs three
  times.
* in `JZ`, `jnz`, and `Getchar`, there are occurrences of `pop %eax`,
  which is one byte (88). The new code would be five bytes:
  sub $4, %edi
  mov (%edi), %eax
  `JZ` occurs 38 times in the output, `jnz` occurs 5 times, and
  Getchar occurs once.
* `Msyscall` would change a bit.

There are 193 occurrences of `push %eax` in the output code at the
moment, each of which is followed by a move-immediate into %eax.
These would just change to `stosd`, which is also one byte.

So this change would increase the amount of machine code in the
compiler source by 10 - 3 + 7 - 4 + 5 - 1 + 5 - 1 + 5 - 1 = 22 bytes,
which is a lot given that there’s only 58 bytes there now; I think
that would make the compiler harder to follow, although `Restack` does
too. It would also increase the size of the compiler output by (10 -
3) \* 17 + (7 - 4) \* 23 + (5 - 1) \* (38 + 5 + 1) = 364 bytes, although
it would eliminate 430 `xchg %esp, %ebp` instructions, two bytes each,
for a total of 2 \* 430 - 364 = 496 bytes less; and the resulting
program would gain (4 - 2) \* 17 + (3 - 2) \* 23 + (2 - 1) \* (38 + 5 +

1. = 101 instructions, then lose the 430 `xchg` instructions.

My initial thought was that it would be silly to space-optimize
popping at the expense of pushing; although they happen the same
number of times during the execution of the program, generally more
data is passed to callees than is returned to callers, so the number
of push sites is greater than the number of pop sites. (In this
program, it’s about a factor of 2, according to the above numbers.)
Also, consumers of values from the stack often want to do something
interesting with the top two values on the stack, not just discard the
top-of-stack: `-` subtracts, `<` compares, `!` and `store` send it to
memory. Only `JZ` and `jnz` (and `pop`) just want to discard
top-of-stack — but to my surprise, they make up half of the pops.

However, I wasn’t thinking about the number of places in the compiler
where machine code would be added. What if I used `%esi` instead of
`%edi`, to get a single-byte single-instruction pop (in the form of
`lodsl`) instead of a single-byte push? This would make `Lit` (the
only thing that increases the depth of the operand stack) uglier, and
each of the 193 occurrencies of `push %eax` that result from the 193
calls to `Lit` in the compilation process would get four bytes bigger
(totaling 772 extra bytes) but the seven or so primitives that
*decrease* the depth of the operand stack would gain less extra
complexity. And we’d still lose the `xchg %esp, %ebp` crap, including
the code to avoid emitting them.

## What’s Next

Maybe putting the operand stack on `%esi`.

Maybe factor some commonality out of the implementation of `-` and
`<`.

If we move the creation of the ELF header and `Msyscall` and `/buf` to
run-time instead of compile-time, we could eliminate the `#` and
`byte` compile-time directives, both from the compiler and the
interpreter; the output would be simpler; `Msyscall` and `/buf`
wouldn't need two separate names and tricky code to poke them into the
output; the tricky code wouldn’t need a nine-line comment explaining
it; the characters ‘E’, ‘L’, and ‘F’ wouldn’t need to be magic
numbers.

Maybe factor out "0 1 -"!

Maybe pull the interpreter and compiler code into a literate document
that explains them.

Maybe building a compiler for a slightly bigger and better language on
top of this one. Maybe something like Lua, Scheme, or Smalltalk. A
first step toward that would be something that makes parsing a little
more convenient. Another step might be to establish some kind of
intermediate representation in the compiler, and perhaps some kind of
pattern-matching facility to make it easier to specify rewrites on the
intermediate representation (either for optimizations or for code
generation).

Certainly the system as it exists is not that convenient to program
in, the code is pretty hard to read, and when it fails, it is hard to
debug — especially in the compiler, which doesn’t have any way to emit
error messages. Garbage collection, arrays with bounds-checking,
finite maps (associative arrays), strong typing (any typing, really),
dynamic dispatch, some thing that saves you from incorrect stack
effects, metaprogramming, and so on, these would all help; an
interactive REPL would be a useful non-linguistic feature.

## Copyright status

I (Kragen Javier Sitaker) wrote StoneKnifeForth in 2008 and published
it immediately in Argentina. I didn't add an explicit copyright
license at the time, but here is one now:

> [To the extent possible under law, Kragen Javier Sitaker has waived
> all copyright and related or neighboring rights to
> StoneKnifeForth](https://creativecommons.org/publicdomain/zero/1.0/). This
> work is published from: Argentina.

For more details, I've included the CC0 "legal code" in
[LICENSE.md](/kragen/stoneknifeforth/blob/master/LICENSE.md).

## Related work

Andre Adrian’s 2008 BASICO: <http://www.andreadrian.de/tbng/>

> * is a small imperative programming language that is just powerful
>   enough to compile itself (compiler bootstrapping).
> * has no GOTO, but has while-break-wend and multiple return
> * has C-like string handling.
> * is implemented in less then 1000 source code lines for the compiler.
> * produces real binary programs for x86 processors, not P-code or
>   Byte-Code.
> * uses the C compiler toolchain (assembler, linker)
> * uses C library functions like printf(), getchar(), strcpy(),
>   isdigit(), rand() for run-time support.

Actually it produces assembly, not executables.

Version 0.9 was released 15 Jul 2006. The 1000 source lines include a
recursive-descent parser and a hand-coded lexer.

Sample code:

```
// return 1 if ch is in s, 0 else
func in(ch: char, s: array char): int
var i: int
begin
  i = 0
  while s[i] # 0 do
    if ch = s[i] then
      return 1
    endif
    i = i + 1
  wend
  return 0
end
```

FIRST and THIRD, from [the IOCCC entry](http://www.ioccc.org/1992/buzzard.2.design).

[Ian Piumarta’s COLA](http://piumarta.com/software/cola/) system.

Oberon.

Fabrice Bellard’s [OTCC](https://bellard.org/otcc/).

[F-83](https://github.com/ForthHub/F83).

eForth, especially the ITC [eForth](http://www.exemark.com/FORTH/eForthOverviewv5.pdf).

Jack Crenshaw’s [Let’s Build a Compiler](http://compilers.iecc.com/crenshaw/). This is a how-to book
that walks you through an incrementally-constructed compiler for a toy
language, written in Pascal, in about 340 pages of text. The text is
really easy to read, but it will still take at least three to ten
hours to read. It uses recursive-descent parsing, no intermediate
representation, and it emits 68000 assembly code.

Ikarus.

[Ur-Scheme](http://canonical.org/~kragen/sw/urscheme/).

PyPy.

Bootstrapping a simple compiler from nothing:
Edmund GRIMLEY EVANS
2001
<http://web.archive.org/web/20061108010907/http://www.rano.org/bcompiler.html>

## About

a tiny self-hosted Forth implementation

### Resources

[Readme](#readme-ov-file)

### License

[CC0-1.0 license](#CC0-1.0-1-ov-file)

### Uh oh!

There was an error while loading. Please reload this page.

[Activity](/kragen/stoneknifeforth/activity)

### Stars

[**436**
stars](/kragen/stoneknifeforth/stargazers)

### Watchers

[**21**
watching](/kragen/stoneknifeforth/watchers)

### Forks

[**24**
forks](/kragen/stoneknifeforth/forks)

[Report repository](/contact/report-content?content_url=https%3A%2F%2Fgithub.com%2Fkragen%2Fstoneknifeforth&report=kragen+%28user%29)

## [Releases](/kragen/stoneknifeforth/releases)

No releases published

## [Packages 0](/users/kragen/packages?repo_name=stoneknifeforth)

No packages published

## [Contributors 4](/kragen/stoneknifeforth/graphs/contributors)

### Uh oh!

There was an error while loading. Please reload this page.

## Languages

* [Forth
  50.1%](/kragen/stoneknifeforth/search?l=forth)
* [C
  23.4%](/kragen/stoneknifeforth/search?l=c)
* [Python
  16.6%](/kragen/stoneknifeforth/search?l=python)
* [Assembly
  7.6%](/kragen/stoneknifeforth/search?l=assembly)
* [Shell
  2.3%](/kragen/stoneknifeforth/search?l=shell)

## Footer

© 2026 GitHub, Inc.

### Footer navigation

* [Terms](https://docs.github.com/site-policy/github-terms/github-terms-of-service)
* [Privacy](https://docs.github.com/site-policy/privacy-policies/github-privacy-statement)
* [Security](https://github.com/security)
* [Status](https://www.githubstatus.com/)
* [Community](https://github.community/)
* [Docs](https://docs.github.com/)
* [Contact](https://support.github.com?tags=dotcom-footer)
* Manage cookies
* Do not share my personal information



You can’t perform that action at this time.