commit 21fd5f91cb8798e4a79b49ef028e8a307bf84e87
parent a4c301bf59b6424923e0cd84aa6ceb91533cc0b7
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 20 Jul 2026 00:20:56 -0700
rv32: bootstrap through cc.scm
Diffstat:
98 files changed, 7589 insertions(+), 605 deletions(-)
diff --git a/M1pp/M1pp.P1 b/M1pp/M1pp.P1
@@ -231,11 +231,15 @@ DEFINE OFF_macro_body_is_local_label 80632A1F00000000
la_br &err_usage
blt_a0,a2
- # Stash argv[1] and argv[2] into memory before anything clobbers a1.
- ld_t0,a1,8
+ # Stash argv[1] and argv[2] before anything clobbers a1. Native argv
+ # entries are one target word; internal M1pp records remain padded to 8.
+ li_t2 p1wordbytes
+ add_t1,a1,t2
+ ld_t0,t1,0
la_a2 &input_path
st_t0,a2,0
- ld_t0,a1,16
+ add_t1,t1,t2
+ ld_t0,t1,0
la_a2 &output_path
st_t0,a2,0
diff --git a/Makefile b/Makefile
@@ -6,7 +6,7 @@
# prep-musl.sh) script with the appropriate ARCH and DRIVER.
#
# Output layout: build/<arch>/<driver>/boot{0..7}/<artifacts>.
-# <arch> ∈ {aarch64, amd64, riscv64}
+# <arch> ∈ {aarch64, amd64, riscv64, riscv32}
# <driver> ∈ {podman, seed}
#
# Path-based builds:
@@ -31,7 +31,8 @@
ARCH ?= aarch64
DRIVER ?= podman
-ALL_ARCHES := aarch64 amd64 riscv64
+ALL_ARCHES := aarch64 amd64 riscv64 riscv32
+TCC_ARCHES := aarch64 amd64 riscv64
ALL_DRIVERS := podman seed
ifeq ($(filter $(ARCH),$(ALL_ARCHES)),)
@@ -40,15 +41,22 @@ endif
ifeq ($(filter $(DRIVER),$(ALL_DRIVERS)),)
$(error DRIVER '$(DRIVER)' not supported — use one of $(ALL_DRIVERS))
endif
+ifeq ($(ARCH):$(DRIVER),riscv32:seed)
+ $(error DRIVER=seed for riscv32 requires the intentionally unimplemented TCC/kernel stages; use DRIVER=podman)
+endif
# Per-arch metadata mirrored from boot/lib-arch.sh.
PLATFORM_aarch64 := linux/arm64
PLATFORM_amd64 := linux/amd64
PLATFORM_riscv64 := linux/riscv64
+# RV32 controller containers use the Podman server's native platform;
+# tests/Makefile and tests/run.sh resolve it only when they invoke Podman.
+PLATFORM_riscv32 := native
KERNEL_NAME_aarch64 := Image
KERNEL_NAME_amd64 := kernel.elf
KERNEL_NAME_riscv64 := kernel.elf
+KERNEL_NAME_riscv32 := tcc-target-unimplemented
MUSL_ARCH_aarch64 := aarch64
MUSL_ARCH_amd64 := x86_64
@@ -64,16 +72,25 @@ OUT_DIR := build/$(ARCH)/$(DRIVER)
# ── Top-level targets ────────────────────────────────────────────────────
+ifeq ($(filter $(ARCH),$(TCC_ARCHES)),)
+all: build/$(ARCH)/$(DRIVER)/boot2/catm \
+ build/$(ARCH)/$(DRIVER)/boot2/scheme1
+else
all: build/$(ARCH)/$(DRIVER)/boot6/$(KERNEL_NAME_$(ARCH)) \
build/$(ARCH)/$(DRIVER)/boot7/toolchain/MANIFEST.sha256
+endif
# Prepare the canonical source tree (prep-src + prep-musl) for ARCH.
+ifeq ($(filter $(ARCH),$(TCC_ARCHES)),)
+src: build/$(ARCH)/src/.stamp
+else
src: build/$(ARCH)/src/musl/.stamp
+endif
help:
@echo 'Targets (default ARCH=$(ARCH) DRIVER=$(DRIVER)):'
- @echo ' make all build boot6 kernel + boot7 toolchain'
- @echo ' make src prep canonical src/ tree (incl. musl)'
+ @echo ' make all build implemented chain (RV32: through boot2)'
+ @echo ' make src prep canonical src/ tree (RV32 omits TCC/musl)'
@echo ' make package quick: package boot2-<arch>.tar.gz from current build'
@echo ' make release validated: build + input/tar/output proofs, mint to dist/'
@echo ' make build/<arch>/<driver>/boot6/<kn> kernel branch (kn = Image | kernel.elf)'
@@ -93,16 +110,26 @@ clean:
# toolchain (which pulls in boot5 musl), so every manifest entry exists.
# Lands at build/<arch>/release/boot2-<arch>.tar.gz. Fast: no
# reproducibility or verify check.
+ifeq ($(filter $(ARCH),$(TCC_ARCHES)),)
+package:
+ @echo "package is unavailable for $(ARCH): TCC support is intentionally unimplemented; the RV32 chain ends at boot2" >&2; exit 2
+else
package: build/$(ARCH)/$(DRIVER)/boot6/$(KERNEL_NAME_$(ARCH)) \
build/$(ARCH)/$(DRIVER)/boot7/toolchain/MANIFEST.sha256
DRIVER=$(DRIVER) tools/mkrelease.sh $(ARCH)
+endif
# `release`: the validated path. Builds + packages once, regenerates
# and compares canonical inputs, repackages the sealed payload to prove
# stable tar bytes, then rebuilds from the archive and compares every
# output hash. This is the only way a tarball lands in dist/.
+ifeq ($(filter $(ARCH),$(TCC_ARCHES)),)
+release:
+ @echo "release is unavailable for $(ARCH): TCC support is intentionally unimplemented; the RV32 chain ends at boot2" >&2; exit 2
+else
release:
DRIVER=$(DRIVER) tools/release.sh $(ARCH)
+endif
# ── prep-src + boot0..boot7 chain (rules per arch × driver) ──────────────
#
@@ -145,17 +172,18 @@ prep_src_arch_srcs = \
vendor/seed/$1/hex0.hex0 vendor/seed/$1/hex1.hex0 vendor/seed/$1/hex2.hex1 \
vendor/seed/$1/catm.hex2 vendor/seed/$1/M0.hex2 \
P1/P1-$1.M1 P1/P1-$1.M1pp \
- tcc/libc/$1/start.S tcc/libc/$1/sys_stubs.S \
- $(wildcard seed-kernel/arch/$1/*) \
- $(wildcard seed-kernel/user/*) \
- vendor/musl/generated/$(MUSL_ARCH_$1)/alltypes.h \
- vendor/musl/generated/$(MUSL_ARCH_$1)/syscall.h \
- $(wildcard vendor/musl/skip-$1.txt) \
- $(shell find vendor/musl/overrides -type f 2>/dev/null) \
- $(shell find vendor/mes-libc -type f \( -name '*.c' -o -name '*.h' \) 2>/dev/null) \
- $(wildcard vendor/mes-libc/patches/*.before) \
- $(wildcard vendor/mes-libc/patches/*.after) \
- $(wildcard vendor/tcc/0.9.26.tar.gz)
+ $(if $(filter $1,$(TCC_ARCHES)), \
+ tcc/libc/$1/start.S tcc/libc/$1/sys_stubs.S \
+ $(wildcard seed-kernel/arch/$1/*) \
+ $(wildcard seed-kernel/user/*) \
+ vendor/musl/generated/$(MUSL_ARCH_$1)/alltypes.h \
+ vendor/musl/generated/$(MUSL_ARCH_$1)/syscall.h \
+ $(wildcard vendor/musl/skip-$1.txt) \
+ $(shell find vendor/musl/overrides -type f 2>/dev/null) \
+ $(shell find vendor/mes-libc -type f \( -name '*.c' -o -name '*.h' \) 2>/dev/null) \
+ $(wildcard vendor/mes-libc/patches/*.before) \
+ $(wildcard vendor/mes-libc/patches/*.after) \
+ $(wildcard vendor/tcc/0.9.26.tar.gz))
# DRIVER=seed bootN stages run scheme1 under QEMU using the podman-built
# boot6 kernel. Add that as a make dep so a clean-tree seed build pulls
@@ -287,7 +315,12 @@ build/$1/$2/boot7/toolchain/lib/crtn.o \
build/$1/$2/boot7/toolchain/MANIFEST.sha256: build/$1/$2/boot7/.stamp ;
endef
-$(foreach a,$(ALL_ARCHES),$(foreach d,$(ALL_DRIVERS),$(eval $(call BOOT_CHAIN_RULES,$a,$d))))
+BOOT_DRIVERS_aarch64 := $(ALL_DRIVERS)
+BOOT_DRIVERS_amd64 := $(ALL_DRIVERS)
+BOOT_DRIVERS_riscv64 := $(ALL_DRIVERS)
+BOOT_DRIVERS_riscv32 := podman
+
+$(foreach a,$(ALL_ARCHES),$(foreach d,$(BOOT_DRIVERS_$(a)),$(eval $(call BOOT_CHAIN_RULES,$a,$d))))
# ── Top-level catm'd cc bundle (arch-independent) ────────────────────────
#
diff --git a/P1/P1-aarch64.M1 b/P1/P1-aarch64.M1
@@ -5,6 +5,8 @@
## ---- Materialization
+DEFINE p1wordbytes 0800000000000000
+DEFINE p1wordshift 0300000000000000
DEFINE li_a0 4000005803000014
DEFINE li_a1 4100005803000014
DEFINE li_a2 4200005803000014
@@ -99,6 +101,7 @@ DEFINE add_t0,t0,a3 2901038B
DEFINE add_t0,t0,t1 29010A8B
DEFINE add_t0,t2,a3 6901038B
DEFINE add_t1,a0,t0 0A00098B
+DEFINE add_t1,a1,t2 2A000B8B
DEFINE add_t1,a3,t1 6A000A8B
DEFINE add_t1,t0,a0 2A01008B
DEFINE add_t1,t0,t1 2A010A8B
@@ -149,6 +152,7 @@ DEFINE or_t0,t0,t1 29010AAA
DEFINE xor_a2,a2,a3 420003CA
DEFINE xor_a3,a3,a2 630002CA
DEFINE shl_a2,a2,a3 4220C39A
+DEFINE shl_t2,t0,t2 2B21CB9A
DEFINE sar_a2,a2,a3 4228C39A
DEFINE mul_a0,a0,a3 007C039B
DEFINE mul_a0,a0,t0 007C099B
@@ -301,8 +305,6 @@ DEFINE ld_t0,a0,8 090440F9
DEFINE ld_t0,a0,16 090840F9
DEFINE ld_t0,a0,24 090C40F9
DEFINE ld_t0,a1,0 290040F9
-DEFINE ld_t0,a1,8 290440F9
-DEFINE ld_t0,a1,16 290840F9
DEFINE ld_t0,a1,24 290C40F9
DEFINE ld_t0,a1,32 291040F9
DEFINE ld_t0,a1,40 291440F9
@@ -311,6 +313,7 @@ DEFINE ld_t0,a1,64 292040F9
DEFINE ld_t0,a2,0 490040F9
DEFINE ld_t0,a3,0 690040F9
DEFINE ld_t0,t0,0 290140F9
+DEFINE ld_t0,t1,0 490140F9
DEFINE ld_t0,t2,0 690140F9
DEFINE ld_t0,t2,16 690940F9
DEFINE ld_t0,sp,0 E90B40F9
diff --git a/P1/P1-aarch64.M1pp b/P1/P1-aarch64.M1pp
@@ -1,4 +1,21 @@
# aarch64.M1M -- P1 aarch64 backend expressed in m1macro.
+
+# Width hooks consumed by the portable P1pp support library.
+%macro p1_word_bytes()
+8
+%endm
+%macro p1_word_bits()
+64
+%endm
+%macro p1_word_shift()
+3
+%endm
+%macro p1_scheme_heap_bytes()
+0x10000000
+%endm
+%macro p1_waitid_status_off()
+24
+%endm
#
# This mirrors p1/aarch64.py using the m1macro integer builtins:
# %(sexpr), $(sexpr), and %select(cond, then, else).
@@ -638,6 +655,14 @@
%macro p1_sys_lseek()
62
%endm
+%macro p1_sys_lseek_wrapper()
+%mov(a3, a2)
+%mov(a2, a1)
+%mov(a1, a0)
+%li(a0, %p1_sys_lseek)
+%syscall
+%ret
+%endm
%macro p1_sys_brk()
214
%endm
diff --git a/P1/P1-amd64.M1 b/P1/P1-amd64.M1
@@ -5,6 +5,8 @@
## ---- Materialization
+DEFINE p1wordbytes 0800000000000000
+DEFINE p1wordshift 0300000000000000
DEFINE li_a0 48BF
DEFINE li_a1 48BE
DEFINE li_a2 48BA
@@ -99,6 +101,7 @@ DEFINE add_t0,t0,a3 4D89D24901CA
DEFINE add_t0,t0,t1 4D89D24D01DA
DEFINE add_t0,t2,a3 4D89C24901CA
DEFINE add_t1,a0,t0 4989FB4D01D3
+DEFINE add_t1,a1,t2 4989F34D01C3
DEFINE add_t1,a3,t1 4D89D94989CB4D01CB
DEFINE add_t1,t0,a0 4D89D34901FB
DEFINE add_t1,t0,t1 4D89D94D89D34D01CB
@@ -149,6 +152,7 @@ DEFINE or_t0,t0,t1 4D89D24D09DA
DEFINE xor_a2,a2,a3 4889D24831CA
DEFINE xor_a3,a3,a2 4889C94831D1
DEFINE shl_a2,a2,a3 4889CD4989D14889C949D3E14889E94C89CA
+DEFINE shl_t2,t0,t2 4889CD4D89D14C89C149D3E14889E94D89C8
DEFINE sar_a2,a2,a3 4889CD4989D14889C949D3F94889E94C89CA
DEFINE mul_a0,a0,a3 4889FF480FAFF9
DEFINE mul_a0,a0,t0 4889FF490FAFFA
@@ -301,8 +305,6 @@ DEFINE ld_t0,a0,8 4C8B5708
DEFINE ld_t0,a0,16 4C8B5710
DEFINE ld_t0,a0,24 4C8B5718
DEFINE ld_t0,a1,0 4C8B5600
-DEFINE ld_t0,a1,8 4C8B5608
-DEFINE ld_t0,a1,16 4C8B5610
DEFINE ld_t0,a1,24 4C8B5618
DEFINE ld_t0,a1,32 4C8B5620
DEFINE ld_t0,a1,40 4C8B5628
@@ -311,6 +313,7 @@ DEFINE ld_t0,a1,64 4C8B5640
DEFINE ld_t0,a2,0 4C8B5200
DEFINE ld_t0,a3,0 4C8B5100
DEFINE ld_t0,t0,0 4D8B5200
+DEFINE ld_t0,t1,0 4D8B5300
DEFINE ld_t0,t2,0 4D8B5000
DEFINE ld_t0,t2,16 4D8B5010
DEFINE ld_t0,sp,0 4C8B542410
diff --git a/P1/P1-amd64.M1pp b/P1/P1-amd64.M1pp
@@ -1,4 +1,21 @@
# P1-amd64.M1pp -- P1 amd64 backend expressed in m1macro.
+
+# Width hooks consumed by the portable P1pp support library.
+%macro p1_word_bytes()
+8
+%endm
+%macro p1_word_bits()
+64
+%endm
+%macro p1_word_shift()
+3
+%endm
+%macro p1_scheme_heap_bytes()
+0x10000000
+%endm
+%macro p1_waitid_status_off()
+24
+%endm
#
# Mirrors p1/P1-aarch64.M1pp. Native register mapping is backend-private;
# see the amd_reg_* table below. amd64 is variable-length, so every op
@@ -945,6 +962,14 @@ $(imm)
%macro p1_sys_lseek()
8
%endm
+%macro p1_sys_lseek_wrapper()
+%mov(a3, a2)
+%mov(a2, a1)
+%mov(a1, a0)
+%li(a0, %p1_sys_lseek)
+%syscall
+%ret
+%endm
%macro p1_sys_brk()
12
%endm
diff --git a/P1/P1-riscv32.M1 b/P1/P1-riscv32.M1
@@ -0,0 +1,613 @@
+## riscv32.M1 — GENERATED by p1/gen/p1_gen.py. Do not edit by hand.
+##
+## This table targets the P1 ISA described in docs/P1.md.
+## Row shapes are shared; per-arch lowering lives in p1/gen/<arch>.py.
+
+
+## ---- Materialization
+DEFINE p1wordbytes 0400000000000000
+DEFINE p1wordshift 0200000000000000
+DEFINE li_a0 170500000325C5006F00C000
+DEFINE li_a1 9705000083A5C5006F00C000
+DEFINE li_a2 170600000326C6006F00C000
+DEFINE li_a3 9706000083A6C6006F00C000
+DEFINE li_t0 9702000083A2C2006F00C000
+DEFINE li_t1 170300000323C3006F00C000
+DEFINE li_t2 9703000083A3C3006F00C000
+DEFINE la_a0 170500000325C5006F008000
+DEFINE la_a1 9705000083A5C5006F008000
+DEFINE la_a2 170600000326C6006F008000
+DEFINE la_a3 9706000083A6C6006F008000
+DEFINE la_t0 9702000083A2C2006F008000
+DEFINE la_t1 170300000323C3006F008000
+DEFINE la_t2 9703000083A3C3006F008000
+DEFINE la_br 970F000083AFCF006F008000
+
+## ---- Moves
+DEFINE mov_a0,a1 13850500
+DEFINE mov_a0,a3 13850600
+DEFINE mov_a0,t0 13850200
+DEFINE mov_a0,t1 13050300
+DEFINE mov_a1,a0 93050500
+DEFINE mov_a1,t0 93850200
+DEFINE mov_a1,t1 93050300
+DEFINE mov_a1,t2 93850300
+DEFINE mov_a2,a1 13860500
+DEFINE mov_a2,t0 13860200
+DEFINE mov_a2,t1 13060300
+DEFINE mov_t0,a0 93020500
+DEFINE mov_t1,a0 13030500
+DEFINE mov_t1,t0 13830200
+DEFINE mov_t2,a0 93030500
+DEFINE mov_t2,t1 93030300
+
+## ---- Register Arithmetic
+DEFINE add_a0,a0,a1 3305B500
+DEFINE add_a0,a0,a2 3305C500
+DEFINE add_a0,a0,a3 3305D500
+DEFINE add_a0,a0,t0 33055500
+DEFINE add_a0,a0,t1 33056500
+DEFINE add_a0,a2,a0 3305A600
+DEFINE add_a0,a2,t2 33057600
+DEFINE add_a0,t0,a3 3385D200
+DEFINE add_a0,t1,a0 3305A300
+DEFINE add_a0,t1,t2 33057300
+DEFINE add_a0,t2,a1 3385B300
+DEFINE add_a1,a0,a1 B305B500
+DEFINE add_a1,a0,a3 B305D500
+DEFINE add_a1,a1,a0 B385A500
+DEFINE add_a1,a1,a2 B385C500
+DEFINE add_a1,a1,a3 B385D500
+DEFINE add_a1,a1,t0 B3855500
+DEFINE add_a1,a1,t2 B3857500
+DEFINE add_a1,a2,t0 B3055600
+DEFINE add_a1,a3,a1 B385B600
+DEFINE add_a1,t0,a0 B385A200
+DEFINE add_a1,t2,a1 B385B300
+DEFINE add_a1,t2,a3 B385D300
+DEFINE add_a1,t2,t1 B3856300
+DEFINE add_a2,a1,a3 3386D500
+DEFINE add_a2,a1,t0 33865500
+DEFINE add_a2,a2,a0 3306A600
+DEFINE add_a2,a2,a1 3306B600
+DEFINE add_a2,a2,a3 3306D600
+DEFINE add_a2,a2,t0 33065600
+DEFINE add_a2,a2,t1 33066600
+DEFINE add_a2,a2,t2 33067600
+DEFINE add_a2,a3,a1 3386B600
+DEFINE add_a2,a3,a2 3386C600
+DEFINE add_a2,t0,t1 33866200
+DEFINE add_a2,t2,a0 3386A300
+DEFINE add_a2,t2,a2 3386C300
+DEFINE add_a3,a0,a2 B306C500
+DEFINE add_a3,a1,t1 B3866500
+DEFINE add_a3,a1,t2 B3867500
+DEFINE add_a3,a2,t1 B3066600
+DEFINE add_a3,a3,a0 B386A600
+DEFINE add_a3,a3,a1 B386B600
+DEFINE add_a3,a3,a2 B386C600
+DEFINE add_a3,a3,t0 B3865600
+DEFINE add_a3,a3,t1 B3866600
+DEFINE add_a3,a3,t2 B3867600
+DEFINE add_a3,t0,t2 B3867200
+DEFINE add_a3,t1,a2 B306C300
+DEFINE add_t0,a1,t2 B3827500
+DEFINE add_t0,a2,a3 B302D600
+DEFINE add_t0,a3,a1 B382B600
+DEFINE add_t0,a3,t0 B3825600
+DEFINE add_t0,t0,a0 B382A200
+DEFINE add_t0,t0,a1 B382B200
+DEFINE add_t0,t0,a3 B382D200
+DEFINE add_t0,t0,t1 B3826200
+DEFINE add_t0,t2,a3 B382D300
+DEFINE add_t1,a0,t0 33035500
+DEFINE add_t1,a1,t2 33837500
+DEFINE add_t1,a3,t1 33836600
+DEFINE add_t1,t0,a0 3383A200
+DEFINE add_t1,t0,t1 33836200
+DEFINE add_t1,t1,a2 3303C300
+DEFINE add_t1,t1,t0 33035300
+DEFINE add_t1,t1,t2 33037300
+DEFINE add_t2,a0,t0 B3035500
+DEFINE add_t2,a0,t1 B3036500
+DEFINE add_t2,a1,t2 B3837500
+DEFINE add_t2,a2,t1 B3036600
+DEFINE add_t2,t0,t1 B3836200
+DEFINE add_t2,t1,t0 B3035300
+DEFINE add_t2,t1,t2 B3037300
+DEFINE add_t2,t2,a0 B383A300
+DEFINE add_t2,t2,a3 B383D300
+DEFINE add_t2,t2,t1 B3836300
+DEFINE sub_a0,a0,t1 33056540
+DEFINE sub_a0,a1,a0 3385A540
+DEFINE sub_a0,a1,t2 33857540
+DEFINE sub_a0,a3,a0 3385A640
+DEFINE sub_a0,t0,a1 3385B240
+DEFINE sub_a0,t0,t1 33856240
+DEFINE sub_a0,t1,a0 3305A340
+DEFINE sub_a1,t0,a0 B385A240
+DEFINE sub_a2,a1,a0 3386A540
+DEFINE sub_a2,a2,a2 3306C640
+DEFINE sub_a2,a2,t0 33065640
+DEFINE sub_a2,t0,t1 33866240
+DEFINE sub_a2,t2,a3 3386D340
+DEFINE sub_a3,a1,a2 B386C540
+DEFINE sub_a3,a3,a2 B386C640
+DEFINE sub_a3,t0,a2 B386C240
+DEFINE sub_a3,t0,a3 B386D240
+DEFINE sub_a3,t1,a2 B306C340
+DEFINE sub_a3,t1,a3 B306D340
+DEFINE sub_a3,t1,t0 B3065340
+DEFINE sub_a3,t2,t1 B3866340
+DEFINE sub_t0,a1,a2 B382C540
+DEFINE sub_t0,t0,a1 B382B240
+DEFINE sub_t0,t0,t1 B3826240
+DEFINE sub_t0,t1,t0 B3025340
+DEFINE sub_t1,t1,t0 33035340
+DEFINE sub_t2,t1,t0 B3035340
+DEFINE and_a3,a3,a2 B3F6C600
+DEFINE or_a0,a0,a2 3365C500
+DEFINE or_a3,a3,a2 B3E6C600
+DEFINE or_t0,t0,t1 B3E26200
+DEFINE xor_a2,a2,a3 3346D600
+DEFINE xor_a3,a3,a2 B3C6C600
+DEFINE shl_a2,a2,a3 3316D600
+DEFINE shl_t2,t0,t2 B3937200
+DEFINE sar_a2,a2,a3 3356D640
+DEFINE mul_a0,a0,a3 3305D502
+DEFINE mul_a0,a0,t0 33055502
+DEFINE mul_a0,t1,t2 33057302
+DEFINE mul_a1,t0,t1 B3856202
+DEFINE mul_a2,a2,t1 33066602
+DEFINE mul_a3,a3,a2 B386C602
+DEFINE mul_t0,t0,a1 B382B202
+DEFINE mul_t0,t0,t1 B3826202
+DEFINE mul_t2,t0,a2 B383C202
+DEFINE div_a0,a0,a1 3345B502
+DEFINE div_a0,a0,t1 33456502
+DEFINE div_a2,a2,a3 3346D602
+DEFINE rem_a2,a0,a1 3366B502
+DEFINE rem_a2,a0,t1 33666502
+DEFINE rem_a2,a2,a3 3366D602
+DEFINE rem_a2,t0,a1 33E6B202
+
+## ---- Immediate Arithmetic
+DEFINE addi_a0,a0,neg1 1305F5FF
+DEFINE addi_a0,a0,1 13051500
+DEFINE addi_a0,a0,32 13050502
+DEFINE addi_a1,a1,neg32 938505FE
+DEFINE addi_a1,a1,neg2 9385E5FF
+DEFINE addi_a1,a1,1 93851500
+DEFINE addi_a1,a1,2 93852500
+DEFINE addi_a1,a1,3 93853500
+DEFINE addi_a1,t0,1 93851200
+DEFINE addi_a2,a0,1 13061500
+DEFINE addi_a2,a2,neg1 1306F6FF
+DEFINE addi_a2,a2,1 13061600
+DEFINE addi_a2,a2,2 13062600
+DEFINE addi_a2,a2,3 13063600
+DEFINE addi_a2,a2,8 13068600
+DEFINE addi_a2,a2,24 13068601
+DEFINE addi_a2,a2,48 13060603
+DEFINE addi_a2,t0,1 13861200
+DEFINE addi_a2,t0,32 13860202
+DEFINE addi_a2,t2,neg48 138603FD
+DEFINE addi_a3,a3,neg1 9386F6FF
+DEFINE addi_a3,a3,1 93861600
+DEFINE addi_a3,a3,32 93860602
+DEFINE addi_a3,t0,32 93860202
+DEFINE addi_a3,t1,32 93060302
+DEFINE addi_t0,a0,32 93020502
+DEFINE addi_t0,a1,neg32 938205FE
+DEFINE addi_t0,a1,32 93820502
+DEFINE addi_t0,a3,neg1 9382F6FF
+DEFINE addi_t0,t0,neg1 9382F2FF
+DEFINE addi_t0,t0,1 93821200
+DEFINE addi_t0,t0,2 93822200
+DEFINE addi_t0,t0,7 93827200
+DEFINE addi_t0,t0,32 93820202
+DEFINE addi_t1,t0,32 13830202
+DEFINE addi_t1,t1,neg32 130303FE
+DEFINE addi_t1,t1,neg1 1303F3FF
+DEFINE addi_t1,t1,1 13031300
+DEFINE addi_t1,t1,16 13030301
+DEFINE addi_t1,t1,32 13030302
+DEFINE addi_t2,a1,neg32 938305FE
+DEFINE addi_t2,t0,neg32 938302FE
+DEFINE addi_t2,t0,32 93830202
+DEFINE addi_t2,t1,2 93032300
+DEFINE addi_t2,t2,neg1 9383F3FF
+DEFINE addi_t2,t2,1 93831300
+DEFINE addi_t2,t2,7 93837300
+DEFINE addi_t2,t2,24 93838301
+DEFINE addi_t2,t2,32 93830302
+DEFINE andi_a0,a0,255 1375F50F
+DEFINE andi_a2,a2,15 1376F600
+DEFINE andi_a3,a3,15 93F6F600
+DEFINE andi_a3,a3,255 93F6F60F
+DEFINE andi_a3,t2,255 93F6F30F
+DEFINE shli_a0,a0,3 13153500
+DEFINE shli_a0,a0,4 13154500
+DEFINE shli_a1,a1,1 93951500
+DEFINE shli_a2,a2,3 13163600
+DEFINE shli_a2,t1,3 13163300
+DEFINE shli_a3,t0,1 93961200
+DEFINE shli_a3,t0,4 93964200
+DEFINE shli_t0,t0,4 93924200
+DEFINE shli_t1,a2,3 13133600
+DEFINE shli_t1,t1,3 13133300
+DEFINE shli_t2,t0,3 93933200
+DEFINE shri_a0,a0,5 13555500
+DEFINE shri_a2,a3,4 13D64600
+DEFINE shri_t2,t2,8 93D38300
+
+## ---- Memory
+DEFINE ld_a0,a0,0 03250500
+DEFINE ld_a0,a0,8 03258500
+DEFINE ld_a0,a0,16 03250501
+DEFINE ld_a0,a1,0 03A50500
+DEFINE ld_a0,a1,16 03A50501
+DEFINE ld_a0,a2,0 03250600
+DEFINE ld_a0,a2,24 03258601
+DEFINE ld_a0,a3,0 03A50600
+DEFINE ld_a0,a3,8 03A58600
+DEFINE ld_a0,a3,16 03A50601
+DEFINE ld_a0,a3,24 03A58601
+DEFINE ld_a0,t0,0 03A50200
+DEFINE ld_a0,t0,8 03A58200
+DEFINE ld_a0,t0,16 03A50201
+DEFINE ld_a0,sp,0 03258100
+DEFINE ld_a0,sp,8 03250101
+DEFINE ld_a0,sp,24 03250102
+DEFINE ld_a1,a0,0 83250500
+DEFINE ld_a1,a0,8 83258500
+DEFINE ld_a1,a0,16 83250501
+DEFINE ld_a1,a1,0 83A50500
+DEFINE ld_a1,a1,8 83A58500
+DEFINE ld_a1,a2,8 83258600
+DEFINE ld_a1,a3,8 83A58600
+DEFINE ld_a1,t0,0 83A50200
+DEFINE ld_a1,t0,8 83A58200
+DEFINE ld_a1,t0,16 83A50201
+DEFINE ld_a1,t0,24 83A58201
+DEFINE ld_a1,t1,0 83250300
+DEFINE ld_a1,t1,24 83258301
+DEFINE ld_a1,t2,16 83A50301
+DEFINE ld_a1,sp,0 83258100
+DEFINE ld_a1,sp,8 83250101
+DEFINE ld_a2,a0,0 03260500
+DEFINE ld_a2,a0,16 03260501
+DEFINE ld_a2,a0,24 03268501
+DEFINE ld_a2,a1,0 03A60500
+DEFINE ld_a2,a2,0 03260600
+DEFINE ld_a2,t0,0 03A60200
+DEFINE ld_a2,t0,8 03A68200
+DEFINE ld_a2,t0,16 03A60201
+DEFINE ld_a2,t0,24 03A68201
+DEFINE ld_a2,t1,neg32 032603FE
+DEFINE ld_a2,t1,0 03260300
+DEFINE ld_a2,t2,0 03A60300
+DEFINE ld_a2,sp,16 03268101
+DEFINE ld_a3,a0,0 83260500
+DEFINE ld_a3,a0,8 83268500
+DEFINE ld_a3,a0,16 83260501
+DEFINE ld_a3,a1,0 83A60500
+DEFINE ld_a3,a1,8 83A68500
+DEFINE ld_a3,a2,0 83260600
+DEFINE ld_a3,a3,0 83A60600
+DEFINE ld_a3,t0,0 83A60200
+DEFINE ld_a3,t0,8 83A68200
+DEFINE ld_a3,t0,16 83A60201
+DEFINE ld_a3,t0,24 83A68201
+DEFINE ld_a3,t1,8 83268300
+DEFINE ld_t0,a0,0 83220500
+DEFINE ld_t0,a0,8 83228500
+DEFINE ld_t0,a0,16 83220501
+DEFINE ld_t0,a0,24 83228501
+DEFINE ld_t0,a1,0 83A20500
+DEFINE ld_t0,a1,24 83A28501
+DEFINE ld_t0,a1,32 83A20502
+DEFINE ld_t0,a1,40 83A28502
+DEFINE ld_t0,a1,48 83A20503
+DEFINE ld_t0,a1,64 83A20504
+DEFINE ld_t0,a2,0 83220600
+DEFINE ld_t0,a3,0 83A20600
+DEFINE ld_t0,t0,0 83A20200
+DEFINE ld_t0,t1,0 83220300
+DEFINE ld_t0,t2,0 83A20300
+DEFINE ld_t0,t2,16 83A20301
+DEFINE ld_t0,sp,0 83228100
+DEFINE ld_t0,sp,8 83220101
+DEFINE ld_t0,sp,16 83228101
+DEFINE ld_t0,sp,24 83220102
+DEFINE ld_t0,sp,32 83228102
+DEFINE ld_t0,sp,40 83220103
+DEFINE ld_t1,a0,0 03230500
+DEFINE ld_t1,a0,8 03238500
+DEFINE ld_t1,a0,16 03230501
+DEFINE ld_t1,a0,24 03238501
+DEFINE ld_t1,a1,0 03A30500
+DEFINE ld_t1,a1,8 03A38500
+DEFINE ld_t1,a1,16 03A30501
+DEFINE ld_t1,a1,56 03A38503
+DEFINE ld_t1,a2,0 03230600
+DEFINE ld_t1,a2,8 03238600
+DEFINE ld_t1,a2,16 03230601
+DEFINE ld_t1,a3,0 03A30600
+DEFINE ld_t1,a3,8 03A38600
+DEFINE ld_t1,t0,0 03A30200
+DEFINE ld_t1,t0,16 03A30201
+DEFINE ld_t1,t1,0 03230300
+DEFINE ld_t1,t1,16 03230301
+DEFINE ld_t1,sp,8 03230101
+DEFINE ld_t1,sp,32 03238102
+DEFINE ld_t1,sp,48 03238103
+DEFINE ld_t2,a0,0 83230500
+DEFINE ld_t2,a1,0 83A30500
+DEFINE ld_t2,a3,0 83A30600
+DEFINE ld_t2,a3,16 83A30601
+DEFINE ld_t2,t0,0 83A30200
+DEFINE ld_t2,t0,8 83A38200
+DEFINE ld_t2,t0,24 83A38201
+DEFINE ld_t2,t1,0 83230300
+DEFINE ld_t2,t1,8 83238300
+DEFINE ld_t2,t1,16 83230301
+DEFINE ld_t2,t2,0 83A30300
+DEFINE ld_t2,sp,16 83238101
+DEFINE st_a0,a1,0 23A0A500
+DEFINE st_a0,a2,0 2320A600
+DEFINE st_a0,a2,8 2324A600
+DEFINE st_a0,a2,16 2328A600
+DEFINE st_a0,a2,24 232CA600
+DEFINE st_a0,a3,0 23A0A600
+DEFINE st_a0,a3,8 23A4A600
+DEFINE st_a0,a3,16 23A8A600
+DEFINE st_a0,t0,0 23A0A200
+DEFINE st_a0,t0,8 23A4A200
+DEFINE st_a0,t1,0 2320A300
+DEFINE st_a0,t2,0 23A0A300
+DEFINE st_a0,t2,8 23A4A300
+DEFINE st_a0,t2,16 23A8A300
+DEFINE st_a0,sp,0 2324A100
+DEFINE st_a0,sp,16 232CA100
+DEFINE st_a1,a0,0 2320B500
+DEFINE st_a1,a2,0 2320B600
+DEFINE st_a1,a2,16 2328B600
+DEFINE st_a1,a2,24 232CB600
+DEFINE st_a1,a3,0 23A0B600
+DEFINE st_a1,a3,8 23A4B600
+DEFINE st_a1,t0,0 23A0B200
+DEFINE st_a1,t0,8 23A4B200
+DEFINE st_a1,t0,16 23A8B200
+DEFINE st_a1,t0,24 23ACB200
+DEFINE st_a1,t1,0 2320B300
+DEFINE st_a1,t1,8 2324B300
+DEFINE st_a1,t1,16 2328B300
+DEFINE st_a1,t1,24 232CB300
+DEFINE st_a1,t2,16 23A8B300
+DEFINE st_a1,sp,8 2328B100
+DEFINE st_a1,sp,16 232CB100
+DEFINE st_a2,a0,0 2320C500
+DEFINE st_a2,a1,0 23A0C500
+DEFINE st_a2,a3,0 23A0C600
+DEFINE st_a2,a3,16 23A8C600
+DEFINE st_a2,a3,24 23ACC600
+DEFINE st_a2,a3,32 23A0C602
+DEFINE st_a2,t0,0 23A0C200
+DEFINE st_a2,t0,16 23A8C200
+DEFINE st_a2,t1,0 2320C300
+DEFINE st_a2,t2,0 23A0C300
+DEFINE st_a3,a0,0 2320D500
+DEFINE st_a3,a1,0 23A0D500
+DEFINE st_a3,a2,0 2320D600
+DEFINE st_a3,t0,24 23ACD200
+DEFINE st_a3,t1,0 2320D300
+DEFINE st_a3,t1,8 2324D300
+DEFINE st_a3,t2,0 23A0D300
+DEFINE st_a3,t2,8 23A4D300
+DEFINE st_a3,t2,16 23A8D300
+DEFINE st_a3,t2,24 23ACD300
+DEFINE st_t0,a0,0 23205500
+DEFINE st_t0,a0,8 23245500
+DEFINE st_t0,a0,16 23285500
+DEFINE st_t0,a0,24 232C5500
+DEFINE st_t0,a1,0 23A05500
+DEFINE st_t0,a1,8 23A45500
+DEFINE st_t0,a1,16 23A85500
+DEFINE st_t0,a1,24 23AC5500
+DEFINE st_t0,a1,32 23A05502
+DEFINE st_t0,a1,40 23A45502
+DEFINE st_t0,a1,48 23A85502
+DEFINE st_t0,a1,56 23AC5502
+DEFINE st_t0,a1,64 23A05504
+DEFINE st_t0,a2,0 23205600
+DEFINE st_t0,a3,0 23A05600
+DEFINE st_t0,t1,0 23205300
+DEFINE st_t0,t2,0 23A05300
+DEFINE st_t0,sp,0 23245100
+DEFINE st_t0,sp,8 23285100
+DEFINE st_t0,sp,16 232C5100
+DEFINE st_t0,sp,24 23205102
+DEFINE st_t0,sp,32 23245102
+DEFINE st_t0,sp,40 23285102
+DEFINE st_t0,sp,48 232C5102
+DEFINE st_t1,a0,0 23206500
+DEFINE st_t1,a0,24 232C6500
+DEFINE st_t1,a0,56 232C6502
+DEFINE st_t1,a1,0 23A06500
+DEFINE st_t1,a2,0 23206600
+DEFINE st_t1,a3,0 23A06600
+DEFINE st_t1,a3,8 23A46600
+DEFINE st_t1,a3,16 23A86600
+DEFINE st_t1,a3,24 23AC6600
+DEFINE st_t1,t0,0 23A06200
+DEFINE st_t2,a0,0 23207500
+DEFINE st_t2,a1,0 23A07500
+DEFINE st_t2,a2,0 23207600
+DEFINE st_t2,a3,0 23A07600
+DEFINE st_t2,t0,0 23A07200
+DEFINE st_t2,t1,0 23207300
+DEFINE lb_a0,a0,0 03450500
+DEFINE lb_a0,a1,0 03C50500
+DEFINE lb_a0,a2,0 03450600
+DEFINE lb_a0,t0,0 03C50200
+DEFINE lb_a1,a1,0 83C50500
+DEFINE lb_a1,a2,0 83450600
+DEFINE lb_a1,a2,1 83451600
+DEFINE lb_a2,a1,0 03C60500
+DEFINE lb_a2,a2,0 03460600
+DEFINE lb_a2,t0,0 03C60200
+DEFINE lb_a3,a0,0 83460500
+DEFINE lb_a3,a1,0 83C60500
+DEFINE lb_a3,a2,0 83460600
+DEFINE lb_a3,a3,0 83C60600
+DEFINE lb_a3,t2,0 83C60300
+DEFINE lb_a3,t2,1 83C61300
+DEFINE lb_t0,a0,0 83420500
+DEFINE lb_t0,a0,1 83421500
+DEFINE lb_t0,t0,0 83C20200
+DEFINE lb_t1,t1,0 03430300
+DEFINE lb_t2,a2,0 83430600
+DEFINE lb_t2,t0,0 83C30200
+DEFINE lb_t2,t2,0 83C30300
+DEFINE sb_a0,a1,0 2380A500
+DEFINE sb_a0,a2,0 2300A600
+DEFINE sb_a0,a3,0 2380A600
+DEFINE sb_a0,t2,0 2380A300
+DEFINE sb_a1,a2,0 2300B600
+DEFINE sb_a1,t0,0 2380B200
+DEFINE sb_a2,a1,0 2380C500
+DEFINE sb_a2,a3,0 2380C600
+DEFINE sb_a2,t2,0 2380C300
+DEFINE sb_a3,a0,0 2300D500
+DEFINE sb_a3,a1,0 2380D500
+DEFINE sb_a3,a2,0 2300D600
+DEFINE sb_a3,t2,0 2380D300
+DEFINE sb_t0,a3,0 23805600
+DEFINE sb_t1,a0,0 23006500
+DEFINE sb_t1,a2,0 23006600
+DEFINE sb_t2,a1,0 23807500
+DEFINE sb_t2,a2,0 23007600
+
+## ---- ABI Access
+
+## ---- Branches
+DEFINE b 67800F00
+DEFINE beq_a0,a1 6314B50067800F00
+DEFINE beq_a0,t0 6314550067800F00
+DEFINE beq_a0,t1 6314650067800F00
+DEFINE beq_a1,a0 6394A50067800F00
+DEFINE beq_a1,a2 6394C50067800F00
+DEFINE beq_a2,a1 6314B60067800F00
+DEFINE beq_a2,a3 6314D60067800F00
+DEFINE beq_a3,a0 6394A60067800F00
+DEFINE beq_a3,a1 6394B60067800F00
+DEFINE beq_a3,a2 6394C60067800F00
+DEFINE beq_a3,t0 6394560067800F00
+DEFINE beq_a3,t1 6394660067800F00
+DEFINE beq_t0,a0 6394A20067800F00
+DEFINE beq_t0,a1 6394B20067800F00
+DEFINE beq_t0,a2 6394C20067800F00
+DEFINE beq_t0,t1 6394620067800F00
+DEFINE beq_t0,t2 6394720067800F00
+DEFINE beq_t1,a0 6314A30067800F00
+DEFINE beq_t1,a1 6314B30067800F00
+DEFINE beq_t1,a2 6314C30067800F00
+DEFINE beq_t1,t0 6314530067800F00
+DEFINE beq_t1,t2 6314730067800F00
+DEFINE beq_t2,a2 6394C30067800F00
+DEFINE beq_t2,a3 6394D30067800F00
+DEFINE beq_t2,t1 6394630067800F00
+DEFINE bne_a0,a1 6304B50067800F00
+DEFINE bne_a0,t0 6304550067800F00
+DEFINE bne_a0,t1 6304650067800F00
+DEFINE bne_a1,a0 6384A50067800F00
+DEFINE bne_a1,a2 6384C50067800F00
+DEFINE bne_a1,t0 6384550067800F00
+DEFINE bne_a2,a1 6304B60067800F00
+DEFINE bne_a2,a3 6304D60067800F00
+DEFINE bne_a3,a0 6384A60067800F00
+DEFINE bne_a3,a1 6384B60067800F00
+DEFINE bne_a3,a2 6384C60067800F00
+DEFINE bne_a3,t0 6384560067800F00
+DEFINE bne_t0,a0 6384A20067800F00
+DEFINE bne_t0,a2 6384C20067800F00
+DEFINE bne_t0,t1 6384620067800F00
+DEFINE bne_t0,t2 6384720067800F00
+DEFINE bne_t1,a0 6304A30067800F00
+DEFINE bne_t1,a2 6304C30067800F00
+DEFINE bne_t1,a3 6304D30067800F00
+DEFINE bne_t1,t2 6304730067800F00
+DEFINE bne_t2,a2 6384C30067800F00
+DEFINE bne_t2,a3 6384D30067800F00
+DEFINE bne_t2,t0 6384530067800F00
+DEFINE blt_a0,a1 6354B50067800F00
+DEFINE blt_a0,a2 6354C50067800F00
+DEFINE blt_a0,t1 6354650067800F00
+DEFINE blt_a1,a0 63D4A50067800F00
+DEFINE blt_a1,a2 63D4C50067800F00
+DEFINE blt_a1,t0 63D4550067800F00
+DEFINE blt_a1,t2 63D4750067800F00
+DEFINE blt_a2,a1 6354B60067800F00
+DEFINE blt_a2,a3 6354D60067800F00
+DEFINE blt_a2,t0 6354560067800F00
+DEFINE blt_a2,t1 6354660067800F00
+DEFINE blt_a3,a2 63D4C60067800F00
+DEFINE blt_a3,t2 63D4760067800F00
+DEFINE blt_t0,a0 63D4A20067800F00
+DEFINE blt_t0,a2 63D4C20067800F00
+DEFINE blt_t0,t1 63D4620067800F00
+DEFINE blt_t1,a0 6354A30067800F00
+DEFINE blt_t1,a1 6354B30067800F00
+DEFINE blt_t1,t0 6354530067800F00
+DEFINE blt_t2,a3 63D4D30067800F00
+DEFINE blt_t2,t1 63D4630067800F00
+DEFINE beqz_a0 6314050067800F00
+DEFINE beqz_a1 6394050067800F00
+DEFINE beqz_a2 6314060067800F00
+DEFINE beqz_a3 6394060067800F00
+DEFINE beqz_t0 6394020067800F00
+DEFINE beqz_t1 6314030067800F00
+DEFINE beqz_t2 6394030067800F00
+DEFINE bnez_a0 6304050067800F00
+DEFINE bnez_a1 6384050067800F00
+DEFINE bnez_a2 6304060067800F00
+DEFINE bnez_a3 6384060067800F00
+DEFINE bnez_t0 6384020067800F00
+DEFINE bnez_t1 6304030067800F00
+DEFINE bnez_t2 6384030067800F00
+DEFINE bltz_a0 6354050067800F00
+DEFINE bltz_t0 63D4020067800F00
+
+## ---- Calls And Returns
+DEFINE call E7800F00
+DEFINE ret 67800000
+DEFINE eret 83200100032441001301040067800000
+DEFINE tail 83200100032441001301040067800F00
+
+## ---- Frame Management
+DEFINE enter_0 130101FF232011001304010123228100
+DEFINE enter_8 130101FF232011001304010123228100
+DEFINE enter_16 130101FE232011001304010223228100
+DEFINE enter_40 130101FD232011001304010323228100
+DEFINE enter_56 130101FC232011001304010423228100
+
+## ---- System
+DEFINE syscall 938E0500130E0600138806009308050013850E0093050E00130608009386020013870400930709007300000093850E0013060E0093060800
+DEFINE sys_exit 5D00000000000000
+DEFINE sys_openat 3800000000000000
+DEFINE sys_read 3F00000000000000
+DEFINE sys_write 4000000000000000
+
+## ---- Program Entry
+## Backend-owned :_start stub per docs/P1.md §Program Entry.
+## Calls p1_main under the one-word direct-result convention
+## (a0=argc, a1=argv) and sys_exits its return value.
+:_start
+'03250100'
+'93054100'
+'970F000083AFCF006F008000'
+&p1_main
+'E7800F00'
+'9308D005'
+'73000000'
diff --git a/P1/P1-riscv32.M1pp b/P1/P1-riscv32.M1pp
@@ -0,0 +1,672 @@
+# P1-riscv32.M1pp -- P1 RV32IM backend expressed in m1macro.
+#
+# Mirrors p1/P1-aarch64.M1pp; same macro surface, different encodings.
+# Native register picks follow docs/P1.md's RISC-V mapping table.
+#
+# Hidden backend regs:
+# br = t6 (x31) -- dedicated branch-target mechanism
+# scratch = t5 (x30) -- per-expansion scratch, never live across ops
+# save0 = t4 (x29) -- transient across SYSCALL only
+# save1 = t3 (x28)
+# save2 = a6 (x16)
+# saved_fp = fp (x8) -- used by ENTER/ERET to capture caller sp
+# a7 = x17 -- Linux RISC-V syscall-number slot
+# a4 = x14 -- syscall arg4 slot
+# a5 = x15 -- syscall arg5 slot
+
+# Width hooks consumed by the portable P1pp support library.
+%macro p1_word_bytes()
+4
+%endm
+%macro p1_word_bits()
+32
+%endm
+%macro p1_word_shift()
+2
+%endm
+%macro p1_scheme_heap_bytes()
+0x00F00000
+%endm
+%macro p1_waitid_status_off()
+20
+%endm
+
+# ---- Native register numbers --------------------------------------------
+
+%macro rv_reg_a0()
+10
+%endm
+%macro rv_reg_a1()
+11
+%endm
+%macro rv_reg_a2()
+12
+%endm
+%macro rv_reg_a3()
+13
+%endm
+%macro rv_reg_a4()
+14
+%endm
+%macro rv_reg_a5()
+15
+%endm
+%macro rv_reg_a6()
+16
+%endm
+%macro rv_reg_a7()
+17
+%endm
+%macro rv_reg_t0()
+5
+%endm
+%macro rv_reg_t1()
+6
+%endm
+%macro rv_reg_t2()
+7
+%endm
+%macro rv_reg_s0()
+9
+%endm
+%macro rv_reg_s1()
+18
+%endm
+%macro rv_reg_s2()
+19
+%endm
+%macro rv_reg_s3()
+20
+%endm
+%macro rv_reg_sp()
+2
+%endm
+%macro rv_reg_zero()
+0
+%endm
+%macro rv_reg_ra()
+1
+%endm
+%macro rv_reg_fp()
+8
+%endm
+%macro rv_reg_br()
+31
+%endm
+%macro rv_reg_scratch()
+30
+%endm
+%macro rv_reg_save0()
+29
+%endm
+%macro rv_reg_save1()
+28
+%endm
+%macro rv_reg_save2()
+16
+%endm
+
+%macro rv_reg(r)
+%rv_reg_##r
+%endm
+
+%macro rv_is_sp_a0()
+0
+%endm
+%macro rv_is_sp_a1()
+0
+%endm
+%macro rv_is_sp_a2()
+0
+%endm
+%macro rv_is_sp_a3()
+0
+%endm
+%macro rv_is_sp_a4()
+0
+%endm
+%macro rv_is_sp_a5()
+0
+%endm
+%macro rv_is_sp_a6()
+0
+%endm
+%macro rv_is_sp_a7()
+0
+%endm
+%macro rv_is_sp_t0()
+0
+%endm
+%macro rv_is_sp_t1()
+0
+%endm
+%macro rv_is_sp_t2()
+0
+%endm
+%macro rv_is_sp_s0()
+0
+%endm
+%macro rv_is_sp_s1()
+0
+%endm
+%macro rv_is_sp_s2()
+0
+%endm
+%macro rv_is_sp_s3()
+0
+%endm
+%macro rv_is_sp_sp()
+1
+%endm
+%macro rv_is_sp_zero()
+0
+%endm
+%macro rv_is_sp_ra()
+0
+%endm
+%macro rv_is_sp_fp()
+0
+%endm
+%macro rv_is_sp_br()
+0
+%endm
+%macro rv_is_sp_scratch()
+0
+%endm
+%macro rv_is_sp_save0()
+0
+%endm
+%macro rv_is_sp_save1()
+0
+%endm
+%macro rv_is_sp_save2()
+0
+%endm
+
+%macro rv_is_sp(r)
+%rv_is_sp_##r
+%endm
+
+# ---- Low-level instruction encoders --------------------------------------
+
+# R-type: funct7[31:25] rs2[24:20] rs1[19:15] funct3[14:12] rd[11:7] opcode[6:0]
+%macro rv_r_type(base, rd, ra, rb)
+%((| base (<< %rv_reg(rb) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7)))
+%endm
+
+# I-type: imm[31:20] rs1[19:15] funct3[14:12] rd[11:7] opcode[6:0]
+%macro rv_i_type(base, rd, ra, imm12)
+%((| base (<< (& imm12 0xFFF) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7)))
+%endm
+
+# S-type: imm[31:25] rs2[24:20] rs1[19:15] funct3[14:12] imm[11:7] opcode[6:0]
+%macro rv_s_type(base, rs, ra, imm12)
+%((| base (<< (& (>> imm12 5) 0x7F) 25) (<< %rv_reg(rs) 20) (<< %rv_reg(ra) 15) (<< (& imm12 0x1F) 7)))
+%endm
+
+# B-type: imm[12|10:5] rs2 rs1 funct3 imm[4:1|11] opcode. 12-bit signed,
+# imm[0] always 0. For the hardcoded skip-over-jalr we only need a fixed
+# positive offset (8 bytes = 2 insns), so inline the resulting bit pattern.
+%macro rv_b_type_skip8(base, ra, rb)
+# imm value 8 -> imm[11:0] = 0000_0000_0100. Bits of encoded imm:
+# imm[12]=0, imm[10:5]=0, imm[4:1]=0100 (=4), imm[11]=0.
+# encoded bits: [31:25]=0, [11:7]= (imm[4:1] << 1) | imm[11] = (4<<1)|0 = 8.
+%((| base (<< %rv_reg(rb) 20) (<< %rv_reg(ra) 15) (<< 8 7)))
+%endm
+
+%macro rv_addi(rd, ra, imm12)
+%rv_i_type(0x00000013, rd, ra, imm12)
+%endm
+
+# rv_addi with arbitrary 32-bit signed immediate. Falls back to a
+# word literal load into `scratch` followed by an R-type ADD when the
+# immediate doesn't fit in ADDI's 12-bit signed field. `scratch` (t5/x30)
+# is per-expansion and never live across ops, so clobbering it is safe.
+%macro rv_addi_any(rd, ra, imm)
+%select((>= imm -2048),
+ %select((<= imm 2047),
+ %rv_addi(rd, ra, imm),
+ %rv_lit32_prefix(scratch)
+ %(imm)
+ %rv_r_type(0x00000033, rd, ra, scratch)),
+ %rv_lit32_prefix(scratch)
+ %(imm)
+ %rv_r_type(0x00000033, rd, ra, scratch))
+%endm
+
+%macro rv_lw(rd, ra, imm12)
+%rv_i_type(0x00002003, rd, ra, imm12)
+%endm
+
+%macro rv_sw(rs, ra, imm12)
+%rv_s_type(0x00002023, rs, ra, imm12)
+%endm
+
+%macro rv_lbu(rd, ra, imm12)
+%rv_i_type(0x00004003, rd, ra, imm12)
+%endm
+
+%macro rv_sb(rs, ra, imm12)
+%rv_s_type(0x00000023, rs, ra, imm12)
+%endm
+
+# Load/store with arbitrary signed offset. The native I-type/S-type
+# imm12 covers [-2048, 2047]; past that, materialize the offset in
+# scratch (t5/x30), compute scratch = ra + scratch via R-type ADD, and
+# issue the load/store with offset 0. Callers must not pass scratch as
+# `ra` or `rs` — the materialize would clobber it before the address
+# computation reads it.
+%macro rv_lw_any(rd, ra, off)
+%select((>= off -2048),
+ %select((<= off 2047),
+ %rv_lw(rd, ra, off),
+ %rv_lit32_prefix(scratch)
+ %(off)
+ %rv_r_type(0x00000033, scratch, ra, scratch)
+ %rv_lw(rd, scratch, 0)),
+ %rv_lit32_prefix(scratch)
+ %(off)
+ %rv_r_type(0x00000033, scratch, ra, scratch)
+ %rv_lw(rd, scratch, 0))
+%endm
+
+%macro rv_sw_any(rs, ra, off)
+%select((>= off -2048),
+ %select((<= off 2047),
+ %rv_sw(rs, ra, off),
+ %rv_lit32_prefix(scratch)
+ %(off)
+ %rv_r_type(0x00000033, scratch, ra, scratch)
+ %rv_sw(rs, scratch, 0)),
+ %rv_lit32_prefix(scratch)
+ %(off)
+ %rv_r_type(0x00000033, scratch, ra, scratch)
+ %rv_sw(rs, scratch, 0))
+%endm
+
+%macro rv_lbu_any(rd, ra, off)
+%select((>= off -2048),
+ %select((<= off 2047),
+ %rv_lbu(rd, ra, off),
+ %rv_lit32_prefix(scratch)
+ %(off)
+ %rv_r_type(0x00000033, scratch, ra, scratch)
+ %rv_lbu(rd, scratch, 0)),
+ %rv_lit32_prefix(scratch)
+ %(off)
+ %rv_r_type(0x00000033, scratch, ra, scratch)
+ %rv_lbu(rd, scratch, 0))
+%endm
+
+%macro rv_sb_any(rs, ra, off)
+%select((>= off -2048),
+ %select((<= off 2047),
+ %rv_sb(rs, ra, off),
+ %rv_lit32_prefix(scratch)
+ %(off)
+ %rv_r_type(0x00000033, scratch, ra, scratch)
+ %rv_sb(rs, scratch, 0)),
+ %rv_lit32_prefix(scratch)
+ %(off)
+ %rv_r_type(0x00000033, scratch, ra, scratch)
+ %rv_sb(rs, scratch, 0))
+%endm
+
+%macro rv_mov_rr(dst, src)
+%rv_addi(dst, src, 0)
+%endm
+
+%macro rv_slli(rd, ra, shamt)
+%((| 0x00001013 (<< (& shamt 0x1F) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7)))
+%endm
+
+%macro rv_srli(rd, ra, shamt)
+%((| 0x00005013 (<< (& shamt 0x1F) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7)))
+%endm
+
+%macro rv_srai(rd, ra, shamt)
+%((| 0x40005013 (<< (& shamt 0x1F) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7)))
+%endm
+
+%macro rv_jalr(rd, rs, imm12)
+%((| 0x00000067 (<< (& imm12 0xFFF) 20) (<< %rv_reg(rs) 15) (<< %rv_reg(rd) 7)))
+%endm
+
+%macro rv_ecall()
+%(0x00000073)
+%endm
+
+# 32-bit word/address literal-pool prefix:
+# auipc rd, 0 pc-relative base
+# lw rd, 12(rd) load the 4-byte literal from pc+12
+# jal x0, 8 skip the 4-byte literal.
+%macro rv_lit32_prefix(rd)
+%((| 0x00000017 (<< %rv_reg(rd) 7)))
+%((| 0x00C02003 (<< %rv_reg(rd) 15) (<< %rv_reg(rd) 7)))
+%(0x0080006F)
+%endm
+
+# Memory op fallback: offset outside signed 12-bit range. Load the
+# offset into `scratch` via LUI+ADDI dance? For stage0 programs the
+# curated offsets stay inside -2048..2047, so fall back is unused;
+# still emit a defensive failure to flag any future overflow.
+# (In practice none of the LD/ST off values in p1_gen.py exceed the
+# signed 12-bit range, so no fallback path is wired in here.)
+
+# ---- P1 register-register op lowering -----------------------------------
+
+%macro rv_rrr_ADD(rd, ra, rb)
+%rv_r_type(0x00000033, rd, ra, rb)
+%endm
+%macro rv_rrr_SUB(rd, ra, rb)
+%rv_r_type(0x40000033, rd, ra, rb)
+%endm
+%macro rv_rrr_AND(rd, ra, rb)
+%rv_r_type(0x00007033, rd, ra, rb)
+%endm
+%macro rv_rrr_OR(rd, ra, rb)
+%rv_r_type(0x00006033, rd, ra, rb)
+%endm
+%macro rv_rrr_XOR(rd, ra, rb)
+%rv_r_type(0x00004033, rd, ra, rb)
+%endm
+%macro rv_rrr_SHL(rd, ra, rb)
+%rv_r_type(0x00001033, rd, ra, rb)
+%endm
+%macro rv_rrr_SHR(rd, ra, rb)
+%rv_r_type(0x00005033, rd, ra, rb)
+%endm
+%macro rv_rrr_SAR(rd, ra, rb)
+%rv_r_type(0x40005033, rd, ra, rb)
+%endm
+%macro rv_rrr_MUL(rd, ra, rb)
+%rv_r_type(0x02000033, rd, ra, rb)
+%endm
+%macro rv_rrr_DIV(rd, ra, rb)
+%rv_r_type(0x02004033, rd, ra, rb)
+%endm
+%macro rv_rrr_REM(rd, ra, rb)
+%rv_r_type(0x02006033, rd, ra, rb)
+%endm
+
+%macro rv_rrr_op(op, rd, ra, rb)
+%rv_rrr_##op(rd, ra, rb)
+%endm
+
+# ---- P1 operation lowering -----------------------------------------------
+
+%macro p1_li(rd, imm)
+%rv_lit32_prefix(rd)
+%(imm)
+%endm
+
+%macro p1_la(rd)
+%rv_lit32_prefix(rd)
+%endm
+
+%macro p1_labr()
+%rv_lit32_prefix(br)
+%endm
+
+%macro p1_mov(rd, rs)
+%select((= %rv_is_sp(rs) 1),
+ %rv_addi(rd, sp, 8),
+ %rv_mov_rr(rd, rs))
+%endm
+
+%macro p1_rrr(op, rd, ra, rb)
+%rv_rrr_op(op, rd, ra, rb)
+%endm
+
+%macro p1_addi(rd, ra, imm)
+%rv_addi_any(rd, ra, imm)
+%endm
+
+# Logical-immediate fallback: when imm fits the I-type's 12-bit signed
+# field, emit the native ANDI/ORI; otherwise materialize the immediate
+# in scratch (t5/x30) and use the R-type AND/OR. funct3=7 (AND) or 6
+# (OR) is shared between the I-type (opcode 0x13) and R-type
+# (opcode 0x33) encodings.
+%macro rv_logi_any(rd, ra, imm, base_i, base_r)
+%select((>= imm -2048),
+ %select((<= imm 2047),
+ %rv_i_type(base_i, rd, ra, imm),
+ %rv_lit32_prefix(scratch)
+ %(imm)
+ %rv_r_type(base_r, rd, ra, scratch)),
+ %rv_lit32_prefix(scratch)
+ %(imm)
+ %rv_r_type(base_r, rd, ra, scratch))
+%endm
+
+%macro p1_logi_ANDI(rd, ra, imm)
+%rv_logi_any(rd, ra, imm, 0x00007013, 0x00007033)
+%endm
+%macro p1_logi_ORI(rd, ra, imm)
+%rv_logi_any(rd, ra, imm, 0x00006013, 0x00006033)
+%endm
+%macro p1_logi(op, rd, ra, imm)
+%p1_logi_##op(rd, ra, imm)
+%endm
+
+%macro p1_shifti_SHLI(rd, ra, imm)
+%rv_slli(rd, ra, imm)
+%endm
+%macro p1_shifti_SHRI(rd, ra, imm)
+%rv_srli(rd, ra, imm)
+%endm
+%macro p1_shifti_SARI(rd, ra, imm)
+%rv_srai(rd, ra, imm)
+%endm
+%macro p1_shifti(op, rd, ra, imm)
+%p1_shifti_##op(rd, ra, imm)
+%endm
+
+%macro p1_mem_LD(rt, rn, off)
+%rv_lw_any(rt, rn, off)
+%endm
+%macro p1_mem_ST(rt, rn, off)
+%rv_sw_any(rt, rn, off)
+%endm
+%macro p1_mem_LB(rt, rn, off)
+%rv_lbu_any(rt, rn, off)
+%endm
+%macro p1_mem_SB(rt, rn, off)
+%rv_sb_any(rt, rn, off)
+%endm
+%macro p1_mem(op, rt, rn, off)
+%select((= %rv_is_sp(rn) 1),
+ %p1_mem_##op(rt, rn, (+ off 8)),
+ %p1_mem_##op(rt, rn, off))
+%endm
+
+%macro p1_ldarg(rd, slot)
+%rv_lw(rd, sp, 4)
+%rv_lw_any(rd, rd, (+ 8 (* 4 slot)))
+%endm
+
+%macro p1_b()
+%rv_jalr(zero, br, 0)
+%endm
+
+%macro p1_br(rs)
+%rv_jalr(zero, rs, 0)
+%endm
+
+%macro p1_call()
+%rv_jalr(ra, br, 0)
+%endm
+
+%macro p1_callr(rs)
+%rv_jalr(ra, rs, 0)
+%endm
+
+%macro p1_ret()
+%rv_jalr(zero, ra, 0)
+%endm
+
+%macro p1_eret()
+%rv_lw(ra, sp, 0)
+%rv_lw(fp, sp, 4)
+%rv_mov_rr(sp, fp)
+%rv_jalr(zero, ra, 0)
+%endm
+
+%macro p1_tail()
+%rv_lw(ra, sp, 0)
+%rv_lw(fp, sp, 4)
+%rv_mov_rr(sp, fp)
+%rv_jalr(zero, br, 0)
+%endm
+
+%macro p1_tailr(rs)
+%rv_lw(ra, sp, 0)
+%rv_lw(fp, sp, 4)
+%rv_mov_rr(sp, fp)
+%rv_jalr(zero, rs, 0)
+%endm
+
+# Conditional branch: emit a skip-taken native branch over the `%p1_b`
+# fall-through, then the jalr(br) that takes the P1 branch. Each native
+# B-type here uses the inverted condition with a +8 offset so the `jalr`
+# two insns below is the taken target.
+%macro p1_condb_BEQ(ra, rb)
+%rv_b_type_skip8(0x00001063, ra, rb)
+%p1_b
+%endm
+%macro p1_condb_BNE(ra, rb)
+%rv_b_type_skip8(0x00000063, ra, rb)
+%p1_b
+%endm
+%macro p1_condb_BLT(ra, rb)
+%rv_b_type_skip8(0x00005063, ra, rb)
+%p1_b
+%endm
+%macro p1_condb_BLTU(ra, rb)
+%rv_b_type_skip8(0x00007063, ra, rb)
+%p1_b
+%endm
+%macro p1_condb(op, ra, rb)
+%p1_condb_##op(ra, rb)
+%endm
+
+%macro p1_condbz_BEQZ(ra)
+%rv_b_type_skip8(0x00001063, ra, zero)
+%p1_b
+%endm
+%macro p1_condbz_BNEZ(ra)
+%rv_b_type_skip8(0x00000063, ra, zero)
+%p1_b
+%endm
+%macro p1_condbz_BLTZ(ra)
+%rv_b_type_skip8(0x00005063, ra, zero)
+%p1_b
+%endm
+%macro p1_condbz(op, ra)
+%p1_condbz_##op(ra)
+%endm
+
+%macro p1_enter(size)
+%rv_addi_any(sp, sp, (- 0 (& (+ (+ 8 size) 15) -16)))
+%rv_sw(ra, sp, 0)
+%rv_addi_any(fp, sp, (& (+ (+ 8 size) 15) -16))
+%rv_sw(fp, sp, 4)
+%endm
+
+%macro p1_entry()
+# :_start stub per the P1 program-entry model. Linux RV32 puts argc
+# at [sp] and argv starting at [sp+4], matching the generic SysV entry
+# stack. Load argc into a0, compute &argv[0] into a1, call p1_main under
+# the one-word direct-result convention, then issue sys_exit with the
+# returned status.
+:_start
+%rv_lw(a0, sp, 0)
+%rv_addi(a1, sp, 4)
+%rv_lit32_prefix(br)
+&p1_main
+%rv_jalr(ra, br, 0)
+%rv_addi(a7, zero, 93)
+%rv_ecall
+%endm
+
+%macro p1_syscall()
+# P1: a0=number, a1,a2,a3,t0,s0,s1 = args 0..5.
+# Linux RISC-V: a7=number, a0..a5 = args 0..5, return in a0.
+# SYSCALL clobbers only P1 a0; restore a1/a2/a3 after ecall.
+# Native a4/a5 (x14/x15) aren't P1-exposed; we use them as syscall arg
+# slots and don't need to save them.
+%rv_mov_rr(save0, a1)
+%rv_mov_rr(save1, a2)
+%rv_mov_rr(save2, a3)
+%rv_mov_rr(a7, a0)
+%rv_mov_rr(a0, save0)
+%rv_mov_rr(a1, save1)
+%rv_mov_rr(a2, save2)
+%rv_mov_rr(a3, t0)
+%rv_mov_rr(a4, s0)
+%rv_mov_rr(a5, s1)
+%rv_ecall
+%rv_mov_rr(a1, save0)
+%rv_mov_rr(a2, save1)
+%rv_mov_rr(a3, save2)
+%endm
+
+# ---- Linux RV32 syscall numbers ------------------------------------------
+# Each macro returns the syscall number as an integer atom so callers can
+# use it inside expressions (e.g. `%li(a0, %sys_write)`).
+
+%macro p1_sys_read()
+63
+%endm
+%macro p1_sys_write()
+64
+%endm
+%macro p1_sys_close()
+57
+%endm
+%macro p1_sys_openat()
+56
+%endm
+%macro p1_sys_exit()
+93
+%endm
+%macro p1_sys_clone()
+220
+%endm
+%macro p1_sys_execve()
+221
+%endm
+%macro p1_sys_spawn()
+1024
+%endm
+%macro p1_sys_waitid()
+95
+%endm
+%macro p1_sys_lseek()
+62
+%endm
+%macro p1_sys_lseek_wrapper()
+# Linux RV32 exposes syscall 62 as _llseek(fd, hi, lo, result, whence),
+# whereas the 64-bit ABIs expose lseek(fd, off, whence). Keep libp1pp's
+# target-word API and adapt the signed 32-bit offset here.
+%enter(12)
+%st(s0, sp, 8)
+%mov(s0, a2)
+%mov(a3, a1)
+%sari(a2, a1, 31)
+%mov(t0, sp)
+%mov(a1, a0)
+%li(a0, %p1_sys_lseek)
+%syscall
+%ld(s0, sp, 8)
+%bltz(a0, &@done)
+%ld(a0, sp, 0)
+:@done
+%eret
+%endm
+%macro p1_sys_brk()
+214
+%endm
+%macro p1_sys_unlinkat()
+35
+%endm
diff --git a/P1/P1-riscv64.M1 b/P1/P1-riscv64.M1
@@ -5,6 +5,8 @@
## ---- Materialization
+DEFINE p1wordbytes 0800000000000000
+DEFINE p1wordshift 0300000000000000
DEFINE li_a0 170500000335C5006F00C000
DEFINE li_a1 9705000083B5C5006F00C000
DEFINE li_a2 170600000336C6006F00C000
@@ -99,6 +101,7 @@ DEFINE add_t0,t0,a3 B382D200
DEFINE add_t0,t0,t1 B3826200
DEFINE add_t0,t2,a3 B382D300
DEFINE add_t1,a0,t0 33035500
+DEFINE add_t1,a1,t2 33837500
DEFINE add_t1,a3,t1 33836600
DEFINE add_t1,t0,a0 3383A200
DEFINE add_t1,t0,t1 33836200
@@ -149,6 +152,7 @@ DEFINE or_t0,t0,t1 B3E26200
DEFINE xor_a2,a2,a3 3346D600
DEFINE xor_a3,a3,a2 B3C6C600
DEFINE shl_a2,a2,a3 3316D600
+DEFINE shl_t2,t0,t2 B3937200
DEFINE sar_a2,a2,a3 3356D640
DEFINE mul_a0,a0,a3 3305D502
DEFINE mul_a0,a0,t0 33055502
@@ -301,8 +305,6 @@ DEFINE ld_t0,a0,8 83328500
DEFINE ld_t0,a0,16 83320501
DEFINE ld_t0,a0,24 83328501
DEFINE ld_t0,a1,0 83B20500
-DEFINE ld_t0,a1,8 83B28500
-DEFINE ld_t0,a1,16 83B20501
DEFINE ld_t0,a1,24 83B28501
DEFINE ld_t0,a1,32 83B20502
DEFINE ld_t0,a1,40 83B28502
@@ -311,6 +313,7 @@ DEFINE ld_t0,a1,64 83B20504
DEFINE ld_t0,a2,0 83320600
DEFINE ld_t0,a3,0 83B20600
DEFINE ld_t0,t0,0 83B20200
+DEFINE ld_t0,t1,0 83320300
DEFINE ld_t0,t2,0 83B20300
DEFINE ld_t0,t2,16 83B20301
DEFINE ld_t0,sp,0 83320101
diff --git a/P1/P1-riscv64.M1pp b/P1/P1-riscv64.M1pp
@@ -1,4 +1,21 @@
# P1-riscv64.M1pp -- P1 riscv64 backend expressed in m1macro.
+
+# Width hooks consumed by the portable P1pp support library.
+%macro p1_word_bytes()
+8
+%endm
+%macro p1_word_bits()
+64
+%endm
+%macro p1_word_shift()
+3
+%endm
+%macro p1_scheme_heap_bytes()
+0x10000000
+%endm
+%macro p1_waitid_status_off()
+24
+%endm
#
# Mirrors p1/P1-aarch64.M1pp; same macro surface, different encodings.
# Native register picks follow docs/P1.md's 64-bit mapping table.
@@ -646,6 +663,14 @@ $(imm)
%macro p1_sys_lseek()
62
%endm
+%macro p1_sys_lseek_wrapper()
+%mov(a3, a2)
+%mov(a2, a1)
+%mov(a1, a0)
+%li(a0, %p1_sys_lseek)
+%syscall
+%ret
+%endm
%macro p1_sys_brk()
214
%endm
diff --git a/P1/P1pp.P1pp b/P1/P1pp.P1pp
@@ -5,8 +5,11 @@
#
# catm P1-<arch>.M1pp P1.M1pp p1pp.P1pp usersrc.P1pp > program.M1
#
-# Targets P1-64 only (WORD = 8). All internal labels use the
-# `libp1pp__` prefix; public entry points are unprefixed.
+# Targets both P1-64 and P1-32. Data structures declared with `%struct`
+# deliberately retain their 8-byte padded field layout on both widths;
+# `%p1_word_*` describes target registers, pointers, and native arrays.
+# All internal labels use the `libp1pp__` prefix; public entry points are
+# unprefixed.
#
# See docs/LIBP1PP.md for the public contract.
@@ -101,8 +104,8 @@
# Sub-word memory access
# =========================================================================
#
-# P1 has only 1-byte (%lb/%sb) and 8-byte (%ld/%st) memory ops, and the
-# 8-byte ops require natural 8-byte alignment. For struct fields and
+# P1 has only 1-byte (%lb/%sb) and word-sized (%ld/%st) memory ops. For
+# struct fields and
# packed data laid out at narrower widths, sub-word access is byte-
# decomposed: %lb-gather + shli/or for loads, %sb-scatter + shri for
# stores. These macros encapsulate that pattern so callers do not have
@@ -114,7 +117,7 @@
# Stores preserve `rs`; loads clobber `rd`. `scratch` is a working
# register distinct from rd/rs and base. Bytes are little-endian:
# byte 0 (low) at off+0. The signed-load variants (%ld_sh, %ld_sw)
-# sign-extend the gathered value to the canonical 64-bit form.
+# sign-extend the gathered value to the canonical target-word form.
#
# %ld_h(rd, base, off, scratch) — 2-byte zero-extending load
# %ld_w(rd, base, off, scratch) — 4-byte zero-extending load
@@ -145,14 +148,14 @@
%macro ld_sh(rd, base, off, scratch)
%ld_h(rd, base, off, scratch)
- %shli(rd, rd, 48)
- %sari(rd, rd, 48)
+ %shli(rd, rd, (- %p1_word_bits 16))
+ %sari(rd, rd, (- %p1_word_bits 16))
%endm
%macro ld_sw(rd, base, off, scratch)
%ld_w(rd, base, off, scratch)
- %shli(rd, rd, 32)
- %sari(rd, rd, 32)
+ %shli(rd, rd, (- %p1_word_bits 32))
+ %sari(rd, rd, (- %p1_word_bits 32))
%endm
%macro st_h(rs, base, off, scratch)
@@ -175,8 +178,8 @@
# Sign and zero extension
# =========================================================================
#
-# %sextN(rd, ra) truncate ra to N bits and sign-extend to 64.
-# %zextN(rd, ra) truncate ra to N bits and zero-extend to 64.
+# %sextN(rd, ra) truncate ra to N bits and sign-extend to a word.
+# %zextN(rd, ra) truncate ra to N bits and zero-extend to a word.
# %zext32(rd, ra, scratch)
# like zextN but needs a scratch register because
# 0xFFFFFFFF does not fit a 16-bit movz immediate
@@ -187,18 +190,18 @@
# needed); zext32 materializes the mask explicitly.
%macro sext8(rd, ra)
- %shli(rd, ra, 56)
- %sari(rd, rd, 56)
+ %shli(rd, ra, (- %p1_word_bits 8))
+ %sari(rd, rd, (- %p1_word_bits 8))
%endm
%macro sext16(rd, ra)
- %shli(rd, ra, 48)
- %sari(rd, rd, 48)
+ %shli(rd, ra, (- %p1_word_bits 16))
+ %sari(rd, rd, (- %p1_word_bits 16))
%endm
%macro sext32(rd, ra)
- %shli(rd, ra, 32)
- %sari(rd, rd, 32)
+ %shli(rd, ra, (- %p1_word_bits 32))
+ %sari(rd, rd, (- %p1_word_bits 32))
%endm
%macro zext8(rd, ra)
@@ -377,6 +380,200 @@
%endm
# =========================================================================
+# Two-word 64-bit integer helpers for P1-32
+# =========================================================================
+#
+# cc.scm uses these only when `%p1_word_bits == 32`. Values are little-
+# endian register pairs `(lo, hi)`. The helpers stay in the portable P1pp
+# layer because they lower entirely through one-word P1 operations.
+
+%macro i64_neg(rlo, rhi, lo, hi, scratch)
+ %li(scratch, 0)
+ %sub(rlo, scratch, lo)
+ %cmpset_eqz(scratch, rlo)
+ %li(rhi, -1)
+ %xor(rhi, hi, rhi)
+ %add(rhi, rhi, scratch)
+%endm
+
+%macro i64_add(rlo, rhi, alo, ahi, blo, bhi, scratch)
+ %add(rlo, alo, blo)
+ %cmpset_ltu(scratch, rlo, alo)
+ %add(rhi, ahi, bhi)
+ %add(rhi, rhi, scratch)
+%endm
+
+%macro i64_sub(rlo, rhi, alo, ahi, blo, bhi, scratch)
+ %cmpset_ltu(scratch, alo, blo)
+ %sub(rlo, alo, blo)
+ %sub(rhi, ahi, bhi)
+ %sub(rhi, rhi, scratch)
+%endm
+
+# Low 64 bits of a two-limb product. The cross terms contribute directly to
+# the high limb; the high half of alo*blo is recovered with 16-bit pieces so
+# this needs no target-specific multiply-high instruction. Inputs are
+# clobbered and therefore must be distinct from outputs and scratch.
+%macro i64_mul(rlo, rhi, alo, ahi, blo, bhi, scratch)
+ %mul(rhi, alo, bhi)
+ %mul(scratch, ahi, blo)
+ %add(rhi, rhi, scratch)
+ %mul(rlo, alo, blo)
+
+ %andi(ahi, alo, 65535)
+ %shri(alo, alo, 16)
+ %andi(bhi, blo, 65535)
+ %shri(blo, blo, 16)
+
+ %mul(scratch, ahi, bhi)
+ %shri(scratch, scratch, 16)
+ %mul(bhi, alo, bhi)
+ %add(scratch, scratch, bhi)
+ %andi(bhi, scratch, 65535)
+ %shri(scratch, scratch, 16)
+ %mul(ahi, ahi, blo)
+ %add(bhi, bhi, ahi)
+ %shri(bhi, bhi, 16)
+ %mul(alo, alo, blo)
+ %add(scratch, scratch, bhi)
+ %add(scratch, scratch, alo)
+ %add(rhi, rhi, scratch)
+%endm
+
+%macro i64_cmpset_eq(rd, alo, ahi, blo, bhi, scratch)
+ %xor(scratch, ahi, bhi)
+ %xor(rd, alo, blo)
+ %or(rd, rd, scratch)
+ %cmpset_eqz(rd, rd)
+%endm
+
+%macro i64_cmpset_ne(rd, alo, ahi, blo, bhi, scratch)
+ %xor(scratch, ahi, bhi)
+ %xor(rd, alo, blo)
+ %or(rd, rd, scratch)
+ %cmpset_nez(rd, rd)
+%endm
+
+%macro i64_cmpset_lt(rd, alo, ahi, blo, bhi, scratch)
+ .scope
+ %beq(ahi, bhi, &.low)
+ %cmpset_lt(rd, ahi, bhi)
+ %b(&.done)
+ :.low
+ %cmpset_ltu(rd, alo, blo)
+ :.done
+ .endscope
+%endm
+
+%macro i64_cmpset_ltu(rd, alo, ahi, blo, bhi, scratch)
+ .scope
+ %beq(ahi, bhi, &.low)
+ %cmpset_ltu(rd, ahi, bhi)
+ %b(&.done)
+ :.low
+ %cmpset_ltu(rd, alo, blo)
+ :.done
+ .endscope
+%endm
+
+%macro i64_cmpset_gt(rd, alo, ahi, blo, bhi, scratch)
+ %i64_cmpset_lt(rd, blo, bhi, alo, ahi, scratch)
+%endm
+
+%macro i64_cmpset_gtu(rd, alo, ahi, blo, bhi, scratch)
+ %i64_cmpset_ltu(rd, blo, bhi, alo, ahi, scratch)
+%endm
+
+%macro i64_cmpset_le(rd, alo, ahi, blo, bhi, scratch)
+ %i64_cmpset_gt(rd, alo, ahi, blo, bhi, scratch)
+ %cmpset_eqz(rd, rd)
+%endm
+
+%macro i64_cmpset_leu(rd, alo, ahi, blo, bhi, scratch)
+ %i64_cmpset_gtu(rd, alo, ahi, blo, bhi, scratch)
+ %cmpset_eqz(rd, rd)
+%endm
+
+%macro i64_cmpset_ge(rd, alo, ahi, blo, bhi, scratch)
+ %i64_cmpset_lt(rd, alo, ahi, blo, bhi, scratch)
+ %cmpset_eqz(rd, rd)
+%endm
+
+%macro i64_cmpset_geu(rd, alo, ahi, blo, bhi, scratch)
+ %i64_cmpset_ltu(rd, alo, ahi, blo, bhi, scratch)
+ %cmpset_eqz(rd, rd)
+%endm
+
+%macro i64_shl(rlo, rhi, lo, hi, count, scratch)
+ .scope
+ %beqz(count, &.zero)
+ %li(scratch, 32)
+ %bltu(count, scratch, &.small)
+ %sub(scratch, count, scratch)
+ %shl(rhi, lo, scratch)
+ %li(rlo, 0)
+ %b(&.done)
+ :.small
+ %sub(scratch, scratch, count)
+ %shr(rlo, lo, scratch)
+ %shl(rhi, hi, count)
+ %or(rhi, rhi, rlo)
+ %shl(rlo, lo, count)
+ %b(&.done)
+ :.zero
+ %mov(rlo, lo)
+ %mov(rhi, hi)
+ :.done
+ .endscope
+%endm
+
+%macro i64_shr(rlo, rhi, lo, hi, count, scratch)
+ .scope
+ %beqz(count, &.zero)
+ %li(scratch, 32)
+ %bltu(count, scratch, &.small)
+ %sub(scratch, count, scratch)
+ %shr(rlo, hi, scratch)
+ %li(rhi, 0)
+ %b(&.done)
+ :.small
+ %sub(scratch, scratch, count)
+ %shl(rlo, hi, scratch)
+ %shr(scratch, lo, count)
+ %or(rlo, rlo, scratch)
+ %shr(rhi, hi, count)
+ %b(&.done)
+ :.zero
+ %mov(rlo, lo)
+ %mov(rhi, hi)
+ :.done
+ .endscope
+%endm
+
+%macro i64_sar(rlo, rhi, lo, hi, count, scratch)
+ .scope
+ %beqz(count, &.zero)
+ %li(scratch, 32)
+ %bltu(count, scratch, &.small)
+ %sub(scratch, count, scratch)
+ %sar(rlo, hi, scratch)
+ %sari(rhi, hi, 31)
+ %b(&.done)
+ :.small
+ %sub(scratch, scratch, count)
+ %shl(rlo, hi, scratch)
+ %shr(scratch, lo, count)
+ %or(rlo, rlo, scratch)
+ %sar(rhi, hi, count)
+ %b(&.done)
+ :.zero
+ %mov(rlo, lo)
+ %mov(rhi, hi)
+ :.done
+ .endscope
+%endm
+
+# =========================================================================
# Switch dispatch
# =========================================================================
#
@@ -830,6 +1027,97 @@
%macro ldl(reg, slot) %ld(reg, sp, %local(slot)) %endm
# =========================================================================
+# RV32 64-bit division helpers
+# =========================================================================
+#
+# Both helpers use the P1 two-word direct-result convention for the quotient
+# (a0=lo, a1=hi) and additionally return the remainder in a2/a3. They are
+# emitted on every target but called only by cc.scm's P1-32 lowering.
+
+%fn(p1_i64_udivmod, (* 4 %p1_word_bytes), {
+ %st(s0, sp, 0)
+ %st(s1, sp, (* 1 %p1_word_bytes))
+ %st(s2, sp, (* 2 %p1_word_bytes))
+ %st(s3, sp, (* 3 %p1_word_bytes))
+
+ %mov(s0, a2)
+ %mov(s1, a3)
+ %li(s2, 0)
+ %li(s3, 0)
+ %li(a2, 64)
+
+ :.loop
+ %beqz(a2, &.done)
+
+ # Shift the combined (remainder:quotient) 128-bit state left once.
+ %shri(t0, a1, 31)
+ %shri(t1, a0, 31)
+ %shli(a1, a1, 1)
+ %or(a1, a1, t1)
+ %shli(a0, a0, 1)
+ %shri(t1, s2, 31)
+ %shli(s3, s3, 1)
+ %or(s3, s3, t1)
+ %shli(s2, s2, 1)
+ %or(s2, s2, t0)
+
+ # If remainder >= denominator, subtract it and set quotient bit 0.
+ %bltu(s3, s1, &.skip_sub)
+ %bltu(s1, s3, &.subtract)
+ %bltu(s2, s0, &.skip_sub)
+ :.subtract
+ %i64_sub(s2, s3, s2, s3, s0, s1, t0)
+ %ori(a0, a0, 1)
+ :.skip_sub
+
+ %addi(a2, a2, -1)
+ %b(&.loop)
+
+ :.done
+ %mov(a2, s2)
+ %mov(a3, s3)
+ %ld(s0, sp, 0)
+ %ld(s1, sp, (* 1 %p1_word_bytes))
+ %ld(s2, sp, (* 2 %p1_word_bytes))
+ %ld(s3, sp, (* 3 %p1_word_bytes))
+})
+
+%fn(p1_i64_divmod, (* 2 %p1_word_bytes), {
+ %st(s0, sp, 0)
+ %st(s1, sp, (* 1 %p1_word_bytes))
+ %sari(s0, a1, 31)
+ %sari(s1, a3, 31)
+
+ %if_ltz(s0, {
+ %i64_neg(t0, t1, a0, a1, t2)
+ %mov(a0, t0)
+ %mov(a1, t1)
+ })
+ %if_ltz(s1, {
+ %i64_neg(t0, t1, a2, a3, t2)
+ %mov(a2, t0)
+ %mov(a3, t1)
+ })
+
+ %call(&p1_i64_udivmod)
+
+ %xor(t0, s0, s1)
+ %if_ltz(t0, {
+ %i64_neg(t0, t1, a0, a1, t2)
+ %mov(a0, t0)
+ %mov(a1, t1)
+ })
+ %if_ltz(s0, {
+ %i64_neg(t0, t1, a2, a3, t2)
+ %mov(a2, t0)
+ %mov(a3, t1)
+ })
+
+ %ld(s0, sp, 0)
+ %ld(s1, sp, (* 1 %p1_word_bytes))
+})
+
+# =========================================================================
# %assert_<cc> macros
# =========================================================================
#
@@ -1383,12 +1671,7 @@
# sys_lseek(fd=a0, off=a1, whence=a2) -> off (a0)
:sys_lseek
- %mov(a3, a2)
- %mov(a2, a1)
- %mov(a1, a0)
- %li(a0, %p1_sys_lseek)
- %syscall
- %ret
+ %p1_sys_lseek_wrapper
# sys_brk(addr=a0) -> new_break (a0). addr=0 returns the current break.
:sys_brk
diff --git a/P1/gen/p1_gen.py b/P1/gen/p1_gen.py
@@ -40,9 +40,13 @@ from common import (
import aarch64
import amd64
+import riscv32
import riscv64
-ARCHES = {a.name: a for a in (aarch64.ARCH, amd64.ARCH, riscv64.ARCH)}
+ARCHES = {
+ a.name: a
+ for a in (aarch64.ARCH, amd64.ARCH, riscv32.ARCH, riscv64.ARCH)
+}
P1_GPRS = ('a0', 'a1', 'a2', 'a3', 't0', 't1', 't2', 's0', 's1', 's2', 's3')
@@ -66,8 +70,6 @@ LOGI_IMMS = (
2047,
)
-SHIFT_IMMS = tuple(range(64))
-
MEM_OFFS = (
-256, -128, -64, -48, -32, -24, -16, -8, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8,
15, 16, 24, 32, 40, 48, 56, 64, 128, 255,
@@ -92,6 +94,18 @@ def rows(arch):
out = []
out.append(Banner('Materialization'))
+ # Stage0 .P1 sources use these target facts for native pointer-array
+ # indexing while retaining their deliberately padded 8-byte records.
+ literal_bytes = 8 if arch.name == 'riscv32' else arch.word_bytes
+ out.append(Literal(name='p1wordbytes', hex_by_arch={
+ arch.name: word_hex(literal_bytes, arch.word_bytes),
+ }))
+ out.append(Literal(name='p1wordbits', hex_by_arch={
+ arch.name: word_hex(literal_bytes, arch.word_bytes * 8),
+ }))
+ out.append(Literal(name='p1wordshift', hex_by_arch={
+ arch.name: word_hex(literal_bytes, 2 if arch.word_bytes == 4 else 3),
+ }))
for rd in P1_GPRS:
out.append(Li(name=f'LI_{rd.upper()}', rd=rd))
for rd in P1_GPRS:
@@ -116,7 +130,8 @@ def rows(arch):
for op, rd, ra, imm in product(LOGI_OPS, P1_GPRS, P1_GPRS, LOGI_IMMS):
out.append(LogI(name=f'{op}_{rd.upper()}_{ra.upper()}_{imm_suffix(imm)}',
op=op, rd=rd, ra=ra, imm=imm))
- for op, rd, ra, imm in product(SHIFT_OPS, P1_GPRS, P1_GPRS, SHIFT_IMMS):
+ shift_imms = tuple(range(arch.word_bytes * 8))
+ for op, rd, ra, imm in product(SHIFT_OPS, P1_GPRS, P1_GPRS, shift_imms):
out.append(ShiftI(name=f'{op}_{rd.upper()}_{ra.upper()}_{imm}',
op=op, rd=rd, ra=ra, imm=imm))
@@ -155,7 +170,10 @@ def rows(arch):
out.append(Banner('System'))
out.append(Nullary(name='SYSCALL', kind='SYSCALL'))
for name, number in sorted(arch.syscall_numbers.items()):
- out.append(Literal(name=name, hex_by_arch={arch.name: word_hex(arch.word_bytes, number)}))
+ # The two stage0 .P1 consumers retain their original padded 8-byte
+ # immediate spelling on RV32; its generated LI skips that padding.
+ # M1pp-authored P1-32 programs use the canonical four-byte form.
+ out.append(Literal(name=name, hex_by_arch={arch.name: word_hex(literal_bytes, number)}))
return out
diff --git a/P1/gen/riscv32.py b/P1/gen/riscv32.py
@@ -0,0 +1,256 @@
+"""RV32IM Linux lowering for the generated P1-32 DEFINE table."""
+
+from common import (
+ AddI,
+ ArchDef,
+ BranchReg,
+ CondB,
+ CondBZ,
+ Enter,
+ La,
+ LaBr,
+ LdArg,
+ Li,
+ LogI,
+ Mem,
+ Mov,
+ Nullary,
+ Rrr,
+ ShiftI,
+ le32,
+ round_up,
+)
+from riscv64 import (
+ CONDBZ_INV_BASE,
+ CONDB_INV_BASE,
+ NAT,
+ RRR_BASE,
+ SYSCALL_NUMBERS,
+ rv_addi,
+ rv_b_type_skip8,
+ rv_ecall,
+ rv_i_type,
+ rv_jalr,
+ rv_lbu,
+ rv_mov_rr,
+ rv_r_type,
+ rv_s_type,
+ rv_sb,
+)
+
+
+def rv_lw(rd, ra, imm12):
+ return rv_i_type(0x00002003, rd, ra, imm12)
+
+
+def rv_sw(rs, ra, imm12):
+ return rv_s_type(0x00002023, rs, ra, imm12)
+
+
+def rv_slli(rd, ra, shamt):
+ d = NAT[rd]
+ a = NAT[ra]
+ return le32(0x00001013 | ((shamt & 0x1F) << 20) | (a << 15) | (d << 7))
+
+
+def rv_srli(rd, ra, shamt):
+ d = NAT[rd]
+ a = NAT[ra]
+ return le32(0x00005013 | ((shamt & 0x1F) << 20) | (a << 15) | (d << 7))
+
+
+def rv_srai(rd, ra, shamt):
+ d = NAT[rd]
+ a = NAT[ra]
+ return le32(0x40005013 | ((shamt & 0x1F) << 20) | (a << 15) | (d << 7))
+
+
+def rv_lit32_prefix(rd):
+ # auipc rd, 0 ; lw rd, 12(rd) ; jal x0, +8. The four bytes that
+ # follow in source are both the P1 word literal and an ELF32 pointer.
+ d = NAT[rd]
+ auipc = 0x00000017 | (d << 7)
+ lw = 0x00C02003 | (d << 15) | (d << 7)
+ jal = 0x0080006F
+ return le32(auipc) + le32(lw) + le32(jal)
+
+
+def rv_lit32_padded64_prefix(rd):
+ # The stage0 .P1 sources predate P1-32 and spell immediate payloads as
+ # eight source bytes. Load the low RV32 word, then skip the full padded
+ # payload. The M1pp backend uses the canonical four-byte form instead.
+ d = NAT[rd]
+ auipc = 0x00000017 | (d << 7)
+ lw = 0x00C02003 | (d << 15) | (d << 7)
+ jal = 0x00C0006F
+ return le32(auipc) + le32(lw) + le32(jal)
+
+
+def rv_epilogue():
+ return rv_lw('ra', 'sp', 0) + rv_lw('fp', 'sp', 4) + rv_mov_rr('sp', 'fp')
+
+
+def encode_li(_arch, row):
+ return rv_lit32_padded64_prefix(row.rd)
+
+
+def encode_la(_arch, row):
+ return rv_lit32_prefix(row.rd)
+
+
+def encode_labr(_arch, _row):
+ return rv_lit32_prefix('br')
+
+
+def encode_mov(_arch, row):
+ # The portable stack pointer starts after the two-word hidden header.
+ if row.rs == 'sp':
+ return rv_addi(row.rd, 'sp', 8)
+ return rv_mov_rr(row.rd, row.rs)
+
+
+def encode_rrr(_arch, row):
+ return rv_r_type(RRR_BASE[row.op], row.rd, row.ra, row.rb)
+
+
+def encode_addi(_arch, row):
+ return rv_addi(row.rd, row.ra, row.imm)
+
+
+def encode_logi(_arch, row):
+ base = {'ANDI': 0x00007013, 'ORI': 0x00006013}[row.op]
+ return rv_i_type(base, row.rd, row.ra, row.imm)
+
+
+def encode_shifti(_arch, row):
+ if row.op == 'SHLI':
+ return rv_slli(row.rd, row.ra, row.imm)
+ if row.op == 'SHRI':
+ return rv_srli(row.rd, row.ra, row.imm)
+ if row.op == 'SARI':
+ return rv_srai(row.rd, row.ra, row.imm)
+ raise ValueError(f'unknown shift op: {row.op}')
+
+
+def encode_mem(_arch, row):
+ off = row.off + 8 if row.rn == 'sp' else row.off
+ if row.op == 'LD':
+ return rv_lw(row.rt, row.rn, off)
+ if row.op == 'ST':
+ return rv_sw(row.rt, row.rn, off)
+ if row.op == 'LB':
+ return rv_lbu(row.rt, row.rn, off)
+ if row.op == 'SB':
+ return rv_sb(row.rt, row.rn, off)
+ raise ValueError(f'unknown mem op: {row.op}')
+
+
+def encode_ldarg(_arch, row):
+ # [native sp+4] holds caller sp; its portable argument area starts
+ # eight bytes above that native pointer and advances in 4-byte words.
+ return rv_lw('scratch', 'sp', 4) + rv_lw(row.rd, 'scratch', 8 + 4 * row.slot)
+
+
+def encode_branch_reg(_arch, row):
+ if row.kind == 'BR':
+ return rv_jalr('zero', row.rs, 0)
+ if row.kind == 'CALLR':
+ return rv_jalr('ra', row.rs, 0)
+ if row.kind == 'TAILR':
+ return rv_epilogue() + rv_jalr('zero', row.rs, 0)
+ raise ValueError(f'unknown branch-reg kind: {row.kind}')
+
+
+def encode_condb(_arch, row):
+ return rv_b_type_skip8(CONDB_INV_BASE[row.op], row.ra, row.rb) + rv_jalr('zero', 'br', 0)
+
+
+def encode_condbz(_arch, row):
+ return rv_b_type_skip8(CONDBZ_INV_BASE[row.op], row.ra, 'zero') + rv_jalr('zero', 'br', 0)
+
+
+def encode_enter(arch, row):
+ frame_bytes = round_up(arch.stack_align, 2 * arch.word_bytes + row.size)
+ return (
+ rv_addi('sp', 'sp', -frame_bytes)
+ + rv_sw('ra', 'sp', 0)
+ + rv_addi('fp', 'sp', frame_bytes)
+ + rv_sw('fp', 'sp', 4)
+ )
+
+
+def encode_nullary(_arch, row):
+ if row.kind == 'B':
+ return rv_jalr('zero', 'br', 0)
+ if row.kind == 'CALL':
+ return rv_jalr('ra', 'br', 0)
+ if row.kind == 'RET':
+ return rv_jalr('zero', 'ra', 0)
+ if row.kind == 'ERET':
+ return rv_epilogue() + rv_jalr('zero', 'ra', 0)
+ if row.kind == 'TAIL':
+ return rv_epilogue() + rv_jalr('zero', 'br', 0)
+ if row.kind == 'SYSCALL':
+ # Linux's RV32 and RV64 register syscall ABIs are identical.
+ return ''.join([
+ rv_mov_rr('save0', 'a1'),
+ rv_mov_rr('save1', 'a2'),
+ rv_mov_rr('save2', 'a3'),
+ rv_mov_rr('a7', 'a0'),
+ rv_mov_rr('a0', 'save0'),
+ rv_mov_rr('a1', 'save1'),
+ rv_mov_rr('a2', 'save2'),
+ rv_mov_rr('a3', 't0'),
+ rv_mov_rr('a4', 's0'),
+ rv_mov_rr('a5', 's1'),
+ rv_ecall(),
+ rv_mov_rr('a1', 'save0'),
+ rv_mov_rr('a2', 'save1'),
+ rv_mov_rr('a3', 'save2'),
+ ])
+ raise ValueError(f'unknown nullary kind: {row.kind}')
+
+
+def rv_start_stub():
+ def q(hex_bytes):
+ return f"'{hex_bytes}'"
+
+ return [
+ ':_start',
+ q(rv_lw('a0', 'sp', 0)),
+ q(rv_addi('a1', 'sp', 4)),
+ q(rv_lit32_prefix('br')),
+ '&p1_main',
+ q(rv_jalr('ra', 'br', 0)),
+ q(rv_addi('a7', 'zero', 93)),
+ q(rv_ecall()),
+ ]
+
+
+ENCODERS = {
+ Li: encode_li,
+ La: encode_la,
+ LaBr: encode_labr,
+ Mov: encode_mov,
+ Rrr: encode_rrr,
+ AddI: encode_addi,
+ LogI: encode_logi,
+ ShiftI: encode_shifti,
+ Mem: encode_mem,
+ LdArg: encode_ldarg,
+ Nullary: encode_nullary,
+ BranchReg: encode_branch_reg,
+ CondB: encode_condb,
+ CondBZ: encode_condbz,
+ Enter: encode_enter,
+}
+
+
+ARCH = ArchDef(
+ name='riscv32',
+ word_bytes=4,
+ stack_align=16,
+ syscall_numbers=SYSCALL_NUMBERS,
+ encoders=ENCODERS,
+ start_stub=rv_start_stub,
+)
diff --git a/README.md b/README.md
@@ -6,6 +6,11 @@ intermediate stage is small enough to read end-to-end. The compiler that
builds the C compiler is in this repository. So is the kernel that runs
it.
+The complete boot0–boot7 chain targets `aarch64`, `amd64`, and `riscv64`.
+`riscv32` is the first P1-32 target: it bootstraps through boot2 (including
+scheme1 and an RV32-capable `cc.scm`), while TCC and therefore boot3–boot7
+are intentionally left unimplemented.
+
## Writing
A series walking through the chain, one rung at a time:
@@ -86,17 +91,17 @@ turns those bytes into everything else.
Per arch, seven files from
[live-bootstrap](https://github.com/fosslinux/live-bootstrap)'s
stage0-posix; full provenance in [vendor/seed/README.md](vendor/seed/README.md).
-Sizes for `aarch64 / amd64 / riscv64`:
-
-| file | role | bytes (a/x/r) |
-| ----------- | ----------------------------------------- | ------------- |
-| `hex0-seed` | the only opaque ELF; assembles `hex0.hex0` | 526 / 229 / 392 |
-| `hex0.hex0` | hex assembler — source of `hex1` | 9763 / 6387 / 8065 |
-| `hex1.hex0` | hex assembler with labels | 18971 / 10784 / 27080 |
-| `hex2.hex1` | hex assembler with ELF-aware linking | 31017 / 24767 / 39860 |
-| `catm.hex2` | concatenates files | 6456 / 5468 / 6231 |
-| `M0.hex2` | macro stage above hex2 | 50189 / 43551 / 65364 |
-| `ELF.hex2` | ELF header preamble | 2981 / 2672 / 2661 |
+Sizes for `aarch64 / amd64 / riscv64 / riscv32`:
+
+| file | role | bytes (a/x/r64/r32) |
+| ----------- | ----------------------------------------- | ------------------------- |
+| `hex0-seed` | the only opaque ELF; assembles `hex0.hex0` | 526 / 229 / 392 / 356 |
+| `hex0.hex0` | hex assembler — source of `hex1` | 9763 / 6387 / 8065 / 7994 |
+| `hex1.hex0` | hex assembler with labels | 18971 / 10784 / 27080 / 27107 |
+| `hex2.hex1` | hex assembler with ELF-aware linking | 31017 / 24767 / 39860 / 39968 |
+| `catm.hex2` | concatenates files | 6456 / 5468 / 6231 / 6171 |
+| `M0.hex2` | macro stage above hex2 | 50189 / 43551 / 65364 / 65307 |
+| `ELF.hex2` | ELF header preamble | 2981 / 2672 / 2661 / 2573 |
Every one of these except `hex0-seed` is a textual hex file you can
read. `hex0-seed` itself is a few hundred bytes; it is the smallest
@@ -120,12 +125,12 @@ later in the chain (`tcc-0.9.26`, `musl-1.2.5`):
| layer | files | LoC |
| ------------- | -------------------------------------------- | ----- |
-| M1pp | `M1pp/M1pp.P1` | 5000 |
-| hex2pp | `hex2pp/hex2pp.P1` | 3087 |
-| P1 | `P1/{P1.M1pp, P1pp.P1pp, P1-<arch>.M1pp, …}` | 3236 |
+| M1pp | `M1pp/M1pp.P1` | 5003 |
+| hex2pp | `hex2pp/hex2pp.P1` | 3089 |
+| P1 | `P1/{P1.M1pp, P1pp.P1pp, P1-<arch>.M1pp, …}` | 4071 |
| catm | `catm/catm.P1pp` | 105 |
-| scheme1 | `scheme1/{scheme1.P1pp, prelude.scm}` | 4842 |
-| cc | `cc/cc.scm` | 5173 |
+| scheme1 | `scheme1/{scheme1.P1pp, prelude.scm}` | 6769 |
+| cc | `cc/cc.scm` | 5866 |
| mes-libc | `vendor/mes-libc/libc.c` | 1019 |
| seed-kernel | `seed-kernel/{kernel.c, arch/<arch>/*}` | ~1700 (incl. asm) |
@@ -149,7 +154,9 @@ files crossed by the chain, and the order in which to read them, is in
## Architectures × drivers
-`DRIVER={podman,seed} × ARCH={aarch64,amd64,riscv64}`
+The full chain is `DRIVER={podman,seed} ×
+ARCH={aarch64,amd64,riscv64}`. RV32 currently supports
+`DRIVER=podman × ARCH=riscv32` through boot2 only.
`DRIVER` selects the runtime that executes the compiling `bootN` stages;
boot7 is a host-side installation step over that driver's verified outputs:
@@ -165,6 +172,11 @@ boot7 is a host-side installation step over that driver's verified outputs:
Both drivers write to disjoint trees (`build/<arch>/<driver>/...`), so
they coexist.
+OCI has no RV32 platform identifier. Its Podman build therefore uses a
+native controller image and executes the RV32 target binaries through the
+Podman VM's `qemu-riscv32` binfmt handler. `DRIVER=seed` is unavailable until
+the RV32 TCC/kernel stages exist.
+
## Building
End-to-end via the driver script:
@@ -172,6 +184,7 @@ End-to-end via the driver script:
```sh
./boot/boot.sh aarch64 # default DRIVER=podman
DRIVER=seed ./boot/boot.sh aarch64 # re-run on the tcc-built kernel
+./boot/boot.sh riscv32 # boot0 -> boot2; stops before TCC
./boot/boot.sh --help # env vars (DRIVER, BOOT*_TIMEOUT, …)
```
@@ -183,11 +196,13 @@ make build/aarch64/podman/boot6/Image # kernel branch
make build/amd64/podman/boot6/kernel.elf
make build/aarch64/podman/boot7/toolchain/MANIFEST.sha256
make build/riscv64/podman/boot1/M1pp # only prep-src + boot0 + boot1
+make all ARCH=riscv32 # implemented RV32 chain: boot0..2
make all ARCH=aarch64 DRIVER=podman # kernel + installed toolchain
make help # target list
```
-Per-stage outputs land at `build/<arch>/<driver>/boot{0..7}/`; the
+Full-chain per-stage outputs land at `build/<arch>/<driver>/boot{0..7}/`;
+RV32 currently produces `boot{0..2}/`. The
canonical generated source tree (used by every stage) is at
`build/<arch>/src/`.
@@ -218,6 +233,9 @@ ARCH=aarch64 # or amd64, riscv64
make release ARCH="$ARCH" DRIVER=podman
```
+Packaging and release targets require boot7 and therefore reject `riscv32`
+with the same explicit TCC-unimplemented boundary.
+
**Warning:** `make release` deliberately removes the entire `build/` tree
before the build and again before the canonical-input check. The publishable
results are:
@@ -263,6 +281,9 @@ Suites: `m1pp`, `p1`, `scheme1`, `cc-util`, `cc-lex`, `cc-pp`, `cc-cg`,
`cc`, `cc-libc`, `cc-ext`, `tcc-cc`, `tcc-libc`. Full per-suite contract
in [tests/README.md](tests/README.md).
+RV32 runs the core suites through `cc`. The `cc-libc`, `cc-ext`, and `tcc-*`
+suites require boot3/TCC artifacts and are intentionally unavailable there.
+
`tests/seed-accept.sh` is a separate seed-driver acceptance harness that
diffs seed-built vs podman-built artifacts for byte equivalence; see
that script's header for modes.
diff --git a/boot/boot.sh b/boot/boot.sh
@@ -6,10 +6,13 @@ set -eu
case "${1:-}" in
-h|--help)
cat <<'EOF'
-boot.sh — drive boot0 → boot7 end-to-end under one driver.
+boot.sh — drive the implemented bootstrap chain under one driver.
Usage:
- boot/boot.sh <aarch64|amd64|riscv64>
+ boot/boot.sh <aarch64|amd64|riscv64|riscv32>
+
+RV32 currently runs boot0 → boot2. Its TCC target (boot3+) is deliberately
+left unimplemented.
Environment variables (all optional):
DRIVER podman (default) | seed.
@@ -48,6 +51,10 @@ rm -rf build/$ARCH/$DRIVER
./boot/boot0.sh $ARCH
./boot/boot1.sh $ARCH
./boot/boot2.sh $ARCH
+if [ "$TCC_SUPPORTED" != 1 ]; then
+ echo "[$BOOT_TAG] OK: RV32 bootstrap completed through boot2; TCC support (boot3+) is intentionally unimplemented"
+ exit 0
+fi
./boot/boot3.sh $ARCH
./boot/boot4.sh $ARCH
./boot/boot5.sh $ARCH
diff --git a/boot/boot3.sh b/boot/boot3.sh
@@ -43,6 +43,7 @@ set -eu
. boot/lib-arch.sh
bootlib_init boot3 "${1:-}"
+require_tcc_target
driver_init empty
require_src
diff --git a/boot/boot4.sh b/boot/boot4.sh
@@ -56,6 +56,7 @@ set -eu
. boot/lib-arch.sh
bootlib_init boot4 "${1:-}"
+require_tcc_target
driver_init empty
require_src
diff --git a/boot/boot5.sh b/boot/boot5.sh
@@ -36,6 +36,7 @@ set -eu
. boot/lib-arch.sh
bootlib_init boot5 "${1:-}"
+require_tcc_target
driver_init empty
require_src
diff --git a/boot/boot6.sh b/boot/boot6.sh
@@ -35,6 +35,7 @@ set -eu
. boot/lib-arch.sh
bootlib_init boot6 "${1:-}"
+require_tcc_target
driver_init empty
require_src
diff --git a/boot/boot7.sh b/boot/boot7.sh
@@ -26,6 +26,7 @@ set -eu
. boot/lib-arch.sh
bootlib_init boot7 "${1:-}"
+require_tcc_target
require_src
OUT=build/$ARCH/$DRIVER/boot7
diff --git a/boot/lib-arch.sh b/boot/lib-arch.sh
@@ -17,12 +17,15 @@
# # uniform diagnostic with hint.
#
# After bootlib_init, the following shell vars are set/exported:
-# ARCH input architecture token (aarch64|amd64|riscv64)
+# ARCH input architecture token (aarch64|amd64|riscv64|riscv32)
# ROOT repo root (cwd is set to ROOT)
# DRIVER podman|seed (defaults to podman)
-# PLATFORM linux/<arm64|amd64|riscv64> for podman --platform
+# PLATFORM target platform for podman, empty when the controller
+# container deliberately stays native (riscv32)
+# PODMAN_PLATFORM_ARGS optional `--platform=...` argument
# KERNEL_NAME Image (aarch64) | kernel.elf (amd64,riscv64)
-# MUSL_ARCH aarch64 | x86_64 | riscv64
+# MUSL_ARCH aarch64 | x86_64 | riscv64 (empty before the RV32 TCC port)
+# TCC_SUPPORTED 1 when boot3+ is implemented, otherwise 0
# BOOT_TAG "<stage>/<driver>/<arch>" for log prefixes
# BOOT_STAGE stage name as passed in (boot0|boot1|...)
#
@@ -36,8 +39,8 @@ bootlib_init() {
_stage=$1; _arch=${2:-}
[ -n "$_stage" ] || { echo "lib-arch: bootlib_init: stage required" >&2; exit 2; }
case "$_arch" in
- aarch64|amd64|riscv64) ;;
- *) echo "usage: $0 <aarch64|amd64|riscv64>" >&2; exit 2 ;;
+ aarch64|amd64|riscv64|riscv32) ;;
+ *) echo "usage: $0 <aarch64|amd64|riscv64|riscv32>" >&2; exit 2 ;;
esac
ARCH=$_arch
ROOT=$(cd "$(dirname "$0")/.." && pwd)
@@ -51,11 +54,21 @@ bootlib_init() {
BOOT_TAG="$_stage/$DRIVER/$ARCH"
BOOT_T0=$(date +%s)
case "$ARCH" in
- aarch64) PLATFORM=linux/arm64; KERNEL_NAME=Image; MUSL_ARCH=aarch64 ;;
- amd64) PLATFORM=linux/amd64; KERNEL_NAME=kernel.elf; MUSL_ARCH=x86_64 ;;
- riscv64) PLATFORM=linux/riscv64; KERNEL_NAME=kernel.elf; MUSL_ARCH=riscv64 ;;
+ aarch64) PLATFORM=linux/arm64; KERNEL_NAME=Image; MUSL_ARCH=aarch64; TCC_SUPPORTED=1 ;;
+ amd64) PLATFORM=linux/amd64; KERNEL_NAME=kernel.elf; MUSL_ARCH=x86_64; TCC_SUPPORTED=1 ;;
+ riscv64) PLATFORM=linux/riscv64; KERNEL_NAME=kernel.elf; MUSL_ARCH=riscv64; TCC_SUPPORTED=1 ;;
+ # No OCI image platform names RV32. Keep the controller image native
+ # and let the host's qemu-riscv32 binfmt handler execute target tools.
+ riscv32) PLATFORM=; KERNEL_NAME=; MUSL_ARCH=; TCC_SUPPORTED=0 ;;
esac
- export ARCH ROOT DRIVER PLATFORM KERNEL_NAME MUSL_ARCH BOOT_TAG BOOT_STAGE BOOT_T0
+ PODMAN_PLATFORM_ARGS=
+ [ -n "$PLATFORM" ] && PODMAN_PLATFORM_ARGS="--platform=$PLATFORM"
+ if [ "$ARCH" = riscv32 ] && [ "$DRIVER" = seed ]; then
+ echo "[$BOOT_TAG] DRIVER=seed is unavailable for riscv32 (boot6 requires the unimplemented TCC target)" >&2
+ exit 2
+ fi
+ export ARCH ROOT DRIVER PLATFORM PODMAN_PLATFORM_ARGS KERNEL_NAME MUSL_ARCH \
+ TCC_SUPPORTED BOOT_TAG BOOT_STAGE BOOT_T0
trap _bootlib_finish EXIT
}
@@ -110,14 +123,40 @@ driver_init() {
export OUT STAGE
case "$DRIVER" in
podman)
+ # RV32 has no OCI platform identifier. Resolve the Podman
+ # server's native architecture here (rather than the client
+ # host's uname) and use that for the controller image. Target
+ # executables still run through the server's RV32 binfmt entry.
+ if [ "$ARCH" = riscv32 ]; then
+ _controller_arch=$(podman info --format '{{.Host.Arch}}')
+ case "$_controller_arch" in
+ aarch64) _controller_arch=arm64 ;;
+ x86_64) _controller_arch=amd64 ;;
+ arm64|amd64|riscv64) ;;
+ *) echo "[$BOOT_TAG] unsupported Podman controller architecture: $_controller_arch" >&2; exit 2 ;;
+ esac
+ PLATFORM=linux/$_controller_arch
+ PODMAN_PLATFORM_ARGS="--platform=$PLATFORM"
+ export PLATFORM PODMAN_PLATFORM_ARGS
+ fi
IMAGE=boot2-$_image_kind:$ARCH
+ _build_image=0
if ! podman image exists "$IMAGE"; then
+ _build_image=1
+ elif [ "$ARCH" = riscv32 ]; then
+ _image_arch=$(podman image inspect "$IMAGE" --format '{{.Architecture}}')
+ [ "$_image_arch" = "$_controller_arch" ] || {
+ echo "[$BOOT_TAG] rebuilding $IMAGE for native controller $_controller_arch (was $_image_arch)"
+ _build_image=1
+ }
+ fi
+ if [ "$_build_image" = 1 ]; then
echo "[$BOOT_TAG] building $IMAGE"
# Containerfile.empty drops /etc resolver state etc.; no-cache
# avoids a stale layer surviving an upstream tag bump.
_no_cache=
[ "$_image_kind" = empty ] && _no_cache=--no-cache
- podman build $_no_cache --platform "$PLATFORM" -t "$IMAGE" \
+ podman build $_no_cache $PODMAN_PLATFORM_ARGS -t "$IMAGE" \
-f boot/containers/Containerfile.$_image_kind boot/containers/
fi
export IMAGE
@@ -138,6 +177,16 @@ driver_init() {
esac
}
+# Stop at the intentional RV32 boundary with one consistent diagnostic.
+# Keep this in the shared library so direct boot3..boot7 invocations fail
+# before probing for TCC, musl, or seed-kernel inputs that do not exist yet.
+require_tcc_target() {
+ [ "$TCC_SUPPORTED" = 1 ] || {
+ echo "[$BOOT_TAG] TCC support is intentionally unimplemented for $ARCH; RV32 currently ends after boot2" >&2
+ exit 2
+ }
+}
+
require_prev() {
_dir=$1; shift
for _n in "$@"; do
diff --git a/boot/lib-pipeline.sh b/boot/lib-pipeline.sh
@@ -30,7 +30,7 @@
# names. <bin> is also a name in the flat namespace — typically a
# pipeline_input, but may be the output of an earlier stage.
#
-# Required env for podman driver: PLATFORM, IMAGE.
+# Required env for podman driver: IMAGE, PODMAN_PLATFORM_ARGS (may be empty).
# Required env for seed driver: KERNEL_IMAGE, EXTRACT.
P_DRIVER=
@@ -286,7 +286,6 @@ pipeline_run() {
}
_run_podman() {
- : "${PLATFORM:?lib-pipeline:podman: PLATFORM not set}"
: "${IMAGE:?lib-pipeline:podman: IMAGE not set}"
if [ -n "$P_EXPORTS" ]; then
cmd="cp"
@@ -296,7 +295,7 @@ _run_podman() {
fi
chmod +x "$P_SCRIPT"
SDIR=$(cd "$P_STAGE_DIR" && pwd)
- podman run --rm -i --pull=never --platform "$PLATFORM" \
+ podman run --rm -i --pull=never $PODMAN_PLATFORM_ARGS \
--tmpfs /tmp:size=512M \
-v "$SDIR/run.sh:/work/run.sh:ro" \
-v "$SDIR/in:/work/in:ro" \
diff --git a/boot/lib-runscm.sh b/boot/lib-runscm.sh
@@ -27,7 +27,7 @@
# runscm_run [timeout-s] # default 600s
#
# Required env per driver:
-# podman: IMAGE, PLATFORM
+# podman: IMAGE, PODMAN_PLATFORM_ARGS (may be empty)
# seed: KERNEL_IMAGE, EXTRACT, optional QEMU_MEM (default 2048M)
# both: DRIVER=podman|seed
@@ -125,11 +125,10 @@ runscm_run() {
# Outputs land in $S_STAGE_DIR/out/ directly via the rw bind mount.
_runscm_run_podman() {
: "${IMAGE:?lib-runscm: IMAGE not set}"
- : "${PLATFORM:?lib-runscm: PLATFORM not set}"
in_abs=$(cd "$S_STAGE_DIR/in" && pwd)
out_abs=$(cd "$S_STAGE_DIR/out" && pwd)
echo "[runscm/podman] scheme1 combined.scm under $IMAGE" >&2
- podman run --rm -i --pull=never --platform "$PLATFORM" \
+ podman run --rm -i --pull=never $PODMAN_PLATFORM_ARGS \
-v "$in_abs:/work/in:ro" \
-v "$out_abs:/work/out:rw" \
-w /work "$IMAGE" \
diff --git a/bootprep/prep-src.sh b/bootprep/prep-src.sh
@@ -43,7 +43,7 @@
## changes.
##
## Usage: bootprep/prep-src.sh <arch>
-## <arch> ∈ {aarch64, amd64, riscv64}
+## <arch> ∈ {aarch64, amd64, riscv64, riscv32}
set -eu
@@ -95,6 +95,14 @@ mkdir -p "$DST_SRC/scheme1"
cp scheme1/scheme1.P1pp "$DST_SRC/scheme1/scheme1.P1pp"
cp scheme1/prelude.scm "$DST_SRC/scheme1/prelude.scm"
+# RV32 is supported through the native P1/Scheme bootstrap. TCC and all
+# consumers of it are a separate target port, intentionally not present yet.
+if [ "$TCC_SUPPORTED" != 1 ]; then
+ n_files=$(find "$DST" -type f | wc -l | tr -d ' ')
+ echo "$TAG OK -> $DST ($n_files files; boot0..boot2 sources, TCC intentionally omitted)"
+ exit 0
+fi
+
mkdir -p "$DST_SRC/cc"
cp cc/cc.scm "$DST_SRC/cc/cc.scm"
cp cc/main.scm "$DST_SRC/cc/main.scm"
diff --git a/catm/catm.P1pp b/catm/catm.P1pp
@@ -46,7 +46,7 @@
%bltu(s0, t0, &.usage)
# Open OUT = argv[1].
- %ld(a0, s1, 8)
+ %ld(a0, s1, %p1_word_bytes)
%li(a1, %CATM_O_WRONLY_CREAT_TRUNC)
%li(a2, %CATM_MODE_0640)
%call(&sys_open)
@@ -59,7 +59,7 @@
%beq(s3, s0, &.arg_done) # i == argc -> done
# in_path = argv[i]
- %shli(t0, s3, 3)
+ %shli(t0, s3, %p1_word_shift)
%add(t0, s1, t0)
%ld(a0, t0, 0)
%li(a1, %CATM_O_RDONLY)
diff --git a/cc/cc.scm b/cc/cc.scm
@@ -117,6 +117,427 @@
(bit-and (+ n mask) (bit-not mask))))
;; --------------------------------------------------------------------
+;; Fixed-width C integer carrier
+;; --------------------------------------------------------------------
+;;
+;; scheme1 fixnums have three tag bits, so a P1-32 host cannot carry every
+;; 32-bit C value directly (and a P1-64 host cannot carry every u64 either).
+;; Keep out-of-range C values as an exact, little-endian 64-bit bytevector.
+;; Small results normalize back to fixnums so sizes, offsets, and loop counts
+;; keep using the cheap representation. All helpers accept either shape.
+
+(define-record-type c-value
+ (%c-value bytes)
+ c-value?
+ (bytes c-value-bytes))
+
+(define-record-type c-int-lit
+ (%c-int-lit value unsigned? long-count decimal?)
+ c-int-lit?
+ (value c-int-lit-value)
+ (unsigned? c-int-lit-unsigned?)
+ (long-count c-int-lit-long-count)
+ (decimal? c-int-lit-decimal?))
+
+(define %C-VALUE-BYTES 8)
+
+(define (%c-value-coerce v)
+ (cond
+ ((c-int-lit? v) (%c-value-coerce (c-int-lit-value v)))
+ ((c-value? v) v)
+ (else
+ (let ((bv (make-bytevector %C-VALUE-BYTES 0)))
+ (let loop ((i 0) (n v))
+ (cond
+ ((= i %C-VALUE-BYTES) (%c-value bv))
+ (else
+ (bytevector-u8-set! bv i (bit-and n 255))
+ (loop (+ i 1) (arithmetic-shift n -8)))))))))
+
+(define (%c-value-copy v)
+ (let ((bv (c-value-bytes (%c-value-coerce v))))
+ (%c-value (bytevector-copy bv 0 (bytevector-length bv)))))
+
+(define (%c-value-zero) (%c-value (make-bytevector %C-VALUE-BYTES 0)))
+
+(define (%c-value-zero? v)
+ (cond
+ ((not (c-value? v)) (= v 0))
+ (else
+ (let ((bv (c-value-bytes v)))
+ (let loop ((i 0))
+ (cond ((= i %C-VALUE-BYTES) #t)
+ ((not (= (bytevector-u8-ref bv i) 0)) #f)
+ (else (loop (+ i 1)))))))))
+
+(define (%c-value-positive-small cv)
+ ;; Conservative common subset of both Scheme fixnum ranges: [0,2^27).
+ (let ((bv (c-value-bytes cv)))
+ (cond
+ ((or (not (= (bytevector-u8-ref bv 7) 0))
+ (not (= (bytevector-u8-ref bv 6) 0))
+ (not (= (bytevector-u8-ref bv 5) 0))
+ (not (= (bytevector-u8-ref bv 4) 0))
+ (>= (bytevector-u8-ref bv 3) 8)) #f)
+ (else
+ (let loop ((i 3) (n 0))
+ (cond ((< i 0) n)
+ (else
+ (loop (- i 1)
+ (+ (* n 256) (bytevector-u8-ref bv i))))))))))
+
+(define (%c-value-negate/raw v)
+ (let* ((src (c-value-bytes (%c-value-coerce v)))
+ (out (make-bytevector %C-VALUE-BYTES 0)))
+ (let loop ((i 0) (carry 1))
+ (cond
+ ((= i %C-VALUE-BYTES) (%c-value out))
+ (else
+ (let ((z (+ (- 255 (bytevector-u8-ref src i)) carry)))
+ (bytevector-u8-set! out i (bit-and z 255))
+ (loop (+ i 1) (if (> z 255) 1 0))))))))
+
+(define (%c-value-normalize v)
+ (cond
+ ((not (c-value? v)) v)
+ (else
+ (let ((p (%c-value-positive-small v)))
+ (cond
+ (p p)
+ ((>= (bytevector-u8-ref (c-value-bytes v) 7) 128)
+ (let* ((mag (%c-value-negate/raw v))
+ (m (%c-value-positive-small mag)))
+ (if m (- 0 m) v)))
+ (else v))))))
+
+(define (%c-value-normalize-unsigned v)
+ ;; Lexer accumulation starts from an unsigned source spelling. Preserve
+ ;; values with bit 63 set as c-values rather than collapsing (for example)
+ ;; 18446744073709551615 to the signed fixnum -1.
+ (cond
+ ((not (c-value? v)) v)
+ (else
+ (let ((p (%c-value-positive-small v)))
+ (if p p v)))))
+
+(define (%c-value->fixnum v context)
+ (let ((n (%c-value-normalize v)))
+ (cond ((c-value? n) (die #f context "value does not fit host fixnum"))
+ (else n))))
+
+(define (%c-value-mul-small-add v scale addend)
+ ;; Used by the lexer. scale <= 16 and addend <= 15, so every step is
+ ;; far below even RV32's fixnum ceiling.
+ (let* ((src (c-value-bytes (%c-value-coerce v)))
+ (out (make-bytevector %C-VALUE-BYTES 0)))
+ (let loop ((i 0) (carry addend))
+ (cond
+ ((= i %C-VALUE-BYTES) (%c-value-normalize-unsigned (%c-value out)))
+ (else
+ (let ((z (+ (* (bytevector-u8-ref src i) scale) carry)))
+ (bytevector-u8-set! out i (remainder z 256))
+ (loop (+ i 1) (quotient z 256))))))))
+
+(define (%c-value-add a b)
+ (let* ((av (c-value-bytes (%c-value-coerce a)))
+ (bv (c-value-bytes (%c-value-coerce b)))
+ (out (make-bytevector %C-VALUE-BYTES 0)))
+ (let loop ((i 0) (carry 0))
+ (cond
+ ((= i %C-VALUE-BYTES) (%c-value-normalize (%c-value out)))
+ (else
+ (let ((z (+ (bytevector-u8-ref av i)
+ (bytevector-u8-ref bv i) carry)))
+ (bytevector-u8-set! out i (remainder z 256))
+ (loop (+ i 1) (quotient z 256))))))))
+
+(define (%c-value-negate v)
+ (%c-value-normalize (%c-value-negate/raw v)))
+
+(define (%c-value-sub a b)
+ (%c-value-add a (%c-value-negate/raw b)))
+
+(define (%c-value-mul a b)
+ (let* ((av (c-value-bytes (%c-value-coerce a)))
+ (bv (c-value-bytes (%c-value-coerce b)))
+ (out (make-bytevector %C-VALUE-BYTES 0)))
+ (let outer ((i 0))
+ (cond
+ ((= i %C-VALUE-BYTES) (%c-value-normalize (%c-value out)))
+ (else
+ (let inner ((j 0) (carry 0))
+ (cond
+ ((= (+ i j) %C-VALUE-BYTES) (outer (+ i 1)))
+ (else
+ (let* ((k (+ i j))
+ (z (+ (bytevector-u8-ref out k)
+ (* (bytevector-u8-ref av i)
+ (bytevector-u8-ref bv j))
+ carry)))
+ (bytevector-u8-set! out k (remainder z 256))
+ (inner (+ j 1) (quotient z 256)))))))))))
+
+(define (%c-value-bitop op a b)
+ (let* ((av (c-value-bytes (%c-value-coerce a)))
+ (bv (c-value-bytes (%c-value-coerce b)))
+ (out (make-bytevector %C-VALUE-BYTES 0)))
+ (let loop ((i 0))
+ (cond
+ ((= i %C-VALUE-BYTES) (%c-value-normalize (%c-value out)))
+ (else
+ (bytevector-u8-set! out i
+ (op (bytevector-u8-ref av i) (bytevector-u8-ref bv i)))
+ (loop (+ i 1)))))))
+
+(define (%c-value-and a b) (%c-value-bitop bit-and a b))
+(define (%c-value-or a b) (%c-value-bitop bit-or a b))
+(define (%c-value-xor a b) (%c-value-bitop bit-xor a b))
+(define (%c-value-not a) (%c-value-xor a -1))
+
+(define (%c-value-ucmp a b)
+ ;; -1 / 0 / 1 under unsigned 64-bit ordering.
+ (let ((av (c-value-bytes (%c-value-coerce a)))
+ (bv (c-value-bytes (%c-value-coerce b))))
+ (let loop ((i (- %C-VALUE-BYTES 1)))
+ (cond ((< i 0) 0)
+ ((< (bytevector-u8-ref av i) (bytevector-u8-ref bv i)) -1)
+ ((> (bytevector-u8-ref av i) (bytevector-u8-ref bv i)) 1)
+ (else (loop (- i 1)))))))
+
+(define (%c-value-eq? a b) (= (%c-value-ucmp a b) 0))
+
+(define (%c-value-trunc v nbytes signed?)
+ (let* ((src (c-value-bytes (%c-value-coerce v)))
+ (out (bytevector-copy src 0 (bytevector-length src)))
+ (fill (if (and signed? (> nbytes 0)
+ (>= (bytevector-u8-ref src (- nbytes 1)) 128))
+ 255 0)))
+ (let loop ((i nbytes))
+ (cond ((= i %C-VALUE-BYTES)
+ (%c-value-normalize (%c-value out)))
+ (else
+ (bytevector-u8-set! out i fill)
+ (loop (+ i 1)))))))
+
+(define (%c-value-signed-negative? v nbytes)
+ (and (> nbytes 0)
+ (>= (bytevector-u8-ref
+ (c-value-bytes (%c-value-coerce v)) (- nbytes 1))
+ 128)))
+
+(define (%c-value-cmp a b nbytes unsigned?)
+ (let ((aa (%c-value-trunc a nbytes (not unsigned?)))
+ (bb (%c-value-trunc b nbytes (not unsigned?))))
+ (cond
+ (unsigned? (%c-value-ucmp aa bb))
+ ((and (%c-value-signed-negative? aa %C-VALUE-BYTES)
+ (not (%c-value-signed-negative? bb %C-VALUE-BYTES))) -1)
+ ((and (not (%c-value-signed-negative? aa %C-VALUE-BYTES))
+ (%c-value-signed-negative? bb %C-VALUE-BYTES)) 1)
+ (else (%c-value-ucmp aa bb)))))
+
+(define (%c-value-shift-one-left/raw cv)
+ (let* ((src (c-value-bytes cv))
+ (out (make-bytevector %C-VALUE-BYTES 0)))
+ (let loop ((i 0) (carry 0))
+ (cond ((= i %C-VALUE-BYTES) (%c-value out))
+ (else
+ (let ((z (+ (* (bytevector-u8-ref src i) 2) carry)))
+ (bytevector-u8-set! out i (remainder z 256))
+ (loop (+ i 1) (quotient z 256))))))))
+
+(define (%c-value-shift-one-right/raw cv arithmetic?)
+ (let* ((src (c-value-bytes cv))
+ (out (make-bytevector %C-VALUE-BYTES 0))
+ (initial (if (and arithmetic?
+ (>= (bytevector-u8-ref src 7) 128)) 1 0)))
+ (let loop ((i 7) (carry initial))
+ (cond ((< i 0) (%c-value out))
+ (else
+ (let ((byte (bytevector-u8-ref src i)))
+ (bytevector-u8-set! out i
+ (+ (quotient byte 2) (* carry 128)))
+ (loop (- i 1) (remainder byte 2))))))))
+
+(define (%c-value-shift v count arithmetic-right?)
+ (let ((start (%c-value-coerce v)))
+ (cond
+ ((>= count 64)
+ (if (and arithmetic-right?
+ (%c-value-signed-negative? start %C-VALUE-BYTES))
+ -1 0))
+ ((<= count -64) 0)
+ ((>= count 0)
+ (let loop ((n count) (x start))
+ (cond ((= n 0) (%c-value-normalize x))
+ (else
+ (loop (- n 1)
+ (%c-value-shift-one-right/raw x arithmetic-right?))))))
+ (else
+ (let loop ((n (- 0 count)) (x start))
+ (cond ((= n 0) (%c-value-normalize x))
+ (else
+ (loop (- n 1) (%c-value-shift-one-left/raw x)))))))))
+
+(define (%c-value-bv-sub! dst rhs)
+ (let loop ((i 0) (borrow 0))
+ (cond
+ ((= i %C-VALUE-BYTES) dst)
+ (else
+ (let ((z (- (bytevector-u8-ref dst i)
+ (bytevector-u8-ref rhs i) borrow)))
+ (cond ((< z 0)
+ (bytevector-u8-set! dst i (+ z 256))
+ (loop (+ i 1) 1))
+ (else
+ (bytevector-u8-set! dst i z)
+ (loop (+ i 1) 0))))))))
+
+(define (%c-value-bv-shl1! bv incoming)
+ (let loop ((i 0) (carry incoming))
+ (cond ((= i %C-VALUE-BYTES) bv)
+ (else
+ (let ((z (+ (* (bytevector-u8-ref bv i) 2) carry)))
+ (bytevector-u8-set! bv i (remainder z 256))
+ (loop (+ i 1) (quotient z 256)))))))
+
+(define (%c-value-bv-ucmp a b)
+ (let loop ((i 7))
+ (cond ((< i 0) 0)
+ ((< (bytevector-u8-ref a i) (bytevector-u8-ref b i)) -1)
+ ((> (bytevector-u8-ref a i) (bytevector-u8-ref b i)) 1)
+ (else (loop (- i 1))))))
+
+(define (%c-value-udivmod a b)
+ ;; Returns (quotient . remainder), both c-values. Binary long division
+ ;; keeps every Scheme temporary byte-sized.
+ (let* ((num (c-value-bytes (%c-value-coerce a)))
+ (den (c-value-bytes (%c-value-coerce b)))
+ (q (make-bytevector %C-VALUE-BYTES 0))
+ (r (make-bytevector %C-VALUE-BYTES 0)))
+ (cond ((%c-value-zero? (%c-value den))
+ (die #f "C constant division by zero")))
+ (let loop ((bit 63))
+ (cond
+ ((< bit 0)
+ (cons (%c-value-normalize (%c-value q))
+ (%c-value-normalize (%c-value r))))
+ (else
+ (let* ((byte-index (quotient bit 8))
+ (bit-index (remainder bit 8))
+ (incoming
+ (bit-and (arithmetic-shift
+ (bytevector-u8-ref num byte-index)
+ (- 0 bit-index))
+ 1)))
+ (%c-value-bv-shl1! r incoming)
+ (cond
+ ((>= (%c-value-bv-ucmp r den) 0)
+ (%c-value-bv-sub! r den)
+ (bytevector-u8-set! q byte-index
+ (bit-or (bytevector-u8-ref q byte-index)
+ (arithmetic-shift 1 bit-index)))))
+ (loop (- bit 1))))))))
+
+(define (%c-value-divmod a b nbytes unsigned?)
+ (let* ((aa (%c-value-trunc a nbytes (not unsigned?)))
+ (bb (%c-value-trunc b nbytes (not unsigned?)))
+ (aneg (and (not unsigned?)
+ (%c-value-signed-negative? aa %C-VALUE-BYTES)))
+ (bneg (and (not unsigned?)
+ (%c-value-signed-negative? bb %C-VALUE-BYTES)))
+ (amag (if aneg (%c-value-negate/raw aa) aa))
+ (bmag (if bneg (%c-value-negate/raw bb) bb))
+ (qr (%c-value-udivmod amag bmag))
+ (q (if (if aneg (not bneg) bneg)
+ (%c-value-negate (car qr)) (car qr)))
+ (r (if aneg (%c-value-negate (cdr qr)) (cdr qr))))
+ (cons (%c-value-trunc q nbytes (not unsigned?))
+ (%c-value-trunc r nbytes (not unsigned?)))))
+
+(define (%c-value->decimal-bv v)
+ (cond
+ ((not (c-value? v)) (fixnum->bv v 10))
+ ((%c-value-zero? v) "0")
+ (else
+ (let* ((src (c-value-bytes v))
+ (work (bytevector-copy src 0 (bytevector-length src))))
+ (let digits ((acc '()))
+ (let divide ((i 7) (rem 0))
+ (cond
+ ((< i 0)
+ (let ((acc2 (cons (bv-of-byte (+ 48 rem)) acc)))
+ (if (= (%c-value-bv-ucmp work
+ (make-bytevector %C-VALUE-BYTES 0)) 0)
+ (bv-cat acc2)
+ (digits acc2))))
+ (else
+ (let ((z (+ (* rem 256) (bytevector-u8-ref work i))))
+ (bytevector-u8-set! work i (quotient z 10))
+ (divide (- i 1) (remainder z 10)))))))))))
+
+(define (%c-value->hex-bv v)
+ (let* ((src (c-value-bytes (%c-value-coerce v)))
+ (hex "0123456789abcdef")
+ (out (make-bytevector 18 48)))
+ (bytevector-u8-set! out 0 48)
+ (bytevector-u8-set! out 1 120)
+ (let loop ((i 7) (j 2))
+ (cond ((< i 0) out)
+ (else
+ (let ((b (bytevector-u8-ref src i)))
+ (bytevector-u8-set! out j
+ (bytevector-u8-ref hex (quotient b 16)))
+ (bytevector-u8-set! out (+ j 1)
+ (bytevector-u8-ref hex (remainder b 16)))
+ (loop (- i 1) (+ j 2))))))))
+
+(define (%c-value-source-bv v)
+ (%c-value->decimal-bv (if (c-int-lit? v) (c-int-lit-value v) v)))
+(define (%c-value-literal-bv v)
+ (let ((n (if (c-int-lit? v) (c-int-lit-value v) v)))
+ (if (c-value? n) (%c-value->hex-bv n) (fixnum->bv n 10))))
+
+(define (%c-value-u32-literal-bv v word-index)
+ ;; Render one little-endian 32-bit limb without ever converting it to a
+ ;; Scheme integer. This is used when an RV32 %li materializes one half of
+ ;; an i64/u64 C constant.
+ (let ((full (%c-value->hex-bv v)))
+ (cond
+ ((= word-index 0) (bv-cat (list "0x" (bv-slice full 10 18))))
+ ((= word-index 1) (bv-cat (list "0x" (bv-slice full 2 10))))
+ (else (die #f "C constant: bad u32 limb index" word-index)))))
+
+(define (%c-value-parse-decimal-bv bv)
+ (let ((n (bytevector-length bv)))
+ (let loop ((i 0) (v 0))
+ (cond
+ ((= i n) (cons #t v))
+ (else
+ (let ((b (bytevector-u8-ref bv i)))
+ (cond ((or (< b 48) (> b 57)) (cons #f #f))
+ (else
+ (loop (+ i 1)
+ (%c-value-mul-small-add v 10 (- b 48)))))))))))
+
+(define (%c-value-fits-unsigned-bits? v bits)
+ (let* ((cv (%c-value-coerce v))
+ (bv (c-value-bytes cv))
+ (whole (quotient bits 8))
+ (part (remainder bits 8)))
+ (let loop ((i (+ whole (if (= part 0) 0 1))))
+ (cond
+ ((= i %C-VALUE-BYTES)
+ (if (= part 0) #t
+ (< (bytevector-u8-ref bv whole)
+ (arithmetic-shift 1 part))))
+ ((not (= (bytevector-u8-ref bv i) 0)) #f)
+ (else (loop (+ i 1)))))))
+
+(define (%c-int-raw v)
+ (if (c-int-lit? v) (c-int-lit-value v) v))
+
+;; --------------------------------------------------------------------
;; output buffer (fixed-size pre-allocated byte storage)
;;
;; Every buf owns one bytevector of `cap` bytes, plus a write `offset`.
@@ -335,7 +756,7 @@
tok?
(kind tok-kind) ; IDENT | INT | STR | CHAR | KW | PUNCT
; | NL | HASH | EOF
- (value tok-value) ; bv | fixnum | symbol | #f
+ (value tok-value) ; bv | fixnum/c-value/c-int-lit | symbol | #f
(loc tok-loc) ; loc
(hide tok-hide)) ; list of bv (macro names already expanded)
@@ -367,6 +788,12 @@
(align ctype-align ctype-align-set!)
(ext ctype-ext ctype-ext-set!))
+;; Active P1 data model. scheme1 supplies these from the selected backend,
+;; so cc.scm emits ILP32 for RV32 and LP64 for the existing targets.
+(define %CC-WORD-BYTES (target-word-bytes))
+(define %CC-WORD-BITS (target-word-bits))
+(define %CC-PAIR-BYTES (* 2 %CC-WORD-BYTES))
+
;; Interned primitive ctypes. Equality is eq?.
(define %t-void (%ctype 'void -1 -1 #f))
(define %t-i8 (%ctype 'i8 1 1 #f))
@@ -377,6 +804,8 @@
(define %t-u32 (%ctype 'u32 4 4 #f))
(define %t-i64 (%ctype 'i64 8 8 #f))
(define %t-u64 (%ctype 'u64 8 8 #f))
+(define %t-word-i (if (= %CC-WORD-BYTES 4) %t-i32 %t-i64))
+(define %t-word-u (if (= %CC-WORD-BYTES 4) %t-u32 %t-u64))
(define %t-bool (%ctype 'bool 1 1 #f))
;; Floating-point ctypes are parsed but never codegen'd; see CC.md §Cut.
;; Sizes/aligns match the SysV ABI so struct layout containing fp fields
@@ -385,6 +814,47 @@
(define %t-dbl (%ctype 'dbl 8 8 #f))
(define %t-ldbl (%ctype 'ldbl 8 8 #f))
+;; Select the first type in C11 6.4.4.1's candidate list that can represent
+;; an integer constant. CType intentionally interns same-width C types into
+;; one representation (for example, int and long are both i32 on ILP32), so
+;; this returns the correct width and signedness even where their ranks differ.
+(define (%c-int-type lit)
+ (let* ((v (%c-int-raw lit))
+ (u? (and (c-int-lit? lit) (c-int-lit-unsigned? lit)))
+ (lc (if (c-int-lit? lit) (c-int-lit-long-count lit) 0))
+ (decimal? (if (c-int-lit? lit) (c-int-lit-decimal? lit) #t))
+ (fits-i32? (%c-value-fits-unsigned-bits? v 31))
+ (fits-u32? (%c-value-fits-unsigned-bits? v 32))
+ (fits-i64? (%c-value-fits-unsigned-bits? v 63)))
+ (cond
+ ;; LL / ULL have only a 64-bit candidate in the supported data models.
+ ((>= lc 2)
+ (cond (u? %t-u64) (fits-i64? %t-i64) (else %t-u64)))
+ ;; L / UL begin at the target's C long width.
+ ((= lc 1)
+ (cond
+ (u?
+ (if (= %CC-WORD-BITS 32)
+ (if fits-u32? %t-u32 %t-u64)
+ %t-u64))
+ (decimal?
+ (if (= %CC-WORD-BITS 32)
+ (cond (fits-i32? %t-i32) (fits-i64? %t-i64) (else %t-u64))
+ (if fits-i64? %t-i64 %t-u64)))
+ ((= %CC-WORD-BITS 32)
+ (cond (fits-i32? %t-i32) (fits-u32? %t-u32)
+ (fits-i64? %t-i64) (else %t-u64)))
+ (else (if fits-i64? %t-i64 %t-u64))))
+ ;; A bare U suffix starts at unsigned int on both targets.
+ (u? (if fits-u32? %t-u32 %t-u64))
+ ;; Unsuffixed decimal has no unsigned candidates before the extension
+ ;; fallback; octal/hex may select unsigned int or unsigned long.
+ (decimal?
+ (cond (fits-i32? %t-i32) (fits-i64? %t-i64) (else %t-u64)))
+ (else
+ (cond (fits-i32? %t-i32) (fits-u32? %t-u32)
+ (fits-i64? %t-i64) (else %t-u64))))))
+
;; --------------------------------------------------------------------
;; sym — declared identifier (function, variable, typedef, …)
;; defined? distinguishes a forward declaration (extern fn proto, extern
@@ -554,6 +1024,13 @@
(cond ((eq? k 'flt) #t) ((eq? k 'dbl) #t) ((eq? k 'ldbl) #t)
(else #f))))
+(define (%ctype-wide-int? t)
+ ;; P1-32 lowers C's 64-bit integer types through a two-word pair. P1-64
+ ;; keeps the historical one-register representation.
+ (and (= %CC-WORD-BYTES 4)
+ (let ((k (ctype-kind t)))
+ (or (eq? k 'i64) (eq? k 'u64)))))
+
;; --------------------------------------------------------------------
;; Symbol alphabets — canonical alists.
;; --------------------------------------------------------------------
@@ -905,50 +1382,30 @@
((if (< b 97) #f (if (< 102 b) #f #t)) (+ (- b 97) 10))
(else 0)))
-;; C integer tokens may contain a full 64-bit bit pattern, wider than
-;; boot2's signed 61-bit Scheme exact-integer range. Accumulate through two
-;; u32 limbs so the Scheme arithmetic itself remains representable, then
-;; pack the low 61 bits into the historical signed carrier used by cc.scm.
-;; This keeps fixed-width C literal handling separate from Scheme's checked
-;; exact arithmetic instead of depending on host/tag wraparound.
-(define %c-int-limb-base 4294967296)
-
-(define (%c-int-pack hi lo)
- (let ((h (bit-and hi #x1fffffff)))
- (if (= 0 (bit-and h #x10000000))
- (+ (* h %c-int-limb-base) lo)
- (+ (* (- h #x20000000) %c-int-limb-base) lo))))
-
(define (%accum-int-while pred src pos line col base)
(let ((n (bytevector-length src)))
- (let loop ((pos pos) (line line) (col col) (hi 0) (lo 0) (cnt 0))
+ (let loop ((pos pos) (line line) (col col) (val 0) (cnt 0))
(cond
- ((>= pos n) (list (%c-int-pack hi lo) cnt pos line col))
+ ((>= pos n) (list val cnt pos line col))
(else
(let ((b (bytevector-u8-ref src pos)))
(cond
((%fast-byte? b)
(if (pred b)
- (let* ((wide-lo (+ (* lo base) (%digit-val-byte b)))
- (next-lo (bit-and wide-lo #xffffffff))
- (carry (arithmetic-shift wide-lo -32))
- (wide-hi (+ (* hi base) carry))
- (next-hi (bit-and wide-hi #xffffffff)))
- (loop (+ pos 1) line (+ col 1)
- next-hi next-lo (+ cnt 1)))
- (list (%c-int-pack hi lo) cnt pos line col)))
+ (loop (+ pos 1) line (+ col 1)
+ (%c-value-mul-small-add
+ val base (%digit-val-byte b))
+ (+ cnt 1))
+ (list val cnt pos line col)))
(else
(let* ((p (%lex-peek src pos line col))
(b2 (%pk-byte p)))
(if (and b2 (pred b2))
- (let* ((wide-lo (+ (* lo base) (%digit-val-byte b2)))
- (next-lo (bit-and wide-lo #xffffffff))
- (carry (arithmetic-shift wide-lo -32))
- (wide-hi (+ (* hi base) carry))
- (next-hi (bit-and wide-hi #xffffffff)))
- (loop (%pk-pos p) (%pk-line p) (%pk-col p)
- next-hi next-lo (+ cnt 1)))
- (list (%c-int-pack hi lo) cnt pos line col)))))))))))
+ (loop (%pk-pos p) (%pk-line p) (%pk-col p)
+ (%c-value-mul-small-add
+ val base (%digit-val-byte b2))
+ (+ cnt 1))
+ (list val cnt pos line col)))))))))))
(define (%accum-octal-bounded src pos line col k)
;; Up to k octal digits. Returns (val count npos nline ncol).
@@ -1018,6 +1475,20 @@
(define (lex-read-number src pos file)
(%lex-read-number src pos 1 (+ pos 1) file))
+(define (%lex-finish-int val start-loc decimal? after)
+ ;; AFTER carries the scanner position followed by the parsed suffix flags.
+ ;; Keep only the position in the public lexer result; retain the flags on
+ ;; the INT token so the parser can apply the target C data model.
+ (let ((npos (car after))
+ (nline (car (cdr after)))
+ (ncol (car (cdr (cdr after))))
+ (unsigned? (car (cdr (cdr (cdr after)))))
+ (long-count (car (cdr (cdr (cdr (cdr after)))))))
+ (cons (make-tok 'INT
+ (%c-int-lit val unsigned? long-count decimal?)
+ start-loc)
+ (list npos nline ncol))))
+
(define (%lex-read-number src pos line col file)
(let* ((start-loc (%loc file line col))
(p (%lex-peek src pos line col))
@@ -1039,7 +1510,7 @@
(if (zero? cnt)
(die start-loc "expected hex digits after 0x")
(let ((after (%lex-strip-int-suffix src pos2 line2 col2 file)))
- (cons (make-tok 'INT val start-loc) after)))))
+ (%lex-finish-int val start-loc #f after)))))
;; '0' alone → octal sequence (could be just zero)
((= b 48)
(let* ((r (%accum-int-while %octal? src
@@ -1056,7 +1527,7 @@
(if (and b3 (%digit? b3))
(die start-loc "invalid octal digit" (bv-of-byte b3))
(let ((after (%lex-strip-int-suffix src pos2 line2 col2 file)))
- (cons (make-tok 'INT val start-loc) after))))))
+ (%lex-finish-int val start-loc #f after))))))
;; '1'-'9' → decimal
((%digit? b)
(let* ((r (%accum-int-while %digit? src pos line col 10))
@@ -1066,7 +1537,7 @@
(col2 (car (cdr (cdr (cdr (cdr r)))))))
(%check-no-float src pos2 line2 col2 file start-loc)
(let ((after (%lex-strip-int-suffix src pos2 line2 col2 file)))
- (cons (make-tok 'INT val start-loc) after))))
+ (%lex-finish-int val start-loc #t after))))
;; '.' followed by a digit = float-style literal — reject.
((= b 46)
(let* ((q (%lex-peek src (%pk-pos p) (%pk-line p) (%pk-col p)))
@@ -1106,16 +1577,18 @@
(define (%lex-strip-int-suffix src pos line col file)
;; Consume any combination of u U l L (the long can be doubled). We
;; don't validate orderings strictly; tcc.c uses the canonical forms.
- ;; Returns (npos nline ncol).
- (let loop ((pos pos) (line line) (col col))
+ ;; Returns (npos nline ncol unsigned? long-count).
+ (let loop ((pos pos) (line line) (col col) (unsigned? #f) (long-count 0))
(let* ((p (%lex-peek src pos line col))
(b (%pk-byte p)))
(cond
- ((not b) (list pos line col))
- ((or (= b 117) (= b 85) ; u U
- (= b 108) (= b 76)) ; l L
- (loop (%pk-pos p) (%pk-line p) (%pk-col p)))
- (else (list pos line col))))))
+ ((not b) (list pos line col unsigned? long-count))
+ ((or (= b 117) (= b 85)) ; u U
+ (loop (%pk-pos p) (%pk-line p) (%pk-col p) #t long-count))
+ ((or (= b 108) (= b 76)) ; l L
+ (loop (%pk-pos p) (%pk-line p) (%pk-col p)
+ unsigned? (+ long-count 1)))
+ (else (list pos line col unsigned? long-count))))))
;; --------------------------------------------------------------------
;; Escape sequence reader.
@@ -2081,7 +2554,7 @@
(pps-cond-stack-set! state (cons (%pp-frame #f #f #f) (pps-cond-stack state))))
(else
(let* ((v (pp-eval-cexpr line state))
- (a? (not (= v 0))))
+ (a? (not (%c-value-zero? v))))
(pps-cond-stack-set! state (cons (%pp-frame a? a? #f) (pps-cond-stack state)))))))
(define (%pp-do-ifdef line state)
@@ -2129,7 +2602,7 @@
(pps-cond-stack-set! state (cons (%pp-frame #f taken? #f) rest)))
(else
(let* ((v (pp-eval-cexpr line state))
- (a? (not (= v 0))))
+ (a? (not (%c-value-zero? v))))
(pps-cond-stack-set! state
(cons (%pp-frame a? (or a? taken?) #f) rest))))))))))
@@ -2204,7 +2677,7 @@
(let ((k (tok-kind t)) (v (tok-value t)))
(cond
((eq? k 'IDENT) v)
- ((eq? k 'INT) (fixnum->bv v 10))
+ ((eq? k 'INT) (%c-value-source-bv v))
((eq? k 'STR) (%pp-quote-bytes v 34))
((eq? k 'CHAR) (%pp-quote-bytes (bv-of-byte v) 39))
((eq? k 'KW) (symbol->string v))
@@ -2242,7 +2715,7 @@
((not (%pp-int? (car line)))
(die (tok-loc (car line)) "#line: expected integer"))
(else
- (let* ((nt (car line)) (n (tok-value nt))
+ (let* ((nt (car line)) (n (%c-int-raw (tok-value nt)))
(rest (cdr line)))
(pps-line-delta-set! state (- n here 1))
(cond
@@ -2471,16 +2944,17 @@
(%tok 'IDENT (bytevector-append (tok-value lhs) (tok-value rhs))
(tok-loc lhs) (%pp-bv-union (tok-hide lhs) (tok-hide rhs))))
((and (eq? lk 'IDENT) (eq? rk 'INT))
- (%tok 'IDENT (bytevector-append (tok-value lhs) (fixnum->bv (tok-value rhs) 10))
+ (%tok 'IDENT (bytevector-append (tok-value lhs)
+ (%c-value-source-bv (tok-value rhs)))
(tok-loc lhs) (%pp-bv-union (tok-hide lhs) (tok-hide rhs))))
((and (eq? lk 'INT) (eq? rk 'INT))
- (let ((s (bytevector-append (fixnum->bv (tok-value lhs) 10)
- (fixnum->bv (tok-value rhs) 10))))
- (let-values (((ok? n) (bv->fixnum s 10)))
- (cond
- ((not ok?) (die (tok-loc lhs) "paste: cannot reparse as integer" s))
- (else (%tok 'INT n (tok-loc lhs)
- (%pp-bv-union (tok-hide lhs) (tok-hide rhs))))))))
+ (let* ((s (bytevector-append (%c-value-source-bv (tok-value lhs))
+ (%c-value-source-bv (tok-value rhs))))
+ (r (%c-value-parse-decimal-bv s)))
+ (cond
+ ((not (car r)) (die (tok-loc lhs) "paste: cannot reparse as integer" s))
+ (else (%tok 'INT (%c-int-lit (cdr r) #f 0 #t) (tok-loc lhs)
+ (%pp-bv-union (tok-hide lhs) (tok-hide rhs)))))))
(else (die (tok-loc lhs) "paste: unsupported token kinds" lk rk)))))
(define (%pp-relocate t state)
@@ -2590,7 +3064,7 @@
(define (%cg-emit-many cg bvs)
(for-each (lambda (b) (%cg-emit cg b)) bvs))
-(define (%n n) (fixnum->bv n 10))
+(define (%n n) (%c-value-literal-bv n))
;; Per-fn metadata (name, ret-slot, ret-type, switch-case lists, ...)
;; lives on cg-fn-meta, reset at every cg-fn-begin/v.
@@ -2672,14 +3146,15 @@
;; Width-aware load/store. Dispatches on ctype-size:
;; 1: %lb / %sb (LB zero-extends; for signed i8 we sign-extend by
-;; shli/sari 56 to materialize the canonical 64-bit form).
-;; 2/4: byte-decomposed (P1 has only 1-byte and 8-byte memory ops,
+;; target-word-relative shli/sari to materialize canonical form).
+;; 2/4: handled by libp1pp helpers (P1 has only 1-byte and target-word ops,
;; and word ops require natural alignment which we can't promise
;; for struct fields or non-word-aligned local slots). Loads
;; gather bytes via %lb + shli/or; stores scatter via shri/%sb.
;; Signed loads (i16/i32) sign-extend via shli/sari to canonical
-;; 64-bit form.
-;; 8 (and any other size): %ld / %st.
+;; target-word form.
+;; target-word-sized fallback: %ld / %st. RV32 i64/u64 accesses use
+;; the dedicated adjacent-pair path before reaching these helpers.
;; Scratch convention: helpers may clobber t1; callers never pass
;; reg=t1.
@@ -2720,7 +3195,7 @@
;; Canonicalize REG against CTYPE's kind: signed narrow types sign-extend,
;; unsigned narrow types zero-extend, anything else is left alone (the
-;; full 64-bit value is already canonical). Used after operations that
+;; full target-word value is already canonical). Used after operations that
;; may have left a non-canonical bit pattern in reg — frame-rval load,
;; narrowing cast, narrow-typed binop result.
(define (%cg-canonicalize cg reg ctype)
@@ -2743,7 +3218,7 @@
;; through %cg-slot-expr — and the typed variants, where base is a
;; register and off is a raw integer rendered via %n). 1-byte uses
;; %lb/%sb (with i8 sext); 2- and 4-byte use the sub-word helpers; the
-;; 8-byte fallback emits a plain %ld/%st against the same base/off.
+;; target-word fallback emits a plain %ld/%st against the same base/off.
(define (%cg-emit-ld-bv cg reg ctype base-bv off-bv)
(%cg-fp-reject! 'ld ctype)
(let* ((sz (ctype-size ctype)) (kind (ctype-kind ctype))
@@ -2779,19 +3254,95 @@
(define (%cg-emit-st-typed cg reg ctype base off)
(%cg-emit-st-bv cg reg ctype (%cg-reg->bv base) (%n off)))
+(define (%cg-emit-li-wide cg lo hi value)
+ (%cg-emit-many cg
+ (list "%li(" (%cg-reg->bv lo) ", "
+ (%c-value-u32-literal-bv value 0) ")\n"
+ "%li(" (%cg-reg->bv hi) ", "
+ (%c-value-u32-literal-bv value 1) ")\n")))
+
+(define (%cg-load-wide-opnd-into cg op lo hi)
+ ;; Load an RV32 i64/u64 as (low-word, high-word). Wide frame rvalues own
+ ;; two adjacent target-word slots; wide lvalues read two adjacent words
+ ;; from their object storage. t2 is the address scratch for indirect and
+ ;; global lvalues, so callers must keep it distinct from LO/HI.
+ (cond
+ ((not (%ctype-wide-int? (opnd-type op)))
+ (die #f "cg internal: pair load of non-wide operand" (ctype-kind (opnd-type op))))
+ ((or (eq? lo 't2) (eq? hi 't2))
+ (die #f "cg internal: t2 cannot hold a wide operand limb"))
+ (else
+ (pmatch op
+ (($ opnd? (kind imm) (ext ,n))
+ (%cg-emit-li-wide cg lo hi n))
+ (($ opnd? (kind frame) (lval? #t) (ext ,off))
+ (guard (%cg-indirect? cg off))
+ (%cg-emit-ld-slot cg 't2 off)
+ (%cg-emit-ld cg lo 't2 0)
+ (%cg-emit-ld cg hi 't2 %CC-WORD-BYTES))
+ (($ opnd? (kind frame) (ext ,off))
+ (%cg-emit-ld-slot cg lo off)
+ (%cg-emit-ld-slot cg hi (+ off %CC-WORD-BYTES)))
+ (($ opnd? (kind global) (lval? #t) (ext ,lbl))
+ (%cg-emit-la cg 't2 lbl)
+ (%cg-emit-ld cg lo 't2 0)
+ (%cg-emit-ld cg hi 't2 %CC-WORD-BYTES))
+ (else
+ (die #f "cg internal: unsupported wide operand" (opnd-kind op)))))))
+
+(define (%cg-spill-pair cg lo hi ty)
+ (cond ((not (%ctype-wide-int? ty))
+ (die #f "cg internal: pair spill of non-wide type" (ctype-kind ty))))
+ (let* ((off (cg-alloc-slot cg (ctype-size ty)
+ (max %CC-WORD-BYTES (ctype-align ty))))
+ (op (%opnd 'frame ty off #f)))
+ (%cg-emit-st-slot cg lo off)
+ (%cg-emit-st-slot cg hi (+ off %CC-WORD-BYTES))
+ (cg-vstack-set! cg (cons op (cg-vstack cg)))
+ op))
+
+(define (%cg-store-pair-to-lval cg lo hi lhs)
+ (cond ((not (%ctype-wide-int? (opnd-type lhs)))
+ (die #f "cg internal: pair store to non-wide lvalue"
+ (ctype-kind (opnd-type lhs)))))
+ (pmatch lhs
+ (($ opnd? (kind frame) (ext ,off))
+ (guard (%cg-indirect? cg off))
+ (%cg-emit-ld-slot cg 't2 off)
+ (%cg-emit-st cg lo 't2 0)
+ (%cg-emit-st cg hi 't2 %CC-WORD-BYTES))
+ (($ opnd? (kind frame) (ext ,off))
+ (%cg-emit-st-slot cg lo off)
+ (%cg-emit-st-slot cg hi (+ off %CC-WORD-BYTES)))
+ (($ opnd? (kind global) (ext ,lbl))
+ (%cg-emit-la cg 't2 lbl)
+ (%cg-emit-st cg lo 't2 0)
+ (%cg-emit-st cg hi 't2 %CC-WORD-BYTES))
+ (else (die #f "cg-assign: unsupported wide lhs kind" (opnd-kind lhs)))))
+
+(define (%cg-load-truth-into cg op reg)
+ (cond
+ ((%ctype-wide-int? (opnd-type op))
+ (%cg-load-wide-opnd-into cg op 'a0 'a1)
+ (%cg-emit-rrr cg "or" reg 'a0 'a1))
+ (else (%cg-load-opnd-into cg op reg))))
+
(define (%cg-load-opnd-into cg op reg)
- ;; frame lval: load at type width. frame rval is a spilled word
- ;; (alloc-slot 8 8 in %cg-spill-reg) — always 8-byte load.
+ ;; frame lval: load at type width. frame rval is a spilled target word
+ ;; (allocated by %cg-spill-reg) — always a target-word load.
;; global lval width > 1 byte-gathers must not alias dest with base —
;; the first %lb would otherwise clobber the address before subsequent
;; byte loads. Stage the address in t2.
(%cg-fp-reject! 'load (opnd-type op))
+ (cond ((%ctype-wide-int? (opnd-type op))
+ (die #f "cg internal: wide operand used as one word"
+ (ctype-kind (opnd-type op)))))
(pmatch op
(($ opnd? (kind imm) (ext ,n)) (%cg-emit-li cg reg n))
(($ opnd? (kind frame) (lval? #t) (type ,ty) (ext ,off))
(%cg-emit-ld-slot-typed cg reg ty off))
(($ opnd? (kind frame) (lval? #f) (type ,ty) (ext ,off))
- ;; Frame rval: spilled as 8 bytes, but the slot's bit-pattern may
+ ;; Frame rval: spilled as one target word, but the slot's bit-pattern may
;; not be canonical for the opnd's CURRENT type (e.g.
;; cg-arith-conv relabeled a signed slot as unsigned). Canonicalize
;; on load so downstream 64-bit ALU/compare ops see the C-semantic
@@ -2806,7 +3357,10 @@
(else (die #f "cg internal: unknown opnd-kind" (opnd-kind op)))))
(define (%cg-spill-reg cg reg ty)
- (let* ((off (cg-alloc-slot cg 8 8))
+ (cond ((%ctype-wide-int? ty)
+ (die #f "cg internal: wide value spilled from one register"
+ (ctype-kind ty))))
+ (let* ((off (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES))
(op (%opnd 'frame ty off #f)))
(%cg-emit-st-slot cg reg off)
(cg-vstack-set! cg (cons op (cg-vstack cg)))
@@ -2909,7 +3463,7 @@
(define (cg-fn-begin cg name params return-type)
(cg-fn-begin/v cg name params return-type #f))
-;; Variadic-aware variant. variadic? = #t reserves 16 contiguous 8-byte
+;; Variadic-aware variant. variadic? = #t reserves 16 contiguous target-word
;; slots covering incoming arg indices 0..15, populating each from the
;; appropriate source: a-register for idx 0..3, LDARG slot (idx-4) for
;; idx 4..15. va_start computes the address of the slot at index =
@@ -2936,12 +3490,15 @@
(%cg-fn-set! cg '%fn-ret-type return-type)
(%cg-fn-set! cg '%indirect-slots '())
(%cg-fn-set! cg '%fn-variadic? variadic?)
- ;; Return slot per P1.md §Arguments. ≤8B → a0; 9–16B → a0+a1; >16B
+ ;; Return slot per P1.md §Arguments. One word → a0; two words → a0+a1;
+ ;; wider aggregates
;; struct/union → indirect-result (A2): caller passes sret ptr in
;; a0; cg-return writes through it; sret-slot saves a0 for cg-fn-end.
- (let* ((rsz (cond ((eq? (ctype-kind return-type) 'void) 8)
- (else (align-up (max 8 (ctype-size return-type)) 8))))
- (ret-slot (cg-alloc-slot cg rsz 8)))
+ (let* ((rsz (cond ((eq? (ctype-kind return-type) 'void) %CC-WORD-BYTES)
+ (else (align-up (max %CC-WORD-BYTES
+ (ctype-size return-type))
+ %CC-WORD-BYTES))))
+ (ret-slot (cg-alloc-slot cg rsz %CC-WORD-BYTES)))
(%cg-fn-set! cg '%fn-ret-slot ret-slot)
(cond
((not (eq? (ctype-kind return-type) 'void))
@@ -2954,14 +3511,14 @@
"%st(t0, sp, "
(%cg-slot-expr cg (+ ret-slot k))
")\n")))
- (zinit (+ k 8))))))))
+ (zinit (+ k %CC-WORD-BYTES))))))))
(let* ((rk (ctype-kind return-type))
(sret? (and (or (eq? rk 'struct) (eq? rk 'union))
- (> (ctype-size return-type) 16))))
+ (> (ctype-size return-type) %CC-PAIR-BYTES))))
(%cg-fn-set! cg '%fn-sret? sret?)
(cond
(sret?
- (let ((ss (cg-alloc-slot cg 8 8)))
+ (let ((ss (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES)))
(%cg-fn-set! cg '%fn-sret-slot ss)
(buf-push! (cg-prologue-buf cg)
(bv-cat (list "%st(a0, sp, "
@@ -3000,7 +3557,7 @@
(%cg-fn-set! cg '%fn-vararg-first-slot (or vfirst fs))
(reverse out))
(else
- (let ((off (cg-alloc-slot cg 8 8)))
+ (let ((off (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES)))
(spill (+ i sret-shift) off)
(pad (+ i 1) (or vfirst off) (or fs off)))))))
(else (reverse out))))
@@ -3008,37 +3565,41 @@
(let* ((p (car ps))
(nm (car p))
(ty (cdr p))
- ;; AAPCS: 9..16B aggregates ride two consecutive arg
- ;; positions (regs or stack slots), wider-than-16B
+ ;; Two-word aggregates ride two consecutive arg positions;
+ ;; wider aggregates
;; aggregates would normally pass by reference — not
;; supported here yet.
(n (%cg-param-reg-count ty))
- (sz (cond ((%cg-param-aggregate? ty)
- (align-up (ctype-size ty) 8))
- (else 8)))
- (al (cond ((%cg-param-aggregate? ty)
- (max 8 (ctype-align ty)))
- (else 8)))
+ (sz (cond ((or (%cg-param-aggregate? ty)
+ (%ctype-wide-int? ty))
+ (align-up (ctype-size ty) %CC-WORD-BYTES))
+ (else %CC-WORD-BYTES)))
+ (al (cond ((or (%cg-param-aggregate? ty)
+ (%ctype-wide-int? ty))
+ (max %CC-WORD-BYTES (ctype-align ty)))
+ (else %CC-WORD-BYTES)))
(off (cg-alloc-slot cg sz al))
(psym (%sym nm 'param #f ty off #t)))
(let chunk ((i 0))
(cond ((>= i n) 0)
(else
- (spill (+ idx sret-shift i) (+ off (* i 8)))
+ (spill (+ idx sret-shift i)
+ (+ off (* i %CC-WORD-BYTES)))
(chunk (+ i 1)))))
(walk (cdr ps) (+ idx n) (cons (cons nm psym) out)
(or first-slot off))))))))
;; Number of consecutive ABI slots (regs or stack words) consumed by a
-;; parameter of TY. Aggregates ≤16B take ⌈size/8⌉; everything else 1.
+;; parameter of TY. Aggregates up to two words take ceil(size/word).
(define (%cg-param-reg-count ty)
(cond
+ ((%ctype-wide-int? ty) 2)
((%cg-param-aggregate? ty)
(let ((sz (ctype-size ty)))
(cond
- ((> sz 16)
- (die #f "cg: aggregate arg/param >16B not supported" sz))
- ((> sz 8) 2)
+ ((> sz %CC-PAIR-BYTES)
+ (die #f "cg: aggregate arg/param wider than two words not supported" sz))
+ ((> sz %CC-WORD-BYTES) 2)
(else 1))))
(else 1)))
@@ -3055,7 +3616,7 @@
(ret-slot (%cg-fn-get cg '%fn-ret-slot))
(ret-type (%cg-fn-get cg '%fn-ret-type))
(locals-hi (cg-frame-hi cg))
- (staging-bytes (* 8 (cg-max-outgoing cg)))
+ (staging-bytes (* %CC-WORD-BYTES (cg-max-outgoing cg)))
(raw-size (+ staging-bytes locals-hi))
(frame-size (align-up raw-size 16))
;; Look up the bound sym for this fn so `static void foo(){...}`
@@ -3100,7 +3661,7 @@
(buf-push! tb (%n (bytevector-length mangled)))
(buf-push! tb ")\n"))))
(buf-drain! tb (cg-fn-buf cg))
- ;; ret block: ≤8B → a0; 9–16B → a0+a1; >16B sret → a0 = saved sret ptr.
+ ;; ret block: one word → a0; two words → a0+a1; wider sret → a0.
(buf-push! tb ":.ret\n")
(let ((rk (ctype-kind ret-type))
(sret? (%cg-fn-get cg '%fn-sret?)))
@@ -3116,9 +3677,10 @@
(buf-push! tb (%cg-slot-expr cg ret-slot))
(buf-push! tb ")\n")
(cond
- ((> (ctype-size ret-type) 8)
+ ((> (ctype-size ret-type) %CC-WORD-BYTES)
(buf-push! tb "%ld(a1, sp, ")
- (buf-push! tb (%cg-slot-expr cg (+ ret-slot 8)))
+ (buf-push! tb (%cg-slot-expr cg
+ (+ ret-slot %CC-WORD-BYTES)))
(buf-push! tb ")\n"))))))
(buf-push! tb "})\n")
(cg-vstack-set! cg '())
@@ -3191,7 +3753,7 @@
(define (cg-push-string cg bv-content)
(let* ((label (cg-intern-string cg bv-content))
- (cp-ty (%ctype 'ptr 8 8 %t-i8)))
+ (cp-ty (%mk-ptr %t-i8)))
(cg-push cg (%opnd 'global cp-ty label #f))))
(define (cg-push-sym cg sm)
@@ -3234,7 +3796,7 @@
((not pe) (die #f "cg-push-deref: not a pointer" pt))
(else
(%cg-load-opnd-into cg p 't0)
- (let ((off (cg-alloc-slot cg 8 8)))
+ (let ((off (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES)))
(%cg-emit-st-slot cg 't0 off)
(%cg-mark-indirect! cg off)
(cg-push cg (%opnd 'frame pe off #t)))))))
@@ -3313,7 +3875,7 @@
(cond
((> fo 0)
(%cg-emit-many cg (list "%addi(t0, t0, " (%n fo) ")\n"))))
- (let ((no (cg-alloc-slot cg 8 8)))
+ (let ((no (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES)))
(%cg-emit-st-slot cg 't0 no)
(%cg-mark-indirect! cg no)
(cg-push cg (%opnd 'frame fty no #t))))
@@ -3323,7 +3885,7 @@
(cond
((> fo 0)
(%cg-emit-many cg (list "%addi(t0, t0, " (%n fo) ")\n"))))
- (let ((no (cg-alloc-slot cg 8 8)))
+ (let ((no (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES)))
(%cg-emit-st-slot cg 't0 no)
(%cg-mark-indirect! cg no)
(cg-push cg (%opnd 'frame fty no #t))))
@@ -3341,7 +3903,7 @@
((and (opnd-lval? tp) (eq? (ctype-kind (opnd-type tp)) 'arr))
(let* ((p (cg-pop cg))
(et (car (ctype-ext (opnd-type p))))
- (pty (%ctype 'ptr 8 8 et)))
+ (pty (%mk-ptr et)))
(pmatch p
;; direct frame lval: address is sp+off.
(($ opnd? (kind frame) (ext ,off))
@@ -3446,7 +4008,7 @@
;; the result scales by sizeof(T[N]) (the whole array), so
;; &arr + 1 is one-past-end. Array-to-pointer decay happens
;; on use via cg-decay-array, not at the & operator.
- (pty (%ctype 'ptr 8 8 ty)))
+ (pty (%mk-ptr ty)))
(pmatch p
;; &function: a function designator (rval of fn type pushed by
;; cg-push-sym) already evaluates to its entry-point address. The
@@ -3481,13 +4043,15 @@
(cg-push cg p) (cg-decay-array cg))
;; Struct/union lvalues stay as lvalues — there is no
;; register-sized rvalue form for an aggregate, and the
- ;; existing 8-byte spill path silently truncated anything
- ;; wider (the bug that broke `c = cond ? a : b` for
- ;; sizeof(struct) > 8). Surrounding expression machinery
+ ;; historical one-word spill path silently truncated wider aggregates
+ ;; (the bug that broke `c = cond ? a : b`). Surrounding machinery
;; (cg-ifelse-merge / cg-assign-struct / cg-call) consumes
;; aggregate operands as lvalues already.
((or (eq? (ctype-kind ty) 'struct) (eq? (ctype-kind ty) 'union))
(cg-push cg p))
+ ((%ctype-wide-int? ty)
+ (%cg-load-wide-opnd-into cg p 't0 't1)
+ (%cg-spill-pair cg 't0 't1 ty))
((and (eq? (opnd-kind p) 'frame)
(%cg-indirect? cg (opnd-ext p)))
;; Indirect frame-lval: slot holds the address. Stage the
@@ -3511,14 +4075,40 @@
(%cg-fp-reject! 'cast-from from-ty)
(cond
((eq? to-kind 'bool)
- (%cg-load-opnd-into cg p 't0)
+ (cond
+ ((%ctype-wide-int? from-ty)
+ (%cg-load-wide-opnd-into cg p 't0 't1)
+ (%cg-emit-rrr cg "or" 't0 't0 't1))
+ (else (%cg-load-opnd-into cg p 't0)))
(%cg-emit-many cg (list "%bool(t0, t0)\n"))
(%cg-spill-reg cg 't0 to-type))
- ((or (eq? to-kind 'ptr)
- (and (or (eq? to-kind 'i64) (eq? to-kind 'u64))
- (or (eq? (ctype-kind from-ty) 'ptr)
- (eq? (ctype-kind from-ty) 'arr))))
+ ;; Pointer-to-pointer casts preserve the one-word representation.
+ ((and (eq? to-kind 'ptr)
+ (or (eq? (ctype-kind from-ty) 'ptr)
+ (eq? (ctype-kind from-ty) 'arr)
+ (eq? (ctype-kind from-ty) 'fn)))
+ (cg-push cg (%opnd (opnd-kind p) to-type (opnd-ext p) (opnd-lval? p))))
+ ;; Any RV32 narrowing conversion consumes only the low limb, then
+ ;; canonicalizes it for the destination type.
+ ((and (%ctype-wide-int? from-ty) (not (%ctype-wide-int? to-type)))
+ (%cg-load-wide-opnd-into cg p 't0 't1)
+ (%cg-canonicalize cg 't0 to-type)
+ (%cg-spill-reg cg 't0 to-type))
+ ;; Same-width signedness changes do not alter an i64 bit pattern.
+ ((and (%ctype-wide-int? from-ty) (%ctype-wide-int? to-type))
(cg-push cg (%opnd (opnd-kind p) to-type (opnd-ext p) (opnd-lval? p))))
+ ;; Widen a one-word RV32 value into a real two-word integer. The low
+ ;; limb is already canonical; signed sources replicate their sign bit.
+ ((%ctype-wide-int? to-type)
+ (%cg-load-opnd-into cg p 't0)
+ (cond
+ ((%ctype-unsigned? from-ty)
+ (%cg-emit-many cg (list "%li(t1, 0)\n")))
+ (else
+ (%cg-emit-many cg
+ (list "%sari(t1, t0, "
+ (%n (- %CC-WORD-BITS 1)) ")\n"))))
+ (%cg-spill-pair cg 't0 't1 to-type))
;; Same-size or widening cast — retag only when the canonical
;; 64-bit slot form for FROM-TY is also canonical for TO-TYPE.
;; That holds unless we're crossing from a signed type into an
@@ -3590,7 +4180,7 @@
((%ctype-unsigned? tb) tb)
(else ta))))
;; Route through cg-cast (rather than relabel only) so the
- ;; canonical 64-bit slot form lines up with COMMON. Same-size
+ ;; canonical target-word slot form lines up with COMMON. Same-size
;; cross-signedness conversions (i32→u32, u32→i32, …) need an
;; actual zext/sext to canonicalize; otherwise an i32 -3
;; relabeled to u32 keeps its sign-extended slot bits and
@@ -3628,11 +4218,76 @@
((or (eq? op 'eq) (eq? op 'ne)
(eq? op 'lt) (eq? op 'le) (eq? op 'gt) (eq? op 'ge))
%t-i32)
- ((and a-ptr? b-ptr? (eq? op 'sub)) %t-i64)
+ ((and a-ptr? b-ptr? (eq? op 'sub)) %t-word-i)
(a-ptr? ta)
(b-ptr? tb)
(else ta))))
(cond
+ ((%ctype-wide-int? ta)
+ (%cg-load-wide-opnd-into cg a 'a0 'a1)
+ (cond
+ ((or (eq? op 'shl) (eq? op 'shr))
+ (cond
+ ((%ctype-wide-int? tb)
+ (%cg-load-wide-opnd-into cg b 'a2 'a3))
+ (else (%cg-load-opnd-into cg b 'a2)))
+ (%cg-emit-many
+ cg
+ (list (cond ((eq? op 'shl) "%i64_shl(")
+ ((%ctype-unsigned? ta) "%i64_shr(")
+ (else "%i64_sar("))
+ "t0, t1, a0, a1, a2, t2)\n"))
+ (%cg-spill-pair cg 't0 't1 result-ty))
+ (else
+ (%cg-load-wide-opnd-into cg b 'a2 'a3)
+ (cond
+ ((eq? op 'add)
+ (%cg-emit-many cg
+ (list "%i64_add(t0, t1, a0, a1, a2, a3, t2)\n")))
+ ((eq? op 'sub)
+ (%cg-emit-many cg
+ (list "%i64_sub(t0, t1, a0, a1, a2, a3, t2)\n")))
+ ((eq? op 'mul)
+ (%cg-emit-many cg
+ (list "%i64_mul(t0, t1, a0, a1, a2, a3, t2)\n")))
+ ((eq? op 'and)
+ (%cg-emit-rrr cg "and" 't0 'a0 'a2)
+ (%cg-emit-rrr cg "and" 't1 'a1 'a3))
+ ((eq? op 'or)
+ (%cg-emit-rrr cg "or" 't0 'a0 'a2)
+ (%cg-emit-rrr cg "or" 't1 'a1 'a3))
+ ((eq? op 'xor)
+ (%cg-emit-rrr cg "xor" 't0 'a0 'a2)
+ (%cg-emit-rrr cg "xor" 't1 'a1 'a3))
+ ((or (eq? op 'div) (eq? op 'rem))
+ (%cg-emit-many
+ cg
+ (list "%call(&"
+ (if (%ctype-unsigned? ta)
+ "p1_i64_udivmod" "p1_i64_divmod")
+ ")\n")))
+ ((or (eq? op 'eq) (eq? op 'ne)
+ (eq? op 'lt) (eq? op 'le) (eq? op 'gt) (eq? op 'ge))
+ (%cg-emit-many
+ cg
+ (list "%i64_cmpset_"
+ (cond ((eq? op 'eq) "eq")
+ ((eq? op 'ne) "ne")
+ ((eq? op 'lt) (if unsigned? "ltu" "lt"))
+ ((eq? op 'le) (if unsigned? "leu" "le"))
+ ((eq? op 'gt) (if unsigned? "gtu" "gt"))
+ (else (if unsigned? "geu" "ge")))
+ "(t0, a0, a1, a2, a3, t1)\n")))
+ (else (die #f "cg-binop: unknown wide op" op)))
+ (cond
+ ((or (eq? op 'eq) (eq? op 'ne)
+ (eq? op 'lt) (eq? op 'le) (eq? op 'gt) (eq? op 'ge))
+ (%cg-spill-reg cg 't0 %t-i32))
+ ((eq? op 'div)
+ (%cg-spill-pair cg 'a0 'a1 result-ty))
+ ((eq? op 'rem)
+ (%cg-spill-pair cg 'a2 'a3 result-ty))
+ (else (%cg-spill-pair cg 't0 't1 result-ty))))))
((and a-ptr? (or (eq? op 'add) (eq? op 'sub)) (not b-ptr?))
(%cg-load-opnd-into cg a 'a0)
(%cg-load-opnd-into cg b 'a1)
@@ -3713,8 +4368,25 @@
(define (cg-unop cg op)
(let* ((p (cg-pop cg)) (ty (opnd-type p)))
- (%cg-load-opnd-into cg p 't0)
(cond
+ ((%ctype-wide-int? ty)
+ (%cg-load-wide-opnd-into cg p 'a0 'a1)
+ (cond
+ ((eq? op 'neg)
+ (%cg-emit-many cg (list "%i64_neg(t0, t1, a0, a1, t2)\n"))
+ (%cg-spill-pair cg 't0 't1 ty))
+ ((eq? op 'bnot)
+ (%cg-emit-many cg (list "%bnot(t0, a0, t2)\n"
+ "%bnot(t1, a1, t2)\n"))
+ (%cg-spill-pair cg 't0 't1 ty))
+ ((eq? op 'lnot)
+ (%cg-emit-rrr cg "or" 't0 'a0 'a1)
+ (%cg-emit-many cg (list "%cmpset_eqz(t0, t0)\n"))
+ (%cg-spill-reg cg 't0 %t-i32))
+ (else (die #f "cg-unop: unknown wide op" op))))
+ (else
+ (%cg-load-opnd-into cg p 't0)
+ (cond
((eq? op 'neg)
(%cg-emit-many cg (list "%neg(t0, t0, t1)\n"))
(%cg-spill-reg cg 't0 ty))
@@ -3724,7 +4396,7 @@
((eq? op 'lnot)
(%cg-emit-many cg (list "%cmpset_eqz(t0, t0)\n"))
(%cg-spill-reg cg 't0 %t-i32))
- (else (die #f "cg-unop: unknown op" op)))))
+ (else (die #f "cg-unop: unknown op" op)))))))
(define (cg-assign cg)
;; Pops rhs, pops lhs, casts rhs to lhs's type (parser cannot peek
@@ -3738,19 +4410,25 @@
(cg-push cg rhs0)
(cg-cast cg ty)
(let ((rhs (cg-pop cg)))
- (%cg-load-opnd-into cg rhs 'a0)
- (pmatch lhs
- (($ opnd? (kind frame) (ext ,off))
- (guard (%cg-indirect? cg off))
- (%cg-emit-ld-slot cg 't0 off)
- (%cg-emit-st-typed cg 'a0 ty 't0 0))
- (($ opnd? (kind frame) (ext ,off))
- (%cg-emit-st-slot-typed cg 'a0 ty off))
- (($ opnd? (kind global) (ext ,lbl))
- (%cg-emit-la cg 't0 lbl)
- (%cg-emit-st-typed cg 'a0 ty 't0 0))
- (else (die #f "cg-assign: unsupported lhs kind" (opnd-kind lhs))))
- (%cg-spill-reg cg 'a0 ty))))
+ (cond
+ ((%ctype-wide-int? ty)
+ (%cg-load-wide-opnd-into cg rhs 'a0 'a1)
+ (%cg-store-pair-to-lval cg 'a0 'a1 lhs)
+ (%cg-spill-pair cg 'a0 'a1 ty))
+ (else
+ (%cg-load-opnd-into cg rhs 'a0)
+ (pmatch lhs
+ (($ opnd? (kind frame) (ext ,off))
+ (guard (%cg-indirect? cg off))
+ (%cg-emit-ld-slot cg 't0 off)
+ (%cg-emit-st-typed cg 'a0 ty 't0 0))
+ (($ opnd? (kind frame) (ext ,off))
+ (%cg-emit-st-slot-typed cg 'a0 ty off))
+ (($ opnd? (kind global) (ext ,lbl))
+ (%cg-emit-la cg 't0 lbl)
+ (%cg-emit-st-typed cg 'a0 ty 't0 0))
+ (else (die #f "cg-assign: unsupported lhs kind" (opnd-kind lhs))))
+ (%cg-spill-reg cg 'a0 ty))))))
;; --------------------------------------------------------------------
;; Calls
@@ -3760,19 +4438,19 @@
(cond ((= i arity) acc)
(else (loop (+ i 1) (cons (cg-pop cg) acc))))))
(fn-op (cg-pop cg))
- ;; sret = struct/union > 16B return; shift args by one reg
+ ;; sret = struct/union wider than two target words; shift args by one reg
;; and place a0 last so it's not clobbered by arg loads.
(fty (opnd-type fn-op))
(rty (cond
((eq? (ctype-kind fty) 'fn) (car (ctype-ext fty)))
((eq? (ctype-kind fty) 'ptr)
(let ((p (ctype-ext fty)))
- (if (eq? (ctype-kind p) 'fn) (car (ctype-ext p)) %t-i64)))
- (else %t-i64)))
+ (if (eq? (ctype-kind p) 'fn) (car (ctype-ext p)) %t-word-i)))
+ (else %t-word-i)))
(rk (ctype-kind rty))
(sret? (and has-result?
(or (eq? rk 'struct) (eq? rk 'union))
- (> (ctype-size rty) 16)))
+ (> (ctype-size rty) %CC-PAIR-BYTES)))
;; If the callee is variadic, the callee's save area caps total
;; incoming args at 16. Reject silent miscompiles up front.
(callee-fty (cond
@@ -3795,8 +4473,8 @@
(recv-slot (cond
(sret?
(cg-alloc-slot cg
- (align-up (ctype-size rty) 8)
- (max 8 (ctype-align rty))))
+ (align-up (ctype-size rty) %CC-WORD-BYTES)
+ (max %CC-WORD-BYTES (ctype-align rty))))
(else #f))))
(let stage ((xs args) (idx 0))
(cond
@@ -3806,9 +4484,30 @@
(aty (opnd-type arg))
(n (%cg-param-reg-count aty)))
(cond
- ;; Aggregate >8B: load both halves into successive arg
+ ;; RV32 i64/u64 values consume two consecutive ABI words.
+ ((%ctype-wide-int? aty)
+ (%cg-load-wide-opnd-into cg arg 't0 't1)
+ (let chunk ((i 0))
+ (cond
+ ((= i 2) 0)
+ (else
+ (let ((tabi (+ idx sret-shift i))
+ (src (if (= i 0) 't0 't1)))
+ (cond
+ ((< tabi 4)
+ (%cg-emit-many
+ cg
+ (list "%mov("
+ (%cg-reg->bv (%reg-by-idx tabi)) ", "
+ (%cg-reg->bv src) ")\n")))
+ (else
+ (%cg-emit-st cg src 'sp
+ (* %CC-WORD-BYTES (- tabi 4)))))
+ (chunk (+ i 1))))))
+ (stage (cdr xs) (+ idx 2)))
+ ;; Multi-word aggregate: load its target-word chunks into successive arg
;; regs / stack slots. Stage the struct's address in t0
- ;; once and chunk-load 8 bytes at a time.
+ ;; once and chunk-load one target word at a time.
((and (%cg-param-aggregate? aty) (> n 1))
(%cg-emit-addr-of cg arg 't0)
(let chunk ((i 0))
@@ -3821,12 +4520,14 @@
(%cg-emit-many cg
(list "%ld("
(%cg-reg->bv (%reg-by-idx tabi))
- ", t0, " (%n (* i 8)) ")\n")))
+ ", t0, "
+ (%n (* i %CC-WORD-BYTES)) ")\n")))
(else
(%cg-emit-many cg
(list "%ld(t1, t0, "
- (%n (* i 8)) ")\n"))
- (%cg-emit-st cg 't1 'sp (* 8 (- tabi 4))))))
+ (%n (* i %CC-WORD-BYTES)) ")\n"))
+ (%cg-emit-st cg 't1 'sp
+ (* %CC-WORD-BYTES (- tabi 4))))))
(chunk (+ i 1)))))
(stage (cdr xs) (+ idx n)))
(else
@@ -3837,7 +4538,8 @@
(stage (cdr xs) (+ idx 1)))
(else
(%cg-load-opnd-into cg arg 't0)
- (%cg-emit-st cg 't0 'sp (* 8 (- abi 4)))
+ (%cg-emit-st cg 't0 'sp
+ (* %CC-WORD-BYTES (- abi 4)))
(stage (cdr xs) (+ idx 1)))))))))))
;; Stack-arg footprint accounts for the extra ABI slot any
;; >8B-aggregate arg consumed beyond its single-position cousin.
@@ -3860,16 +4562,20 @@
(cond
(has-result?
(cond
- ;; >16B sret (A2): a0 holds recv-slot; push as struct lval.
+ ((%ctype-wide-int? rty)
+ (%cg-spill-pair cg 'a0 'a1 rty))
+ ;; Wider-than-two-word sret (A2): a0 holds recv-slot; push as struct lval.
(sret? (cg-push cg (%opnd 'frame rty recv-slot #t)))
- ;; ≤16B struct/union (A1): fresh slot, spill from a0/a1.
+ ;; At-most-two-word struct/union (A1): fresh slot, spill from a0/a1.
((and (or (eq? rk 'struct) (eq? rk 'union))
- (<= (ctype-size rty) 16))
+ (<= (ctype-size rty) %CC-PAIR-BYTES))
(let* ((sz (ctype-size rty))
- (al (max 8 (ctype-align rty)))
- (slot (cg-alloc-slot cg (align-up sz 8) al)))
+ (al (max %CC-WORD-BYTES (ctype-align rty)))
+ (slot (cg-alloc-slot cg
+ (align-up sz %CC-WORD-BYTES) al)))
(%cg-emit-st-slot cg 'a0 slot)
- (cond ((> sz 8) (%cg-emit-st-slot cg 'a1 (+ slot 8))))
+ (cond ((> sz %CC-WORD-BYTES)
+ (%cg-emit-st-slot cg 'a1 (+ slot %CC-WORD-BYTES))))
(cg-push cg (%opnd 'frame rty slot #t))))
(else
(%cg-spill-reg cg 'a0 rty))))
@@ -3887,7 +4593,8 @@
((eq? rk 'void)
(%cg-emit-many cg (list "%b(&.ret)\n")))
((or (eq? rk 'struct) (eq? rk 'union))
- ;; struct-by-value: ≤16B (A1) → ret-slot; >16B (A2 sret) → *sret-slot.
+ ;; struct-by-value: at most two words (A1) → ret-slot; wider
+ ;; (A2 sret) → *sret-slot.
(let* ((p (cg-pop cg)) (sz (ctype-size ret-type)))
(cond ((not (opnd-lval? p))
(die #f "cg-return: struct value must be an lvalue")))
@@ -3899,6 +4606,12 @@
(%cg-emit-lea-slot cg "t2" (%cg-slot-expr cg ret-slot))))
(%cg-emit-byte-copy cg 't2 't0 't1 sz)
(%cg-emit-many cg (list "%b(&.ret)\n"))))
+ ((%ctype-wide-int? ret-type)
+ (let ((p (cg-pop cg)))
+ (%cg-load-wide-opnd-into cg p 'a0 'a1)
+ (%cg-emit-st-slot cg 'a0 ret-slot)
+ (%cg-emit-st-slot cg 'a1 (+ ret-slot %CC-WORD-BYTES))
+ (%cg-emit-many cg (list "%b(&.ret)\n"))))
(else
(let ((p (cg-pop cg)))
(%cg-load-opnd-into cg p 'a0)
@@ -3910,14 +4623,14 @@
;; --------------------------------------------------------------------
(define (cg-if cg then-thunk)
(let ((p (cg-pop cg)))
- (%cg-load-opnd-into cg p 't0)
+ (%cg-load-truth-into cg p 't0)
(%cg-emit-many cg (list "%if_nez(t0, {\n"))
(then-thunk)
(%cg-emit-many cg (list "})\n"))))
(define (cg-ifelse cg then-thunk else-thunk)
(let ((p (cg-pop cg)))
- (%cg-load-opnd-into cg p 't0)
+ (%cg-load-truth-into cg p 't0)
(%cg-emit-many cg (list "%ifelse_nez(t0, {\n"))
(then-thunk)
(%cg-emit-many cg (list "}, {\n"))
@@ -3933,38 +4646,48 @@
;; rval and store into the slot. Push the slot as one frame rval.
;;
;; Result type follows C11 §6.5.15 ¶5 for ternary: the usual arithmetic
-;; conversions over the two arms' types. The slot stores the raw 8-byte
+;; conversions over the two arms' types. The slot stores the raw target-word
;; payload (per cc.scm's canonical-form discipline); %cg-load-opnd-into
;; then re-canonicalizes on read against whatever common type we picked.
;; For `&&` / `||` callers both arms are pre-cast to %t-i32 by the
;; parser, so the merge is a no-op on type.
(define (cg-ifelse-merge cg then-thunk else-thunk)
(let* ((cond-op (cg-pop cg)))
- (%cg-load-opnd-into cg cond-op 't0)
+ (%cg-load-truth-into cg cond-op 't0)
(%cg-emit-many cg (list "%ifelse_nez(t0, {\n"))
(then-thunk)
(let* ((p (cg-pop cg))
(rty1 (opnd-type p))
(rk1 (ctype-kind rty1))
- ;; Struct/union arms can't ride the canonical 8-byte word
+ ;; Struct/union arms can't ride the canonical target-word
;; slot — the arm's bytes have to land in a slot sized to
;; the struct, and each arm memcpys its lvalue in. tcc's
;; expr_cond does this exact `type = bt1 == 6 ? type1 : type2`
;; pattern across CType structs, so without this case
;; cc.scm-compiled tcc-boot2 self-corrupts.
(aggr? (or (eq? rk1 'struct) (eq? rk1 'union)))
+ ;; On RV32 reserve both words for scalar merges. The second arm
+ ;; can widen the common type to i64/u64 after the first arm has
+ ;; already been emitted.
+ (pair-slot? (and (not aggr?) (= %CC-WORD-BYTES 4)))
(slot (cond (aggr?
(cg-alloc-slot cg
- (align-up (ctype-size rty1) 8)
- (max 8 (ctype-align rty1))))
+ (align-up (ctype-size rty1)
+ %CC-WORD-BYTES)
+ (max %CC-WORD-BYTES
+ (ctype-align rty1))))
+ (pair-slot?
+ (cg-alloc-slot cg
+ %CC-PAIR-BYTES %CC-WORD-BYTES))
(else
- (cg-alloc-slot cg 8 8)))))
- (%cg-merge-write-arm cg p slot aggr?)
+ (cg-alloc-slot cg
+ %CC-WORD-BYTES %CC-WORD-BYTES)))))
+ (%cg-merge-write-arm cg p slot aggr? pair-slot?)
(%cg-emit-many cg (list "}, {\n"))
(else-thunk)
(let* ((q (cg-pop cg))
(rty2 (opnd-type q)))
- (%cg-merge-write-arm cg q slot aggr?)
+ (%cg-merge-write-arm cg q slot aggr? pair-slot?)
(%cg-emit-many cg (list "})\n"))
;; Aggregate result is pushed as a frame lval so cg-copy-struct
;; (which asserts src must be lval) accepts it; %cg-emit-addr-of
@@ -3975,23 +4698,39 @@
slot
aggr?))))))
-(define (%cg-merge-write-arm cg op slot aggr?)
+(define (%cg-merge-write-arm cg op slot aggr? pair-slot?)
(cond
(aggr?
(%cg-emit-addr-of cg op 't0)
(%cg-emit-lea-slot cg "t2" (%cg-slot-expr cg slot))
(%cg-emit-byte-copy cg 't2 't0 't1 (ctype-size (opnd-type op))))
+ ((and pair-slot? (%ctype-wide-int? (opnd-type op)))
+ (%cg-load-wide-opnd-into cg op 'a0 'a1)
+ (%cg-emit-st-slot cg 'a0 slot)
+ (%cg-emit-st-slot cg 'a1 (+ slot %CC-WORD-BYTES)))
+ (pair-slot?
+ (%cg-load-opnd-into cg op 'a0)
+ (cond
+ ((%ctype-unsigned? (opnd-type op))
+ (%cg-emit-many cg (list "%li(a1, 0)\n")))
+ (else
+ (%cg-emit-many cg
+ (list "%sari(a1, a0, "
+ (%n (- %CC-WORD-BITS 1)) ")\n"))))
+ (%cg-emit-st-slot cg 'a0 slot)
+ (%cg-emit-st-slot cg 'a1 (+ slot %CC-WORD-BYTES)))
(else
(%cg-load-opnd-into cg op 'a0)
(%cg-emit-st-slot cg 'a0 slot))))
-;; Usual arithmetic conversion over two ctypes (C11 §6.3.1.8):
-;; integer-promote each (sub-int → int), then pick the wider with
-;; unsigned tie-break. Falls back to t1 for non-arithmetic kinds
-;; (pointer, struct, array — ternary on those preserves the first
-;; arm's type as before).
+;; Conditional-expression common type (C11 §6.5.15): preserve a pointer
+;; operand regardless of arm order (the other valid mixed operand is a null
+;; pointer constant), otherwise apply the usual arithmetic conversions.
+;; Aggregate conditionals retain the first arm's type as before.
(define (%cg-merge-arith-type t1 t2)
(cond
+ ((%ctype-ptr? t1) t1)
+ ((%ctype-ptr? t2) t2)
((and (%ctype-arith? t1) (%ctype-arith? t2))
(let ((p1 (cond ((< (ctype-size t1) 4) %t-i32) (else t1)))
(p2 (cond ((< (ctype-size t2) 4) %t-i32) (else t2))))
@@ -4014,7 +4753,7 @@
((zero? (cg-depth cg)) 0)
(else
(let ((c (cg-pop cg)))
- (%cg-load-opnd-into cg c 't0)
+ (%cg-load-truth-into cg c 't0)
(%cg-emit-many cg (list "%if_eqz(t0, { %break })\n")))))
(body-thunk tag)
(%cg-emit-many cg (list "%b(&.top)\n"
@@ -4030,11 +4769,11 @@
;; --------------------------------------------------------------------
;; Variadic receive (§G.2). Layout: cg-fn-begin/v reserves a 16-slot
-;; (8 bytes each) save area at known frame offsets, populating each
+;; (one target word each) save area at known frame offsets, populating each
;; slot from the appropriate ABI source — a-register for indices 0..3,
;; LDARG for indices 4..15. va_start sets ap to the address of the
;; first slot past the named-arg count; va_arg reads *ap, advances ap
-;; by 8, and pushes the value as the requested type.
+;; by one target word, and pushes the value as the requested type.
;;
;; ap is an lval (typically a `va_list` local). cg-va-start pops it,
;; computes the address, stores into *ap (or the slot directly), and
@@ -4063,8 +4802,8 @@
(%cg-emit-st cg 'a0 't0 0)))
(define (cg-va-arg cg ctype)
- ;; Pop ap-lval. Load ap into a0. Read 8 bytes at [a0] into a1.
- ;; Advance a0 by 8 and store back through ap-lval. Push a1 as rval
+ ;; Pop ap-lval. Load ap into a0. Read one word at [a0] into a1.
+ ;; Advance a0 by one word and store back through ap-lval. Push a1 as rval
;; of type ctype (caller cg-cast's if needed).
(let ((ap-lv (cg-pop cg)))
(cond ((not (opnd-lval? ap-lv))
@@ -4072,13 +4811,24 @@
;; Address of the storage that holds ap → t0; ap value → a0.
(%cg-emit-addr-of cg ap-lv 't0)
(%cg-emit-ld cg 'a0 't0 0)
- ;; Read *ap into a1 (full 8 bytes; cg-cast on the rval the caller
- ;; pushes will narrow if needed). Advance ap by 8 and store back.
+ ;; Read one or two ABI words, advance ap by the consumed width, and
+ ;; store the updated cursor back through the va_list lvalue.
(%cg-emit-ld cg 'a1 'a0 0)
- (%cg-emit-many cg (list "%addi(a0, a0, 8)\n"))
- (%cg-emit-st cg 'a0 't0 0)
- ;; Spill the loaded value (a1) to a fresh frame slot under ctype.
- (%cg-spill-reg cg 'a1 ctype)))
+ (cond
+ ((%ctype-wide-int? ctype)
+ (%cg-emit-ld cg 'a2 'a0 %CC-WORD-BYTES)
+ (%cg-emit-many cg
+ (list "%addi(a0, a0, "
+ (%n %CC-PAIR-BYTES) ")\n"))
+ (%cg-emit-st cg 'a0 't0 0)
+ (%cg-spill-pair cg 'a1 'a2 ctype))
+ (else
+ (%cg-emit-many cg
+ (list "%addi(a0, a0, "
+ (%n %CC-WORD-BYTES) ")\n"))
+ (%cg-emit-st cg 'a0 't0 0)
+ ;; Spill the loaded value (a1) to a fresh frame slot under ctype.
+ (%cg-spill-reg cg 'a1 ctype)))))
(define (cg-va-end cg)
;; va_end is a no-op in this design. Pop and discard ap-lval.
@@ -4106,22 +4856,33 @@
;; switch
;; --------------------------------------------------------------------
(define-record-type swctx
- (%swctx ctrl-slot end-tag default-lbl)
+ (%swctx ctrl-slot ctrl-type end-tag default-lbl)
swctx?
(ctrl-slot swctx-ctrl-slot)
+ (ctrl-type swctx-ctrl-type)
(end-tag swctx-end-tag)
(default-lbl swctx-default-lbl swctx-default-lbl-set!))
(define (cg-switch-begin cg)
(let* ((p (cg-pop cg))
- (off (cg-alloc-slot cg 8 8))
+ (ty (opnd-type p))
+ (wide? (%ctype-wide-int? ty))
+ (off (cg-alloc-slot cg
+ (if wide? %CC-PAIR-BYTES %CC-WORD-BYTES)
+ %CC-WORD-BYTES))
(tag (%cg-fresh-loop-tag cg))
(disp-lbl (bytevector-append "sw_disp_" tag)))
- (%cg-load-opnd-into cg p 't0)
- (%cg-emit-st-slot cg 't0 off)
+ (cond
+ (wide?
+ (%cg-load-wide-opnd-into cg p 't0 't1)
+ (%cg-emit-st-slot cg 't0 off)
+ (%cg-emit-st-slot cg 't1 (+ off %CC-WORD-BYTES)))
+ (else
+ (%cg-load-opnd-into cg p 't0)
+ (%cg-emit-st-slot cg 't0 off)))
(%cg-emit-many cg (list ".scope\n"
"%b(&." disp-lbl ")\n"))
- (%swctx off tag #f)))
+ (%swctx off ty tag #f)))
(define (cg-switch-case cg sw const-int)
(let* ((lbl (%cg-fresh-lbl cg))
@@ -4145,13 +4906,27 @@
(disp-lbl (bytevector-append "sw_disp_" tag)))
(%cg-emit-many cg (list "%break\n"
":." disp-lbl "\n"))
- (%cg-emit-many cg (list "%ld(t0, sp, "
- (%cg-slot-expr cg (swctx-ctrl-slot sw)) ")\n"))
- (for-each
- (lambda (c)
- (%cg-emit-many cg (list "%switch_case(t0, t1, "
- (%n (car c)) ", &." (cdr c) ")\n")))
- cases)
+ (cond
+ ((%ctype-wide-int? (swctx-ctrl-type sw))
+ (%cg-emit-ld-slot cg 't0 (swctx-ctrl-slot sw))
+ (%cg-emit-ld-slot cg 't1
+ (+ (swctx-ctrl-slot sw) %CC-WORD-BYTES))
+ (for-each
+ (lambda (c)
+ (%cg-emit-li-wide cg 'a0 'a1 (car c))
+ (%cg-emit-many
+ cg
+ (list "%i64_cmpset_eq(t2, t0, t1, a0, a1, a2)\n"
+ "%bnez(t2, &." (cdr c) ")\n")))
+ cases))
+ (else
+ (%cg-emit-many cg (list "%ld(t0, sp, "
+ (%cg-slot-expr cg (swctx-ctrl-slot sw)) ")\n"))
+ (for-each
+ (lambda (c)
+ (%cg-emit-many cg (list "%switch_case(t0, t1, "
+ (%n (car c)) ", &." (cdr c) ")\n")))
+ cases)))
(cond
(default-lbl (%cg-emit-many cg (list "%b(&." default-lbl ")\n")))
(else 0))
@@ -4172,21 +4947,21 @@
;; Each piece is either:
;; <bytevector> — raw bytes; emitted as bare hex chunks
;; (64 bytes / 128 hex chars per line).
-;; (label-ref . <label-bv>) — 8-byte pointer slot containing &label;
-;; emitted as `&<label> %(0)` (4B label ref +
-;; 4B zero pad).
+;; (label-ref . <label-bv>) — target-word pointer slot containing &label;
+;; (`&label` on RV32; `&label %(0)` on LP64).
(define (%cg-init-piece->bv piece)
(cond
((bytes? piece)
(bv-cat (%cg-bv->hex-lines piece #f)))
((and (pair? piece) (eq? (car piece) 'label-ref))
- (bv-cat (list "&" (cdr piece) " %(0)\n")))
+ (bv-cat (list "&" (cdr piece)
+ (if (= %CC-WORD-BYTES 4) "\n" " %(0)\n"))))
(else (die #f "cg-emit-global: bad init piece" piece))))
(define (cg-emit-global cg sym init)
(let* ((lbl (%cg-sym-label sym))
(sz (ctype-size (sym-type sym)))
- (size (if (< sz 0) 8 sz))
+ (size (if (< sz 0) %CC-WORD-BYTES sz))
(al (max 1 (ctype-align (sym-type sym)))))
(cond
(init
@@ -4447,7 +5222,7 @@
(let ((sm (scope-lookup ps n)))
(and sm (eq? (sym-kind sm) 'typedef))))
-(define (%mk-ptr p) (%ctype 'ptr 8 8 p))
+(define (%mk-ptr p) (%ctype 'ptr %CC-WORD-BYTES %CC-WORD-BYTES p))
(define (%mk-arr e n)
(%ctype 'arr (if (< n 0) -1 (* n (ctype-size e)))
(ctype-align e) (cons e n)))
@@ -4534,14 +5309,14 @@
;; __builtin_va_list — gcc/clang builtin type. We don't model
;; it as a struct; for our P1 ABI a va_list is just a char*
;; into the stack save area (cg-va-start/arg/end work over an
- ;; 8-byte slot). Letting __builtin_va_list mean `char *` here
+ ;; target-word slot). Letting __builtin_va_list mean `char *` here
;; lets a single header source — `typedef __builtin_va_list
;; va_list;` — compile cleanly under both cc.scm and stock
;; gcc/clang (where it's their native struct).
((and (not b) (eq? (tok-kind t) 'IDENT)
(bv= (tok-value t) "__builtin_va_list"))
(advance ps)
- (loop sto sn lg (%ctype 'ptr 8 8 %t-i8) #t))
+ (loop sto sn lg (%mk-ptr %t-i8) #t))
((and (not b) (eq? (tok-kind t) 'IDENT)
(let ((sm (scope-lookup ps (tok-value t))))
(and sm (eq? (sym-kind sm) 'typedef))))
@@ -4563,6 +5338,7 @@
((or (eq? b 'int) (and (not b) (or sn (not (zero? lg)))))
(cond ((= lg -1) (if (eq? sn 'unsigned) %t-u16 %t-i16))
((= lg 0) (if (eq? sn 'unsigned) %t-u32 %t-i32))
+ ((= lg 1) (if (eq? sn 'unsigned) %t-word-u %t-word-i))
(else (if (eq? sn 'unsigned) %t-u64 %t-i64))))
((eq? b 'float)
(if (or sn (not (zero? lg))) (die loc "float+qual") %t-flt))
@@ -4684,7 +5460,7 @@
(cond ((at-punct? ps 'comma) (advance ps))
((at-punct? ps 'rbrace) #t)
(else (die (tok-loc (peek ps)) "enum")))
- (loop (cons (cons nm val) vs) (+ val 1)))))))
+ (loop (cons (cons nm val) vs) (%c-value-add val 1)))))))
(tag (let ((e (tag-lookup ps tag)))
(cond (e e)
(else (let ((c (%ctype 'enum 4 4 (list tag '()))))
@@ -4715,28 +5491,12 @@
;; — pointer/array/etc. operands abort upstream.
(define (%const-trunc value ct)
(let* ((sz (ctype-size ct))
- (k (ctype-kind ct))
- (mask (cond ((<= sz 0) 0)
- ((= sz 1) #xff)
- ((= sz 2) #xffff)
- ((= sz 4) #xffffffff)
- (else -1))))
+ (k (ctype-kind ct)))
(cond
;; bool: 0 or 1.
- ((eq? k 'bool) (if (= value 0) 0 1))
- ;; 8-byte integers — value already fits in scheme's bignum.
- ((or (eq? k 'i64) (eq? k 'u64))
- (cond ((eq? k 'u64)
- ;; Mask to 64 bits without losing sign on negative values.
- value)
- (else value)))
- ((%ctype-unsigned? ct) (bit-and value mask))
- (else
- ;; Signed: mask to width, then sign-extend if top bit is set.
- (let* ((m (bit-and value mask))
- (sign-bit (arithmetic-shift 1 (- (* sz 8) 1))))
- (cond ((= 0 (bit-and m sign-bit)) m)
- (else (- m (arithmetic-shift 1 (* sz 8))))))))))
+ ((eq? k 'bool) (if (%c-value-zero? value) 0 1))
+ ((<= sz 0) 0)
+ (else (%c-value-trunc value sz (not (%ctype-unsigned? ct)))))))
;; Usual arithmetic conversions on (value . ctype) pairs. Both operands
;; have already been integer-promoted (≤ int → int) by the caller.
@@ -4773,7 +5533,7 @@
((< sz 4) (cons (%const-trunc v %t-i32) %t-i32))
(else vp))))
-(define (%const-bool? vp) (not (= 0 (car vp))))
+(define (%const-bool? vp) (not (%c-value-zero? (car vp))))
(define (parse-const-expr ps) (parse-const-cond ps))
@@ -4849,21 +5609,41 @@
(cond ((not hit) a)
(else (advance ps) (lp (hit a (next ps))))))))
-;; Arithmetic combiner: promote both, arith-conv, apply fn, truncate.
-(define (%const-arith-op fn a b)
+;; Arithmetic combiner: promote both, arith-conv, apply op, truncate.
+(define (%const-arith-op op a b)
(let-values (((av bv rt) (%const-arith-conv (%const-promote a) (%const-promote b))))
- (cons (%const-trunc (fn av bv) rt) rt)))
+ (cons (%const-trunc
+ (cond ((eq? op 'add) (%c-value-add av bv))
+ ((eq? op 'sub) (%c-value-sub av bv))
+ ((eq? op 'mul) (%c-value-mul av bv))
+ ((eq? op 'and) (%c-value-and av bv))
+ ((eq? op 'or) (%c-value-or av bv))
+ ((eq? op 'xor) (%c-value-xor av bv))
+ (else (die #f "const-expr: bad arithmetic op" op)))
+ rt)
+ rt)))
;; Like %const-arith-op but rejects a zero divisor.
-(define (%const-div-op fn a b)
+(define (%const-div-op want-rem? a b)
(let-values (((av bv rt) (%const-arith-conv (%const-promote a) (%const-promote b))))
- (cond ((= bv 0) (die #f "const-expr: divide by zero")))
- (cons (%const-trunc (fn av bv) rt) rt)))
+ (cond ((%c-value-zero? bv) (die #f "const-expr: divide by zero")))
+ (let ((qr (%c-value-divmod av bv (ctype-size rt) (%ctype-unsigned? rt))))
+ (cons (%const-trunc (if want-rem? (cdr qr) (car qr)) rt) rt))))
;; Comparison combiner: result is always (0-or-1 . %t-i32).
-(define (%const-cmp-op fn a b)
- (let-values (((av bv _rt) (%const-arith-conv (%const-promote a) (%const-promote b))))
- (cons (if (fn av bv) 1 0) %t-i32)))
+(define (%const-cmp-op op a b)
+ (let-values (((av bv rt) (%const-arith-conv (%const-promote a) (%const-promote b))))
+ (let ((c (%c-value-cmp av bv (ctype-size rt) (%ctype-unsigned? rt))))
+ (cons
+ (if (cond ((eq? op 'eq) (= c 0))
+ ((eq? op 'ne) (not (= c 0)))
+ ((eq? op 'lt) (< c 0))
+ ((eq? op 'le) (<= c 0))
+ ((eq? op 'gt) (> c 0))
+ ((eq? op 'ge) (>= c 0))
+ (else (die #f "const-expr: bad comparison op" op)))
+ 1 0)
+ %t-i32))))
;; Short-circuit per C11 §6.5.13/14 ¶4: rhs is not evaluated when the
;; lhs determines the result. Required so `1 || (1/0)` and
@@ -4922,31 +5702,35 @@
#f))
(define (parse-const-bor ps)
- (%const-binl ps parse-const-bxor (list (cons 'bar (lambda (a b) (%const-arith-op bit-or a b))))))
+ (%const-binl ps parse-const-bxor (list (cons 'bar (lambda (a b) (%const-arith-op 'or a b))))))
(define (parse-const-bxor ps)
- (%const-binl ps parse-const-band (list (cons 'caret (lambda (a b) (%const-arith-op bit-xor a b))))))
+ (%const-binl ps parse-const-band (list (cons 'caret (lambda (a b) (%const-arith-op 'xor a b))))))
(define (parse-const-band ps)
- (%const-binl ps parse-const-eq (list (cons 'amp (lambda (a b) (%const-arith-op bit-and a b))))))
+ (%const-binl ps parse-const-eq (list (cons 'amp (lambda (a b) (%const-arith-op 'and a b))))))
(define (parse-const-eq ps)
(%const-binl ps parse-const-rel
- (list (cons 'eq2 (lambda (a b) (%const-cmp-op = a b)))
- (cons 'ne (lambda (a b) (%const-cmp-op (lambda (x y) (not (= x y))) a b))))))
+ (list (cons 'eq2 (lambda (a b) (%const-cmp-op 'eq a b)))
+ (cons 'ne (lambda (a b) (%const-cmp-op 'ne a b))))))
(define (parse-const-rel ps)
(%const-binl ps parse-const-shift
- (list (cons 'lt (lambda (a b) (%const-cmp-op < a b)))
- (cons 'le (lambda (a b) (%const-cmp-op <= a b)))
- (cons 'gt (lambda (a b) (%const-cmp-op > a b)))
- (cons 'ge (lambda (a b) (%const-cmp-op >= a b))))))
+ (list (cons 'lt (lambda (a b) (%const-cmp-op 'lt a b)))
+ (cons 'le (lambda (a b) (%const-cmp-op 'le a b)))
+ (cons 'gt (lambda (a b) (%const-cmp-op 'gt a b)))
+ (cons 'ge (lambda (a b) (%const-cmp-op 'ge a b))))))
;; Shift combiner: result type is the (promoted) lhs type — rhs is
;; just a count, promoted independently. SIGN selects shl (+1) or shr (-1).
(define (%const-shift-op sign a b)
(let* ((ap (%const-promote a))
(bp (%const-promote b))
- (rt (cdr ap)))
- (cons (%const-trunc (arithmetic-shift (car ap) (* sign (car bp))) rt)
+ (rt (cdr ap))
+ (count (%c-value->fixnum (car bp) "const-expr shift")))
+ (cons (%const-trunc
+ (%c-value-shift (car ap) (* (- 0 sign) count)
+ (and (= sign -1) (not (%ctype-unsigned? rt))))
+ rt)
rt)))
(define (parse-const-shift ps)
@@ -4956,14 +5740,14 @@
(define (parse-const-add ps)
(%const-binl ps parse-const-mul
- (list (cons 'plus (lambda (a b) (%const-arith-op + a b)))
- (cons 'minus (lambda (a b) (%const-arith-op - a b))))))
+ (list (cons 'plus (lambda (a b) (%const-arith-op 'add a b)))
+ (cons 'minus (lambda (a b) (%const-arith-op 'sub a b))))))
(define (parse-const-mul ps)
(%const-binl ps parse-const-cast
- (list (cons 'star (lambda (a b) (%const-arith-op * a b)))
- (cons 'slash (lambda (a b) (%const-div-op quotient a b)))
- (cons 'pct (lambda (a b) (%const-div-op remainder a b))))))
+ (list (cons 'star (lambda (a b) (%const-arith-op 'mul a b)))
+ (cons 'slash (lambda (a b) (%const-div-op #f a b)))
+ (cons 'pct (lambda (a b) (%const-div-op #t a b))))))
(define (parse-const-cast ps)
;; (typename) operand — distinguished from ( expr ) by paren-is-group?.
@@ -5012,12 +5796,12 @@
(advance ps)
(let* ((vp (%const-promote (parse-const-cast ps)))
(rt (cdr vp)))
- (cons (%const-trunc (- 0 (car vp)) rt) rt)))
+ (cons (%const-trunc (%c-value-negate (car vp)) rt) rt)))
(($ tok? (kind PUNCT) (value tilde))
(advance ps)
(let* ((vp (%const-promote (parse-const-cast ps)))
(rt (cdr vp)))
- (cons (%const-trunc (bit-not (car vp)) rt) rt)))
+ (cons (%const-trunc (%c-value-not (car vp)) rt) rt)))
(($ tok? (kind PUNCT) (value bang))
(advance ps)
(let ((vp (parse-const-cast ps)))
@@ -5042,16 +5826,16 @@
(let*-values (((_sto bty) (parse-decl-spec ps))
((_n ty) (parse-declarator ps bty)))
(expect-punct ps 'rparen)
- (cons (max (ctype-size ty) 0) %t-u64)))
+ (cons (max (ctype-size ty) 0) %t-word-u)))
(else
;; sizeof(EXPR) in const-expr context. Operand is not
;; evaluated (C11 §6.5.3.4) — snapshot the cg, parse the
;; expr through the regular parser to recover its ctype,
;; then rewind to discard any emission/vstack pushes.
- (cons (%const-sizeof-expr ps #t) %t-u64))))
+ (cons (%const-sizeof-expr ps #t) %t-word-u))))
(else
;; `sizeof EXPR` (no parens). Same no-eval rule.
- (cons (%const-sizeof-expr ps #f) %t-u64))))
+ (cons (%const-sizeof-expr ps #f) %t-word-u))))
(else (parse-const-primary ps)))))
;; Does TOK begin a type-name? Type specifiers, qualifiers,
@@ -5079,10 +5863,7 @@
(pmatch t
(($ tok? (kind INT) (value ,v))
(advance ps)
- ;; Untyped INT literals ride as i32. Suffixes (L, LL, U) aren't
- ;; preserved through to the parser, but const-expr operands at
- ;; the granularity 118 cares about all fit in i32.
- (cons v %t-i32))
+ (cons (%c-int-raw v) (%c-int-type v)))
(($ tok? (kind CHAR) (value ,v))
(advance ps)
;; Character constants have type int in C.
@@ -5562,13 +6343,15 @@
;; N-byte little-endian encoding of integer n into a fresh bv. Bytes
;; >= sign-bit are filled by repeated >>8 (works for both signed and
;; unsigned because we only keep the low N bytes).
- (let ((out (make-bytevector nbytes 0)))
- (let loop ((i 0) (v n))
+ (let* ((out (make-bytevector nbytes 0))
+ (src (c-value-bytes (%c-value-coerce n))))
+ (let loop ((i 0))
(cond
((= i nbytes) out)
(else
- (bytevector-u8-set! out i (bit-and v 255))
- (loop (+ i 1) (arithmetic-shift v -8)))))))
+ (bytevector-u8-set! out i
+ (if (< i %C-VALUE-BYTES) (bytevector-u8-ref src i) 0))
+ (loop (+ i 1)))))))
;; File-scope compound literal (C99 §6.5.2.5). The bracketed initializer
;; following a typename in a static-storage initializer (or behind `&`
@@ -5963,10 +6746,10 @@
(define (%piece-bytesize p)
;; Output width of one piece (cf. %cg-init-piece->bv): a bv emits
- ;; one byte per element; a (label-ref . _) emits an 8-byte slot.
+ ;; one byte per element; a (label-ref . _) emits a target-word slot.
(cond
((bytes? p) (bytevector-length p))
- ((and (pair? p) (eq? (car p) 'label-ref)) 8)
+ ((and (pair? p) (eq? (car p) 'label-ref)) %CC-WORD-BYTES)
(else (die #f "init: unknown piece" p))))
(define (%pieces-bytesize ps-list)
@@ -6460,7 +7243,7 @@
(parse-expr ps) (rval! ps)
(expect-punct ps 'rparen) (expect-punct ps 'semi)
(let ((c (cg-pop cg)))
- (%cg-load-opnd-into cg c 't0)
+ (%cg-load-truth-into cg c 't0)
(%cg-emit-many cg (list "%if_eqz(t0, { %break })\n")))
(%cg-emit-many cg (list "%b(&.body)\n"
":.end\n"
@@ -6495,7 +7278,7 @@
((null? cond-toks) (cg-push-imm cg %t-i32 1))
(else (parse-saved-expr ps cond-toks) (rval! ps)))
(let ((c (cg-pop cg)))
- (%cg-load-opnd-into cg c 't0)
+ (%cg-load-truth-into cg c 't0)
(%cg-emit-many cg (list "%if_eqz(t0, { %break })\n")))
(push-loop-ctx! ps 'for tag #t)
(parse-stmt ps)
@@ -6822,7 +7605,7 @@
(let*-values (((_sto bty) (parse-decl-spec ps))
((_n ty) (parse-declarator ps bty)))
(expect-punct ps 'rparen)
- (cg-push-imm (ps-cg ps) %t-u64
+ (cg-push-imm (ps-cg ps) %t-word-u
(max (ctype-size ty) 0))))
(else
;; sizeof(EXPR): C semantics — operand is NOT evaluated.
@@ -6835,7 +7618,7 @@
(let* ((tp (cg-top (ps-cg ps)))
(sz (max (ctype-size (opnd-type tp)) 0)))
(cg-rewind (ps-cg ps) tag)
- (cg-push-imm (ps-cg ps) %t-u64 sz))))))
+ (cg-push-imm (ps-cg ps) %t-word-u sz))))))
(else
;; sizeof EXPR (no parens) — same no-eval rule.
(let ((tag (cg-snapshot (ps-cg ps))))
@@ -6843,7 +7626,7 @@
(let* ((tp (cg-top (ps-cg ps)))
(sz (max (ctype-size (opnd-type tp)) 0)))
(cg-rewind (ps-cg ps) tag)
- (cg-push-imm (ps-cg ps) %t-u64 sz))))))
+ (cg-push-imm (ps-cg ps) %t-word-u sz))))))
(else (parse-postfix ps))))
(define (token-is-decl? ps) (%tok-decl-start? ps (peek ps)))
@@ -7102,15 +7885,7 @@
(pmatch t
(($ tok? (kind INT) (value ,n))
(advance ps)
- ;; C99 §6.4.4.1: pick the smallest type that holds the value.
- ;; The lexer drops u/U/l/L suffixes before we get here, so we can't
- ;; tell `0x1L` from `0x1`. But a value that doesn't fit in int has
- ;; to widen anyway — otherwise `4294967296L + 7L` truncates to 7,
- ;; because cg-arith-conv leaves both operands at i32 width.
- (cg-push-imm (ps-cg ps)
- (cond ((<= n 2147483647) %t-i32)
- (else %t-i64))
- n))
+ (cg-push-imm (ps-cg ps) (%c-int-type n) (%c-int-raw n)))
(($ tok? (kind CHAR) (value ,c))
(advance ps)
;; C99 §6.4.4.4: an integer character constant has type int.
diff --git a/docs/CC.md b/docs/CC.md
@@ -1,7 +1,7 @@
# Minimal C subset (boot2)
Working doc. Baseline is C99; everything here is a delta against it. The
-target is **just enough C** to compile
+original target is **just enough C** to compile
`tcc-0.9.26-1147-gee75a10c/tcc.c`
@@ -38,7 +38,11 @@ Things outside both bounds are cut. Things admitted are load-bearing.
pre-flattening pass (system headers + tcc.c's `#include "libtcc.c"`
/ `"tcctools.c"` are spliced upstream of our compiler). See
[§Toolchain envelope](#toolchain-envelope).
-- **P1-64 only.** Sizes assume LP64. Porting to P1-32 is out of scope.
+- **P1-64 and P1-32.** `cc.scm` always emits portable P1pp. It selects
+ LP64 on the existing 64-bit backends and ILP32 on RV32 using scheme1's
+ `target-word-{bytes,bits}` primitives. The RV32 compiler is exercised
+ through emitted P1pp and native RV32 execution even though using it to
+ bootstrap an RV32 TCC is a separate, intentionally unimplemented stage.
- **No optimization.** Output P1pp is a stack-machine lowering with
every operand spilled to a frame slot. Codegen quality is a v2
problem.
@@ -94,7 +98,9 @@ Subset of C99 lexical grammar.
(`\uXXXX`) **not** supported.
- **Integers**: decimal, octal (`0…`), hex (`0x…`); suffixes
`u`, `U`, `l`, `L`, `ll`, `LL`, `ul`, `ull`, etc. (case-insensitive).
- All values fit in `unsigned long long` (64 bits).
+ All values fit in `unsigned long long` (64 bits). The lexer retains suffix
+ width/signedness metadata, while an exact eight-byte carrier keeps values
+ such as `UINT64_MAX` independent of scheme1's tagged-integer range.
- **Floats**: **not** present. The lexer rejects floating-point
literals. (HAVE_FLOAT is off.)
- **Characters**: `'c'` and standard escapes `\n \t \r \\ \' \" \0
@@ -152,7 +158,8 @@ Expression evaluator (used by `#if`/`#elif`):
- All integer operators including `defined NAME` / `defined(NAME)`.
- Identifiers that aren't macros evaluate to `0`. (Standard.)
-- Result is a 64-bit signed integer.
+- Values use the compiler's exact 64-bit C-integer carrier; literal type and
+ signedness follow the active ILP32/LP64 data model.
Macro expansion uses C11 6.10.3.4 hide-set discipline. Each token
carries the set of macro names already expanded into it; an identifier
@@ -161,28 +168,31 @@ defense against `#define A B\n#define B A`.
## Types
-### Primitives (P1-64)
-
-| Type | Size (bytes) | Align | Notes |
-|-----------------------|--------------|-------|------------------------------|
-| `void` | — | — | only as ptr-target / fn-ret |
-| `char` | 1 | 1 | signed by default |
-| `signed char` | 1 | 1 | |
-| `unsigned char` | 1 | 1 | |
-| `short` | 2 | 2 | |
-| `unsigned short` | 2 | 2 | |
-| `int` | 4 | 4 | |
-| `unsigned int` | 4 | 4 | |
-| `long` | 8 | 8 | LP64 |
-| `unsigned long` | 8 | 8 | |
-| `long long` | 8 | 8 | same as `long` in LP64 |
-| `unsigned long long` | 8 | 8 | |
-| pointer | 8 | 8 | tag-free; raw native address |
-| `_Bool` | 1 | 1 | values: `0`, `1` |
+### Primitive data models
+
+| Type | RV32 ILP32 size/align | P1-64 LP64 size/align | Notes |
+|------------------------|-----------------------|-----------------------|-------|
+| `void` | — | — | only as ptr-target / fn-ret |
+| `char` / `signed char` | 1 / 1 | 1 / 1 | `char` is signed by default |
+| `unsigned char` | 1 / 1 | 1 / 1 | |
+| `short` | 2 / 2 | 2 / 2 | |
+| `unsigned short` | 2 / 2 | 2 / 2 | |
+| `int` | 4 / 4 | 4 / 4 | |
+| `unsigned int` | 4 / 4 | 4 / 4 | |
+| `long` | 4 / 4 | 8 / 8 | target signed word |
+| `unsigned long` | 4 / 4 | 8 / 8 | target unsigned word |
+| `long long` | 8 / 8 | 8 / 8 | two-word pair on RV32 |
+| `unsigned long long` | 8 / 8 | 8 / 8 | two-word pair on RV32 |
+| pointer | 4 / 4 | 8 / 8 | tag-free native address |
+| `_Bool` | 1 / 1 | 1 / 1 | values: `0`, `1` |
`size_t` is `unsigned long`; `ptrdiff_t` is `long`; `intptr_t` /
`uintptr_t` are `long` / `unsigned long`. These typedefs come from the
-flattened headers; the language doesn't bake them in.
+flattened headers; the language doesn't bake them in. On RV32, 64-bit
+integer arithmetic, shifts, comparisons, calls/returns, globals, variadics,
+and `switch` values lower through libp1pp's little-endian `(lo, hi)` pair
+operations. Signed and unsigned division/remainder use the pair runtime
+helpers.
**Floating-point types** (`float`, `double`, `long double`,
`_Complex`, `_Imaginary`) are **parsed but never codegen'd**: prototypes
@@ -367,6 +377,15 @@ flattened header. They expand to direct frame-slot reads keyed off the
`stdarg.h` substitute is one of the headers shipped with the
compiler.
+The compiler ABI has four target-word argument registers (`a0`–`a3`) and
+word-indexed stack arguments after them. A scalar or pointer consumes one
+slot; an RV32 `long long` consumes two consecutive slots and is returned in
+`a0:a1`. Aggregates of at most two target words use the same direct-register
+convention; wider aggregate returns pass a hidden result pointer in `a0`.
+Variadic functions reserve a contiguous 16-target-word save area, and
+`va_arg(ap, long long)` advances by two RV32 words. Calls exceeding that
+documented 16-word cap are rejected rather than silently miscompiled.
+
## Standard library expectations
Our compiler doesn't bundle libc. The bootstrap script links the
diff --git a/docs/CCSCM.md b/docs/CCSCM.md
@@ -128,7 +128,7 @@ allocations at the end of a large translation unit.
| **2431–2540** | Paste operator: `%pp-paste-tokens`; string fusion: `%pp-maybe-fuse-str`; `#if` evaluator: `%pp-make-const-ps` (IO adapter wrapping token list as minimal pstate), `pp-eval-cexpr`, `%pp-resolve-defined`, `%pp-expand-line`, `%pp-idents-as-zero` |
| **2541–2640** | CG emission primitives: `%cg-emit-buf`, `%cg-emit`, `%cg-emit-many`, `%cg-fresh-label`, `%n` (number→bv) |
| **2641–2745** | CG metadata: `%cg-fn-set!/%cg-fn-get`; register/label helpers: `%cg-reg→bv`, `%cg-emit-li`, `%cg-emit-la`, slot-expr (`(+ %<fn>__SO N)` so the slot offset resolves through the per-fn `__SO` macro at M1pp time) |
-| **2745–2810** | Load/store emission: `%cg-emit-ld/st`, sub-byte width helpers; `%cg-emit-sext`; `%cg-canonicalize` (kind-driven sext/zext that puts a register back in canonical 64-bit form for its ctype); `%cg-emit-{ld,st}-bv` (width-dispatch core for the typed/slot-typed load+store family) |
+| **2745–2810** | Load/store emission: `%cg-emit-ld/st`, sub-byte width helpers; `%cg-emit-sext`; `%cg-canonicalize` (kind-driven sext/zext that puts a register back in canonical target-word form for its ctype); `%cg-emit-{ld,st}-bv` (width-dispatch core for the typed/slot-typed load+store family) |
| **2810–2860** | `%cg-emit-{ld,st}-{slot-,}typed` thin wrappers calling the `-bv` core; `%cg-spill-reg` |
| **2860–3020** | Operand loading: `%cg-load-opnd-into` (imm/frame/global) — re-canonicalizes a frame rval against its type kind on load via `%cg-canonicalize`; vstack ops: `cg-push/pop/top/depth/dup`, snapshot/rewind for sizeof |
| **3020–3170** | Materialize: `cg-push-imm`, `cg-push-string` (with intern), `cg-push-sym` (fn/enum/var/param), `cg-push-deref` (indirect-slot tracking) |
@@ -175,15 +175,15 @@ allocations at the end of a large translation unit.
- **Vstack-based codegen** — expression evaluation pushes/pops `opnd` records; values optionally spilled to frame slots
- **Macro hide-sets** — `tok` carries hide set to prevent recursive expansion (C11 §6.10.3.4)
- **Shared constant-expression evaluator** — `parse-const-*` serves both the parser (typed, with sizeof/cast/offsetof) and the preprocessor `#if` evaluator (`%pp-make-const-ps` wraps a token list as a minimal pstate with empty scope and `ps-cg = #f`); `%const-binl` is the generic left-associative binary-level pattern, fed by combiners (`%const-arith-op`, `%const-div-op`, `%const-cmp-op`, `%const-shift-op`) for every level from `||` down to `*` / `/` / `%`
-- **Sign-extension discipline** — narrow types (i8/i16/i32) stored as canonical 64-bit forms via shli/sari; widening casts are relabel-only. `%cg-canonicalize` centralises kind-driven sext/zext and is called from `%cg-load-opnd-into` (frame-rval load), `cg-cast` (narrowing), and `cg-binop` (narrow-typed result), so a relabel-only cast (e.g. via `cg-arith-conv`) reads correctly downstream.
-- **Sret (struct return)** — structs >16B use indirect result: caller passes pointer in `a0`
-- **Variadic ABI** — 16 contiguous 8-byte slots; args 0–3 from `a`-regs, 4+ from `LDARG`. `cg-va-start` / `cg-va-arg` route ap-lvalue stores/loads through `%cg-emit-addr-of`.
+- **Sign-extension discipline** — narrow types are stored in canonical target-word form via shli/sari; widening casts are relabel-only. `%cg-canonicalize` centralises kind-driven sext/zext and is called from `%cg-load-opnd-into` (frame-rval load), `cg-cast` (narrowing), and `cg-binop` (narrow-typed result), so a relabel-only cast (e.g. via `cg-arith-conv`) reads correctly downstream. RV32 i64/u64 values bypass the one-word path and use adjacent `(lo, hi)` slots.
+- **Sret (struct return)** — structs wider than two target words use an indirect result: caller passes a pointer in `a0`; one- and two-word aggregates return directly in `a0[:a1]`.
+- **Variadic ABI** — 16 contiguous target-word slots; args 0–3 come from `a`-regs and 4+ from `LDARG`. RV32 i64/u64 values consume two slots. `cg-va-start` / `cg-va-arg` route ap-lvalue stores/loads through `%cg-emit-addr-of`.
- **Tentative definitions** — collected in `world-tentatives`; emitted as `.bss` only if no full definition appears by TU end
- **FP softening** — float/double types parsed and sized per SysV ABI but all FP ops emit integer bitpattern operations
- **M1pp + hex2++ output** — bodies are wrapped in libp1pp's `%fn(name, frame, { … })`, which opens a hex2++ `.scope` and emits `%enter`/`%eret`. Compiler-internal labels (`:.ret`, loop `:.top`/`:.end`, switch `:.lbl_N`) are dotted scope-locals resolved by hex2++'s innermost-out scope walk; `%break` / `%continue` resolve through the same walk to the nearest enclosing scoped loop. User C labels use `cc__<fn>__user_<name>` global mangling so `goto` is unaffected by nested scopes (C labels have function scope, not block).
- **Alignment via `.align`** — `cg-emit-global` emits `.align <ctype-align>` before every `.data` or `.bss` symbol; `cg-intern-string` brackets each pooled string with `.align 8` so a non-multiple-of-4 string doesn't misalign the next instruction on aarch64. Intra-struct field padding is inline zero bytes — offsets are constant relative to the aligned struct start, so a `.align` directive there would be redundant.
- **Bare-hex string emission** — string pool and `(label-ref . LBL)` initializer pieces emit as bare hex chunks (≤64 bytes / 128 hex chars per line) consumed directly by hex2++.
-- **Ternary common type** — `cg-ifelse-merge` runs `%cg-merge-arith-type` over both arms after they emit; the result `opnd` carries the C11 §6.5.15 common type. The slot stores the raw 8-byte payload; `%cg-load-opnd-into` re-canonicalizes against whichever common type was picked. `&&`/`||` callers pre-cast both arms to `%t-i32`, so the merge is a no-op for them.
+- **Ternary common type** — `cg-ifelse-merge` runs `%cg-merge-arith-type` over both arms after they emit; the result `opnd` carries the C11 §6.5.15 common type. The merge slot stores one target word, or an adjacent pair for RV32 i64/u64; loads re-canonicalize against the selected common type. `&&`/`||` callers pre-cast both arms to `%t-i32`, so the merge is a no-op for them.
- **Single type-name predicate** — every "does this token start a type-name?" check runs through `%tok-decl-start?` (`%const-tok-is-decl?`, `%const-paren-is-cast?`, `token-is-decl?`, the cast-or-unary disambiguator, and `stmt-starts-decl?` which adds storage classes).
- **Shared bracket scanner** — `%punct-scan` is the one paren/bracket-depth walker, parameterised by stop predicate and an optional ternary-`?` tracking flag. All const-expr "skip dead arm" / "skip short-circuited rhs" helpers route through it.
- **One core ld/st helper** — `%cg-emit-{ld,st}-bv` is the shared body behind both the slot-typed (base = `sp`, off rendered through `%cg-slot-expr`) and typed (explicit base register, raw int off via `%n`) variants. Width dispatch lives in one place; the four wrappers are 1-line trampolines.
diff --git a/docs/LIBP1PP.md b/docs/LIBP1PP.md
@@ -25,8 +25,14 @@ definitions.
### Width
-libp1pp targets **P1-64 only**. Word size is 8 bytes. Pointer values,
-integer results, and syscall arguments are all one word.
+libp1pp targets both P1-64 and P1-32. Pointer values, ordinary integer
+results, and syscall arguments are one target word. Portable layouts use the
+backend constants `%p1_word_bytes`, `%p1_word_bits`, and `%p1_word_shift`;
+these are `8/64/3` on P1-64 and `4/32/2` on RV32.
+
+M1PP `%struct` records intentionally retain their historical padded
+eight-byte field stride on both variants. They are suitable for stable
+internal records, but must not be mistaken for packed arrays of target words.
### Syscall numbers
@@ -46,6 +52,13 @@ Parsers return two words under the two-word direct-result convention:
`(value, consumed)`. `consumed == 0` means the input did not begin with a
syntactically valid token; the `value` word is then unspecified.
+On P1-32, the `%i64_*` macro family implements add/subtract/negate,
+multiplication, comparisons, and shifts over a little-endian `(lo, hi)` word
+pair. `p1_i64_udivmod` and `p1_i64_divmod` provide unsigned and signed
+division/remainder for the same representation, returning quotient in
+`a0:a1` and remainder in `a2:a3`. `cc.scm` uses this layer to lower C
+`long long`; P1-64 continues to use native one-word operations.
+
Functions whose return type is "nothing meaningful" return `0` in `a0`.
### String representation
@@ -261,9 +274,9 @@ per-expansion local labels that cannot collide.
## Frame locals
-libp1pp does not introduce a new local-variable macro. Use M1PP's `%struct`
-directly: its 8-byte stride matches `WORD` on P1-64, and it already
-synthesizes `%name.SIZE` for `ENTER`.
+libp1pp does not introduce a packed target-word local-variable macro. M1PP's
+`%struct` can be used directly when its stable eight-byte padded slots are
+acceptable, and it synthesizes `%name.SIZE` for `ENTER`.
%struct parse_f { state cursor endp tmp }
@@ -280,9 +293,11 @@ staging:
%struct parse_f { _o0 _o1 state cursor endp tmp }
-The caller places outgoing argument word `k` at `[sp + k * 8]` immediately
-before the `CALL`, then reads locals from higher offsets. libp1pp does not
-otherwise enforce this convention.
+The caller places outgoing argument word `k` at
+`[sp + k * %p1_word_bytes]` immediately before the `CALL`, then reads locals
+from higher offsets. On P1-32 these packed ABI words do not line up with every
+eight-byte `%struct` field; reserve the outgoing prefix with explicit
+target-word offsets. libp1pp does not otherwise enforce this convention.
## Function definition
@@ -324,7 +339,7 @@ M1PP-time integer expression that the backend `%enter` macro accepts.
Like `%fn`, but the second argument is a braced list of local names
instead of a byte frame size. Synthesizes a `name_FRAME` `%struct` (one
-8-byte slot per local), opens both a hex2++ `.scope` and an M1PP
+padded eight-byte slot per local on every target), opens both a hex2++ `.scope` and an M1PP
`%frame` named after the function, and sizes the stack frame from
`%name_FRAME.SIZE`.
@@ -561,4 +576,3 @@ beyond the original branch.
Because the failure path issues a `CALL`, `%assert_*` may be used only in
functions that have established a frame with `ENTER`.
-
diff --git a/docs/M1PP.md b/docs/M1PP.md
@@ -24,7 +24,7 @@ The implementation lives in `M1pp/M1pp.c`. It is one pass, allocation-free
zero-arg macros plus `SIZE`/`COUNT`
- Named stack-frame access via `%frame` / `%endframe` + `%local(field)`,
composing with `%struct`-generated `<frame>_FRAME.<field>` macros
-- Compile-time integer expression language (Lisp S-expressions:
+- Compile-time target-word integer expression language (Lisp S-expressions:
arithmetic, bitwise, shift, comparison, `strlen`)
- Little-endian hex emission: `!` (1B), `@` (2B), `%` (4B), `$` (8B) —
emits bare hex digits (e.g. `AABBCCDD`) consumable by `hex2++`
@@ -93,6 +93,11 @@ Synthesizes zero-parameter macros for fixed 8-byte-per-field layout:
Fields are separated by whitespace, commas, or newlines.
+The stride remains eight bytes on P1-32 as well as P1-64. It is a stable
+M1PP record-layout convention, not a declaration of the target's native word
+size. P1pp code that lays out native pointers or ABI words uses the backend's
+`%p1_word_bytes` constant explicitly.
+
### `%enum`
%enum NAME { l1 l2 l3 ... }
@@ -170,9 +175,13 @@ These are recognized wherever a token matches, not only at line start.
%(expr) → 4-byte little-endian hex
$(expr) → 8-byte little-endian hex
-The expression is evaluated to a signed 64-bit integer and emitted as
-bare hex digits (e.g. `AABBCCDD`). `hex2++` consumes whitespace-separated
-hex bytes directly, so no quoting or wrapping is required.
+The P1 implementation evaluates expressions in one signed target word:
+32 bits for P1-32 and 64 bits for P1-64. Arithmetic, bitwise operations, and
+shifts therefore have target-word wrap and sign behavior. `!`, `@`, and `%`
+emit the low 8, 16, and 32 bits respectively. `$` always emits eight bytes;
+on P1-32 its high four bytes are zero padding because only one 32-bit value
+was evaluated. `hex2++` consumes the resulting whitespace-separated hex
+bytes directly, so no quoting or wrapping is required.
### `%select(cond, then, else)`
diff --git a/docs/OS.md b/docs/OS.md
@@ -28,18 +28,22 @@ redirection, working-directory state) is out of scope. See
## Targets
-Three architectures, identical contract. P1-64 only (LP64).
+Four Linux user-space targets share the contract. The first three are P1-64
+(LP64); RV32 is P1-32 (ILP32) and currently hosts boot0–boot2 under Podman /
+qemu-user only. The boot6 seed-kernel implementation remains unavailable for
+RV32 until its TCC target exists.
| arch | platform | syscall instr | arg regs | nr reg | ret reg |
|---------|-----------------|------------------|-----------------------------|--------|---------|
| amd64 | Linux x86-64 | `syscall` | rdi rsi rdx r10 r8 r9 | rax | rax |
| aarch64 | Linux ARM64 | `svc #0` | x0 x1 x2 x3 x4 x5 | x8 | x0 |
| riscv64 | Linux RISC-V 64 | `ecall` | a0 a1 a2 a3 a4 a5 | a7 | a0 |
+| riscv32 | Linux RISC-V 32 | `ecall` | a0 a1 a2 a3 a4 a5 | a7 | a0 |
These are the native Linux ABIs; the per-arch shims in
-`P1/P1-{aarch64,amd64,riscv64}.M1pp` (`%macro p1_syscall`, lines
+`P1/P1-{aarch64,amd64,riscv64,riscv32}.M1pp` (`%macro p1_syscall`, lines
~520–930) marshal P1 registers into them. Any kernel that implements
-these three ABIs verbatim can host the chain.
+the relevant ABI verbatim can host that target's implemented chain.
## Platform layers
@@ -96,8 +100,8 @@ Standard Linux SysV layout. The kernel must place at the initial
stack pointer, low to high:
```
-sp + 0 argc (word)
-sp + 8 argv[0] (pointer)
+sp + 0*WORD argc (word)
+sp + 1*WORD argv[0] (pointer)
...
argv[argc-1]
NULL (argv terminator)
@@ -130,9 +134,10 @@ chain reads it.
Trap instruction, argument registers, syscall-number register, and
return register are listed per arch in §Targets. Syscall numbers
default to the standard Linux-on-`uname-m` values used by the per-arch
-P1 macros (e.g. `read=63` on aarch64, `read=0` on amd64). A
+P1 macros (e.g. `read=63` on aarch64/RISC-V, `read=0` on amd64). A
fresh-write OS may renumber, but only at the cost of also rewriting
-the per-arch `p1_sys_*` macros in `P1/P1-{aarch64,amd64,riscv64}.M1pp`.
+the per-arch `p1_sys_*` macros in
+`P1/P1-{aarch64,amd64,riscv64,riscv32}.M1pp`.
Error returns follow the standard Linux convention: a non-negative
result on success or a negative errno value in the return register.
@@ -144,16 +149,21 @@ See [§Error convention](#error-convention).
Wired in `P1/P1pp.P1pp:986-1055`.
-| name | linux nr (aa64 / amd64 / riscv64) | semantics |
-|-----------|-----------------------------------|------------------------------------------------------|
-| read | 63 / 0 / 63 | `ssize_t read(fd, buf, len)` |
-| write | 64 / 1 / 64 | `ssize_t write(fd, buf, len)` |
-| openat | 56 / 257 / 56 | called as `openat(AT_FDCWD=-100, path, flags, mode)` |
-| close | 57 / 3 / 57 | `int close(fd)` |
-| lseek | 62 / 8 / 62 | `off_t lseek(fd, off, whence)` |
-| brk | 214 / 12 / 214 | `void *brk(addr)`; `addr=0` returns current break |
-| unlinkat | 35 / 263 / 35 | called as `unlinkat(AT_FDCWD=-100, path, 0)` |
-| exit_group| 93 / 60 / 93 | `void exit(status)`; never returns |
+| name | linux nr (aa64 / amd64 / rv64 / rv32) | semantics |
+|-----------|----------------------------------------|------------------------------------------------------|
+| read | 63 / 0 / 63 / 63 | `ssize_t read(fd, buf, len)` |
+| write | 64 / 1 / 64 / 64 | `ssize_t write(fd, buf, len)` |
+| openat | 56 / 257 / 56 / 56 | called as `openat(AT_FDCWD=-100, path, flags, mode)` |
+| close | 57 / 3 / 57 / 57 | `int close(fd)` |
+| lseek | 62 / 8 / 62 / 62 | `off_t lseek(fd, off, whence)` |
+| brk | 214 / 12 / 214 / 214 | `void *brk(addr)`; `addr=0` returns current break |
+| unlinkat | 35 / 263 / 35 / 35 | called as `unlinkat(AT_FDCWD=-100, path, 0)` |
+| exit_group| 93 / 60 / 93 / 93 | `void exit(status)`; never returns |
+
+Linux RV32 assigns number 62 to `_llseek(fd, hi, lo, result, whence)` rather
+than the three-argument 64-bit `lseek`. Its backend wrapper sign-extends the
+target-word offset, supplies a result slot, and returns the low 32-bit result
+through libp1pp's one-word API.
Everything in `docs/LIBC.txt`'s "syscall-using" column reduces to
exactly these eight (`fopen → openat`, `fseek → lseek`, `malloc/
@@ -198,11 +208,11 @@ Per-arch macros already exist in `P1/P1-*.M1pp`. The scheme1 prelude's
`spawn` / `run` / `wait` / `exit` are built directly on these
(`scheme1/prelude.scm:520-537`).
-| name | linux nr (aa64 / amd64 / riscv64) | driver role |
-|---------|-----------------------------------|-------------------------------------------|
-| clone | 220 / 56 / 220 | spawn child; called bare (no flags arg in the prelude — kernel must accept clone-as-fork with SIGCHLD) |
-| execve | 221 / 59 / 221 | image swap; takes `(prog, argv)` — no envp arg in the prelude wrapper, so the kernel-side execve must accept a NULL/empty envp without erroring |
-| waitid | 95 / 247 / 95 | reap child; called as `waitid(P_PID=1, pid, info, WEXITED=4)` — info[8]=si_code, info[24]=si_status (`scheme1/prelude.scm:497-506`) |
+| name | linux nr (aa64 / amd64 / rv64 / rv32) | driver role |
+|---------|----------------------------------------|-------------------------------------------|
+| clone | 220 / 56 / 220 / 220 | spawn child; called bare (no flags arg in the prelude — kernel must accept clone-as-fork with SIGCHLD) |
+| execve | 221 / 59 / 221 / 221 | image swap; takes `(prog, argv)` — no envp arg in the prelude wrapper, so the kernel-side execve must accept a NULL/empty envp without erroring |
+| waitid | 95 / 247 / 95 / 95 | reap child; called as `waitid(P_PID=1, pid, info, WEXITED=4)` — info[8]=si_code, info[24]=si_status (`scheme1/prelude.scm:497-506`) |
#### Process lifecycle
diff --git a/docs/P1.md b/docs/P1.md
@@ -16,6 +16,18 @@ portable incoming stack-argument area.
Portable source may directly return `0..1` word. Wider results use the
portable indirect-result convention described below.
+The implemented backends are:
+
+| P1 variant | Backend files | Linux target |
+|------------|---------------|--------------|
+| P1-64 | `P1-{aarch64,amd64,riscv64}.M1[pp]` | AArch64, x86-64, RV64 |
+| P1-32 | `P1-riscv32.M1[pp]` | RV32IM |
+
+Each M1pp backend publishes `%p1_word_bytes`, `%p1_word_bits`, and
+`%p1_word_shift` (`4/32/2` on RV32, `8/64/3` on P1-64). Portable P1pp
+source uses those constants for native pointer arrays, ABI slots, and frame
+layout instead of assuming eight-byte words.
+
## Toolchain envelope
P1 source is assembled by the `M1pp → hex2++` chain, with `catm` as the
diff --git a/docs/R7RS-micro.md b/docs/R7RS-micro.md
@@ -168,9 +168,9 @@ Micro recognizes:
Binary, octal, explicit decimal, exactness, inexactness, rational, decimal
point, exponent, and complex-number syntax are outside the profile.
-The supported exact-integer range is implementation-defined. On every boot2
-target it is `-2^60` through `2^60-1`, inclusive, using one tagged 64-bit
-machine word.
+The supported exact-integer range is implementation-defined. Boot2 uses one
+machine word with three tag bits: `-2^60` through `2^60-1` inclusive on
+P1-64, and `-2^28` through `2^28-1` inclusive on P1-32.
### Characters
@@ -857,10 +857,14 @@ Boot2 exposes low-level operations used for diagnostics and generic copying:
(heap-usage)
(collect-garbage)
+(target-word-bytes)
+(target-word-bits)
```
These procedures expose representation details and are not portable Scheme.
The indexed record operations require a record and a valid field index.
+The target-word procedures return `4`/`32` on P1-32 and `8`/`64` on P1-64;
+they let bootstrap tools such as `cc.scm` choose target-sized layouts.
### Unsafe memory inspection
diff --git a/docs/SCHEME1-GC.md b/docs/SCHEME1-GC.md
@@ -1,15 +1,16 @@
# scheme1 garbage collector
`scheme1` uses a non-moving, stop-the-world mark-and-sweep collector.
-All pairs, headered Scheme objects, and raw byte buffers share one 256 MiB
-managed heap. Object addresses never change, so `eq?`, mutation, record
-identity, and unsafe address-inspection behavior remain stable across a
-collection.
+All pairs, headered Scheme objects, and raw byte buffers share one managed
+heap: 256 MiB on P1-64 and 15 MiB on P1-32. Object addresses never change,
+so `eq?`, mutation, record identity, and unsafe address-inspection behavior
+remain stable across a collection.
## Managed block layout
Every allocation has a 16-byte collector header immediately before its
-unchanged payload:
+unchanged payload. The two fields occupy padded eight-byte layout slots even
+when the stored target word is four bytes:
```
header + 0: (total block bytes << 8) | kind | mark
@@ -25,9 +26,9 @@ candidates. That word is reused by the mark worklist while collecting and
by the free list after sweeping.
Scheme-level layouts and tags are unchanged. A pair payload is still two
-words and receives `TAG.PAIR`; a headered object still starts with its
-`HDR` word and receives `TAG.HEAP`; bytevector data and symbol-name copies
-are untagged `RAW` payload pointers.
+padded layout slots and receives `TAG.PAIR`; a headered object still starts
+with its `HDR` target word and receives `TAG.HEAP`; bytevector data and
+symbol-name copies are untagged `RAW` payload pointers.
## Allocation
diff --git a/docs/SCHEME1.md b/docs/SCHEME1.md
@@ -20,8 +20,9 @@ boot2 compiler/process/file-I/O layer.
dotted-pair separator, not a symbol.
- **Booleans**: `#t`, `#f`.
- **Integers**: decimal (`42`, `-7`, `+3`) and lowercase-prefix hex
- (`#xff`, `#x-1a`). Values range from `-2^60` through `2^60-1` on the
- boot2 targets. Out-of-range literals and results fail explicitly. No
+ (`#xff`, `#x-1a`). Three tag bits leave the range `-2^60` through
+ `2^60-1` on P1-64 and `-2^28` through `2^28-1` on P1-32. Out-of-range
+ literals and results fail explicitly. No
`#X`, `#o`, `#b`, floats, rationals, or bignums.
- **Strings**: `"…"`. Escapes: `\n \t \r \\ \"` and inline-hex `\xNN;`
(1+ hex digits, value 0..255, terminated by `;`). Strings have an
@@ -49,7 +50,7 @@ The runtime knows exactly:
| Type | Notes |
|----------------|--------------------------------------------------------------|
| boolean | `#t`, `#f` |
-| integer | tagged exact integer in `[-2^60, 2^60-1]` |
+| integer | tagged exact integer; 61 value bits on P1-64, 29 on P1-32 |
| character | byte-repertoire character; disjoint from integer |
| symbol | globally interned; `eq?`-comparable |
| string | mutable, explicit-length character sequence (`HDR.STRING`) |
@@ -224,10 +225,14 @@ single-value context; 0 or 2+ args produce an MV-pack consumable by
`sys-exit code` (does not return).
**Garbage collection**
-`heap-usage`, `collect-garbage`.
+`heap-usage`, `collect-garbage`, `target-word-bytes`, `target-word-bits`.
`heap-usage` reports currently allocated managed bytes, including the
16-byte header on each allocation. `collect-garbage` performs a synchronous
-collection and returns unspecified. Collection also runs automatically when
+collection and returns unspecified. `target-word-bytes` and
+`target-word-bits` return `4`/`32` on
+P1-32 and `8`/`64` on P1-64 so Scheme-hosted tools can select their target
+data model without inspecting the host platform.
+Collection also runs automatically when
an allocation cannot be satisfied without reclaiming garbage. See
[SCHEME1-GC.md](SCHEME1-GC.md) for the heap and exact-rooting design.
diff --git a/docs/TOUR.md b/docs/TOUR.md
@@ -31,13 +31,20 @@ change where compiling stages execute; their inputs, outputs, and shell
scripts are identical. Boot7 is the same host-side installation step over
either driver's already-built artifacts.
+That eight-stage map is complete for `aarch64`, `amd64`, and `riscv64`.
+`riscv32` follows the same seed-derived path through boot2, producing native
+RV32 `catm` and `scheme1`, then stops at an explicit boundary: the TCC target
+and boot3–boot7 are intentionally unimplemented. `cc.scm` itself is
+ILP32-aware and is tested by compiling C to P1pp and lowering that P1pp to
+RV32; it simply is not yet used to bootstrap an RV32 TCC.
+
## §1. boot0 — from a hex seed to a macro assembler
**You arrive with**: nothing of ours. Just `sh`, `podman` or
`qemu-user-static`, and the seven [vendored seed
files](../vendor/seed/) per arch. `hex0-seed` is the only opaque
artifact; it is a few hundred bytes (526 / 229 / 392 for
-aarch64 / amd64 / riscv64).
+aarch64 / amd64 / riscv64, and 356 for riscv32).
**boot0 builds**: `hex2`, `catm`, `M0`.
@@ -87,6 +94,9 @@ mnemonics into native machine code. `M1pp.P1` and `hex2pp.P1` are
~5000 and ~3100 lines of P1 source. They are the first programs in
this chain written in our own pseudo-ISA, and are the first sources that
are naturally human-readable (ie not hex bytes) and portable.
+The `riscv32` backend is the first P1-32 implementation; its word-sized
+loads, pointers, argument slots, and frame header are four bytes while the
+same portable sources continue to use eight-byte words on P1-64.
**Trust extension**: M1pp accepts the macro flavour every later
`.P1pp` file uses (function-like macros, struct/enum synthesis,
diff --git a/hex2pp/hex2pp.P1 b/hex2pp/hex2pp.P1
@@ -145,7 +145,8 @@ DEFINE H2_OFF_scope_history 0042801100000000
# arg_ptr = argv[i]
la_a0 &saved_argv
ld_a1,a0,0
- shli_t2,t0,3
+ li_t2 p1wordshift
+ shl_t2,t0,t2
add_a1,a1,t2
ld_a0,a1,0
la_a2 &arg_ptr
@@ -244,7 +245,8 @@ DEFINE H2_OFF_scope_history 0042801100000000
blt_t1,t0
la_a0 &saved_argv
ld_a1,a0,0
- shli_t2,t0,3
+ li_t2 p1wordshift
+ shl_t2,t0,t2
add_a1,a1,t2
ld_a0,a1,0
la_br &parse_long
diff --git a/scheme1/scheme1.P1pp b/scheme1/scheme1.P1pp
@@ -16,7 +16,7 @@
%enum HDR { BV STRING CLOSURE PRIM TD REC MV HASH HASHDATA ENV }
%enum GCKIND { FREE PAIR HEAP RAW }
-# Each managed block begins with two machine words. The first word is
+# Each managed block begins with two padded 8-byte slots. The first word is
# (total_block_bytes << 8) | kind | mark
# and the second is an intrusive link, reused by the free list and mark
# worklist. Payloads retain their historical layouts and tags.
@@ -50,7 +50,10 @@
%macro SYMTAB_CAP_SLOTS() 8192 %endm
%macro READBUF_CAP_BYTES() 1048576 %endm
-%macro HEAP_CAP_BYTES() 0x10000000 %endm
+# A block header stores byte size above eight flag bits. P1-32 therefore
+# caps an individual block below 16 MiB; P1-64 keeps the original 256 MiB
+# heap. The source/read/symbol arenas are unchanged.
+%macro HEAP_CAP_BYTES() %p1_scheme_heap_bytes %endm
%macro GC_ROOT_CAP_FRAMES() 8192 %endm
%macro GC_ROOT_FRAME_BYTES() 24 %endm
@@ -168,17 +171,17 @@
# Build the two-bit lexical-environment filter mask for tagged symbol `sym`.
# Symbol identity is its stable intern-table index, so two inexpensive affine
-# projections give deterministic bit positions in one machine word. `rd`,
+# projections give deterministic bit positions in one target word. `rd`,
# `scratch0`, and `scratch1` must be distinct; both scratches are clobbered.
%macro env_sym_mask(rd, sym, scratch0, scratch1)
%untag_sym(scratch0, sym)
- %andi(rd, scratch0, 63)
+ %andi(rd, scratch0, (- %p1_word_bits 1))
%li(scratch1, 1)
%shl(rd, scratch1, rd)
%li(scratch1, 13)
%mul(scratch0, scratch0, scratch1)
%addi(scratch0, scratch0, 17)
- %andi(scratch0, scratch0, 63)
+ %andi(scratch0, scratch0, (- %p1_word_bits 1))
%li(scratch1, 1)
%shl(scratch0, scratch1, scratch0)
%or(rd, rd, scratch0)
@@ -263,7 +266,7 @@
%call(&sym_set_global)
%endm
-# Exact shadow-root frames. Every frame records the native frame pointer
+# Exact shadow-root frames. Every frame records the portable local base
# plus two bitmaps: tagged Scheme-reference slots and temporarily live raw
# managed-allocation pointers. Bit N describes native local slot N. The
# collector dereferences only those described slots; ordinary machine
@@ -273,7 +276,8 @@
%addi(t1, t0, %GC_ROOT_FRAME_BYTES)
%ld_global(t2, &gc_root_end)
%bltu(t2, t1, &@overflow)
- %addi(t2, sp, 16)
+ # p1_addi sees native sp directly; step over the two-word backend header.
+ %addi(t2, sp, (* 2 %p1_word_bytes))
%st(t2, t0, 0)
%li(t2, scheme_mask)
%st(t2, t0, 8)
@@ -299,7 +303,7 @@
%endm
%macro gc_frame_clear(frame_size)
- %addi(t0, sp, 16)
+ %addi(t0, sp, (* 2 %p1_word_bytes))
%li(t1, frame_size)
%li(t2, 0)
:@loop
@@ -409,7 +413,7 @@
# load_source(argv[1])
%ld_global(a0, &saved_argv)
- %ld(a0, a0, 8)
+ %ld(a0, a0, %p1_word_bytes)
%call(&load_source)
# read-eval loop
@@ -504,7 +508,8 @@
%ldl(t0, cursor)
%stl(t0, digits)
- # magnitude limit = 2^60-1, plus one for the negative endpoint.
+ # Magnitude limit is the target word's tagged-fixnum maximum, plus
+ # one for the negative endpoint.
%li(t0, -1)
%shri(t0, t0, 4)
%ldl(t1, negative)
@@ -673,7 +678,7 @@
:.hex_parse
# Strip redundant leading zeroes so a long but representable spelling
# is accepted without allowing parse_hex's machine-word accumulator to
- # wrap. At most sixteen significant hexadecimal digits remain.
+ # wrap. At most one target word of significant hex digits remains.
:.hex_trim_zero
%lb(t1, a0, 0)
%addi(t1, t1, -48)
@@ -684,7 +689,7 @@
%addi(a1, a1, -1)
%b(&.hex_trim_zero)
:.hex_count_check
- %li(t0, 17)
+ %li(t0, (+ (/ %p1_word_bits 4) 1))
%bltu(a1, t0, &.hex_parse_go)
%b(&.integer_overflow)
:.hex_parse_go
@@ -693,8 +698,8 @@
%ldl(t0, number_len)
%bne(a1, t0, &.number_bad)
# Compare the unsigned magnitude before applying the sign. Positive
- # values allow 2^60-1; the one extra magnitude 2^60 is valid only for
- # the negative endpoint.
+ # Positive values allow the target tagged-fixnum maximum; one extra
+ # magnitude is valid only for the negative endpoint.
%li(t0, -1)
%shri(t0, t0, 4)
%ldl(t1, number_neg)
@@ -4650,7 +4655,7 @@
%stl(t0, len)
%b(&.hex_trim_zero)
:.hex_count_check
- %li(t1, 17)
+ %li(t1, (+ (/ %p1_word_bits 4) 1))
%bltu(t0, t1, &.hex_parse_go)
%b(&.overflow)
:.hex_parse_go
@@ -4825,28 +4830,14 @@
.endscope
# checked_mul_raw(x=a0, y=a1) -> raw exact result. Inputs are already in
-# the tagged-fixnum range. Products of two values whose magnitudes are below
-# 2^30 are provably in range and take a division-free fast path. Larger
-# operands use sign-quadrant bounds checks before the machine multiply, so
-# host wraparound is never used as a Scheme result.
+# the tagged-fixnum range. Sign-quadrant bounds checks happen before the
+# machine multiply, so host-word wraparound is never used as a Scheme result.
:checked_mul_raw
.scope
%beqz(a0, &.zero)
%beqz(a1, &.zero)
- # abs(x), abs(y) <= 2^30-1 implies
- # abs(x*y) <= (2^30-1)^2 < 2^60.
- %li(t0, 1073741823)
- %blt(t0, a0, &.full_check)
- %blt(t0, a1, &.full_check)
- %li(t1, -1073741823)
- %blt(a0, t1, &.full_check)
- %blt(a1, t1, &.full_check)
- %mul(a0, a0, a1)
- %ret
-
- # max = 2^60-1; min = -2^60 on the P1-64 targets.
- :.full_check
+ # max/min derive from the target word's three tag bits.
%li(t0, -1)
%shri(t0, t0, 4) ; max
%addi(t1, t0, 1)
@@ -4948,7 +4939,7 @@
%die(msg_arity)
.endscope
-# Multiply uses the checked raw helper because a 61-bit by 61-bit product
+# Multiply uses the checked raw helper because a full-width product
# can overflow the host word before a post-hoc tag check could observe it.
%fn2(prim_mult_entry, {args acc}, {
%stl(a0, args)
@@ -5160,7 +5151,7 @@
%untag_fix(t1, t1)
%bltz(t1, &.right)
%beqz(t0, &.zero)
- %li(t2, 61)
+ %li(t2, (- %p1_word_bits 3))
%bltu(t1, t2, &.left_do)
%b(&.overflow)
:.left_do
@@ -5172,7 +5163,7 @@
:.right
%li(t2, 0)
%sub(t1, t2, t1)
- %li(t2, 64)
+ %li(t2, %p1_word_bits)
%bltu(t1, t2, &.right_do)
%bltz(t0, &.minus_one)
:.zero
@@ -7905,10 +7896,14 @@
%mov(a1, a0)
%li(a0, %p1_sys_waitid)
%syscall
+ # Canonicalize RV32's 4-byte-aligned siginfo child-status field to the
+ # offset used by the shared Scheme prelude (24 on the 64-bit ABIs).
+ %lb(t0, a3, %p1_waitid_status_off)
+ %sb(t0, a3, 24)
%ret
# build_execve_argv(list=a0) -> raw NULL-terminated array (a0).
-# Walks `list` (cons-list of bytevectors), allocates (count+1)*8 bytes,
+# Walks `list` (cons-list of bytevectors), allocates a native pointer array,
# writes each bv's data_ptr, terminates with NULL.
#
# Locals:
@@ -7921,7 +7916,7 @@
%stl(a0, count)
%addi(a0, a0, 1)
- %shli(a0, a0, 3)
+ %shli(a0, a0, %p1_word_shift)
%call(&alloc_bytes)
%stl(a0, array)
@@ -7933,7 +7928,7 @@
%car(a3, t0)
%heap_ld(a2, a3, %BV.data)
%st(a2, t1, 0)
- %addi(t1, t1, 8)
+ %addi(t1, t1, %p1_word_bytes)
%cdr(t0, t0)
%b(&.fill_loop)
@@ -8056,7 +8051,7 @@
# via the head/tail trick.
#
# Locals:
-# argv ptr (advancing 8 bytes per iteration)
+# argv ptr (advancing one native pointer per iteration)
# count remaining (decrementing from saved_argc)
# head
# tail
@@ -8112,7 +8107,7 @@
:.advance
%ldl(t0, argv)
- %addi(t0, t0, 8)
+ %addi(t0, t0, %p1_word_bytes)
%stl(t0, argv)
%ldl(t0, count)
%addi(t0, t0, -1)
@@ -8143,6 +8138,19 @@
%mkfix(a0, a0)
%ret
+# (target-word-bytes) / (target-word-bits) expose the active P1 data model
+# to portable Scheme-hosted tools such as cc.scm. Arguments are ignored,
+# matching the other zero-argument introspection primitives.
+:prim_target_word_bytes_entry
+ %li(a0, %p1_word_bytes)
+ %mkfix(a0, a0)
+ %ret
+
+:prim_target_word_bits_entry
+ %li(a0, %p1_word_bits)
+ %mkfix(a0, a0)
+ %ret
+
# (collect-garbage) -> unspecified. The primitive's argument list is
# intentionally ignored, so it does not retain otherwise unreachable data.
%fn(prim_collect_garbage_entry, 0, {
@@ -8441,6 +8449,8 @@
:name_error %cstr8("error")
:name_format %cstr8("format")
:name_heap_usage %cstr8("heap-usage")
+:name_target_word_bytes %cstr8("target-word-bytes")
+:name_target_word_bits %cstr8("target-word-bits")
:name_collect_garbage %cstr8("collect-garbage")
:name_recordq %cstr8("record?")
:name_record_td %cstr8("record-td")
@@ -8551,6 +8561,8 @@
&name_error %(0) $(5) &prim_error_entry %(0)
&name_format %(0) $(6) &prim_format_entry %(0)
&name_heap_usage %(0) $(10) &prim_heap_usage_entry %(0)
+&name_target_word_bytes %(0) $(17) &prim_target_word_bytes_entry %(0)
+&name_target_word_bits %(0) $(16) &prim_target_word_bits_entry %(0)
&name_collect_garbage %(0) $(15) &prim_collect_garbage_entry %(0)
&name_recordq %(0) $(7) &prim_recordq_entry %(0)
&name_record_td %(0) $(9) &prim_record_td_entry %(0)
diff --git a/tests/M1pp/003-builtins.expected.riscv32 b/tests/M1pp/003-builtins.expected.riscv32
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+7F
+FF
+3412
+FFFF
+78563412
+FFFFFFFF
+8877665500000000
+0000008000000000
+END
diff --git a/tests/M1pp/004-expr-ops.expected.riscv32 b/tests/M1pp/004-expr-ops.expected.riscv32
@@ -0,0 +1,43 @@
+
+
+
+0F00000000000000
+1800000000000000
+0700000000000000
+7000000000000000
+0000000000000000
+
+
+F9FFFFFF00000000
+2800000000000000
+
+
+8E00000000000000
+0600000000000000
+
+
+0000010000000000
+000000F800000000
+
+
+FFFFFFFF00000000
+
+
+0100000000000000
+0000000000000000
+0100000000000000
+0100000000000000
+0100000000000000
+0100000000000000
+0000000000000000
+
+
+0D00000000000000
+
+
+
+05000000
+06000000
+01
+00000000
+END
diff --git a/tests/M1pp/005-int-atoms.expected.riscv32 b/tests/M1pp/005-int-atoms.expected.riscv32
@@ -0,0 +1,13 @@
+
+
+
+
+0000000000000000
+2A00000000000000
+F9FFFFFF00000000
+40420F0000000000
+FF00000000000000
+FF00000000000000
+EFBEADDE00000000
+0000000000000000
+END
diff --git a/tests/Makefile b/tests/Makefile
@@ -47,30 +47,36 @@ boot5 = build/$(1)/$(TEST_DRIVER)/boot5
IMAGE_STAMP := build/$(ARCH)/.image
IMAGE_STAMPS := $(foreach a,$(ALL_ARCHES),build/$(a)/.image)
+PODMAN_PLATFORM_ARGS_aarch64 := --platform linux/arm64
+PODMAN_PLATFORM_ARGS_amd64 := --platform linux/amd64
+PODMAN_PLATFORM_ARGS_riscv64 := --platform linux/riscv64
+PODMAN_NATIVE_ARCH = $(shell podman info --format '{{.Host.Arch}}' | sed -e 's/^aarch64$$/arm64/' -e 's/^x86_64$$/amd64/')
+PODMAN_PLATFORM_ARGS_riscv32 = --platform linux/$(PODMAN_NATIVE_ARCH)
+
.PHONY: image
image: $(IMAGE_STAMP)
$(IMAGE_STAMPS): build/%/.image: tests/containers/Containerfile.busybox-test
mkdir -p $(@D)
- podman build --platform $(PLATFORM_$*) -t boot2-busybox-test:$* \
+ podman build $(PODMAN_PLATFORM_ARGS_$*) -t boot2-busybox-test:$* \
-f tests/containers/Containerfile.busybox-test tests/containers/
@touch $@
-ALPINE_GCC_IMAGES := $(foreach a,$(ALL_ARCHES),build/$(a)/.image-alpine-gcc)
+ALPINE_GCC_IMAGES := $(foreach a,$(TCC_ARCHES),build/$(a)/.image-alpine-gcc)
$(ALPINE_GCC_IMAGES): build/%/.image-alpine-gcc: tests/containers/Containerfile.alpine-gcc
mkdir -p $(@D)
- podman build --platform $(PLATFORM_$*) \
+ podman build $(PODMAN_PLATFORM_ARGS_$*) \
-t boot2-alpine-gcc:$* \
-f tests/containers/Containerfile.alpine-gcc tests/containers/
@touch $@
-PODMAN = podman run --rm --pull=never --platform $(PLATFORM_$(1)) \
+PODMAN = podman run --rm --pull=never $(PODMAN_PLATFORM_ARGS_$(1)) \
--tmpfs /tmp:size=512M \
-e ARCH=$(1) \
-v $(CURDIR):/work -w /work boot2-busybox-test:$(1)
-ALPINE_GCC = podman run --rm --pull=never --platform $(PLATFORM_$(1)) \
+ALPINE_GCC = podman run --rm --pull=never $(PODMAN_PLATFORM_ARGS_$(1)) \
--tmpfs /tmp:size=128M \
-e ARCH=$(1) \
-v $(CURDIR):/work -w /work boot2-alpine-gcc:$(1)
@@ -190,6 +196,11 @@ else
TEST_ARCHES := $(ARCH)
endif
+# Suites that consume boot3+ artifacts stay on implemented TCC targets.
+# The Scheme-hosted compiler itself is architecture-neutral and is tested on
+# every P1 backend, including RV32.
+TEST_TCC_ARCHES := $(filter $(TCC_ARCHES),$(TEST_ARCHES))
+
# Per-suite build dependency sets. Every entry resolves to a bootN
# artifact (or, for `start.o` / `sys_stubs.o` / `va_list.o`, a small
# per-arch .S stub built next to bootN binaries).
@@ -217,7 +228,7 @@ TEST_CC_UNIT_DEPS := $(foreach a,$(TEST_ARCHES), \
TEST_CC_DEPS := $(TEST_CC_UNIT_DEPS) build/cc.scm
TEST_CC_LIBC_DEPS := $(TEST_CC_DEPS) \
- $(foreach a,$(TEST_ARCHES),$(call boot3,$(a))/libc.P1pp) \
+ $(foreach a,$(TEST_TCC_ARCHES),$(call boot3,$(a))/libc.P1pp) \
P1/entry-libc.P1pp P1/elf-end.P1pp
TEST_TCC_CC_DEPS := build/$(ARCH)/.image \
@@ -245,7 +256,9 @@ ifeq ($(SUITE),)
@$(MAKE) --no-print-directory test SUITE=cc-pp
@$(MAKE) --no-print-directory test SUITE=cc-cg
@$(MAKE) --no-print-directory test SUITE=cc
+ifneq ($(strip $(TEST_TCC_ARCHES)),)
@$(MAKE) --no-print-directory test SUITE=cc-libc
+endif
else ifeq ($(SUITE),m1pp)
@$(MAKE) --no-print-directory $(TEST_M1PP_DEPS)
sh tests/run.sh --suite=m1pp $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES)
@@ -262,9 +275,15 @@ else ifeq ($(SUITE),cc)
@$(MAKE) --no-print-directory $(TEST_CC_DEPS)
sh tests/run.sh --suite=cc $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES)
else ifeq ($(SUITE),cc-libc)
+ @if [ -z "$(TEST_TCC_ARCHES)" ]; then \
+ echo "cc-libc is unavailable for ARCH=$(ARCH): it consumes the intentionally unimplemented RV32 TCC/libc stage" >&2; exit 2; \
+ fi
@$(MAKE) --no-print-directory $(TEST_CC_LIBC_DEPS)
sh tests/run.sh --suite=cc-libc $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES)
else ifeq ($(SUITE),cc-ext)
+ @if [ -z "$(TEST_TCC_ARCHES)" ]; then \
+ echo "cc-ext is unavailable for ARCH=$(ARCH): some fixtures consume the intentionally unimplemented RV32 TCC/libc stage" >&2; exit 2; \
+ fi
@$(MAKE) --no-print-directory $(TEST_CC_LIBC_DEPS)
sh tests/run.sh --suite=cc-ext $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES)
else ifeq ($(SUITE),tcc-cc)
diff --git a/tests/P1/014-sys_calls.P1pp b/tests/P1/014-sys_calls.P1pp
@@ -26,7 +26,7 @@
# --- create + write "X" ------------------------------------------------
%la(a0, &fname)
%li(a1, 577) # O_WRONLY|O_CREAT|O_TRUNC
- %li(a2, 0644)
+ %li(a2, 420) # 0644; M1pp integer literals are decimal/hex
%call(&sys_open)
%bltz(a0, &.fail)
%mov(s0, a0) # save fd
diff --git a/tests/README.md b/tests/README.md
@@ -37,6 +37,7 @@ for the `cc-ext` suite.
make test # all suites, default ARCH
make test SUITE=cc # one suite
make test SUITE=cc NAMES='001 042' # filter by fixture-name prefix
+make test ARCH=riscv32 # RV32 core through cc (no TCC suites)
make test SUITE=cc-libc ARCH=amd64 # one arch
make test SUITE=tcc-cc ARCH=amd64 STAGE=2 # tcc-built test runners
```
@@ -44,6 +45,13 @@ make test SUITE=tcc-cc ARCH=amd64 STAGE=2 # tcc-built test runners
`make image` builds the per-arch `boot2-busybox-test` container used by all
podman-driven suites.
+RV32 has no OCI platform name, so its test image is a native controller
+image; RV32 ELFs execute through the Podman VM's `qemu-riscv32` binfmt
+handler. Its supported matrix is `m1pp`, `p1`, `scheme1`, `cc-util`,
+`cc-lex`, `cc-pp`, `cc-cg`, and `cc`. Suites needing boot3 artifacts
+(`cc-libc`, `cc-ext`, and `tcc-*`) fail early with the intentional
+TCC-unimplemented diagnostic.
+
## Per-suite contract
Every suite picks fixtures by `<name>.<input-ext>` under its directory
@@ -51,6 +59,12 @@ and compares against `<name>.expected` (for the suite's primary output)
and/or `<name>.expected-exit` (for the runtime exit code). Missing
expected files default to empty stdout / exit 0.
+Any fixture input or golden may have an architecture-specific override:
+`<name>.scm.$ARCH`, `<name>.c.$ARCH`, `<name>.expected.$ARCH`, or
+`<name>.expected-exit.$ARCH`. The runner prefers that file when present and
+otherwise uses the unsuffixed fixture. This keeps target-width semantics
+explicit without duplicating entire suites.
+
Names start with a 3-digit prefix (`NNN-`) so listings stay stable as
suites grow.
diff --git a/tests/boot-run-scheme1.sh b/tests/boot-run-scheme1.sh
@@ -9,7 +9,7 @@
## catm /tmp/combined.scm scheme1/prelude.scm <user_src>
## scheme1 /tmp/combined.scm
##
-## Env: ARCH=aarch64|amd64|riscv64
+## Env: ARCH=aarch64|amd64|riscv64|riscv32
## Usage: boot-run-scheme1.sh <user_src>
set -eu
diff --git a/tests/build-cc.sh b/tests/build-cc.sh
@@ -6,7 +6,7 @@
## cc.scm source. Mirrors tests/build-p1pp.sh's contract: env-driven,
## one thing only, no host work.
##
-## Env: ARCH=aarch64|amd64|riscv64
+## Env: ARCH=aarch64|amd64|riscv64|riscv32
## CC_DEBUG=1 (optional) — pass --cc-debug to cc.scm so it prints
## per-phase heap usage on stderr.
## CC_TRACE_EMIT=1 (optional) — pass --cc-trace-emit so cc.scm
diff --git a/tests/build-p1.sh b/tests/build-p1.sh
@@ -21,7 +21,7 @@
## that path so tooling (tools/disasm-elf.sh) can find the artifacts
## from the binary alone.
##
-## Env: ARCH=aarch64|amd64|riscv64
+## Env: ARCH=aarch64|amd64|riscv64|riscv32
## Usage: tests/build-p1.sh <src> <out>
set -eu
diff --git a/tests/build-p1pp.sh b/tests/build-p1pp.sh
@@ -44,7 +44,7 @@
## records the resolved work dir so tooling (tools/disasm-elf.sh) can
## locate the artifacts from the binary alone.
##
-## Env: ARCH=aarch64|amd64|riscv64
+## Env: ARCH=aarch64|amd64|riscv64|riscv32
## WORK_SUBPATH=<repo-relative-path-without-ext> — overrides the
## work-dir name; required when the first src isn't
## the logical primary source.
diff --git a/tests/cc-cg/003-two-params.scm b/tests/cc-cg/003-two-params.scm
@@ -3,7 +3,7 @@
;; Both params spill, but only argc is loaded for return.
(let* ((cg (cg-init))
- (cpp (%ctype 'ptr 8 8 (%ctype 'ptr 8 8 %t-i8)))
+ (cpp (%mk-ptr (%mk-ptr %t-i8)))
(params (cg-fn-begin cg "main"
(list (cons "argc" %t-i32)
(cons "argv" cpp))
diff --git a/tests/cc-cg/010-string.scm b/tests/cc-cg/010-string.scm
@@ -7,11 +7,11 @@
;; share an address, distinct literals don't.
(let* ((cg (cg-init))
- (cp-ty (%ctype 'ptr 8 8 %t-i8)))
+ (cp-ty (%mk-ptr %t-i8)))
(cg-fn-begin cg "main" '() %t-i32)
- (let* ((p-off (cg-alloc-slot cg 8 8))
- (q-off (cg-alloc-slot cg 8 8))
- (r-off (cg-alloc-slot cg 8 8))
+ (let* ((p-off (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES))
+ (q-off (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES))
+ (r-off (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES))
(p (%sym "p" 'var 'auto cp-ty p-off #t))
(q (%sym "q" 'var 'auto cp-ty q-off #t))
(r (%sym "r" 'var 'auto cp-ty r-off #t)))
diff --git a/tests/cc-cg/017-int-roundtrip.scm b/tests/cc-cg/017-int-roundtrip.scm
@@ -19,16 +19,19 @@
(sym-b (%sym "b" 'var 'auto %t-u32 off-b #t)))
;; a = 0xAABBCCDD
(cg-push-sym cg sym-a)
- (cg-push-imm cg %t-u32 2864434397)
+ (cg-push-imm cg %t-u32
+ (cdr (%c-value-parse-decimal-bv "2864434397")))
(cg-assign cg) (cg-pop cg)
;; b = 0x11223344
(cg-push-sym cg sym-b)
- (cg-push-imm cg %t-u32 287454020)
+ (cg-push-imm cg %t-u32
+ (cdr (%c-value-parse-decimal-bv "287454020")))
(cg-assign cg) (cg-pop cg)
;; return a == 0xAABBCCDD
(cg-push-sym cg sym-a)
(cg-load cg)
- (cg-push-imm cg %t-u32 2864434397)
+ (cg-push-imm cg %t-u32
+ (cdr (%c-value-parse-decimal-bv "2864434397")))
(cg-binop cg 'eq)
(cg-return cg))
(cg-fn-end cg)
diff --git a/tests/cc-cg/024-cmpd-ptr.scm b/tests/cc-cg/024-cmpd-ptr.scm
@@ -6,8 +6,8 @@
(cg-fn-begin cg "main" '() %t-i32)
(let* ((off-x (cg-alloc-slot cg 4 4))
(sym-x (%sym "x" 'var 'auto %t-i32 off-x #t))
- (off-p (cg-alloc-slot cg 8 8))
- (ptr-i32 (%ctype 'ptr 8 8 %t-i32))
+ (off-p (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES))
+ (ptr-i32 (%mk-ptr %t-i32))
(sym-p (%sym "p" 'var 'auto ptr-i32 off-p #t)))
;; x = 7
(cg-push-sym cg sym-x)
diff --git a/tests/cc-cg/025-deref-postinc.scm b/tests/cc-cg/025-deref-postinc.scm
@@ -17,8 +17,8 @@
(let* ((arr-i32 (%ctype 'arr 12 4 (cons %t-i32 3)))
(off-a (cg-alloc-slot cg 12 4))
(sym-a (%sym "a" 'var 'auto arr-i32 off-a #t))
- (off-p (cg-alloc-slot cg 8 8))
- (ptr-i32 (%ctype 'ptr 8 8 %t-i32))
+ (off-p (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES))
+ (ptr-i32 (%mk-ptr %t-i32))
(sym-p (%sym "p" 'var 'auto ptr-i32 off-p #t))
(off-s (cg-alloc-slot cg 4 4))
(sym-s (%sym "s" 'var 'auto %t-i32 off-s #t)))
diff --git a/tests/cc-cg/038-arrow.scm b/tests/cc-cg/038-arrow.scm
@@ -17,10 +17,10 @@
(list "S" #t
(list (list "a" %t-i32 0)
(list "b" %t-i32 4)))))
- (pt-ty (%ctype 'ptr 8 8 st-ty)))
+ (pt-ty (%mk-ptr st-ty)))
(cg-fn-begin cg "main" '() %t-i32)
(let* ((off-s (cg-alloc-slot cg 8 4))
- (off-p (cg-alloc-slot cg 8 8))
+ (off-p (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES))
(sym-s (%sym "s" 'var 'auto st-ty off-s #t))
(sym-p (%sym "p" 'var 'auto pt-ty off-p #t)))
;; p = &s
diff --git a/tests/cc-cg/050-init-addr.scm b/tests/cc-cg/050-init-addr.scm
@@ -13,7 +13,7 @@
(bvx (make-bytevector 4 0))
(_x (bytevector-u8-set! bvx 0 7))
;; int *p = &x -> structured init: a single label-ref to cc__x
- (p (%sym "p" 'var 'static (%ctype 'ptr 8 8 %t-i32) #f #t)))
+ (p (%sym "p" 'var 'static (%mk-ptr %t-i32) #f #t)))
(cg-emit-global cg x (list bvx))
(cg-emit-global cg p (list (cons 'label-ref "cc__x")))
(cg-fn-begin cg "main" '() %t-i32)
diff --git a/tests/cc-cg/051-init-array-list.scm b/tests/cc-cg/051-init-array-list.scm
@@ -18,7 +18,7 @@
(let ((emit-elem
(lambda (idx)
(cg-push-sym cg a) (cg-take-addr cg) ; rval ptr-to-arr
- (cg-cast cg (%ctype 'ptr 8 8 elem)) ; relabel as int*
+ (cg-cast cg (%mk-ptr elem)) ; relabel as int*
(cg-push-imm cg %t-i32 idx)
(cg-binop cg 'add) ; int* (scaling x 4)
(cg-push-deref cg) (cg-load cg)))) ; rval int
diff --git a/tests/cc-cg/052-init-array-str.scm b/tests/cc-cg/052-init-array-str.scm
@@ -17,7 +17,7 @@
;; Read the first byte from cc__s. Just take address, cast to char*,
;; deref.
(cg-push-sym cg s) (cg-take-addr cg)
- (cg-cast cg (%ctype 'ptr 8 8 elem))
+ (cg-cast cg (%mk-ptr elem))
(cg-push-deref cg) (cg-load cg)
(cg-return cg)
(cg-fn-end cg)
diff --git a/tests/cc-cg/053-init-struct-pos.scm b/tests/cc-cg/053-init-struct-pos.scm
@@ -22,7 +22,7 @@
(let ((load-field
(lambda (off)
(cg-push-sym cg s) (cg-take-addr cg)
- (cg-cast cg (%ctype 'ptr 8 8 %t-i32))
+ (cg-cast cg (%mk-ptr %t-i32))
(cg-push-imm cg %t-i32 off)
(cg-binop cg 'add)
(cg-push-deref cg) (cg-load cg))))
diff --git a/tests/cc-cg/054-init-struct-desig.scm b/tests/cc-cg/054-init-struct-desig.scm
@@ -17,7 +17,7 @@
(cg-emit-global cg s (list pad bvB))
(cg-fn-begin cg "main" '() %t-i32)
(cg-push-sym cg s) (cg-take-addr cg)
- (cg-cast cg (%ctype 'ptr 8 8 %t-i32))
+ (cg-cast cg (%mk-ptr %t-i32))
(cg-push-imm cg %t-i32 1)
(cg-binop cg 'add)
(cg-push-deref cg) (cg-load cg)
diff --git a/tests/cc-cg/058-fnptr-tab.scm b/tests/cc-cg/058-fnptr-tab.scm
@@ -7,12 +7,12 @@
;; int (*tab[])(void) = { f1, f2 };
;; int main(void) { return tab[0]() + tab[1]() * 10; } (== 21)
;;
-;; tab[i] is read via &tab + i (in fn-ptr units == 8 bytes), then a
+;; tab[i] is read via &tab + i (in target-sized fn-ptr units), then a
;; %callr indirect call.
(let* ((cg (cg-init))
(fnty (%ctype 'fn -1 -1 (list %t-i32 '() #f)))
- (fnptr (%ctype 'ptr 8 8 fnty)))
+ (fnptr (%mk-ptr fnty)))
;; int f1(void) { return 1; }
(cg-fn-begin cg "f1" '() %t-i32)
(cg-push-imm cg %t-i32 1)
@@ -24,19 +24,19 @@
(cg-return cg)
(cg-fn-end cg)
;; tab[2] = { &f1, &f2 } — two label-ref pieces.
- (let* ((aty (%ctype 'arr 16 8 (cons fnptr 2)))
+ (let* ((aty (%mk-arr fnptr 2))
(tab (%sym "tab" 'var 'static aty #f #t)))
(cg-emit-global cg tab (list (cons 'label-ref "f1")
(cons 'label-ref "f2")))
;; int main(void) { return tab[0]() + tab[1]() * 10; }
(cg-fn-begin cg "main" '() %t-i32)
- (let ((pp (%ctype 'ptr 8 8 fnptr)) ; ptr to fnptr = pointee size 8
+ (let ((pp (%mk-ptr fnptr))
(emit-call
(lambda (idx ppt)
(cg-push-sym cg tab) (cg-take-addr cg)
(cg-cast cg ppt) ; relabel to ptr-to-fnptr
(cg-push-imm cg %t-i32 idx)
- (cg-binop cg 'add) ; ptr arith scales by 8
+ (cg-binop cg 'add) ; scales by sizeof(fnptr)
(cg-push-deref cg) (cg-load cg)
(cg-call cg 0 #t))))
(emit-call 0 pp)
diff --git a/tests/cc-cg/067-fnptr-call.scm b/tests/cc-cg/067-fnptr-call.scm
@@ -23,8 +23,8 @@
(cg-fn-end cg))
;; int main(void) { int (*fp)(int) = triple; return fp(7); }
(let* ((params (cg-fn-begin cg "main" '() %t-i32))
- (fp-ty (%ctype 'ptr 8 8 triple-fnty))
- (fp-sl (cg-alloc-slot cg 8 8))
+ (fp-ty (%mk-ptr triple-fnty))
+ (fp-sl (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES))
(fp-sym (%sym "fp" 'var 'auto fp-ty fp-sl #t)))
;; fp = triple (address of fn into the slot)
(cg-push-sym cg fp-sym)
diff --git a/tests/cc-cg/069-vararg-recv.scm b/tests/cc-cg/069-vararg-recv.scm
@@ -24,8 +24,8 @@
%t-i32
#t))
(n* (cdr (car params)))
- (ap-ty (%ctype 'ptr 8 8 %t-i8)) ; va_list = char* (just a pointer)
- (ap-sl (cg-alloc-slot cg 8 8))
+ (ap-ty (%mk-ptr %t-i8)) ; va_list = char* (just a pointer)
+ (ap-sl (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES))
(ap-sym (%sym "ap" 'var 'auto ap-ty ap-sl #t))
(tot-sl (cg-alloc-slot cg 4 4))
(tot-sym (%sym "total" 'var 'auto %t-i32 tot-sl #t))
diff --git a/tests/cc-cg/071-struct-ret-2word.scm b/tests/cc-cg/071-struct-ret-2word.scm
@@ -1,7 +1,7 @@
;; tests/cc-cg/71-struct-ret-2word.scm — two-word direct struct return.
;;
;; Models:
-;; struct P { long a; long b; }; /* 16 bytes */
+;; struct P { intptr_t a; intptr_t b; }; /* two target words */
;; struct P pair(long a, long b) {
;; struct P p; p.a = a; p.b = b; return p;
;; }
@@ -12,24 +12,24 @@
;;
;; Exercises Stream A1's two-word direct return convention (P1.md
;; §Arguments and return values): word 0 in a0, word 1 in a1; cg-fn-end
-;; loads both at exit; cg-call's receive side allocates a 16-byte slot
+;; loads both at exit; cg-call's receive side allocates a two-word slot
;; and stores back from a0 (word 0) and a1 (word 1).
(let* ((cg (cg-init))
- (pair-ty (%ctype 'struct 16 8
+ (pair-ty (%ctype 'struct %CC-PAIR-BYTES %CC-WORD-BYTES
(list "P" #t
- (list (list "a" %t-i64 0)
- (list "b" %t-i64 8)))))
- (fn-ty (%ctype 'fn 8 8
- (cons pair-ty (cons (list %t-i64 %t-i64) #f))))
+ (list (list "a" %t-word-i 0)
+ (list "b" %t-word-i %CC-WORD-BYTES)))))
+ (fn-ty (%mk-fn pair-ty (list %t-word-i %t-word-i) #f))
(pair-sym (%sym "pair" 'fn 'extern fn-ty #f #t)))
;; struct P pair(long a, long b) { ... return p; }
(let* ((params (cg-fn-begin cg "pair"
- (list (cons "a" %t-i64) (cons "b" %t-i64))
+ (list (cons "a" %t-word-i)
+ (cons "b" %t-word-i))
pair-ty))
(a* (cdr (car params)))
(b* (cdr (cadr params)))
- (p-off (cg-alloc-slot cg 16 8))
+ (p-off (cg-alloc-slot cg %CC-PAIR-BYTES %CC-WORD-BYTES))
(p-sym (%sym "p" 'var 'auto pair-ty p-off #t)))
;; p.a = a
(cg-push-sym cg p-sym) (cg-push-field cg "a")
@@ -46,13 +46,14 @@
;; int main(void) { struct P q = pair(11, 22); return q.a + q.b; }
(cg-fn-begin cg "main" '() %t-i32)
(cg-push-sym cg pair-sym)
- (cg-push-imm cg %t-i64 11)
- (cg-push-imm cg %t-i64 22)
+ (cg-push-imm cg %t-word-i 11)
+ (cg-push-imm cg %t-word-i 22)
(cg-call cg 2 #t)
(let ((q-lval (cg-pop cg)))
(cg-push cg q-lval) (cg-push-field cg "a") (cg-load cg)
(cg-push cg q-lval) (cg-push-field cg "b") (cg-load cg)
- (cg-binop cg 'add))
+ (cg-binop cg 'add)
+ (cg-cast cg %t-i32))
(cg-return cg)
(cg-fn-end cg)
(write-bv-fd 1 (cg-finish cg)))
diff --git a/tests/cc-cg/080-uneg-canonical.scm b/tests/cc-cg/080-uneg-canonical.scm
@@ -16,7 +16,8 @@
(cg-fn-begin cg "main" '() %t-i32)
(cg-push-imm cg %t-u32 1)
(cg-unop cg 'neg)
- (cg-push-imm cg %t-u32 4294967295)
+ (cg-push-imm cg %t-u32
+ (cdr (%c-value-parse-decimal-bv "4294967295")))
(cg-binop cg 'eq)
(cg-return cg)
(cg-fn-end cg)
diff --git a/tests/cc-cg/081-ubnot-canonical.scm b/tests/cc-cg/081-ubnot-canonical.scm
@@ -13,7 +13,8 @@
(cg-fn-begin cg "main" '() %t-i32)
(cg-push-imm cg %t-u32 0)
(cg-unop cg 'bnot)
- (cg-push-imm cg %t-u32 4294967295)
+ (cg-push-imm cg %t-u32
+ (cdr (%c-value-parse-decimal-bv "4294967295")))
(cg-binop cg 'eq)
(cg-return cg)
(cg-fn-end cg)
diff --git a/tests/cc-cg/082-uadd-wrap-canonical.scm b/tests/cc-cg/082-uadd-wrap-canonical.scm
@@ -11,7 +11,8 @@
(let ((cg (cg-init)))
(cg-fn-begin cg "main" '() %t-i32)
- (cg-push-imm cg %t-u32 4294967295)
+ (cg-push-imm cg %t-u32
+ (cdr (%c-value-parse-decimal-bv "4294967295")))
(cg-push-imm cg %t-u32 1)
(cg-binop cg 'add)
(cg-push-imm cg %t-u32 0)
diff --git a/tests/cc-cg/085-i32-u32-eq.scm b/tests/cc-cg/085-i32-u32-eq.scm
@@ -13,7 +13,8 @@
(cg-fn-begin cg "main" '() %t-i32)
(cg-push-imm cg %t-i32 -3)
(cg-promote cg)
- (cg-push-imm cg %t-u32 4294967293)
+ (cg-push-imm cg %t-u32
+ (cdr (%c-value-parse-decimal-bv "4294967293")))
(cg-promote cg)
(cg-arith-conv cg)
(cg-binop cg 'eq)
diff --git a/tests/cc-lex/002-integers.c b/tests/cc-lex/002-integers.c
@@ -1 +1,2 @@
42 0 7 100u 0L 0xff 0XFF 0755 1ULL
+4294967295 0xffffffff 18446744073709551615ULL 0xffffffffffffffffULL
diff --git a/tests/cc-lex/002-integers.expected b/tests/cc-lex/002-integers.expected
@@ -8,4 +8,9 @@
(INT 493 "002-integers.c" 1 26)
(INT 1 "002-integers.c" 1 31)
(NL #f "002-integers.c" 1 35)
-(EOF #f "002-integers.c" 2 1)
+(INT 4294967295 "002-integers.c" 2 1)
+(INT 4294967295 "002-integers.c" 2 12)
+(INT 18446744073709551615 "002-integers.c" 2 23)
+(INT 18446744073709551615 "002-integers.c" 2 47)
+(NL #f "002-integers.c" 2 68)
+(EOF #f "002-integers.c" 3 1)
diff --git a/tests/cc-lex/_run-lex.scm b/tests/cc-lex/_run-lex.scm
@@ -47,7 +47,7 @@
(cond
((eq? kind 'IDENT) (%bv-escape val))
((eq? kind 'STR) (%bv-escape val))
- ((eq? kind 'INT) (format "~d" val))
+ ((eq? kind 'INT) (%c-value-source-bv val))
((eq? kind 'CHAR) (format "~d" val))
((eq? kind 'KW) (format "~a" val))
((eq? kind 'PUNCT) (format "~a" val))
diff --git a/tests/cc-pp/_run-pp.scm b/tests/cc-pp/_run-pp.scm
@@ -42,7 +42,7 @@
(cond
((eq? kind 'IDENT) (%bv-escape val))
((eq? kind 'STR) (%bv-escape val))
- ((eq? kind 'INT) (format "~d" val))
+ ((eq? kind 'INT) (%c-value-source-bv val))
((eq? kind 'CHAR) (format "~d" val))
((eq? kind 'KW) (format "~a" val))
((eq? kind 'PUNCT) (format "~a" val))
diff --git a/tests/cc-util/014-c-value-small.scm b/tests/cc-util/014-c-value-small.scm
@@ -0,0 +1,29 @@
+;; The fixed-width C integer carrier must be safe even on a 32-bit
+;; scheme1 host, where an unsigned C int cannot always be a fixnum.
+(define (cv s) (cdr (%c-value-parse-decimal-bv s)))
+(define (dec=? v s) (bv= (%c-value-source-bv v) s))
+(define u32max (cv "4294967295"))
+(define u64max (cv "18446744073709551615"))
+
+(cond
+ ((not (= (%c-value-trunc 1 4 #t) 1)) (sys-exit 1))
+ ((not (dec=? u32max "4294967295")) (sys-exit 2))
+ ((not (dec=? u64max "18446744073709551615")) (sys-exit 3))
+ ((not (dec=? (%c-value-add u32max 1) "4294967296")) (sys-exit 4))
+ ((not (= (%c-value-trunc (%c-value-add u32max 1) 4 #f) 0))
+ (sys-exit 5))
+ ((not (= (%c-value-add u64max 1) 0)) (sys-exit 6))
+ ((not (dec=? (%c-value-mul (cv "4294967296") 3) "12884901888"))
+ (sys-exit 7))
+ ((not (dec=? (%c-value-shift 1 -63 #f) "9223372036854775808"))
+ (sys-exit 8))
+ ((not (= (%c-value-shift (cv "9223372036854775808") 63 #f) 1))
+ (sys-exit 9))
+ ((not (= (%c-value-shift (cv "9223372036854775808") 63 #t) -1))
+ (sys-exit 10))
+ (else
+ (let ((qr (%c-value-udivmod (cv "123456789012345") (cv "12345"))))
+ (cond
+ ((not (dec=? (car qr) "10000549940")) (sys-exit 11))
+ ((not (= (cdr qr) 3045)) (sys-exit 12))
+ (else (sys-exit 0))))))
diff --git a/tests/cc/027-sizeof-types.c b/tests/cc/027-sizeof-types.c
@@ -1,14 +1,15 @@
// tests/cc-parse/27-sizeof-types.c — sizeof over struct, array, ptr,
-// char, plus the named integer types (§C.2). Sums to a known total.
+// char, plus the named integer types (§C.2). Sums to a known total for
+// the active data model (51 on LP64, 43 on ILP32).
struct S { int a; int b; };
int main(void) {
int sum = 0;
sum += sizeof(char); // 1
sum += sizeof(short); // 2
sum += sizeof(int); // 4
- sum += sizeof(long); // 8
- sum += sizeof(int *); // 8
+ sum += sizeof(long); // LP64: 8, ILP32: 4
+ sum += sizeof(int *); // LP64: 8, ILP32: 4
sum += sizeof(int[5]); // 20
sum += sizeof(struct S); // 8
- return sum; // 1+2+4+8+8+20+8 = 51
+ return sum;
}
diff --git a/tests/cc/027-sizeof-types.expected-exit.riscv32 b/tests/cc/027-sizeof-types.expected-exit.riscv32
@@ -0,0 +1 @@
+43
diff --git a/tests/cc/111-struct-ret-1word.c b/tests/cc/111-struct-ret-1word.c
@@ -1,12 +1,12 @@
/* Struct return — 1 word.
*
- * Whole struct fits in a single 8-byte return slot. The function exit
+ * Whole struct fits in a single target-word return slot. The function exit
* emits `LD a0, [sp + <return-slot>]`; a single-int struct rides
* through that slot intact. */
struct OneInt { int x; }; /* 4 bytes */
struct TwoInt { int x; int y; }; /* 8 bytes */
-struct OneLong { long v; }; /* 8 bytes */
+struct OneLong { long v; }; /* one target word */
struct OneInt ret1(int v) { struct OneInt s; s.x = v; return s; }
struct TwoInt ret2(int a, int b) { struct TwoInt s; s.x = a; s.y = b; return s; }
@@ -19,8 +19,9 @@ int main(int argc, char **argv) {
struct TwoInt b = ret2(7, 9);
if (b.x != 7 || b.y != 9) return 2;
- struct OneLong c = retL(0x1122334455667788L);
- if (c.v != 0x1122334455667788L) return 3;
+ long lv = (long)0x1122334455667788L;
+ struct OneLong c = retL(lv);
+ if (c.v != lv) return 3;
/* Call result used directly without intermediate. */
if (ret1(99).x != 99) return 4;
diff --git a/tests/cc/112-struct-ret-2word.c b/tests/cc/112-struct-ret-2word.c
@@ -1,12 +1,12 @@
-/* Struct return — 2 words (16 bytes).
+/* Struct return — 2 target words.
*
- * The cg return path uses one 8-byte return slot loaded into a0 at
+ * The cg return path uses one target-word return slot loaded into a0 at
* function exit. A 2-word struct does not fit; on
* most ABIs the second word goes through a1 (or a hidden-pointer
* convention is used). This test surfaces whichever path is wired,
* or the absence of one. */
-struct Pair { long a; long b; }; /* 16 bytes */
+struct Pair { long a; long b; }; /* two target words */
struct Pair make_pair(long a, long b) {
struct Pair p;
@@ -16,9 +16,9 @@ struct Pair make_pair(long a, long b) {
}
int main(int argc, char **argv) {
- struct Pair p = make_pair(0x1111111111111111L, 0x2222222222222222L);
- if (p.a != 0x1111111111111111L) return 1;
- if (p.b != 0x2222222222222222L) return 2;
+ struct Pair p = make_pair(0x11111111L, 0x22222222L);
+ if (p.a != 0x11111111L) return 1;
+ if (p.b != 0x22222222L) return 2;
/* Direct field access on the call result. */
if (make_pair(7, 8).a != 7) return 3;
diff --git a/tests/cc/118-const-expr.c b/tests/cc/118-const-expr.c
@@ -38,7 +38,7 @@ int g_arr_paren[(1 + 2) * (3 + 1)]; /* 12 */
/* File-scope scalar initializers — must be constant expressions. */
int g_init_arith = 1 + 2 * 3; /* 7 */
int g_init_shift = (1 << 4) | 1; /* 17 */
-int g_init_sizeof = sizeof(long); /* 8 */
+int g_init_sizeof = sizeof(long); /* target data model */
int g_init_ternary = (5 > 3) ? 42 : 0; /* 42 */
int g_init_enum = B * 10 + C; /* 2*10 + 8 = 28 */
int g_init_cast = (int)(unsigned char)257; /* 257 & 0xff = 1 */
@@ -70,7 +70,7 @@ int test_global_array_bounds(void) {
int test_global_inits(void) {
if (g_init_arith != 7) return 1;
if (g_init_shift != 17) return 2;
- if (g_init_sizeof != 8) return 3;
+ if (g_init_sizeof != sizeof(long)) return 3;
if (g_init_ternary != 42) return 4;
if (g_init_enum != 28) return 5;
if (g_init_cast != 1) return 6;
@@ -97,7 +97,7 @@ int test_static_local_init(void) {
static int s_enum = C * D; /* 8 * 4 = 32 */
static int s_logic = (1 || 0) && (2 < 3); /* 1 */
if (s_arith != 99) return 1;
- if (s_sizeof != 8) return 2;
+ if (s_sizeof != sizeof(int *)) return 2;
if (s_enum != 32) return 3;
if (s_logic != 1) return 4;
return 0;
diff --git a/tests/cc/128-cast-signedness.c b/tests/cc/128-cast-signedness.c
@@ -27,8 +27,9 @@ int main(int argc, char **argv) {
/* Comparison at u32 width: -54 (slot=…FFCA) ≠ 4294967242 only
* if the wider compare reads high bits correctly. */
- /* (unsigned long)(signed char)(-54) = 0xFFFFFFFFFFFFFFCA. */
- if ((unsigned long) s[1] != 0xFFFFFFFFFFFFFFCAul) return 5;
+ /* The result fills the active unsigned-long width: ...FFFFFFCA on
+ * both LP64 and ILP32. */
+ if ((unsigned long) s[1] != (~0UL - 53UL)) return 5;
/* --- unsigned → signed, same size (u8 → i8) ------------------- */
{
diff --git a/tests/cc/131-vararg-mixed.c b/tests/cc/131-vararg-mixed.c
@@ -9,7 +9,7 @@
* function to be variadic — but that path was untested.
*
* This test pins down:
- * - va_arg(ap, long) — full 64-bit value survives (past INT_MAX)
+ * - va_arg(ap, long long) — full 64-bit value survives (past INT_MAX)
* - va_arg(ap, char *) — pointer round-trips intact
* - alternating types in one call
* - va_list passed as a parameter to a non-variadic helper which
@@ -36,17 +36,17 @@ static int streq(char *a, char *b) {
return *a == *b;
}
-/* Each "record" is (long, char *, int). */
-static long check(int n, ...) {
+/* Each "record" is (long long, char *, int). */
+static long long check(int n, ...) {
va_list ap;
- long sum = 0;
+ long long sum = 0;
int i = 0;
- long lv;
+ long long lv;
char *sv;
int iv;
va_start(ap, n);
while (i < n) {
- lv = va_arg(ap, long);
+ lv = va_arg(ap, long long);
sv = va_arg(ap, char *);
iv = va_arg(ap, int);
if (!streq(sv, "ok")) return -1;
@@ -89,21 +89,21 @@ static long psum(char *tag, int n, ...) {
}
int main(int argc, char **argv) {
- /* --- Mixed-type direct va_arg (long / char* / int) ----------- */
+ /* --- Mixed-type direct va_arg (long long / char* / int) ------ */
/* Two records. Longs chosen so the sum exceeds INT_MAX
* (2^31 - 1 = 2147483647); a 32-bit truncation in va_arg would
* corrupt the total. */
- long r = check(2,
- 1000000000L, "ok", 5,
- 2000000000L, "ok", 7);
+ long long r = check(2,
+ 1000000000LL, "ok", 5,
+ 2000000000LL, "ok", 7);
if (r == -1) return 1; /* pointer arg lost or corrupted */
- if (r != 3000000012L) return 2; /* long arg truncated */
+ if (r != 3000000012LL) return 2; /* long-long arg truncated */
/* Single record, distinct pointer to confirm we don't latch the
* literal across calls. */
- long r2 = check(1, 4000000000L, "ok", 1);
+ long long r2 = check(1, 4000000000LL, "ok", 1);
if (r2 == -1) return 3;
- if (r2 != 4000000001L) return 4;
+ if (r2 != 4000000001LL) return 4;
/* --- printf-style two-level va_list forwarding --------------- */
/* psum -> vsum_fwd -> vsum. va_arg only runs in vsum, on an ap
diff --git a/tests/cc/132-tentative-bss-sizing.c b/tests/cc/132-tentative-bss-sizing.c
@@ -15,11 +15,11 @@
char g_char; /* 1 byte */
short g_short; /* 2 bytes */
int g_int; /* 4 bytes */
-long g_long; /* 8 bytes */
-char *g_p; /* 8 bytes */
-char **g_pp; /* 8 bytes — the environ shape */
+long g_long; /* one target word */
+char *g_p; /* one target word */
+char **g_pp; /* one target word — the environ shape */
int g_arr[4]; /* 16 bytes */
-char *g_parr[3]; /* 24 bytes */
+char *g_parr[3]; /* three target words */
int
main (void)
@@ -41,16 +41,18 @@ main (void)
* slot is undersized, the high bytes spill into the next global
* and the read returns a truncated value. */
long *pl = &g_long;
- *pl = 0x1122334455667788L;
- if (g_long != 0x1122334455667788L)
+ long long_sentinel = (long)0x1122334455667788L;
+ *pl = long_sentinel;
+ if (g_long != long_sentinel)
return 9;
- g_pp = (char **) 0xdeadbeef00112233L;
- if ((long) g_pp != (long) 0xdeadbeef00112233L)
+ long ptr_sentinel = (long)0xdeadbeef00112233L;
+ g_pp = (char **) ptr_sentinel;
+ if ((long) g_pp != ptr_sentinel)
return 10;
/* Array element: write the last slot — only valid if the full
- * 16-byte / 24-byte allocation actually exists. */
+ * array allocation actually exists. */
g_arr[3] = 42;
if (g_arr[3] != 42) return 11;
g_parr[2] = (char *) 0x99;
diff --git a/tests/cc/342-i64-two-word.c b/tests/cc/342-i64-two-word.c
@@ -0,0 +1,42 @@
+/* 64-bit integers use P1's two-word convention on RV32. This exercises
+ * both data movement and the operations whose high limb cannot be ignored. */
+typedef unsigned long long u64;
+typedef signed long long i64;
+
+static u64 g = 0x1122334455667788ULL;
+
+static u64 id(u64 x) { return x; }
+static u64 add2(u64 a, u64 b) { return a + b; }
+static u64 stack_pair(int a, int b, int c, u64 x)
+{
+ return x + (u64)(a + b + c);
+}
+
+int main(void)
+{
+ u64 x = g;
+
+ if (x != 0x1122334455667788ULL) return 1;
+ if ((x >> 32) != 0x11223344ULL) return 2;
+ if ((x << 16) != 0x3344556677880000ULL) return 3;
+ if (id(x) != x) return 4;
+ if (add2(0x123456789abcdef0ULL, 0xfedcba9876543210ULL)
+ != 0x1111111111111100ULL) return 5;
+ if (stack_pair(1, 2, 3, 0x100000000ULL) != 0x100000006ULL)
+ return 6;
+
+ if ((0x100000000ULL - 1ULL) != 0xffffffffULL) return 7;
+ if ((0x12345678ULL * 0x100000003ULL)
+ != 0x12345678369d0368ULL) return 8;
+ if ((0x123456789abcdef0ULL / 0x12345ULL) != 0x100005b00205ULL)
+ return 9;
+ if ((0x123456789abcdef0ULL % 0x12345ULL) != 0xa497ULL)
+ return 10;
+
+ if ((i64)-0x100000001LL != -4294967297LL) return 11;
+ if ((i64)-0x100000001LL >= -1LL) return 12;
+ if ((i64)-0x123456789LL / 0x12345LL != -0x10000LL) return 13;
+ if ((i64)-0x123456789LL % 0x12345LL != -0x6789LL) return 14;
+
+ return 0;
+}
diff --git a/tests/cc/343-i64-vararg-switch.c b/tests/cc/343-i64-vararg-switch.c
@@ -0,0 +1,46 @@
+/* Pair-valued variadic arguments and switch controls must retain their high
+ * word on RV32. The two case labels deliberately share the same low word. */
+typedef unsigned long long u64;
+typedef signed long long i64;
+
+#ifndef CCSCM
+#include <stdarg.h>
+#else
+typedef char *va_list;
+#define va_start(ap, n) __builtin_va_start(ap, n)
+#define va_arg(ap, t) __builtin_va_arg(ap, t)
+#define va_end(ap) __builtin_va_end(ap)
+#endif
+
+static u64 read_pairs(int marker, ...)
+{
+ va_list ap;
+ u64 a;
+ int middle;
+ i64 b;
+ va_start(ap, marker);
+ a = va_arg(ap, u64);
+ middle = va_arg(ap, int);
+ b = va_arg(ap, i64);
+ va_end(ap);
+ return a + (u64)middle + (u64)b + (u64)marker;
+}
+
+static int classify(u64 x)
+{
+ switch (x) {
+ case 2ULL: return 1;
+ case 0x100000002ULL: return 2;
+ default: return 3;
+ }
+}
+
+int main(void)
+{
+ if (read_pairs(7, 0x100000000ULL, 5, (i64)-3LL)
+ != 0x100000009ULL) return 1;
+ if (classify(2ULL) != 1) return 2;
+ if (classify(0x100000002ULL) != 2) return 3;
+ if (classify(0x200000002ULL) != 3) return 4;
+ return 0;
+}
diff --git a/tests/cc/344-integer-literal-types.c b/tests/cc/344-integer-literal-types.c
@@ -0,0 +1,25 @@
+/* Integer suffixes must select types from the active ILP32/LP64 candidate
+ * lists. In particular, a small LL/ULL constant is still 64-bit on RV32. */
+int main(void)
+{
+ if (sizeof(1) != sizeof(int)) return 1;
+ if (sizeof(1U) != sizeof(unsigned int)) return 2;
+ if (sizeof(1L) != sizeof(long)) return 3;
+ if (sizeof(1UL) != sizeof(unsigned long)) return 4;
+ if (sizeof(1LL) != sizeof(long long)) return 5;
+ if (sizeof(1ULL) != sizeof(unsigned long long)) return 6;
+
+ /* Unsuffixed decimal cannot select unsigned int, while a hex constant
+ * tests int, unsigned int, long, ... in that order. */
+ if (sizeof(2147483648) != 8) return 7;
+ if (sizeof(0xffffffff) != sizeof(unsigned int)) return 8;
+ if (0xffffffff < 0) return 9;
+
+ /* ILP32 converts this pair to unsigned long; LP64 keeps signed long. */
+ if (sizeof(long) == 4) {
+ if (-1L < 1U) return 10;
+ } else {
+ if (!(-1L < 1U)) return 11;
+ }
+ return 0;
+}
diff --git a/tests/cc/345-pointer-ternary.c b/tests/cc/345-pointer-ternary.c
@@ -0,0 +1,21 @@
+/* A null-pointer conditional must retain the pointer arm's full native
+ * width even when the integer arm appears first. On LP64, truncating the
+ * merge to the integer arm's 32-bit type corrupts ordinary stack pointers.
+ */
+
+static char *pick(int use_null, char *p)
+{
+ return use_null ? 0 : p;
+}
+
+int main(int argc, char **argv)
+{
+ char *p = argv[0];
+ char *q = pick(0, p);
+
+ if (q != p) return 1;
+ if (*q != *p) return 2;
+ if (pick(1, p) != 0) return 3;
+ if (sizeof(1 ? 0 : p) != sizeof(p)) return 4;
+ return argc < 1;
+}
diff --git a/tests/run-suite.sh b/tests/run-suite.sh
@@ -13,7 +13,7 @@
## except for m1pp: tools/lint.sh runs python on the host, so the
## host preflights lint and passes the explicit kept list down.
##
-## Env: ARCH=aarch64|amd64|riscv64
+## Env: ARCH=aarch64|amd64|riscv64|riscv32
## Usage: tests/run-suite.sh --suite=<m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc|cc-libc|cc-ext|tcc-cc|tcc-libc> [name ...]
set -eu
@@ -58,6 +58,7 @@ run_m1pp_suite() {
fi
for name in $NAMES; do
expected=tests/M1pp/$name.expected
+ [ ! -e "$expected.$ARCH" ] || expected=$expected.$ARCH
m1pp_src=tests/M1pp/$name.M1pp
if [ ! -e "$m1pp_src" ]; then
@@ -131,8 +132,11 @@ run_scheme1_suite() {
fi
for name in $NAMES; do
fixture=tests/scheme1/$name.scm
+ [ ! -e "$fixture.$ARCH" ] || fixture=$fixture.$ARCH
expected_stdout_file=tests/scheme1/$name.expected
+ [ ! -e "$expected_stdout_file.$ARCH" ] || expected_stdout_file=$expected_stdout_file.$ARCH
expected_exit_file=tests/scheme1/$name.expected-exit
+ [ ! -e "$expected_exit_file.$ARCH" ] || expected_exit_file=$expected_exit_file.$ARCH
if [ ! -e "$fixture" ]; then echo " SKIP $name (no .scm)"; continue; fi
if [ -e "$expected_stdout_file" ]; then
@@ -186,14 +190,19 @@ _cc_unit_suite() {
[ -n "$NAMES" ] || NAMES=$(discover tests/$suite scm)
for name in $NAMES; do
fixture=tests/$suite/$name.scm
+ [ ! -e "$fixture.$ARCH" ] || fixture=$fixture.$ARCH
[ -e "$fixture" ] || { echo " SKIP $name (no .scm)"; continue; }
- if [ -e "tests/$suite/$name.$ext" ]; then
- expout=$(cat "tests/$suite/$name.$ext")
+ expected_file=tests/$suite/$name.$ext
+ [ ! -e "$expected_file.$ARCH" ] || expected_file=$expected_file.$ARCH
+ if [ -e "$expected_file" ]; then
+ expout=$(cat "$expected_file")
else
expout=
fi
- if [ -e "tests/$suite/$name.expected-exit" ]; then
- expexit=$(cat "tests/$suite/$name.expected-exit")
+ expected_exit_file=tests/$suite/$name.expected-exit
+ [ ! -e "$expected_exit_file.$ARCH" ] || expected_exit_file=$expected_exit_file.$ARCH
+ if [ -e "$expected_exit_file" ]; then
+ expexit=$(cat "$expected_exit_file")
else
expexit=0
fi
@@ -223,14 +232,19 @@ _cc_pipeline_suite() {
[ -n "$NAMES" ] || NAMES=$(discover tests/$suite c)
for name in $NAMES; do
fixture=tests/$suite/$name.c
+ [ ! -e "$fixture.$ARCH" ] || fixture=$fixture.$ARCH
[ -e "$fixture" ] || { echo " SKIP $name (no .c)"; continue; }
- if [ -e "tests/$suite/$name.$ext" ]; then
- expout=$(grep -v '^;;' "tests/$suite/$name.$ext" || true)
+ expected_file=tests/$suite/$name.$ext
+ [ ! -e "$expected_file.$ARCH" ] || expected_file=$expected_file.$ARCH
+ if [ -e "$expected_file" ]; then
+ expout=$(grep -v '^;;' "$expected_file" || true)
else
expout=
fi
- if [ -e "tests/$suite/$name.expected-exit" ]; then
- expexit=$(cat "tests/$suite/$name.expected-exit")
+ expected_exit_file=tests/$suite/$name.expected-exit
+ [ ! -e "$expected_exit_file.$ARCH" ] || expected_exit_file=$expected_exit_file.$ARCH
+ if [ -e "$expected_exit_file" ]; then
+ expexit=$(cat "$expected_exit_file")
else
expexit=0
fi
@@ -277,15 +291,20 @@ _cc_runtime_suite() {
[ -n "$NAMES" ] || NAMES=$(discover tests/$suite "$fext")
for name in $NAMES; do
fixture=tests/$suite/$name.$fext
+ [ ! -e "$fixture.$ARCH" ] || fixture=$fixture.$ARCH
[ -e "$fixture" ] || { echo " SKIP $name (no .$fext)"; continue; }
- if [ -e tests/$suite/$name.expected ]; then
- expout=$(cat tests/$suite/$name.expected)
+ expected_file=tests/$suite/$name.expected
+ [ ! -e "$expected_file.$ARCH" ] || expected_file=$expected_file.$ARCH
+ if [ -e "$expected_file" ]; then
+ expout=$(cat "$expected_file")
else
expout=
fi
- if [ -e tests/$suite/$name.expected-exit ]; then
- expexit=$(cat tests/$suite/$name.expected-exit)
+ expected_exit_file=tests/$suite/$name.expected-exit
+ [ ! -e "$expected_exit_file.$ARCH" ] || expected_exit_file=$expected_exit_file.$ARCH
+ if [ -e "$expected_exit_file" ]; then
+ expexit=$(cat "$expected_exit_file")
else
expexit=0
fi
@@ -341,14 +360,19 @@ run_cc_suite() {
[ -n "$NAMES" ] || NAMES=$(discover tests/cc c)
for name in $NAMES; do
src=tests/cc/$name.c
+ [ ! -e "$src.$ARCH" ] || src=$src.$ARCH
[ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; }
- if [ -e tests/cc/$name.expected ]; then
- expout=$(cat tests/cc/$name.expected)
+ expected_file=tests/cc/$name.expected
+ [ ! -e "$expected_file.$ARCH" ] || expected_file=$expected_file.$ARCH
+ if [ -e "$expected_file" ]; then
+ expout=$(cat "$expected_file")
else
expout=
fi
- if [ -e tests/cc/$name.expected-exit ]; then
- expexit=$(cat tests/cc/$name.expected-exit)
+ expected_exit_file=tests/cc/$name.expected-exit
+ [ ! -e "$expected_exit_file.$ARCH" ] || expected_exit_file=$expected_exit_file.$ARCH
+ if [ -e "$expected_exit_file" ]; then
+ expexit=$(cat "$expected_exit_file")
else
expexit=0
fi
diff --git a/tests/run.sh b/tests/run.sh
@@ -38,7 +38,8 @@
## libc.o, then compiles + links
## each fixture against it -> run.
##
-## All three arches by default; --arch restricts to one.
+## Core and cc.scm suites run on all four bootstrap arches by default;
+## suites consuming boot3+ TCC/libc artifacts retain three targets.
##
## Usage: tests/run.sh --suite <suite> [--arch ARCH] [name ...]
@@ -78,6 +79,11 @@ platform_of() {
aarch64) echo linux/arm64 ;;
amd64) echo linux/amd64 ;;
riscv64) echo linux/riscv64 ;;
+ riscv32)
+ native=$(podman info --format '{{.Host.Arch}}')
+ case "$native" in aarch64) native=arm64 ;; x86_64) native=amd64 ;; esac
+ echo "linux/$native"
+ ;;
*) echo "$0: unknown arch '$1'" >&2; return 1 ;;
esac
}
@@ -95,11 +101,23 @@ run_in_container() {
}
if [ -z "$ARCH" ]; then
- ARCHES="aarch64 amd64 riscv64"
+ case "$SUITE" in
+ cc-libc|cc-ext|tcc-cc|tcc-libc)
+ ARCHES="aarch64 amd64 riscv64"
+ ;;
+ *) ARCHES="aarch64 amd64 riscv64 riscv32" ;;
+ esac
else
ARCHES=$ARCH
fi
+case "$SUITE:$ARCHES" in
+ cc-libc:riscv32|cc-ext:riscv32|tcc-cc:riscv32|tcc-libc:riscv32)
+ echo "$SUITE is unavailable for riscv32: it consumes the intentionally unimplemented TCC/libc stages" >&2
+ exit 2
+ ;;
+esac
+
PASS=0
FAIL=0
diff --git a/tests/scheme1/116-hex-conv.scm b/tests/scheme1/116-hex-conv.scm
@@ -12,7 +12,7 @@
(if (eq-bv "10" (number->string 16 16)) 0 (sys-exit 5))
(if (eq-bv "ff" (number->string 255 16)) 0 (sys-exit 6))
(if (eq-bv "100" (number->string 256 16)) 0 (sys-exit 7))
-(if (eq-bv "deadbeef" (number->string #xdeadbeef 16)) 0 (sys-exit 8))
+(if (eq-bv "deadbee" (number->string #xdeadbee 16)) 0 (sys-exit 8))
(if (eq-bv "-1" (number->string -1 16)) 0 (sys-exit 9))
(if (eq-bv "-ff" (number->string -255 16)) 0 (sys-exit 10))
@@ -29,7 +29,7 @@
(if (= 16 (string->number "10" 16)) 0 (sys-exit 24))
(if (= 255 (string->number "ff" 16)) 0 (sys-exit 25))
(if (= 255 (string->number "FF" 16)) 0 (sys-exit 26))
-(if (= #xdeadbeef (string->number "deadbeef" 16)) 0 (sys-exit 27))
+(if (= #xdeadbee (string->number "deadbee" 16)) 0 (sys-exit 27))
(if (= -1 (string->number "-1" 16)) 0 (sys-exit 28))
(if (= -255 (string->number "-ff" 16)) 0 (sys-exit 29))
@@ -62,7 +62,7 @@
(if (eq-bv "0" (format (bv-from '(126 120)) 0)) 0 (sys-exit 50))
(if (eq-bv "ff" (format (bv-from '(126 120)) 255)) 0 (sys-exit 51))
(if (eq-bv "-1" (format (bv-from '(126 120)) -1)) 0 (sys-exit 52))
-(if (eq-bv "deadbeef" (format (bv-from '(126 120)) #xdeadbeef)) 0 (sys-exit 53))
+(if (eq-bv "deadbee" (format (bv-from '(126 120)) #xdeadbee)) 0 (sys-exit 53))
; Template "~x ~d ~x" -> mix dec and hex
; "~x ~d ~x" = (126 120 32 126 100 32 126 120)
diff --git a/tests/scheme1/149-target-bindings.scm b/tests/scheme1/149-target-bindings.scm
@@ -3,4 +3,8 @@
(if (procedure? fd-write-string/result) 0 (sys-exit 3))
(if (procedure? peek-memory-u8) 0 (sys-exit 4))
(if (pair? (argv)) 0 (sys-exit 5))
+(if (or (= (target-word-bytes) 4) (= (target-word-bytes) 8))
+ 0 (sys-exit 6))
+(if (= (target-word-bits) (* 8 (target-word-bytes)))
+ 0 (sys-exit 7))
(sys-exit 0)
diff --git a/tests/scheme1/151-exact-boundaries.scm.riscv32 b/tests/scheme1/151-exact-boundaries.scm.riscv32
@@ -0,0 +1,13 @@
+; P1-32's three tag bits leave exact integers in [-2^28, 2^28-1].
+(define max #xfffffff)
+(define min #x-10000000)
+(if (= max 268435455) 0 (sys-exit 1))
+(if (= min -268435456) 0 (sys-exit 2))
+(if (= min (string->number "-268435456")) 0 (sys-exit 3))
+(if (= max (string->number "268435455")) 0 (sys-exit 4))
+(if (string=? "-268435456" (number->string min)) 0 (sys-exit 5))
+(if (string=? "-10000000" (number->string min 16)) 0 (sys-exit 6))
+(if (= 0 (remainder min -1)) 0 (sys-exit 7))
+(if (= min (* #x-4000000 4)) 0 (sys-exit 8))
+(if (= -1 (arithmetic-shift min -1000)) 0 (sys-exit 9))
+(sys-exit 0)
diff --git a/tools/lint.sh b/tools/lint.sh
@@ -13,7 +13,7 @@
## quoted literals (can span lines and carry prose like "usage: lisp")
## and `#`/`;` line comments, so the pass is written in Python.
##
-## Env: ARCH=aarch64|amd64|riscv64
+## Env: ARCH=aarch64|amd64|riscv64|riscv32
## Usage: lint.sh <prog.M1> [<prog.M1> ...]
## Exit: 0 on success; 1 + diagnostic on any missing token; 2 on misuse.
diff --git a/vendor/seed/README.md b/vendor/seed/README.md
@@ -4,7 +4,7 @@ Bootstrap seeds and stage0-posix sources, vendored from
[live-bootstrap](https://github.com/fosslinux/live-bootstrap)
(`seed/stage0-posix/`).
-Per arch (`aarch64`, `amd64`, `riscv64`):
+Per arch (`aarch64`, `amd64`, `riscv64`, `riscv32`):
| file | upstream path |
| ----------- | ------------------------------------------------------- |
@@ -16,11 +16,20 @@ Per arch (`aarch64`, `amd64`, `riscv64`):
| `M0.hex2` | `<A>/M0_<A>.hex2` |
| `ELF.hex2` | `<A>/ELF-<arch>.hex2` |
-`<A>` is the upstream's mixed-case directory: `AArch64`, `AMD64`, `riscv64`.
+`<A>` is the upstream directory: `AArch64`, `AMD64`, `riscv64`, or
+`riscv32`.
`aarch64/catm.hex2` is locally produced — upstream ships AArch64's catm only
as `.hex1`, not `.hex2`, so the chain in `scripts/bootstrap.sh` (which feeds
-`catm.hex2` into `hex2-0`) needs the `.hex2` form. The other two arches use
+`catm.hex2` into `hex2-0`) needs the `.hex2` form. The other three arches use
the upstream `.hex2` directly.
+The RV32 files were copied from live-bootstrap commit
+`9a268c4c39cae952b268bc86da342be2175f03d4` under
+`seed/stage0-posix/{bootstrap-seeds/POSIX/riscv32,riscv32}`. The six seed and
+assembler files are byte-identical to that tree. `riscv32/ELF.hex2` has one
+local, auditable change: its ELF32 `PT_LOAD.p_memsz` is fixed at 512 MiB
+instead of equalling `p_filesz`, because boot2 programs place large static
+BSS arenas after `ELF_end`. The load base and file bytes are unchanged.
+
License: GPL-3.0-or-later, see [`LICENSE`](LICENSE).
diff --git a/vendor/seed/riscv32/ELF.hex2 b/vendor/seed/riscv32/ELF.hex2
@@ -0,0 +1,68 @@
+### Copyright (C) 2016 Jeremiah Orians
+### Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org>
+### 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/>.
+
+### 32 bit elf header in hex2
+### 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
+
+01 ## e_ident[EI_CLASS] Indicating 32 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] Set at 0 because none cares
+
+00 00 00 00 00 00 00 ## e_ident[EI_PAD]
+02 00 ## e_type Indicating Executable
+F3 00 ## e_machine Indicating RISC-V
+01 00 00 00 ## e_version Indicating original elf
+
+&_start ## e_entry Address of the entry point (Number of bytes this header is + Base Address)
+%ELF_program_headers>ELF_base ## e_phoff Address of program header table
+00 00 00 00 ## e_shoff Address of section header table
+
+00 00 00 00 ## e_flags
+34 00 ## e_ehsize Indicating our 52 Byte header
+
+20 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
+
+## Program Header
+:ELF_program_headers
+01 00 00 00 ## ph_type: PT-LOAD = 1
+00 00 00 00 ## p_offset
+
+&ELF_base ## ph_vaddr
+&ELF_base ## ph_physaddr
+
+%ELF_end>ELF_base ## p_filesz
+00 00 00 20 ## p_memsz = 512 MB (boot2: BSS past ELF_end)
+
+07 00 00 00 ## ph_flags: PF-X|PF-W|PF-R = 7
+01 00 00 00 ## ph_align
+
+:ELF_text
diff --git a/vendor/seed/riscv32/M0.hex2 b/vendor/seed/riscv32/M0.hex2
@@ -0,0 +1,1701 @@
+## Copyright (C) 2017 Jeremiah Orians
+## Copyright (C) 2021 Andrius Štikonas
+## Copyright (C) 2021 Gabriel Wicki
+## This file is part of stage0.
+##
+## stage0 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.
+##
+## stage0 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 stage0. If not, see <http://www.gnu.org/licenses/>.
+
+; Where the ELF Header is going to hit
+; Simply jump to _start
+; Our main function
+
+; Register use:
+; s1: malloc pointer
+; s2: input fd
+; s3: output fd
+; s4: struct HEAD
+; s5: protected char
+; s6: scratch
+
+; Struct format: (size 16)
+; NEXT => 0 ; Next element in linked list
+; TYPE => 4 ; Token type
+; TEXT => 8
+; EXPRESSION => 12
+
+; Types
+; None => 0
+; MACRO => 1
+; STRING => 2
+
+:_start
+ # rd_s4 addi
+ .000A0000 13000000
+
+ # rd_a2 rs1_sp !8 lw ; Input file name
+ .00060000 .00000100 .00008000 03200000
+
+ ; Open input file and store FD in s2
+ # rd_a7 !56 addi ; sys_openat
+ .80080000 .00008003 13000000
+ # rd_a0 !-100 addi ; AT_FDCWD
+ .00050000 .0000C0F9 13000000
+ # rd_a1 rs1_a2 mv ; file name
+ .80050000 .00000600 13000000
+ # rd_a2 addi ; read only
+ .00060000 13000000
+ # ecall ; syscall
+ 73000000
+ # rs1_a0 @Fail bltz ; Error opening file
+ .00000500 @Fail 63400000
+ # rd_s2 rs1_a0 mv ; Save fd in for later
+ .00090000 .00000500 13000000
+
+ ; Set default FD for output file to stdout
+ # rd_s3 !1 addi
+ .80090000 .00001000 13000000
+
+ ; If we only have 2 arguments, don't use the third (it's not set)
+ # rd_t0 !2 addi
+ .80020000 .00002000 13000000
+ # rd_a0 rs1_sp lw ; Get number of the args
+ .00050000 .00000100 03200000
+ # rs1_a0 rs2_t0 @Fail blt ; No input file provided
+ .00000500 .00005000 @Fail 63400000
+ # rs1_a0 rs2_t0 @after_open beq ; No output file provided. Use stdout
+ .00000500 .00005000 @after_open 63000000
+
+ ; Open output file and store the FD in s3
+ # rd_a7 !56 addi ; sys_openat
+ .80080000 .00008003 13000000
+ # rd_a0 !-100 addi ; AT_FDCWD
+ .00050000 .0000C0F9 13000000
+ # rd_a1 rs1_sp !12 lw ; Output file (argument 3)
+ .80050000 .00000100 .0000C000 03200000
+ # rd_a2 !577 addi ; octal 00001101
+ .00060000 .00001024 13000000
+ ; O_TRUNC 00001000
+ ; O_CREAT 00000100
+ ; O_WRONLY 00000001
+ ; OCTAL!
+ # rd_a3 !384 addi ; Set read and write permission on user
+ .80060000 .00000018 13000000
+ # ecall ; syscall
+ 73000000
+ # rd_s3 rs1_a0 mv ; Save fd in for later
+ .80090000 .00000500 13000000
+
+:after_open
+ ; Prepare heap memory
+ # rd_a7 !214 addi ; sys_brk
+ .80080000 .0000600D 13000000
+ # rd_a0 addi ; Get current brk
+ .00050000 13000000
+ # ecall ; syscall
+ 73000000
+ # rd_s1 rs1_a0 mv ; Set our malloc pointer
+ .80040000 .00000500 13000000
+
+ # rd_a0 !512 addi ; Allocate scratch
+ .00050000 .00000020 13000000
+ # rd_ra $malloc jal ; Get S pointer
+ .80000000 $malloc 6F000000
+ # rd_s6 rs1_a0 mv ; Save scratch pointer
+ .000B0000 .00000500 13000000
+
+ # rd_ra $Tokenize_Line jal ; Get all lines
+ .80000000 $Tokenize_Line 6F000000
+ # rd_a0 rs1_s4 mv ; Prepare for Reverse_List
+ .00050000 .00000A00 13000000
+ # rd_ra $Reverse_List jal ; Correct order
+ .80000000 $Reverse_List 6F000000
+ # rd_s4 rs1_a0 mv ; Update HEAD
+ .000A0000 .00000500 13000000
+ # rd_ra $Identify_Macros jal ; Find the DEFINEs
+ .80000000 $Identify_Macros 6F000000
+ # rd_ra $Line_Macro jal ; Apply the DEFINEs
+ .80000000 $Line_Macro 6F000000
+ # rd_ra $Process_String jal ; Handle strings
+ .80000000 $Process_String 6F000000
+ # rd_ra $Eval_Immediates jal ; Handle numbers
+ .80000000 $Eval_Immediates 6F000000
+ # rd_ra $Preserve_Other jal ; Collect the remaining
+ .80000000 $Preserve_Other 6F000000
+ # rd_ra $Print_Hex jal ; Output our results
+ .80000000 $Print_Hex 6F000000
+
+ ; Terminate program with 0 return code
+ # rd_a7 !93 addi ; sys_exit
+ .80080000 .0000D005 13000000
+ # rd_a0 mv ; Return code 0
+ .00050000 13000000
+ # ecall ; exit(0)
+ 73000000
+
+
+; Tokenize_Line Function
+; Using input file s2 and Head s4
+; Creates a linked list of structs
+; Uses a1 for in_set strings, a2 for Int C and a3 for Struct Token* p
+:Tokenize_Line
+ # rd_sp rs1_sp !-4 addi ; allocate stack
+ .00010000 .00000100 .0000C0FF 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+
+:restart
+ # rd_ra $fgetc jal ; Read a char
+ .80000000 $fgetc 6F000000
+ # rd_t0 !-4 addi ; EOF
+ .80020000 .0000C0FF 13000000
+ # rs1_a0 rs2_t0 @done beq ; File is collected
+ .00000500 .00005000 @done 63000000
+
+ # rd_a2 rs1_a0 mv ; Protect C
+ .00060000 .00000500 13000000
+
+ # rd_a1 ~comments auipc ; Get pointer to "#;"
+ .80050000 ~comments 17000000
+ # rd_a1 rs1_a1 !comments addi ; Get pointer to "#;"
+ .80050000 .00800500 !comments 13000000
+ # rd_ra $In_Set jal ; Check for comments
+ .80000000 $In_Set 6F000000
+ # rd_t0 !1 addi ; If comment
+ .80020000 .00001000 13000000
+ # rs1_a0 rs2_t0 @Purge_LineComment beq ; try again
+ .00000500 .00005000 @Purge_LineComment 63000000
+
+ # rd_a0 rs1_a2 mv ; Put C in place for check
+ .00050000 .00000600 13000000
+ # rd_a1 ~terminators auipc ; Get pointer to "\n\t "
+ .80050000 ~terminators 17000000
+ # rd_a1 rs1_a1 !terminators addi ; Get pointer to "\n\t "
+ .80050000 .00800500 !terminators 13000000
+ # rd_ra $In_Set jal ; Check for terminators
+ .80000000 $In_Set 6F000000
+ # rd_t0 !1 addi ; If terminator
+ .80020000 .00001000 13000000
+ # rs1_a0 rs2_t0 @restart beq ; try again
+ .00000500 .00005000 @restart 63000000
+
+ # rd_a0 !16 addi ; malloc struct P
+ .00050000 .00000001 13000000
+ # rd_ra $malloc jal ; Get pointer to P
+ .80000000 $malloc 6F000000
+ # rd_a3 rs1_a0 mv ; Protect P
+ .80060000 .00000500 13000000
+ # rs1_a3 rs2_s4 sw ; P->NEXT = HEAD
+ .00800600 .00004001 23200000
+ # rd_s4 rs1_a3 mv ; HEAD = P
+ .000A0000 .00800600 13000000
+
+ # rd_a0 rs1_a2 mv ; Put C in place for check
+ .00050000 .00000600 13000000
+ # rd_a1 ~string_char auipc ; Get pointer to "\"'"
+ .80050000 ~string_char 17000000
+ # rd_a1 rs1_a1 !string_char addi ; Get pointer to "\"'"
+ .80050000 .00800500 !string_char 13000000
+ # rd_ra $In_Set jal ; Check for string char
+ .80000000 $In_Set 6F000000
+ # rd_t0 !1 addi ; If string char
+ .80020000 .00001000 13000000
+ # rs1_a0 rs2_t0 @Store_String beq ; Get string
+ .00000500 .00005000 @Store_String 63000000
+
+ # rd_ra $Store_Atom jal ; Get whole token
+ .80000000 $Store_Atom 6F000000
+ # $restart jal
+ $restart 6F000000
+
+:done
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_sp rs1_sp !4 addi ; deallocate stack
+ .00010000 .00000100 .00004000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; In_Set function
+; Receives char C in a0 and Char* in a1
+; Returns 1 if true, zero if false in a0
+:In_Set
+ # rd_sp rs1_sp !-4 addi ; allocate stack
+ .00010000 .00000100 .0000C0FF 13000000
+ # rs1_sp rs2_a1 sw ; protect a1
+ .00000100 .0000B000 23200000
+
+:In_Set_loop
+ # rd_t0 rs1_a1 lbu ; Read char
+ .80020000 .00800500 03400000
+ # rs1_a0 rs2_t0 @In_Set_True beq ; Return true
+ .00000500 .00005000 @In_Set_True 63000000
+ # rs1_t0 @In_Set_False beqz ; Return False if NULL
+ .00800200 @In_Set_False 63000000
+ # rd_a1 rs1_a1 !1 addi ; s = s + 1
+ .80050000 .00800500 .00001000 13000000
+ # $In_Set_loop jal ; Continue looping
+ $In_Set_loop 6F000000
+
+:In_Set_True
+ # rd_a0 !1 addi ; Set True
+ .00050000 .00001000 13000000
+ # rd_a1 rs1_sp lw ; restore a1
+ .80050000 .00000100 03200000
+ # rd_sp rs1_sp !4 addi ; deallocate stack
+ .00010000 .00000100 .00004000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+:In_Set_False
+ # rd_a0 mv ; Set False
+ .00050000 13000000
+ # rd_a1 rs1_sp lw ; restore a1
+ .80050000 .00000100 03200000
+ # rd_sp rs1_sp !4 addi ; deallocate stack
+ .00010000 .00000100 .00004000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; Purge_LineComment function
+; Reads chars until LF and jumps to restart
+:Purge_LineComment
+ # rd_ra $fgetc jal ; Get a char
+ .80000000 $fgetc 6F000000
+ # rd_t0 !10 addi ; While not LF
+ .80020000 .0000A000 13000000
+ # rs1_a0 rs2_t0 @Purge_LineComment bne ; Keep reading
+ .00000500 .00005000 @Purge_LineComment 63100000
+ # $restart jal
+ $restart 6F000000
+
+
+; Store_String Function
+; Receives C in a2, HEAD in a3 and Input file in s2
+; Uses a1 for terminator, a2 for C and a3 for string
+:Store_String
+ # rd_sp rs1_sp !-12 addi ; allocate stack
+ .00010000 .00000100 .000040FF 13000000
+ # rs1_sp rs2_a1 sw ; protect a1
+ .00000100 .0000B000 23200000
+ # rs1_sp rs2_a2 @4 sw ; protect a2
+ .00000100 .0000C000 .00020000 23200000
+ # rs1_sp rs2_a3 @8 sw ; protect a3
+ .00000100 .0000D000 .00040000 23200000
+
+ # rd_a0 !2 addi ; Using TYPE STRING
+ .00050000 .00002000 13000000
+ # rs1_a3 rs2_a0 @4 sw ; HEAD->TYPE = STRING
+ .00800600 .0000A000 .00020000 23200000
+ # rd_a1 rs1_a2 mv ; Protect terminator
+ .80050000 .00000600 13000000
+ # rd_a3 rs1_s6 mv ; Protect string pointer
+ .80060000 .00000B00 13000000
+:Store_String_Loop
+ # rs1_a3 rs2_a2 sb ; write byte
+ .00800600 .0000C000 23000000
+ # rd_ra $fgetc jal ; read next char
+ .80000000 $fgetc 6F000000
+ # rd_a2 rs1_a0 mv ; Update C
+ .00060000 .00000500 13000000
+ # rd_a3 rs1_a3 !1 addi ; STRING = STRING + 1
+ .80060000 .00800600 .00001000 13000000
+ # rs1_a1 rs2_a2 @Store_String_Loop bne ; Keep looping unless we hit terminator
+ .00800500 .0000C000 @Store_String_Loop 63100000
+
+ # rd_a0 rs1_s6 mv ; Prepare the string in scratch
+ .00050000 .00000B00 13000000
+ # rd_ra $string_length jal ; Calculate length
+ .80000000 $string_length 6F000000
+ # rd_a0 rs1_a0 !1 addi ; Add 1 for 0 terminator
+ .00050000 .00000500 .00001000 13000000
+ # rd_ra $malloc jal ; Allocate memory
+ .80000000 $malloc 6F000000
+ # rd_a3 rs1_sp !8 lw ; restore a3 (HEAD)
+ .80060000 .00000100 .00008000 03200000
+ # rs1_a3 rs2_a0 @8 sw ; HEAD->TEXT = STRING
+ .00800600 .0000A000 .00040000 23200000
+ # rd_ra $copy_string jal ; Copy the string
+ .80000000 $copy_string 6F000000
+
+ # rd_a1 rs1_sp lw ; restore a1
+ .80050000 .00000100 03200000
+ # rd_a2 rs1_sp !4 lw ; restore a2
+ .00060000 .00000100 .00004000 03200000
+ # rd_sp rs1_sp !12 addi ; deallocate stack
+ .00010000 .00000100 .0000C000 13000000
+ # $restart jal
+ $restart 6F000000
+
+; copy_string function
+; Receives target in a0, and scratch s6 for source
+; Uses a0, for target string T, a1 for C, a2 for source string S
+; Returns nothing
+:copy_string
+ # rd_sp rs1_sp !-12 addi ; allocate stack
+ .00010000 .00000100 .000040FF 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a1 @4 sw ; protect a1
+ .00000100 .0000B000 .00020000 23200000
+ # rs1_sp rs2_a2 @8 sw ; protect a2
+ .00000100 .0000C000 .00040000 23200000
+
+ # rd_a2 rs1_s6 mv ; Get S
+ .00060000 .00000B00 13000000
+
+:copy_string_loop
+ # rd_a1 rs1_a2 lbu ; S[0]
+ .80050000 .00000600 03400000
+ # rs1_a1 @copy_string_done beqz ; Check if we are done
+ .00800500 @copy_string_done 63000000
+
+ # rs1_a0 rs2_a1 sb ; Copy char
+ .00000500 .0000B000 23000000
+ # rd_a2 rs1_a2 !1 addi ; S = S + 1
+ .00060000 .00000600 .00001000 13000000
+ # rd_a0 rs1_a0 !1 addi ; T = T + 1
+ .00050000 .00000500 .00001000 13000000
+ # $copy_string_loop jal ; Keep going
+ $copy_string_loop 6F000000
+
+:copy_string_done
+ # rd_ra $ClearScratch jal ; Clear scratch
+ .80000000 $ClearScratch 6F000000
+
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a1 rs1_sp !4 lw ; restore a1
+ .80050000 .00000100 .00004000 03200000
+ # rd_a2 rs1_sp !8 lw ; restore a2
+ .00060000 .00000100 .00008000 03200000
+ # rd_sp rs1_sp !12 addi ; deallocate stack
+ .00010000 .00000100 .0000C000 13000000
+ # ret
+ 67800000
+
+
+; Zero scratch area
+:ClearScratch
+ # rd_sp rs1_sp !-12 addi ; allocate stack
+ .00010000 .00000100 .000040FF 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a0 @4 sw ; protect a0
+ .00000100 .0000A000 .00020000 23200000
+ # rs1_sp rs2_a1 @8 sw ; protect a1
+ .00000100 .0000B000 .00040000 23200000
+
+ # rd_a0 rs1_s6 mv ; Prepare scratch
+ .00050000 .00000B00 13000000
+
+:ClearScratch_loop
+ # rd_a1 rs1_a0 lb ; Read current byte: s[i]
+ .80050000 .00000500 03000000
+ # rs1_a0 sb ; Write zero: s[i] = 0
+ .00000500 23000000
+ # rd_a0 rs1_a0 !1 addi ; Increment: i = i + 1
+ .00050000 .00000500 .00001000 13000000
+ # rs1_a1 @ClearScratch_loop bnez ; Keep looping
+ .00800500 @ClearScratch_loop 63100000
+
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a0 rs1_sp !4 lw ; restore a0
+ .00050000 .00000100 .00004000 03200000
+ # rd_a1 rs1_sp !8 lw ; restore a1
+ .80050000 .00000100 .00008000 03200000
+ # rd_sp rs1_sp !12 addi ; deallocate stack
+ .00010000 .00000100 .0000C000 13000000
+ # ret
+ 67800000
+
+
+; Store_Atom Function
+; Receives C in a2, HEAD in a3 and Input file in s2
+; Uses a1 for in_set strings, a2 for C and a3 for string
+:Store_Atom
+ # rd_sp rs1_sp !-16 addi ; allocate stack
+ .00010000 .00000100 .000000FF 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a1 @4 sw ; protect a1
+ .00000100 .0000B000 .00020000 23200000
+ # rs1_sp rs2_a2 @8 sw ; protect a2
+ .00000100 .0000C000 .00040000 23200000
+ # rs1_sp rs2_a3 @12 sw ; protect a3
+ .00000100 .0000D000 .00060000 23200000
+
+ # rd_a1 ~terminators auipc ; Get pointer to "\n\t "
+ .80050000 ~terminators 17000000
+ # rd_a1 rs1_a1 !terminators addi ; Get pointer to "\n\t "
+ .80050000 .00800500 !terminators 13000000
+ # rd_a3 rs1_s6 mv ; Protect string pointer
+ .80060000 .00000B00 13000000
+
+:Store_Atom_loop
+ # rs1_a3 rs2_a2 sb ; write byte
+ .00800600 .0000C000 23000000
+ # rd_ra $fgetc jal ; read next char
+ .80000000 $fgetc 6F000000
+ # rd_a2 rs1_a0 mv ; Update C
+ .00060000 .00000500 13000000
+ # rd_a3 rs1_a3 !1 addi ; STRING = STRING + 1
+ .80060000 .00800600 .00001000 13000000
+ # rd_ra $In_Set jal ; Check for terminators
+ .80000000 $In_Set 6F000000
+ # rs1_a0 @Store_Atom_loop beqz ; Loop if not "\n\t "
+ .00000500 @Store_Atom_loop 63000000
+
+ # rd_a0 rs1_s6 mv ; Prepare the string in scratch
+ .00050000 .00000B00 13000000
+ # rd_ra $string_length jal ; Calculate length
+ .80000000 $string_length 6F000000
+ # rd_a0 rs1_a0 !1 addi ; Add 1 for 0 terminator
+ .00050000 .00000500 .00001000 13000000
+ # rd_ra $malloc jal ; Allocate memory
+ .80000000 $malloc 6F000000
+ # rd_a3 rs1_sp !12 lw ; restore a3
+ .80060000 .00000100 .0000C000 03200000
+ # rs1_a3 rs2_a0 @8 sw ; HEAD->TEXT = STRING
+ .00800600 .0000A000 .00040000 23200000
+ # rd_ra $copy_string jal ; Copy the string
+ .80000000 $copy_string 6F000000
+
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a1 rs1_sp !4 lw ; restore a1
+ .80050000 .00000100 .00004000 03200000
+ # rd_a2 rs1_sp !8 lw ; restore a2
+ .00060000 .00000100 .00008000 03200000
+ # rd_sp rs1_sp !16 addi ; deallocate stack
+ .00010000 .00000100 .00000001 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; Reverse_List function
+; Receives list in a0
+; Returns the list reversed in a0
+:Reverse_List
+ # rd_sp rs1_sp !-8 addi ; allocate stack
+ .00010000 .00000100 .000080FF 13000000
+ # rs1_sp rs2_a1 sw ; protect a1
+ .00000100 .0000B000 23200000
+ # rs1_sp rs2_a2 @4 sw ; protect a2
+ .00000100 .0000C000 .00020000 23200000
+ # rd_a1 rs1_a0 mv ; Set HEAD
+ .80050000 .00000500 13000000
+ # rd_a0 mv ; ROOT = NULL
+ .00050000 13000000
+:Reverse_List_Loop
+ # rs1_a1 @Reverse_List_Done beqz ; Stop if HEAD == NULL
+ .00800500 @Reverse_List_Done 63000000
+
+ # rd_a2 rs1_a1 lw ; NEXT = HEAD->NEXT
+ .00060000 .00800500 03200000
+ # rs1_a1 rs2_a0 sw ; HEAD->NEXT = ROOT
+ .00800500 .0000A000 23200000
+ # rd_a0 rs1_a1 mv ; ROOT = HEAD
+ .00050000 .00800500 13000000
+ # rd_a1 rs1_a2 mv ; HEAD = NEXT
+ .80050000 .00000600 13000000
+ # $Reverse_List_Loop jal ; Continue looping
+ $Reverse_List_Loop 6F000000
+
+:Reverse_List_Done
+ # rd_a1 rs1_sp lw ; restore a1
+ .80050000 .00000100 03200000
+ # rd_a2 rs1_sp !4 lw ; restore a2
+ .00060000 .00000100 .00004000 03200000
+ # rd_sp rs1_sp !8 addi ; deallocate stack
+ .00010000 .00000100 .00008000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; Identify_Macros function
+; Receives List in a0
+; Updates the list in place; does not modify registers
+; Uses a1 for DEFINE, a2 for I
+:Identify_Macros
+ # rd_sp rs1_sp !-16 addi ; allocate stack
+ .00010000 .00000100 .000000FF 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a0 @4 sw ; protect a0
+ .00000100 .0000A000 .00020000 23200000
+ # rs1_sp rs2_a1 @8 sw ; protect a1
+ .00000100 .0000B000 .00040000 23200000
+ # rs1_sp rs2_a2 @12 sw ; protect a2
+ .00000100 .0000C000 .00060000 23200000
+
+ # rd_a1 ~DEFINE_str auipc ; Setup DEFINE string
+ .80050000 ~DEFINE_str 17000000
+ # rd_a1 rs1_a1 !DEFINE_str addi ; Setup DEFINE string
+ .80050000 .00800500 !DEFINE_str 13000000
+ # rd_a2 rs1_a0 mv ; I = HEAD
+ .00060000 .00000500 13000000
+
+:Identify_Macros_Loop
+ # rd_a0 rs1_a2 !8 lw ; I->TEXT
+ .00050000 .00000600 .00008000 03200000
+ # rd_ra $match jal ; IF "DEFINE" == I->TEXT
+ .80000000 $match 6F000000
+ # rs1_a0 @Identify_Macros_Next bnez ; Check if we got macro
+ .00000500 @Identify_Macros_Next 63100000
+
+ ; Deal with MACRO
+ # rd_a0 !1 addi ; a0 = MACRO
+ .00050000 .00001000 13000000
+ # rs1_a2 rs2_a0 @4 sw ; I->TYPE = MACRO
+ .00000600 .0000A000 .00020000 23200000
+
+ # rd_a0 rs1_a2 lw ; I->NEXT
+ .00050000 .00000600 03200000
+ # rd_a0 rs1_a0 !8 lw ; I->NEXT->TEXT
+ .00050000 .00000500 .00008000 03200000
+ # rs1_a2 rs2_a0 @8 sw ; I->TEXT = I->NEXT->TEXT
+ .00000600 .0000A000 .00040000 23200000
+
+ # rd_a0 rs1_a2 lw ; I->NEXT
+ .00050000 .00000600 03200000
+ # rd_a0 rs1_a0 lw ; I->NEXT->NEXT
+ .00050000 .00000500 03200000
+ # rd_a0 rs1_a0 !8 lw ; I->NEXT->NEXT->TEXT
+ .00050000 .00000500 .00008000 03200000
+ # rs1_a2 rs2_a0 @12 sw ; I->EXPRESSION = I->NEXT->NEXT->TEXT
+ .00000600 .0000A000 .00060000 23200000
+
+ # rd_a0 rs1_a2 lw ; I->NEXT
+ .00050000 .00000600 03200000
+ # rd_a0 rs1_a0 lw ; I->NEXT->NEXT
+ .00050000 .00000500 03200000
+ # rd_a0 rs1_a0 lw ; I->NEXT->NEXT->NEXT
+ .00050000 .00000500 03200000
+ # rs1_a2 rs2_a0 sw ; I->NEXT = I->NEXT->NEXT->NEXT
+ .00000600 .0000A000 23200000
+
+:Identify_Macros_Next
+ # rd_a2 rs1_a2 lw ; I = I->NEXT
+ .00060000 .00000600 03200000
+ # rs1_a2 @Identify_Macros_Loop bnez ; Check if we are done
+ .00000600 @Identify_Macros_Loop 63100000
+
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a0 rs1_sp !4 lw ; restore a0
+ .00050000 .00000100 .00004000 03200000
+ # rd_a1 rs1_sp !8 lw ; restore a1
+ .80050000 .00000100 .00008000 03200000
+ # rd_a2 rs1_sp !12 lw ; restore a2
+ .00060000 .00000100 .0000C000 03200000
+ # rd_sp rs1_sp !16 addi ; deallocate stack
+ .00010000 .00000100 .00000001 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; match function
+; Receives CHAR* in a0 and CHAR* in a1
+; Returns 0 (TRUE) or 1 (FALSE) in a0
+:match
+ # rd_sp rs1_sp !-12 addi ; allocate stack
+ .00010000 .00000100 .000040FF 13000000
+ # rs1_sp rs2_a1 sw ; protect a1
+ .00000100 .0000B000 23200000
+ # rs1_sp rs2_a2 @4 sw ; protect a2
+ .00000100 .0000C000 .00020000 23200000
+ # rs1_sp rs2_a3 @8 sw ; protect a3
+ .00000100 .0000D000 .00040000 23200000
+
+ # rd_a2 rs1_a0 mv ; S1 in place
+ .00060000 .00000500 13000000
+ # rd_a3 rs1_a1 mv ; S2 in place
+ .80060000 .00800500 13000000
+
+:match_Loop
+ # rd_a0 rs1_a2 lbu ; S1[i]
+ .00050000 .00000600 03400000
+ # rd_a1 rs1_a3 lbu ; S2[i]
+ .80050000 .00800600 03400000
+ # rs1_a0 rs2_a1 @match_False bne ; Check if they match
+ .00000500 .0000B000 @match_False 63100000
+
+ # rd_a2 rs1_a2 !1 addi ; S1 = S1 + 1
+ .00060000 .00000600 .00001000 13000000
+ # rd_a3 rs1_a3 !1 addi ; S2 = S2 + 1
+ .80060000 .00800600 .00001000 13000000
+ # rs1_a0 @match_Done beqz ; Match if we reached end of string
+ .00000500 @match_Done 63000000
+ # $match_Loop jal ; Otherwise keep looping
+ $match_Loop 6F000000
+
+:match_False
+ # rd_a0 !1 addi ; Return false
+ .00050000 .00001000 13000000
+:match_Done
+ # rd_a1 rs1_sp lw ; restore a1
+ .80050000 .00000100 03200000
+ # rd_a2 rs1_sp !4 lw ; restore a2
+ .00060000 .00000100 .00004000 03200000
+ # rd_a3 rs1_sp !8 lw ; restore a3
+ .80060000 .00000100 .00008000 03200000
+ # rd_sp rs1_sp !12 addi ; deallocate stack
+ .00010000 .00000100 .0000C000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; Line_Macro function
+; Receives List in a0
+; Updates the list in place; does not modify registers
+; Uses a0 for I, a1 for I->TEXT, a2 for I->EXPRESSION
+:Line_Macro
+ # rd_sp rs1_sp !-16 addi ; allocate stack
+ .00010000 .00000100 .000000FF 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a0 @4 sw ; protect a0
+ .00000100 .0000A000 .00020000 23200000
+ # rs1_sp rs2_a1 @8 sw ; protect a1
+ .00000100 .0000B000 .00040000 23200000
+ # rs1_sp rs2_a2 @12 sw ; protect a2
+ .00000100 .0000C000 .00060000 23200000
+
+:Line_Macro_Loop
+ # rd_a1 rs1_a0 !4 lw ; I->TYPE
+ .80050000 .00000500 .00004000 03200000
+ # rd_t0 !1 addi ; t0 = MACRO
+ .80020000 .00001000 13000000
+ # rs1_a1 rs2_t0 @Line_Macro_Next bne ; Move on unless I->TYPE == MACRO
+ .00800500 .00005000 @Line_Macro_Next 63100000
+
+ ; Apply macro
+ # rd_a1 rs1_a0 !8 lw ; I->TEXT
+ .80050000 .00000500 .00008000 03200000
+ # rd_a2 rs1_a0 !12 lw ; I->EXPRESSION
+ .00060000 .00000500 .0000C000 03200000
+ # rd_a0 rs1_a0 lw ; I->NEXT
+ .00050000 .00000500 03200000
+ # rd_ra $Set_Expression jal ; Apply it
+ .80000000 $Set_Expression 6F000000
+ # $Line_Macro_Loop jal ; Move on to next
+ $Line_Macro_Loop 6F000000
+
+:Line_Macro_Next
+ # rd_a0 rs1_a0 lw ; I->NEXT
+ .00050000 .00000500 03200000
+ # rs1_a0 @Line_Macro_Loop bnez ; Check if we are done
+ .00000500 @Line_Macro_Loop 63100000
+
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a0 rs1_sp !4 lw ; restore a0
+ .00050000 .00000100 .00004000 03200000
+ # rd_a1 rs1_sp !8 lw ; restore a1
+ .80050000 .00000100 .00008000 03200000
+ # rd_a2 rs1_sp !12 lw ; restore a2
+ .00060000 .00000100 .0000C000 03200000
+ # rd_sp rs1_sp !16 addi ; deallocate stack
+ .00010000 .00000100 .00000001 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; Set_Expression function
+; Receives List in a0, CHAR* in a1 and CHAR* in a2
+; Updates the list in place; does not modify registers
+; Uses a1 for C, a2 for EXP and a3 for I
+:Set_Expression
+ # rd_sp rs1_sp !-20 addi ; allocate stack
+ .00010000 .00000100 .0000C0FE 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a0 @4 sw ; protect a0
+ .00000100 .0000A000 .00020000 23200000
+ # rs1_sp rs2_a1 @8 sw ; protect a1
+ .00000100 .0000B000 .00040000 23200000
+ # rs1_sp rs2_a2 @12 sw ; protect a2
+ .00000100 .0000C000 .00060000 23200000
+ # rs1_sp rs2_a3 @16 sw ; protect a3
+ .00000100 .0000D000 .00080000 23200000
+
+ # rd_a3 rs1_a0 mv ; Set I
+ .80060000 .00000500 13000000
+:Set_Expression_Loop
+ # rd_a0 rs1_a3 !4 lw ; I->TYPE
+ .00050000 .00800600 .00004000 03200000
+ # rd_t0 !1 addi ; t0 = MACRO
+ .80020000 .00001000 13000000
+ # rs1_a0 rs2_t0 @Set_Expression_Next beq ; If MACRO == I->Type then ignore and move on
+ .00000500 .00005000 @Set_Expression_Next 63000000
+
+ # rd_a0 rs1_a3 !8 lw ; I->TEXT
+ .00050000 .00800600 .00008000 03200000
+ # rd_ra $match jal ; Check for match
+ .80000000 $match 6F000000
+ # rs1_a0 @Set_Expression_Next bnez ; Check next if does not match
+ .00000500 @Set_Expression_Next 63100000
+
+ ; Non-macro match
+ # rs1_a3 rs2_a2 @12 sw ; I->EXPRESSION = EXP
+ .00800600 .0000C000 .00060000 23200000
+
+:Set_Expression_Next
+ # rd_a3 rs1_a3 lw ; I = I->NEXT
+ .80060000 .00800600 03200000
+ # rs1_a3 @Set_Expression_Loop bnez ; Check if we are done
+ .00800600 @Set_Expression_Loop 63100000
+
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a0 rs1_sp !4 lw ; restore a0
+ .00050000 .00000100 .00004000 03200000
+ # rd_a1 rs1_sp !8 lw ; restore a1
+ .80050000 .00000100 .00008000 03200000
+ # rd_a2 rs1_sp !12 lw ; restore a2
+ .00060000 .00000100 .0000C000 03200000
+ # rd_a3 rs1_sp !16 lw ; restore a3
+ .80060000 .00000100 .00000001 03200000
+ # rd_sp rs1_sp !20 addi ; deallocate stack
+ .00010000 .00000100 .00004001 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; Process_String function
+; Receives List in a0
+; Update the list in place; does not modify registers
+; Uses a1 for I->TEXT, a2 for I and RDX for S
+:Process_String
+ # rd_sp rs1_sp !-20 addi ; allocate stack
+ .00010000 .00000100 .0000C0FE 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a0 @4 sw ; protect a0
+ .00000100 .0000A000 .00020000 23200000
+ # rs1_sp rs2_a1 @8 sw ; protect a1
+ .00000100 .0000B000 .00040000 23200000
+ # rs1_sp rs2_a2 @12 sw ; protect a2
+ .00000100 .0000C000 .00060000 23200000
+ # rs1_sp rs2_a3 @16 sw ; protect a3
+ .00000100 .0000D000 .00080000 23200000
+
+ # rd_a2 rs1_a0 mv ; I = HEAD
+ .00060000 .00000500 13000000
+
+:Process_String_loop
+ # rd_a0 rs1_a2 !4 lw ; I->TYPE
+ .00050000 .00000600 .00004000 03200000
+ # rd_t0 !2 addi ; t0 = STRING
+ .80020000 .00002000 13000000
+ # rs1_a0 rs2_t0 @Process_String_Next bne ; Skip to next
+ .00000500 .00005000 @Process_String_Next 63100000
+
+ # rd_a1 rs1_a2 !8 lw ; I->TEXT
+ .80050000 .00000600 .00008000 03200000
+ # rd_a0 rs1_a1 lbu ; I->TEXT[0]
+ .00050000 .00800500 03400000
+ # rd_t0 !39 addi ; t0 = \'
+ .80020000 .00007002 13000000
+ # rs1_a0 rs2_t0 @Process_String_Raw bne ; Deal with '"'
+ .00000500 .00005000 @Process_String_Raw 63100000
+
+ ; Deal with \'
+ # rd_a1 rs1_a1 !1 addi ; I->TEXT + 1
+ .80050000 .00800500 .00001000 13000000
+ # rs1_a2 rs2_a1 @12 sw ; I->EXPRESSION = I->TEXT + 1
+ .00000600 .0000B000 .00060000 23200000
+ # $Process_String_Next jal ; Move on to next
+ $Process_String_Next 6F000000
+
+:Process_String_Raw
+ # rd_a0 rs1_a1 mv ; I->TEXT
+ .00050000 .00800500 13000000
+ # rd_ra $string_length jal ; Get length of I->TEXT
+ .80000000 $string_length 6F000000
+ # rd_a0 rs1_a0 rs2_x2 srli ; LENGTH = LENGTH >> 2
+ .00050000 .00000500 .00002000 13500000
+ # rd_a0 rs1_a0 !1 addi ; LENGTH = LENGTH + 1
+ .00050000 .00000500 .00001000 13000000
+ # rd_a0 rs1_a0 rs2_x3 slli ; LENGTH = LENGTH << 3
+ .00050000 .00000500 .00003000 13100000
+ # rd_ra $malloc jal ; Get string
+ .80000000 $malloc 6F000000
+ # rd_a3 rs1_a1 mv ; S = I->TEXT
+ .80060000 .00800500 13000000
+ # rd_a3 rs1_a3 !1 addi ; S = S + 1
+ .80060000 .00800600 .00001000 13000000
+ # rs1_a2 rs2_a0 @12 sw ; I->EXPRESSION = hexify
+ .00000600 .0000A000 .00060000 23200000
+ # rd_a1 rs1_a0 mv ; Put hexify buffer in a1
+ .80050000 .00000500 13000000
+
+:Process_String_Raw_Loop
+ # rd_a0 rs1_a3 lbu ; Read 1 character
+ .00050000 .00800600 03400000
+ # rd_a3 rs1_a3 !1 addi ; S = S + 1
+ .80060000 .00800600 .00001000 13000000
+ # rd_s5 rs1_a0 mv ; Protect character
+ .800A0000 .00000500 13000000
+ # rd_ra $hex8 jal ; write them all
+ .80000000 $hex8 6F000000
+ # rd_a0 rs1_s5 mv ; Restore character
+ .00050000 .00800A00 13000000
+ # rs1_a0 @Process_String_Raw_Loop bnez ; Keep looping
+ .00000500 @Process_String_Raw_Loop 63100000
+
+:Process_String_Next
+ # rd_a2 rs1_a2 lw ; I = I->NEXT
+ .00060000 .00000600 03200000
+ # rs1_a2 @Process_String_loop bnez ; Check if we are done
+ .00000600 @Process_String_loop 63100000
+
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a0 rs1_sp !4 lw ; restore a0
+ .00050000 .00000100 .00004000 03200000
+ # rd_a1 rs1_sp !8 lw ; restore a1
+ .80050000 .00000100 .00008000 03200000
+ # rd_a2 rs1_sp !12 lw ; restore a2
+ .00060000 .00000100 .0000C000 03200000
+ # rd_a3 rs1_sp !16 lw ; restore a3
+ .80060000 .00000100 .00000001 03200000
+ # rd_sp rs1_sp !20 addi ; deallocate stack
+ .00010000 .00000100 .00004001 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; string_length function
+; Receives CHAR* in a0
+; Returns INT in a0
+; Uses a0 for CH, a1 for S and a2 for INDEX
+:string_length
+ # rd_sp rs1_sp !-8 addi ; allocate stack
+ .00010000 .00000100 .000080FF 13000000
+ # rs1_sp rs2_a1 sw ; protect a1
+ .00000100 .0000B000 23200000
+ # rs1_sp rs2_a2 @4 sw ; protect a2
+ .00000100 .0000C000 .00020000 23200000
+
+ # rd_a1 rs1_a0 mv ; Set S
+ .80050000 .00000500 13000000
+ # rd_a2 mv ; INDEX = 0
+ .00060000 13000000
+
+:string_length_loop
+ # rd_t0 rs1_a1 rs2_a2 add ; S + INDEX
+ .80020000 .00800500 .0000C000 33000000
+ # rd_a0 rs1_t0 lbu ; S[INDEX]
+ .00050000 .00800200 03400000
+ # rs1_a0 @string_length_done beqz ; Check if we are done
+ .00000500 @string_length_done 63000000
+
+ # rd_a2 rs1_a2 !1 addi ; INDEX = INDEX + 1
+ .00060000 .00000600 .00001000 13000000
+ # $string_length_loop jal ; Keep going
+ $string_length_loop 6F000000
+
+:string_length_done
+ # rd_a0 rs1_a2 mv ; return INDEX
+ .00050000 .00000600 13000000
+ # rd_a1 rs1_sp lw ; restore a1
+ .80050000 .00000100 03200000
+ # rd_a2 rs1_sp !4 lw ; restore a2
+ .00060000 .00000100 .00004000 03200000
+ # rd_sp rs1_sp !8 addi ; deallocate stack
+ .00010000 .00000100 .00008000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; Eval_Immediates function
+; Receives List in a0
+; Updates the list in place; does not modify registers
+; Uses a1 for I->TEXT[0], a2 for I->TEXT[1] and a3 for I
+:Eval_Immediates
+ # rd_sp rs1_sp !-20 addi ; allocate stack
+ .00010000 .00000100 .0000C0FE 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a0 @4 sw ; protect a0
+ .00000100 .0000A000 .00020000 23200000
+ # rs1_sp rs2_a1 @8 sw ; protect a1
+ .00000100 .0000B000 .00040000 23200000
+ # rs1_sp rs2_a2 @12 sw ; protect a2
+ .00000100 .0000C000 .00060000 23200000
+ # rs1_sp rs2_a3 @16 sw ; protect a3
+ .00000100 .0000D000 .00080000 23200000
+
+ # rd_a3 rs1_a0 mv ; I = HEAD
+ .80060000 .00000500 13000000
+
+:Eval_Immediates_Loop
+ ; Check for MACRO
+ # rd_a0 rs1_a3 !4 lw ; I->TYPE
+ .00050000 .00800600 .00004000 03200000
+ # rd_t0 !1 addi ; t0 = MACRO
+ .80020000 .00001000 13000000
+ # rs1_a0 rs2_t0 @Eval_Immediates_Next beq ; Skip to next if I->TYPE == MACRO
+ .00000500 .00005000 @Eval_Immediates_Next 63000000
+
+ ; Check for NULL EXPRESSION
+ # rd_a0 rs1_a3 !12 lw ; I->EXPRESSION
+ .00050000 .00800600 .0000C000 03200000
+ # rs1_a0 @Eval_Immediates_Next bnez ; Skip to next if NULL == I->EXPRESSION
+ .00000500 @Eval_Immediates_Next 63100000
+
+ ; Check if number
+ # rd_a0 rs1_a3 !8 lw ; I->TEXT
+ .00050000 .00800600 .00008000 03200000
+ # rd_a1 rs1_a0 lbu ; I->TEXT[0]
+ .80050000 .00000500 03400000
+ # rd_a0 rs1_a0 !1 addi ; I->TEXT + 1
+ .00050000 .00000500 .00001000 13000000
+ # rd_a2 rs1_a0 lbu ; I->TEXT[1]
+ .00060000 .00000500 03400000
+ # rd_ra $numerate_string jal ; Convert string to INT
+ .80000000 $numerate_string 6F000000
+ # rs1_a0 @Eval_Immediates_value bnez ; Has a value IF 0 != numerate_string(I->TEXT + 1)
+ .00000500 @Eval_Immediates_value 63100000
+
+ ; Last chance for Immediate
+ # rd_t0 !48 addi ; If '0' = I->TEXT[1]
+ .80020000 .00000003 13000000
+ # rs1_a2 rs2_t0 @Eval_Immediates_Next bne ; Skip to next
+ .00000600 .00005000 @Eval_Immediates_Next 63100000
+
+:Eval_Immediates_value
+ # rd_ra $express_number jal ; Convert value to hex string
+ .80000000 $express_number 6F000000
+ # rs1_a3 rs2_a0 @12 sw ; I->EXPRESSION = express_number(value, I-TEXT[0])
+ .00800600 .0000A000 .00060000 23200000
+
+:Eval_Immediates_Next
+ # rd_a3 rs1_a3 lw ; I = I->NEXT
+ .80060000 .00800600 03200000
+ # rs1_a3 @Eval_Immediates_Loop bnez ; Check if we are done
+ .00800600 @Eval_Immediates_Loop 63100000
+
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a0 rs1_sp !4 lw ; restore a0
+ .00050000 .00000100 .00004000 03200000
+ # rd_a1 rs1_sp !8 lw ; restore a1
+ .80050000 .00000100 .00008000 03200000
+ # rd_a2 rs1_sp !12 lw ; restore a2
+ .00060000 .00000100 .0000C000 03200000
+ # rd_a3 rs1_sp !16 lw ; restore a3
+ .80060000 .00000100 .00000001 03200000
+ # rd_sp rs1_sp !20 addi ; deallocate stack
+ .00010000 .00000100 .00004001 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; numerate_string function
+; Receives CHAR* in a0
+; Returns value of CHAR* in a0
+; Uses a0 for VALUE, a1 for S, a2 for CH and a3 for NEGATIVE?
+:numerate_string
+ # rd_sp rs1_sp !-12 addi ; allocate stack
+ .00010000 .00000100 .000040FF 13000000
+ # rs1_sp rs2_a1 sw ; protect a1
+ .00000100 .0000B000 23200000
+ # rs1_sp rs2_a2 @4 sw ; protect a2
+ .00000100 .0000C000 .00020000 23200000
+ # rs1_sp rs2_a3 @8 sw ; protect a3
+ .00000100 .0000D000 .00040000 23200000
+
+ # rd_a1 rs1_a0 mv ; put S in correct place
+ .80050000 .00000500 13000000
+ # rd_a0 mv ; Initialize to Zero
+ .00050000 13000000
+:numerate_string_loop
+ # rd_t0 rs1_a1 !1 addi ; S + 1
+ .80020000 .00800500 .00001000 13000000
+ # rd_a2 rs1_t0 lbu ; S[1]
+ .00060000 .00800200 03400000
+ # rd_t0 !120 addi ; 'x'
+ .80020000 .00008007 13000000
+ # rs1_a2 rs2_t0 @numerate_hex beq ; Deal with hex_input
+ .00000600 .00005000 @numerate_hex 63000000
+
+ ; Assume decimal input
+ # rd_a3 mv ; Assume no negation
+ .80060000 13000000
+ # rd_a2 rs1_a1 lbu ; S[0]
+ .00060000 .00800500 03400000
+ # rd_t0 !45 addi ; '-'
+ .80020000 .0000D002 13000000
+ # rs1_a2 rs2_t0 @numerate_decimal bne ; Skip negation
+ .00000600 .00005000 @numerate_decimal 63100000
+
+ # rd_a3 !1 addi ; Set FLAG
+ .80060000 .00001000 13000000
+ # rd_a1 rs1_a1 !1 addi ; S = S + 1
+ .80050000 .00800500 .00001000 13000000
+
+:numerate_decimal
+ # rd_a2 rs1_a1 lbu ; S[i]
+ .00060000 .00800500 03400000
+ # rs1_a2 @numerate_decimal_done beqz ; We are done if NULL == S[i]
+ .00000600 @numerate_decimal_done 63000000
+
+ ; a0 = a0 * 10 = (a0 << 3) + (a0 << 1)
+ # rd_t0 rs1_a0 rs2_x3 slli ; a0 * 8
+ .80020000 .00000500 .00003000 13100000
+ # rd_t1 rs1_a0 rs2_x1 slli ; a0 * 2
+ .00030000 .00000500 .00001000 13100000
+ # rd_a0 rs1_t0 rs2_t1 add ; VALUE = VALUE * 10
+ .00050000 .00800200 .00006000 33000000
+ # rd_a2 rs1_a2 !-48 addi ; CH = CH - '0'
+ .00060000 .00000600 .000000FD 13000000
+ # rd_t0 !9 addi ; t0 = 9
+ .80020000 .00009000 13000000
+ # rs1_t0 rs2_a2 @numerate_string_fail blt ; Check for illegal CH > 9
+ .00800200 .0000C000 @numerate_string_fail 63400000
+ # rs1_a2 @numerate_string_fail bltz ; Check for illegal CH < 0
+ .00000600 @numerate_string_fail 63400000
+ # rd_a0 rs1_a0 rs2_a2 add ; VALUE = VALUE + CH
+ .00050000 .00000500 .0000C000 33000000
+ # rd_a1 rs1_a1 !1 addi ; S = S + 1
+ .80050000 .00800500 .00001000 13000000
+ # $numerate_decimal jal
+ $numerate_decimal 6F000000
+
+:numerate_decimal_done
+ # rd_t0 !1 addi ; Check for negative FLAG
+ .80020000 .00001000 13000000
+ # rs1_a3 rs2_t0 @numerate_string_done bne ; Nope
+ .00800600 .00005000 @numerate_string_done 63100000
+
+ # rd_a0 rs2_a0 sub ; VALUE = -VALUE
+ .00050000 .0000A000 33000040
+ # $numerate_string_done jal ; Done
+ $numerate_string_done 6F000000
+
+:numerate_hex
+ # rd_a1 rs1_a1 !2 addi ; S = S + 2
+ .80050000 .00800500 .00002000 13000000
+:numerate_hex_loop
+ # rd_a2 rs1_a1 lbu ; S[i]
+ .00060000 .00800500 03400000
+ # rs1_a2 @numerate_string_done beqz ; We are done if NULL == S[i]
+ .00000600 @numerate_string_done 63000000
+
+ # rd_a0 rs1_a0 rs2_x4 slli ; VALUE = VALUE << 4
+ .00050000 .00000500 .00004000 13100000
+ # rd_a2 rs1_a2 !-48 addi ; CH = CH - '0'
+ .00060000 .00000600 .000000FD 13000000
+ # rd_t0 !10 addi ; t0 = 10
+ .80020000 .0000A000 13000000
+ # rs1_a2 rs2_t0 @numerate_hex_digit blt ; Check if we are dealing with number or letter
+ .00000600 .00005000 @numerate_hex_digit 63400000
+ # rd_a2 rs1_a2 !-7 addi ; Push A-F into range
+ .00060000 .00000600 .000090FF 13000000
+
+:numerate_hex_digit
+ # rd_t0 !15 addi ; t0 = 15
+ .80020000 .0000F000 13000000
+ # rs1_t0 rs2_a2 @numerate_string_fail blt ; Check for CH > 'F'
+ .00800200 .0000C000 @numerate_string_fail 63400000
+ # rs1_a2 @numerate_string_fail bltz ; Check for CH < 0
+ .00000600 @numerate_string_fail 63400000
+ # rd_a0 rs1_a0 rs2_a2 add ; VALUE = VALUE + CH
+ .00050000 .00000500 .0000C000 33000000
+ # rd_a1 rs1_a1 !1 addi ; S = S + 1
+ .80050000 .00800500 .00001000 13000000
+ # $numerate_hex_loop jal ; Keep looping
+ $numerate_hex_loop 6F000000
+
+:numerate_string_fail
+ # rd_a0 mv ; return ZERO
+ .00050000 13000000
+
+:numerate_string_done
+ # rd_a1 rs1_sp lw ; restore a1
+ .80050000 .00000100 03200000
+ # rd_a2 rs1_sp !4 lw ; restore a2
+ .00060000 .00000100 .00004000 03200000
+ # rd_a3 rs1_sp !8 lw ; restore a3
+ .80060000 .00000100 .00008000 03200000
+ # rd_sp rs1_sp !12 addi ; deallocate stack
+ .00010000 .00000100 .0000C000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; express_number function
+; Receives INT in a0 and CHAR in a1
+; Allocates a string and expresses the value in appropriate RISC-V encoding
+; Returns string in a0
+; Uses a0 for VALUE, a1 for S and a2 for CH
+:express_number
+ # rd_sp rs1_sp !-16 addi ; allocate stack
+ .00010000 .00000100 .000000FF 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a1 @4 sw ; protect a1
+ .00000100 .0000B000 .00020000 23200000
+ # rs1_sp rs2_a2 @8 sw ; protect a2
+ .00000100 .0000C000 .00040000 23200000
+ # rs1_sp rs2_a3 @12 sw ; protect a3
+ .00000100 .0000D000 .00060000 23200000
+
+ # rd_a2 rs1_a1 mv ; Put CH in right place
+ .00060000 .00800500 13000000
+ # rd_s5 rs1_a0 mv ; Protect VALUE
+ .800A0000 .00000500 13000000
+
+ # rd_a0 !10 addi ; We need 10 bytes
+ .00050000 .0000A000 13000000
+ # rd_ra $malloc jal ; Get S pointer
+ .80000000 $malloc 6F000000
+ # rd_a1 rs1_a0 mv ; Put S in place
+ .80050000 .00000500 13000000
+ # rd_a0 rs1_s5 mv ; Restore VALUE
+ .00050000 .00800A00 13000000
+
+ ; Check for %
+ # rd_t0 !0x25 addi
+ .80020000 .00005002 13000000
+ # rs1_a2 rs2_t0 @express_number_const beq
+ .00000600 .00005000 @express_number_const 63000000
+
+ # rd_s5 rs1_a1 mv ; Protect S
+ .800A0000 .00800500 13000000
+ # rd_t0 !0x2E addi ; t0 = '.'
+ .80020000 .0000E002 13000000
+ # rs1_a1 rs2_t0 sw ; S[0] = '.'
+ .00800500 .00005000 23200000
+ # rd_a1 rs1_a1 !1 addi ; Next byte
+ .80050000 .00800500 .00001000 13000000
+
+ ; Check for !
+ # rd_t0 !0x21 addi
+ .80020000 .00001002 13000000
+ # rs1_a2 rs2_t0 @express_number_I beq
+ .00000600 .00005000 @express_number_I 63000000
+
+ ; Check for @
+ # rd_t0 !0x40 addi
+ .80020000 .00000004 13000000
+ # rs1_a2 rs2_t0 @express_number_S beq
+ .00000600 .00005000 @express_number_S 63000000
+
+ ; Check for ~
+ # rd_t0 !0x7E addi
+ .80020000 .0000E007 13000000
+ # rs1_a2 rs2_t0 @express_number_U beq
+ .00000600 .00005000 @express_number_U 63000000
+
+ # $Fail jal ; Error
+ $Fail 6F000000
+
+:express_number_const
+ ; provides an option for 32-bit immediate constants
+
+ # rd_t0 !2 addi
+ .80020000 .00002000 13000000
+ # rd_t0 rs1_t0 rs2_x31 slli
+ .80020000 .00800200 .0000F001 13100000
+ # rd_t0 rs1_t0 !-1 addi ; t0 = 0xffffffff
+ .80020000 .00800200 .0000F0FF 13000000
+ # rd_a0 rs1_a0 rs2_t0 and ; immediate = value & 0xffffffff
+ .00050000 .00000500 .00005000 33700000
+
+ # rd_s5 rs1_a1 mv ; Protect S
+ .800A0000 .00800500 13000000
+ # rd_ra $hex32l jal ; Store 32-bits
+ .80000000 $hex32l 6F000000
+ # $express_number_done jal ; done
+ $express_number_done 6F000000
+
+:express_number_I
+ ; Corresponds to RISC-V S format
+ ; (value & 0xfff) << 20
+ # rd_t0 !0xFFF addi
+ .80020000 .0000F0FF 13000000
+ # rd_a0 rs1_a0 rs2_t0 and ; value & 0xfff
+ .00050000 .00000500 .00005000 33700000
+ # rd_a0 rs1_a0 rs2_x20 slli ; (value & 0xfff) << 20
+ .00050000 .00000500 .00004001 13100000
+ # rd_ra $hex32l jal ; Store 32-bits
+ .80000000 $hex32l 6F000000
+ # $express_number_done jal ; done
+ $express_number_done 6F000000
+
+:express_number_S
+ ; Corresponds to RISC-V S format
+ ; ((value & 0x1f) << 7) | ((value & 0xfe0) << (31 - 11))
+ # rd_t0 !0x1F addi
+ .80020000 .0000F001 13000000
+ # rd_t1 rs1_a0 rs2_t0 and ; value & 0x1f
+ .00030000 .00000500 .00005000 33700000
+ # rd_t1 rs1_t1 rs2_x7 slli ; (value & 0x1f) << 7
+ .00030000 .00000300 .00007000 13100000
+ # rd_t0 !0xFE0 addi
+ .80020000 .000000FE 13000000
+ # rd_t0 rs1_a0 rs2_t0 and ; value & 0xfe0
+ .80020000 .00000500 .00005000 33700000
+ # rd_t0 rs1_t0 rs2_x20 slli ; (value & 0xfe0) << (31 - 11)
+ .80020000 .00800200 .00004001 13100000
+ # rd_a0 rs1_t0 rs2_t1 or ; Combine two parts
+ .00050000 .00800200 .00006000 33600000
+ # rd_ra $hex32l jal ; Store 32-bits
+ .80000000 $hex32l 6F000000
+ # $express_number_done jal ; done
+ $express_number_done 6F000000
+
+:express_number_U
+ ; Corresponds to RISC-V U format
+ ; if value is 0x800 or more we have to add 11-th bit (0x1000) to compensate for signed extension
+
+ # rd_t0 ~0x800 lui ; load higher bits
+ .80020000 .00100000 37000000
+ # rd_t0 rs1_t0 !0x800 addi
+ .80020000 .00800200 .00000080 13000000
+ # rd_t1 ~0xFFF lui ; load higher bits
+ .00030000 .00100000 37000000
+ # rd_t1 rs1_t1 !0xFFF addi
+ .00030000 .00000300 .0000F0FF 13000000
+ ; We are outside 31-bit that ~ can normally load
+ # rd_t2 ~0x100000 lui ; load 0xfffff000
+ .80030000 .00001000 37000000
+ # rd_t2 rs1_t2 !-1 addi ; load 0xfffff000
+ .80030000 .00800300 .0000F0FF 13000000
+ # rd_t2 rs1_t2 rs2_x12 slli ; load 0xfffff000
+ .80030000 .00800300 .0000C000 13100000
+ # rd_t1 rs1_a0 rs2_t1 and ; value & 0xfff
+ .00030000 .00000500 .00006000 33700000
+ # rd_a0 rs1_a0 rs2_t2 and ; value & 0xfffff000
+ .00050000 .00000500 .00007000 33700000
+ # rs1_t1 rs2_t0 @express_number_U_small blt
+ .00000300 .00005000 @express_number_U_small 63400000
+
+ ; Deal with sign extension: add 0x1000
+ # rd_t0 ~0x1000 lui
+ .80020000 .00100000 37000000
+ # rd_a0 rs1_t0 rs2_a0 add ; (value & 0xfffff000) + 0x1000
+ .00050000 .00800200 .0000A000 33000000
+:express_number_U_small
+ # rd_ra $hex32l jal ; Store 32-bits
+ .80000000 $hex32l 6F000000
+ # $express_number_done jal ; done
+ $express_number_done 6F000000
+
+:express_number_done
+ # rd_a0 rs1_s5 mv ; Restore S
+ .00050000 .00800A00 13000000
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a1 rs1_sp !4 lw ; restore a1
+ .80050000 .00000100 .00004000 03200000
+ # rd_a2 rs1_sp !8 lw ; restore a2
+ .00060000 .00000100 .00008000 03200000
+ # rd_a3 rs1_sp !12 lw ; restore a3
+ .80060000 .00000100 .0000C000 03200000
+ # rd_sp rs1_sp !16 addi ; deallocate stack
+ .00010000 .00000100 .00000001 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; HEX to ascii routine
+; Receives INT in a0 and CHAR* in a1
+; Stores ascii of INT in CHAR*
+; Returns only modifying a0
+:hex32l
+ # rd_sp rs1_sp !-8 addi ; allocate stack
+ .00010000 .00000100 .000080FF 13000000
+ # rs1_sp rs2_ra sw ; Protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a0 @4 sw ; Protect top 16 bits
+ .00000100 .0000A000 .00020000 23200000
+ # rd_ra $hex16l jal ; Store it
+ .80000000 $hex16l 6F000000
+ # rd_a0 rs1_sp !4 lw ; do high 16-bits
+ .00050000 .00000100 .00004000 03200000
+ # rd_a0 rs1_a0 rs2_x16 srli ; do bottom 16 bits
+ .00050000 .00000500 .00000001 13500000
+ # rd_ra $hex16l jal ; Store it
+ .80000000 $hex16l 6F000000
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_sp rs1_sp !8 addi ; deallocate stack
+ .00010000 .00000100 .00008000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+:hex16l
+ # rd_sp rs1_sp !-8 addi ; allocate stack
+ .00010000 .00000100 .000080FF 13000000
+ # rs1_sp rs2_ra sw ; Protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a0 @4 sw ; Protect top byte
+ .00000100 .0000A000 .00020000 23200000
+ # rd_ra $hex8 jal ; Store it
+ .80000000 $hex8 6F000000
+ # rd_a0 rs1_sp !4 lw ; do high byte
+ .00050000 .00000100 .00004000 03200000
+ # rd_a0 rs1_a0 rs2_x8 srli ; do bottom byte
+ .00050000 .00000500 .00008000 13500000
+ # rd_ra $hex8 jal ; Store it
+ .80000000 $hex8 6F000000
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_sp rs1_sp !8 addi ; deallocate stack
+ .00010000 .00000100 .00008000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+:hex8
+ # rd_sp rs1_sp !-8 addi ; allocate stack
+ .00010000 .00000100 .000080FF 13000000
+ # rs1_sp rs2_ra sw ; Protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a0 @4 sw ; Protect bottom nibble
+ .00000100 .0000A000 .00020000 23200000
+ # rd_a0 rs1_a0 rs2_x4 srli ; do high nibble first
+ .00050000 .00000500 .00004000 13500000
+ # rd_ra $hex4 jal ; Store it
+ .80000000 $hex4 6F000000
+ # rd_a0 rs1_sp !4 lw ; do low nibble
+ .00050000 .00000100 .00004000 03200000
+ # rd_ra $hex4 jal ; Store it
+ .80000000 $hex4 6F000000
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_sp rs1_sp !8 addi ; deallocate stack
+ .00010000 .00000100 .00008000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+:hex4
+ # rd_t0 !0xF addi
+ .80020000 .0000F000 13000000
+ # rd_a0 rs1_a0 rs2_t0 and ; isolate nibble
+ .00050000 .00000500 .00005000 33700000
+ # rd_a0 rs1_a0 !0x30 addi ; convert to ascii
+ .00050000 .00000500 .00000003 13000000
+ # rd_t0 !0x39 addi ; t0 = '9'
+ .80020000 .00009003 13000000
+ # rs1_t0 rs2_a0 @hex1 bge ; check if valid digit
+ .00800200 .0000A000 @hex1 63500000
+ # rd_a0 rs1_a0 !7 addi ; use alpha range
+ .00050000 .00000500 .00007000 13000000
+:hex1
+ # rs1_a1 rs2_a0 sb ; store result
+ .00800500 .0000A000 23000000
+ # rd_a1 rs1_a1 !1 addi ; next position
+ .80050000 .00800500 .00001000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; Preserve_Other function
+; Receives list in a0
+; Update the list in place; does not modify registers
+; Uses a0 for I, a1 for I->TEXT
+:Preserve_Other
+ # rd_sp rs1_sp !-16 addi ; allocate stack
+ .00010000 .00000100 .000000FF 13000000
+ # rs1_sp rs2_a1 sw ; protect a1
+ .00000100 .0000B000 23200000
+ # rs1_sp rs2_a2 @4 sw ; protect a2
+ .00000100 .0000C000 .00020000 23200000
+ # rs1_sp rs2_a3 @8 sw ; protect a3
+ .00000100 .0000D000 .00040000 23200000
+ # rs1_sp rs2_a4 @21 sw ; protect a4
+ .00000100 .0000E000 .00060000 23200000
+:Preserve_Other_Loop
+ # rd_a1 rs1_a0 !12 lw ; I->EXPRESSION
+ .80050000 .00000500 .0000C000 03200000
+ # rs1_a1 @Preserve_Other_Next bnez ; IF NULL == I->EXPRESSION then preserve
+ .00800500 @Preserve_Other_Next 63100000
+
+ # rd_a1 rs1_a0 !8 lw ; I->TEXT
+ .80050000 .00000500 .00008000 03200000
+ # rs1_a0 rs2_a1 @12 sw ; I->EXPRESSION = I->TEXT
+ .00000500 .0000B000 .00060000 23200000
+
+:Preserve_Other_Next
+ # rd_a0 rs1_a0 lw ; I = I->NEXT
+ .00050000 .00000500 03200000
+ # rs1_a0 @Preserve_Other_Loop bnez ; Keep looping until I == NULL
+ .00000500 @Preserve_Other_Loop 63100000
+
+ # rd_a1 rs1_sp lw ; restore a1
+ .80050000 .00000100 03200000
+ # rd_a2 rs1_sp !4 lw ; restore a2
+ .00060000 .00000100 .00004000 03200000
+ # rd_a3 rs1_sp !8 lw ; restore a3
+ .80060000 .00000100 .00008000 03200000
+ # rd_a4 rs1_sp !12 lw ; restore a4
+ .00070000 .00000100 .0000C000 03200000
+ # rd_sp rs1_sp !16 addi ; deallocate stack
+ .00010000 .00000100 .00000001 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; Print_Hex function
+; Receives list in a0
+; walks the list and prints the I->EXPRESSION for all nodes followed by newline
+; Uses a1 for I
+:Print_Hex
+ # rd_sp rs1_sp !-12 addi ; allocate stack
+ .00010000 .00000100 .000040FF 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a1 @4 sw ; protect a1
+ .00000100 .0000B000 .00020000 23200000
+ # rs1_sp rs2_a2 @8 sw ; protect a2
+ .00000100 .0000C000 .00040000 23200000
+
+ # rd_a1 rs1_s4 mv ; I = HEAD
+ .80050000 .00000A00 13000000
+
+:Print_Hex_Loop
+ # rd_a0 rs1_a1 !4 lw ; I->TYPE
+ .00050000 .00800500 .00004000 03200000
+ # rd_t0 !1 addi ; t0 = MACRO
+ .80020000 .00001000 13000000
+ # rs1_a0 rs2_t0 @Print_Hex_Next beq ; Skip if MACRO = I->TYPE
+ .00000500 .00005000 @Print_Hex_Next 63000000
+
+ # rd_a0 rs1_a1 !12 lw ; Using EXPRESSION
+ .00050000 .00800500 .0000C000 03200000
+ # rd_ra $File_Print jal ; Print it
+ .80000000 $File_Print 6F000000
+ # rd_a0 !10 addi ; \n
+ .00050000 .0000A000 13000000
+ # rd_ra $fputc jal ; Print newline
+ .80000000 $fputc 6F000000
+
+:Print_Hex_Next
+ # rd_a1 rs1_a1 lw ; Iterate to next Token
+ .80050000 .00800500 03200000
+ # rs1_a1 @Print_Hex_Loop bnez ; Stop if NULL, otherwise keep looping
+ .00800500 @Print_Hex_Loop 63100000
+
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a1 rs1_sp !4 lw ; restore a1
+ .80050000 .00000100 .00004000 03200000
+ # rd_a2 rs1_sp !8 lw ; restore a2
+ .00060000 .00000100 .00008000 03200000
+ # rd_sp rs1_sp !12 addi ; deallocate stack
+ .00010000 .00000100 .0000C000 13000000
+ # ret
+ 67800000
+
+
+; File_Print function
+; Receives CHAR* in a0
+; calls fputc for every non-null char
+:File_Print
+ # rd_sp rs1_sp !-12 addi ; allocate stack
+ .00010000 .00000100 .000040FF 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a1 @4 sw ; protect a1
+ .00000100 .0000B000 .00020000 23200000
+ # rs1_sp rs2_a2 @8 sw ; protect a2
+ .00000100 .0000C000 .00040000 23200000
+ # rd_a1 rs1_a0 mv ; protect a0
+ .80050000 .00000500 13000000
+
+ # rs1_a0 @File_Print_Done beqz ; Protect against nulls
+ .00000500 @File_Print_Done 63000000
+
+:File_Print_Loop
+ # rd_a0 rs1_a1 lbu ; Read byte
+ .00050000 .00800500 03400000
+ # rs1_a0 @File_Print_Done beqz ; Stop at NULL
+ .00000500 @File_Print_Done 63000000
+
+ # rd_ra $fputc jal ; print it
+ .80000000 $fputc 6F000000
+ # rd_a1 rs1_a1 !1 addi ; S = S + 1
+ .80050000 .00800500 .00001000 13000000
+ # $File_Print_Loop jal ; Keep printing
+ $File_Print_Loop 6F000000
+
+:File_Print_Done
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a1 rs1_sp !4 lw ; restore a1
+ .80050000 .00000100 .00004000 03200000
+ # rd_a2 rs1_sp !8 lw ; restore a2
+ .00060000 .00000100 .00008000 03200000
+ # rd_sp rs1_sp !12 addi ; deallocate stack
+ .00010000 .00000100 .0000C000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; fgetc function
+; Loads FILE* from s2
+; Returns -4 (EOF) or char in a0
+:fgetc
+ # rd_sp rs1_sp !-16 addi ; allocate stack
+ .00010000 .00000100 .000000FF 13000000
+ # rs1_sp rs2_ra @4 sw ; protect ra
+ .00000100 .00001000 .00020000 23200000
+ # rs1_sp rs2_a1 @8 sw ; protect a1
+ .00000100 .0000B000 .00040000 23200000
+ # rs1_sp rs2_a2 @12 sw ; protect a2
+ .00000100 .0000C000 .00060000 23200000
+
+ # rd_a7 !63 addi ; sys_read
+ .80080000 .0000F003 13000000
+ # rd_a1 rs1_sp mv ; Get stack address for buffer
+ .80050000 .00000100 13000000
+ # rd_a0 rs1_s2 mv ; read from input file
+ .00050000 .00000900 13000000
+ # rd_a2 !1 addi ; read 1 character
+ .00060000 .00001000 13000000
+ # ecall ; syscall
+ 73000000
+
+ # rs1_a0 @fgetc_done bnez ; Check if nothing was read
+ .00000500 @fgetc_done 63100000
+ # rd_a2 !-4 addi ; Use -4 as EOF
+ .00060000 .0000C0FF 13000000
+ # rs1_a1 rs2_a2 sb ; Store EOF in *a1
+ .00800500 .0000C000 23000000
+
+:fgetc_done
+ # rd_a0 rs1_a1 lb ; return char in a0
+ .00050000 .00800500 03000000
+ # rd_ra rs1_sp !4 lw ; restore ra
+ .80000000 .00000100 .00004000 03200000
+ # rd_a1 rs1_sp !8 lw ; restore a1
+ .80050000 .00000100 .00008000 03200000
+ # rd_a2 rs1_sp !12 lw ; restore a2
+ .00060000 .00000100 .0000C000 03200000
+ # rd_sp rs1_sp !16 addi ; deallocate stack
+ .00010000 .00000100 .00000001 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; Malloc isn't actually required if the program being built fits in the initial memory
+; However, it doesn't take much to add it.
+; Requires MALLOC pointer to be initialized and a0 to have the number of desired bytes
+:malloc
+ # rd_sp rs1_sp !-8 addi ; allocate stack
+ .00010000 .00000100 .000080FF 13000000
+ # rs1_sp rs2_ra sw ; protect ra
+ .00000100 .00001000 23200000
+ # rs1_sp rs2_a1 @4 sw ; protect a1
+ .00000100 .0000B000 .00020000 23200000
+
+ # rd_a1 rs1_s1 mv ; Store the current pointer
+ .80050000 .00800400 13000000
+ # rd_a0 rs1_a0 rs2_s1 add ; Request the number of desired bytes
+ .00050000 .00000500 .00009000 33000000
+ # rd_a7 !214 addi ; sys_brk
+ .80080000 .0000600D 13000000
+ # ecall ; syscall
+ 73000000
+ # rd_s1 rs1_a0 mv ; Set our malloc pointer
+ .80040000 .00000500 13000000
+ # rd_a0 rs1_a1 mv ; Return the pointer
+ .00050000 .00800500 13000000
+
+ # rd_ra rs1_sp lw ; restore ra
+ .80000000 .00000100 03200000
+ # rd_a1 rs1_sp !4 lw ; restore a1
+ .80050000 .00000100 .00004000 03200000
+ # rd_sp rs1_sp !8 addi ; deallocate stack
+ .00010000 .00000100 .00008000 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+; fputc function
+; receives CHAR in a0 and load FILE* from s3
+; writes char and returns
+:fputc
+ # rd_sp rs1_sp !-16 addi ; allocate stack
+ .00010000 .00000100 .000000FF 13000000
+ # rs1_sp rs2_a0 sw ; protect a0
+ .00000100 .0000A000 23200000
+ # rs1_sp rs2_ra @4 sw ; protect ra
+ .00000100 .00001000 .00020000 23200000
+ # rs1_sp rs2_a1 @8 sw ; protect a1
+ .00000100 .0000B000 .00040000 23200000
+ # rs1_sp rs2_a2 @12 sw ; protect a2
+ .00000100 .0000C000 .00060000 23200000
+
+ # rd_a7 !64 addi ; sys_write
+ .80080000 .00000004 13000000
+ # rd_a0 rs1_s3 mv ; write to output
+ .00050000 .00800900 13000000
+ # rd_a1 rs1_sp mv ; Get stack address
+ .80050000 .00000100 13000000
+ # rd_a2 !1 addi ; write 1 character
+ .00060000 .00001000 13000000
+ # ecall ; syscall
+ 73000000
+
+ # rd_a0 rs1_sp lw ; restore a0
+ .00050000 .00000100 03200000
+ # rd_ra rs1_sp !4 lw ; restore ra
+ .80000000 .00000100 .00004000 03200000
+ # rd_a1 rs1_sp !8 lw ; restore a1
+ .80050000 .00000100 .00008000 03200000
+ # rd_a2 rs1_sp !12 lw ; restore a2
+ .00060000 .00000100 .0000C000 03200000
+ # rd_sp rs1_sp !16 addi ; deallocate stack
+ .00010000 .00000100 .00000001 13000000
+ # rs1_ra jalr ; return
+ .00800000 67000000
+
+
+:Fail
+ ; Terminate program with 1 return code
+ # rd_a7 !93 addi ; sys_exit
+ .80080000 .0000D005 13000000
+ # rd_a0 !1 addi ; Return code 1
+ .00050000 .00001000 13000000
+ # ecall ; exit(1)
+ 73000000
+
+; PROGRAM END
+
+:terminators
+# "
+# "
+0A 09 20 00
+
+:comments
+# "#;"
+23 3B 00
+
+:string_char
+# '22 27 00'
+22 27 00
+
+:DEFINE_str
+# "DEFINE"
+44 45 46 49 4E 45 00
+
+:ELF_end
diff --git a/vendor/seed/riscv32/catm.hex2 b/vendor/seed/riscv32/catm.hex2
@@ -0,0 +1,171 @@
+## Copyright (C) 2022 Andrius Štikonas
+## This file is part of stage0.
+##
+## stage0 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.
+##
+## stage0 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 stage0. If not, see <http://www.gnu.org/licenses/>.
+
+
+## ELF Header
+
+:ELF_base
+7F 45 4C 46 ## e_ident[EI_MAG0-3] ELF's magic number
+
+01 ## e_ident[EI_CLASS] Indicating 32 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] Set at 0 because none cares
+
+00 00 00 00 00 00 00 ## e_ident[EI_PAD]
+02 00 ## e_type Indicating Executable
+F3 00 ## e_machine Indicating RISC-V
+01 00 00 00 ## e_version Indicating original elf
+
+&_start ## e_entry Address of the entry point (Number of bytes this header is + Base Address)
+%ELF_program_headers>ELF_base ## e_phoff Address of program header table
+00 00 00 00 ## e_shoff Address of section header table
+
+00 00 00 00 ## e_flags
+34 00 ## e_ehsize Indicating our 52 Byte header
+
+20 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
+
+## Program Header
+:ELF_program_headers
+01 00 00 00 ## ph_type: PT-LOAD = 1
+00 00 00 00 ## p_offset
+
+&ELF_base ## ph_vaddr
+&ELF_base ## ph_physaddr
+
+%ELF_end>ELF_base ## p_filesz
+%ELF_end>ELF_base ## p_memsz
+
+07 00 00 00 ## ph_flags: PF-X|PF-W|PF-R = 7
+01 00 00 00 ## ph_align
+
+:ELF_text
+
+; Simply jump to _start
+; Our main function
+:_start
+
+# Register use:
+# s1: output file descriptor
+# s2: buffer
+# s3: input file descriptor
+# s4: number of bytes read
+
+# Open output file and store the FD in s1
+ # rd_a7 !56 addi ; sys_openat
+ .80080000 .00008003 13000000
+ # rd_a0 !-100 addi ; AT_FDCWD
+ .00050000 .0000C0F9 13000000
+ # rd_sp rs1_sp !8 addi ; Prepare stack for reading output file
+ .00010000 .00000100 .00008000 13000000
+ # rd_a1 rs1_sp lw ; Output file (argument 1)
+ .80050000 .00000100 03200000
+ # rd_a2 !577 addi ; Prepare file as O_WRONLY|O_CREAT|O_TRUNC
+ .00060000 .00001024 13000000
+ # rd_a3 !384 addi ; Prepare file as RW for owner only (600 in octal)
+ .80060000 .00000018 13000000
+ # ecall ; syscall
+ 73000000
+ # rd_s1 rs1_a0 addi ; Save fd in for later
+ .80040000 .00000500 13000000
+
+ # Prepare heap memory
+ # rd_a7 !214 addi ; sys_brk
+ .80080000 .0000600D 13000000
+ # rd_a0 addi ; Get current brk
+ .00050000 13000000
+ # ecall ; syscall
+ 73000000
+ # rd_s2 rs1_a0 addi ; Set our malloc pointer
+ .00090000 .00000500 13000000
+
+ # rd_a1 ~0x100000 lui ; a1=1MiB
+ .80050000 .00001000 37000000
+ # rd_a0 rs1_a0 rs2_a1 add ; Allocate 1MiB
+ .00050000 .00000500 .0000B000 33000000
+ # ecall ; syscall
+ 73000000
+
+:core
+ # rd_sp rs1_sp !4 addi ; Move stack pointer to next input file
+ .00010000 .00000100 .00004000 13000000
+ # rd_a1 rs1_sp lw ; Get the input file name
+ .80050000 .00000100 03200000
+ # rs1_a1 @Done beq ; This was the last file, we are done
+ .00800500 @Done 63000000
+
+ # rd_a7 !56 addi ; sys_openat
+ .80080000 .00008003 13000000
+ # rd_a0 !-100 addi ; AT_FDCWD
+ .00050000 .0000C0F9 13000000
+ # rd_a2 addi ; read only
+ .00060000 13000000
+ # ecall ; syscall
+ 73000000
+ # rd_s3 rs1_a0 addi ; protect input fd
+ .80090000 .00000500 13000000
+
+:keep
+ # rd_a7 !63 addi ; sys_read
+ .80080000 .0000F003 13000000
+ # rd_a0 rs1_s3 addi ; input fd
+ .00050000 .00800900 13000000
+ # rd_a1 rs1_s2 addi ; read into buffer
+ .80050000 .00000900 13000000
+ # rd_a2 ~0x100000 lui ; a2=1MiB
+ .00060000 .00001000 37000000
+ # ecall ; syscall
+ 73000000
+ # rd_s4 rs1_a0 addi ; actual number of bytes read
+ .000A0000 .00000500 13000000
+
+ # rd_a7 !64 addi ; sys_write
+ .80080000 .00000004 13000000
+ # rd_a0 rs1_s1 addi ; output fd
+ .00050000 .00800400 13000000
+ # rd_a1 rs1_s2 addi ; write from buffer
+ .80050000 .00000900 13000000
+ # rd_a2 rs1_s4 addi ; number of bytes to write
+ .00060000 .00000A00 13000000
+ # ecall ; syscall
+ 73000000
+
+ # rd_a2 ~0x100000 lui ; 1MiB
+ .00060000 .00001000 37000000
+ # rs1_s4 rs2_a2 @keep beq ; keep looping if buffer was full
+ .00000A00 .0000C000 @keep 63000000
+ # $core jal ; otherwise move to next file
+ $core 6F000000
+
+:Done
+ # Terminate program with 0 return code
+ # rd_a7 !93 addi ; sys_exit
+ .80080000 .0000D005 13000000
+ # rd_a0 addi ; Return code 0
+ .00050000 13000000
+ # ecall ; exit(0)
+ 73000000
+
+:ELF_end
diff --git a/vendor/seed/riscv32/hex0-seed b/vendor/seed/riscv32/hex0-seed
Binary files differ.
diff --git a/vendor/seed/riscv32/hex0.hex0 b/vendor/seed/riscv32/hex0.hex0
@@ -0,0 +1,213 @@
+## Copyright (C) 2021 Ekaitz Zarraga
+## Copyright (C) 2021 Andrius Štikonas
+## Copyright (C) 2021,2022 Gabriel Wicki
+## This file is part of stage0.
+##
+## stage0 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.
+##
+## stage0 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 stage0. If not, see <http://www.gnu.org/licenses/>.
+
+; Register use:
+; s2: input fd
+; s3: output fd
+; s4: toggle
+; s5: hold
+
+; Uses top of the stack as i/o buffer
+
+## ELF Header
+#:ELF_base
+7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number
+
+01 # e_ident[EI_CLASS] Indicating 32 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 for Linux
+00 # e_ident[EI_ABIVERSION] Ignored for Statically linked executables
+
+00 00 00 00 00 00 00 # e_ident[EI_PAD]
+02 00 # e_type Indicating Executable
+F3 00 # e_machine Indicating RISC-V
+01 00 00 00 # e_version Indicating original elf
+
+54 00 60 00 # e_entry Address of the entry point
+34 00 00 00 # e_phoff Address of program header table
+00 00 00 00 # e_shoff Address of section header table
+
+00 00 00 00 # e_flags
+
+34 00 # e_ehsize Indicating our 52 Byte header
+
+20 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
+
+## Program Header
+#:ELF_program_headers
+#:ELF_program_header__text
+01 00 00 00 # ph_type: PT-LOAD = 1
+00 00 00 00 # ph_offset
+
+00 00 60 00 # ph_vaddr
+00 00 60 00 # ph_physaddr
+
+64 01 00 00 # ph_filesz
+64 01 00 00 # ph_memsz
+
+07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7
+01 00 00 00 # ph_align
+
+# :_start ; (0x0600054)
+ 13 0A 00 00 # rd_s4 mv ; Initialize register
+ 83 25 81 00 # rd_a1 rs1_sp !8 lw ; Input file name
+
+ ; Open input file and store FD in s2
+ 93 08 80 03 # rd_a7 !56 addi ; sys_openat
+ 13 05 C0 F9 # rd_a0 !-100 addi ; AT_FDCWD
+ 13 06 00 00 # rd_a2 mv ; read only
+ 73 00 00 00 # ecall
+ 13 09 05 00 # rd_s2 rs1_a0 mv ; Save fd in s2 for later
+
+ ; Open output file and store the FD in s3
+ 13 05 C0 F9 # rd_a0 !-100 addi ; AT_FDCWD
+ 83 25 C1 00 # rd_a1 rs1_sp !12 lw ; Output file (argument 3)
+ 13 06 10 24 # rd_a2 !577 addi ; octal 00001101
+ ; O_TRUNC 00001000
+ ; O_CREAT 00000100
+ ; O_WRONLY 00000001
+ ; OCTAL!
+
+ 93 06 00 1C # rd_a3 !448 addi ; Set read, write, execute permission on user
+ ; S_IRWXU 00700
+ ; OCTAL!
+
+ 73 00 00 00 # ecall
+ 93 09 05 00 # rd_s3 rs1_a0 mv ; Save fd in s3 for later
+
+# :next_byte ; (0x0600088)
+ 93 08 F0 03 # rd_a7 !63 addi ; sys_read
+ 13 05 09 00 # rd_a0 rs1_s2 mv ; File descriptor
+ 93 05 01 00 # rd_a1 rs1_sp mv ; Buffer
+ 13 06 10 00 # rd_a2 !1 addi ; Size of what we want to read (set for all subsequent syscalls)
+ 73 00 00 00 # ecall
+
+ ; If the file ended (0 bytes read) terminate
+ 63 00 05 0C # rs1_a0 @terminate beqz
+ # +192B
+
+ ; Check if it's a comment
+ 03 05 01 00 # rd_a0 rs1_sp lb
+ 93 02 30 02 # rd_t0 !0x23 addi
+ 63 08 55 00 # rs1_a0 rs2_t0 @loop beq ; a0 eq to '#'
+ # +16B
+ 93 02 B0 03 # rd_t0 !0x3B addi
+ 63 04 55 00 # rs1_a0 rs2_t0 @loop beq ; a0 eq to ';'
+ # +8B
+ 6F 00 80 02 # $not_comment jal
+ # +40B
+# :loop ; (0x06000B8)
+ 13 05 09 00 # rd_a0 rs1_s2 mv ; File descriptor
+ 73 00 00 00 # ecall ; sys_read
+
+ ; If the file ended (0 bytes read) terminate
+ 63 0E 05 08 # rs1_a0 @terminate beqz
+ # +156B
+ ; Check if read byte is the end of the comment (i.e. a newline character),
+ ; in that case we continue processing
+ 03 05 01 00 # rd_a0 rs1_sp lb
+ 93 02 A0 00 # rd_t0 !0xA addi
+ E3 0E 55 FA # rs1_a0 rs2_t0 @next_byte beq ; a0 eq to \n
+ # -68B
+ 93 02 D0 00 # rd_t0 !0xD addi
+ E3 0A 55 FA # rs1_a0 rs2_t0 @next_byte beq ; a0 eq to \r
+ # -76B
+ 6F F0 1F FE # $loop jal
+# :not_comment ; (0x06000DC)
+ ; Check if it's a hex character:
+ ; in the case it's not, ignores and reads next byte
+ 03 05 01 00 # rd_a0 rs1_sp lb
+
+ ; Is it between '0' and '9'?
+ 93 02 00 03 # rd_t0 !48 addi ; '0' character
+ 63 4A 55 00 # rs1_a0 rs2_t0 @uppercase_alpha blt
+ # +20B
+ 93 02 90 03 # rd_t0 !57 addi ; '9' character
+ 63 C6 A2 00 # rs1_t0 rs2_a0 @uppercase_alpha blt
+ # +12B
+ 13 05 05 FD # rd_a0 rs1_a0 !-48 addi
+ 6F 00 00 03 # $hex_read jal
+ # +48B
+# :uppercase_alpha ; (0x06000F8)
+ ; Is it between 'A' and 'F'?
+ 93 02 10 04 # rd_t0 !65 addi ; 'A' character
+ 63 4A 55 00 # rs1_a0 rs2_t0 @lowercase_alpha blt
+ # +20B
+ 93 02 60 04 # rd_t0 !70 addi ; 'F' character
+ 63 C6 A2 00 # rs1_t0 rs2_a0 @lowercase_alpha blt
+ # +12B
+ 13 05 95 FC # rd_a0 rs1_a0 !-55 addi
+ 6F 00 80 01 # $hex_read jal
+ # +24B
+# :lowercase_alpha ; (0x0600110)
+ ; Is it between 'a' and 'f'?
+ 93 02 10 06 # rd_t0 !97 addi ; 'a' character
+ E3 4A 55 F6 # rs1_a0 rs2_t0 @next_byte blt ; Not hex, continue reading
+ # -140B
+ 93 02 60 06 # rd_t0 !102 addi ; 'f' character
+ E3 C6 A2 F6 # rs1_t0 rs2_a0 @next_byte blt ; Not hex, continue reading
+ # -148B
+ 13 05 95 FA # rd_a0 rs1_a0 !-87 addi
+# :hex_read ; (0x0600124)
+ ; END check hex -- leaves the half byte in a0
+
+ 63 18 0A 00 # rs1_s4 @combine bnez ; if toggle != 0 -> combine
+ # +16B
+ ; Toggle == 0, we need to prepare for later
+ 93 0A 05 00 # rd_s5 rs1_a0 mv ; Load hold
+
+ 13 0A 10 00 # rd_s4 !1 addi ; Set toggle
+ 6F F0 9F F5 # $next_byte jal ; Read next byte
+ # -168B
+
+# :combine ; (0x0600134)
+ ; Combine half bytes
+ 93 95 4A 00 # rd_a1 rs1_s5 rs2_x4 slli ; Shift logical left 4 times
+ 33 05 B5 00 # rd_a0 rs1_a0 rs2_a1 add ; Combine two half bytes
+ ; Leaves the full byte in a0
+
+ ; Values are combined, now we write in the file
+ 23 00 A1 00 # rs1_sp rs2_a0 sb ; Store prepared byte in buffer
+ 93 08 00 04 # rd_a7 !64 addi ; sys_write
+ 13 85 09 00 # rd_a0 rs1_s3 mv ; file descriptor (stdout)
+ 93 05 01 00 # rd_a1 rs1_sp mv ; string address
+ 73 00 00 00 # ecall
+
+ ; Update globals
+ 13 0A 00 00 # rd_s4 mv ; Clear toggle
+ 93 0A 00 00 # rd_s5 mv ; Clear hold
+
+ ; Read next byte
+ 6F F0 1F F3 # $next_byte jal
+ # -208B
+
+# :terminate ; (0x60015C)
+ ; Terminate program with 0 return code
+ 93 08 D0 05 # rd_a7 !93 addi ; sys_exit
+ 73 00 00 00 # ecall
+# PROGRAM END
+
+# :ELF_end ; (0x600164)
diff --git a/vendor/seed/riscv32/hex1.hex0 b/vendor/seed/riscv32/hex1.hex0
@@ -0,0 +1,648 @@
+## Copyright (C) 2021 Andrius Štikonas
+## This file is part of stage0.
+##
+## stage0 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.
+##
+## stage0 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 stage0. If not, see <http://www.gnu.org/licenses/>.
+
+## ELF Header
+#:ELF_base
+7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number
+
+01 # e_ident[EI_CLASS] Indicating 32 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 for Linux
+00 # e_ident[EI_ABIVERSION] Ignored for Statically linked executables
+
+00 00 00 00 00 00 00 # e_ident[EI_PAD]
+02 00 # e_type Indicating Executable
+F3 00 # e_machine Indicating RISC-V
+01 00 00 00 # e_version Indicating original elf
+
+54 00 60 00 # e_entry Address of the entry point
+34 00 00 00 # e_phoff Address of program header table
+00 00 00 00 # e_shoff Address of section header table
+
+00 00 00 00 # e_flags
+
+34 00 # e_ehsize Indicating our 52 Byte header
+
+20 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
+
+## Program Header
+#:ELF_program_headers
+#:ELF_program_header__text
+01 00 00 00 # ph_type: PT-LOAD = 1
+00 00 00 00 # ph_offset
+
+00 00 60 00 # ph_vaddr
+00 00 60 00 # ph_physaddr
+
+A5 04 00 00 # ph_filesz
+A5 04 00 00 # ph_memsz
+
+07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7
+01 00 00 00 # ph_align
+
+; Register use:
+; s2: input fd
+; s3: output fd
+; s4: toggle
+; s5: hold
+; s6: ip
+; s7: tempword
+; s8: shiftregister
+
+; Our main function
+# :_start ; (0x0600054)
+
+ 03 26 81 00 # rd_a2 rs1_sp !8 lw ; Input file name
+
+ ; Initialize globals
+ 13 0A F0 FF # rd_s4 !-1 addi ; Toggle
+ 93 0A 00 00 # rd_s5 addi ; Hold
+ 13 0B 00 00 # rd_s6 addi ; Instruction Pointer
+
+ ; Open input file and store FD in s2
+ 93 08 80 03 # rd_a7 !56 addi ; sys_openat
+ 13 05 C0 F9 # rd_a0 !-100 addi ; AT_FDCWD
+ 93 05 06 00 # rd_a1 rs1_a2 mv ; file name
+ 13 06 00 00 # rd_a2 addi ; read only
+ 73 00 00 00 # ecall ; syscall
+ 63 40 05 42 # rs1_a0 @Fail bltz ; Error opening file
+ # +1056
+ 13 09 05 00 # rd_s2 rs1_a0 mv ; Save fd in for later
+
+ ; Set default FD for output file to stdout
+ 93 09 10 00 # rd_s3 !1 addi
+
+ ; If we only have 2 arguments, don't use the third (it's not set)
+ 93 02 20 00 # rd_t0 !2 addi
+ 03 25 01 00 # rd_a0 rs1_sp lw ; Get number of the args
+ 63 46 55 40 # rs1_a0 rs2_t0 @Fail blt ; No input file provided
+ # +1036B
+ 63 00 55 02 # rs1_a0 rs2_t0 @after_open beq ; No output file provided. Use stdout
+ # +32B
+
+ ; Open output file and store the FD in s3
+ 93 08 80 03 # rd_a7 !56 addi ; sys_openat
+ 13 05 C0 F9 # rd_a0 !-100 addi ; AT_FDCWD
+ 83 25 C1 00 # rd_a1 rs1_sp !12 lw ; Output file (argument 3)
+ 13 06 10 24 # rd_a2 !00001101 addi ; decimal 577
+ ; O_TRUNC 00001000
+ ; O_CREAT 00000100
+ ; O_WRONLY 00000001
+ ; OCTAL!
+ 93 06 00 1C # rd_a3 !00700 addi ; Set read, write, execute permission on user
+ ; S_IRWXU 00700
+ ; OCTAL!
+ 73 00 00 00 # ecall ; syscall
+ 93 09 05 00 # rd_s3 rs1_a0 mv ; Save fd in for later
+
+# :after_open ; (0x06000B0)
+ EF 00 C0 03 # rd_ra $First_pass jal ; First pass
+
+ ; Rewind input file
+ 93 08 E0 03 # rd_a7 !62 addi ; sys_llseek
+ 13 05 09 00 # rd_a0 rs1_s2 mv ; Input file descriptor
+ 93 05 00 00 # rd_a1 mv ; Set offset to zero
+ 13 06 00 00 # rd_a2 mv ; Set offset to zero
+ 93 06 00 00 # rd_a3 mv ; Set result pointer to zero
+ 13 07 00 00 # rd_a4 mv ; Set whence to zero
+ 73 00 00 00 # ecall ; syscall
+
+ ; Initialize globals
+ 13 0A F0 FF # rd_s4 !-1 addi ; Toggle
+ 93 0A 00 00 # rd_s5 addi ; Hold
+ 13 0B 00 00 # rd_s6 addi ; Instruction Pointer
+ 93 0B 00 00 # rd_s7 addi ; tempword
+ 13 0C 00 00 # rd_s8 addi ; Shift register
+
+ EF 00 00 07 # rd_ra $Second_pass jal ; Now do the second pass
+ # +112B
+
+ 6F 00 40 3A # $Done jal ; We are done
+ # +392B
+
+; First pass loop to determine addresses of labels
+# :First_pass ; (0x06000EC)
+ 13 01 C1 FF # rd_sp rs1_sp !-4 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+
+# :First_pass_loop ; (0x06000F4)
+ EF 00 C0 2D # rd_ra $Read_byte jal ; Get another byte
+ # +732B
+
+ ; Deal with EOF
+ 13 03 C0 FF # rd_t1 !-4 addi
+ 63 06 65 04 # rs1_a0 rs2_t1 @First_pass_done beq
+ # +76B
+
+ ; Check for :
+ 13 03 A0 03 # rd_t1 !0x3A addi
+ 63 14 65 00 # rs1_a0 rs2_t1 @First_pass_0 bne
+ # +8B
+ EF 00 C0 32 # rd_ra $StoreLabel jal ; Store this label
+ # +812B
+
+# :First_pass_0 ; (0x060010C)
+ ; Check for !
+ 13 03 10 02 # rd_t1 !0x21 addi
+ 63 08 65 02 # rs1_a0 rs2_t1 @Throwaway_token beq
+ # +48B
+
+ ; Check for @
+ 13 03 00 04 # rd_t1 !0x40 addi
+ 63 04 65 02 # rs1_a0 rs2_t1 @Throwaway_token beq
+ # +40B
+
+ ; Check for $
+ 13 03 40 02 # rd_t1 !0x24 addi
+ 63 00 65 02 # rs1_a0 rs2_t1 @Throwaway_token beq
+ # +32B
+
+ ; Check for ~
+ 13 03 E0 07 # rd_t1 !0x7E addi
+ 63 0C 65 00 # rs1_a0 rs2_t1 @Throwaway_token beq
+ # +24B
+
+ 93 05 F0 FF # rd_a1 !-1 addi ; write = false
+ EF 00 C0 19 # rd_ra $DoByte jal ; Deal with everything else
+ # +412B
+
+ 13 03 C0 FF # rd_t1 !-4 addi ; Deal with EOF
+ 63 08 65 00 # rs1_a0 rs2_t1 @First_pass_done beq
+ # +16B
+
+ 6F F0 9F FB # $First_pass_loop jal ; Keep looping
+ # -72B
+
+# :Throwaway_token ; (0x0600140)
+ ; Deal with Pointer to label
+ EF 00 00 29 # rd_ra $Read_byte jal ; Drop the char
+ # +656B
+ 6F F0 1F FB # $First_pass_loop jal ; Loop again
+ # -80B
+
+# :First_pass_done ; (0x0600148)
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 13 01 41 00 # rd_sp rs1_sp !4 addi ; deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+# :Second_pass ; (0x0600154)
+ 13 01 C1 FF # rd_sp rs1_sp !-4 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+
+# :Second_pass_loop ; (0x060015C)
+ EF 00 40 27 # rd_ra $Read_byte jal ; Read another byte
+ # +628B
+
+ ; Deal with EOF
+ 13 03 C0 FF # rd_t1 !-4 addi ; Deal with EOF
+ 63 0E 65 14 # rs1_a0 rs2_t1 @Second_pass_done beq
+ # +348B
+
+ ; Drop the label
+ 13 03 A0 03 # rd_t1 !0x3A addi
+ 63 16 65 00 # rs1_a0 rs2_t1 @Second_pass_0 bne
+ # +12B
+
+ EF 00 00 26 # rd_ra $Read_byte jal ; Read the label
+ # +608B
+ 6F F0 9F FE # $Second_pass_loop jal ; Continue looping
+ # -24B
+
+# :Second_pass_0 ; (0x0600178)
+ ; Check for !
+ 13 03 10 02 # rd_t1 !0x21 addi
+ 63 08 65 02 # rs1_a0 rs2_t1 @UpdateShiftRegister beq
+ # +48B
+
+ ; Check for @
+ 13 03 00 04 # rd_t1 !0x40 addi
+ 63 04 65 02 # rs1_a0 rs2_t1 @UpdateShiftRegister beq
+ # +40B
+
+ ; Check for $
+ 13 03 40 02 # rd_t1 !0x24 addi
+ 63 00 65 02 # rs1_a0 rs2_t1 @UpdateShiftRegister beq
+ # +32B
+
+ ; Check for ~
+ 13 03 E0 07 # rd_t1 !0x7E addi
+ 63 0C 65 00 # rs1_a0 rs2_t1 @UpdateShiftRegister beq
+ # +24B
+
+ ; Deal with everything else
+ 93 05 00 00 # rd_a1 mv ; write = true
+ EF 00 00 13 # rd_ra $DoByte jal ; Process our char
+ # +304B
+
+ # Deal with EOF
+ 13 03 C0 FF # rd_t1 !-4 addi
+ 63 0E 65 10 # rs1_a0 rs2_t1 @Second_pass_done beq ; We are done
+ # +284B
+
+ 6F F0 5F FB # $Second_pass_loop jal ; continue looping
+ # -76B
+
+# :UpdateShiftRegister ; (0x06001B0)
+ 93 05 05 00 # rd_a1 rs1_a0 mv ; Store label prefix
+ EF 00 C0 25 # rd_ra $Get_table_target jal ; Get target
+ # +604B
+ 03 25 05 00 # rd_a0 rs1_a0 lw ; Dereference pointer
+ 33 05 65 41 # rd_a0 rs1_a0 rs2_s6 sub ; target - ip
+
+ ; Check for !
+ 13 03 10 02 # rd_t1 !0x21 addi
+ 63 80 65 02 # rs1_a1 rs2_t1 @UpdateShiftRegister_I beq
+ # +32B
+
+ ; Check for @
+ 13 03 00 04 # rd_t1 !0x40 addi
+ 63 8A 65 02 # rs1_a1 rs2_t1 @UpdateShiftRegister_B beq
+ # +52B
+
+ ; Check for $
+ 13 03 40 02 # rd_t1 !0x24 addi
+ 63 8A 65 06 # rs1_a1 rs2_t1 @UpdateShiftRegister_J beq
+ # +116B
+
+ ; Check for ~
+ 13 03 E0 07 # rd_t1 !0x7E addi
+ 63 88 65 0A # rs1_a1 rs2_t1 @UpdateShiftRegister_U beq
+ # +176B
+
+ 6F F0 1F F8 # $Second_pass_loop jal ; continue looping
+ # -128B
+
+# :UpdateShiftRegister_I ; (0x06001E0)
+ ; Corresponds to RISC-V I format
+ 13 05 45 00 # rd_a0 rs1_a0 !4 addi ; add 4 due to this being 2nd part of auipc combo
+
+ 37 13 00 00 # rd_t1 ~0xFFF lui ; load higher bits
+ 13 03 F3 FF # rd_t1 rs1_t1 !0xFFF addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; (value & 0xfff)
+ 93 1B 43 01 # rd_s7 rs1_t1 rs2_x20 slli ; tempword = (value & 0xfff) << 20
+ 33 4C 7C 01 # rd_s8 rs1_s8 rs2_s7 xor ; shiftregister = shiftregister ^ tempword
+
+ 6F F0 5F F6 # $Second_pass_loop jal ; continue looping
+ # -156B
+
+# :UpdateShiftRegister_B ; (0x06001FC)
+ ; Corresponds to RISC-V B format
+
+ ; tempword = ((value & 0x1e) << 7) ; imm[4:1]
+ ; | ((value & 0x7e0) << (31 - 11)) ; imm[10:5]
+ ; | ((value & 0x800) >> 4) ; imm[11]
+ ; | ((value & 0x1000) << (31 - 12)) ; imm[12]
+
+ 13 03 E0 01 # rd_t1 !0x1E addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x1e
+ 93 12 73 00 # rd_t0 rs1_t1 rs2_x7 slli ; tempword = (value & 0x1e) << 7
+
+ 13 03 00 7E # rd_t1 !0x7E0 addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x7e0
+ 13 13 43 01 # rd_t1 rs1_t1 rs2_x20 slli ; (value & 0x7e0) << (31 - 11)
+ B3 E2 62 00 # rd_t0 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 37 13 00 00 # rd_t1 ~0x800 lui ; load higher bits
+ 13 03 03 80 # rd_t1 rs1_t1 !0x800 addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x800
+ 13 53 43 00 # rd_t1 rs1_t1 rs2_x4 srli ; (value & 0x800) >> 4
+ B3 E2 62 00 # rd_t0 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 37 13 00 00 # rd_t1 ~0x1000 lui ; load higher bits
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x1000
+ 13 13 33 01 # rd_t1 rs1_t1 rs2_x19 slli ; (value & 0x1000) << (31 - 12)
+ B3 EB 62 00 # rd_s7 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 33 4C 7C 01 # rd_s8 rs1_s8 rs2_s7 xor ; shiftregister = shiftregister ^ tempword
+
+ 6F F0 DF F1 # $Second_pass_loop jal ; continue looping
+ # -228B
+
+# :UpdateShiftRegister_J ; (0x0600244)
+ ; Corresponds to RISC-V J format
+
+ ; tempword = ((value & 0x7fe) << (30 - 10)) ; imm[10:1]
+ ; | ((value & 0x800) << (20 - 11)) ; imm[11]
+ ; | ((value & 0xff000)) ; imm[19:12]
+ ; | ((value & 0x100000) << (31 - 20)) ; imm[20]
+
+ 13 03 E0 7F # rd_t1 !0x7FE addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x7fe
+ 93 12 43 01 # rd_t0 rs1_t1 rs2_x20 slli ; tempword = (value & 0x7fe) << 20
+
+ 37 13 00 00 # rd_t1 ~0x800 lui ; load higher bits
+ 13 03 03 80 # rd_t1 rs1_t1 !0x800 addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x800
+ 13 13 93 00 # rd_t1 rs1_t1 rs2_x9 slli ; (value & 0x800) << (20 - 11)
+ B3 E2 62 00 # rd_t0 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 37 F3 0F 00 # rd_t1 ~0xFF000 lui ; load higher bits
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0xff000
+ B3 E2 62 00 # rd_t0 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 37 03 10 00 # rd_t1 ~0x100000 lui ; load higher bits
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x100000
+ 13 13 B3 00 # rd_t1 rs1_t1 rs2_x11 slli ; (value & 0x100000) << (31 - 20)
+ B3 EB 62 00 # rd_s7 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 33 4C 7C 01 # rd_s8 rs1_s8 rs2_s7 xor ; shiftregister = shiftregister ^ tempword
+
+ 6F F0 9F ED # $Second_pass_loop jal ; continue looping
+ # -296B
+
+# :UpdateShiftRegister_U ; (0x0600288)
+ ; Corresponds to RISC-V U format
+ ; if value is 0x800 or more we have to add 11-th bit (0x1000) to compensate for signed extension
+
+ B7 12 00 00 # rd_t0 ~0x800 lui ; load higher bits
+ 93 82 02 80 # rd_t0 rs1_t0 !0x800 addi
+ 37 13 00 00 # rd_t1 ~0xFFF lui ; load higher bits
+ 13 03 F3 FF # rd_t1 rs1_t1 !0xFFF addi
+
+ ; We are outside 31-bit that ~ can normally load
+ B7 03 10 00 # rd_t2 ~0x100000 lui ; load 0xfffff000
+ 93 83 F3 FF # rd_t2 rs1_t2 !-1 addi ; load 0xfffff000
+ 93 93 C3 00 # rd_t2 rs1_t2 rs2_x12 slli ; load 0xfffff000
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0xfff
+ B3 7B 75 00 # rd_s7 rs1_a0 rs2_t2 and ; value & 0xfffff000
+ 63 46 53 00 # rs1_t1 rs2_t0 @UpdateShiftRegister_U_small blt
+ # +12B
+
+ # Deal with sign extension: add 0x1000
+ B7 12 00 00 # rd_t0 ~0x1000 lui ; load higher bits
+ B3 8B 72 01 # rd_s7 rs1_t0 rs2_s7 add ; (value & 0xfffff000) + 0x1000
+
+# :UpdateShiftRegister_U_small ; (0x06002B8)
+ 33 4C 7C 01 # rd_s8 rs1_s8 rs2_s7 xor ; shiftregister = shiftregister ^ tempword
+
+ 6F F0 1F EA # $Second_pass_loop jal ; continue looping
+ # -352B
+
+# :Second_pass_done ; (0x06002C0)
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 13 01 41 00 # rd_sp rs1_sp !4 addi ; deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+
+; DoByte function
+; Receives:
+; character in a0
+; bool write in a1
+; Does not return anything
+# :DoByte ; (0x06002CC)
+ 13 01 C1 FF # rd_sp rs1_sp !-4 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+
+ EF 00 00 05 # rd_ra $hex jal ; Process hex, store it in a6
+ # +80B
+
+ 63 40 08 04 # rs1_a6 @DoByte_Done bltz ; Deal with EOF and unrecognized characters
+ # +64B
+
+ 63 1A 0A 02 # rs1_s4 @DoByte_NotToggle bnez ; Check if toggle is set
+ # +56B
+
+ ; toggle = true
+ 63 92 05 02 # rs1_a1 @DoByte_1 bnez ; check if we have to write
+ # +36B
+
+ ; write = true
+ ; We calculate (hold * 16) + hex(c) ^ sr_nextb()
+ ; First, calculate new shiftregister
+ 93 02 F0 0F # rd_t0 !0xFF addi
+ B3 72 5C 00 # rd_t0 rs1_s8 rs2_t0 and ; sr_nextb = shiftregister & 0xff
+ 13 5C 8C 00 # rd_s8 rs1_s8 rs2_x8 srli ; shiftregister >> 8
+
+ B3 C2 02 01 # rd_t0 rs1_t0 rs2_a6 xor ; hex(c) ^ sr_nextb
+ 13 93 4A 00 # rd_t1 rs1_s5 rs2_x4 slli ; hold << 4
+ 33 85 62 00 # rd_a0 rs1_t0 rs2_t1 add ; (hold << 4) + hex(c) ^ sr_nextb()
+ EF 00 40 15 # rd_ra $fputc jal ; print it
+ # +340B
+ 63 0C 05 18 # rs1_a0 @Fail beqz ; Fail if nothing was written
+ # +408B
+
+# :DoByte_1 ; (0x0600304)
+ 13 0B 1B 00 # rd_s6 rs1_s6 !1 addi ; Increment IP
+ 93 0A 00 00 # rd_s5 mv ; hold = 0
+ 6F 00 80 00 # $DoByte_FlipToggle jal ; return
+ # +8B
+
+# :DoByte_NotToggle ; (0x0600310)
+ 93 0A 08 00 # rd_s5 rs1_a6 mv ; hold = hex(c)
+
+# :DoByte_FlipToggle ; (0x0600314)
+ 13 4A FA FF # rd_s4 rs1_s4 not ; Flip the toggle
+
+# :DoByte_Done ; (0x0600318)
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 13 01 41 00 # rd_sp rs1_sp !4 addi ; deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+; Convert ASCII hex characters into binary representation, e.g. 'a' -> 0xA
+; Receives:
+; character in a0
+; Returns:
+; a6 with character's hex value.
+# :hex ; (0x0600324)
+ 13 01 81 FF # rd_sp rs1_sp !-8 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+ 23 22 B1 00 # rs1_sp rs2_a1 @4 sw ; protect a1
+
+ ; Deal with EOF
+ 13 03 C0 FF # rd_t1 !-4 addi
+ 63 06 65 08 # rs1_a0 rs2_t1 @hex_return beq
+ # +140B
+
+ ; deal with line comments starting with #
+ 13 03 30 02 # rd_t1 !0x23 addi
+ 63 06 65 06 # rs1_a0 rs2_t1 @ascii_comment beq ; a0 eq to '#'
+ # +108B
+
+ ; deal with line comments starting with ;
+ 13 03 B0 03 # rd_t1 !0x3B addi
+ 63 02 65 06 # rs1_a0 rs2_t1 @ascii_comment beq ; a0 eq to ';'
+ # +100B
+
+ ; deal all ascii less than 0
+ 13 03 00 03 # rd_t1 !0x30 addi
+ 63 4A 65 04 # rs1_a0 rs2_t1 @ascii_other blt
+ # +84B
+
+ ; deal with 0-9
+ 13 03 A0 03 # rd_t1 !0x3A addi
+ 63 44 65 02 # rs1_a0 rs2_t1 @ascii_num blt
+ # +40B
+
+ ; deal with all ascii less than A
+ 13 03 10 04 # rd_t1 !0x41 addi
+ 63 42 65 04 # rs1_a0 rs2_t1 @ascii_other blt
+ # +68B
+
+ ; deal with A-F
+ 13 03 70 04 # rd_t1 !0x47 addi
+ 63 48 65 02 # rs1_a0 rs2_t1 @ascii_high blt
+ # +48B
+
+ ; deal with all ascii less than a
+ 13 03 10 06 # rd_t1 !0x61 addi
+ 63 4A 65 02 # rs1_a0 rs2_t1 @ascii_other blt
+ # +52B
+
+ ; deal with a-f
+ 13 03 70 06 # rd_t1 !0x67 addi
+ 63 4A 65 00 # rs1_a0 rs2_t1 @ascii_low blt
+ # +20B
+
+ ; The rest that remains needs to be ignored
+ 6F 00 80 02 # $ascii_other jal
+ # +40B
+
+# :ascii_num ; (0x060037C)
+ 13 03 00 03 # rd_t1 !0x30 addi ; '0' -> 0
+ 33 08 65 40 # rd_a6 rs1_a0 rs2_t1 sub
+ 6F 00 C0 03 # $hex_return jal ; return
+ # +60B
+# :ascii_low ; (0x0600388)
+ 13 03 70 05 # rd_t1 !0x57 addi ; 'a' -> 0xA
+ 33 08 65 40 # rd_a6 rs1_a0 rs2_t1 sub
+ 6F 00 00 03 # $hex_return jal ; return
+ # +48B
+# :ascii_high ; (0x0600394)
+ 13 03 70 03 # rd_t1 !0x37 addi ; 'A' -> 0xA
+ 33 08 65 40 # rd_a6 rs1_a0 rs2_t1 sub
+ 6F 00 40 02 # $hex_return jal ; return
+ # +36B
+# :ascii_other ; (0x06003A0)
+ 13 08 F0 FF # rd_a6 !-1 addi ; Return -1
+ 6F 00 C0 01 # $hex_return jal ; return
+ # +28B
+# :ascii_comment ; (0x06003A8) ; Read the comment until newline
+ EF 00 80 02 # rd_ra $Read_byte jal
+ # +40B
+ 13 03 D0 00 # rd_t1 !0xD addi ; CR
+ 63 06 65 00 # rs1_a0 rs2_t1 @ascii_comment_cr beq
+ # +12B
+ 13 03 A0 00 # rd_t1 !0xA addi ; LF
+ E3 18 65 FE # rs1_a0 rs2_t1 @ascii_comment bne ; Keep reading comment
+ # -16B
+# :ascii_comment_cr ; (0x06003BC)
+ 13 08 F0 FF # rd_a6 !-1 addi ; Return -1
+# :hex_return ; (0x06003C0)
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 83 25 41 00 # rd_a1 rs1_sp !4 lw ; restore a1
+ 13 01 81 00 # rd_sp rs1_sp !8 addi ; Deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+; Read byte into a0
+# :Read_byte ; (0x06003D0)
+ 13 01 81 FF # rd_sp rs1_sp !-8 addi ; Allocate stack
+ 23 22 B1 00 # rs1_sp rs2_a1 @4 sw ; protect a1
+
+ 93 08 F0 03 # rd_a7 !63 addi ; sys_read
+ 13 05 09 00 # rd_a0 rs1_s2 mv ; File descriptor
+ 93 05 01 00 # rd_a1 rs1_sp mv ; Get stack address for buffer
+ 13 00 00 00 # nop ; no-op
+ 13 06 10 00 # rd_a2 !1 addi ; Size of what we want to read
+ 73 00 00 00 # ecall ; syscall
+
+ 63 06 05 00 # rs1_a0 @Read_byte_1 beqz ; Deal with EOF
+ # +12B
+ 03 85 05 00 # rd_a0 rs1_a1 lb ; Dereference pointer
+
+ 6F 00 80 00 # $Read_byte_done jal ; return
+ # +8B
+
+# :Read_byte_1 ; (0x06003FC)
+ 13 05 C0 FF # rd_a0 !-4 addi ; Put EOF in a0
+# :Read_byte_done ; (0x0600400)
+ 83 25 41 00 # rd_a1 rs1_sp !4 lw ; restore a1
+ 13 01 81 00 # rd_sp rs1_sp !16 addi ; Deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+; Reads a byte and calculates table address
+; Returns a pointer in a0
+# :Get_table_target ; (0x060040C)
+ 13 01 C1 FF # rd_sp rs1_sp !-4 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+
+ EF F0 DF FB # rd_ra $Read_byte jal ; Get single char label
+ # -68B
+ 13 15 25 00 # rd_a0 rs1_a0 rs2_x2 slli ; Each label in table takes 4 bytes to store
+ 97 02 00 00 # rd_t0 ~table auipc ; Load address of table
+ 93 82 82 08 # rd_t0 rs1_t0 !table addi ; into register t0
+ # +136B
+ 33 05 55 00 # rd_a0 rs1_a0 rs2_t0 add ; Calculate offset
+
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 13 01 41 00 # rd_sp rs1_sp !4 addi ; deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+# :StoreLabel ; (0x0600434)
+ 13 01 C1 FF # rd_sp rs1_sp !-4 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+
+ EF F0 1F FD # rd_ra $Get_table_target jal
+ # -48B
+ 23 20 65 01 # rs1_a0 rs2_s6 sw ; Store ip into table target
+
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 13 01 41 00 # rd_sp rs1_sp !4 addi ; deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+; fputc function
+; Receives CHAR in a0
+; Writes and returns number of bytes written in a0
+# :fputc ; (0x0600450)
+ 13 01 01 FF # rd_sp rs1_sp !-16 addi ; allocate stack
+ 23 20 A1 00 # rs1_sp rs2_a0 sw ; protect a0
+ 23 22 11 00 # rs1_sp rs2_ra @4 sw ; protect ra
+ 23 24 B1 00 # rs1_sp rs2_a1 @8 sw ; protect a1
+ 23 26 C1 00 # rs1_sp rs2_a2 @12 sw ; protect a2
+
+ 93 08 00 04 # rd_a7 !64 addi ; sys_write
+ 13 85 09 00 # rd_a0 rs1_s3 mv ; write to output
+ 93 05 01 00 # rd_a1 rs1_sp mv ; Get stack address
+ 13 06 10 00 # rd_a2 !1 addi ; write 1 character
+ 73 00 00 00 # ecall ; syscall
+
+ 83 20 41 00 # rd_ra rs1_sp !4 lw ; restore ra
+ 83 25 81 00 # rd_a1 rs1_sp !8 lw ; restore a1
+ 03 26 C1 00 # rd_a2 rs1_sp !12 lw ; restore a2
+ 13 01 01 01 # rd_sp rs1_sp !16 addi ; Deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+# :Done ; (0x060048C)
+ ; Terminate program with 0 return code
+ 93 08 D0 05 # rd_a7 !93 addi ; sys_exit
+ 13 05 00 00 # rd_a0 mv ; Return code 0
+ 73 00 00 00 # ecall ; exit(0)
+# :Fail ; (0x0600498)
+ ; Terminate program with 1 return code
+ 93 08 D0 05 # rd_a7 !93 addi ; sys_exit
+ 13 05 10 00 # rd_a0 !1 addi ; Return code 1
+ 73 00 00 00 # ecall ; exit(1)
+# PROGRAM END
+
+# :table; (0x06004A4)
+ 00
diff --git a/vendor/seed/riscv32/hex2.hex1 b/vendor/seed/riscv32/hex2.hex1
@@ -0,0 +1,904 @@
+## Copyright (C) 2021 Andrius Štikonas
+## This file is part of stage0.
+##
+## stage0 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.
+##
+## stage0 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 stage0. If not, see <http://www.gnu.org/licenses/>.
+
+## ELF Header
+
+#:ELF_base
+7F 45 4C 46 ## e_ident[EI_MAG0-3] ELF's magic number
+
+01 ## e_ident[EI_CLASS] Indicating 32 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] Set at 0 because none cares
+
+00 00 00 00 00 00 00 ## e_ident[EI_PAD]
+02 00 ## e_type Indicating Executable
+F3 00 ## e_machine Indicating RISC-V
+01 00 00 00 ## e_version Indicating original elf
+
+54 00 60 00 ## e_entry Address of the entry point (Number of bytes this header is + Base Address)
+34 00 00 00 ## e_phoff Address of program header table
+00 00 00 00 ## e_shoff Address of section header table
+
+00 00 00 00 ## e_flags
+34 00 ## e_ehsize Indicating our 52 Byte header
+
+20 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
+
+## Program Header
+#:ELF_program_headers
+01 00 00 00 ## ph_type: PT-LOAD = 1
+00 00 00 00 ## p_offset
+
+00 00 60 00 ## ph_vaddr
+00 00 60 00 ## ph_physaddr
+
+D0 07 00 00 ## p_filesz
+D0 07 00 00 ## p_memsz
+
+07 00 00 00 ## ph_flags: PF-X|PF-W|PF-R = 7
+01 00 00 00 ## ph_align
+
+#:ELF_text
+; Register use:
+; s1: jump table
+; s2: input fd
+; s3: output fd
+; s4: toggle
+; s5: hold
+; s6: ip
+; s7: tempword
+; s8: shiftregister
+; s9: malloc pointer
+; s10: updates
+
+; Struct format: (size 12)
+; next => 0 ; Next element in linked list
+; target => 4 ; Target (ip)
+; name => 8 ; Label name
+
+; Our main function
+#:_start
+ 03 26 81 00 # rd_a2 rs1_sp !8 lw ; Input file name
+
+ ; Initialize globals
+ 13 0A F0 FF # rd_s4 !-1 addi ; Toggle
+ 93 0A 00 00 # rd_s5 addi ; Hold
+ 37 0B 60 00 # rd_s6 ~0x600000 lui ; Instruction Pointer
+
+ ; Open input file and store FD in s2
+ 93 08 80 03 # rd_a7 !56 addi ; sys_openat
+ 13 05 C0 F9 # rd_a0 !-100 addi ; AT_FDCWD
+ 93 05 06 00 # rd_a1 rs1_a2 mv ; file name
+ 13 06 00 00 # rd_a2 addi ; read only
+ 73 00 00 00 # ecall ; syscall
+ @F 63 40 05 00 # rs1_a0 @Fail bltz ; Error opening file
+ 13 09 05 00 # rd_s2 rs1_a0 mv ; Save fd in for later
+
+ ; Set default FD for output file to stdout
+ 93 09 10 00 # rd_s3 !1 addi
+
+ ; If we only have 2 arguments, don't use the third (it's not set)
+ 93 02 20 00 # rd_t0 !2 addi
+ 03 25 01 00 # rd_a0 rs1_sp lw ; Get number of the args
+ @F 63 40 55 00 # rs1_a0 rs2_t0 @Fail blt ; No input file provided
+ @a 63 00 55 00 # rs1_a0 rs2_t0 @after_open beq ; No output file provided. Use stdout
+
+ ; Open output file and store the FD in s3
+ 93 08 80 03 # rd_a7 !56 addi ; sys_openat
+ 13 05 C0 F9 # rd_a0 !-100 addi ; AT_FDCWD
+ 83 25 C1 00 # rd_a1 rs1_sp !12 lw ; Output file (argument 3)
+ 13 06 10 24 # rd_a2 !00001101 addi ; decimal 577
+ ; O_TRUNC 00001000
+ ; O_CREAT 00000100
+ ; O_WRONLY 00000001
+ ; OCTAL!
+ 93 06 00 1C # rd_a3 !00700 addi ; Set read, write, execute permission on user
+ ; S_IRWXU 00700
+ ; OCTAL!
+ 73 00 00 00 # ecall ; syscall
+ 93 09 05 00 # rd_s3 rs1_a0 mv ; Save fd in for later
+
+:a ;after_open
+ ; Prepare heap memory
+ 93 08 60 0D # rd_a7 !214 addi ; sys_brk
+ 13 05 00 00 # rd_a0 addi ; Get current brk
+ 73 00 00 00 # ecall ; syscall
+ 93 0C 05 00 # rd_s9 rs1_a0 addi ; Set our malloc pointer
+
+ B7 05 10 00 # rd_a1 ~0x100000 lui
+ 33 05 B5 00 # rd_a0 rs1_a0 rs2_a1 add ; Request the 1 MiB
+ 93 08 60 0D # rd_a7 !214 addi ; sys_brk
+ 73 00 00 00 # ecall ; syscall
+
+ $C EF 00 00 00 # rd_ra $ClearScratch jal ; Zero scratch
+ $f EF 00 00 00 # rd_ra $First_pass jal ; First pass
+
+ ; Rewind input file
+ 93 08 E0 03 # rd_a7 !62 addi ; sys_llseek
+ 13 05 09 00 # rd_a0 rs1_s2 mv ; Input file descriptor
+ 93 05 00 00 # rd_a1 mv ; Set offset to zero
+ 13 06 00 00 # rd_a2 mv ; Set offset to zero
+ 93 06 00 00 # rd_a3 mv ; Set result pointer to zero
+ 13 07 00 00 # rd_a4 mv ; Set whence to zero
+ 73 00 00 00 # ecall ; syscall
+
+ ; Initialize globals
+ 13 0A F0 FF # rd_s4 !-1 addi ; Toggle
+ 93 0A 00 00 # rd_s5 addi ; Hold
+ 37 0B 60 00 # rd_s6 ~0x600000 lui ; Instruction Pointer
+ 93 0B 00 00 # rd_s7 addi ; tempword
+ 13 0C 00 00 # rd_s8 addi ; Shift register
+
+ $X EF 00 00 00 # rd_ra $Second_pass jal ; Now do the second pass
+
+ ; Terminate program with 0 return code
+ 93 08 D0 05 # rd_a7 !93 addi ; sys_exit
+ 13 05 00 00 # rd_a0 mv ; Return code 0
+ 73 00 00 00 # ecall ; exit(0)
+
+; First pass loop to determine addresses of labels
+:f ;First_pass
+ 13 01 C1 FF # rd_sp rs1_sp !-4 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+
+:1 ;First_pass_loop
+ $R EF 00 00 00 # rd_ra $Read_byte jal ; Get another byte
+
+ ; Deal with EOF
+ 13 03 C0 FF # rd_t1 !-4 addi
+ @3 63 00 65 00 # rs1_a0 rs2_t1 @First_pass_done beq
+
+ ; Check for :
+ 13 03 A0 03 # rd_t1 !0x3A addi
+ @L 63 00 65 00 # rs1_a0 rs2_t1 @StoreLabel beq ; Store this label
+
+ ; Check for .
+ 13 03 E0 02 # rd_t1 !0x2E addi
+ @w 63 00 65 00 # rs1_a0 rs2_t1 @First_pass_UpdateWord beq
+
+ ; Check for %
+ 13 03 50 02 # rd_t1 !0x25 addi
+ @p 63 00 65 00 # rs1_a0 rs2_t1 @First_pass_pointer beq
+
+ ; Check for &
+ 13 03 60 02 # rd_t1 !0x26 addi
+ @p 63 00 65 00 # rs1_a0 rs2_t1 @First_pass_pointer beq
+
+ ; Check for !
+ 13 03 10 02 # rd_t1 !0x21 addi
+ @T 63 00 65 00 # rs1_a0 rs2_t1 @Throwaway_token beq
+
+ ; Check for @
+ 13 03 00 04 # rd_t1 !0x40 addi
+ @T 63 00 65 00 # rs1_a0 rs2_t1 @Throwaway_token beq
+
+ ; Check for $
+ 13 03 40 02 # rd_t1 !0x24 addi
+ @T 63 00 65 00 # rs1_a0 rs2_t1 @Throwaway_token beq
+
+ ; Check for ~
+ 13 03 E0 07 # rd_t1 !0x7E addi
+ @T 63 00 65 00 # rs1_a0 rs2_t1 @Throwaway_token beq
+
+ ; Check for <
+ 13 03 C0 03 # rd_t1 !0x3C addi
+ 93 05 F0 FF # rd_a1 !-1 addi ; write = false
+ @A 63 00 65 00 # rs1_a0 rs2_t1 @PadToAlign beq
+
+ 93 05 F0 FF # rd_a1 !-1 addi ; write = false
+ 13 06 F0 FF # rd_a2 !-1 addi ; update = false
+ $D EF 00 00 00 # rd_ra $DoByte jal ; Deal with everything else
+
+ 13 03 C0 FF # rd_t1 !-4 addi ; Deal with EOF
+ @3 63 00 65 00 # rs1_a0 rs2_t1 @First_pass_done beq
+
+ $1 6F 00 00 00 # $First_pass_loop jal ; Keep looping
+
+:T ;Throwaway_token
+ ~s 97 05 00 00 # rd_a1 ~scratch auipc
+ !s 93 85 05 00 # rd_a1 rs1_a1 !scratch addi ; get scratch
+ $c EF 00 00 00 # rd_ra $consume_token jal ; Read token
+ $C EF 00 00 00 # rd_ra $ClearScratch jal ; Throw away token
+ $1 6F 00 00 00 # $First_pass_loop jal ; Loop again
+
+:p ;First_pass_pointer
+ 13 0B 4B 00 # rd_s6 rs1_s6 !4 addi ; Update ip
+ ; Deal with Pointer to label
+ ~s 97 05 00 00 # rd_a1 ~scratch auipc
+ !s 93 85 05 00 # rd_a1 rs1_a1 !scratch addi ; Using scratch
+ $c EF 00 00 00 # rd_ra $consume_token jal ; Read token
+ $C EF 00 00 00 # rd_ra $ClearScratch jal ; Throw away token
+ 13 03 E0 03 # rd_t1 !0x3E addi ; Check for '>'
+ @1 63 10 65 00 # rs1_a0 rs2_t1 @First_pass_loop bne ; Loop again
+
+ ; Deal with %label>label case
+ ~s 97 05 00 00 # rd_a1 ~scratch auipc
+ !s 93 85 05 00 # rd_a1 rs1_a1 !scratch addi ; Using scratch
+ $c EF 00 00 00 # rd_ra $consume_token jal ; Read token
+ $C EF 00 00 00 # rd_ra $ClearScratch jal ; Throw away token
+ $1 6F 00 00 00 # $First_pass_loop jal ; Loop again
+
+:w ;First_pass_UpdateWord
+ 13 0D 00 00 # rd_s10 addi ; updates = 0
+ 93 0B 00 00 # rd_s7 addi ; tempword = 0
+ 93 07 40 00 # rd_a5 !4 addi ; a5 = 4
+:4 ;First_pass_UpdateWord_loop
+ $R EF 00 00 00 # rd_ra $Read_byte jal ; Read another byte into a0
+
+ 93 05 F0 FF # rd_a1 !-1 addi ; write = false
+ 13 06 00 00 # rd_a2 addi ; update = true
+ $D EF 00 00 00 # rd_ra $DoByte jal ; Process byte
+ @4 63 40 FD 00 # rs1_s10 rs2_a5 @First_pass_UpdateWord_loop blt ; loop 4 times
+
+ 13 0B CB FF # rd_s6 rs1_s6 !-4 addi ; ip = ip - 4
+
+ $1 6F 00 00 00 # $First_pass_loop jal ; Loop again
+
+:3 ;First_pass_done
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 13 01 41 00 # rd_sp rs1_sp !4 addi ; deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+:X ;Second_pass
+ 13 01 C1 FF # rd_sp rs1_sp !-4 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+
+:5 ;Second_pass_loop
+ $R EF 00 00 00 # rd_ra $Read_byte jal ; Read another byte
+
+ ; Deal with EOF
+ 13 03 C0 FF # rd_t1 !-4 addi ; Deal with EOF
+ @6 63 00 65 00 # rs1_a0 rs2_t1 @Second_pass_done beq
+
+ ; Drop the label
+ 13 03 A0 03 # rd_t1 !0x3A addi
+ @7 63 10 65 00 # rs1_a0 rs2_t1 @Second_pass_0 bne
+
+ ~s 97 05 00 00 # rd_a1 ~scratch auipc
+ !s 93 85 05 00 # rd_a1 rs1_a1 !scratch addi ; Using scratch
+ $c EF 00 00 00 # rd_ra $consume_token jal ; Read the label
+ $C EF 00 00 00 # rd_ra $ClearScratch jal ; Throw away token
+
+ $5 6F 00 00 00 # $Second_pass_loop jal ; Continue looping
+
+:7 ;Second_pass_0
+ ; Check for .
+ 13 03 E0 02 # rd_t1 !0x2E addi
+ @8 63 00 65 00 # rs1_a0 rs2_t1 @Second_pass_UpdateWord beq
+
+ ; Check for %
+ 13 03 50 02 # rd_t1 !0x25 addi
+ @S 63 00 65 00 # rs1_a0 rs2_t1 @StorePointer beq
+
+ ; Check for &
+ 13 03 60 02 # rd_t1 !0x26 addi
+ @S 63 00 65 00 # rs1_a0 rs2_t1 @StorePointer beq
+
+ ; Check for !
+ 13 03 10 02 # rd_t1 !0x21 addi
+ @Y 63 00 65 00 # rs1_a0 rs2_t1 @UpdateShiftRegister beq
+
+ ; Check for @
+ 13 03 00 04 # rd_t1 !0x40 addi
+ @Y 63 00 65 00 # rs1_a0 rs2_t1 @UpdateShiftRegister beq
+
+ ; Check for $
+ 13 03 40 02 # rd_t1 !0x24 addi
+ @Y 63 00 65 00 # rs1_a0 rs2_t1 @UpdateShiftRegister beq
+
+ ; Check for ~
+ 13 03 E0 07 # rd_t1 !0x7E addi
+ @Y 63 00 65 00 # rs1_a0 rs2_t1 @UpdateShiftRegister beq
+
+ ; Check for <
+ 13 03 C0 03 # rd_t1 !0x3C addi
+ 93 05 00 00 # rd_a1 addi ; write = true
+ @A 63 00 65 00 # rs1_a0 rs2_t1 @PadToAlign beq
+
+ ; Deal with everything else
+ 93 05 00 00 # rd_a1 addi ; write = true
+ 13 06 F0 FF # rd_a2 !-1 addi ; update = false
+ $D EF 00 00 00 # rd_ra $DoByte jal ; Process our char
+
+ # Deal with EOF
+ 13 03 C0 FF # rd_t1 !-4 addi
+ @6 63 00 65 00 # rs1_a0 rs2_t1 @Second_pass_done beq ; We are done
+
+ $5 6F 00 00 00 # $Second_pass_loop jal ; continue looping
+
+:8 ;Second_pass_UpdateWord
+ 13 0D 00 00 # rd_s10 addi ; updates = 0
+ 93 0B 00 00 # rd_s7 addi ; tempword = 0
+ 93 07 40 00 # rd_a5 !4 addi ; a5 = 4
+
+:9 ;Second_pass_UpdateWord_loop
+ $R EF 00 00 00 # rd_ra $Read_byte jal ; Read another byte into a0
+
+ 93 05 F0 FF # rd_a1 !-1 addi ; write = false
+ 13 06 00 00 # rd_a2 addi ; update = true
+ $D EF 00 00 00 # rd_ra $DoByte jal ; Process our char
+ @9 63 40 FD 00 # rs1_s10 rs2_a5 @Second_pass_UpdateWord_loop blt ; loop 4 times
+
+ 13 85 0B 00 # rd_a0 rs1_s7 mv ; tempword
+ $d 6F 00 00 00 # $UpdateShiftRegister_DOT jal ; UpdateShiftRegister('.', tempword)
+
+:Y ;UpdateShiftRegister
+ 13 06 05 00 # rd_a2 rs1_a0 mv ; Store label prefix
+ ~s 97 05 00 00 # rd_a1 ~scratch auipc
+ !s 93 85 05 00 # rd_a1 rs1_a1 !scratch addi ; Get scratch
+ $C EF 00 00 00 # rd_ra $ClearScratch jal ; Clear scratch
+ $c EF 00 00 00 # rd_ra $consume_token jal ; Read token
+ $G EF 00 00 00 # rd_ra $GetTarget jal ; Get target
+ 03 25 05 00 # rd_a0 rs1_a0 lw ; Dereference pointer
+ 33 05 65 41 # rd_a0 rs1_a0 rs2_s6 sub ; target - ip
+
+ ; Check for !
+ 13 03 10 02 # rd_t1 !0x21 addi
+ @I 63 00 66 00 # rs1_a2 rs2_t1 @UpdateShiftRegister_I beq
+
+ ; Check for @
+ 13 03 00 04 # rd_t1 !0x40 addi
+ @B 63 00 66 00 # rs1_a2 rs2_t1 @UpdateShiftRegister_B beq
+
+ ; Check for $
+ 13 03 40 02 # rd_t1 !0x24 addi
+ @J 63 00 66 00 # rs1_a2 rs2_t1 @UpdateShiftRegister_J beq
+
+ ; Check for ~
+ 13 03 E0 07 # rd_t1 !0x7E addi
+ @U 63 00 66 00 # rs1_a2 rs2_t1 @UpdateShiftRegister_U beq
+
+ $5 6F 00 00 00 # $Second_pass_loop jal ; continue looping
+
+:d ;UpdateShiftRegister_DOT
+ ; . before instruction means it has to be added to the final word
+
+ ; swap = (((value >> 24) & 0xff) |
+ ; ((value << 8) & 0xff0000) |
+ ; ((value >> 8) & 0xff00) |
+ ; ((value << 24) & 0xff000000))
+
+ 93 53 85 01 # rd_t2 rs1_a0 rs2_x24 srli ; value >> 24
+ 13 03 F0 0F # rd_t1 !0xFF addi ; t1 = 0xff
+ B3 72 73 00 # rd_t0 rs1_t1 rs2_t2 and ; (value >> 24) & 0xff
+
+ 93 13 85 00 # rd_t2 rs1_a0 rs2_x8 slli ; value << 8
+ 37 03 FF 00 # rd_t1 ~0xFF0000 lui ; t1 = 0xff0000
+ B3 73 73 00 # rd_t2 rs1_t1 rs2_t2 and ; (value << 8) & 0xff0000
+ B3 E2 72 00 # rd_t0 rs1_t0 rs2_t2 or ; logical or with the previous expression
+
+ 93 53 85 00 # rd_t2 rs1_a0 rs2_x8 srli ; value >> 8
+ 37 03 01 00 # rd_t1 ~0xFF00 lui ; t1 = 0xff00
+ 13 03 03 F0 # rd_t1 rs1_t1 !0xFF00 addi ; t1 = 0xff00
+ B3 73 73 00 # rd_t2 rs1_t1 rs2_t2 and ; (value << 8) & 0xff00
+ B3 E2 72 00 # rd_t0 rs1_t0 rs2_t2 or ; logical or with the previous expression
+
+ 93 13 85 01 # rd_t2 rs1_a0 rs2_x24 slli ; value << 24
+ 13 03 F0 0F # rd_t1 !0xFF addi
+ 13 13 83 01 # rd_t1 rs1_t1 rs2_x24 slli ; t1 = 0xff000000
+ B3 73 73 00 # rd_t2 rs1_t1 rs2_t2 and ; (value << 24) & 0xff000000
+ B3 E2 72 00 # rd_t0 rs1_t0 rs2_t2 or ; swap
+
+ 33 4C 5C 00 # rd_s8 rs1_s8 rs2_t0 xor ; shiftregister = shiftregister ^ swap
+
+ 13 0B CB FF # rd_s6 rs1_s6 !-4 addi ; ip = ip - 4
+ $5 6F 00 00 00 # $Second_pass_loop jal ; continue looping
+
+:I ;UpdateShiftRegister_I
+ ; Corresponds to RISC-V I format
+ 13 05 45 00 # rd_a0 rs1_a0 !4 addi ; add 4 due to this being 2nd part of auipc combo
+
+ 37 13 00 00 # rd_t1 ~0xFFF lui ; load higher bits
+ 13 03 F3 FF # rd_t1 rs1_t1 !0xFFF addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; (value & 0xfff)
+ 93 1B 43 01 # rd_s7 rs1_t1 rs2_x20 slli ; tempword = (value & 0xfff) << 20
+ 33 4C 7C 01 # rd_s8 rs1_s8 rs2_s7 xor ; shiftregister = shiftregister ^ tempword
+
+ $5 6F 00 00 00 # $Second_pass_loop jal ; continue looping
+
+:B ;UpdateShiftRegister_B
+ ; Corresponds to RISC-V B format
+
+ ; tempword = ((value & 0x1e) << 7) ; imm[4:1]
+ ; | ((value & 0x7e0) << (31 - 11)) ; imm[10:5]
+ ; | ((value & 0x800) >> 4) ; imm[11]
+ ; | ((value & 0x1000) << (31 - 12)) ; imm[12]
+
+ 13 03 E0 01 # rd_t1 !0x1E addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x1e
+ 93 12 73 00 # rd_t0 rs1_t1 rs2_x7 slli ; tempword = (value & 0x1e) << 7
+
+ 13 03 00 7E # rd_t1 !0x7E0 addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x7e0
+ 13 13 43 01 # rd_t1 rs1_t1 rs2_x20 slli ; (value & 0x7e0) << (31 - 11)
+ B3 E2 62 00 # rd_t0 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 37 13 00 00 # rd_t1 ~0x800 lui ; load higher bits
+ 13 03 03 80 # rd_t1 rs1_t1 !0x800 addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x800
+ 13 53 43 00 # rd_t1 rs1_t1 rs2_x4 srli ; (value & 0x800) >> 4
+ B3 E2 62 00 # rd_t0 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 37 13 00 00 # rd_t1 ~0x1000 lui ; load higher bits
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x1000
+ 13 13 33 01 # rd_t1 rs1_t1 rs2_x19 slli ; (value & 0x1000) << (31 - 12)
+ B3 EB 62 00 # rd_s7 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 33 4C 7C 01 # rd_s8 rs1_s8 rs2_s7 xor ; shiftregister = shiftregister ^ tempword
+
+ $5 6F 00 00 00 # $Second_pass_loop jal ; continue looping
+
+:J ;UpdateShiftRegister_J
+ ; Corresponds to RISC-V J format
+
+ ; tempword = ((value & 0x7fe) << (30 - 10)) ; imm[10:1]
+ ; | ((value & 0x800) << (20 - 11)) ; imm[11]
+ ; | ((value & 0xff000)) ; imm[19:12]
+ ; | ((value & 0x100000) << (31 - 20)) ; imm[20]
+
+ 13 03 E0 7F # rd_t1 !0x7FE addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x7fe
+ 93 12 43 01 # rd_t0 rs1_t1 rs2_x20 slli ; tempword = (value & 0x7fe) << 20
+
+ 37 13 00 00 # rd_t1 ~0x800 lui ; load higher bits
+ 13 03 03 80 # rd_t1 rs1_t1 !0x800 addi
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x800
+ 13 13 93 00 # rd_t1 rs1_t1 rs2_x9 slli ; (value & 0x800) << (20 - 11)
+ B3 E2 62 00 # rd_t0 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 37 F3 0F 00 # rd_t1 ~0xFF000 lui ; load higher bits
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0xff000
+ B3 E2 62 00 # rd_t0 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 37 03 10 00 # rd_t1 ~0x100000 lui ; load higher bits
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0x100000
+ 13 13 B3 00 # rd_t1 rs1_t1 rs2_x11 slli ; (value & 0x100000) << (31 - 20)
+ B3 EB 62 00 # rd_s7 rs1_t0 rs2_t1 or ; logical or with the previous expression
+
+ 33 4C 7C 01 # rd_s8 rs1_s8 rs2_s7 xor ; shiftregister = shiftregister ^ tempword
+
+ $5 6F 00 00 00 # $Second_pass_loop jal ; continue looping
+
+:U ;UpdateShiftRegister_U
+ ; Corresponds to RISC-V U format
+ ; if value is 0x800 or more we have to add 11-th bit (0x1000) to compensate for signed extension
+
+ B7 12 00 00 # rd_t0 ~0x800 lui ; load higher bits
+ 93 82 02 80 # rd_t0 rs1_t0 !0x800 addi
+ 37 13 00 00 # rd_t1 ~0xFFF lui ; load higher bits
+ 13 03 F3 FF # rd_t1 rs1_t1 !0xFFF addi
+
+ ; We are outside 31-bit that ~ can normally load
+ B7 03 10 00 # rd_t2 ~0x100000 lui ; load 0xfffff000
+ 93 83 F3 FF # rd_t2 rs1_t2 !-1 addi ; load 0xfffff000
+ 93 93 C3 00 # rd_t2 rs1_t2 rs2_x12 slli ; load 0xfffff000
+ 33 73 65 00 # rd_t1 rs1_a0 rs2_t1 and ; value & 0xfff
+ B3 7B 75 00 # rd_s7 rs1_a0 rs2_t2 and ; value & 0xfffff000
+ @u 63 40 53 00 # rs1_t1 rs2_t0 @UpdateShiftRegister_U_small blt
+
+ # Deal with sign extension: add 0x1000
+ B7 12 00 00 # rd_t0 ~0x1000 lui ; load higher bits
+ B3 8B 72 01 # rd_s7 rs1_t0 rs2_s7 add ; (value & 0xfffff000) + 0x1000
+
+:u ;UpdateShiftRegister_U_small
+ 33 4C 7C 01 # rd_s8 rs1_s8 rs2_s7 xor ; shiftregister = shiftregister ^ tempword
+
+ $5 6F 00 00 00 # $Second_pass_loop jal ; continue looping
+
+:S ;StorePointer
+ 13 0B 4B 00 # rd_s6 rs1_s6 !4 addi ; update ip
+ 13 06 05 00 # rd_a2 rs1_a0 mv ; Store label prefix
+
+ ~s 97 05 00 00 # rd_a1 ~scratch auipc
+ !s 93 85 05 00 # rd_a1 rs1_a1 !scratch addi ; Get scratch
+ $C EF 00 00 00 # rd_ra $ClearScratch jal ; clear scratch
+ $c EF 00 00 00 # rd_ra $consume_token jal ; Read token
+ 93 07 05 00 # rd_a5 rs1_a0 mv ; save char
+ $G EF 00 00 00 # rd_ra $GetTarget jal ; Get target
+ 83 25 05 00 # rd_a1 rs1_a0 lw ; Dereference pointer
+
+ ; If char is > then change relative base address to ip
+ 13 03 E0 03 # rd_t1 !0x3E addi ; t1 = 0x3e
+ @P 63 00 F3 00 # rs1_t1 rs2_a5 @StorePointer_1 beq
+
+ ; Check for &
+ 13 03 60 02 # rd_t1 !0x26 addi
+ @0 63 00 66 00 # rs1_a2 rs2_t1 @StorePointer_0 beq
+
+ ; Check for %
+ 13 03 50 02 # rd_t1 !0x25 addi
+ @F 63 10 66 00 # rs1_a2 rs2_t1 @Fail bne
+ B3 85 65 41 # rd_a1 rs1_a1 rs2_s6 sub ; displacement = target - ip
+
+:0 ;StorePointer_0
+ ; Output pointer
+ 93 07 40 00 # rd_a5 !4 addi ; number of bytes
+:l ;StorePointer_loop
+ 13 D3 85 00 # rd_t1 rs1_a1 rs2_x8 srli ; value / 256
+ 13 15 83 00 # rd_a0 rs1_t1 rs2_x8 slli
+ 33 85 A5 40 # rd_a0 rs1_a1 rs2_a0 sub ; byte = value % 256
+
+ 93 05 03 00 # rd_a1 rs1_t1 mv ; value = value / 256
+ $t EF 00 00 00 # rd_ra $fputc jal ; write value
+ 93 87 F7 FF # rd_a5 rs1_a5 !-1 addi ; decrease number of bytes to write
+ @l 63 90 07 00 # rs1_a5 @StorePointer_loop bnez ; continue looping
+
+ $5 6F 00 00 00 # $Second_pass_loop jal ; Continue looping
+
+:P ;StorePointer_1
+ 13 86 05 00 # rd_a2 rs1_a1 mv ; save target
+ ~s 97 05 00 00 # rd_a1 ~scratch auipc
+ !s 93 85 05 00 # rd_a1 rs1_a1 !scratch addi ; Get scratch
+ $C EF 00 00 00 # rd_ra $ClearScratch jal ; clear scratch
+ $c EF 00 00 00 # rd_ra $consume_token jal ; consume token
+ $G EF 00 00 00 # rd_ra $GetTarget jal ; Get target
+ 83 25 05 00 # rd_a1 rs1_a0 lw ; Dereference pointer
+ B3 05 B6 40 # rd_a1 rs1_a2 rs2_a1 sub ; displacement = target - ip
+
+ $0 6F 00 00 00 # $StorePointer_0 jal ; Continue looping
+
+:6 ;Second_pass_done
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 13 01 41 00 # rd_sp rs1_sp !4 addi ; deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+; Pad with zeros to align to word size
+; bool write in a1
+:A ;PadToAlign
+ 13 03 10 00 # rd_t1 !1 addi ; t1 = 1
+ 33 75 6B 00 # rd_a0 rs1_s6 rs2_t1 and ; ip & 0x1
+ @b 63 10 65 00 # rs1_a0 rs2_t1 @PadToAlign_1 bne ; check if ip & 0x1 == 1
+ 33 0B 6B 00 # rd_s6 rs1_s6 rs2_t1 add ; ip = ip + 1
+
+ @b 63 90 05 00 # rs1_a1 @PadToAlign_1 bnez ; check if we have to write
+ 13 05 00 00 # rd_a0 mv ; a0 = 0
+ $t EF 00 00 00 # rd_ra $fputc jal ; write 0
+
+:b ;PadToAlign_1
+ 13 03 20 00 # rd_t1 !2 addi ; t1 = 2
+ 33 75 6B 00 # rd_a0 rs1_s6 rs2_t1 and ; ip & 0x1
+ @e 63 10 65 00 # rs1_a0 rs2_t1 @PadToAlign_2 bne ; check if ip & 0x2 == 2
+ 33 0B 6B 00 # rd_s6 rs1_s6 rs2_t1 add ; ip = ip + 2
+
+ @e 63 90 05 00 # rs1_a1 @PadToAlign_2 bnez ; check if we have to write
+ 13 05 00 00 # rd_a0 mv ; a0 = 0
+ $t EF 00 00 00 # rd_ra $fputc jal ; write 0
+ 13 05 00 00 # rd_a0 mv ; a0 = 0
+ $t EF 00 00 00 # rd_ra $fputc jal ; write 0
+
+:e ;PadToAlign_2
+ @5 63 80 05 00 # rs1_a1 @Second_pass_loop beqz ; return to Second_pass
+ $1 6F 00 00 00 # $First_pass_loop jal ; return to First_pass
+
+; Zero scratch area
+:C ;ClearScratch
+ 13 01 41 FF # rd_sp rs1_sp !-12 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra SD ; protect ra
+ 23 22 A1 00 # rs1_sp rs2_a0 @4 SD ; protect a0
+ 23 24 B1 00 # rs1_sp rs2_a1 @8 SD ; protect a1
+
+ ~s 17 05 00 00 # rd_a0 ~scratch auipc
+ !s 13 05 05 00 # rd_a0 rs1_a0 !scratch addi ; Find where our scratch area is
+
+:g ;ClearScratch_loop
+ 83 05 05 00 # rd_a1 rs1_a0 lb ; Read current byte: s[i]
+ 23 00 05 00 # rs1_a0 sb ; Write zero: s[i] = 0
+ 13 05 15 00 # rd_a0 rs1_a0 !1 addi ; Increment: i = i + 1
+ @g 63 90 05 00 # rs1_a1 @ClearScratch_loop bnez ; Keep looping
+
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 03 25 41 00 # rd_a0 rs1_sp !4 lw ; restore a0
+ 83 25 81 00 # rd_a1 rs1_sp !8 lw ; restore a1
+ 13 01 C1 00 # rd_sp rs1_sp !12 addi ; Deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+; Receives pointer in a1
+; Writes our token and updates pointer in a1
+:c ;consume_token
+ 13 01 C1 FF # rd_sp rs1_sp !-4 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+
+:h ;consume_token_0
+ $R EF 00 00 00 # rd_ra $Read_byte jal ; Read byte into a0
+
+ ; Check for \t
+ 13 03 90 00 # rd_t1 !0x09 addi
+ @j 63 00 65 00 # rs1_a0 rs2_t1 @consume_token_done beq
+
+ ; Check for \n
+ 13 03 A0 00 # rd_t1 !0x0A addi
+ @j 63 00 65 00 # rs1_a0 rs2_t1 @consume_token_done beq
+
+ ; Check for ' '
+ 13 03 00 02 # rd_t1 !0x20 addi
+ @j 63 00 65 00 # rs1_a0 rs2_t1 @consume_token_done beq
+
+ ; Check for >
+ 13 03 E0 03 # rd_t1 !0x3E addi
+ @j 63 00 65 00 # rs1_a0 rs2_t1 @consume_token_done beq
+
+ 23 80 A5 00 # rs1_a1 rs2_a0 sb ; Store char
+ 93 85 15 00 # rd_a1 rs1_a1 !1 addi ; Point to next spot
+ $h 6F 00 00 00 # $consume_token_0 jal ; Continue looping
+
+:j ;consume_token_done
+ 23 A0 05 00 # rs1_a1 sw ; Pad with nulls
+ 93 85 45 00 # rd_a1 rs1_a1 !4 addi ; Update the pointer
+
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 13 01 41 00 # rd_sp rs1_sp !4 addi ; deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+; DoByte function
+; Receives:
+; character in a0
+; bool write in a1
+; bool update in a2
+; Does not return anything
+:D ;DoByte
+ 13 01 81 FF # rd_sp rs1_sp !-8 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+ 23 22 01 01 # rs1_sp rs2_a6 @4 sw ; protect a6
+
+ $H EF 00 00 00 # rd_ra $hex jal ; Process hex, store it in a6
+
+ @k 63 40 08 00 # rs1_a6 @DoByte_Done bltz ; Deal with EOF and unrecognized characters
+
+ @2 63 10 0A 00 # rs1_s4 @DoByte_NotToggle bnez ; Check if toggle is set
+
+ ; toggle = true
+ @m 63 90 05 00 # rs1_a1 @DoByte_1 bnez ; check if we have to write
+
+ ; write = true
+ ; We calculate (hold * 16) + hex(c) ^ sr_nextb()
+ ; First, calculate new shiftregister
+ 93 02 F0 0F # rd_t0 !0xFF addi
+ B3 72 5C 00 # rd_t0 rs1_s8 rs2_t0 and ; sr_nextb = shiftregister & 0xff
+ 13 5C 8C 00 # rd_s8 rs1_s8 rs2_x8 srli ; shiftregister >> 8
+
+ B3 C2 02 01 # rd_t0 rs1_t0 rs2_a6 xor ; hex(c) ^ sr_nextb
+ 13 93 4A 00 # rd_t1 rs1_s5 rs2_x4 slli ; hold << 4
+ 33 85 62 00 # rd_a0 rs1_t0 rs2_t1 add ; (hold << 4) + hex(c) ^ sr_nextb()
+ $t EF 00 00 00 # rd_ra $fputc jal ; print it
+ @F 63 00 05 00 # rs1_a0 @Fail beqz ; Fail if nothing was written
+
+:m ;DoByte_1
+ 13 0B 1B 00 # rd_s6 rs1_s6 !1 addi ; Increment IP
+ @o 63 00 06 00 # rs1_a2 @DoByte_2 beqz ; check if we have to update
+:n ;DoByte_2b
+ 93 0A 00 00 # rd_s5 mv ; hold = 0
+ $q 6F 00 00 00 # $DoByte_FlipToggle jal ; return
+
+:2 ;DoByte_NotToggle
+ 93 0A 08 00 # rd_s5 rs1_a6 mv ; hold = hex(c)
+
+:q ;DoByte_FlipToggle
+ 13 4A FA FF # rd_s4 rs1_s4 not ; Flip the toggle
+
+:k ;DoByte_Done
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 03 28 41 00 # rd_a6 rs1_sp !4 lw ; restore a6
+ 13 01 81 00 # rd_sp rs1_sp !8 addi ; Deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+:o ;DoByte_2
+ 13 93 4A 00 # rd_t1 rs1_s5 rs2_x4 slli ; hold * 16
+ B3 0A 03 01 # rd_s5 rs1_t1 rs2_a6 add ; hold = hold * 16 + hex(c)
+ 13 93 8B 00 # rd_t1 rs1_s7 rs2_x8 slli ; tempword << 8
+ B3 4B 53 01 # rd_s7 rs1_t1 rs2_s5 xor ; tempword = (tempword << 8) ^ hold
+ 13 0D 1D 00 # rd_s10 rs1_s10 !1 addi ; updates = updates + 1
+ $n 6F 00 00 00 # $DoByte_2b jal
+
+; Convert ASCII hex characters into binary representation, e.g. 'a' -> 0xA
+; Receives:
+; character in a0
+; Returns:
+; a6 with character's hex value.
+:H ;hex
+ 13 01 81 FF # rd_sp rs1_sp !-8 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+ 23 22 B1 00 # rs1_sp rs2_a1 @4 sw ; protect a1
+
+ ; Deal with EOF
+ 13 03 C0 FF # rd_t1 !-4 addi
+ @r 63 00 65 00 # rs1_a0 rs2_t1 @hex_return beq
+
+ ; deal with line comments starting with #
+ 13 03 30 02 # rd_t1 !0x23 addi
+ @x 63 00 65 00 # rs1_a0 rs2_t1 @ascii_comment beq ; a0 eq to '#'
+
+ ; deal with line comments starting with ;
+ 13 03 B0 03 # rd_t1 !0x3B addi
+ @x 63 00 65 00 # rs1_a0 rs2_t1 @ascii_comment beq ; a0 eq to ';'
+
+ ; deal all ascii less than 0
+ 13 03 00 03 # rd_t1 !0x30 addi
+ @y 63 40 65 00 # rs1_a0 rs2_t1 @ascii_other blt
+
+ ; deal with 0-9
+ 13 03 A0 03 # rd_t1 !0x3A addi
+ @N 63 40 65 00 # rs1_a0 rs2_t1 @ascii_num blt
+
+ ; deal with all ascii less than A
+ 13 03 10 04 # rd_t1 !0x41 addi
+ @y 63 40 65 00 # rs1_a0 rs2_t1 @ascii_other blt
+
+ ; deal with A-F
+ 13 03 70 04 # rd_t1 !0x47 addi
+ @z 63 40 65 00 # rs1_a0 rs2_t1 @ascii_high blt
+
+ ; deal with all ascii less than a
+ 13 03 10 06 # rd_t1 !0x61 addi
+ @y 63 40 65 00 # rs1_a0 rs2_t1 @ascii_other blt
+
+ ; deal with a-f
+ 13 03 70 06 # rd_t1 !0x67 addi
+ @Z 63 40 65 00 # rs1_a0 rs2_t1 @ascii_low blt
+
+ ; The rest that remains needs to be ignored
+ $y 6F 00 00 00 # $ascii_other jal
+
+:N ;ascii_num
+ 13 03 00 03 # rd_t1 !0x30 addi ; '0' -> 0
+ 33 08 65 40 # rd_a6 rs1_a0 rs2_t1 sub
+ $r 6F 00 00 00 # $hex_return jal ; return
+:Z ;ascii_low
+ 13 03 70 05 # rd_t1 !0x57 addi ; 'a' -> 0xA
+ 33 08 65 40 # rd_a6 rs1_a0 rs2_t1 sub
+ $r 6F 00 00 00 # $hex_return jal ; return
+:z ;ascii_high
+ 13 03 70 03 # rd_t1 !0x37 addi ; 'A' -> 0xA
+ 33 08 65 40 # rd_a6 rs1_a0 rs2_t1 sub
+ $r 6F 00 00 00 # $hex_return jal ; return
+:y ;ascii_other
+ 13 08 F0 FF # rd_a6 !-1 addi ; Return -1
+ $r 6F 00 00 00 # $hex_return jal ; return
+:x ;ascii_comment ; Read the comment until newline
+ $R EF 00 00 00 # rd_ra $Read_byte jal
+ 13 03 D0 00 # rd_t1 !0xD addi ; CR
+ @E 63 00 65 00 # rs1_a0 rs2_t1 @ascii_comment_cr beq
+ 13 03 A0 00 # rd_t1 !0xA addi ; LF
+ @x 63 10 65 00 # rs1_a0 rs2_t1 @ascii_comment bne ; Keep reading comment
+:E ;ascii_comment_cr
+ 13 08 F0 FF # rd_a6 !-1 addi ; Return -1
+:r ;hex_return
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 83 25 41 00 # rd_a1 rs1_sp !4 lw ; restore a1
+ 13 01 81 00 # rd_sp rs1_sp !8 addi ; Deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+; Read byte into a0
+:R ;Read_byte
+ 13 01 41 FF # rd_sp rs1_sp !-12 addi ; Allocate stack
+ 23 22 B1 00 # rs1_sp rs2_a1 @4 sw ; protect a1
+ 23 24 C1 00 # rs1_sp rs2_a2 @8 sw ; protect a2
+
+ 93 08 F0 03 # rd_a7 !63 addi ; sys_read
+ 13 05 09 00 # rd_a0 rs1_s2 mv ; File descriptor
+ 93 05 01 00 # rd_a1 rs1_sp mv ; Get stack address for buffer
+ 13 06 10 00 # rd_a2 !1 addi ; Size of what we want to read
+ 73 00 00 00 # ecall ; syscall
+
+ @K 63 00 05 00 # rs1_a0 @Read_byte_1 beqz ; Deal with EOF
+ 03 C5 05 00 # rd_a0 rs1_a1 lbu ; return char in a0
+
+ $M 6F 00 00 00 # $Read_byte_done jal ; return
+
+:K ;Read_byte_1
+ 13 05 C0 FF # rd_a0 !-4 addi ; Put EOF in a0
+:M ;Read_byte_done
+ 83 25 41 00 # rd_a1 rs1_sp !4 lw ; restore a1
+ 03 26 81 00 # rd_a2 rs1_sp !8 lw ; restore a2
+ 13 01 C1 00 # rd_sp rs1_sp !12 addi ; Deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+; Find a label matching pointer in scratch
+; Returns a pointer in a0
+:G ;GetTarget
+ 13 01 C1 FF # rd_sp rs1_sp !-4 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+
+ 93 82 04 00 # rd_t0 rs1_s1 mv ; grab jump_table
+
+:O ;GetTarget_loop_0
+ ; Compare the strings
+ ~s 17 03 00 00 # rd_t1 ~scratch auipc
+ !s 13 03 03 00 # rd_t1 rs1_t1 !scratch addi ; reset scratch
+ 83 A3 82 00 # rd_t2 rs1_t0 !8 lw ; I->name
+:Q ;GetTarget_loop
+ 83 CE 03 00 # rd_t4 rs1_t2 lbu ; I->name[i]
+ 03 4E 03 00 # rd_t3 rs1_t1 lbu ; scratch[i]
+ @v 63 10 DE 01 # rs1_t3 rs2_t4 @GetTarget_miss bne ; strings don't match
+
+ ; Look at the next char
+ 13 03 13 00 # rd_t1 rs1_t1 !1 addi
+ 93 83 13 00 # rd_t2 rs1_t2 !1 addi
+ @Q 63 90 0E 00 # rs1_t4 @GetTarget_loop bnez ; Loop until zero (end of string)
+ $V 6F 00 00 00 # $GetTarget_done jal ; We have a match
+
+:v ;GetTarget_miss
+ 83 A2 02 00 # rd_t0 rs1_t0 lw ; I = I->next
+ @F 63 80 02 00 # rs1_t0 @Fail beqz ; Abort, no match found
+
+ $O 6F 00 00 00 # $GetTarget_loop_0 jal ; Try another label
+
+:V ;GetTarget_done
+ 13 85 42 00 # rd_a0 rs1_t0 !4 addi ; Get target address
+
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 13 01 41 00 # rd_sp rs1_sp !4 addi ; deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+:L ;StoreLabel
+ 13 01 C1 FF # rd_sp rs1_sp !-4 addi ; Allocate stack
+ 23 20 11 00 # rs1_sp rs2_ra sw ; protect ra
+
+ 13 85 0C 00 # rd_a0 rs1_s9 mv ; struct entry
+ 93 8C 8C 01 # rd_s9 rs1_s9 !24 addi ; calloc
+ 23 22 65 01 # rs1_a0 rs2_s6 @4 sw ; entry->target = ip
+ 23 20 95 00 # rs1_a0 rs2_s1 sw ; entry->next = jump_table
+ 93 04 05 00 # rd_s1 rs1_a0 mv ; jump_table = entry
+ 23 24 95 01 # rs1_a0 rs2_s9 @8 sw ; entry->name = token
+ 93 85 0C 00 # rd_a1 rs1_s9 mv ; Write after struct
+ $c EF 00 00 00 # rd_ra $consume_token jal ; Collect string
+ 93 8C 05 00 # rd_s9 rs1_a1 mv ; update HEAP
+
+ 83 20 01 00 # rd_ra rs1_sp lw ; restore ra
+ 13 01 41 00 # rd_sp rs1_sp !4 addi ; deallocate stack
+ $1 6F 00 00 00 # $First_pass_loop jal ; return
+
+; fputc function
+; Receives CHAR in a0
+; Writes and returns number of bytes written in a0
+:t ;fputc
+ 13 01 01 FF # rd_sp rs1_sp !-16 addi ; allocate stack
+ 23 20 A1 00 # rs1_sp rs2_a0 sw ; protect a0
+ 23 22 11 00 # rs1_sp rs2_ra @4 sw ; protect ra
+ 23 24 B1 00 # rs1_sp rs2_a1 @8 sw ; protect a1
+ 23 26 C1 00 # rs1_sp rs2_a2 @12 sw ; protect a2
+
+ 93 08 00 04 # rd_a7 !64 addi ; sys_write
+ 13 85 09 00 # rd_a0 rs1_s3 mv ; write to output
+ 93 05 01 00 # rd_a1 rs1_sp mv ; Get stack address
+ 13 06 10 00 # rd_a2 !1 addi ; write 1 character
+ 73 00 00 00 # ecall ; syscall
+
+ 83 20 41 00 # rd_ra rs1_sp !4 lw ; restore ra
+ 83 25 81 00 # rd_a1 rs1_sp !8 lw ; restore a1
+ 03 26 C1 00 # rd_a2 rs1_sp !12 lw ; restore a2
+ 13 01 01 01 # rd_sp rs1_sp !16 addi ; Deallocate stack
+ 67 80 00 00 # rs1_ra jalr ; return
+
+:F ;Fail
+ ; Terminate program with 1 return code
+ 93 08 D0 05 # rd_a7 !93 addi ; sys_exit
+ 13 05 10 00 # rd_a0 !1 addi ; Return code 1
+ 73 00 00 00 # ecall ; exit(1)
+# PROGRAM END
+
+:s ;scratch
+ 00 00 00 00
+
+#:ELF_end