(λ [blog] KING)   KING Is Not Genera, but it will be

defλ Part 1

Introduction

Let’s define some primitives for a preliminary lisp (and associate some symbols while we’re at it). We need:

  • nil (∅) and true (⊤)
  • cons (ζ)
  • car (α)
  • cdr (ω) (or should this be β??)
  • eq (=)
  • cond (χ)
  • quote (’)
  • eval (Ξ)
  • lambda (λ)
  • map (↦)
  • apply (Λ)

In order to get there, we’re going to have to define some preliminaries:

Preliminaries: Types and Tags

We’re going to use tagged pointers in order to know what kind of thing (or type) our pointer is pointing to. In AArch64 architectures, bits 56-63 of a virtual address (a pointer) may be used for program specific data, and that is where we will put our tags. Right now we’re only going to define a few general categories:

Pointer Type        Pointer tag
general data        #b000xxxxx 
numerical values    #b001xxxxx 
displayable values  #b010xxxxx 
procedural data     #b100xxxxx 
symbolic data       #b101xxxxx 
structured data     #b110xxxxx 

We’ll really only deal with two of them today: structured data and displayable values.

Displayable Values: Booleans

What are displayable values? They’re values that aren’t numbers, evaluate to themselves, and can be displayed. This includes things like characters, strings, keywords, and booleans. It also includes things like images, videos, diagrams, and lots more.

We’ll define the pointer tag for booleans as follows:

Pointer Type     Pointer tag
boolean          #b01010001 

The actual value of the boolean will be encoded in the pointers least significant bit. Keeping with convention, we’ll define the values with 1 being true and 0 being false.

Structured Data: The Cons Pair

We’re going to start with one data structure today: the cons pair.

What’s a Cons Pair?

In any lisp, the core method of constructing data, the core primitive of all data structures, is the cons pair. See SICP Ch. 2.

(cons 'a 'b) ;;=> ('a . 'b)

It’s typically denoted with a period in between the two objects like so (<object-a> . <object-b>)

Typically, a cons pair can depicted in a box and pointer diagram like this1:

img

  • A piece of data (the first part of the pair)
  • Another piece of data (the second part of the pair)
  • The pointers to two pieces of data, which constitute the actual constructed object of a cons pair

All a cons pair really is is just two pointers. So in memory we just have to put two things:

  1. The pointer to the first object.
  2. The pointer to the second object

Now it’s kind of a magical thing that you can use these pairs to generate lists, and trees, and numerical representations, and all sorts of crazy things! For instance, here’s how you represent lists:

img

It’s just:

(cons 1 (cons 2 (cons 3 (cons 4 nil))))

And that becomes:

'(1 2 3 4)

Amazing, right!?

Tagging a cons cell

Now, we’re going to tag the cons pair as such:

Pointer Type     Pointer tag
cons cell        #b11000000 

Procedural Data

Now why do we have to have a tag for procedural data in our pointers? Because we’re leaving the door open for an (optional) complication to lisp evaluation. Here’s an example:

This is what happens in most lisps when you do this:

