host.c raw

   1  #include <stdlib.h>
   2  #include <string.h>
   3  #include <stdint.h>
   4  #include <unistd.h>
   5  #include <sys/wait.h>
   6  #include <sys/syscall.h>
   7  #include <sys/resource.h>
   8  #include <sys/mman.h>
   9  #include <fcntl.h>
  10  #include <pthread.h>
  11  #include <stdatomic.h>
  12  
  13  __attribute__((constructor))
  14  static void raise_stack_limit(void) {
  15      struct rlimit rl;
  16      getrlimit(RLIMIT_STACK, &rl);
  17      if (rl.rlim_cur < 268435456UL) {
  18          rl.rlim_cur = 268435456UL;
  19          setrlimit(RLIMIT_STACK, &rl);
  20      }
  21  }
  22  
  23  int32_t mxc_writefile(const char* path, int32_t pathLen, const char* data, int32_t dataLen, uint32_t mode) {
  24      char pbuf[4096];
  25      if (pathLen > 4095) pathLen = 4095;
  26      memcpy(pbuf, path, pathLen);
  27      pbuf[pathLen] = 0;
  28      int fd = syscall(SYS_openat, -100, pbuf, O_WRONLY|O_CREAT|O_TRUNC, mode);
  29      if (fd < 0) return -1;
  30      int32_t written = 0;
  31      while (written < dataLen) {
  32          long n = syscall(SYS_write, fd, data + written, dataLen - written);
  33          if (n <= 0) break;
  34          written += (int32_t)n;
  35      }
  36      syscall(SYS_close, fd);
  37      return written;
  38  }
  39  
  40  int32_t mxc_mkdir(const char* path, int32_t pathLen, uint32_t mode) {
  41      char pbuf[4096];
  42      if (pathLen > 4095) return -1;
  43      int i;
  44      for (i = 1; i <= pathLen; i++) {
  45          if (i == pathLen || path[i] == '/') {
  46              memcpy(pbuf, path, i);
  47              pbuf[i] = 0;
  48              syscall(SYS_mkdirat, -100, pbuf, mode);
  49          }
  50      }
  51      return 0;
  52  }
  53  
  54  int32_t mxc_readfile(const char* path, int32_t pathLen, char* buf, int32_t bufCap) {
  55      char pbuf[4096];
  56      if (pathLen > 4095) pathLen = 4095;
  57      memcpy(pbuf, path, pathLen);
  58      pbuf[pathLen] = 0;
  59      int fd = syscall(SYS_openat, -100, pbuf, O_RDONLY, 0);
  60      if (fd < 0) return -1;
  61      int32_t total = 0;
  62      while (total < bufCap) {
  63          long n = syscall(SYS_read, fd, buf + total, bufCap - total);
  64          if (n <= 0) break;
  65          total += (int32_t)n;
  66      }
  67      syscall(SYS_close, fd);
  68      return total;
  69  }
  70  
  71  int32_t mxc_filesize(const char* path, int32_t pathLen) {
  72      char pbuf[4096];
  73      if (pathLen > 4095) pathLen = 4095;
  74      memcpy(pbuf, path, pathLen);
  75      pbuf[pathLen] = 0;
  76      int fd = syscall(SYS_openat, -100, pbuf, O_RDONLY, 0);
  77      if (fd < 0) return -1;
  78      long size = syscall(SYS_lseek, fd, 0, 2); /* SEEK_END=2 */
  79      syscall(SYS_close, fd);
  80      return (int32_t)size;
  81  }
  82  
  83  int32_t mxc_run(const char** argv, int32_t argc) {
  84      int pid = fork();
  85      if (pid < 0) return -1;
  86      if (pid == 0) {
  87          char** args = (char**)malloc((argc + 1) * sizeof(char*));
  88          if (!args) _exit(127);
  89          int i;
  90          for (i = 0; i < argc; i++) args[i] = (char*)argv[i];
  91          args[i] = NULL;
  92          execvp(args[0], args);
  93          _exit(127);
  94      }
  95      int status;
  96      waitpid(pid, &status, 0);
  97      if (WIFEXITED(status)) return WEXITSTATUS(status);
  98      return -1;
  99  }
 100  
 101  int32_t mxc_run_async(const char** argv, int32_t argc) {
 102      int pid = fork();
 103      if (pid < 0) return -1;
 104      if (pid == 0) {
 105          char** args = (char**)malloc((argc + 1) * sizeof(char*));
 106          if (!args) _exit(127);
 107          int i;
 108          for (i = 0; i < argc; i++) args[i] = (char*)argv[i];
 109          args[i] = NULL;
 110          execvp(args[0], args);
 111          _exit(127);
 112      }
 113      return pid;
 114  }
 115  
 116  /* Fork without exec. Returns 0 in child, pid in parent, -1 on error.
 117     Child inherits the full address space (COW) so all loaded compiler
 118     state (importRegistry, type info, etc.) is immediately available. */
 119  int32_t mxc_fork(void) {
 120      return (int32_t)fork();
 121  }
 122  
 123  /* Reap any child. Returns pid on exit 0, -pid on failure, -1 if no children. */
 124  int32_t mxc_wait_any(void) {
 125      int status;
 126      int pid = waitpid(-1, &status, 0);
 127      if (pid < 0) return -1;
 128      if (WIFEXITED(status) && WEXITSTATUS(status) == 0) return pid;
 129      return -pid;
 130  }
 131  
 132  struct linux_dirent64 {
 133      uint64_t d_ino;
 134      int64_t  d_off;
 135      uint16_t d_reclen;
 136      uint8_t  d_type;
 137      char     d_name[];
 138  };
 139  
 140  int32_t mxc_listdir(const char* dir, int32_t dirLen, char* buf, int32_t bufCap) {
 141      char dbuf[4096];
 142      if (dirLen > 4095) dirLen = 4095;
 143      memcpy(dbuf, dir, dirLen);
 144      dbuf[dirLen] = 0;
 145      int fd = syscall(SYS_openat, -100, dbuf, 0x10000, 0); /* AT_FDCWD=-100, O_RDONLY|O_DIRECTORY */
 146      if (fd < 0) return 0;
 147      int32_t pos = 0;
 148      char dbytes[4096];
 149      for (;;) {
 150          long nread = syscall(SYS_getdents64, fd, dbytes, sizeof(dbytes));
 151          if (nread <= 0) break;
 152          long offset = 0;
 153          while (offset < nread) {
 154              struct linux_dirent64* ent = (struct linux_dirent64*)(dbytes + offset);
 155              char* name = ent->d_name;
 156              if (!(name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0)))) {
 157                  int32_t nlen = (int32_t)strlen(name);
 158                  if (pos + nlen + 1 <= bufCap) {
 159                      memcpy(buf + pos, name, nlen);
 160                      pos += nlen;
 161                      buf[pos] = 0;
 162                      pos++;
 163                  }
 164              }
 165              offset += ent->d_reclen;
 166          }
 167      }
 168      syscall(SYS_close, fd);
 169      return pos;
 170  }
 171  
 172  int32_t mxc_getenv(const char* name, int32_t nameLen, char* buf, int32_t bufCap) {
 173      char nbuf[256];
 174      if (nameLen > 255) nameLen = 255;
 175      memcpy(nbuf, name, nameLen);
 176      nbuf[nameLen] = 0;
 177      const char* val = getenv(nbuf);
 178      if (!val) return 0;
 179      int32_t vlen = (int32_t)strlen(val);
 180      if (vlen > bufCap) vlen = bufCap;
 181      memcpy(buf, val, vlen);
 182      return vlen;
 183  }
 184  
 185  /* ---------------------------------------------------------------
 186     Ring buffer - SPSC lock-free, matches src/runtime/ringbuf.mx
 187     protocol: big-endian length prefix, 8-byte aligned messages,
 188     monotonically increasing indices with mask-based wrap.
 189  
 190     Layout: [writeIdx:4][readIdx:4][size:4][closed:4][data:size]
 191     Messages: [len:4 BE][payload:len][pad to align 8]
 192     --------------------------------------------------------------- */
 193  
 194  typedef struct {
 195      _Atomic(uint32_t) writeIdx;
 196      _Atomic(uint32_t) readIdx;
 197      uint32_t          size;
 198      _Atomic(uint32_t) closed;
 199      char              data[];
 200  } mxc_ring;
 201  
 202  /* Round up to next power of 2 */
 203  static uint32_t next_pow2(uint32_t v) {
 204      v--;
 205      v |= v >> 1; v |= v >> 2; v |= v >> 4;
 206      v |= v >> 8; v |= v >> 16;
 207      return v + 1;
 208  }
 209  
 210  static uint32_t ring_align8(uint32_t n) { return (n + 7) & ~(uint32_t)7; }
 211  
 212  void* mxc_ring_create(int32_t dataSize) {
 213      uint32_t cap = next_pow2((uint32_t)dataSize);
 214      if (cap < 4096) cap = 4096;
 215      size_t total = sizeof(mxc_ring) + cap;
 216      mxc_ring* r = (mxc_ring*)mmap(NULL, total, PROT_READ|PROT_WRITE,
 217                                    MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
 218      if (r == MAP_FAILED) return NULL;
 219      atomic_store_explicit(&r->writeIdx, 0, memory_order_relaxed);
 220      atomic_store_explicit(&r->readIdx,  0, memory_order_relaxed);
 221      r->size = cap;
 222      atomic_store_explicit(&r->closed, 0, memory_order_relaxed);
 223      return r;
 224  }
 225  
 226  void mxc_ring_destroy(void* ring) {
 227      if (!ring) return;
 228      mxc_ring* r = (mxc_ring*)ring;
 229      size_t total = sizeof(mxc_ring) + r->size;
 230      munmap(r, total);
 231  }
 232  
 233  void mxc_ring_close(void* ring) {
 234      mxc_ring* r = (mxc_ring*)ring;
 235      atomic_store_explicit(&r->closed, 1, memory_order_release);
 236  }
 237  
 238  int32_t mxc_ring_closed(void* ring) {
 239      mxc_ring* r = (mxc_ring*)ring;
 240      return atomic_load_explicit(&r->closed, memory_order_acquire) ? 1 : 0;
 241  }
 242  
 243  static void ring_write_bytes(mxc_ring* r, uint32_t pos, const char* src, uint32_t len) {
 244      uint32_t mask = r->size - 1;
 245      uint32_t off = pos & mask;
 246      uint32_t first = r->size - off;
 247      if (first >= len) {
 248          memcpy(r->data + off, src, len);
 249      } else {
 250          memcpy(r->data + off, src, first);
 251          memcpy(r->data, src + first, len - first);
 252      }
 253  }
 254  
 255  static void ring_read_bytes(mxc_ring* r, uint32_t pos, char* dst, uint32_t len) {
 256      uint32_t mask = r->size - 1;
 257      uint32_t off = pos & mask;
 258      uint32_t first = r->size - off;
 259      if (first >= len) {
 260          memcpy(dst, r->data + off, len);
 261      } else {
 262          memcpy(dst, r->data + off, first);
 263          memcpy(dst + first, r->data, len - first);
 264      }
 265  }
 266  
 267  /* Send a length-prefixed message. Big-endian length, 8-byte aligned.
 268     Returns 0=success, 1=full (retry), -1=closed. */
 269  int32_t mxc_ring_send(void* ring, const char* data, int32_t len) {
 270      mxc_ring* r = (mxc_ring*)ring;
 271      if (atomic_load_explicit(&r->closed, memory_order_acquire)) return -1;
 272      uint32_t total = ring_align8(4 + (uint32_t)len);
 273      uint32_t w = atomic_load_explicit(&r->writeIdx, memory_order_relaxed);
 274      uint32_t rd = atomic_load_explicit(&r->readIdx, memory_order_acquire);
 275      uint32_t space = r->size - (w - rd);
 276      if (space < total) return 1; /* full */
 277      /* Big-endian length prefix (matches ringbuf.mx) */
 278      char hdr[4];
 279      hdr[0] = (char)((uint32_t)len >> 24);
 280      hdr[1] = (char)((uint32_t)len >> 16);
 281      hdr[2] = (char)((uint32_t)len >> 8);
 282      hdr[3] = (char)((uint32_t)len);
 283      ring_write_bytes(r, w, hdr, 4);
 284      if (len > 0) {
 285          ring_write_bytes(r, w + 4, data, (uint32_t)len);
 286      }
 287      /* Zero padding bytes for 8-byte alignment */
 288      uint32_t padStart = 4 + (uint32_t)len;
 289      uint32_t padEnd = total;
 290      if (padEnd > padStart) {
 291          char zeros[8] = {0};
 292          ring_write_bytes(r, w + padStart, zeros, padEnd - padStart);
 293      }
 294      atomic_store_explicit(&r->writeIdx, w + total, memory_order_release);
 295      return 0;
 296  }
 297  
 298  /* Peek at the next message length without consuming it.
 299     Returns payload length, 0=empty, -1=closed+empty. */
 300  int32_t mxc_ring_peek_len(void* ring) {
 301      mxc_ring* r = (mxc_ring*)ring;
 302      uint32_t w = atomic_load_explicit(&r->writeIdx, memory_order_acquire);
 303      uint32_t rd = atomic_load_explicit(&r->readIdx, memory_order_relaxed);
 304      uint32_t avail = w - rd;
 305      if (avail < 4) {
 306          if (atomic_load_explicit(&r->closed, memory_order_acquire)) return -1;
 307          return 0;
 308      }
 309      char hdr[4];
 310      ring_read_bytes(r, rd, hdr, 4);
 311      uint32_t msgLen = ((uint32_t)(uint8_t)hdr[0] << 24) |
 312                        ((uint32_t)(uint8_t)hdr[1] << 16) |
 313                        ((uint32_t)(uint8_t)hdr[2] << 8)  |
 314                        ((uint32_t)(uint8_t)hdr[3]);
 315      uint32_t total = ring_align8(4 + msgLen);
 316      if (avail < total) return 0; /* partial message */
 317      return (int32_t)msgLen;
 318  }
 319  
 320  /* Receive a length-prefixed message. Returns payload length on success,
 321     0=empty, -1=closed+empty. Caller must provide buf >= msgLen (use peek_len first). */
 322  int32_t mxc_ring_recv(void* ring, char* buf, int32_t bufcap) {
 323      mxc_ring* r = (mxc_ring*)ring;
 324      uint32_t w = atomic_load_explicit(&r->writeIdx, memory_order_acquire);
 325      uint32_t rd = atomic_load_explicit(&r->readIdx, memory_order_relaxed);
 326      uint32_t avail = w - rd;
 327      if (avail < 4) {
 328          if (atomic_load_explicit(&r->closed, memory_order_acquire)) return -1;
 329          return 0;
 330      }
 331      char hdr[4];
 332      ring_read_bytes(r, rd, hdr, 4);
 333      uint32_t msgLen = ((uint32_t)(uint8_t)hdr[0] << 24) |
 334                        ((uint32_t)(uint8_t)hdr[1] << 16) |
 335                        ((uint32_t)(uint8_t)hdr[2] << 8)  |
 336                        ((uint32_t)(uint8_t)hdr[3]);
 337      uint32_t total = ring_align8(4 + msgLen);
 338      if (avail < total) return 0; /* partial message */
 339      uint32_t copyLen = msgLen;
 340      if (copyLen > (uint32_t)bufcap) copyLen = (uint32_t)bufcap;
 341      if (copyLen > 0) {
 342          ring_read_bytes(r, rd + 4, buf, copyLen);
 343      }
 344      atomic_store_explicit(&r->readIdx, rd + total, memory_order_release);
 345      return (int32_t)msgLen;
 346  }
 347  
 348  /* ---------------------------------------------------------------
 349     Thread management - worker threads with status tracking
 350     --------------------------------------------------------------- */
 351  
 352  /* Per-worker status: 0=running, 1=exited, 2=crashed.
 353     Allocated by parent, pointer shared with the thread. */
 354  typedef struct {
 355      _Atomic(uint32_t) status;
 356  } mxc_worker_status;
 357  
 358  typedef struct {
 359      void (*fn)(void*);
 360      void* arg;
 361      mxc_worker_status* ws;
 362  } mxc_thread_arg;
 363  
 364  static void* mxc_thread_entry(void* raw) {
 365      mxc_thread_arg* ta = (mxc_thread_arg*)raw;
 366      void (*fn)(void*) = ta->fn;
 367      void* arg = ta->arg;
 368      mxc_worker_status* ws = ta->ws;
 369      free(ta);
 370  
 371      fn(arg);
 372  
 373      atomic_store_explicit(&ws->status, 1, memory_order_release);
 374      return NULL;
 375  }
 376  
 377  /* Spawn a worker thread.
 378     fn: worker entry point, called as fn(arg).
 379     arg: opaque argument passed to fn.
 380     handle_out: receives pthread_t (as uint64_t).
 381     status_out: receives mxc_worker_status* (as uint64_t).
 382     Returns 0 on success, -1 on failure. */
 383  int32_t mxc_spawn_thread(void* fn, void* arg, void* handle_out, void* status_out) {
 384      mxc_worker_status* ws = (mxc_worker_status*)malloc(sizeof(mxc_worker_status));
 385      if (!ws) return -1;
 386      atomic_store_explicit(&ws->status, 0, memory_order_relaxed);
 387  
 388      mxc_thread_arg* ta = (mxc_thread_arg*)malloc(sizeof(mxc_thread_arg));
 389      if (!ta) { free(ws); return -1; }
 390      ta->fn  = (void (*)(void*))fn;
 391      ta->arg = arg;
 392      ta->ws  = ws;
 393  
 394      pthread_attr_t attr;
 395      pthread_attr_init(&attr);
 396      /* Give workers a large stack (64MB) matching the parent's raised limit */
 397      pthread_attr_setstacksize(&attr, 67108864UL);
 398  
 399      pthread_t tid;
 400      int err = pthread_create(&tid, &attr, mxc_thread_entry, ta);
 401      pthread_attr_destroy(&attr);
 402      if (err != 0) {
 403          free(ta);
 404          free(ws);
 405          return -1;
 406      }
 407  
 408      *(uint64_t*)handle_out = (uint64_t)tid;
 409      *(uint64_t*)status_out = (uint64_t)(uintptr_t)ws;
 410      return 0;
 411  }
 412  
 413  /* Check worker status: 1=alive, 0=exited, -1=crashed */
 414  int32_t mxc_thread_alive(void* status_ptr) {
 415      mxc_worker_status* ws = (mxc_worker_status*)(uintptr_t)(*(uint64_t*)status_ptr);
 416      uint32_t s = atomic_load_explicit(&ws->status, memory_order_acquire);
 417      if (s == 0) return 1;  /* alive */
 418      if (s == 1) return 0;  /* exited */
 419      return -1;             /* crashed */
 420  }
 421  
 422  /* Join worker thread, then free status. */
 423  void mxc_thread_join(void* handle_ptr, void* status_ptr) {
 424      pthread_t tid = (pthread_t)(*(uint64_t*)handle_ptr);
 425      pthread_join(tid, NULL);
 426      mxc_worker_status* ws = (mxc_worker_status*)(uintptr_t)(*(uint64_t*)status_ptr);
 427      free(ws);
 428  }
 429  
 430  void mxc_usleep(int32_t us) {
 431      usleep((useconds_t)us);
 432  }
 433  
 434  void mxc_exit(int32_t code) {
 435      _exit(code);
 436  }
 437  
 438  /* Worker entry trampoline: the Moxie side exports mxc_worker_entry(void*),
 439     and mxc_spawn_worker calls mxc_spawn_thread with that specific function. */
 440  extern void mxc_worker_entry(void* arg);
 441  
 442  int32_t mxc_spawn_worker(void* arg, void* handle_out, void* status_out) {
 443      return mxc_spawn_thread((void*)mxc_worker_entry, arg, handle_out, status_out);
 444  }
 445  
 446  /* TLS runtime probe: detects whether Moxie globals are thread_local.
 447     Spawns a probe thread that writes to a Moxie global via exported function.
 448     If the parent doesn't see the write, globals are TLS-isolated.
 449     Returns 1=TLS works, 0=globals shared (force -j 1). */
 450  extern void mxc_tls_probe_write(void);
 451  extern int32_t mxc_tls_probe_read(void);
 452  
 453  static void* tls_probe_thread(void* arg) {
 454      (void)arg;
 455      mxc_tls_probe_write();
 456      return NULL;
 457  }
 458  
 459  int32_t mxc_detect_tls(void) {
 460      pthread_t t;
 461      pthread_attr_t attr;
 462      pthread_attr_init(&attr);
 463      pthread_attr_setstacksize(&attr, 1048576); /* 1MB stack for probe */
 464      int err = pthread_create(&t, &attr, tls_probe_thread, NULL);
 465      pthread_attr_destroy(&attr);
 466      if (err != 0) return 0; /* can't create thread, assume no TLS */
 467      pthread_join(t, NULL);
 468      int32_t val = mxc_tls_probe_read();
 469      /* val==0 means child wrote to its own TLS copy (parent unaffected) */
 470      /* val==1 means globals are shared (no TLS) */
 471      return (val == 0) ? 1 : 0;
 472  }
 473