aboutsummaryrefslogtreecommitdiff
path: root/src/baremetal
diff options
context:
space:
mode:
authorGravatar Moon Flower <moonflower@elhenney.local> 2026-10-12 08:46:23 +0330
committerGravatar Moon Flower <moonflower@elhenney.local> 2026-10-12 08:46:23 +0330
commitecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 (patch)
tree70e0af76a1b13affff7c6cc8aac7b3e6ef960478 /src/baremetal
a
Diffstat (limited to 'src/baremetal')
-rw-r--r--src/baremetal/link_aarch64.ld37
-rw-r--r--src/baremetal/link_riscv.ld39
-rw-r--r--src/baremetal/port.c339
-rwxr-xr-xsrc/baremetal/run.sh104
-rw-r--r--src/baremetal/start_aarch64.S38
-rw-r--r--src/baremetal/start_riscv.S20
6 files changed, 577 insertions, 0 deletions
diff --git a/src/baremetal/link_aarch64.ld b/src/baremetal/link_aarch64.ld
new file mode 100644
index 0000000..6740f21
--- /dev/null
+++ b/src/baremetal/link_aarch64.ld
@@ -0,0 +1,37 @@
+OUTPUT_ARCH(aarch64)
+OUTPUT_FORMAT(elf64-littleaarch64)
+ENTRY(_start)
+
+/* virt RAM starts at 0x40000000. The PL011 UART is at 0x09000000, outside this. */
+MEMORY
+{
+ RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 48M
+}
+
+SECTIONS
+{
+ .text : {
+ KEEP(*(.text.init))
+ *(.text .text.*)
+ } > RAM
+ .rodata : {
+ *(.rodata .rodata.*)
+ } > RAM
+ .data : {
+ *(.data .data.*)
+ } > RAM
+ .bss : {
+ . = ALIGN(16);
+ _sbss = .;
+ *(.bss .bss.*)
+ *(COMMON)
+ . = ALIGN(16);
+ _ebss = .;
+ } > RAM
+ .stack : {
+ . = ALIGN(16);
+ . += 64K;
+ _stack_top = .;
+ } > RAM
+ /DISCARD/ : { *(.eh_frame*) *(.comment) *(.note*) }
+}
diff --git a/src/baremetal/link_riscv.ld b/src/baremetal/link_riscv.ld
new file mode 100644
index 0000000..cd470a4
--- /dev/null
+++ b/src/baremetal/link_riscv.ld
@@ -0,0 +1,39 @@
+OUTPUT_ARCH(riscv)
+OUTPUT_FORMAT(elf64-littleriscv)
+ENTRY(_start)
+
+MEMORY
+{
+ RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 48M
+}
+
+SECTIONS
+{
+ .text : {
+ KEEP(*(.text.init))
+ *(.text .text.*)
+ } > RAM
+ .rodata : {
+ *(.rodata .rodata.*)
+ *(.srodata .srodata.*)
+ } > RAM
+ .data : {
+ *(.data .data.*)
+ *(.sdata .sdata.*)
+ } > RAM
+ .bss : {
+ . = ALIGN(16);
+ _sbss = .;
+ *(.bss .bss.*)
+ *(.sbss .sbss.*)
+ *(COMMON)
+ . = ALIGN(16);
+ _ebss = .;
+ } > RAM
+ .stack : {
+ . = ALIGN(16);
+ . += 64K;
+ _stack_top = .;
+ } > RAM
+ /DISCARD/ : { *(.eh_frame*) *(.comment) *(.note*) *(.riscv.attributes) }
+}
diff --git a/src/baremetal/port.c b/src/baremetal/port.c
new file mode 100644
index 0000000..636c1ea
--- /dev/null
+++ b/src/baremetal/port.c
@@ -0,0 +1,339 @@
+/* UART port for the microcore. No libc. BARE_RISCV is the 16550 at
+ 0x10000000 on qemu's riscv virt. BARE_AARCH64 is the PL011 at 0x09000000
+ on qemu's aarch64 virt.
+
+ BARE_INPUT=0 (a program baked into the image, nothing typed): eof is
+ true and getc is -1. BARE_INPUT=1: bytes come from the UART. getc and
+ eof both wait until a byte is there. 0x04 (EOT) is end of input and is
+ not delivered. A gap is not the end, so a stream longer than the UART
+ FIFO still echoes until EOT. The compiler's backtick still ends a unit;
+ the byte after it is the program's first input, or EOT when the program
+ reads nothing.
+
+ Syscalls, same numbers as the hosted ports:
+ 1 open 2 close 3 read 4 write a small RAM file store
+ 5 getenv BOARD is "riscv" or "aarch64"
+ 6 time the hardware counter, not unix seconds
+ 7 exit(status)
+ 8 random
+ 9 memory 0 micro_mem_words(), 1 HEAP_LO, 2 HEAP_HI, 3 micro_mem_words()*8
+ 10 bopen 11 bread 12 bwrite 13 bgetenv
+ 10..13 use packed bytes. Anything else is -1. */
+#include "micro_port.h"
+#include "bops.h"
+
+#ifndef BARE_INPUT
+#define BARE_INPUT 0
+#endif
+
+#if defined(BARE_RISCV)
+static volatile unsigned char *const uart = (volatile unsigned char *)0x10000000;
+#define UART_LSR 5
+#define LSR_RX 0x01
+#define LSR_TX 0x20
+
+static void uart_init(void)
+{
+ /* QEMU's 16550 already speaks 8N1. Rewriting LCR or FCR after
+ bytes have arrived drops the first one, and the source is
+ already in the FIFO by the time this runs. */
+}
+
+static int rx_ready(void) { return uart[UART_LSR] & LSR_RX; }
+static void uart_write(unsigned char c)
+{
+ while ((uart[UART_LSR] & LSR_TX) == 0) ;
+ uart[0] = c;
+}
+static int uart_read(void)
+{
+ while (!rx_ready()) ;
+ return uart[0];
+}
+static unsigned long timer(void)
+{
+ unsigned long t;
+ __asm__ volatile("rdtime %0" : "=r"(t));
+ return t;
+}
+static void exit_status(int code)
+{
+ volatile unsigned *test = (volatile unsigned *)0x100000;
+ /* PASS exits 0. FAIL's high half is the status (16 bits). */
+ if (code == 0) *test = 0x5555;
+ else *test = ((unsigned)(code & 0xffff) << 16) | 0x3333;
+ for (;;) ;
+}
+#elif defined(BARE_AARCH64)
+static volatile unsigned *const uart = (volatile unsigned *)0x09000000;
+#define UART_FR 6 /* 0x18 / 4 */
+#define FR_RXFE 0x10
+#define FR_TXFF 0x20
+
+static void uart_init(void) { }
+static int rx_ready(void) { return (uart[UART_FR] & FR_RXFE) == 0; }
+static void uart_write(unsigned char c)
+{
+ while (uart[UART_FR] & FR_TXFF) ;
+ uart[0] = c;
+}
+static int uart_read(void)
+{
+ while (!rx_ready()) ;
+ return (int)uart[0];
+}
+static unsigned long timer(void)
+{
+ unsigned long t;
+ __asm__ volatile("mrs %0, cntvct_el0" : "=r"(t));
+ return t;
+}
+static void exit_status(int code)
+{
+ /* SYS_EXIT. x1 points at {ADP_Stopped_ApplicationExit, status}.
+ QEMU has to be started with -semihosting; PSCI SYSTEM_OFF cannot
+ carry a status. */
+ unsigned long blk[2];
+ register unsigned long x0 __asm__("x0") = 0x18;
+ register unsigned long x1 __asm__("x1");
+ blk[0] = 0x20026ul;
+ blk[1] = (unsigned long)code;
+ x1 = (unsigned long)blk;
+ __asm__ volatile("hlt #0xf000" :: "r"(x0), "r"(x1) : "memory");
+ for (;;) ;
+}
+#else
+#error define BARE_RISCV or BARE_AARCH64
+#endif
+
+static int saw_end, have_peek, peekc;
+
+void micro_putc(int c) { uart_write((unsigned char)c); }
+
+void micro_msg(const char *s)
+{
+ while (*s) uart_write((unsigned char)*s++);
+}
+
+/* the machine's memory on this board: the arena below, which is the board's RAM as
+ memmap.h sizes it. a board's RAM is a fixed amount of hardware, so this is the one
+ place the port has a size; it returns NULL past it. */
+/* physical backing is separate from the vm's configurable growth limit. */
+#ifndef MICRO_PORT_WORDS
+#define MICRO_PORT_WORDS (HEAP_HI > PW ? HEAP_HI : PW)
+#endif
+static Cell arena[MICRO_PORT_WORDS];
+
+Cell *micro_mem_alloc(UCell words)
+{
+ if (words > MICRO_PORT_WORDS) return 0;
+ return arena;
+}
+
+static int inmem(Cell a) { return (UCell)a < (UCell)micro_mem_words(); }
+
+static int zcstr(Cell *mem, Cell addr, char *dst, int max)
+{
+ int i = 0, c;
+ if (max < 1) return -1;
+ if (!inmem(addr)) { dst[0] = 0; return -1; }
+ while (i < max - 1 && inmem(addr + i)) {
+ c = (int)mem[addr + i];
+ if (!c) break;
+ dst[i++] = (char)(c & 255);
+ }
+ dst[i] = 0;
+ return i;
+}
+
+static int streq(const char *a, const char *b)
+{
+ int i = 0;
+ while (a[i] && a[i] == b[i]) i++;
+ return a[i] == b[i];
+}
+
+#define NFILES 4
+#define FCAP 128
+#define NLEN 32
+static struct {
+ char name[NLEN];
+ unsigned char data[FCAP];
+ int len, pos;
+} ramf[NFILES];
+
+static int ram_slot(const char *name, int create)
+{
+ int i, free_i = -1, n;
+ for (i = 0; i < NFILES; i++) {
+ if (!ramf[i].name[0]) { if (free_i < 0) free_i = i; continue; }
+ if (streq(ramf[i].name, name)) return i;
+ }
+ if (!create || free_i < 0) return -1;
+ for (n = 0; name[n] && n < NLEN - 1; n++) ramf[free_i].name[n] = name[n];
+ ramf[free_i].name[n] = 0;
+ ramf[free_i].len = 0;
+ ramf[free_i].pos = 0;
+ return free_i;
+}
+
+static UCell rng;
+static Cell rnd(void)
+{
+ if (!rng) rng = timer() ^ 0x9E3779B97F4A7C15ull;
+ if (!rng) rng = 0x9E3779B97F4A7C15ull;
+ rng ^= rng << 13;
+ rng ^= rng >> 7;
+ rng ^= rng << 17;
+ if ((Cell)rng == -1) rng ^= 0x100;
+ return (Cell)rng;
+}
+
+static void board_name(char *dst)
+{
+#if defined(BARE_RISCV)
+ dst[0] = 'r'; dst[1] = 'i'; dst[2] = 's'; dst[3] = 'c'; dst[4] = 'v'; dst[5] = 0;
+#else
+ dst[0] = 'a'; dst[1] = 'a'; dst[2] = 'r'; dst[3] = 'c'; dst[4] = 'h';
+ dst[5] = '6'; dst[6] = '4'; dst[7] = 0;
+#endif
+}
+
+Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
+{
+ char path[NLEN];
+ int fd, n, i;
+ switch ((int)op) {
+ case 1:
+ if (zcstr(mem, a, path, NLEN) < 0 || !path[0]) return -1;
+ fd = ram_slot(path, b == 1);
+ if (fd < 0) return -1;
+ if (b == 1) { ramf[fd].len = 0; ramf[fd].pos = 0; }
+ else ramf[fd].pos = 0;
+ return fd;
+ case 2:
+ if ((UCell)a >= NFILES || !ramf[a].name[0]) return -1;
+ ramf[a].pos = 0;
+ return 0;
+ case 3:
+ if ((UCell)a >= NFILES || !ramf[a].name[0] || c < 0 || !inmem(b)) return -1;
+ n = 0;
+ while (n < c && ramf[a].pos < ramf[a].len && inmem(b + n)) {
+ mem[b + n] = ramf[a].data[ramf[a].pos++];
+ n++;
+ }
+ return n;
+ case 4:
+ if ((UCell)a >= NFILES || !ramf[a].name[0] || c < 0 || !inmem(b)) return -1;
+ n = 0;
+ while (n < c && ramf[a].len < FCAP && inmem(b + n)) {
+ ramf[a].data[ramf[a].len++] = (unsigned char)mem[b + n];
+ n++;
+ }
+ ramf[a].pos = ramf[a].len;
+ return n;
+ case 5: {
+ char board[8], *e;
+ if (zcstr(mem, a, path, NLEN) < 0) return -1;
+ board_name(board);
+ e = streq(path, "BOARD") ? board : 0;
+ if (!e) return -1;
+ if (c < 1 || !inmem(b)) return -1;
+ i = 0;
+ while (e[i] && i < c - 1 && inmem(b + i)) { mem[b + i] = (unsigned char)e[i]; i++; }
+ if (inmem(b + i)) mem[b + i] = 0;
+ return i;
+ }
+ case 6:
+ return (Cell)timer();
+ case 7:
+ exit_status((int)a);
+ return 0;
+ case 8:
+ return rnd();
+ case 9:
+ if (a == 0) return micro_mem_words();
+ if (a == 1) return HEAP_LO;
+ if (a == 2) return HEAP_HI;
+ if (a == 3) return (Cell)micro_mem_words() * 8;
+ return -1;
+ case 10:
+ if (bop_cstr(mem, a, path, NLEN, micro_mem_words()) < 0 || !path[0]) return -1;
+ fd = ram_slot(path, b == 1);
+ if (fd < 0) return -1;
+ if (b == 1) { ramf[fd].len = 0; ramf[fd].pos = 0; }
+ else ramf[fd].pos = 0;
+ return fd;
+ case 11:
+ if ((UCell)a >= NFILES || !ramf[a].name[0] || c < 0 || (c > 0 && !bop_ok(b, 1, micro_mem_words()))) return -1;
+ n = 0;
+ while (n < c && ramf[a].pos < ramf[a].len && bop_ok(b + n, 1, micro_mem_words())) {
+ bop_put1(mem, b + n, ramf[a].data[ramf[a].pos++]);
+ n++;
+ }
+ return n;
+ case 12:
+ if ((UCell)a >= NFILES || !ramf[a].name[0] || c < 0 || (c > 0 && !bop_ok(b, 1, micro_mem_words()))) return -1;
+ n = 0;
+ while (n < c && ramf[a].len < FCAP && bop_ok(b + n, 1, micro_mem_words())) {
+ ramf[a].data[ramf[a].len++] = (unsigned char)bop_load(mem, b + n, 1);
+ n++;
+ }
+ ramf[a].pos = ramf[a].len;
+ return n;
+ case 13: {
+ char board[8], *e;
+ if (bop_cstr(mem, a, path, NLEN, micro_mem_words()) < 0) return -1;
+ board_name(board);
+ e = streq(path, "BOARD") ? board : 0;
+ if (!e) return -1;
+ if (c < 1 || !bop_ok(b, 1, micro_mem_words())) return -1;
+ i = 0;
+ while (e[i] && i < c - 1 && bop_ok(b + i, 1, micro_mem_words())) { bop_put1(mem, b + i, (unsigned char)e[i]); i++; }
+ if (bop_ok(b + i, 1, micro_mem_words())) bop_put1(mem, b + i, 0);
+ return i;
+ }
+ case 21: /* argc: a board has no command line, so <eval> alone */
+ return 1;
+ case 22: { /* argv: entry 0 is <eval>, as in zy(1) */
+ static const char lead[] = "<eval>";
+ if (a != 0) return -1;
+ if (!b) return 6;
+ if (c < 7 || !inmem(b) || !inmem(b + 6)) return -1;
+ for (i = 0; i < 6; i++) mem[b + i] = (unsigned char)lead[i];
+ mem[b + 6] = 0;
+ return 6;
+ }
+ case 23: /* envc: a board has no environment */
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+int micro_getc(void)
+{
+ int ch;
+ if (!BARE_INPUT || saw_end) return -1;
+ if (have_peek) { have_peek = 0; ch = peekc; }
+ else ch = uart_read();
+ if (ch == 4) { saw_end = 1; return -1; }
+ return ch;
+}
+
+int micro_eof(void)
+{
+ if (!BARE_INPUT || saw_end) return 1;
+ if (!have_peek) {
+ peekc = uart_read(); /* wait: an empty FIFO is not the end */
+ have_peek = 1;
+ }
+ if (peekc == 4) { saw_end = 1; have_peek = 0; return 1; }
+ return 0;
+}
+
+void bare_main(void)
+{
+ uart_init();
+ exit_status(micro_run() != 0);
+}
diff --git a/src/baremetal/run.sh b/src/baremetal/run.sh
new file mode 100755
index 0000000..d6ac333
--- /dev/null
+++ b/src/baremetal/run.sh
@@ -0,0 +1,104 @@
+#!/bin/bash
+# Build a freestanding microcore and boot it on qemu-system-*.
+# src/baremetal/run.sh ready riscv|aarch64
+# src/baremetal/run.sh riscv|aarch64 run examples/hello.zc
+# src/baremetal/run.sh riscv|aarch64 io examples/echo.zc
+# src/baremetal/run.sh riscv|aarch64 compile examples/hello.zc
+# run: the program is compiled into the image. nothing is read.
+# io: the same image, but stdin is the program's input and 0x04 ends it.
+# compile: the default 1,048,576-word genome. the source, a backtick, and
+# 0x04 are written to the UART. the backtick ends the unit; the
+# 0x04 is what eof() waits for after that.
+# Prints the program's UART output and nothing else. Exits 0 on a clean
+# power-off. A missing toolchain exits 0 from `ready` and 1 otherwise.
+set -euo pipefail
+cd "$(dirname "$0")/../.."
+
+ready() {
+ case "$1" in
+ riscv) command -v riscv64-linux-gnu-gcc >/dev/null && command -v qemu-system-riscv64 >/dev/null ;;
+ aarch64) command -v aarch64-linux-gnu-gcc >/dev/null && command -v qemu-system-aarch64 >/dev/null ;;
+ *) return 1 ;;
+ esac
+}
+
+if [ "${1:-}" = ready ]; then
+ ready "${2:-}"
+ exit $?
+fi
+if [ $# -ne 3 ]; then
+ echo "usage: src/baremetal/run.sh riscv|aarch64 run|io|compile FILE" >&2
+ exit 2
+fi
+target=$1 mode=$2 src=$3
+ready "$target" || { echo "baremetal: missing toolchain for $target" >&2; exit 1; }
+[ -f "$src" ] || { echo "baremetal: no $src" >&2; exit 1; }
+
+case "$target" in
+riscv)
+ cc=riscv64-linux-gnu-gcc
+ qemu=qemu-system-riscv64
+ cflags="-march=rv64gc -mabi=lp64d -mcmodel=medany -mno-relax"
+ asm=src/baremetal/start_riscv.S
+ ld=src/baremetal/link_riscv.ld
+ def=-DBARE_RISCV
+ qargs=(-machine virt -bios none -cpu rv64 -m 64)
+ ;;
+aarch64)
+ cc=aarch64-linux-gnu-gcc
+ qemu=qemu-system-aarch64
+ cflags="-march=armv8-a"
+ asm=src/baremetal/start_aarch64.S
+ ld=src/baremetal/link_aarch64.ld
+ def=-DBARE_AARCH64
+ qargs=(-machine virt -cpu cortex-a57 -m 64 -semihosting-config enable=on,target=native)
+ ;;
+*) echo "baremetal: unknown target $target" >&2; exit 2 ;;
+esac
+
+D=$(mktemp -d)
+trap 'rm -rf "$D"' EXIT
+common="$cflags -ffreestanding -fno-pic -fno-pie -fno-builtin -fno-unwind-tables -fno-asynchronous-unwind-tables -O2 -Wall -Wextra"
+
+if [ "$mode" = run ] || [ "$mode" = io ]; then
+ # the map is sized to the program, so a bigger runtime or program just gets a bigger board
+ gcc -O2 -w src/tools/genmap.c -o "$D/genmap"
+ GENMAP="$D/genmap" bash src/tools/automap.sh run "$D/img" src/runtime/run.zc "$src" >"$D/genmap.log" 2>&1 || { cat "$D/genmap.log" >&2; exit 1; }
+ cp src/micro/micro.c src/micro/micro_port.h src/core/fops.h src/core/bops.h src/core/stack.h "$D/img/"
+ "$cc" $common $def -I"$D/img" -c "$D/img/micro.c" -o "$D/micro.o"
+ if [ "$mode" = io ]; then
+ "$cc" $common $def -DBARE_INPUT=1 -I"$D/img" -c src/baremetal/port.c -o "$D/port.o"
+ timeout=60
+ else
+ "$cc" $common $def -DBARE_INPUT=0 -I"$D/img" -c src/baremetal/port.c -o "$D/port.o"
+ timeout=120
+ fi
+elif [ "$mode" = compile ]; then
+ cp src/micro/micro.c src/micro/micro_port.h src/core/memmap.h src/core/genome.h src/core/fops.h src/core/bops.h src/core/stack.h "$D/"
+ "$cc" $common $def -I"$D" -c "$D/micro.c" -o "$D/micro.o"
+ "$cc" $common $def -DBARE_INPUT=1 -I"$D" -c src/baremetal/port.c -o "$D/port.o"
+ timeout=90
+else
+ echo "baremetal: mode is run, io or compile" >&2
+ exit 2
+fi
+"$cc" $common $def -c "$asm" -o "$D/start.o"
+"$cc" $common $def -nostdlib -static -Wl,--build-id=none -Wl,--no-warn-rwx-segments -T "$ld" -o "$D/bare.elf" "$D/start.o" "$D/port.o" "$D/micro.o"
+if [ "${BARE_SIZE:-}" = 1 ]; then
+ size=$(wc -c < "$D/bare.elf")
+ text=$(size "$D/bare.elf" | awk 'NR==2{print $1+$2}')
+ bss=$(size "$D/bare.elf" | awk 'NR==2{print $3}')
+ echo "elf $size bytes, text+data $text, bss $bss" >&2
+fi
+
+# -serial stdio is the UART. The monitor stays off so it cannot eat bytes.
+runq() {
+ timeout "$timeout" "$qemu" "${qargs[@]}" -nographic -monitor none -serial stdio -kernel "$D/bare.elf"
+}
+if [ "$mode" = compile ]; then
+ (cat "$src"; printf '`\004') | runq
+elif [ "$mode" = io ]; then
+ (cat; printf '\004') | runq
+else
+ runq </dev/null
+fi
diff --git a/src/baremetal/start_aarch64.S b/src/baremetal/start_aarch64.S
new file mode 100644
index 0000000..36b2752
--- /dev/null
+++ b/src/baremetal/start_aarch64.S
@@ -0,0 +1,38 @@
+/* AArch64 virt. Drop EL2 to EL1 if we came up there, enable SIMD
+ (the compiler copies the genome with q registers), then stack, bss, C. */
+.section .text.init
+.global _start
+_start:
+ mrs x0, CurrentEL
+ lsr x0, x0, #2
+ cmp x0, #2
+ b.ne 1f
+ mov x0, #(1 << 31) /* EL1 is AArch64 */
+ msr HCR_EL2, x0
+ mrs x0, CPTR_EL2
+ bic x0, x0, #(1 << 10) /* do not trap SIMD to EL2 */
+ msr CPTR_EL2, x0
+ mov x0, #0x3c5 /* EL1h, interrupts masked */
+ msr SPSR_EL2, x0
+ adr x0, 1f
+ msr ELR_EL2, x0
+ eret
+1:
+ mrs x0, CPACR_EL1
+ orr x0, x0, #(3 << 20) /* FPEN: SIMD does not trap */
+ msr CPACR_EL1, x0
+ isb
+ ldr x0, =_stack_top
+ mov sp, x0
+ ldr x0, =_sbss
+ ldr x1, =_ebss
+2:
+ cmp x0, x1
+ b.hs 3f
+ str xzr, [x0], #8
+ b 2b
+3:
+ bl bare_main
+4:
+ wfe
+ b 4b
diff --git a/src/baremetal/start_riscv.S b/src/baremetal/start_riscv.S
new file mode 100644
index 0000000..527f109
--- /dev/null
+++ b/src/baremetal/start_riscv.S
@@ -0,0 +1,20 @@
+/* RISC-V virt, machine mode, no firmware. Stack, clear bss, enter C. */
+.section .text.init
+.global _start
+_start:
+ /* FS = Dirty, so a compiler that emits float ops does not trap. */
+ li t0, 0x6000
+ csrs mstatus, t0
+ la sp, _stack_top
+ la t0, _sbss
+ la t1, _ebss
+1:
+ bgeu t0, t1, 2f
+ sd zero, 0(t0)
+ addi t0, t0, 8
+ j 1b
+2:
+ call bare_main
+3:
+ wfi
+ j 3b