boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

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:
MM1pp/M1pp.P1 | 10+++++++---
MMakefile | 65+++++++++++++++++++++++++++++++++++++++++++++++++----------------
MP1/P1-aarch64.M1 | 7+++++--
MP1/P1-aarch64.M1pp | 25+++++++++++++++++++++++++
MP1/P1-amd64.M1 | 7+++++--
MP1/P1-amd64.M1pp | 25+++++++++++++++++++++++++
AP1/P1-riscv32.M1 | 613+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AP1/P1-riscv32.M1pp | 672+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MP1/P1-riscv64.M1 | 7+++++--
MP1/P1-riscv64.M1pp | 25+++++++++++++++++++++++++
MP1/P1pp.P1pp | 329+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
MP1/gen/p1_gen.py | 28+++++++++++++++++++++++-----
AP1/gen/riscv32.py | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MREADME.md | 57+++++++++++++++++++++++++++++++++++++++------------------
Mboot/boot.sh | 11+++++++++--
Mboot/boot3.sh | 1+
Mboot/boot4.sh | 1+
Mboot/boot5.sh | 1+
Mboot/boot6.sh | 1+
Mboot/boot7.sh | 1+
Mboot/lib-arch.sh | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mboot/lib-pipeline.sh | 5++---
Mboot/lib-runscm.sh | 5++---
Mbootprep/prep-src.sh | 10+++++++++-
Mcatm/catm.P1pp | 4++--
Mcc/cc.scm | 1305+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mdocs/CC.md | 65++++++++++++++++++++++++++++++++++++++++++-----------------------
Mdocs/CCSCM.md | 10+++++-----
Mdocs/LIBP1PP.md | 34++++++++++++++++++++++++----------
Mdocs/M1PP.md | 17+++++++++++++----
Mdocs/OS.md | 54++++++++++++++++++++++++++++++++----------------------
Mdocs/P1.md | 12++++++++++++
Mdocs/R7RS-micro.md | 10+++++++---
Mdocs/SCHEME1-GC.md | 17+++++++++--------
Mdocs/SCHEME1.md | 15++++++++++-----
Mdocs/TOUR.md | 12+++++++++++-
Mhex2pp/hex2pp.P1 | 6++++--
Mscheme1/scheme1.P1pp | 92+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Atests/M1pp/003-builtins.expected.riscv32 | 15+++++++++++++++
Atests/M1pp/004-expr-ops.expected.riscv32 | 43+++++++++++++++++++++++++++++++++++++++++++
Atests/M1pp/005-int-atoms.expected.riscv32 | 13+++++++++++++
Mtests/Makefile | 31+++++++++++++++++++++++++------
Mtests/P1/014-sys_calls.P1pp | 2+-
Mtests/README.md | 14++++++++++++++
Mtests/boot-run-scheme1.sh | 2+-
Mtests/build-cc.sh | 2+-
Mtests/build-p1.sh | 2+-
Mtests/build-p1pp.sh | 2+-
Mtests/cc-cg/003-two-params.scm | 2+-
Mtests/cc-cg/010-string.scm | 8++++----
Mtests/cc-cg/017-int-roundtrip.scm | 9++++++---
Mtests/cc-cg/024-cmpd-ptr.scm | 4++--
Mtests/cc-cg/025-deref-postinc.scm | 4++--
Mtests/cc-cg/038-arrow.scm | 4++--
Mtests/cc-cg/050-init-addr.scm | 2+-
Mtests/cc-cg/051-init-array-list.scm | 2+-
Mtests/cc-cg/052-init-array-str.scm | 2+-
Mtests/cc-cg/053-init-struct-pos.scm | 2+-
Mtests/cc-cg/054-init-struct-desig.scm | 2+-
Mtests/cc-cg/058-fnptr-tab.scm | 10+++++-----
Mtests/cc-cg/067-fnptr-call.scm | 4++--
Mtests/cc-cg/069-vararg-recv.scm | 4++--
Mtests/cc-cg/071-struct-ret-2word.scm | 25+++++++++++++------------
Mtests/cc-cg/080-uneg-canonical.scm | 3++-
Mtests/cc-cg/081-ubnot-canonical.scm | 3++-
Mtests/cc-cg/082-uadd-wrap-canonical.scm | 3++-
Mtests/cc-cg/085-i32-u32-eq.scm | 3++-
Mtests/cc-lex/002-integers.c | 1+
Mtests/cc-lex/002-integers.expected | 7++++++-
Mtests/cc-lex/_run-lex.scm | 2+-
Mtests/cc-pp/_run-pp.scm | 2+-
Atests/cc-util/014-c-value-small.scm | 29+++++++++++++++++++++++++++++
Mtests/cc/027-sizeof-types.c | 9+++++----
Atests/cc/027-sizeof-types.expected-exit.riscv32 | 1+
Mtests/cc/111-struct-ret-1word.c | 9+++++----
Mtests/cc/112-struct-ret-2word.c | 12++++++------
Mtests/cc/118-const-expr.c | 6+++---
Mtests/cc/128-cast-signedness.c | 5+++--
Mtests/cc/131-vararg-mixed.c | 26+++++++++++++-------------
Mtests/cc/132-tentative-bss-sizing.c | 20+++++++++++---------
Atests/cc/342-i64-two-word.c | 42++++++++++++++++++++++++++++++++++++++++++
Atests/cc/343-i64-vararg-switch.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Atests/cc/344-integer-literal-types.c | 25+++++++++++++++++++++++++
Atests/cc/345-pointer-ternary.c | 21+++++++++++++++++++++
Mtests/run-suite.sh | 58+++++++++++++++++++++++++++++++++++++++++-----------------
Mtests/run.sh | 22++++++++++++++++++++--
Mtests/scheme1/116-hex-conv.scm | 6+++---
Mtests/scheme1/149-target-bindings.scm | 4++++
Atests/scheme1/151-exact-boundaries.scm.riscv32 | 13+++++++++++++
Mtools/lint.sh | 2+-
Mvendor/seed/README.md | 15++++++++++++---
Avendor/seed/riscv32/ELF.hex2 | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Avendor/seed/riscv32/M0.hex2 | 1701+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Avendor/seed/riscv32/catm.hex2 | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Avendor/seed/riscv32/hex0-seed | 0
Avendor/seed/riscv32/hex0.hex0 | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Avendor/seed/riscv32/hex1.hex0 | 648+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Avendor/seed/riscv32/hex2.hex1 | 904+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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