commit 04e5c663480809c351cc3e9668c4b72900aa4d89
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 20 Apr 2026 11:59:53 -0700
Initial commit: aarch64 hello-world via M1 + hex2
Sets up the lispcc project skeleton: PLAN.md outlining the alternative
bootstrap path (Lisp-in-M1 + C compiler in Lisp, replacing M2-Planet/
Mes/MesCC/nyacc), a hand-written aarch64 hello.M1, and a podman-driven
Makefile that builds M1/hex2 statically in an alpine+gcc builder and
runs them inside a pristine alpine:latest.
Diffstat:
| A | .gitignore | | | 1 | + |
| A | Containerfile | | | 6 | ++++++ |
| A | ELF-aarch64.hex2 | | | 75 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | Makefile | | | 85 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | PLAN.md | | | 115 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | README.md | | | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
| A | aarch64_defs.M1 | | | 229 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | hello.M1 | | | 27 | +++++++++++++++++++++++++++ |
8 files changed, 585 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+build/
diff --git a/Containerfile b/Containerfile
@@ -0,0 +1,6 @@
+# Builder image: alpine + gcc, used only to compile M1 and hex2 statically.
+# The compiled binaries are then run inside a pristine alpine:latest with
+# nothing else added.
+FROM alpine:latest
+RUN apk add --no-cache gcc musl-dev
+WORKDIR /work
diff --git a/ELF-aarch64.hex2 b/ELF-aarch64.hex2
@@ -0,0 +1,75 @@
+### Copyright (C) 2016 Jeremiah Orians
+### Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org>
+### Copyright (C) 2020 deesix <deesix@tuta.io>
+### This file is part of M2-Planet.
+###
+### M2-Planet is free software: you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation, either version 3 of the License, or
+### (at your option) any later version.
+###
+### M2-Planet is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with M2-Planet. If not, see <http://www.gnu.org/licenses/>.
+
+### stage0's hex2 format
+### !<label> 1 byte relative
+### $<label> 2 byte address
+### @<label> 2 byte relative
+### &<label> 4 byte address
+### %<label> 4 byte relative
+
+### if you wish to use this header, you need to add :ELF_end to the end of your
+### M1 or hex2 files.
+
+## ELF Header
+
+:ELF_base
+7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number
+
+02 # e_ident[EI_CLASS] Indicating 64 bit
+01 # e_ident[EI_DATA] Indicating little endianness
+01 # e_ident[EI_VERSION] Indicating original elf
+
+03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict
+00 # e_ident[EI_ABIVERSION] See above
+
+00 00 00 00 00 00 00 # e_ident[EI_PAD]
+
+02 00 # e_type Indicating Executable
+B7 00 # e_machine Indicating AArch64
+01 00 00 00 # e_version Indicating original elf
+
+&_start 00 00 00 00 # e_entry Address of the entry point
+%ELF_program_headers>ELF_base 00 00 00 00 # e_phoff Address of program header table
+00 00 00 00 00 00 00 00 # e_shoff Address of section header table
+
+00 00 00 00 # e_flags
+
+40 00 # e_ehsize Indicating our 64 Byte header
+
+38 00 # e_phentsize size of a program header table
+01 00 # e_phnum number of entries in program table
+
+00 00 # e_shentsize size of a section header table
+00 00 # e_shnum number of entries in section table
+
+00 00 # e_shstrndx index of the section names
+
+
+:ELF_program_headers
+:ELF_program_header__text
+01 00 00 00 # ph_type: PT-LOAD = 1
+07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7
+00 00 00 00 00 00 00 00 # ph_offset
+&ELF_base 00 00 00 00 # ph_vaddr
+&ELF_base 00 00 00 00 # ph_physaddr
+%ELF_end>ELF_base 00 00 00 00 # ph_filesz
+%ELF_end>ELF_base 00 00 00 00 # ph_memsz
+01 00 00 00 00 00 00 00 # ph_align
+
+:ELF_text
diff --git a/Makefile b/Makefile
@@ -0,0 +1,85 @@
+# lispcc — alternative bootstrap path: aarch64 Lisp-in-M1 → C compiler in
+# Lisp → tcc-boot. See PLAN.md.
+#
+# Two-image setup:
+# - lispcc-builder (alpine + gcc): builds M1 and hex2 statically
+# - alpine:latest (pristine): runs M1, hex2, and the assembled output
+#
+# Usage:
+# make image Build the builder image (one-time, idempotent)
+# make Build toolchain + assemble hello.M1 → build/hello
+# make run Run build/hello in pristine alpine
+# make clean Remove build/ artifacts
+
+# --- Configuration ---------------------------------------------------------
+
+HOST_ROOT := $(abspath $(CURDIR)/..)
+TOOLCHAIN_SRC := /work/live-bootstrap/seed/stage0-posix/mescc-tools
+BUILDER_IMAGE := lispcc-builder:latest
+RUNTIME_IMAGE := docker.io/library/alpine:latest
+
+# Builder: alpine + gcc, mounts the bootstrap-explore parent dir at /work
+# so we can reach upstream mescc-tools C source.
+PODMAN_BUILD := podman run --rm \
+ -v $(HOST_ROOT):/work \
+ -w /work/lispcc \
+ $(BUILDER_IMAGE)
+
+# Runtime: pristine alpine, mounts only this project dir at /work.
+PODMAN_RUN := podman run --rm \
+ -v $(CURDIR):/work \
+ -w /work \
+ $(RUNTIME_IMAGE)
+
+# Static linking so M1/hex2 have no libc dep at runtime.
+CFLAGS := -D_GNU_SOURCE -std=c99 -ggdb -fno-common -static
+
+# --- Targets ---------------------------------------------------------------
+
+.PHONY: all image toolchain run clean
+
+all: build/hello
+
+image:
+ podman build -t $(BUILDER_IMAGE) .
+
+toolchain: build/M1 build/hex2
+
+build:
+ mkdir -p build
+
+build/M1: | build
+ $(PODMAN_BUILD) gcc $(CFLAGS) \
+ $(TOOLCHAIN_SRC)/M1-macro.c \
+ $(TOOLCHAIN_SRC)/stringify.c \
+ $(TOOLCHAIN_SRC)/M2libc/bootstrappable.c \
+ -o build/M1
+
+build/hex2: | build
+ $(PODMAN_BUILD) gcc $(CFLAGS) \
+ $(TOOLCHAIN_SRC)/hex2.c \
+ $(TOOLCHAIN_SRC)/hex2_linker.c \
+ $(TOOLCHAIN_SRC)/hex2_word.c \
+ $(TOOLCHAIN_SRC)/M2libc/bootstrappable.c \
+ -o build/hex2
+
+build/hello.hex2: hello.M1 aarch64_defs.M1 build/M1
+ $(PODMAN_RUN) ./build/M1 \
+ -f aarch64_defs.M1 \
+ -f hello.M1 \
+ --little-endian --architecture aarch64 \
+ -o build/hello.hex2
+
+build/hello: build/hello.hex2 ELF-aarch64.hex2 build/hex2
+ $(PODMAN_RUN) ./build/hex2 \
+ -f ELF-aarch64.hex2 \
+ -f build/hello.hex2 \
+ --little-endian --architecture aarch64 \
+ --base-address 0x400000 \
+ -o build/hello
+
+run: build/hello
+ $(PODMAN_RUN) ./build/hello
+
+clean:
+ rm -rf build/
diff --git a/PLAN.md b/PLAN.md
@@ -0,0 +1,115 @@
+# Alternative bootstrap path: Lisp-in-M1 → C compiler in Lisp → tcc-boot
+
+## Goal
+
+Shrink the auditable LOC between M1 assembly and tcc-boot by replacing the
+current `M2-Planet → mes → MesCC → nyacc` stack with a small Lisp written
+directly in M1 asm and a C compiler written in that Lisp.
+
+## Current chain (validated counts)
+
+| Layer | Lang | Lines |
+|---|---|---|
+| `cc_amd64.M1` (subset-C compiler in M1 asm) | M1 | 5,413 (~3,152 actual instructions) |
+| M2-Planet (`*.c`, compiles mes) | C | 8,140 |
+| Mes interpreter (`src/*.c`) | C | 7,033 |
+| Mes headers (`include/mes/*.h`) | C | 6,145 |
+| MesCC + mes Scheme (`module/`) | Scheme | 8,271 |
+| Bundled mes runtime (SRFI/ice-9/rnrs shims) | Scheme | 9,191 |
+| nyacc (LALR engine + C99 parser/grammar/cpp) | Scheme | ~10,000 (essentials of 12,868) |
+| **Total auditable** | mixed | **~54,000** |
+
+## Proposed chain
+
+```
+M1 asm → Lisp interpreter (in M1 asm) → C compiler (in Lisp) → tcc-boot
+```
+
+Two languages, one new interpreter, one new compiler. No M2-Planet, no Mes
+core, no MesCC, no nyacc.
+
+## Asm Lisp — feature floor
+
+Justification: empirical audit of MesCC's actual Scheme usage. MesCC barely
+exercises Scheme.
+
+**Required:**
+- Special forms: `define`, `lambda`, `if`, `cond`, `let`, `let*`, `letrec`,
+ `quote`, `quasiquote`/`unquote`, `set!`, `begin`
+- Data: pairs, fixnums, vectors, immutable ASCII strings, symbols
+- Primitives (~40): `cons/car/cdr`, list ops (`map/filter/fold/append/reverse/member/assoc`),
+ arithmetic (`+ - * / %`), bitwise (`and or xor << >>`), string ops
+ (`string-append/string-ref/substring/string-length`), type predicates,
+ `display`/`write`, basic `format` (`~a ~s ~d ~%` only), `apply`, `error`
+- Mark-sweep GC over tagged cells
+- Built-in `pmatch` macro (otherwise hand-expanding 57 call sites in the
+ C compiler costs ~1k extra LOC)
+- A records-via-vectors layer (replaces SRFI-9 `define-immutable-record-type`)
+- File I/O: `read-file path → string` and `write-file path string`. No port
+ type at all. The C lexer indexes into the source string with an integer
+ cursor (gives `read-char`/`peek-char` semantics for free); CPP keeps
+ `#include` context as a stack of (string, cursor) pairs. Codegen
+ accumulates output as a reversed list of chunks and concatenates once
+ via a one-pass variadic `string-append` (or a `string-concat list →
+ string` primitive). Output for tcc-boot is single-digit MB — well within
+ the existing mes 20MB arena budget.
+
+**Deliberately omitted:**
+- `call/cc`, `dynamic-wind`, `parameterize`, exception system
+- Mutable strings, Unicode
+- Bignums, rationals, floats
+- `syntax-rules` / `define-syntax` (only `pmatch` macro is needed)
+- First-class modules (single-file load in dependency order)
+- `do` loops, `delay`/`force`
+
+Tail calls: convenient for AST recursion; not strictly required if stack is
+generous (≥1MB).
+
+## C subset to support
+
+Start from MesCC's already-reduced subset; consider further reductions if
+they justify patching tcc-boot.
+
+**Must support (used by tcc-boot):**
+- Types: `char/short/int/long/long long`, signed/unsigned, pointers, arrays,
+ structs, unions, enums, **bitfields**, typedefs, `void`
+- Storage: `static`, `extern`, `register`; `const`/`volatile` parsed and
+ ignored
+- Operators: full arithmetic/bitwise/relational/logical, compound
+ assignment, ternary, `sizeof` (types and expressions), casts, comma
+- Statements: all loops, switch/case, goto/labels, `&&`/`||` short-circuit
+- Function declarations: ANSI only
+
+**Not supported (and not needed):**
+- `float`/`double` (errors at parse time)
+- `inline` (parsed and stripped, like MesCC)
+- Variadic functions (tcc-boot already works around this)
+- K&R declarations
+- C99 mid-block declarations
+- Statement expressions, nested functions, compound literals,
+ designated initializers
+
+**Candidate further reductions (require tcc-boot patches):**
+- Drop bitfields (significant tcc-boot rework — probably not worth it)
+- Drop compound assignment (modest tcc-boot patches)
+
+**Preprocessor:** target full `#define`/`#include`/`#if`/`#ifdef`/`#elif`/
+`#else`/`#endif` with function-like macros and stringification. tcc's source
+uses these heavily.
+
+## Backend
+
+Emit **text M1 assembly** for x86_64. Reuse the existing M1 macro assembler
++ hex2 linker downstream (no change there). Single architecture only.
+
+## Estimated budget
+
+| Component | Lines |
+|---|---|
+| Lisp interpreter in M1 (reader, eval, GC, primitives, I/O, pmatch) | 4,000–6,000 M1 |
+| C lexer + recursive-descent parser + CPP (in Lisp) | 2,000–3,000 |
+| Type checker + IR (slimmed compile.scm + info.scm) | 2,000–3,000 |
+| x86_64 codegen + M1 emit | 800–1,200 |
+| **Total** | **~9,000–13,000 LOC** |
+
+vs. **~54,000 LOC** current = **~4–6× shrink**.
diff --git a/README.md b/README.md
@@ -0,0 +1,47 @@
+# lispcc
+
+Experimental alternative bootstrap path: a small Lisp written directly in
+M1 assembly + a C compiler written in that Lisp, replacing the
+`M2-Planet → mes → MesCC → nyacc` stack between M1 asm and tcc-boot.
+Goal is a 4–6× shrink in auditable LOC. See [PLAN.md](PLAN.md).
+
+## Status
+
+Stage 0: aarch64 hello-world in hand-written M1, assembled and run inside
+a pristine alpine container. Toolchain (M1, hex2) builds statically from
+the upstream mescc-tools C source.
+
+## Layout
+
+```
+PLAN.md design doc
+hello.M1 hand-written aarch64 hello
+aarch64_defs.M1 vendored instruction macros (from M2libc)
+ELF-aarch64.hex2 vendored ELF header (from M2libc)
+Containerfile builder image: alpine + gcc + musl-dev
+Makefile podman-driven build
+build/ outputs (M1, hex2, hello.hex2, hello)
+```
+
+## Build & run
+
+Requires podman with an arm64 Linux machine (e.g. `podman machine init`
+on Apple Silicon).
+
+```
+make image # one-time: build the alpine+gcc builder image
+make # build M1/hex2 statically + assemble hello.M1 → build/hello
+make run # run build/hello in pristine alpine, prints "Hello, world!"
+make clean # wipe build/
+```
+
+Two images are used: `lispcc-builder` (alpine+gcc, ~184 MB) only compiles
+M1/hex2; `alpine:latest` (~9 MB) runs everything, with the static binaries
+mounted in.
+
+## Source layout assumption
+
+The Makefile reaches the upstream mescc-tools C source via the parent dir
+mount (`HOST_ROOT := $(abspath $(CURDIR)/..)`), expecting
+`../live-bootstrap/seed/stage0-posix/mescc-tools/`. Override `TOOLCHAIN_SRC`
+in the Makefile if your layout differs.
diff --git a/aarch64_defs.M1 b/aarch64_defs.M1
@@ -0,0 +1,229 @@
+## Copyright (C) 2020 deesix <deesix@tuta.io>
+## Copyright (C) 2020 Sanne Wouda
+## This file is part of M2-Planet.
+##
+## M2-Planet is free software: you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## M2-Planet is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with M2-Planet. If not, see <http://www.gnu.org/licenses/>.
+
+DEFINE NULL 0000000000000000
+
+# Stack (x18 as SP, 64 bits per element)
+DEFINE PUSH_X0 408e1ff8
+DEFINE PUSH_X1 418e1ff8
+DEFINE PUSH_X13 4d8e1ff8 # str x13, [x18,-8]!
+DEFINE PUSH_X14 4e8e1ff8 # str x14, [x18,-8]!
+DEFINE PUSH_X15 4f8e1ff8 # str x15, [x18,-8]!
+DEFINE PUSH_X16 508e1ff8
+DEFINE PUSH_BP 518e1ff8
+DEFINE PUSH_LR 5e8e1ff8
+
+DEFINE POP_X0 408640f8
+DEFINE POP_X1 418640f8
+DEFINE POP_X13 4d8640f8 # ldr x13, [x18],8
+DEFINE POP_X14 4e8640f8 # ldr x14, [x18],8
+DEFINE POP_X15 4f8640f8 # ldr x15, [x18],8
+DEFINE POP_X16 508640f8
+DEFINE POP_BP 518640f8
+DEFINE POP_LR 5e8640f8
+
+DEFINE INIT_SP f2030091 # mov x18, sp
+
+
+# Jump/branch/call/return
+DEFINE BR_X16 00021fd6
+DEFINE BLR_X16 00023fd6
+DEFINE RETURN c0035fd6
+
+DEFINE CBZ_X0_PAST_BR a00000b4
+DEFINE CBNZ_X0_PAST_BR a00000b5
+
+DEFINE SKIP_INST_EQ 40000054
+DEFINE SKIP_INST_NE 41000054
+DEFINE SKIP_INST_LT 4b000054
+DEFINE SKIP_INST_LE 4d000054
+DEFINE SKIP_INST_GT 4c000054
+DEFINE SKIP_INST_GE 4a000054
+
+DEFINE SKIP_INST_LO 43000054
+DEFINE SKIP_INST_LS 49000054
+DEFINE SKIP_INST_HS 42000054
+DEFINE SKIP_INST_HI 48000054
+
+DEFINE SKIP_32_DATA 02000014
+
+
+# Load literals (PC-relative)
+DEFINE LOAD_W0_AHEAD 40000098
+DEFINE LOAD_W1_AHEAD 41000018
+DEFINE LOAD_W2_AHEAD 42000018
+DEFINE LOAD_W13_AHEAD 4d000018 ; ldr w13, 8
+DEFINE LOAD_W14_AHEAD 4e000018 ; ldr w14, 8
+DEFINE LOAD_W15_AHEAD 4f000018 ; ldr w15, 8
+DEFINE LOAD_W16_AHEAD 50000018
+
+
+# Load/store/dereference
+DEFINE LDR_X0_[SP] 400240f9
+DEFINE STR_X0_[X1] 200000f9
+DEFINE STR_W0_[X1] 200000B9
+DEFINE STRH_W0_[X1] 20000079
+DEFINE STR_BYTE_W0_[X1] 20000039
+DEFINE DEREF_X0 000040f9
+DEFINE DEREF_X1 210040f9
+DEFINE LDRH_W0_[X0] 00004079
+DEFINE LDRSB_X0_[X0] 00008039
+DEFINE LDRSH_X0_[X0] 00008079
+DEFINE LDR_W0_[X0] 000040B9
+DEFINE DEREF_X0_BYTE 00004039
+DEFINE DEREF_X1_BYTE 21004039
+
+
+# Move data between registers
+DEFINE SET_X0_FROM_BP e00311aa
+DEFINE SET_X0_FROM_X13 e0030daa # mov x0, x13
+DEFINE SET_X0_FROM_X14 e0030eaa # mov x0, x14
+DEFINE SET_X0_FROM_X15 e0030faa # mov x0, x15
+DEFINE SET_X0_FROM_X16 e00310aa # mov x0, x16
+DEFINE SET_X1_FROM_X0 e10300aa
+DEFINE SET_X1_FROM_X13 e1030daa # mov x1, x13
+DEFINE SET_X1_FROM_X14 e1030eaa # mov x1, x14
+DEFINE SET_X1_FROM_X15 e1030faa # mov x1, x15
+DEFINE SET_X1_FROM_X16 e10310aa # mov x1, x16
+DEFINE SET_X1_FROM_SP e10312aa
+DEFINE SET_X2_FROM_X0 e20300aa
+DEFINE SET_X2_FROM_X1 e20301aa
+DEFINE SET_X13_FROM_SP ed0312aa # mov x13, x18
+DEFINE SET_X14_FROM_X0 ee0300aa # mov x14, x0
+DEFINE SET_X15_FROM_X0 ef0300aa # mov x15, x0
+DEFINE SET_X3_FROM_X0 e30300aa
+DEFINE SET_X3_FROM_X1 e30301aa
+DEFINE SET_X3_FROM_X2 e30302aa
+DEFINE SET_X4_FROM_X0 e40300aa
+DEFINE SET_X5_FROM_X0 e50300aa
+DEFINE SET_X6_FROM_X0 e60300aa
+DEFINE SET_X16_FROM_X0 f00300aa
+DEFINE SET_X16_FROM_SP f00312aa
+DEFINE SET_BP_FROM_X16 f10310aa
+DEFINE SET_BP_FROM_SP f10312aa
+DEFINE SET_SP_FROM_X13 f2030daa # mov x18, x13
+DEFINE SET_SP_FROM_BP f20311aa # mov x18, x17
+
+
+# Move constant to register
+DEFINE SET_X0_TO_0 000080d2
+DEFINE SET_X0_TO_1 200080d2
+DEFINE SET_X0_TO_17 200280d2
+DEFINE SET_X0_TO_MINUS_1 00008092
+DEFINE SET_W0_TO_MINUS_1 00008012
+DEFINE SET_X1_TO_0 010080d2
+DEFINE SET_X1_TO_2 410080d2
+DEFINE SET_X1_TO_8 010180d2
+DEFINE SET_X2_TO_0 020080d2
+DEFINE SET_X2_TO_1 220080d2
+DEFINE SET_X0_TO_FCNTL_H_AT_FDCWD 600c8092
+DEFINE SET_X1_TO_FCNTL_H_AT_FDCWD 610c8092
+
+
+# Arith/logic/relational
+DEFINE ADD_X0_X1_X0 2000008b
+DEFINE ADD_X0_X14_X0 c001008b ; add x0, x14, x0
+DEFINE ADD_X0_X15_X0 e001008b ; add x0, x15, x0
+DEFINE ADD_X0_X16_X0 0020008b ; add x0, x16, x0
+DEFINE ADD_X0_BP_X0 2002008b
+DEFINE ADD_X1_SP_8 41220091
+DEFINE ADD_X1_X14_X1 c101018b ; add x1, x14, x1
+DEFINE ADD_X1_X15_X1 e101018b ; add x1, x15, x1
+DEFINE ADD_X1_X16_X1 0102018b ; add x1, x16, x1
+DEFINE ADD_SP_X14_SP d201128b ; add x18, x14, x18
+DEFINE ADD_SP_X15_SP f201128b ; add x18, x15, x18
+DEFINE ADD_SP_X16_SP 1201128b ; add x18, x16, x18
+
+DEFINE SUB_X0_X1_X0 200000cb
+DEFINE SUB_X0_X14_X0 c00100cb ; sub x0, x14, x0
+DEFINE SUB_X0_X15_X0 e00100cb ; sub x0, x15, x0
+DEFINE SUB_X0_X16_X0 000200cb ; sub x0, x16, x0
+DEFINE SUB_X0_X0_X14 00000ecb ; sub x0, x0, x14
+DEFINE SUB_X0_X0_X15 00000fcb ; sub x0, x0, x15
+DEFINE SUB_X0_X0_X16 000010cb ; sub x0, x0, x16
+DEFINE SUB_X0_X0_X1 000001cb
+DEFINE SUB_X1_X1_X14 21000ecb ; sub x1, x1, x14
+DEFINE SUB_X1_X1_X15 21000fcb ; sub x1, x1, x15
+DEFINE SUB_X1_X1_X16 210010cb ; sub x1, x1, x16
+DEFINE SUB_X1_X14_X1 c10101cb ; sub x1, x14, x1
+DEFINE SUB_X1_X15_X1 e10101cb ; sub x1, x15, x1
+DEFINE SUB_X1_X16_X1 010201cb ; sub x1, x16, x1
+DEFINE SUB_SP_X1_SP 320012cb
+DEFINE SUB_SP_X14_SP d20112cb ; sub x18, x14, x18
+DEFINE SUB_SP_X15_SP f20112cb ; sub x18, x15, x18
+DEFINE SUB_SP_X16_SP 120112cb ; sub x18, x16, x18
+DEFINE SUB_SP_SP_X14 52020ecb ; sub x18, x18, x14
+DEFINE SUB_SP_SP_X15 52020fcb ; sub x18, x18, x15
+DEFINE SUB_SP_SP_X16 520210cb ; sub x18, x18, x16
+DEFINE SUB_X0_8 002000d1
+DEFINE SUB_X0_16 004000d1
+DEFINE SUB_X0_24 006000d1
+DEFINE SUB_X0_32 008000d1
+DEFINE SUB_X0_40 00A000d1
+DEFINE MSUB_X0_X0_X2_X1 0084029b
+
+DEFINE MUL_X0_X1_X0 207c009b
+DEFINE MUL_X0_X14_X0 c07d009b
+DEFINE MUL_X0_X15_X0 e07d009b
+DEFINE MUL_X0_X16_X0 007e009b
+DEFINE SDIV_X0_X1_X0 200cc09a
+DEFINE SDIV_X2_X1_X0 220cc09a
+DEFINE UDIV_X0_X1_X0 2008c09a
+DEFINE UDIV_X2_X1_X0 2208c09a
+
+DEFINE LSHIFT_X0_X0_X2 0020c29a
+DEFINE LSHIFT_X0_X1_X0 2020c09a
+DEFINE LOGICAL_RSHIFT_X0_X1_X0 2024c09a
+DEFINE ARITH_RSHIFT_X0_X1_X0 2028c09a
+
+DEFINE MVN_X0 e00320aa
+DEFINE AND_X0_X1_X0 2000008a
+DEFINE OR_X0_X1_X0 200000aa
+DEFINE XOR_X0_X1_X0 000001ca
+
+DEFINE CMP_X1_X0 3f0000eb
+
+
+# Syscall
+DEFINE SET_X8_TO_SYS_BRK c81a80d2
+DEFINE SET_X8_TO_SYS_CHDIR 280680d2
+DEFINE SET_X8_TO_SYS_CLONE 881b80d2
+DEFINE SET_X8_TO_SYS_CLOSE 280780d2
+DEFINE SET_X8_TO_SYS_EXECVE a81b80d2
+DEFINE SET_X8_TO_SYS_EXIT a80b80d2
+DEFINE SET_X8_TO_SYS_FACCESSAT 080680d2
+DEFINE SET_X8_TO_SYS_FCHDIR 480680d2
+DEFINE SET_X8_TO_SYS_FCHMOD 880680d2
+DEFINE SET_X8_TO_SYS_FCHMODAT a80680d2
+DEFINE SET_X8_TO_SYS_GETCWD 280280d2
+DEFINE SET_X8_TO_SYS_LSEEK c80780d2
+DEFINE SET_X8_TO_SYS_MKDIRAT 480480d2
+DEFINE SET_X8_TO_SYS_MKNODAT 280480d2
+DEFINE SET_X8_TO_SYS_OPENAT 080780d2
+DEFINE SET_X8_TO_SYS_READ e80780d2
+DEFINE SET_X8_TO_SYS_UNAME 081480d2
+DEFINE SET_X8_TO_SYS_WAIT4 882080d2
+DEFINE SET_X8_TO_SYS_WRITE 080880d2
+DEFINE SET_X8_TO_SYS_UNLINKAT 680480d2
+DEFINE SET_X8_TO_SYS_SYMLINKAT 880480d2
+DEFINE SET_X8_TO_SYS_UMASK c81480d2
+DEFINE SET_X8_TO_SYS_UNSHARE 280c80d2
+DEFINE SET_X8_TO_SYS_GETEUID e81580d2
+DEFINE SET_X8_TO_SYS_GETEGID 081680d2
+DEFINE SET_X8_TO_SYS_CHROOT 680680d2
+DEFINE SET_X8_TO_SYS_MOUNT 080580d2
+DEFINE SYSCALL 010000d4
diff --git a/hello.M1 b/hello.M1
@@ -0,0 +1,27 @@
+## aarch64 Linux hello world in M1
+
+:_start
+ ;; write(1, msg, 14)
+ SET_X0_TO_1 ; x0 = 1 (stdout)
+
+ LOAD_W1_AHEAD ; ldr w1, [pc+8]
+ SKIP_32_DATA ; b +8 (over inline data)
+ &msg ; <-- 4-byte msg address loaded into w1/x1
+
+ LOAD_W2_AHEAD ; ldr w2, [pc+8]
+ SKIP_32_DATA
+ '0E000000' ; <-- length 14, little-endian, loaded into w2/x2
+
+ SET_X8_TO_SYS_WRITE ; x8 = 64
+ SYSCALL ; svc 0
+
+ ;; exit(0)
+ SET_X0_TO_0 ; x0 = 0
+ SET_X8_TO_SYS_EXIT ; x8 = 93
+ SYSCALL ; svc 0
+
+:msg
+"Hello, world!
+"
+
+:ELF_end