lispcc-syscall.c (6743B)
1 /* lispcc-syscall.c — replaces mes's per-arch inline-asm syscall wrappers 2 * with C wrappers that call P1pp's labelled syscall entry points. 3 * 4 * The P1pp labels (sys_read / sys_write / sys_open / sys_close / 5 * sys_lseek / sys_brk / sys_unlink / sys_exit) are defined in 6 * P1/P1pp.P1pp and resolved at the linker stage thanks to cc.scm's 7 * external-linkage rule (commit 6488cca). 8 * 9 * Layering: 10 * public libc (read, write, open, …) — comes from the posix layer 11 * └─ low-level _read, _write, _open3 — provided here 12 * └─ named sys_* labels — provided by P1pp.P1pp 13 * 14 * mes's linux/{brk,close,lseek,_open3,_read,unlink}.c are NOT vendored; 15 * the equivalents live here and bypass mes's _sys_callN indirection. 16 * mes's linux/<arch>-mes-{mescc,gcc}/{syscall,_exit,_write,…}.c are 17 * also unused — those are the inline-asm files we replace. 18 */ 19 20 #include <mes/lib.h> 21 #include <fcntl.h> 22 #include <errno.h> 23 #include <stdio.h> 24 #include <unistd.h> 25 #include <sys/stat.h> 26 27 /* mes's <stdio.h> defines stdin/stdout/stderr as macros expanding to 28 * (FILE*)0/1/2. That works for libc's own .c files, but client code 29 * (cc-libc tests, eventually tcc-boot2's compiled output) that uses 30 * `extern FILE *stdout;` needs a real linkage symbol. Drop the macros 31 * after libc's headers have set their guards, then define globals 32 * with the same initializer values. mes's libc bodies that already 33 * referenced `stdout` had the macro substitution happen on their 34 * #include path; subsequent .c files re-include <stdio.h> but hit 35 * the include guard and so see the now-undef'd identifiers, which 36 * resolve to our globals at link time (same numeric value, same fd). */ 37 #undef stdin 38 #undef stdout 39 #undef stderr 40 FILE *stdin = (FILE *) 0; 41 FILE *stdout = (FILE *) 1; 42 FILE *stderr = (FILE *) 2; 43 44 /* `__stdout` and friends (the int-typed fd globals oputs / fdputs read) 45 * are file-scope initialized in mes/mes_open.c — but that initializer 46 * is gated behind `#if SYSTEM_LIBC`, which we don't define. Without 47 * SYSTEM_LIBC we'd be left with only mes/globals.c's tentative 48 * `int __stdout;`, which zero-inits — meaning oputs("ok") writes to 49 * fd 0 (read-only stdin) and silently no-ops. unified-libc.c includes 50 * lispcc-syscall.c before globals.c, so cc.scm's redecl-merge sees 51 * the proper definition first and absorbs the later tentative. */ 52 int __stdin = 0; 53 int __stdout = 1; 54 int __stderr = 2; 55 56 extern long sys_read (long fd, long buf, long n); 57 extern long sys_write (long fd, long buf, long n); 58 extern long sys_open (long path, long flags, long mode); 59 extern long sys_close (long fd); 60 extern long sys_lseek (long fd, long off, long whence); 61 extern long sys_brk (long addr); 62 extern long sys_unlink (long path); 63 extern long sys_exit (long code); 64 65 /* mes/globals.c already provides `int errno;` and the FILE-helper 66 * indirections (__buffered_read_clear, __ungetc_clear, __ungetc_init). 67 * mes/__buffered_read.c and mes/fdgetc.c implement them. */ 68 69 ssize_t 70 _read (int filedes, void *buffer, size_t size) 71 { 72 return sys_read (filedes, (long) buffer, (long) size); 73 } 74 75 ssize_t 76 _write (int filedes, void const *buffer, size_t size) 77 { 78 return sys_write (filedes, (long) buffer, (long) size); 79 } 80 81 int 82 _open3 (char const *file_name, int flags, int mask) 83 { 84 int r = sys_open ((long) file_name, flags, mask); 85 __ungetc_init (); 86 if (r > 2) 87 { 88 if (r >= __FILEDES_MAX) 89 { 90 errno = EMFILE; 91 return -1; 92 } 93 __ungetc_clear (r); 94 __buffered_read_clear (r); 95 } 96 return r; 97 } 98 99 int 100 close (int filedes) 101 { 102 long r = sys_close (filedes); 103 if (r < 0) 104 { 105 errno = -r; 106 return -1; 107 } 108 errno = 0; 109 return (int) r; 110 } 111 112 off_t 113 _lseek (int filedes, off_t offset, int whence) 114 { 115 return sys_lseek (filedes, offset, whence); 116 } 117 118 off_t 119 lseek (int filedes, off_t offset, int whence) 120 { 121 /* mirrors mes's linux/lseek.c: drain the per-fd read buffer before 122 * letting the kernel see a positional move; SEEK_CUR offsets must 123 * back out the buffered-but-not-yet-consumed bytes. */ 124 size_t skip = __buffered_read_clear (filedes); 125 if (whence == SEEK_CUR) 126 offset -= skip; 127 return sys_lseek (filedes, offset, whence); 128 } 129 130 long 131 brk (void *addr) 132 { 133 return sys_brk ((long) addr); 134 } 135 136 int 137 unlink (char const *file_name) 138 { 139 long r = sys_unlink ((long) file_name); 140 if (r < 0) 141 { 142 errno = -r; 143 return -1; 144 } 145 errno = 0; 146 return 0; 147 } 148 149 void 150 _exit (int status) 151 { 152 sys_exit (status); 153 } 154 155 /* Linux aarch64/amd64/riscv64 stack at exec entry: 156 * [sp] = argc 157 * [sp + 8 * (1)] = argv[0] 158 * ... 159 * [sp + 8 * (argc+1)] = NULL (terminates argv) 160 * [sp + 8 * (argc+2)] = envp[0] 161 * Our :_start passes (a0=argc, a1=argv) to :p1_main. envp follows 162 * argv's NULL terminator, so we compute it from argv. tcc's getenv 163 * walks the resulting environ; without initialization it dereferences 164 * NULL on the first call and segfaults during tcc_new(). */ 165 extern char **environ; 166 167 void 168 __libc_init (int argc, char **argv) 169 { 170 (void) argc; 171 char **p = argv; 172 while (*p) 173 p++; 174 environ = p + 1; 175 } 176 177 /* ---- ENOSYS stubs for libc-internal references not exercised by ---- 178 * tcc-boot2's golden path. The mes libc transitively pulls these in 179 * (posix/getcwd.c → _getcwd, posix/execvp.c → execve, stdlib/abort.c 180 * → raise, …). Providing -ENOSYS stubs keeps the link clean; if any 181 * of these surfaces in a real workload, replace the stub with a P1pp 182 * label and a thin wrapper above. */ 183 184 #define ENOSYS 38 185 186 char * 187 _getcwd (char *buffer, size_t size) 188 { 189 (void) buffer; (void) size; 190 errno = ENOSYS; 191 return 0; 192 } 193 194 int 195 access (char const *path, int mode) 196 { 197 (void) path; (void) mode; 198 errno = ENOSYS; 199 return -1; 200 } 201 202 void 203 assert_msg (int check, char *msg) 204 { 205 if (!check) 206 { 207 sys_write (2, (long) msg, 0); 208 sys_exit (1); 209 } 210 } 211 212 int 213 execve (char const *file, char *const argv[], char *const envp[]) 214 { 215 (void) file; (void) argv; (void) envp; 216 errno = ENOSYS; 217 return -1; 218 } 219 220 int 221 fsync (int filedes) 222 { 223 (void) filedes; 224 return 0; 225 } 226 227 int 228 raise (int sig) 229 { 230 /* abort() calls raise(SIGABRT); take that as a hard exit. */ 231 sys_exit (128 + sig); 232 return -1; 233 } 234 235 int 236 rmdir (char const *file_name) 237 { 238 (void) file_name; 239 errno = ENOSYS; 240 return -1; 241 } 242 243 int 244 stat (char const *file_name, struct stat *buf) 245 { 246 (void) file_name; (void) buf; 247 errno = ENOSYS; 248 return -1; 249 } 250 251 double 252 strtod (char const *string, char **tailptr) 253 { 254 /* tcc.flat.c never reaches strtod under our defines (HAVE_FLOAT off), 255 * and stdlib/strtof.c only forwards. cc.scm rejects FP literals, so 256 * we cast a zero int — same numeric value, no `0.0` token. */ 257 if (tailptr) 258 *tailptr = (char *) string; 259 return (double) 0; 260 }