kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

wasm_host.c (41236B)


      1 #include <kit/wasm.h>
      2 #include <setjmp.h>
      3 #include <stddef.h>
      4 #include <stdint.h>
      5 #include <string.h>
      6 
      7 typedef struct KitWasmMemoryRecord {
      8   uint8_t* data;
      9   uint64_t pages;
     10   uint64_t max_pages;
     11   uint32_t flags;
     12 } KitWasmMemoryRecord;
     13 
     14 #define KIT_WASM_HOST_MAX_FDS 64u
     15 
     16 typedef struct KitWasmOpenFd {
     17   uint32_t used;
     18   uint32_t is_dir;
     19   KitFileData data;
     20   const KitFileIO* io;
     21   uint64_t pos;
     22   void* dir_handle;
     23   char* host_path;
     24   size_t host_path_size; /* 0=borrowed (KIT_WASM_FS_FILE), >0=heap-owned */
     25 } KitWasmOpenFd;
     26 
     27 struct KitWasmHost {
     28   KitWasmHostConfig config;
     29 };
     30 
     31 typedef struct KitWasmRuntimeInstance {
     32   KitWasmHost* host;
     33   const KitWasmMemoryLayout* memory_layouts;
     34   uint64_t allocation_bytes;
     35   uint64_t instance_bytes;
     36   uint64_t total_memory_bytes;
     37   uint8_t** memories;
     38   uint64_t* memory_bytes;
     39   uint32_t nmemories;
     40   int exit_called;
     41   int exit_code;
     42   /* proc_exit unwind landing pad. Valid only while running under
     43    * kit_wasm_instance_run_entry (the native JIT path); zero otherwise so the
     44    * interpreter path falls through to its own trap-then-exit-code handling. */
     45   jmp_buf exit_jmp;
     46   int exit_jmp_valid;
     47   KitWasmOpenFd fds[KIT_WASM_HOST_MAX_FDS];
     48   uint8_t instance[];
     49 } KitWasmRuntimeInstance;
     50 
     51 enum {
     52   WASI_ESUCCESS = 0,
     53   WASI_EACCES = 2,
     54   WASI_EBADF = 8,
     55   WASI_EISDIR = 31,
     56   WASI_EINVAL = 28,
     57   WASI_EIO = 29,
     58   WASI_ENOMEM = 34,
     59   WASI_ENOENT = 44,
     60   WASI_ENOSYS = 52,
     61   WASI_ENOTDIR = 54,
     62   WASI_ENOTCAPABLE = 76,
     63 };
     64 
     65 static KitWasmRuntimeInstance* wasm_wrap_from_instance(KitWasmInstance* inst) {
     66   return (KitWasmRuntimeInstance*)((uint8_t*)inst -
     67                                    offsetof(KitWasmRuntimeInstance, instance));
     68 }
     69 
     70 static KitWasmInstance* wasm_instance_from_wrap(KitWasmRuntimeInstance* w) {
     71   return (KitWasmInstance*)w->instance;
     72 }
     73 
     74 static size_t wasm_cstrlen(const char* s) {
     75   size_t n = 0;
     76   if (!s) return 0;
     77   while (s[n]) ++n;
     78   return n;
     79 }
     80 
     81 static int wasm_streq(const char* a, const char* b) {
     82   size_t i = 0;
     83   if (!a || !b) return 0;
     84   while (a[i] && b[i]) {
     85     if (a[i] != b[i]) return 0;
     86     ++i;
     87   }
     88   return a[i] == b[i];
     89 }
     90 
     91 static KitStatus wasm_u64_to_size(uint64_t n, size_t* out) {
     92   if (n > (uint64_t)SIZE_MAX) return KIT_INVALID;
     93   *out = (size_t)n;
     94   return KIT_OK;
     95 }
     96 
     97 static KitStatus wasm_page_bytes(uint64_t pages, uint64_t* out) {
     98   if (pages > UINT64_MAX / (uint64_t)KIT_WASM_PAGE_SIZE) return KIT_INVALID;
     99   *out = pages * (uint64_t)KIT_WASM_PAGE_SIZE;
    100   return KIT_OK;
    101 }
    102 
    103 static uint32_t wasm_preopen_count(const KitWasmHost* host) {
    104   return host ? host->config.nmounts : 0u;
    105 }
    106 
    107 static const KitWasmFsMount* wasm_preopen_for_fd(KitWasmRuntimeInstance* w,
    108                                                  uint32_t fd) {
    109   uint32_t index;
    110   if (!w || !w->host || fd < 3u) return NULL;
    111   index = fd - 3u;
    112   if (index >= w->host->config.nmounts) return NULL;
    113   return &w->host->config.mounts[index];
    114 }
    115 
    116 static size_t wasm_guest_parent_len(const char* path) {
    117   size_t i;
    118   size_t last = 0;
    119   if (!path || path[0] != '/') return 0;
    120   for (i = 1; path[i]; ++i) {
    121     if (path[i] == '/') last = i;
    122   }
    123   return last ? last : 1u;
    124 }
    125 
    126 static const char* wasm_guest_basename(const char* path) {
    127   size_t i;
    128   size_t last = 0;
    129   if (!path) return NULL;
    130   for (i = 0; path[i]; ++i) {
    131     if (path[i] == '/') last = i + 1u;
    132   }
    133   return path + last;
    134 }
    135 
    136 static int wasm_bytes_eq_cstr(const uint8_t* a, uint32_t alen, const char* b) {
    137   uint32_t i;
    138   if (!a || !b) return 0;
    139   for (i = 0; i < alen; ++i) {
    140     if (b[i] == '\0' || a[i] != (uint8_t)b[i]) return 0;
    141   }
    142   return b[alen] == '\0';
    143 }
    144 
    145 static KitWasmMemoryRecord* wasm_memory0(KitWasmRuntimeInstance* w) {
    146   if (!w || w->nmemories == 0 || !w->memory_layouts) return NULL;
    147   if (w->memory_layouts[0].offset > w->instance_bytes) return NULL;
    148   if (w->instance_bytes - w->memory_layouts[0].offset <
    149       sizeof(KitWasmMemoryRecord))
    150     return NULL;
    151   return (KitWasmMemoryRecord*)(w->instance + w->memory_layouts[0].offset);
    152 }
    153 
    154 static int wasm_mem_ptr(KitWasmInstance* inst, uint32_t addr, uint32_t n,
    155                         uint8_t** out) {
    156   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    157   KitWasmMemoryRecord* mem = wasm_memory0(w);
    158   uint64_t end;
    159   uint64_t size;
    160   if (!out || !mem || !mem->data) return 0;
    161   if (addr > UINT32_MAX - n) return 0;
    162   end = (uint64_t)addr + (uint64_t)n;
    163   if (wasm_page_bytes(mem->pages, &size) != KIT_OK) return 0;
    164   if (end > size) return 0;
    165   *out = mem->data + addr;
    166   return 1;
    167 }
    168 
    169 static int wasm_read_u32(KitWasmInstance* inst, uint32_t addr, uint32_t* out) {
    170   uint8_t* p;
    171   if (!wasm_mem_ptr(inst, addr, 4u, &p)) return 0;
    172   *out = ((uint32_t)p[0]) | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
    173          ((uint32_t)p[3] << 24);
    174   return 1;
    175 }
    176 
    177 static int wasm_write_u32(KitWasmInstance* inst, uint32_t addr, uint32_t v) {
    178   uint8_t* p;
    179   if (!wasm_mem_ptr(inst, addr, 4u, &p)) return 0;
    180   p[0] = (uint8_t)(v & 0xffu);
    181   p[1] = (uint8_t)((v >> 8) & 0xffu);
    182   p[2] = (uint8_t)((v >> 16) & 0xffu);
    183   p[3] = (uint8_t)((v >> 24) & 0xffu);
    184   return 1;
    185 }
    186 
    187 static int wasm_write_u64(KitWasmInstance* inst, uint32_t addr, uint64_t v) {
    188   uint8_t* p;
    189   if (!wasm_mem_ptr(inst, addr, 8u, &p)) return 0;
    190   p[0] = (uint8_t)(v & 0xffu);
    191   p[1] = (uint8_t)((v >> 8) & 0xffu);
    192   p[2] = (uint8_t)((v >> 16) & 0xffu);
    193   p[3] = (uint8_t)((v >> 24) & 0xffu);
    194   p[4] = (uint8_t)((v >> 32) & 0xffu);
    195   p[5] = (uint8_t)((v >> 40) & 0xffu);
    196   p[6] = (uint8_t)((v >> 48) & 0xffu);
    197   p[7] = (uint8_t)((v >> 56) & 0xffu);
    198   return 1;
    199 }
    200 
    201 static void wasm_put_u64_le(uint8_t* buf, uint32_t off, uint64_t v) {
    202   buf[off + 0] = (uint8_t)(v);
    203   buf[off + 1] = (uint8_t)(v >> 8);
    204   buf[off + 2] = (uint8_t)(v >> 16);
    205   buf[off + 3] = (uint8_t)(v >> 24);
    206   buf[off + 4] = (uint8_t)(v >> 32);
    207   buf[off + 5] = (uint8_t)(v >> 40);
    208   buf[off + 6] = (uint8_t)(v >> 48);
    209   buf[off + 7] = (uint8_t)(v >> 56);
    210 }
    211 
    212 static void wasm_put_u32_le(uint8_t* buf, uint32_t off, uint32_t v) {
    213   buf[off + 0] = (uint8_t)(v);
    214   buf[off + 1] = (uint8_t)(v >> 8);
    215   buf[off + 2] = (uint8_t)(v >> 16);
    216   buf[off + 3] = (uint8_t)(v >> 24);
    217 }
    218 
    219 /* wasi_filestat_t: dev(u64@0) ino(u64@8) filetype(u8@16)+7pad nlink(u64@24)
    220  * size(u64@32) atim(u64@40) mtim(u64@48) ctim(u64@56) — 64 bytes total */
    221 static int wasm_write_filestat(KitWasmInstance* inst, uint32_t ptr,
    222                                uint8_t filetype, uint64_t size,
    223                                uint64_t mtime_ns) {
    224   uint8_t* p;
    225   if (!wasm_mem_ptr(inst, ptr, 64u, &p)) return 0;
    226   memset(p, 0, 64u);
    227   p[16] = filetype;
    228   wasm_put_u64_le(p, 32u, size);
    229   wasm_put_u64_le(p, 40u, mtime_ns);
    230   wasm_put_u64_le(p, 48u, mtime_ns);
    231   wasm_put_u64_le(p, 56u, mtime_ns);
    232   return 1;
    233 }
    234 
    235 static int wasm_status_errno(KitStatus st) {
    236   switch (st) {
    237     case KIT_OK:
    238       return WASI_ESUCCESS;
    239     case KIT_NOMEM:
    240       return WASI_ENOMEM;
    241     case KIT_NOT_FOUND:
    242       return WASI_ENOENT;
    243     case KIT_IO:
    244       return WASI_EIO;
    245     case KIT_UNSUPPORTED:
    246       return WASI_ENOSYS;
    247     case KIT_INVALID:
    248     case KIT_MALFORMED:
    249     case KIT_AMBIGUOUS:
    250     case KIT_ERR:
    251     default:
    252       return WASI_EINVAL;
    253   }
    254 }
    255 
    256 static int wasm_sig(const KitWasmImportType* t, const KitWasmValType* params,
    257                     uint32_t nparams, const KitWasmValType* results,
    258                     uint32_t nresults) {
    259   uint32_t i;
    260   if (!t || t->nparams != nparams || t->nresults != nresults) return 0;
    261   for (i = 0; i < nparams; ++i)
    262     if (!t->params || t->params[i] != params[i]) return 0;
    263   for (i = 0; i < nresults; ++i)
    264     if (!t->results || t->results[i] != results[i]) return 0;
    265   return 1;
    266 }
    267 
    268 static void wasi_proc_exit(KitWasmInstance* inst, int32_t code) {
    269   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    270   w->exit_called = 1;
    271   w->exit_code = code;
    272   /* proc_exit is _Noreturn: toolchains emit `unreachable` right after the call.
    273    * Under the native JIT path a setjmp landing pad is armed, so unwind there
    274    * instead of returning into the guest (which would hit that `unreachable` and
    275    * trap). The interpreter path leaves exit_jmp_valid clear and recovers the
    276    * exit code from the resulting trap instead. */
    277   if (w->exit_jmp_valid) {
    278     w->exit_jmp_valid = 0;
    279     longjmp(w->exit_jmp, 1);
    280   }
    281 }
    282 
    283 static int32_t wasi_fd_write(KitWasmInstance* inst, int32_t fd_i,
    284                              int32_t iovs_i, int32_t iovs_len_i,
    285                              int32_t nwritten_i) {
    286   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    287   uint32_t fd = (uint32_t)fd_i;
    288   uint32_t iovs = (uint32_t)iovs_i;
    289   uint32_t iovs_len = (uint32_t)iovs_len_i;
    290   uint32_t nwritten = (uint32_t)nwritten_i;
    291   uint32_t total = 0;
    292   uint32_t i;
    293   if (fd != 1u && fd != 2u) return WASI_EBADF;
    294   for (i = 0; i < iovs_len; ++i) {
    295     uint32_t ptr;
    296     uint32_t len;
    297     uint8_t* data;
    298     uint32_t off;
    299     uint32_t rec;
    300     size_t wrote = 0;
    301     KitStatus st;
    302     if (i > UINT32_MAX / 8u) return WASI_EINVAL;
    303     off = i * 8u;
    304     if (iovs > UINT32_MAX - off) return WASI_EINVAL;
    305     rec = iovs + off;
    306     if (!wasm_read_u32(inst, rec, &ptr) ||
    307         !wasm_read_u32(inst, rec + 4u, &len) ||
    308         !wasm_mem_ptr(inst, ptr, len, &data))
    309       return WASI_EINVAL;
    310     if (w->host->config.write) {
    311       st = w->host->config.write(w->host->config.user, fd, data, len, &wrote);
    312       if (st != KIT_OK) return wasm_status_errno(st);
    313       if (wrote > len) wrote = len;
    314     } else {
    315       wrote = len;
    316     }
    317     if (total > UINT32_MAX - (uint32_t)wrote) return WASI_EINVAL;
    318     total += (uint32_t)wrote;
    319     if (wrote < len) break;
    320   }
    321   if (!wasm_write_u32(inst, nwritten, total)) return WASI_EINVAL;
    322   return WASI_ESUCCESS;
    323 }
    324 
    325 static int wasm_strings_size(const char* const* items, uint32_t n,
    326                              uint32_t* bytes_out) {
    327   uint64_t total = 0;
    328   uint32_t i;
    329   for (i = 0; i < n; ++i) {
    330     total += (uint64_t)wasm_cstrlen(items ? items[i] : NULL) + 1u;
    331     if (total > UINT32_MAX) return 0;
    332   }
    333   *bytes_out = (uint32_t)total;
    334   return 1;
    335 }
    336 
    337 static int32_t wasi_environ_sizes_get(KitWasmInstance* inst, int32_t count_i,
    338                                       int32_t size_i) {
    339   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    340   uint32_t bytes;
    341   if (!wasm_strings_size(w->host->config.env, w->host->config.nenv, &bytes))
    342     return WASI_EINVAL;
    343   if (!wasm_write_u32(inst, (uint32_t)count_i, w->host->config.nenv) ||
    344       !wasm_write_u32(inst, (uint32_t)size_i, bytes))
    345     return WASI_EINVAL;
    346   return WASI_ESUCCESS;
    347 }
    348 
    349 static int wasm_write_strings(KitWasmInstance* inst, const char* const* items,
    350                               uint32_t nitems, uint32_t ptrs_addr,
    351                               uint32_t buf_addr) {
    352   uint32_t total;
    353   uint8_t* ptrs;
    354   uint8_t* buf;
    355   uint32_t off = 0;
    356   uint32_t i;
    357   if (!wasm_strings_size(items, nitems, &total)) return 0;
    358   if (nitems > UINT32_MAX / 4u) return 0;
    359   if (!wasm_mem_ptr(inst, ptrs_addr, nitems * 4u, &ptrs) ||
    360       !wasm_mem_ptr(inst, buf_addr, total, &buf))
    361     return 0;
    362   (void)ptrs;
    363   for (i = 0; i < nitems; ++i) {
    364     const char* s = items ? items[i] : NULL;
    365     size_t len = wasm_cstrlen(s);
    366     if (len > (size_t)(UINT32_MAX - off - 1u)) return 0;
    367     if (!wasm_write_u32(inst, ptrs_addr + i * 4u, buf_addr + off)) return 0;
    368     if (len) memcpy(buf + off, s, len);
    369     buf[off + (uint32_t)len] = 0;
    370     off += (uint32_t)len + 1u;
    371   }
    372   return 1;
    373 }
    374 
    375 static int32_t wasi_environ_get(KitWasmInstance* inst, int32_t env_i,
    376                                 int32_t buf_i) {
    377   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    378   return wasm_write_strings(inst, w->host->config.env, w->host->config.nenv,
    379                             (uint32_t)env_i, (uint32_t)buf_i)
    380              ? WASI_ESUCCESS
    381              : WASI_EINVAL;
    382 }
    383 
    384 static int32_t wasi_args_sizes_get(KitWasmInstance* inst, int32_t count_i,
    385                                    int32_t size_i) {
    386   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    387   uint32_t bytes;
    388   if (!wasm_strings_size(w->host->config.args, w->host->config.nargs, &bytes))
    389     return WASI_EINVAL;
    390   if (!wasm_write_u32(inst, (uint32_t)count_i, w->host->config.nargs) ||
    391       !wasm_write_u32(inst, (uint32_t)size_i, bytes))
    392     return WASI_EINVAL;
    393   return WASI_ESUCCESS;
    394 }
    395 
    396 static int32_t wasi_args_get(KitWasmInstance* inst, int32_t args_i,
    397                              int32_t buf_i) {
    398   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    399   return wasm_write_strings(inst, w->host->config.args, w->host->config.nargs,
    400                             (uint32_t)args_i, (uint32_t)buf_i)
    401              ? WASI_ESUCCESS
    402              : WASI_EINVAL;
    403 }
    404 
    405 static int32_t wasi_random_get(KitWasmInstance* inst, int32_t ptr_i,
    406                                int32_t len_i) {
    407   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    408   uint8_t* dst;
    409   KitStatus st;
    410   if (!w->host->config.random) return WASI_ENOSYS;
    411   if (!wasm_mem_ptr(inst, (uint32_t)ptr_i, (uint32_t)len_i, &dst))
    412     return WASI_EINVAL;
    413   st = w->host->config.random(w->host->config.user, dst, (size_t)len_i);
    414   return wasm_status_errno(st);
    415 }
    416 
    417 static int32_t wasi_clock_time_get(KitWasmInstance* inst, int32_t clock_id_i,
    418                                    int64_t precision_i, int32_t time_i) {
    419   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    420   uint64_t ns = 0;
    421   KitStatus st;
    422   (void)precision_i;
    423   if (!w->host->config.clock) return WASI_ENOSYS;
    424   st = w->host->config.clock(w->host->config.user, (uint32_t)clock_id_i, &ns);
    425   if (st != KIT_OK) return wasm_status_errno(st);
    426   return wasm_write_u64(inst, (uint32_t)time_i, ns) ? WASI_ESUCCESS
    427                                                     : WASI_EINVAL;
    428 }
    429 
    430 static int32_t wasi_fd_prestat_get(KitWasmInstance* inst, int32_t fd_i,
    431                                    int32_t prestat_i) {
    432   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    433   const KitWasmFsMount* m = wasm_preopen_for_fd(w, (uint32_t)fd_i);
    434   uint8_t* p;
    435   uint32_t len;
    436   if (!m) return WASI_EBADF;
    437   len = (m->flags & KIT_WASM_FS_FILE)
    438             ? (uint32_t)wasm_guest_parent_len(m->guest_path)
    439             : (uint32_t)wasm_cstrlen(m->guest_path);
    440   if (!wasm_mem_ptr(inst, (uint32_t)prestat_i, 8u, &p)) return WASI_EINVAL;
    441   p[0] = 0;
    442   p[1] = 0;
    443   p[2] = 0;
    444   p[3] = 0;
    445   p[4] = (uint8_t)(len & 0xffu);
    446   p[5] = (uint8_t)((len >> 8) & 0xffu);
    447   p[6] = (uint8_t)((len >> 16) & 0xffu);
    448   p[7] = (uint8_t)((len >> 24) & 0xffu);
    449   return WASI_ESUCCESS;
    450 }
    451 
    452 static int32_t wasi_fd_prestat_dir_name(KitWasmInstance* inst, int32_t fd_i,
    453                                         int32_t path_i, int32_t path_len_i) {
    454   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    455   const KitWasmFsMount* m = wasm_preopen_for_fd(w, (uint32_t)fd_i);
    456   uint8_t* dst;
    457   size_t len;
    458   if (!m) return WASI_EBADF;
    459   len = (m->flags & KIT_WASM_FS_FILE) ? wasm_guest_parent_len(m->guest_path)
    460                                       : wasm_cstrlen(m->guest_path);
    461   if ((uint32_t)path_len_i < len) return WASI_EINVAL;
    462   if (!wasm_mem_ptr(inst, (uint32_t)path_i, (uint32_t)len, &dst))
    463     return WASI_EINVAL;
    464   if (len) memcpy(dst, m->guest_path, len);
    465   return WASI_ESUCCESS;
    466 }
    467 
    468 static int wasm_guest_path_safe(const uint8_t* path, uint32_t len) {
    469   uint32_t i = 0;
    470   if (!path || len == 0 || path[0] == '/') return 0;
    471   while (i < len) {
    472     uint32_t start = i;
    473     while (i < len && path[i] != '/') {
    474       if (path[i] == '\\') return 0;
    475       ++i;
    476     }
    477     if (i == start) return 0;
    478     if (i - start == 2u && path[start] == '.' && path[start + 1u] == '.')
    479       return 0;
    480     if (i < len) ++i;
    481   }
    482   return 1;
    483 }
    484 
    485 static KitStatus wasm_join_path(KitHeap* heap, const char* base,
    486                                 const uint8_t* rel, uint32_t rel_len,
    487                                 char** out, size_t* out_size) {
    488   size_t base_len = wasm_cstrlen(base);
    489   size_t need;
    490   char* p;
    491   int slash;
    492   if (!base || !rel || !out || !out_size) return KIT_INVALID;
    493   slash = base_len && base[base_len - 1u] != '/' && base[base_len - 1u] != '\\';
    494   if (base_len > SIZE_MAX - (size_t)rel_len - (slash ? 2u : 1u))
    495     return KIT_INVALID;
    496   need = base_len + (slash ? 1u : 0u) + (size_t)rel_len + 1u;
    497   p = (char*)heap->alloc(heap, need, 1u);
    498   if (!p) return KIT_NOMEM;
    499   if (base_len) memcpy(p, base, base_len);
    500   if (slash) p[base_len++] = '/';
    501   if (rel_len) memcpy(p + base_len, rel, rel_len);
    502   p[base_len + (size_t)rel_len] = 0;
    503   *out = p;
    504   *out_size = need;
    505   return KIT_OK;
    506 }
    507 
    508 static int wasm_find_free_fd(KitWasmRuntimeInstance* w, uint32_t* out) {
    509   uint32_t start = 3u + wasm_preopen_count(w->host);
    510   uint32_t fd;
    511   if (start >= KIT_WASM_HOST_MAX_FDS) return 0;
    512   for (fd = start; fd < KIT_WASM_HOST_MAX_FDS; ++fd) {
    513     if (!w->fds[fd].used) {
    514       *out = fd;
    515       return 1;
    516     }
    517   }
    518   return 0;
    519 }
    520 
    521 static void wasm_fd_close_slot(KitWasmRuntimeInstance* w, uint32_t fd) {
    522   KitHeap* heap = w->host->config.heap;
    523   if (!w->fds[fd].used) return;
    524   if (w->fds[fd].is_dir) {
    525     if (w->fds[fd].dir_handle && w->host->config.close_dir)
    526       w->host->config.close_dir(w->host->config.user, w->fds[fd].dir_handle);
    527   } else {
    528     if (w->fds[fd].io && w->fds[fd].io->release)
    529       w->fds[fd].io->release(w->fds[fd].io->user, &w->fds[fd].data);
    530   }
    531   if (w->fds[fd].host_path_size > 0)
    532     heap->free(heap, w->fds[fd].host_path, w->fds[fd].host_path_size);
    533   memset(&w->fds[fd], 0, sizeof w->fds[fd]);
    534 }
    535 
    536 static int32_t wasi_path_open(KitWasmInstance* inst, int32_t dirfd_i,
    537                               int32_t dirflags_i, int32_t path_i,
    538                               int32_t path_len_i, int32_t oflags_i,
    539                               int64_t rights_base_i,
    540                               int64_t rights_inheriting_i, int32_t fdflags_i,
    541                               int32_t opened_fd_i) {
    542   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    543   const KitWasmFsMount* m = wasm_preopen_for_fd(w, (uint32_t)dirfd_i);
    544   uint8_t* guest_path;
    545   char* host_path = NULL;
    546   size_t host_path_size = 0;
    547   uint32_t fd;
    548   KitStatus st;
    549   int open_as_dir;
    550   (void)dirflags_i;
    551   (void)rights_base_i;
    552   (void)rights_inheriting_i;
    553   (void)fdflags_i;
    554   if (!m) return WASI_EBADF;
    555   if (!(m->flags & KIT_WASM_FS_READ)) return WASI_EACCES;
    556   /* OFLAGS_DIRECTORY=4; CREAT/EXCL/TRUNC not supported */
    557   if (oflags_i & ~4) return WASI_ENOSYS;
    558   open_as_dir = (oflags_i & 4) != 0;
    559   if (!open_as_dir &&
    560       (!w->host->config.file_io || !w->host->config.file_io->read_all))
    561     return WASI_ENOSYS;
    562   if (!wasm_mem_ptr(inst, (uint32_t)path_i, (uint32_t)path_len_i, &guest_path))
    563     return WASI_EINVAL;
    564   if (!wasm_guest_path_safe(guest_path, (uint32_t)path_len_i))
    565     return WASI_ENOTCAPABLE;
    566   if (m->flags & KIT_WASM_FS_FILE) {
    567     const char* base = wasm_guest_basename(m->guest_path);
    568     if (!base || !*base ||
    569         !wasm_bytes_eq_cstr(guest_path, (uint32_t)path_len_i, base))
    570       return WASI_ENOENT;
    571     host_path = (char*)m->host_path; /* borrowed */
    572   } else {
    573     st = wasm_join_path(w->host->config.heap, m->host_path, guest_path,
    574                         (uint32_t)path_len_i, &host_path, &host_path_size);
    575     if (st != KIT_OK) return wasm_status_errno(st);
    576   }
    577   if (open_as_dir) {
    578     void* dir_handle = NULL;
    579     if (!w->host->config.open_dir) {
    580       if (host_path_size > 0)
    581         w->host->config.heap->free(w->host->config.heap, host_path,
    582                                    host_path_size);
    583       return WASI_ENOSYS;
    584     }
    585     st = w->host->config.open_dir(w->host->config.user, host_path, &dir_handle);
    586     if (st != KIT_OK) {
    587       if (host_path_size > 0)
    588         w->host->config.heap->free(w->host->config.heap, host_path,
    589                                    host_path_size);
    590       return wasm_status_errno(st);
    591     }
    592     if (!wasm_find_free_fd(w, &fd)) {
    593       if (w->host->config.close_dir)
    594         w->host->config.close_dir(w->host->config.user, dir_handle);
    595       if (host_path_size > 0)
    596         w->host->config.heap->free(w->host->config.heap, host_path,
    597                                    host_path_size);
    598       return WASI_ENOMEM;
    599     }
    600     w->fds[fd].used = 1;
    601     w->fds[fd].is_dir = 1;
    602     w->fds[fd].dir_handle = dir_handle;
    603     w->fds[fd].host_path = host_path;
    604     w->fds[fd].host_path_size = host_path_size;
    605     if (!wasm_write_u32(inst, (uint32_t)opened_fd_i, fd)) return WASI_EINVAL;
    606     return WASI_ESUCCESS;
    607   } else {
    608     KitFileData data;
    609     memset(&data, 0, sizeof data);
    610     st = w->host->config.file_io->read_all(w->host->config.file_io->user,
    611                                            host_path, &data);
    612     if (st != KIT_OK) {
    613       if (host_path_size > 0)
    614         w->host->config.heap->free(w->host->config.heap, host_path,
    615                                    host_path_size);
    616       return wasm_status_errno(st);
    617     }
    618     if (!wasm_find_free_fd(w, &fd)) {
    619       if (w->host->config.file_io->release)
    620         w->host->config.file_io->release(w->host->config.file_io->user, &data);
    621       if (host_path_size > 0)
    622         w->host->config.heap->free(w->host->config.heap, host_path,
    623                                    host_path_size);
    624       return WASI_ENOMEM;
    625     }
    626     w->fds[fd].used = 1;
    627     w->fds[fd].data = data;
    628     w->fds[fd].io = w->host->config.file_io;
    629     w->fds[fd].pos = 0;
    630     w->fds[fd].host_path = host_path;
    631     w->fds[fd].host_path_size = host_path_size;
    632     if (!wasm_write_u32(inst, (uint32_t)opened_fd_i, fd)) return WASI_EINVAL;
    633     return WASI_ESUCCESS;
    634   }
    635 }
    636 
    637 static int32_t wasi_fd_read(KitWasmInstance* inst, int32_t fd_i, int32_t iovs_i,
    638                             int32_t iovs_len_i, int32_t nread_i) {
    639   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    640   uint32_t fd = (uint32_t)fd_i;
    641   uint32_t iovs = (uint32_t)iovs_i;
    642   uint32_t iovs_len = (uint32_t)iovs_len_i;
    643   uint32_t total = 0;
    644   uint32_t i;
    645   if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
    646   if (w->fds[fd].is_dir) return WASI_EISDIR;
    647   for (i = 0; i < iovs_len; ++i) {
    648     uint32_t ptr;
    649     uint32_t len;
    650     uint8_t* dst;
    651     uint32_t off;
    652     uint32_t rec;
    653     uint64_t remain64;
    654     uint32_t ncopy;
    655     if (i > UINT32_MAX / 8u) return WASI_EINVAL;
    656     off = i * 8u;
    657     if (iovs > UINT32_MAX - off) return WASI_EINVAL;
    658     rec = iovs + off;
    659     if (!wasm_read_u32(inst, rec, &ptr) ||
    660         !wasm_read_u32(inst, rec + 4u, &len) ||
    661         !wasm_mem_ptr(inst, ptr, len, &dst))
    662       return WASI_EINVAL;
    663     if (w->fds[fd].pos >= (uint64_t)w->fds[fd].data.size) break;
    664     remain64 = (uint64_t)w->fds[fd].data.size - w->fds[fd].pos;
    665     ncopy = len;
    666     if ((uint64_t)ncopy > remain64) ncopy = (uint32_t)remain64;
    667     if (ncopy)
    668       memcpy(dst, w->fds[fd].data.data + (size_t)w->fds[fd].pos, ncopy);
    669     w->fds[fd].pos += ncopy;
    670     if (total > UINT32_MAX - ncopy) return WASI_EINVAL;
    671     total += ncopy;
    672     if (ncopy < len) break;
    673   }
    674   if (!wasm_write_u32(inst, (uint32_t)nread_i, total)) return WASI_EINVAL;
    675   return WASI_ESUCCESS;
    676 }
    677 
    678 static int32_t wasi_fd_close(KitWasmInstance* inst, int32_t fd_i) {
    679   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    680   uint32_t fd = (uint32_t)fd_i;
    681   if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
    682   wasm_fd_close_slot(w, fd);
    683   return WASI_ESUCCESS;
    684 }
    685 
    686 static int32_t wasi_fd_seek(KitWasmInstance* inst, int32_t fd_i, int64_t offset,
    687                             int32_t whence_i, int32_t newoffset_i) {
    688   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    689   uint32_t fd = (uint32_t)fd_i;
    690   int64_t base;
    691   int64_t newpos;
    692   if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
    693   if (w->fds[fd].is_dir) return WASI_EISDIR;
    694   switch (whence_i) {
    695     case 0:
    696       base = 0;
    697       break;
    698     case 1:
    699       base = (int64_t)w->fds[fd].pos;
    700       break;
    701     case 2:
    702       base = (int64_t)(uint64_t)w->fds[fd].data.size;
    703       break;
    704     default:
    705       return WASI_EINVAL;
    706   }
    707   if (offset < 0 && (uint64_t)(-offset) > (uint64_t)base) return WASI_EINVAL;
    708   newpos = base + offset;
    709   if (newpos < 0) return WASI_EINVAL;
    710   if ((uint64_t)newpos > (uint64_t)w->fds[fd].data.size)
    711     newpos = (int64_t)(uint64_t)w->fds[fd].data.size;
    712   w->fds[fd].pos = (uint64_t)newpos;
    713   return wasm_write_u64(inst, (uint32_t)newoffset_i, (uint64_t)newpos)
    714              ? WASI_ESUCCESS
    715              : WASI_EINVAL;
    716 }
    717 
    718 static int32_t wasi_fd_tell(KitWasmInstance* inst, int32_t fd_i,
    719                             int32_t offset_i) {
    720   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    721   uint32_t fd = (uint32_t)fd_i;
    722   if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
    723   if (w->fds[fd].is_dir) return WASI_EISDIR;
    724   return wasm_write_u64(inst, (uint32_t)offset_i, w->fds[fd].pos)
    725              ? WASI_ESUCCESS
    726              : WASI_EINVAL;
    727 }
    728 
    729 static int32_t wasi_fd_filestat_get(KitWasmInstance* inst, int32_t fd_i,
    730                                     int32_t filestat_i) {
    731   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    732   uint32_t fd = (uint32_t)fd_i;
    733   uint32_t npreopen = wasm_preopen_count(w->host);
    734   uint64_t size = 0, mtime_ns = 0;
    735   uint8_t ft;
    736   if (fd <= 2u)
    737     return wasm_write_filestat(inst, (uint32_t)filestat_i,
    738                                KIT_WASM_FILETYPE_CHARACTER_DEVICE, 0u, 0u)
    739                ? WASI_ESUCCESS
    740                : WASI_EINVAL;
    741   if (fd >= 3u && fd < 3u + npreopen) {
    742     const KitWasmFsMount* m = wasm_preopen_for_fd(w, fd);
    743     ft = (m && (m->flags & KIT_WASM_FS_FILE)) ? KIT_WASM_FILETYPE_REGULAR_FILE
    744                                               : KIT_WASM_FILETYPE_DIRECTORY;
    745     return wasm_write_filestat(inst, (uint32_t)filestat_i, ft, 0u, 0u)
    746                ? WASI_ESUCCESS
    747                : WASI_EINVAL;
    748   }
    749   if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
    750   ft = w->fds[fd].is_dir ? KIT_WASM_FILETYPE_DIRECTORY
    751                          : KIT_WASM_FILETYPE_REGULAR_FILE;
    752   if (w->host->config.stat_path && w->fds[fd].host_path) {
    753     w->host->config.stat_path(w->host->config.user, w->fds[fd].host_path, &size,
    754                               &mtime_ns, &ft);
    755   } else if (!w->fds[fd].is_dir) {
    756     size = (uint64_t)w->fds[fd].data.size;
    757   }
    758   return wasm_write_filestat(inst, (uint32_t)filestat_i, ft, size, mtime_ns)
    759              ? WASI_ESUCCESS
    760              : WASI_EINVAL;
    761 }
    762 
    763 static int32_t wasi_path_filestat_get(KitWasmInstance* inst, int32_t dirfd_i,
    764                                       int32_t flags_i, int32_t path_i,
    765                                       int32_t path_len_i, int32_t filestat_i) {
    766   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    767   const KitWasmFsMount* m = wasm_preopen_for_fd(w, (uint32_t)dirfd_i);
    768   uint8_t* guest_path;
    769   char* host_path = NULL;
    770   size_t host_path_size = 0;
    771   uint64_t size = 0, mtime_ns = 0;
    772   uint8_t ft = KIT_WASM_FILETYPE_UNKNOWN;
    773   KitStatus st;
    774   (void)flags_i;
    775   if (!m) return WASI_EBADF;
    776   if (!w->host->config.stat_path) return WASI_ENOSYS;
    777   if (!wasm_mem_ptr(inst, (uint32_t)path_i, (uint32_t)path_len_i, &guest_path))
    778     return WASI_EINVAL;
    779   if (!wasm_guest_path_safe(guest_path, (uint32_t)path_len_i))
    780     return WASI_ENOTCAPABLE;
    781   if (m->flags & KIT_WASM_FS_FILE) {
    782     const char* base = wasm_guest_basename(m->guest_path);
    783     if (!base || !*base ||
    784         !wasm_bytes_eq_cstr(guest_path, (uint32_t)path_len_i, base))
    785       return WASI_ENOENT;
    786     host_path = (char*)m->host_path;
    787   } else {
    788     st = wasm_join_path(w->host->config.heap, m->host_path, guest_path,
    789                         (uint32_t)path_len_i, &host_path, &host_path_size);
    790     if (st != KIT_OK) return wasm_status_errno(st);
    791   }
    792   st = w->host->config.stat_path(w->host->config.user, host_path, &size,
    793                                  &mtime_ns, &ft);
    794   if (host_path_size > 0)
    795     w->host->config.heap->free(w->host->config.heap, host_path, host_path_size);
    796   if (st != KIT_OK) return wasm_status_errno(st);
    797   return wasm_write_filestat(inst, (uint32_t)filestat_i, ft, size, mtime_ns)
    798              ? WASI_ESUCCESS
    799              : WASI_EINVAL;
    800 }
    801 
    802 /* wasi_dirent_t: d_next(u64@0) ino(u64@8) namlen(u32@16) type(u8@20) 3pad
    803  * then namlen bytes of name (no NUL). Cookie = zero-based entry index. */
    804 static int32_t wasi_fd_readdir(KitWasmInstance* inst, int32_t fd_i,
    805                                int32_t buf_i, int32_t buf_len_i,
    806                                int64_t cookie_i, int32_t bufused_i) {
    807   KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
    808   uint32_t fd = (uint32_t)fd_i;
    809   uint32_t buf_len = (uint32_t)buf_len_i;
    810   uint64_t cookie = (uint64_t)cookie_i;
    811   uint8_t* buf;
    812   uint32_t written = 0;
    813   if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
    814   if (!w->fds[fd].is_dir) return WASI_ENOTDIR;
    815   if (!w->host->config.read_dir_entry) return WASI_ENOSYS;
    816   if (!wasm_mem_ptr(inst, (uint32_t)buf_i, buf_len, &buf)) return WASI_EINVAL;
    817   while (written < buf_len) {
    818     KitWasmDirEntry entry;
    819     uint8_t hdr[24];
    820     uint32_t remain = buf_len - written;
    821     uint32_t to_write;
    822     KitStatus st = w->host->config.read_dir_entry(
    823         w->host->config.user, w->fds[fd].dir_handle, cookie, &entry);
    824     if (st == KIT_NOT_FOUND) break;
    825     if (st != KIT_OK) return wasm_status_errno(st);
    826     memset(hdr, 0, 24u);
    827     wasm_put_u64_le(hdr, 0u, cookie + 1u);
    828     wasm_put_u64_le(hdr, 8u, entry.ino);
    829     wasm_put_u32_le(hdr, 16u, entry.name_len);
    830     hdr[20] = entry.filetype;
    831     to_write = (remain < 24u) ? remain : 24u;
    832     memcpy(buf + written, hdr, to_write);
    833     written += to_write;
    834     remain -= to_write;
    835     if (remain > 0u && entry.name_len > 0u && entry.name) {
    836       to_write = (remain < entry.name_len) ? remain : entry.name_len;
    837       memcpy(buf + written, entry.name, to_write);
    838       written += to_write;
    839     }
    840     ++cookie;
    841   }
    842   return wasm_write_u32(inst, (uint32_t)bufused_i, written) ? WASI_ESUCCESS
    843                                                             : WASI_EINVAL;
    844 }
    845 
    846 static void* wasm_wasi_resolve(void* user, const char* module,
    847                                const char* field,
    848                                const KitWasmImportType* type) {
    849   KitWasmHost* host = (KitWasmHost*)user;
    850   KitWasmValType r_i32[1] = {KIT_WASM_VAL_I32};
    851   KitWasmValType p_proc_exit[1] = {KIT_WASM_VAL_I32};
    852   KitWasmValType p2_i32[2] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32};
    853   KitWasmValType p3_i32[3] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
    854                               KIT_WASM_VAL_I32};
    855   KitWasmValType p4_i32[4] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
    856                               KIT_WASM_VAL_I32, KIT_WASM_VAL_I32};
    857   KitWasmValType p5_i32[5] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
    858                               KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
    859                               KIT_WASM_VAL_I32};
    860   KitWasmValType p_clock[3] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I64,
    861                                KIT_WASM_VAL_I32};
    862   KitWasmValType p_path_open[9] = {
    863       KIT_WASM_VAL_I32, KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
    864       KIT_WASM_VAL_I32, KIT_WASM_VAL_I32, KIT_WASM_VAL_I64,
    865       KIT_WASM_VAL_I64, KIT_WASM_VAL_I32, KIT_WASM_VAL_I32};
    866   KitWasmValType p_fd_seek[4] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I64,
    867                                  KIT_WASM_VAL_I32, KIT_WASM_VAL_I32};
    868   KitWasmValType p_fd_readdir[5] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
    869                                     KIT_WASM_VAL_I32, KIT_WASM_VAL_I64,
    870                                     KIT_WASM_VAL_I32};
    871   if (!host || !(host->config.flags & KIT_WASM_HOST_WASI_PREVIEW1)) return NULL;
    872   if (!wasm_streq(module, "wasi_snapshot_preview1")) return NULL;
    873   if (wasm_streq(field, "proc_exit") &&
    874       wasm_sig(type, p_proc_exit, 1u, NULL, 0u))
    875     return (void*)(uintptr_t)wasi_proc_exit;
    876   if (wasm_streq(field, "fd_write") && wasm_sig(type, p4_i32, 4u, r_i32, 1u))
    877     return (void*)(uintptr_t)wasi_fd_write;
    878   if (wasm_streq(field, "environ_sizes_get") &&
    879       wasm_sig(type, p2_i32, 2u, r_i32, 1u))
    880     return (void*)(uintptr_t)wasi_environ_sizes_get;
    881   if (wasm_streq(field, "environ_get") && wasm_sig(type, p2_i32, 2u, r_i32, 1u))
    882     return (void*)(uintptr_t)wasi_environ_get;
    883   if (wasm_streq(field, "args_sizes_get") &&
    884       wasm_sig(type, p2_i32, 2u, r_i32, 1u))
    885     return (void*)(uintptr_t)wasi_args_sizes_get;
    886   if (wasm_streq(field, "args_get") && wasm_sig(type, p2_i32, 2u, r_i32, 1u))
    887     return (void*)(uintptr_t)wasi_args_get;
    888   if (wasm_streq(field, "random_get") && wasm_sig(type, p2_i32, 2u, r_i32, 1u))
    889     return (void*)(uintptr_t)wasi_random_get;
    890   if (wasm_streq(field, "clock_time_get") &&
    891       wasm_sig(type, p_clock, 3u, r_i32, 1u))
    892     return (void*)(uintptr_t)wasi_clock_time_get;
    893   if (wasm_streq(field, "fd_prestat_get") &&
    894       wasm_sig(type, p2_i32, 2u, r_i32, 1u))
    895     return (void*)(uintptr_t)wasi_fd_prestat_get;
    896   if (wasm_streq(field, "fd_prestat_dir_name") &&
    897       wasm_sig(type, p3_i32, 3u, r_i32, 1u))
    898     return (void*)(uintptr_t)wasi_fd_prestat_dir_name;
    899   if (wasm_streq(field, "path_open") &&
    900       wasm_sig(type, p_path_open, 9u, r_i32, 1u))
    901     return (void*)(uintptr_t)wasi_path_open;
    902   if (wasm_streq(field, "fd_read") && wasm_sig(type, p4_i32, 4u, r_i32, 1u))
    903     return (void*)(uintptr_t)wasi_fd_read;
    904   if (wasm_streq(field, "fd_close") &&
    905       wasm_sig(type, p_proc_exit, 1u, r_i32, 1u))
    906     return (void*)(uintptr_t)wasi_fd_close;
    907   if (wasm_streq(field, "fd_seek") && wasm_sig(type, p_fd_seek, 4u, r_i32, 1u))
    908     return (void*)(uintptr_t)wasi_fd_seek;
    909   if (wasm_streq(field, "fd_tell") && wasm_sig(type, p2_i32, 2u, r_i32, 1u))
    910     return (void*)(uintptr_t)wasi_fd_tell;
    911   if (wasm_streq(field, "fd_filestat_get") &&
    912       wasm_sig(type, p2_i32, 2u, r_i32, 1u))
    913     return (void*)(uintptr_t)wasi_fd_filestat_get;
    914   if (wasm_streq(field, "path_filestat_get") &&
    915       wasm_sig(type, p5_i32, 5u, r_i32, 1u))
    916     return (void*)(uintptr_t)wasi_path_filestat_get;
    917   if (wasm_streq(field, "fd_readdir") &&
    918       wasm_sig(type, p_fd_readdir, 5u, r_i32, 1u))
    919     return (void*)(uintptr_t)wasi_fd_readdir;
    920   return NULL;
    921 }
    922 
    923 KIT_API KitStatus kit_wasm_host_new(const KitWasmHostConfig* config,
    924                                     KitWasmHost** out) {
    925   KitWasmHost* host;
    926   uint32_t i;
    927   if (!out || !config || !config->heap) return KIT_INVALID;
    928   *out = NULL;
    929   if (config->nmounts && !config->mounts) return KIT_INVALID;
    930   if (config->nmounts > KIT_WASM_HOST_MAX_FDS - 3u) return KIT_INVALID;
    931   for (i = 0; i < config->nmounts; ++i) {
    932     const KitWasmFsMount* m = &config->mounts[i];
    933     if (!m->host_path || !m->guest_path || m->guest_path[0] != '/')
    934       return KIT_INVALID;
    935     if ((m->flags & KIT_WASM_FS_FILE) && (!*wasm_guest_basename(m->guest_path)))
    936       return KIT_INVALID;
    937   }
    938   host = (KitWasmHost*)config->heap->alloc(config->heap, sizeof(*host),
    939                                            _Alignof(KitWasmHost));
    940   if (!host) return KIT_NOMEM;
    941   host->config = *config;
    942   *out = host;
    943   return KIT_OK;
    944 }
    945 
    946 KIT_API void kit_wasm_host_free(KitWasmHost* host) {
    947   KitHeap* heap;
    948   if (!host) return;
    949   heap = host->config.heap;
    950   heap->free(heap, host, sizeof(*host));
    951 }
    952 
    953 KIT_API KitStatus kit_wasm_instance_new(KitWasmHost* host, KitJit* jit,
    954                                         KitWasmInstance** out) {
    955   KitWasmRuntimeLayout layout;
    956   KitWasmRuntimeInstance* w;
    957   KitHeap* heap;
    958   uint64_t instance_bytes;
    959   uint64_t allocation_bytes;
    960   size_t allocation_size;
    961   uint64_t total_memory = 0;
    962   KitStatus st;
    963   uint32_t i;
    964   if (!out || !host || !host->config.heap || !jit) return KIT_INVALID;
    965   *out = NULL;
    966   heap = host->config.heap;
    967   st = kit_wasm_get_runtime_layout(jit, &layout);
    968   if (st != KIT_OK) return st;
    969   if (layout.nmemories > host->config.max_memories) return KIT_INVALID;
    970   instance_bytes = layout.instance_size ? layout.instance_size : 1u;
    971   if (instance_bytes > host->config.max_instance_bytes) return KIT_INVALID;
    972   if (instance_bytes > UINT64_MAX - offsetof(KitWasmRuntimeInstance, instance))
    973     return KIT_INVALID;
    974   allocation_bytes =
    975       (uint64_t)offsetof(KitWasmRuntimeInstance, instance) + instance_bytes;
    976   st = wasm_u64_to_size(allocation_bytes, &allocation_size);
    977   if (st != KIT_OK) return st;
    978   w = (KitWasmRuntimeInstance*)heap->alloc(heap, allocation_size,
    979                                            _Alignof(KitWasmRuntimeInstance));
    980   if (!w) return KIT_NOMEM;
    981   memset(w, 0, allocation_size);
    982   w->host = host;
    983   w->memory_layouts = layout.memories;
    984   w->allocation_bytes = allocation_bytes;
    985   w->instance_bytes = instance_bytes;
    986   w->nmemories = layout.nmemories;
    987   if (layout.nmemories) {
    988     size_t ptr_bytes;
    989     size_t bytes_bytes;
    990     if ((uint64_t)layout.nmemories > (uint64_t)SIZE_MAX / sizeof(uint8_t*) ||
    991         (uint64_t)layout.nmemories > (uint64_t)SIZE_MAX / sizeof(uint64_t)) {
    992       heap->free(heap, w, allocation_size);
    993       return KIT_INVALID;
    994     }
    995     ptr_bytes = (size_t)layout.nmemories * sizeof(uint8_t*);
    996     bytes_bytes = (size_t)layout.nmemories * sizeof(uint64_t);
    997     w->memories = (uint8_t**)heap->alloc(heap, ptr_bytes, _Alignof(uint8_t*));
    998     w->memory_bytes =
    999         (uint64_t*)heap->alloc(heap, bytes_bytes, _Alignof(uint64_t));
   1000     if (!w->memories || !w->memory_bytes) {
   1001       if (w->memory_bytes) heap->free(heap, w->memory_bytes, bytes_bytes);
   1002       if (w->memories) heap->free(heap, w->memories, ptr_bytes);
   1003       heap->free(heap, w, allocation_size);
   1004       return KIT_NOMEM;
   1005     }
   1006     memset(w->memories, 0, ptr_bytes);
   1007     memset(w->memory_bytes, 0, bytes_bytes);
   1008   }
   1009   for (i = 0; i < layout.nmemories; ++i) {
   1010     const KitWasmMemoryLayout* ml = &layout.memories[i];
   1011     KitWasmMemoryRecord* rec;
   1012     uint64_t mem_bytes;
   1013     size_t mem_size;
   1014     if (ml->max_pages < ml->min_pages) {
   1015       kit_wasm_instance_free(wasm_instance_from_wrap(w));
   1016       return KIT_MALFORMED;
   1017     }
   1018     if (ml->offset > instance_bytes ||
   1019         instance_bytes - ml->offset < sizeof(KitWasmMemoryRecord)) {
   1020       kit_wasm_instance_free(wasm_instance_from_wrap(w));
   1021       return KIT_MALFORMED;
   1022     }
   1023     st = wasm_page_bytes(ml->max_pages, &mem_bytes);
   1024     if (st != KIT_OK) {
   1025       kit_wasm_instance_free(wasm_instance_from_wrap(w));
   1026       return st;
   1027     }
   1028     if (mem_bytes > host->config.max_total_memory_bytes ||
   1029         total_memory > host->config.max_total_memory_bytes - mem_bytes) {
   1030       kit_wasm_instance_free(wasm_instance_from_wrap(w));
   1031       return KIT_INVALID;
   1032     }
   1033     total_memory += mem_bytes;
   1034     st = wasm_u64_to_size(mem_bytes, &mem_size);
   1035     if (st != KIT_OK) {
   1036       kit_wasm_instance_free(wasm_instance_from_wrap(w));
   1037       return st;
   1038     }
   1039     if (mem_size) {
   1040       w->memories[i] = (uint8_t*)heap->alloc(heap, mem_size, 1u);
   1041       if (!w->memories[i]) {
   1042         kit_wasm_instance_free(wasm_instance_from_wrap(w));
   1043         return KIT_NOMEM;
   1044       }
   1045       memset(w->memories[i], 0, mem_size);
   1046     }
   1047     w->memory_bytes[i] = mem_bytes;
   1048     rec = (KitWasmMemoryRecord*)(w->instance + ml->offset);
   1049     rec->data = w->memories[i];
   1050   }
   1051   w->total_memory_bytes = total_memory;
   1052   *out = wasm_instance_from_wrap(w);
   1053   return KIT_OK;
   1054 }
   1055 
   1056 KIT_API void kit_wasm_instance_free(KitWasmInstance* inst) {
   1057   KitWasmRuntimeInstance* w;
   1058   KitHeap* heap;
   1059   uint32_t i;
   1060   size_t allocation_size;
   1061   if (!inst) return;
   1062   w = wasm_wrap_from_instance(inst);
   1063   heap = w->host->config.heap;
   1064   for (i = 0; i < KIT_WASM_HOST_MAX_FDS; ++i) wasm_fd_close_slot(w, i);
   1065   for (i = 0; i < w->nmemories; ++i) {
   1066     size_t mem_size;
   1067     if (w->memories && w->memories[i] &&
   1068         wasm_u64_to_size(w->memory_bytes[i], &mem_size) == KIT_OK)
   1069       heap->free(heap, w->memories[i], mem_size);
   1070   }
   1071   if (w->memories)
   1072     heap->free(heap, w->memories, (size_t)w->nmemories * sizeof(uint8_t*));
   1073   if (w->memory_bytes)
   1074     heap->free(heap, w->memory_bytes, (size_t)w->nmemories * sizeof(uint64_t));
   1075   allocation_size = (size_t)w->allocation_bytes;
   1076   heap->free(heap, w, allocation_size);
   1077 }
   1078 
   1079 KIT_API KitStatus kit_wasm_host_bind_imports(KitWasmHost* host,
   1080                                              KitCompiler* compiler, KitJit* jit,
   1081                                              KitWasmInstance* inst) {
   1082   if (!host || !compiler || !jit || !inst) return KIT_INVALID;
   1083   return kit_wasm_bind_host_imports(compiler, jit, inst, NULL, 0,
   1084                                     wasm_wasi_resolve, host);
   1085 }
   1086 
   1087 KIT_API int kit_wasm_instance_exit_code(KitWasmInstance* inst, int* code_out) {
   1088   KitWasmRuntimeInstance* w;
   1089   if (!inst) return 0;
   1090   w = wasm_wrap_from_instance(inst);
   1091   if (!w->exit_called) return 0;
   1092   if (code_out) *code_out = w->exit_code;
   1093   return 1;
   1094 }
   1095 
   1096 KIT_API KitStatus kit_wasm_instance_run_entry(KitWasmInstance* inst,
   1097                                               KitWasmInitFn init,
   1098                                               KitWasmEntryFn entry,
   1099                                               int* rc_out) {
   1100   KitWasmRuntimeInstance* w;
   1101   int rc = 0;
   1102   if (!inst || !entry) return KIT_INVALID;
   1103   w = wasm_wrap_from_instance(inst);
   1104   if (setjmp(w->exit_jmp) == 0) {
   1105     /* First return from setjmp: arm the pad and run the guest. A proc_exit call
   1106      * longjmps back with a nonzero value, landing in the else branch. */
   1107     w->exit_jmp_valid = 1;
   1108     if (init) init(inst);
   1109     rc = entry(inst);
   1110     w->exit_jmp_valid = 0;
   1111     if (w->exit_called) rc = w->exit_code;
   1112   } else {
   1113     /* Unwound out of the guest via proc_exit; exit_code is authoritative. */
   1114     rc = w->exit_code;
   1115   }
   1116   if (rc_out) *rc_out = rc;
   1117   return KIT_OK;
   1118 }