Remove old code and update documentation

This commit is contained in:
Justine Tunney 2021-10-08 21:11:00 -07:00
parent cbb4ecc4d5
commit 4233210a86
14 changed files with 3755 additions and 702 deletions

View file

@ -1,4 +1,5 @@
Copyright 2020 Justine Alexandra Roberts Tunney
Copyright 2021 Alain Greppin
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the

View file

@ -1,70 +1,28 @@
CFLAGS ?= -g
CFLAGS += -fno-pie
LDFLAGS += -no-pie # -s -static -N
REALFLAGS = \
-Os \
-D__REAL_MODE__ \
-wrapper ./realify.sh \
-ffixed-r8 \
-ffixed-r9 \
-ffixed-r10 \
-ffixed-r11 \
-ffixed-r12 \
-ffixed-r13 \
-ffixed-r14 \
-ffixed-r15 \
-mno-red-zone \
-fcall-used-rbx \
-fno-jump-tables \
-fno-shrink-wrap \
-fno-schedule-insns2 \
-flive-range-shrinkage \
-fno-omit-frame-pointer \
-momit-leaf-frame-pointer \
-mpreferred-stack-boundary=3 \
-fno-delete-null-pointer-checks
CLEANFILES = \
lisp \
lisp.o \
lisp.real.o \
bestline.o \
sectorlisp.o \
start.o \
lisp.bin \
sectorlisp.bin \
lisp.bin.dbg \
sectorlisp.bin.dbg
.PHONY: all
all: lisp \
lisp.bin \
lisp.bin.dbg \
sectorlisp.bin \
sectorlisp.bin.dbg
.PHONY: clean
clean:; $(RM) $(CLEANFILES)
clean:; $(RM) lisp lisp.o bestline.o sectorlisp.o sectorlisp.bin sectorlisp.bin.dbg
lisp.bin.dbg: start.o lisp.real.o lisp.lds
lisp: lisp.o
start.o: start.S Makefile
lisp.o: lisp.c lisp.h Makefile
lisp.real.o: lisp.c lisp.h Makefile
lisp: lisp.o bestline.o
lisp.o: lisp.c bestline.h
bestline.o: bestline.c bestline.h
sectorlisp.o: sectorlisp.S
$(AS) -g -mtune=i386 -o $@ $<
$(AS) -g -mtune=i386 -o $@ $<
sectorlisp.bin.dbg: sectorlisp.o
$(LD) -oformat:binary -Ttext=0x7600 -o $@ $<
sectorlisp.bin: sectorlisp.bin.dbg
objcopy -SO binary sectorlisp.bin.dbg sectorlisp.bin
%.real.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(REALFLAGS) -c -o $@ $<
%.bin.dbg:
$(LD) $(LDFLAGS) -static -o $@ $(patsubst %.lds,-T %.lds,$^)
%.bin: %.bin.dbg
objcopy -SO binary $< $@
objcopy -S -O binary sectorlisp.bin.dbg sectorlisp.bin

View file

