commit 7a1635af1afbf2c4008f2b43cf3762acc215f10e
parent dcd943c71dc2e62e286ab77c062d55083b6921ff
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 5 May 2026 12:03:20 -0700
seed-kernel/user: factor sysc into shared syscall6 toplevel-asm thunk
hello/forktest/child each carried the same x8/x0..x5 inline-asm sysc
helper. Hoist it into a single syscall6 entry in the per-program
toplevel asm() block, declared extern in C — same codegen, prepares
the user programs for tcc3 self-host (tcc 0.9.26 silently drops
register-asm constraints; toplevel-asm avoids that path entirely).
The label-before-.globl ordering inside each asm() block works around
a tcc 0.9.26 quirk where ".globl name; name:" leaves the symbol UND.
Diffstat:
3 files changed, 73 insertions(+), 58 deletions(-)
diff --git a/seed-kernel/user/child.c b/seed-kernel/user/child.c
@@ -7,17 +7,10 @@ typedef unsigned long u64;
#define SYS_write 64
#define SYS_exit_group 93
-static i64 sysc(u64 nr, u64 a, u64 b, u64 c) {
- register u64 x8 asm("x8") = nr;
- register u64 x0 asm("x0") = a;
- register u64 x1 asm("x1") = b;
- register u64 x2 asm("x2") = c;
- asm volatile("svc #0" : "+r"(x0) : "r"(x8), "r"(x1), "r"(x2) : "memory", "cc");
- return (i64)x0;
-}
+extern i64 syscall6(u64 nr, u64 a, u64 b, u64 c, u64 d, u64 e, u64 f);
-static i64 sys_write(int fd, const void *buf, u64 n) { return sysc(SYS_write, (u64)fd, (u64)buf, n); }
-static void sys_exit(int c) { sysc(SYS_exit_group, (u64)c, 0, 0); for(;;); }
+static i64 sys_write(int fd, const void *buf, u64 n) { return syscall6(SYS_write, (u64)fd, (u64)buf, n, 0,0,0); }
+static void sys_exit(int c) { syscall6(SYS_exit_group, (u64)c, 0,0,0,0,0); for(;;); }
void *memset(void *d, int c, u64 n) {
unsigned char *dd = d; for (u64 i = 0; i < n; i++) dd[i] = (unsigned char)c; return d;
@@ -42,10 +35,23 @@ void _start_c(long argc, char **argv) {
sys_exit(42);
}
+/* See forktest.c for the .globl-after-label tcc 0.9.26 quirk. */
asm(
+ "_start: ldr x0, [sp]\n"
".globl _start\n"
".type _start, %function\n"
- "_start:\n"
- " ldr x0, [sp]\n"
" add x1, sp, #8\n"
- " b _start_c\n");
+ " b _start_c\n"
+ "\n"
+ "syscall6:\n"
+ ".globl syscall6\n"
+ ".type syscall6, %function\n"
+ " mov x8, x0\n"
+ " mov x0, x1\n"
+ " mov x1, x2\n"
+ " mov x2, x3\n"
+ " mov x3, x4\n"
+ " mov x4, x5\n"
+ " mov x5, x6\n"
+ " svc #0\n"
+ " ret\n");
diff --git a/seed-kernel/user/forktest.c b/seed-kernel/user/forktest.c
@@ -17,25 +17,12 @@ typedef int i32;
#define SYS_waitid 95
#define SYS_spawn 1024
-static i64 sysc(u64 nr, u64 a, u64 b, u64 c, u64 d, u64 e, u64 f) {
- register u64 x8 asm("x8") = nr;
- register u64 x0 asm("x0") = a;
- register u64 x1 asm("x1") = b;
- register u64 x2 asm("x2") = c;
- register u64 x3 asm("x3") = d;
- register u64 x4 asm("x4") = e;
- register u64 x5 asm("x5") = f;
- asm volatile("svc #0"
- : "+r"(x0)
- : "r"(x8), "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x5)
- : "memory", "cc");
- return (i64)x0;
-}
+extern i64 syscall6(u64 nr, u64 a, u64 b, u64 c, u64 d, u64 e, u64 f);
-static i64 sys_write(int fd, const void *buf, u64 n) { return sysc(SYS_write, (u64)fd, (u64)buf, n, 0,0,0); }
-static void sys_exit(int c) { sysc(SYS_exit_group, (u64)c, 0,0,0,0,0); for(;;); }
-static i64 sys_spawn(const char *p, char **argv) { return sysc(SYS_spawn, (u64)p, (u64)argv, 0, 0, 0, 0); }
-static i64 sys_waitid(int id, int pid, void *info, int opts) { return sysc(SYS_waitid, (u64)id, (u64)pid, (u64)info, (u64)opts, 0, 0); }
+static i64 sys_write(int fd, const void *buf, u64 n) { return syscall6(SYS_write, (u64)fd, (u64)buf, n, 0,0,0); }
+static void sys_exit(int c) { syscall6(SYS_exit_group, (u64)c, 0,0,0,0,0); for(;;); }
+static i64 sys_spawn(const char *p, char **argv) { return syscall6(SYS_spawn, (u64)p, (u64)argv, 0, 0, 0, 0); }
+static i64 sys_waitid(int id, int pid, void *info, int opts) { return syscall6(SYS_waitid, (u64)id, (u64)pid, (u64)info, (u64)opts, 0, 0); }
void *memset(void *d, int c, u64 n) {
unsigned char *dd = d; for (u64 i = 0; i < n; i++) dd[i] = (unsigned char)c; return d;
@@ -70,10 +57,28 @@ void _start_c(long argc, char **argv) {
sys_exit(0);
}
+/* tcc 0.9.26 quirk: in a toplevel asm() block, `.globl name` followed by
+ * `name:` leaves the symbol UND in the .o symtab. Putting the label
+ * first and `.globl name` after makes tcc register it as defined. gcc
+ * accepts both orderings. */
asm(
+ "_start: ldr x0, [sp]\n"
".globl _start\n"
".type _start, %function\n"
- "_start:\n"
- " ldr x0, [sp]\n"
" add x1, sp, #8\n"
- " b _start_c\n");
+ " b _start_c\n"
+ "\n"
+ /* syscall6(nr, a..f) — args land in x0..x6 by SysV ABI. Linux
+ * arm64 wants the syscall number in x8 and a..f in x0..x5. */
+ "syscall6:\n"
+ ".globl syscall6\n"
+ ".type syscall6, %function\n"
+ " mov x8, x0\n"
+ " mov x0, x1\n"
+ " mov x1, x2\n"
+ " mov x2, x3\n"
+ " mov x3, x4\n"
+ " mov x4, x5\n"
+ " mov x5, x6\n"
+ " svc #0\n"
+ " ret\n");
diff --git a/seed-kernel/user/hello.c b/seed-kernel/user/hello.c
@@ -12,27 +12,14 @@ typedef unsigned long u64;
#define SYS_brk 214
#define SYS_exit_group 93
-static i64 sysc(u64 nr, u64 a, u64 b, u64 c, u64 d, u64 e, u64 f) {
- register u64 x8 asm("x8") = nr;
- register u64 x0 asm("x0") = a;
- register u64 x1 asm("x1") = b;
- register u64 x2 asm("x2") = c;
- register u64 x3 asm("x3") = d;
- register u64 x4 asm("x4") = e;
- register u64 x5 asm("x5") = f;
- asm volatile("svc #0"
- : "+r"(x0)
- : "r"(x8), "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x5)
- : "memory", "cc");
- return (i64)x0;
-}
+extern i64 syscall6(u64 nr, u64 a, u64 b, u64 c, u64 d, u64 e, u64 f);
-static i64 sys_write(int fd, const void *buf, u64 n) { return sysc(SYS_write, (u64)fd, (u64)buf, n, 0,0,0); }
-static void sys_exit(int c) { sysc(SYS_exit_group, (u64)c, 0,0,0,0,0); for(;;); }
-static i64 sys_openat(int dfd, const char *p, int fl, int mo) { return sysc(SYS_openat, (u64)dfd, (u64)p, (u64)fl, (u64)mo, 0,0); }
-static i64 sys_read(int fd, void *b, u64 n) { return sysc(SYS_read, (u64)fd, (u64)b, n, 0,0,0); }
-static i64 sys_close(int fd) { return sysc(SYS_close, (u64)fd, 0,0,0,0,0); }
-static i64 sys_brk(u64 a) { return sysc(SYS_brk, a, 0,0,0,0,0); }
+static i64 sys_write(int fd, const void *buf, u64 n) { return syscall6(SYS_write, (u64)fd, (u64)buf, n, 0,0,0); }
+static void sys_exit(int c) { syscall6(SYS_exit_group, (u64)c, 0,0,0,0,0); for(;;); }
+static i64 sys_openat(int dfd, const char *p, int fl, int mo) { return syscall6(SYS_openat, (u64)dfd, (u64)p, (u64)fl, (u64)mo, 0,0); }
+static i64 sys_read(int fd, void *b, u64 n) { return syscall6(SYS_read, (u64)fd, (u64)b, n, 0,0,0); }
+static i64 sys_close(int fd) { return syscall6(SYS_close, (u64)fd, 0,0,0,0,0); }
+static i64 sys_brk(u64 a) { return syscall6(SYS_brk, a, 0,0,0,0,0); }
/* gcc may emit calls to memset/memcpy for stack zeroing or struct copies. */
void *memset(void *d, int c, u64 n) {
@@ -91,8 +78,12 @@ void _start_c(long argc, char **argv) {
unsigned char m[4] = {0};
i64 n = sys_read(fd, m, 4);
puts_("read("); put_d(n); puts_(") magic = ");
+ static const char hex[] = "0123456789abcdef";
for (int i = 0; i < 4; i++) {
- char c[3] = { "0123456789abcdef"[m[i]>>4], "0123456789abcdef"[m[i]&0xf], ' ' };
+ char c[3];
+ c[0] = hex[m[i] >> 4];
+ c[1] = hex[m[i] & 0xf];
+ c[2] = ' ';
sys_write(1, c, 3);
}
puts_("\n");
@@ -121,10 +112,23 @@ void _start_c(long argc, char **argv) {
* kernel sets sp_el0 to point at [argc][argv[0]]... before ERETing.
* Emitted as a plain global symbol with raw asm — no C-compiler-generated
* prologue, since gcc would clobber sp before we read argc. */
+/* See forktest.c for the .globl-after-label tcc 0.9.26 quirk. */
asm(
+ "_start: ldr x0, [sp]\n"
".globl _start\n"
".type _start, %function\n"
- "_start:\n"
- " ldr x0, [sp]\n"
" add x1, sp, #8\n"
- " b _start_c\n");
+ " b _start_c\n"
+ "\n"
+ "syscall6:\n"
+ ".globl syscall6\n"
+ ".type syscall6, %function\n"
+ " mov x8, x0\n"
+ " mov x0, x1\n"
+ " mov x1, x2\n"
+ " mov x2, x3\n"
+ " mov x3, x4\n"
+ " mov x4, x5\n"
+ " mov x5, x6\n"
+ " svc #0\n"
+ " ret\n");