(eval '(1 (+ 1 1) (+ 1 2))) => ERROR: SOMETHING SOMETHING '1' is not a function

And here is what I personally think SHOULD happen:

(eval '(1 (+ 1 1) (+ 1 2))) => '(1 2 3)

Now, a pointer to a general object, or to something that you treat as a literal, or can’t otherwise evaluate, will contain a 0 as the first significant bit (bit 0). As such, the proposed evaluation model would know not to attempt to apply it to a set of arguments. This is subject to change, and may not work, or may be useless. But we’ll stick with it for now.

Preliminaries: Functional Arguments and Procedure Calls

Let’s look at an actual example of the kind of thing we’d like to write:

(defλ some-fn
  [x0 x1 x2 x3 & x4]
  (some-stuff x1 x3)
  (compute-return-value x0 x4))

Becomes something like:

    .text
    .global some-fn
some-fn:
    /* <evaluation of arguments goes here> */

    /* store all the arguments on the stack*/
    stp x0, x1 [sp, #-16]!
    stp x2, x3 [sp, #-16]!
    str x4 [sp, #-16]!

    /* getting the arguments for some-stuff and calling it */
    ldr x0, [sp], #42         // get x1 (second arg)
    ldr x1, [sp], #24         // get x3 (fourth arg)
    bl some-stuff             // call some-stuff

    /* getting the arguments for compute-return-value and calling it */
    ldr x0, [sp], #48         // get x0 (first arg)
    ldr x1, [sp], #8         // get x4 (& args)
    bl compute-return-value   // call compute-return-value

    /* pointer to return value is placed in x0 by compute-return-value */

    ret /* return to point in x30 */

This resource was very helpful in understanding how one might handle procedure calls.

Caveat Hacker

I haven’t run any of the code in this post. We won’t really be able to until we get a REPL going. So, if you’re reading this, and see an error, please feel free to mention it in the github repo.

nil (∅) and true (⊤)

Boolean values, with values encoded in the pointer as described above. Defined as such:

 value      encoded  
 nil (∅)    0        
 true (⊤)   1        

cons (ζ)

Let’s start simple, the absolute simplest we can get: A cons pair.

A cons pair is a pair of pointers. That’s it. It takes 128 bits of memory, and we allocate it on the stack2. The procedure cons just returns the tagged pointer to this 128 bit span of memory. The tagged pointer itself is just the address of the pointers on the stack, with the appropriate memory tag.

So this:

(cons 'a 'b) ;;=> ('a . 'b)

    .text
    .global cons
cons:
 /* store the arguments on the stack*/
    stp x0, x1 [sp, #-16]!

 /* <evaluation of arguments goes here>
  We'll put the pointers to evaluated arguments
  in the same registers (x0, x1) as they came to us */

 /* Get the tagged pointer to our cons pair */
    add x0, [sp], #16

 /* Add our pointer tag. */
    mov x1, #b11000000
    lsl x1, #56
    add x0, x1

 /* set x1 to nil */
    mov x1, #b01010001
    lsl x1, #56

 /* return the pointer to our cons pair */
    ret

car (α)

The car is the first object in your cons-pair. Like so:

(car ('a . 'b)) ;;=> 'a

The procedure for car should return the pointer stored in the first 64 bits of the cons-pair’s memory address. Remember, this address is just the value of the pointer with a different tag.

But there are a couple edge cases here. The first is (car nil) which should return nil. The second is trying to call car on something that isn’t a cons pair, in which case we should throw an error. But since we don’t have an exception handling method yet, we’ll just return nil for now.

    .text
    .global car
car:
  /* <evaluation of arguments goes here>
    We'll put the pointers to evaluated arguments
    in the same register x0 as they came to us.
    This is also where we would check the tag of
    the pointer given to us in x0, to make sure it
    points to a cons pair.*/

 /* check the tag */
    lsr x1, x0 #56
    cmp x1, #b11000000

 /* set x1 to nil */
    mov x1, #b01010001
    lsl x1, #56

 /* load our prospective car from memory */
    ldr x0, x0

 /* if you tried to cons something that wasn't a pair,
    set x0 to nil */
    mov x0, x1

 /* return the pointer to the first value in the pair */
    ret

cdr is similar:

cdr (ω)

The cdr of a cons-pair is the second object stored in a cons-pair. Hence, cdr returns the second pointer stored in the 128 bits of a cons-pair in memory. So this:

(cdr ('a . 'b)) ;;=> 'b

Becomes this:

    .text
    .global cdr
cdr:
  /* <evaluation of arguments goes here>
    We'll put the pointers to evaluated arguments
    in the same register x0 as they came to us. */

 /* check the tag */
    lsr x1, x0 #56
    cmp x1, #b11000000

 /* set x1 to nil */
    mov x1, #b01010001
    lsl x1, #56

 /* load our prospective cdr from memory */
    ldr x0, x0, #8

 /* if you tried to cons something that wasn't a pair,
    set x0 to nil */
    mov x0, x1

 /* return the pointer to the second value in the pair */
    ret

As I said, similar.

cond (χ)

Cond is a procedure that takes a list of those functions that takes an unlimited number of unevaluated pairs of forms (just groups of two, not cons pairs), comprising of a conditional and a result. In our little, clojure-like dialect of lisp, it looks something like this:

(cond
 (condition1 args) (result1 args)
 (condition2 args) (result2 args)
 :else (default-result))

Now, we haven’t had a procedure that takes potentially more arguments than there are registers before, so we need to decide how we’re going to handle that. We’ll define it properly when we define apply (Λ). But for now we’ll state it like this:

  • For procedures which take an unlimited number of arguments, it must receive a list of arguments in register x7.

Let’s try to implement it:

    .text
    .global cond
cond:
 /* store the stack pointer and return address */
    stp x29, x30, [sp, #-16]!

 /* Check if the argument list in x7 is nil. */
 /* set x6 to nil and compare with x7 */
    mov x6, #b01010001
    lsl x6, #56
    cmp x7, x6

 /* Set our return value to zero if the arglist is nil */
    moveq x2, #0  /* put a zero value in x2 for the
                     conditional to follow if args are nil */
    moveq x0, x6  /* put nil in x0 if the args are nil */

 /* Get the first conditional argument if the args are not nil */
    movnq x2, #1  /* put a nonzero value in x2 for the
                     conditional to follow if args not nil*/
    str x0, x7    /* put the pointer to our list in x0
                     so car knows the location of our argument
                     list.*/
    blnz x2, car  /* call car. the pointer to our first
                     conditional will come back in x0 */

 /* evaluation of the conditional in x0 goes here */

 /* Check if the evaluated conditional in x0 is truthy */
    cmp x0, x6    /* remember #4 is #b0010 which is nil */

 /* If truthy, set x2 to non-zero for following conditional branch,
    otherwise set x2 to zero.*/
    movnq x2, #1
    moveq x2, #0

 /* We need the cdr for the arglist regardless of whether our
    conditional was truthy, so let's get that */
    movnq x0, x7  /* store the pointer to our list in x0 */
    bl cdr    /* get the cdr to our argument list, returns in x0*/

 /* If conditional was truthy get the resultant form, which is the second
    item in our argument list. Returns in x0. */
    blnz x2, car

 /* If the conditional was not truthy, get the arglist minus the
    initial conditional and result form */
    blz x2, cdr

 /* If the conditional was not truthy, set the register for the
    arglist to the new arglist returned above*/
    movnq x7, x0

 /* Get back the stack pointer and return address */
    ldp x29, x30, [sp], #16

 /* if the conditional was not truthy, recur */
    bnz x2, cond

 /* evaluation of the return value goes here. It'll return in x0*/

 /* return to calling function */
    ret

Conclusion

Well, this concludes Part 1 of our definition of a preliminary lisp. In Part 2, we’ll define the rest of our primitives. In Part 3 we’ll actually try and get a REPL going and evaluate some code3!

Footnotes

1 (Thanks to Andres Raba for his version of SICP and for these figures!). Now, if we look at that, it becomes pretty clear what exactly that diagram actually represents:

2 We’re not going to worry about memory allocation right now, or heaps and stacks, (although Henry Baker has some interesting things to say on the matter). Why? Because it’s a big topic that I’m not ready to approach. I do have some ideas, and it has ramifications to what we’re going to do in this post, but suffice to say: where we’re going, we don’t need heaps!

3 And by evaluate some code, I mean find out just how glaringly wrong the code in Part 1 and 2 actually is!

Something Boots!

Progress report: Hello World in Aarch64 Assembly on Bare Metal

I got something to boot last night.

Part 1: Hello World! (in virtual userspace)

I started with the arm assembly hello world here. It looks something like this:

/*hello.s*/
.data

/* Data segment: define our message string and calculate its length. */
msg:
    .ascii        "Hello, ARM64!\n"
len = . - msg

.text

/* Our application's entry point. */
.globl _start
_start:
    /* syscall write(int fd, const void *buf, size_t count) */
    mov     x0, #1      /* fd := STDOUT_FILENO */
    ldr     x1, =msg    /* buf := msg */
    ldr     x2, =len    /* count := len */
    mov     w8, #64     /* write is syscall #64 */
    svc     #0          /* invoke syscall */

    /* syscall exit(int status) */
    mov     x0, #0      /* status := 0 */
    mov     w8, #93     /* exit is syscall #93 */
    svc     #0          /* invoke syscall */

Install binutils-arm-linux-gnueabihf then:

aarch64-linux-gnueabihf-as hello.s -o hello.o
aarch64-linux-gnueabihf-ld hello.o -o hello

And now run it!

qemu-aarch64 ./hello

But that’s not very satsifying right? Those svc instructions are interrupts to invoke syscalls. Syscalls that are handled by an operating system. We want something that runs on bare metal, without an operating system. I mean, we’re writing an operating system, right? (Yeah, yeah, an OS is a collection of things that don’t fit into a language… there shouldn’t be one, and so on and so forth.) A bare metal lisp, a lisp from scratch, in the sense of LISP being the primary model of computation, where shutting down your lisp meant shutting down your computer, because to engage in computing was to compute, not merely to use an appliance as one uses a dishwasher.

And this does not run on bare metal. It presumes an operating system.

Anyway. So let’s go further:

Part 2: h(ello world!) on Bare (Virtual) Metal

I started with OS Dev’s handy dandy QEMU AArch64 Virt Bare Bones explanation. Now, I’m really not interested in writing a whole lot of C. I’d like to go from assembly to lisp as quickly as possible with no intermediaries. So let’s just look at the assembly to find the relevant instructions. Here’s what the original kernel.c looks like:

#include <stdint.h>

volatile uint8_t *uart = (uint8_t *) 0x09000000;

void putchar(char c) {
    *uart = c;
}

void print(const char *s) {
    while(*s != '\0') {
        putchar(*s);
        s++;
    }
}

void kmain(void) {
     print("Hello world!\n");
}

And if you run aarch64-elf-gcc -ffreestanding -c kernel.c -S to look at the assembly you’ll see a lot of junk. And I didn’t understand that junk, so I pared kernel.c down to something approachable.

#include <stdint.h>

volatile uint8_t *uart = (uint8_t *) 0x09000000;

void kmain(void) {
    *uart = 'h';
    //*uart = 'e';
    //*uart = 'l';
    //*uart = 'l';
    //*uart = 'o';
}

And now we get this significantly more approachable bit of assembly:

	.arch armv8-a
	.file	"kernel.c"
	.text
	.global	uart
	.data
	.align	3
	.type	uart, %object
	.size	uart, 8
uart:
	.xword	150994944
	.text
	.align	2
	.global	kmain
	.type	kmain, %function
kmain:
.LFB0:
	.cfi_startproc
	adrp	x0, uart
	add	x0, x0, :lo12:uart
	ldr	x0, [x0]
	mov	w1, 104
	strb	w1, [x0]
	nop
	ret
	.cfi_endproc
.LFE0:
	.size	kmain, .-kmain
	.ident	"GCC: (Ubuntu 11.2.0-5ubuntu1) 11.2.0"
	.section	.note.GNU-stack,"",@progbits

Now, that’s quite alot. I don’t think we can simplify too much further than that though.

There are a few relevant bits to understand here.

Declaring the UART register

    .text
    .global uart
uart:
    .xword 150994944

Now, 0x09000000 is the hex representation of 150994944, so it looks like as converted it to decimal here. So this snippet defines the address for the register uart… or something (don’t ask me, I’m figuring it out as I go!)

Getting the Correct Register for UART into x0

adrp	x0, uart
add	x0, x0, :lo12:uart
ldr	x0, [x0]
mov	w1, 104
strb	w1, [x0]

I’ve tried removing particular instructions from this segment, and they all seem to be essential.

Actually Printing a Character to UART

mov	w1, 104
strb	w1, [x0]

The strb is what does the actual reading. This stack overflow post was helpful, even though it’s for an older form of ARM assembly.

Ending the Procedure(?)

nop
ret

I think this is how you end procedures.

Putting it all Together

So I tried to take it to bare essentials and this is what I got:

/* hello_1_5 */
    .text
    .global uart
uart:
    .xword 150994944

    .text
    .global _start
_start:
    adrp x0, uart
    add x0, x0, :lo12:uart
    ldr x0, [x0]

    mov w1, 104
    strb w1, [x0]

    nop
    ret

aarch64-linux-gnu-as hello_1_5.s -o hello_1_5.o && aarch64-linux-gnu-ld hello_1_5.o -o hello_1_5

qemu-system-aarch64 -machine virt -cpu cortex-a57 -kernel hello_1_5 -nographic

And it prints h!

Part 3: prompt (=>) hello world!

Now let’s throw a couple more things in. With the .text and the .local declarations we can define procedures and/or variables; ldrb can read from the uart register the same way that strb writes to it; and procedures defined in assembly can be called recursively. What does that spell?! A listener loop!

/* hello_2.s */
    .text
    .global uart
uart:
    .xword 150994944

    .text
    .local listen
listen:
    /* let's listen for a character typed in the terminal*/
    adrp x0, uart
    add x0, x0, :lo12:uart
    ldr x0, [x0]
    /* now we know that the actual address for uart is in x0 */

    ldrb w1, [x0] /* load the read character into w1 from uart */

    strb w1, [x0] /* echo the character loaded in w1 back to uart */

    b listen /* recur, b means branch, which I think is a procedure call */

    .text
    .global _start
_start:
    adrp x0, uart
    add x0, x0, :lo12:uart
    ldr x0, [x0]

    mov w1, 61 /* 61 is '=' */
    mov w2, 62 /* 62 is '>' */

    strb w1, [x0] /* let's print our mock prompt */
    strb w2, [x0]

    b listen /* start the listener loop */

    nop
    ret

And now when we build that, we get a program that prints => for a mock prompt, and then echoes whatever you type back to you. In other words…

We have I/O running on (virtual) bare metal!

Update: A Response and Clarification from /u/anydalch

I posted this to reddit a little while ago and /u/anydalch provided this very helpful response clarifying how addressing works in AArch64.

neat! if you haven’t found it already, the arm architecture reference manual will be an invaluable resource on this journey: https://developer.arm.com/documentation/ddi0487/ha/?lang=en .

i also want to explain the adrp / add pattern you noticed in your c compiler’s output, because it’s something you’ll want to be used to.

arm64 is designed for writing position-independent relocatable code, where it shouldn’t matter what address you load your binary into; you just put it wherever and jump into it. as a result, all the immediate memory-access instructions use program-counter-relative offsets instead of absolute addresses. so you say, “load from 128 bytes before this instruction,” not “load from the absolute address 0xabcdef.” your assembler will mostly make this transparent to you; when you write a symbol as the immediate argument to an instruction like your b listen, it will be automatically converted into a pc-relative offset. but if you want to put the address of a symbol into a register, you use a variant of the adr (“address”) instruction, which calculates a pc-relative address and stores that address in a register. (it’s sort of similar to intel’s lea instruction, if you’re familiar with that, in that both of them offer an interface to the system’s address calculator which gives you back the address instead of immediately using it in another operation.)

the bare adr instruction has a relatively short range, since the offset has to fit in an immediate value that gets encoded in a 32-bit instruction – iirc, adr gives you a signed 12-bit offset. this poses a problem, because linkers often arrange for your code and data segments to be stored somewhat far apart, outside that range. enter adrp (“address page”), which calculates a 12-bit-aligned page address. the pattern you’ll see a lot in code generated by c compilers is to use adrp to calculate a base, followed by an add to get an offset into that page.

the adrp / add pattern is so common, in fact, that arm defines a “pseudo-instruction” called adrl (“address long”) which expands to both. not all assemblers implement it (the clang assembler notably does not), but i think gnu as does. so you should be able to replace:

adrp x0, uart
add x0, x0, :lo12:uart

with:

adrl x0, uart

U Boot, I Boot, We All Boot for U Boot!

Progress report

Well I got u-boot, a bootloader for ARM systems (and others) to run in QEMU After installing the requisite dependencies and cross-compiling u-boot for arm, I ran it, and it ran.

qemu-system-aarch64 -curses -machine virt -cpu cortex-a57 -bios u-boot.bin

You can hit Esc+1 to get into QEMU’s monitoring interface, and Esc+2 to get into a u-boot command line. Note that you have to hold it for a couple seconds, and it may take a couple tries for QEMU to respond. I don’t know why this is. But the point is I can emulate an ARM processor and bootloader!

Now I just have to write something for it to boot.

Addenda: For my own memory, this is how I compiled u-boot:

export CROSS_COMPILE=aarch64-linux-gnu-
make qemu_arm64_defconfig
make

You need to install gcc-aarch64-linux-gnu

Resources: Pandy Song’s Blog

Introducing KING

KING Is Not Genera

Well, that’s the first thing I wanted to say, anyway. If you know what Symbolics Genera is, that tells you a lot about what this is, how ambitious it is and its likelihood of success or failure.

Some Preliminaries

What is this blog?

It’s a dev blog.

For what?

KING Is Not Genera.

You told me that already. I don’t know what it means.

It’s a recursive acronym. KING for short.

So if it’s not Genera (whatever that is), what is it?

It’s a Lisp.

Okay, so it’s a programming language.

No.

What?

Lisp is not a programming language, Lisp is a way of interfacing with the computer. It’s as much an operating system as it is a programming language. For instance. Emacs Lisp is not a lisp. Emacs is a lisp, which has Emacs Lisp as its programming language.

What?

Think of it like this: Lisp is a process that you interface with to read lisp, write lisp, edit lisp, etc., and everything else that you do with a computer is just a higher-order usage of lisp. For instance your graphics driver is taking a lazy-list of frames from a video-file, which is a list of frames, each of which is an image, which is just an array of pixel values, and maybe some timecode metadata, to sync it to the audio, which is just a list of different amplitude values and timecode values, and both your video and audio can be handled the same way as any other kind of lisp data type, because they’re just lisp data types, because it’s all part of one program, lisp, that runs on your computer, with nothing in between you and it, or between it and the machine. Lisp is the program that you teach you to do everything you want it to, including write programs.

Okay… What’s Genera?

Genera is awesome.

So why is KING not Genera?

Because Genera is not open source, and even though Symbolics (the company that made Genera) shut down the year I was born, you still can’t legally get your hands on Genera. So I’m going to write my own lisp. A lisp from scratch. The king of all programs. And I’ll call it KING.

I’d recommend you read part 4 of Stephen Levy’s book Hackers, about how the GNU project got started, and the relationship between that project and the Lisp Machine.

So has anyone ever done this before?

Well, it’s definitely possible that I’m the first one in the history of ever to even think about attempting it, totally 100% visionary.

So do you have a plan?

Kind of. We’ll make it up as we go.

A Glimmer of a Plan

Step 0: Architecture

We’re targeting the MNT hardware. Their laptop has a hyper key. That’s good enough for us. It means we’re targeting ARMv8. Eventually (hopefully), it means ARMv8.5+ with the Memory Tagging Extension.

Also, Lukas at MNT is kinda our guy. And there are other projects targeting similar hardware, so it looks like it’s kind of the place to be.

Step 1: A Bootstrap Lisp

We need to create a bootstrap lisp. Why? So we’re not writing the whole damn thing in assembly. Eventually, it’d be nice to have an assembly function, which takes something like this:

(asm (:mov x0 x2)
     (:add x0 x1))

and turns it into machine code. But for right now, so that we aren’t completely stuck, we’re writing it using GNU assembler.

Step 2: EXCALIBUR

All lisps are the same in their bare essentials. The sort of data structures, data literals, valid symbols, scoping (dynamic or lexical), macros vs fexprs, lisp-1 vs lisp-2, define vs defun vs defn, Common-Lisp vs Scheme vs Clojure-like vs pico-lisp style vs kernel or something else entirely? Different question. If all anyone gets out of this is the bootstrap lisp, well that would make me very sad, but it would be good for everyone else, because it would mean that every young lisp wizard would be empowered to make their own lisp from our bootstrap lisp, and evolve that lisp into the reflection of their own mind, up to the point where it became what they used for both work and play, building in standards from libraries in order to do real work, but nevertheless in all things which they may choose, that which they have chosen rather than the appliance-like default selected for them by some OS designer external to themselves. Wait… what were we talking about? Right, this is about my lisp. more to come on this.

Step 2a: YOUR-KINGDOM (persistence)

Instead of having a filesystem, we’ll store all data in namespaces as lisp objects under symbols. Namespaces can be stored within other namespaces if they’re part of the same larger project. The top level namespace is your castle, sort of like a symbolics world.

Now dealing with that is a bit of a pain in a multi-address-space world, which is why KING is single address space. I have no idea how security will be handled, but there has been work on the topic, and I suspect that given sufficiently advanced metadata semantics, the fact that all data in memory is lisp data and shares lisp semantics will provide us adequate defenses.

Step 3: Draw a pixel

We can only communicate like a teletype for so long before we get sick of that. So eventually we’re going to need to be able to draw to the screen. That means a graphics library.

Step 4: GALAHAD (Editor)

More to come…

Step 4: ROUND TABLE (Process Control)

Step ?: LANCELOT (Networking and Browser)

Step ?: PERCIVAL (other languages)

Step ?: MERLIN (Compiler)

Some caveats

This is meant to be a long term project. It’s also meant to be a way for me to learn, and for all others who are interested to learn with me. If it ends up being anything more than that, I’ll be ecstatic. Each knight (individual subprocess) will be minimalistic in scope, simply because it’s too much for one person. If it actually gets done, then it would be… well honestly it’d be pretty comparable to TempleOS, which you really should look at, as it’s quite possibly the closest anyone has come to Genera since Genera itself.. Let’s follow that comparison for a bit. My hope is that, if Temple OS be the Altar of Computation, that KING shall be its Throne.