@ -1,37 +1,71 @@
# sectorlisp
sectorlisp is an effort to bootstrap John McCarthy's meta-circular
evaluator on bare metal from a 512-byte boot sector.
sectorlisp is a 512-byte implementation of LISP that's able to bootstrap
John McCarthy's meta-circular evaluator on bare metal.
![Yo dawg, I heard you like LISP so I put a LISP in your LISP so you can eval while you eval](bin/yodawg.png)
## Motivations
## Overview
Much of the information about LISP online tends to focus on
[wild macros](http://www.paulgraham.com/onlisp.html),
[JIT compilation](http://pixielang.org/), or its merits as
[a better XML](http://www.defmacro.org/ramblings/lisp.html)
as well as [a better JSON](https://stopa.io/post/265). However
there's been comparatively little focus on the
[primary materials](https://people.cs.umass.edu/~emery/classes/cmpsci691st/readings/PL/LISP.pdf)
from the 1950's which emphasize the radically simple nature of
LISP, as best evidenced by the meta-circular evaluator above.
LISP has been described as the [Maxwell's equations of
software](https://michaelnielsen.org/ddi/lisp-as-the-maxwells-equations-of-software/).
Yet there's been very little focus to date on reducing these equations
to their simplest possible form. Even the [original LISP
paper](https://people.cs.umass.edu/~emery/classes/cmpsci691st/readings/PL/LISP.pdf)
from the 1960's defines LISP with nonessential elements, e.g. `LABEL`.
This project aims to solve that by doing three things:
1. We provide a LISP implementation that's written in LISP, as a single
pure expression, using only the essential functions of the language.
See [lisp.lisp](lisp.lisp). It's the same meta-circular evaluator in
John McCarthy's paper from the 1960's, except with its bugs fixed,
dependencies included, and syntactic sugar removed.
2. We provide a readable portable C reference implementation to show how
the meta-circular evaluator can be natively bootstrapped on POSIX
conforming platforms, with a pleasant readline-like interface. See
[lisp.c](lisp.c).
2. We provide a 512-byte i8086 implementation of LISP that boots from
BIOS on personal computers. See [sectorlisp.S](sectorlisp.S). To the
best of our knowledge, this is the tiniest true LISP implementation
to date.
<p align="center">
<img alt="Binary Footprint Comparison"
width="750" height="348" src="bin/footprint.png">
<img alt="Binary Footprint Comparison" src="bin/footprint.png">
</p>
This project aims to promote the radical simplicity of the essential
elements of LISP's original design, by building the tiniest LISP machine
possible. With a binary footprint less than one kilobyte, that's capable
of running natively without dependencies on modern PCs, sectorlisp might
be the tiniest self-hosting LISP interpreter to date.
## Getting Started
We're still far off however from reaching our goal, which is to have
sectorlisp be small enough to fit in the master boot record of a floppy
disk, like [sectorforth](https://github.com/cesarblum/sectorforth). If
you can help this project reach its goal, please send us a pull request!
See [lisp.lisp](lisp.lisp) for code examples that you can copy and paste
into your LISP REPL.
You can run the C implementation as follows:
```sh
$ make
$ ./lisp
```
After running `make` you should see a `sectorlisp.bin` file, which is a
master boot record you can put on a flopy disk and boot from BIOS. If
you would prefer to run it in an emulator, we recommend using
[Das Blinkenlights](https://justine.lol/blinkenlights/).
```sh
curl --compressed https://justine.lol/blinkenlights/blinkenlights-latest.com >blinkenlights.com
chmod +x blinkenlights.com
./blinkenlights.com -rt sectorlisp.bin
```
Alternatively you may use QEMU as follows:
```sh
qemu-system-i386 -nographic -fda sectorlisp.bin
```
Further information may be found on [our wiki](https://github.com/jart/sectorlisp/wiki).
## Demo

3481
bestline.c Normal file

File diff suppressed because it is too large Load diff

33
bestline.h Normal file
View file

@ -0,0 +1,33 @@
#pragma once
typedef struct bestlineCompletions {
unsigned long len;
char **cvec;
} bestlineCompletions;
typedef void(bestlineCompletionCallback)(const char *, bestlineCompletions *);
typedef char *(bestlineHintsCallback)(const char *, const char **,
const char **);
typedef void(bestlineFreeHintsCallback)(void *);
void bestlineSetCompletionCallback(bestlineCompletionCallback *);
void bestlineSetHintsCallback(bestlineHintsCallback *);
void bestlineSetFreeHintsCallback(bestlineFreeHintsCallback *);
void bestlineAddCompletion(bestlineCompletions *, const char *);
char *bestline(const char *);
char *bestlineRaw(const char *, int, int);
char *bestlineWithHistory(const char *, const char *);
int bestlineHistoryAdd(const char *);
int bestlineHistorySave(const char *);
int bestlineHistoryLoad(const char *);
void bestlineFreeCompletions(bestlineCompletions *);
void bestlineHistoryFree(void);
void bestlineClearScreen(int);
void bestlineMaskModeEnable(void);
void bestlineMaskModeDisable(void);
void bestlineDisableRawMode(void);
void bestlineFree(void *);
unsigned bestlineLowercase(unsigned);
unsigned bestlineUppercase(unsigned);
void bestlineSetXlatCallback(unsigned(*)(unsigned));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

335
lisp.c
View file

@ -16,110 +16,109 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "lisp.h"
#include "bestline.h"
#define RETRO 1 // auto capitalize input
#define DELETE 1 // allow backspace to rub out symbol
#define QUOTES 1 // allow 'X shorthand (QUOTE X)
#define PROMPT 1 // show repl prompt
#define WORD short
#define WORDS 8192
#ifndef __COSMOPOLITAN__
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#endif
#define QUOTES 1 /* allow 'X shorthand for (QUOTE X) */
#define FUNDEF 1 /* be friendly w/undefined behavior */
#define TRACE 0 /* prints Eval() arguments / result */
/*───────────────────────────────────────────────────────────────────────────│─╗
The LISP Challenge § LISP Machine
*/
#define ATOM 1
#define CONS 0
#define ATOM 0
#define CONS 1
#define NIL (ATOM | 0)
#define UNDEFINED (ATOM | 8)
#define ATOM_T (ATOM | 30)
#define ATOM_QUOTE (ATOM | 34)
#define ATOM_COND (ATOM | 46)
#define ATOM_ATOM (ATOM | 56)
#define ATOM_CAR (ATOM | 66)
#define ATOM_CDR (ATOM | 74)
#define ATOM_CONS (ATOM | 82)
#define ATOM_EQ (ATOM | 92)
#define ATOM_LAMBDA (ATOM | 98)
#define ISATOM(x) (~(x)&1)
#define VALUE(x) ((x)>>1)
#define OBJECT(t,v) ((v)<<1|(t))
#define VALUE(x) ((x) >> 1)
#define NIL OBJECT(ATOM,0)
#define ATOM_T OBJECT(ATOM,4)
#define ATOM_QUOTE OBJECT(ATOM,6)
#define ATOM_COND OBJECT(ATOM,12)
#define ATOM_ATOM OBJECT(ATOM,17)
#define ATOM_CAR OBJECT(ATOM,22)
#define ATOM_CDR OBJECT(ATOM,26)
#define ATOM_CONS OBJECT(ATOM,30)
#define ATOM_EQ OBJECT(ATOM,35)
#define ATOM_LAMBDA OBJECT(ATOM,38)
#define UNDEFINED OBJECT(ATOM,45)
struct Lisp {
WORD mem[WORDS];
int mem[8192];
unsigned char syntax[256];
WORD look;
WORD globals;
WORD index;
int look;
int globals;
int index;
char token[128];
char str[WORDS];
char str[8192];
};
_Static_assert(sizeof(struct Lisp) <= 0x7c00 - 0x600,
"LISP Machine too large for real mode");
_Alignas(char) const char kSymbols[] = "NIL\0"
"*UNDEFINED\0"
"T\0"
"QUOTE\0"
"COND\0"
"ATOM\0"
"CAR\0"
"CDR\0"
"CONS\0"
"EQ\0"
"LAMBDA";
#ifdef __REAL_MODE__
static struct Lisp *const q;
#else
static struct Lisp q[1];
static const char kSymbols[] =
"NIL\0"
"T\0"
"QUOTE\0"
"COND\0"
"ATOM\0"
"CAR\0"
"CDR\0"
"CONS\0"
"EQ\0"
"LAMBDA\0"
#if FUNDEF
"*UNDEFINED"
#endif
;
static void Print(long);
static WORD GetList(void);
static WORD GetObject(void);
static void PrintObject(long);
static WORD Eval(WORD, WORD);
static struct Lisp q[1];
static void Print(int);
static int GetList(void);
static int GetObject(void);
static void PrintObject(int);
static int Eval(int, int);
static void SetupSyntax(void) {
unsigned char *syntax = q->syntax;
asm("" : "+bSD"(syntax));
syntax[' '] = ' ';
syntax['\r'] = ' ';
syntax['\n'] = ' ';
syntax['('] = '(';
syntax[')'] = ')';
syntax['.'] = '.';
#if QUOTES
syntax['\''] = '\'';
#endif
q->syntax[' '] = ' ';
q->syntax['\r'] = ' ';
q->syntax['\n'] = ' ';
q->syntax['('] = '(';
q->syntax[')'] = ')';
q->syntax['.'] = '.';
q->syntax['\''] = '\'';
}
static void SetupBuiltins(void) {
CopyMemory(q->str, kSymbols, sizeof(kSymbols));
memmove(q->str, kSymbols, sizeof(kSymbols));
}
static inline WORD Car(long x) {
return PEEK_ARRAY(q, mem, VALUE(x), 0);
static inline int Car(int x) {
return q->mem[VALUE(x) + 0];
}
static inline WORD Cdr(long x) {
return PEEK_ARRAY(q, mem, VALUE(x), 1);
static inline int Cdr(int x) {
return q->mem[VALUE(x) + 1];
}
static WORD Set(long i, long k, long v) {
POKE_ARRAY(q, mem, VALUE(i), 0, k);
POKE_ARRAY(q, mem, VALUE(i), 1, v);
static int Set(int i, int k, int v) {
q->mem[VALUE(i) + 0] = k;
q->mem[VALUE(i) + 1] = v;
return i;
}
static WORD Cons(WORD car, WORD cdr) {
static int Cons(int car, int cdr) {
int i, cell;
i = q->index;
POKE_ARRAY(q, mem, i, 0, car);
POKE_ARRAY(q, mem, i, 1, cdr);
q->mem[i + 0] = car;
q->mem[i + 1] = cdr;
q->index = i + 2;
cell = OBJECT(CONS, i);
return cell;
@ -128,120 +127,116 @@ static WORD Cons(WORD car, WORD cdr) {
static char *StpCpy(char *d, char *s) {
char c;
do {
c = LODS(s); // a.k.a. c = *s++
STOS(d, c); // a.k.a. *d++ = c
c = *s++;
*d++ = c;
} while (c);
return d;
}
static WORD Intern(char *s) {
static int Intern(char *s) {
int j, cx;
char c, *z, *t;
z = q->str;
c = LODS(z);
c = *z++;
while (c) {
for (j = 0;; ++j) {
if (c != PEEK(s, j, 0)) {
if (c != s[j]) {
break;
}
if (!c) {
return OBJECT(ATOM, z - q->str - j - 1);
}
c = LODS(z);
c = *z++;
}
while (c) c = LODS(z);
c = LODS(z);
while (c) c = *z++;
c = *z++;
}
--z;
StpCpy(z, s);
return OBJECT(ATOM, SUB((long)z, q->str));
return OBJECT(ATOM, z - q->str);
}
static unsigned char XlatSyntax(unsigned char b) {
return PEEK_ARRAY(q, syntax, b, 0);
static void PrintChar(unsigned char b) {
if (write(1, &b, 1) == -1) exit(1);
}
static void PrintString(char *s) {
char c;
for (;;) {
if (!(c = PEEK(s, 0, 0))) break;
if (!(c = s[0])) break;
PrintChar(c);
++s;
}
}
static int GetChar(void) {
int c;
c = ReadChar();
#if RETRO
if (c >= 'a') {
CompilerBarrier();
if (c <= 'z') c -= 'a' - 'A';
unsigned char b;
static char *l, *p;
if (l || (l = p = bestlineWithHistory("* ", "sectorlisp"))) {
if (*p) {
b = *p++;
} else {
free(l);
l = p = 0;
b = '\n';
}
return b;
} else {
PrintChar('\n');
exit(0);
}
#endif
#if DELETE
if (c == '\b') return c;
#endif
PrintChar(c);
if (c == '\r') PrintChar('\n');
return c;
}
static void GetToken(void) {
char *t;
unsigned char b, x;
int b, x;
b = q->look;
t = q->token;
for (;;) {
x = XlatSyntax(b);
x = q->syntax[b];
if (x != ' ') break;
b = GetChar();
}
if (x) {
STOS(t, b);
*t++ = b;
b = GetChar();
} else {
while (b && !x) {
if (!DELETE || b != '\b') {
STOS(t, b);
} else if (t > q->token) {
PrintString("\b \b");
if (t > q->token) --t;
}
*t++ = b;
b = GetChar();
x = XlatSyntax(b);
x = q->syntax[b];
}
}
STOS(t, 0);
*t++ = 0;
q->look = b;
}
static WORD ConsumeObject(void) {
static int ConsumeObject(void) {
GetToken();
return GetObject();
}
static WORD Cadr(long x) {
return Car(Cdr(x)); // ((A B C D) (E F G) H I) → (E F G)
static int Cadr(int x) {
return Car(Cdr(x)); /* ((A B C D) (E F G) H I) → (E F G) */
}
static WORD List(long x, long y) {
static int List(int x, int y) {
return Cons(x, Cons(y, NIL));
}
static WORD Quote(long x) {
static int Quote(int x) {
return List(ATOM_QUOTE, x);
}
static WORD GetQuote(void) {
static int GetQuote(void) {
return Quote(ConsumeObject());
}
static WORD AddList(WORD x) {
static int AddList(int x) {
return Cons(x, GetList());
}
static WORD GetList(void) {
static int GetList(void) {
GetToken();
switch (*q->token & 0xFF) {
default:
@ -257,7 +252,7 @@ static WORD GetList(void) {
}
}
static WORD GetObject(void) {
static int GetObject(void) {
switch (*q->token & 0xFF) {
default:
return Intern(q->token);
@ -270,21 +265,21 @@ static WORD GetObject(void) {
}
}
static WORD ReadObject(void) {
static int ReadObject(void) {
q->look = GetChar();
GetToken();
return GetObject();
}
static WORD Read(void) {
static int Read(void) {
return ReadObject();
}
static void PrintAtom(long x) {
static void PrintAtom(int x) {
PrintString(q->str + VALUE(x));
}
static void PrintList(long x) {
static void PrintList(int x) {
#if QUOTES
if (Car(x) == ATOM_QUOTE) {
PrintChar('\'');
@ -307,7 +302,7 @@ static void PrintList(long x) {
PrintChar(')');
}
static void PrintObject(long x) {
static void PrintObject(int x) {
if (ISATOM(x)) {
PrintAtom(x);
} else {
@ -315,7 +310,7 @@ static void PrintObject(long x) {
}
}
static void Print(long i) {
static void Print(int i) {
PrintObject(i);
PrintString("\r\n");
}
@ -324,55 +319,58 @@ static void Print(long i) {
The LISP Challenge § Bootstrap John McCarthy's Metacircular Evaluator
*/
static WORD Caar(long x) {
return Car(Car(x)); // ((A B C D) (E F G) H I) → A
static int Caar(int x) {
return Car(Car(x)); /* ((A B C D) (E F G) H I) → A */
}
static WORD Cdar(long x) {
return Cdr(Car(x)); // ((A B C D) (E F G) H I) → (B C D)
static int Cdar(int x) {
return Cdr(Car(x)); /* ((A B C D) (E F G) H I) → (B C D) */
}
static WORD Cadar(long x) {
return Cadr(Car(x)); // ((A B C D) (E F G) H I) → B
static int Cadar(int x) {
return Cadr(Car(x)); /* ((A B C D) (E F G) H I) → B */
}
static WORD Caddr(long x) {
return Cadr(Cdr(x)); // ((A B C D) (E F G) H I) → H
static int Caddr(int x) {
return Cadr(Cdr(x)); /* ((A B C D) (E F G) H I) → H */
}
static WORD Caddar(long x) {
return Caddr(Car(x)); // ((A B C D) (E F G) H I) → C
static int Caddar(int x) {
return Caddr(Car(x)); /* ((A B C D) (E F G) H I) → C */
}
static WORD Evcon(long c, long a) {
static int Evcon(int c, int a) {
return Eval(Caar(c), a) != NIL ? Eval(Cadar(c), a) : Evcon(Cdr(c), a);
}
static WORD Assoc(long x, long a) {
return a != NIL ? Caar(a) == x ? Cdar(a) : Assoc(x, Cdr(a)) : NIL;
static int Assoc(int x, int a) {
return a ? Caar(a) == x ? Cdar(a) : Assoc(x, Cdr(a)) : NIL;
}
static WORD Pairlis(WORD x, WORD y, WORD a) {
if (x == NIL)
return a;
WORD di = Cons(Car(x), Car(y));
WORD si = Pairlis(Cdr(x), Cdr(y), a);
return Cons(di, si); // Tail-Modulo-Cons
static int Pairlis(int x, int y, int a) { /* it's zip() basically */
int di, si;
if (!x) return a;
di = Cons(Car(x), Car(y));
si = Pairlis(Cdr(x), Cdr(y), a);
return Cons(di, si); /* Tail-Modulo-Cons */
}
static WORD Evlis(WORD m, WORD a) {
if (m == NIL)
return NIL;
WORD di = Eval(Car(m), a);
WORD si = Evlis(Cdr(m), a);
static int Evlis(int m, int a) {
int di, si;
if (!m) return NIL;
di = Eval(Car(m), a);
si = Evlis(Cdr(m), a);
return Cons(di, si);
}
static WORD Apply(WORD fn, WORD x, WORD a) {
static int Apply(int fn, int x, int a) {
int t1, si, ax;
if (ISATOM(fn)) {
switch (fn) {
#if FUNDEF
case NIL:
return UNDEFINED;
#endif
case ATOM_CAR:
return Caar(x);
case ATOM_CDR:
@ -387,22 +385,20 @@ static WORD Apply(WORD fn, WORD x, WORD a) {
return Apply(Eval(fn, a), x, a);
}
}
if (Car(fn) == ATOM_LAMBDA) {
WORD t1 = Cdr(fn);
WORD si = Pairlis(Car(t1), x, a);
WORD ax = Cadr(t1);
t1 = Cdr(fn);
si = Pairlis(Car(t1), x, a);
ax = Cadr(t1);
return Eval(ax, si);
}
return UNDEFINED;
}
static WORD Eval(WORD e, WORD a) {
static int Evaluate(int e, int a) {
int ax;
if (ISATOM(e))
return Assoc(e, a);
WORD ax = Car(e);
ax = Car(e);
if (ISATOM(ax)) {
if (ax == ATOM_QUOTE)
return Cadr(e);
@ -411,31 +407,42 @@ static WORD Eval(WORD e, WORD a) {
if (ax == ATOM_LAMBDA)
return e;
}
return Apply(ax, Evlis(Cdr(e), a), a);
}
static int Eval(int e, int a) {
int ax;
#if TRACE
PrintString("> ");
PrintObject(e);
PrintString("\r\n ");
PrintObject(a);
PrintString("\r\n");
#endif
ax = Evaluate(e, a);
#if TRACE
PrintString("< ");
PrintObject(ax);
PrintString("\r\n");
#endif
return ax;
}
/*───────────────────────────────────────────────────────────────────────────│─╗
The LISP Challenge § User Interface
*/
void Repl(void) {
for (;;) {
#if PROMPT
PrintString("* ");
#endif
Print(Eval(Read(), q->globals));
}
}
int main(int argc, char *argv[]) {
RawMode();
SetupSyntax();
SetupBuiltins();
#if PROMPT
bestlineSetXlatCallback(bestlineUppercase);
PrintString("THE LISP CHALLENGE V1\r\n"
"VISIT GITHUB.COM/JART\r\n");
#endif
Repl();
return 0;
}

180
lisp.h
View file

@ -1,180 +0,0 @@
#ifndef SECTORLISP_H_
#define SECTORLISP_H_
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
/*───────────────────────────────────────────────────────────────────────────│─╗
The LISP Challenge § Richard Stallman Math 55 Systems Integration Code
*/
#define CompilerBarrier() asm volatile("" ::: "memory")
#define ISATOM(x) /* a.k.a. !(x&1) */ \
({ \
_Bool IsAtom; \
asm("test%z1\t$1,%1" : "=@ccnz"(IsAtom) : "Qm"((char)x)); \
IsAtom; \
})
#define OBJECT(t, v) /* a.k.a. v<<1|t */ \
({ \
__typeof(v) Val = (v); \
asm("shl\t%0" : "+r"(Val)); \
Val | (t); \
})
#define SUB(x, y) /* a.k.a. x-y */ \
({ \
__typeof(x) Reg = (x); \
asm("sub\t%1,%0" : "+rm"(Reg) : "g"(y)); \
Reg; \
})
#define STOS(di, c) asm("stos%z1" : "+D"(di), "=m"(*(di)) : "a"(c))
#define LODS(si) \
({ \
typeof(*(si)) c; \
asm("lods%z2" : "+S"(si), "=a"(c) : "m"(*(si))); \
c; \
})
static inline void *SetMemory(void *di, int al, unsigned long cx) {
asm("rep stosb"
: "=D"(di), "=c"(cx), "=m"(*(char(*)[cx])di)
: "0"(di), "1"(cx), "a"(al));
return di;
}
static inline void *CopyMemory(void *di, const void *si, unsigned long cx) {
asm("rep movsb"
: "=D"(di), "=S"(si), "=c"(cx), "=m"(*(char(*)[cx])di)
: "0"(di), "1"(si), "2"(cx));
return di;
}
static void RawMode(void) {
#ifndef __REAL_MODE__
struct termios t;
if (ioctl(1, TCGETS, &t) != -1) {
t.c_cc[VMIN] = 1;
t.c_cc[VTIME] = 1;
t.c_iflag &= ~(INPCK | ISTRIP | PARMRK | INLCR | IGNCR | ICRNL | IXON);
t.c_lflag &= ~(IEXTEN | ICANON | ECHO | ECHONL);
t.c_cflag &= ~(CSIZE | PARENB);
t.c_oflag &= ~OPOST;
t.c_cflag |= CS8;
t.c_iflag |= IUTF8;
ioctl(1, TCSETS, &t);
}
#endif
}
__attribute__((__noinline__)) static void PrintChar(long c) {
#ifdef __REAL_MODE__
asm volatile("mov\t$0x0E,%%ah\n\t"
"int\t$0x10"
: /* no outputs */
: "a"(c), "b"(7)
: "memory");
#else
static short buf;
int rc;
buf = c;
write(1, &buf, 1);
#endif
}
static int ReadChar(void) {
int c;
#ifdef __REAL_MODE__
asm volatile("int\t$0x16" : "=a"(c) : "0"(0) : "memory");
c &= 0xff;
#else
static int buf;
read(0, &buf, 1);
c = buf;
#endif
return c;
}
#define PEEK_(REG, BASE, INDEX, DISP) \
({ \
__typeof(*(BASE)) Reg; \
if (__builtin_constant_p(INDEX) && !(INDEX)) { \
asm("mov\t%c2(%1),%0" \
: REG(Reg) \
: "bDS"(BASE), "i"((DISP) * sizeof(*(BASE))), \
"m"(BASE[(INDEX) + (DISP)])); \
} else { \
asm("mov\t%c3(%1,%2),%0" \
: REG(Reg) \
: "b"(BASE), "DS"((long)(INDEX) * sizeof(*(BASE))), \
"i"((DISP) * sizeof(*(BASE))), "m"(BASE[(INDEX) + (DISP)])); \
} \
Reg; \
})
#define PEEK(BASE, INDEX, DISP) /* a.k.a. b[i] */ \
(sizeof(*(BASE)) == 1 ? PEEK_("=Q", BASE, INDEX, DISP) \
: PEEK_("=r", BASE, INDEX, DISP))
#define PEEK_ARRAY_(REG, OBJECT, MEMBER, INDEX, DISP) \
({ \
__typeof(*(OBJECT->MEMBER)) Reg; \
if (!(OBJECT)) { \
asm("mov\t%c2(%1),%0" \
: REG(Reg) \
: "bDS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \
"i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) + \
sizeof(*(OBJECT->MEMBER)) * (DISP)), \
"m"(OBJECT->MEMBER)); \
} else { \
asm("mov\t%c3(%1,%2),%0" \
: REG(Reg) \
: "b"(OBJECT), "DS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \
"i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) + \
sizeof(*(OBJECT->MEMBER)) * (DISP)), \
"m"(OBJECT->MEMBER)); \
} \
Reg; \
})
#define PEEK_ARRAY(OBJECT, MEMBER, INDEX, DISP) /* o->m[i] */ \
(sizeof(*(OBJECT->MEMBER)) == 1 \
? PEEK_ARRAY_("=Q", OBJECT, MEMBER, INDEX, DISP) \
: PEEK_ARRAY_("=r", OBJECT, MEMBER, INDEX, DISP))
#define POKE_ARRAY_(REG, OBJECT, MEMBER, INDEX, DISP, VALUE) \
do { \
if (!(OBJECT)) { \
asm("mov\t%1,%c3(%2)" \
: "=m"(OBJECT->MEMBER) \
: REG((__typeof(*(OBJECT->MEMBER)))(VALUE)), \
"bDS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \
"i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) + \
sizeof(*(OBJECT->MEMBER)) * (DISP))); \
} else { \
asm("mov\t%1,%c4(%2,%3)" \
: "=m"(OBJECT->MEMBER) \
: REG((__typeof(*(OBJECT->MEMBER)))(VALUE)), "b"(OBJECT), \
"DS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \
"i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) + \
sizeof(*(OBJECT->MEMBER)) * (DISP))); \
} \
} while (0)
#define POKE_ARRAY(OBJECT, MEMBER, INDEX, DISP, VALUE) /* o->m[i]=v */ \
do { \
__typeof(*(OBJECT->MEMBER)) Reg; \
switch (sizeof(*(OBJECT->MEMBER))) { \
case 1: \
POKE_ARRAY_("Q", OBJECT, MEMBER, INDEX, DISP, VALUE); \
break; \
default: \
POKE_ARRAY_("r", OBJECT, MEMBER, INDEX, DISP, VALUE); \
break; \
} \
} while (0)
#endif /* SECTORLISP_H_ */

View file

@ -1,36 +0,0 @@
ENTRY(_start)
SECTIONS {
.text 0x7c00 - 0x600 : {
*(.start)
*(.text.startup)
rodata = .;
*(.rodata .rodata.*)
. = 0x1fe;
SHORT(0xaa55);
*(.text .text.*)
_etext = .;
. = ALIGN(512);
}
.bss : {
bss = .;
*(.bss .bss.*)
*(COMMON)
}
/DISCARD/ : {
*(.yoink)
*(.*)
}
}
boot = 0x7c00;
q.syntax = 8192*2;
q.look = 8192*2+256;
q.globals = 8192*2+256+2;
q.index = 8192*2+256+2+2;
q.token = 8192*2+256+2+2+2;
q.str = 8192*2+256+2+2+2+128;
v_sectors = SIZEOF(.text) / 512;

View file

@ -23,8 +23,9 @@
;;
;; Listed Projects
;;
;; - 836 bytes: https://github.com/jart/sectorlisp
;; - 512 bytes: https://github.com/jart/sectorlisp
;; - 13 kilobytes: https://t3x.org/klisp/
;; - 47 kilobytes: https://github.com/matp/tiny-lisp
;; - 150 kilobytes: https://github.com/JeffBezanson/femtolisp
;; - Send pull request to be listed here
;;
@ -72,6 +73,7 @@ NIL
;; CORRECT RESULT OF EXPRESSION IS STILL `A`
;; REQUIRES CONS CAR CDR QUOTE ATOM EQ LAMBDA COND
;; SIMPLIFIED BUG FIXED VERSION OF JOHN MCCARTHY PAPER
;; NOTE: ((EQ (CAR E) NIL) (QUOTE *UNDEFINED)) CAN HELP
((LAMBDA (ASSOC EVCON BIND APPEND EVAL)
(EVAL (QUOTE ((LAMBDA (FF X) (FF X))
(QUOTE (LAMBDA (X)
@ -98,7 +100,6 @@ NIL
((ATOM E) (ASSOC E A))
((ATOM (CAR E))
(COND
((EQ (CAR E) NIL) (QUOTE *UNDEFINED))
((EQ (CAR E) (QUOTE QUOTE)) (CAR (CDR E)))
((EQ (CAR E) (QUOTE ATOM)) (ATOM (EVAL (CAR (CDR E)) A)))
((EQ (CAR E) (QUOTE EQ)) (EQ (EVAL (CAR (CDR E)) A)

View file

@ -1,177 +0,0 @@
#-*-mode:sed;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
#───vi: et ft=sed ts=8 tw=8 fenc=utf-8 :vi─────────────────┘
#
# SYNOPSIS
#
# sed -i -f realify.sed foo.s
#
# OVERVIEW
#
# This converts ints and longs to shorts while preserving System V ABI
# x86_64 compatibility. This works better than gcc -m16 because we can
# avoid the ASZ and OSZ prefixes in most cases while also avoiding the
# legacy 32-bit calling conventions.
# remove comments
s/[ \t][ \t]*#.*//
s/leave\(q\|\)/leavew/
s/call\(q\|\)/callw/
s/ret\(q\|\)/retw/
s/popq\t%rbp/pop\t%bp/
s/pushq\t%rbp/push\t%bp/
s/pushq\t\(.*\)/sub\t$6,%sp\n\tpush\t\1/
s/popq\t\(.*\)/pop\t\1\n\tadd\t$6,%sp/
# # preserve hardcoded stack offsets
# # bloats code size 13%
# s/leave\(q\|\)/leavew\n\tadd\t$6,%sp/
# s/call\(q\|\)\t/sub\t$6,%sp\n\tcallw\t/
# s/ret\(q\|\)/retw\t$6/
# s/pushq\t\(.*\)/sub\t$6,%sp\n\tpush\t\1/
# s/popq\t\(.*\)/pop\t\1\n\tadd\t$6,%sp/
s/, /,/g
# 32-bitify
s/rax/eax/g
s/rbx/ebx/g
s/rcx/ecx/g
s/rdx/edx/g
s/rbp/ebp/g
s/rdi/edi/g
s/rsi/esi/g
s/rsp/esp/g
# unextension
s/movswl/mov/
s/movzwl/mov/
s/movslq/mov/
s/movzlq/mov/
s/movsbl/movsbw/
# unsuffix
s/^\(\t\(fild\|fist\|fistp\|fiadd\|fisub\|fisubr\|fimul\|fidiv\|fidivr\|ficom\)\)q\t/\1\t/
s/^\(\t\(mov\|add\|adc\|cmp\|test\|lea\|sbb\|mul\|imul\|div\|idiv\|in\|out\|xor\|sub\|and\|or\|rol\|ror\|rcl\|rcr\|shl\|shr\|sal\|sar\|inc\|dec\|not\|neg\)\)l\t/\1w\t/
s/^\(\t[a-z]*\)q\t/\1w\t/
s/movsww/mov/
# remove fluff
s/mov\t%eax,%eax//
s/mov\t%ebx,%ebx//
s/mov\t%ecx,%ecx//
s/mov\t%edx,%edx//
s/mov\t%ebp,%ebp//
s/mov\t%edi,%edi//
s/mov\t%esi,%esi//
s/mov\t%esp,%esp//
# make pic absolute
s/(%rip)//
# legal real mode modrm
s/(%ebx)/(%bx)/
s/(%edi)/(%di)/
s/(%esi)/(%si)/
s/(%ebp)/(%bp)/
s/(%ebx,%esi\(,1\|\))/(%bx,%si)/
s/(%ebx,%edi\(,1\|\))/(%bx,%di)/
s/(%ebp,%esi\(,1\|\))/(%bp,%si)/
s/(%ebp,%edi\(,1\|\))/(%bp,%di)/
# we need the asz prefix
s/(%eax,%eax/(%EAX,%EAX/
s/(%eax,%ebp/(%EAX,%EBP/
s/(%eax,%ebx/(%EAX,%EBX/
s/(%eax,%ecx/(%EAX,%ECX/
s/(%eax,%edi/(%EAX,%EDI/
s/(%eax,%edx/(%EAX,%EDX/
s/(%eax,%esi/(%EAX,%ESI/
s/(%ebp,%eax/(%EBP,%EAX/
s/(%ebp,%ebp/(%EBP,%EBP/
s/(%ebp,%ebx/(%EBP,%EBX/
s/(%ebp,%ecx/(%EBP,%ECX/
s/(%ebp,%edi/(%EBP,%EDI/
s/(%ebp,%edx/(%EBP,%EDX/
s/(%ebp,%esi/(%EBP,%ESI/
s/(%ebx,%eax/(%EBX,%EAX/
s/(%ebx,%ebp/(%EBX,%EBP/
s/(%ebx,%ebx/(%EBX,%EBX/
s/(%ebx,%ecx/(%EBX,%ECX/
s/(%ebx,%edi/(%EBX,%EDI/
s/(%ebx,%edx/(%EBX,%EDX/
s/(%ebx,%esi/(%EBX,%ESI/
s/(%ecx,%eax/(%ECX,%EAX/
s/(%ecx,%ebp/(%ECX,%EBP/
s/(%ecx,%ebx/(%ECX,%EBX/
s/(%ecx,%ecx/(%ECX,%ECX/
s/(%ecx,%edi/(%ECX,%EDI/
s/(%ecx,%edx/(%ECX,%EDX/
s/(%ecx,%esi/(%ECX,%ESI/
s/(%edi,%eax/(%EDI,%EAX/
s/(%edi,%ebp/(%EDI,%EBP/
s/(%edi,%ebx/(%EDI,%EBX/
s/(%edi,%ecx/(%EDI,%ECX/
s/(%edi,%edi/(%EDI,%EDI/
s/(%edi,%edx/(%EDI,%EDX/
s/(%edi,%esi/(%EDI,%ESI/
s/(%edx,%eax/(%EDX,%EAX/
s/(%edx,%ebp/(%EDX,%EBP/
s/(%edx,%ebx/(%EDX,%EBX/
s/(%edx,%ecx/(%EDX,%ECX/
s/(%edx,%edi/(%EDX,%EDI/
s/(%edx,%edx/(%EDX,%EDX/
s/(%edx,%esi/(%EDX,%ESI/
s/(%esi,%eax/(%ESI,%EAX/
s/(%esi,%ebp/(%ESI,%EBP/
s/(%esi,%ebx/(%ESI,%EBX/
s/(%esi,%ecx/(%ESI,%ECX/
s/(%esi,%edi/(%ESI,%EDI/
s/(%esi,%edx/(%ESI,%EDX/
s/(%esi,%esi/(%ESI,%ESI/
s/(%esp,%eax/(%ESP,%EAX/
s/(%esp,%ebp/(%ESP,%EBP/
s/(%esp,%ebx/(%ESP,%EBX/
s/(%esp,%ecx/(%ESP,%ECX/
s/(%esp,%edi/(%ESP,%EDI/
s/(%esp,%edx/(%ESP,%EDX/
s/(%esp,%esi/(%ESP,%ESI/
s/(,%eax/(,%EAX/
s/(,%ebx/(,%EBX/
s/(,%ecx/(,%ECX/
s/(,%edx/(,%EDX/
s/(,%esi/(,%ESI/
s/(,%edi/(,%EDI/
s/(,%ebp/(,%EBP/
s/(%eax)/(%EAX)/
s/(%ecx)/(%ECX)/
s/(%edx)/(%EDX)/
s/(%esp)/(%ESP)/
# 16bitify
s/eax/ax/g
s/ebx/bx/g
s/ecx/cx/g
s/edx/dx/g
s/ebp/bp/g
s/edi/di/g
s/esi/si/g
s/esp/sp/g
# sigh :\
# gcc needs a flag for not using rex byte regs. workaround:
# - %dil can be avoided through copious use of STOS() macro
# - %sil can be avoided through copious use of LODS() macro
# - %bpl shouldn't be allocated due to -fno-omit-frame-pointer
# - %spl shouldn't be allocated like ever
# beyond that there's only a few cases where %dil and %sil
# need some handcoded asm() macros to workaround, for example
# if ARG1 is long and you say (ARG1 & 1) gcc will use %dil
# so just kludge it using asm("and\t$1,%0" : "+Q"(ARG1))
#s/dil/bl/g
#s/sil/bh/g
#s/spl/bl/g
#s/bpl/bh/g
# nope
s/cltq//

View file

@ -1,23 +0,0 @@
#!/bin/sh
#
# SYNOPSIS
#
# gcc -g0 -Os -wrapper realify.sh -ffixed-r{8,9,1{0,1,2,4,5}}
#
# OVERVIEW
#
# Reconfigures x86_64 compiler to emit 16-bit PC boot code.
if [ "${1##*/}" = as ]; then
for x; do
if [ "${x##*.}" = s ]; then
{
printf "\t.code16gcc"
sed -f realify.sed "$x"
} >"$x".tmp
mv -f "$x".tmp "$x"
fi
done
fi
exec "$@"

46
start.S
View file

@ -1,46 +0,0 @@
/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
.section .start,"ax",@progbits
.globl _start
.code16
_start: jmp 1f # some bios scan for short jump
1: ljmp $0x600>>4,$_begin # end of bios data roundup page
_begin: push %cs # memory model cs=ds=es = 0x600
push %cs
push %cs
pop %ds
pop %es
pop %ss
mov $0x70000>>4,%sp
cld
xor %ax,%ax
xor %di,%di
mov $0x7c00-0x600,%cx
rep stosb # clears our bss memory
xchg %di,%bx # start buffer at 07c00
inc %cx # start at first sector
xor %dh,%dh # drive dl head zero
mov $0x0200+v_sectors,%ax # read sectors
int $0x13 # disk service
// 𝑠𝑙𝑖𝑑𝑒
.section .yoink
nopw main