kit

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

hosted.c (33658B)


      1 #include "hosted.h"
      2 
      3 #include <stddef.h>
      4 #include <stdint.h>
      5 #include <string.h>
      6 
      7 static char* hosted_join2(DriverEnv* env, const char* a, const char* b,
      8                           size_t* out_size) {
      9   size_t alen = driver_strlen(a);
     10   size_t blen = driver_strlen(b);
     11   size_t slash = (alen > 0 && a[alen - 1] != '/') ? 1u : 0u;
     12   size_t bytes = alen + slash + blen + 1u;
     13   char* out = driver_alloc(env, bytes);
     14   size_t off = 0;
     15   if (!out) return NULL;
     16   if (alen) {
     17     driver_memcpy(out + off, a, alen);
     18     off += alen;
     19   }
     20   if (slash) out[off++] = '/';
     21   if (blen) {
     22     driver_memcpy(out + off, b, blen);
     23     off += blen;
     24   }
     25   out[off] = '\0';
     26   if (out_size) *out_size = bytes;
     27   return out;
     28 }
     29 
     30 static int hosted_add_input(DriverHostedInput* items, uint32_t* n, uint32_t cap,
     31                             uint8_t kind, char* path, size_t path_size) {
     32   DriverHostedInput* it;
     33   if (*n >= cap) return 1;
     34   it = &items[*n];
     35   it->kind = kind;
     36   it->path = path;
     37   it->owned_path = path;
     38   it->owned_size = path_size;
     39   (*n)++;
     40   return 0;
     41 }
     42 
     43 /* ---------------- libdir search ---------------- */
     44 
     45 /* Return a freshly-owned `<libdir>/file` for the first libdir that has it, or
     46  * NULL when none do (or on OOM -- the caller treats both as "not found"). */
     47 static char* hosted_find_in_libdirs(DriverEnv* env,
     48                                     const DriverHostedDirs* dirs,
     49                                     const char* file, size_t* out_size) {
     50   uint32_t i;
     51   for (i = 0; i < dirs->nlibdirs; ++i) {
     52     size_t size = 0;
     53     char* path = hosted_join2(env, dirs->libdirs[i], file, &size);
     54     if (!path) return NULL;
     55     if (driver_path_exists(path)) {
     56       *out_size = size;
     57       return path;
     58     }
     59     driver_free(env, path, size);
     60   }
     61   return NULL;
     62 }
     63 
     64 static int hosted_libdir_has(DriverEnv* env, const DriverHostedDirs* dirs,
     65                              const char* file) {
     66   size_t size = 0;
     67   char* path = hosted_find_in_libdirs(env, dirs, file, &size);
     68   if (!path) return 0;
     69   driver_free(env, path, size);
     70   return 1;
     71 }
     72 
     73 /* Search the libdir list for a required crt/libc file, binding the first hit
     74  * as an owned input. The bound file path persists in the plan (freed by
     75  * driver_hosted_plan_fini); the dir list itself is transient scratch. */
     76 static int hosted_add_required_search(DriverHostedInput* items, uint32_t* n,
     77                                       uint32_t cap,
     78                                       const DriverHostedRequest* req,
     79                                       const DriverHostedDirs* dirs,
     80                                       const char* file, uint8_t kind) {
     81   size_t size = 0;
     82   char* path = hosted_find_in_libdirs(req->env, dirs, file, &size);
     83   if (!path) {
     84     driver_errf(req->tool,
     85                 "hosted profile missing required file: %.*s (searched %u "
     86                 "library dir(s))",
     87                 KIT_SLICE_ARG(kit_slice_cstr(file)), (unsigned)dirs->nlibdirs);
     88     return 1;
     89   }
     90   if (hosted_add_input(items, n, cap, kind, path, size) != 0) {
     91     driver_errf(req->tool, "too many hosted inputs");
     92     driver_free(req->env, path, size);
     93     return 1;
     94   }
     95   return 0;
     96 }
     97 
     98 /* ---------------- include dirs ---------------- */
     99 
    100 /* Promote one include dir into the plan as an owned copy, skipping it silently
    101  * when it does not exist. The copy is required: plan->system_includes outlives
    102  * the transient DriverHostedDirs (it feeds the preprocessor). */
    103 static int hosted_add_existing_include(DriverHostedPlan* plan, DriverEnv* env,
    104                                        const char* dir) {
    105   size_t len, bytes;
    106   char* path;
    107   if (plan->nsystem_includes >= DRIVER_HOSTED_MAX_INCLUDES) return 1;
    108   if (!driver_path_exists(dir)) return 0;
    109   len = driver_strlen(dir);
    110   bytes = len + 1u;
    111   path = driver_alloc(env, bytes);
    112   if (!path) return 1;
    113   driver_memcpy(path, dir, len);
    114   path[len] = '\0';
    115   plan->system_includes[plan->nsystem_includes] = path;
    116   plan->owned_system_includes[plan->nsystem_includes] = path;
    117   plan->owned_system_include_sizes[plan->nsystem_includes] = bytes;
    118   plan->nsystem_includes++;
    119   return 0;
    120 }
    121 
    122 static int hosted_add_incdirs(DriverHostedPlan* plan, DriverEnv* env,
    123                               const DriverHostedDirs* dirs) {
    124   uint32_t i;
    125   for (i = 0; i < dirs->nincdirs; ++i) {
    126     if (hosted_add_existing_include(plan, env, dirs->incdirs[i]) != 0) return 1;
    127   }
    128   return 0;
    129 }
    130 
    131 /* ---------------- defines ---------------- */
    132 
    133 static int hosted_add_define(DriverHostedPlan* plan, const char* name,
    134                              const char* body) {
    135   if (plan->ndefines >= DRIVER_HOSTED_MAX_DEFINES) return 1;
    136   plan->defines[plan->ndefines].name = kit_slice_cstr(name);
    137   plan->defines[plan->ndefines].body = kit_slice_cstr(body);
    138   plan->ndefines++;
    139   return 0;
    140 }
    141 
    142 static int hosted_add_clang_compat_defines(DriverHostedPlan* plan) {
    143   if (hosted_add_define(plan, "__clang__", "1") != 0 ||
    144       hosted_add_define(plan, "__clang_major__", "17") != 0 ||
    145       hosted_add_define(plan, "__clang_minor__", "0") != 0 ||
    146       hosted_add_define(plan, "__clang_patchlevel__", "0") != 0 ||
    147       hosted_add_define(plan, "__GNUC__", "4") != 0 ||
    148       hosted_add_define(plan, "__GNUC_MINOR__", "2") != 0 ||
    149       hosted_add_define(plan, "__GNUC_PATCHLEVEL__", "1") != 0 ||
    150       hosted_add_define(plan, "__has_builtin(x)", "0") != 0 ||
    151       hosted_add_define(plan, "__has_include(x)", "0") != 0 ||
    152       hosted_add_define(plan, "__has_include_next(x)", "0") != 0 ||
    153       hosted_add_define(plan, "__has_feature(x)", "0") != 0 ||
    154       hosted_add_define(plan, "__has_extension(x)", "0") != 0 ||
    155       hosted_add_define(plan, "__has_attribute(x)", "0") != 0)
    156     return 1;
    157   return 0;
    158 }
    159 
    160 static int hosted_add_darwin_defines(DriverHostedPlan* plan) {
    161   /* __APPLE_CC__ is what the SDK's <TargetConditionals.h> actually keys on:
    162    * its compiler-detection ladder treats us as "GCC/clang on Mac OS X" only
    163    * when __GNUC__ AND one of __APPLE_CC__/__APPLE_CPP__/__MACOS_CLASSIC__ are
    164    * defined. Without it the header can't map __arm64__/__x86_64__ onto a
    165    * TARGET_CPU_* and hits its `#error unknown compiler`. 6000 is the value
    166    * modern Apple clang advertises. */
    167   if (hosted_add_clang_compat_defines(plan) != 0 ||
    168       hosted_add_define(plan, "__APPLE__", "1") != 0 ||
    169       hosted_add_define(plan, "__APPLE_CC__", "6000") != 0 ||
    170       hosted_add_define(plan, "__MACH__", "1") != 0 ||
    171       hosted_add_define(plan, "__DYNAMIC__", "1") != 0)
    172     return 1;
    173   return 0;
    174 }
    175 
    176 static int hosted_add_linux_defines(DriverHostedPlan* plan, int gnu) {
    177   if (hosted_add_clang_compat_defines(plan) != 0 ||
    178       hosted_add_define(plan, "__linux__", "1") != 0 ||
    179       hosted_add_define(plan, "__linux", "1") != 0 ||
    180       hosted_add_define(plan, "linux", "1") != 0 ||
    181       hosted_add_define(plan, "__ELF__", "1") != 0)
    182     return 1;
    183   if (gnu && hosted_add_define(plan, "__gnu_linux__", "1") != 0) return 1;
    184   return 0;
    185 }
    186 
    187 static const char* freebsd_version_str(uint8_t major) {
    188   /* Static strings indexed by major version; 0 = unspecified → "15". */
    189   static const char* const tab[21] = {
    190     "15",
    191     "1","2","3","4","5","6","7","8","9","10",
    192     "11","12","13","14","15","16","17","18","19","20"
    193   };
    194   return (major < 21) ? tab[major] : "15";
    195 }
    196 
    197 static int hosted_add_freebsd_defines(DriverHostedPlan* plan, uint8_t version_major) {
    198   /* __FreeBSD__ must be present and version-encoded or the base headers fail to
    199    * compile. The version is derived from the triple; 0 falls back to 15. */
    200   if (hosted_add_clang_compat_defines(plan) != 0 ||
    201       hosted_add_define(plan, "__FreeBSD__", freebsd_version_str(version_major)) != 0 ||
    202       hosted_add_define(plan, "__ELF__", "1") != 0 ||
    203       hosted_add_define(plan, "__unix__", "1") != 0 ||
    204       hosted_add_define(plan, "__unix", "1") != 0 ||
    205       hosted_add_define(plan, "unix", "1") != 0)
    206     return 1;
    207   return 0;
    208 }
    209 
    210 /* ---------------- interpreters ---------------- */
    211 
    212 static const char* hosted_glibc_interp(KitArchKind arch) {
    213   switch (arch) {
    214     case KIT_ARCH_ARM_64:
    215       return "/lib/ld-linux-aarch64.so.1";
    216     case KIT_ARCH_X86_64:
    217       return "/lib64/ld-linux-x86-64.so.2";
    218     case KIT_ARCH_RV64:
    219       return "/lib/ld-linux-riscv64-lp64d.so.1";
    220     default:
    221       return NULL;
    222   }
    223 }
    224 
    225 static const char* hosted_musl_interp(KitArchKind arch) {
    226   switch (arch) {
    227     case KIT_ARCH_ARM_64:
    228       return "/lib/ld-musl-aarch64.so.1";
    229     case KIT_ARCH_X86_64:
    230       return "/lib/ld-musl-x86_64.so.1";
    231     case KIT_ARCH_RV64:
    232       return "/lib/ld-musl-riscv64.so.1";
    233     default:
    234       return NULL;
    235   }
    236 }
    237 
    238 /* ---------------- per-OS resolvers ----------------
    239  * Each consumes a DriverHostedDirs (include roots + library search dirs). An
    240  * empty dir list means no sysroot was given and no host default was found, so
    241  * the resolver emits its "requires --sysroot/KIT_SYSROOT" error. */
    242 
    243 static int hosted_resolve_darwin(const DriverHostedRequest* req,
    244                                  const DriverHostedDirs* dirs,
    245                                  DriverHostedPlan* plan) {
    246   if (dirs->nlibdirs == 0) {
    247     driver_errf(req->tool,
    248                 "Darwin hosted profile requires a macOS SDK; pass --sysroot, "
    249                 "set KIT_SYSROOT, or install the Command Line Tools (try: "
    250                 "--sysroot \"$(xcrun --sdk macosx --show-sdk-path)\")");
    251     return 1;
    252   }
    253   plan->profile_name = "macos-libSystem";
    254   if (hosted_add_darwin_defines(plan) != 0 ||
    255       hosted_add_incdirs(plan, req->env, dirs) != 0) {
    256     driver_errf(req->tool, "out of memory");
    257     return 1;
    258   }
    259   if (!req->link_inputs) return 0;
    260   {
    261     size_t size = 0;
    262     char* libsystem =
    263         hosted_find_in_libdirs(req->env, dirs, "libSystem.tbd", &size);
    264     if (!libsystem)
    265       libsystem =
    266           hosted_find_in_libdirs(req->env, dirs, "libSystem.dylib", &size);
    267     if (!libsystem) {
    268       driver_errf(req->tool,
    269                   "hosted profile missing required file: "
    270                   "libSystem.tbd or libSystem.dylib");
    271       return 1;
    272     }
    273     if (hosted_add_input(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER,
    274                          DRIVER_HOSTED_INPUT_DSO, libsystem, size) != 0) {
    275       driver_free(req->env, libsystem, size);
    276       driver_errf(req->tool, "too many hosted inputs");
    277       return 1;
    278     }
    279   }
    280   return 0;
    281 }
    282 
    283 static int hosted_resolve_linux_musl_static(const DriverHostedRequest* req,
    284                                             const DriverHostedDirs* dirs,
    285                                             DriverHostedPlan* plan) {
    286   plan->profile_name = "linux-musl-static";
    287   if (hosted_add_linux_defines(plan, 0) != 0 ||
    288       hosted_add_incdirs(plan, req->env, dirs) != 0) {
    289     driver_errf(req->tool, "out of memory");
    290     return 1;
    291   }
    292   if (!req->link_inputs) return 0;
    293   if (hosted_add_required_search(plan->before, &plan->nbefore,
    294                                  DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crt1.o",
    295                                  DRIVER_HOSTED_INPUT_OBJECT) != 0 ||
    296       hosted_add_required_search(plan->before, &plan->nbefore,
    297                                  DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crti.o",
    298                                  DRIVER_HOSTED_INPUT_OBJECT) != 0)
    299     return 1;
    300   if (hosted_add_required_search(plan->after, &plan->nafter,
    301                                  DRIVER_HOSTED_MAX_AFTER, req, dirs, "libc.a",
    302                                  DRIVER_HOSTED_INPUT_ARCHIVE) != 0)
    303     return 1;
    304   if (hosted_add_required_search(plan->final, &plan->nfinal,
    305                                  DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtn.o",
    306                                  DRIVER_HOSTED_INPUT_OBJECT) != 0)
    307     return 1;
    308   return 0;
    309 }
    310 
    311 static int hosted_resolve_linux_musl_dynamic(const DriverHostedRequest* req,
    312                                              const DriverHostedDirs* dirs,
    313                                              DriverHostedPlan* plan) {
    314   const char* interp = hosted_musl_interp(req->target.arch);
    315   if (!interp) {
    316     driver_errf(req->tool, "no hosted musl profile for target architecture");
    317     return 1;
    318   }
    319   plan->profile_name = "linux-musl-dynamic";
    320   plan->interp_path = interp;
    321   if (hosted_add_linux_defines(plan, 0) != 0 ||
    322       hosted_add_incdirs(plan, req->env, dirs) != 0) {
    323     driver_errf(req->tool, "out of memory");
    324     return 1;
    325   }
    326   if (!req->link_inputs) return 0;
    327   if (hosted_add_required_search(plan->before, &plan->nbefore,
    328                                  DRIVER_HOSTED_MAX_BEFORE, req, dirs, "Scrt1.o",
    329                                  DRIVER_HOSTED_INPUT_OBJECT) != 0 ||
    330       hosted_add_required_search(plan->before, &plan->nbefore,
    331                                  DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crti.o",
    332                                  DRIVER_HOSTED_INPUT_OBJECT) != 0)
    333     return 1;
    334   if (hosted_add_required_search(plan->after, &plan->nafter,
    335                                  DRIVER_HOSTED_MAX_AFTER, req, dirs, "libc.so",
    336                                  DRIVER_HOSTED_INPUT_DSO) != 0)
    337     return 1;
    338   if (hosted_add_required_search(plan->final, &plan->nfinal,
    339                                  DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtn.o",
    340                                  DRIVER_HOSTED_INPUT_OBJECT) != 0)
    341     return 1;
    342   return 0;
    343 }
    344 
    345 static int hosted_resolve_linux_glibc_dynamic(const DriverHostedRequest* req,
    346                                               const DriverHostedDirs* dirs,
    347                                               DriverHostedPlan* plan) {
    348   const char* interp = hosted_glibc_interp(req->target.arch);
    349   if (!interp) {
    350     driver_errf(req->tool, "no hosted glibc profile for target architecture");
    351     return 1;
    352   }
    353   plan->profile_name = "linux-glibc-dynamic";
    354   plan->interp_path = interp;
    355   /* The arch multiarch include (include/<triple>) is already in the dir list,
    356    * so glibc adds the same include set as the musl profiles. */
    357   if (hosted_add_linux_defines(plan, 1) != 0 ||
    358       hosted_add_incdirs(plan, req->env, dirs) != 0) {
    359     driver_errf(req->tool, "out of memory");
    360     return 1;
    361   }
    362   if (!req->link_inputs) return 0;
    363   if (hosted_add_required_search(plan->before, &plan->nbefore,
    364                                  DRIVER_HOSTED_MAX_BEFORE, req, dirs, "Scrt1.o",
    365                                  DRIVER_HOSTED_INPUT_OBJECT) != 0 ||
    366       hosted_add_required_search(plan->before, &plan->nbefore,
    367                                  DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crti.o",
    368                                  DRIVER_HOSTED_INPUT_OBJECT) != 0)
    369     return 1;
    370   if (hosted_add_required_search(plan->after, &plan->nafter,
    371                                  DRIVER_HOSTED_MAX_AFTER, req, dirs,
    372                                  "libc.so.6", DRIVER_HOSTED_INPUT_DSO) != 0 ||
    373       hosted_add_required_search(
    374           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    375           "libc_nonshared.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0)
    376     return 1;
    377   if (hosted_add_required_search(plan->final, &plan->nfinal,
    378                                  DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtn.o",
    379                                  DRIVER_HOSTED_INPUT_OBJECT) != 0)
    380     return 1;
    381   return 0;
    382 }
    383 
    384 static int hosted_resolve_linux(const DriverHostedRequest* req,
    385                                 const DriverHostedDirs* dirs,
    386                                 DriverHostedPlan* plan) {
    387   int has_libc_a, has_libc_so, has_libc_so6, has_glibc_nonshared;
    388   if (dirs->nlibdirs == 0) {
    389     driver_errf(req->tool,
    390                 "Linux hosted profile requires --sysroot or KIT_SYSROOT");
    391     return 1;
    392   }
    393   /* Booleans mean "exists in some libdir". With a single sysroot libdir this is
    394    * bit-identical to the historical single-dir probe; with the multi-dir host
    395    * probe, glibc's split (crt + libc_nonshared.a in /usr/lib/<triple>,
    396    * libc.so.6 in /lib/<triple>) is handled by the per-file ordered search. */
    397   has_libc_a = hosted_libdir_has(req->env, dirs, "libc.a");
    398   has_libc_so = hosted_libdir_has(req->env, dirs, "libc.so");
    399   has_libc_so6 = hosted_libdir_has(req->env, dirs, "libc.so.6");
    400   has_glibc_nonshared = hosted_libdir_has(req->env, dirs, "libc_nonshared.a");
    401   if (!req->static_link && has_libc_so6 && has_glibc_nonshared)
    402     return hosted_resolve_linux_glibc_dynamic(req, dirs, plan);
    403   if (!req->static_link && has_libc_so)
    404     return hosted_resolve_linux_musl_dynamic(req, dirs, plan);
    405   if (has_libc_a && !(has_libc_so6 && has_glibc_nonshared))
    406     return hosted_resolve_linux_musl_static(req, dirs, plan);
    407   driver_errf(req->tool,
    408               "no supported Linux hosted libc found (searched %u library "
    409               "dir(s))",
    410               (unsigned)dirs->nlibdirs);
    411   return 1;
    412 }
    413 
    414 /* FreeBSD base-system hosted profile. libc binds libc.so.7 directly --
    415  * /usr/lib/libc.so is a GNU ld linker script kit's linker cannot parse. */
    416 static int hosted_resolve_freebsd(const DriverHostedRequest* req,
    417                                   const DriverHostedDirs* dirs,
    418                                   DriverHostedPlan* plan) {
    419   int static_link;
    420   if (dirs->nlibdirs == 0) {
    421     driver_errf(req->tool,
    422                 "FreeBSD hosted profile requires --sysroot or KIT_SYSROOT");
    423     return 1;
    424   }
    425   static_link = req->static_link && hosted_libdir_has(req->env, dirs, "libc.a");
    426   plan->profile_name = static_link ? "freebsd-static" : "freebsd-dynamic";
    427   if (!static_link) plan->interp_path = "/libexec/ld-elf.so.1";
    428   if (hosted_add_freebsd_defines(plan, req->target.os_version_major) != 0 ||
    429       hosted_add_incdirs(plan, req->env, dirs) != 0) {
    430     driver_errf(req->tool, "out of memory");
    431     return 1;
    432   }
    433   if (!req->link_inputs) return 0;
    434   if (hosted_add_required_search(plan->before, &plan->nbefore,
    435                                  DRIVER_HOSTED_MAX_BEFORE, req, dirs,
    436                                  static_link ? "crt1.o" : "Scrt1.o",
    437                                  DRIVER_HOSTED_INPUT_OBJECT) != 0 ||
    438       hosted_add_required_search(plan->before, &plan->nbefore,
    439                                  DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crti.o",
    440                                  DRIVER_HOSTED_INPUT_OBJECT) != 0)
    441     return 1;
    442   if (static_link) {
    443     /* FreeBSD 15 split the raw syscall stubs out of libc into libsys; the
    444      * compiler builtins / soft-float helpers (e.g. rv64's binary128 __multf3,
    445      * which libc references because the RISC-V psABI makes long double a
    446      * 128-bit quad) live in libcompiler_rt.a (a.k.a. libgcc.a). libc, libsys
    447      * and libcompiler_rt are mutually recursive, so after the first libc.a we
    448      * append the ones the sysroot provides and re-list libc.a to pick up the
    449      * back-references they introduce -- kit resolves each archive against the
    450      * inputs before it and has no --start-group. */
    451     int has_libsys = hosted_libdir_has(req->env, dirs, "libsys.a");
    452     int has_crt = hosted_libdir_has(req->env, dirs, "libcompiler_rt.a");
    453     if (hosted_add_required_search(plan->after, &plan->nafter,
    454                                    DRIVER_HOSTED_MAX_AFTER, req, dirs, "libc.a",
    455                                    DRIVER_HOSTED_INPUT_ARCHIVE) != 0)
    456       return 1;
    457     if (has_libsys &&
    458         hosted_add_required_search(
    459             plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    460             "libsys.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0)
    461       return 1;
    462     if (has_crt && hosted_add_required_search(plan->after, &plan->nafter,
    463                                               DRIVER_HOSTED_MAX_AFTER, req,
    464                                               dirs, "libcompiler_rt.a",
    465                                               DRIVER_HOSTED_INPUT_ARCHIVE) != 0)
    466       return 1;
    467     if ((has_libsys || has_crt) &&
    468         hosted_add_required_search(plan->after, &plan->nafter,
    469                                    DRIVER_HOSTED_MAX_AFTER, req, dirs, "libc.a",
    470                                    DRIVER_HOSTED_INPUT_ARCHIVE) != 0)
    471       return 1;
    472   } else {
    473     if (hosted_add_required_search(plan->after, &plan->nafter,
    474                                    DRIVER_HOSTED_MAX_AFTER, req, dirs,
    475                                    "libc.so.7", DRIVER_HOSTED_INPUT_DSO) != 0)
    476       return 1;
    477     if (hosted_libdir_has(req->env, dirs, "libsys.so.7") &&
    478         hosted_add_required_search(plan->after, &plan->nafter,
    479                                    DRIVER_HOSTED_MAX_AFTER, req, dirs,
    480                                    "libsys.so.7", DRIVER_HOSTED_INPUT_DSO) != 0)
    481       return 1;
    482   }
    483   if (hosted_add_required_search(plan->final, &plan->nfinal,
    484                                  DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtn.o",
    485                                  DRIVER_HOSTED_INPUT_OBJECT) != 0)
    486     return 1;
    487   return 0;
    488 }
    489 
    490 static int hosted_resolve_windows_mingw(const DriverHostedRequest* req,
    491                                         const DriverHostedDirs* dirs,
    492                                         DriverHostedPlan* plan) {
    493   if (dirs->nlibdirs == 0) {
    494     driver_errf(req->tool,
    495                 "Windows hosted profile requires --sysroot or KIT_SYSROOT");
    496     return 1;
    497   }
    498   plan->profile_name = "windows-mingw-ucrt";
    499   if (hosted_add_incdirs(plan, req->env, dirs) != 0) {
    500     driver_errf(req->tool, "out of memory");
    501     return 1;
    502   }
    503   if (!req->link_inputs) return 0;
    504   if (hosted_add_required_search(plan->before, &plan->nbefore,
    505                                  DRIVER_HOSTED_MAX_BEFORE, req, dirs, "crt2.o",
    506                                  DRIVER_HOSTED_INPUT_OBJECT) != 0 ||
    507       hosted_add_required_search(plan->before, &plan->nbefore,
    508                                  DRIVER_HOSTED_MAX_BEFORE, req, dirs,
    509                                  "crtbegin.o", DRIVER_HOSTED_INPUT_OBJECT) != 0)
    510     return 1;
    511   if (hosted_add_required_search(
    512           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    513           "libmingw32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    514       hosted_add_required_search(
    515           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    516           "libmoldname.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    517       hosted_add_required_search(
    518           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    519           "libmingwex.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    520       /* winpthreads provides mingw's POSIX time/clock/threading entry points
    521        * (nanosleep64, clock_gettime64, ...). <time.h> pulls in pthread_time.h's
    522        * inline wrappers that call these, so it belongs in the default mingw
    523        * runtime set (llvm-mingw ships it as a core lib). Static archive -> no
    524        * libwinpthread-1.dll dependency; archive semantics keep it out of links
    525        * that reference none of its members. */
    526       hosted_add_required_search(
    527           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    528           "libwinpthread.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    529       hosted_add_required_search(
    530           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    531           "libucrt.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    532       hosted_add_required_search(
    533           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    534           "libadvapi32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    535       hosted_add_required_search(
    536           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    537           "libshell32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    538       hosted_add_required_search(
    539           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    540           "libuser32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    541       hosted_add_required_search(
    542           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    543           "libkernel32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    544       hosted_add_required_search(
    545           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    546           "libmingw32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    547       hosted_add_required_search(
    548           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    549           "libmoldname.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    550       hosted_add_required_search(
    551           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    552           "libmingwex.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    553       hosted_add_required_search(
    554           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    555           "libwinpthread.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    556       hosted_add_required_search(
    557           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    558           "libucrt.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 ||
    559       hosted_add_required_search(
    560           plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs,
    561           "libkernel32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0)
    562     return 1;
    563   if (hosted_add_required_search(plan->final, &plan->nfinal,
    564                                  DRIVER_HOSTED_MAX_FINAL, req, dirs, "crtend.o",
    565                                  DRIVER_HOSTED_INPUT_OBJECT) != 0)
    566     return 1;
    567   return 0;
    568 }
    569 
    570 /* ---------------- sysroot expansion (Producer A) ---------------- */
    571 
    572 static const char* hosted_linux_inc_triple_sub(KitArchKind arch) {
    573   switch (arch) {
    574     case KIT_ARCH_ARM_64:
    575       return "include/aarch64-linux-gnu";
    576     case KIT_ARCH_X86_64:
    577       return "include/x86_64-linux-gnu";
    578     case KIT_ARCH_RV64:
    579       return "include/riscv64-linux-gnu";
    580     default:
    581       return NULL;
    582   }
    583 }
    584 
    585 /* Expand an explicit --sysroot/KIT_SYSROOT into the dir list, per target-OS
    586  * layout. Mirrors the per-host probes' shape so both producers converge on the
    587  * same resolver contract. Dirs are added unconditionally (existence is checked
    588  * when the resolver consumes them); returns nonzero only on alloc/overflow.
    589  * One function per hosted OS; selected by the HostedProfile table below. */
    590 
    591 static int hosted_sysroot_layout_darwin(DriverHostedDirs* dirs,
    592                                         KitTargetSpec target,
    593                                         const char* sysroot) {
    594   (void)target;
    595   if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "usr/include") != 0 ||
    596       driver_hosted_dirs_add_lib_join(dirs, sysroot, "usr/lib") != 0)
    597     return 1;
    598   return 0;
    599 }
    600 
    601 static int hosted_sysroot_layout_linux(DriverHostedDirs* dirs,
    602                                        KitTargetSpec target,
    603                                        const char* sysroot) {
    604   const char* triple = hosted_linux_inc_triple_sub(target.arch);
    605   if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "include") != 0) return 1;
    606   if (triple && driver_hosted_dirs_add_inc_join(dirs, sysroot, triple) != 0)
    607     return 1;
    608   if (driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0) return 1;
    609   return 0;
    610 }
    611 
    612 static int hosted_sysroot_layout_freebsd(DriverHostedDirs* dirs,
    613                                          KitTargetSpec target,
    614                                          const char* sysroot) {
    615   (void)target;
    616   if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "usr/include") != 0 ||
    617       driver_hosted_dirs_add_lib_join(dirs, sysroot, "usr/lib") != 0 ||
    618       driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0)
    619     return 1;
    620   return 0;
    621 }
    622 
    623 static int hosted_sysroot_layout_windows(DriverHostedDirs* dirs,
    624                                          KitTargetSpec target,
    625                                          const char* sysroot) {
    626   (void)target;
    627   if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "include") != 0 ||
    628       driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0)
    629     return 1;
    630   return 0;
    631 }
    632 
    633 /* ---------------- hosted-profile registry ---------------- *
    634  *
    635  * One row per hosted (OS, object-format) pairing, mirroring the
    636  * src/{arch,obj,abi}/registry.c tables. A new hosted OS is a new row: its
    637  * crt/libc resolver and its --sysroot directory layout. The dispatcher and
    638  * the sysroot expander both go through hosted_profile_for so the (os,obj)
    639  * decision lives in exactly one place. */
    640 typedef int (*HostedResolveFn)(const DriverHostedRequest*,
    641                                const DriverHostedDirs*, DriverHostedPlan*);
    642 typedef int (*HostedSysrootLayoutFn)(DriverHostedDirs*, KitTargetSpec,
    643                                      const char*);
    644 
    645 typedef struct HostedProfile {
    646   KitOSKind os;
    647   KitObjFmt obj;
    648   HostedResolveFn resolve;
    649   HostedSysrootLayoutFn sysroot_layout;
    650 } HostedProfile;
    651 
    652 static const HostedProfile hosted_profiles[] = {
    653     {KIT_OS_MACOS, KIT_OBJ_MACHO, hosted_resolve_darwin,
    654      hosted_sysroot_layout_darwin},
    655     {KIT_OS_LINUX, KIT_OBJ_ELF, hosted_resolve_linux,
    656      hosted_sysroot_layout_linux},
    657     {KIT_OS_FREEBSD, KIT_OBJ_ELF, hosted_resolve_freebsd,
    658      hosted_sysroot_layout_freebsd},
    659     {KIT_OS_WINDOWS, KIT_OBJ_COFF, hosted_resolve_windows_mingw,
    660      hosted_sysroot_layout_windows},
    661 };
    662 
    663 /* The crt/libc resolve dispatch keys on the full (os, obj) pairing -- a
    664  * matching OS with a foreign object format is not a hosted profile. */
    665 static const HostedProfile* hosted_profile_for(KitTargetSpec target) {
    666   size_t i;
    667   for (i = 0; i < sizeof hosted_profiles / sizeof hosted_profiles[0]; ++i) {
    668     if (hosted_profiles[i].os == target.os &&
    669         hosted_profiles[i].obj == target.obj)
    670       return &hosted_profiles[i];
    671   }
    672   return NULL;
    673 }
    674 
    675 /* The sysroot directory layout keys on OS alone (each hosted OS has a single
    676  * canonical object format), matching the historical os-only expansion. */
    677 static const HostedProfile* hosted_profile_for_os(KitOSKind os) {
    678   size_t i;
    679   for (i = 0; i < sizeof hosted_profiles / sizeof hosted_profiles[0]; ++i) {
    680     if (hosted_profiles[i].os == os) return &hosted_profiles[i];
    681   }
    682   return NULL;
    683 }
    684 
    685 /* Expand an explicit sysroot into the dir list via the target's profile.
    686  * Unknown targets leave the dir list empty (the dispatcher emits the error). */
    687 static int hosted_dirs_from_sysroot(DriverHostedDirs* dirs,
    688                                     KitTargetSpec target, const char* sysroot) {
    689   const HostedProfile* profile = hosted_profile_for_os(target.os);
    690   dirs->root =
    691       sysroot; /* a single explicit sysroot; -print-sysroot reports it */
    692   if (!profile) return 0; /* unsupported target; the dispatcher emits the error */
    693   return profile->sysroot_layout(dirs, target, sysroot);
    694 }
    695 
    696 /* ---------------- orchestration ---------------- */
    697 
    698 int driver_hosted_dirs_resolve(const DriverHostedRequest* req,
    699                                DriverHostedDirs* out) {
    700   const char* sysroot;
    701   if (!req || !req->env || !out) return 1;
    702   memset(out, 0, sizeof(*out));
    703   out->env = req->env;
    704   /* KIT_SYSROOT: the single, portable, explicit env override. Works on every
    705    * OS and applies to any target including cross-compiles, exactly like
    706    * --sysroot. */
    707   sysroot = (req->sysroot && req->sysroot[0]) ? req->sysroot : NULL;
    708   if (!sysroot) {
    709     const char* env_sysroot = driver_getenv("KIT_SYSROOT");
    710     if (env_sysroot && env_sysroot[0]) sysroot = env_sysroot;
    711   }
    712   if (sysroot) {
    713     if (hosted_dirs_from_sysroot(out, req->target, sysroot) != 0) {
    714       driver_hosted_dirs_fini(out);
    715       return 1;
    716     }
    717   } else {
    718     /* Host probe: auto-discover the host's own includes/libs. Gated on the
    719      * target matching the host OS *and* arch so it never fires for any
    720      * cross-compile -- a host SDK is meaningless for a foreign platform.
    721      * Best-effort: leaves dirs empty when nothing is found, and the resolver
    722      * then emits its "requires --sysroot" error. */
    723     KitTargetSpec host = driver_host_target();
    724     if (req->target.os == host.os && req->target.arch == host.arch)
    725       (void)driver_default_hosted_dirs(req->env, req->target, out);
    726   }
    727   return 0;
    728 }
    729 
    730 int driver_hosted_resolve(const DriverHostedRequest* req,
    731                           DriverHostedPlan* out) {
    732   DriverHostedPlan zero = {0};
    733   DriverHostedDirs dirs;
    734   const HostedProfile* profile;
    735   int rc;
    736   if (!req || !out || !req->env || !req->tool) return 1;
    737   *out = zero;
    738   if (driver_hosted_dirs_resolve(req, &dirs) != 0) {
    739     driver_errf(req->tool, "out of memory");
    740     return 1;
    741   }
    742   profile = hosted_profile_for(req->target);
    743   if (profile) {
    744     rc = profile->resolve(req, &dirs, out);
    745   } else {
    746     driver_errf(req->tool, "no hosted libc profile for target");
    747     rc = 1;
    748   }
    749   if (rc == 0) {
    750     /* Transfer libdir ownership from dirs into the plan so callers can add
    751      * them to their lib search path for user-specified -l resolution. */
    752     uint32_t j;
    753     for (j = 0; j < dirs.nlibdirs &&
    754                 j < DRIVER_HOSTED_MAX_LIB_SEARCH_DIRS; ++j) {
    755       out->lib_search_dirs[j] = dirs.libdirs[j];
    756       out->lib_search_dir_sizes[j] = dirs.libdir_sizes[j];
    757       dirs.libdirs[j] = NULL;
    758       dirs.libdir_sizes[j] = 0;
    759     }
    760     out->nlib_search_dirs = j;
    761   }
    762   driver_hosted_dirs_fini(&dirs);
    763   if (rc != 0) driver_hosted_plan_fini(req->env, out);
    764   return rc;
    765 }
    766 
    767 void driver_hosted_plan_fini(DriverEnv* env, DriverHostedPlan* plan) {
    768   uint32_t i;
    769   if (!env || !plan) return;
    770   for (i = 0; i < plan->nbefore; ++i) {
    771     if (plan->before[i].owned_path)
    772       driver_free(env, plan->before[i].owned_path, plan->before[i].owned_size);
    773   }
    774   for (i = 0; i < plan->nafter; ++i) {
    775     if (plan->after[i].owned_path)
    776       driver_free(env, plan->after[i].owned_path, plan->after[i].owned_size);
    777   }
    778   for (i = 0; i < plan->nfinal; ++i) {
    779     if (plan->final[i].owned_path)
    780       driver_free(env, plan->final[i].owned_path, plan->final[i].owned_size);
    781   }
    782   for (i = 0; i < plan->nsystem_includes; ++i) {
    783     if (plan->owned_system_includes[i])
    784       driver_free(env, plan->owned_system_includes[i],
    785                   plan->owned_system_include_sizes[i]);
    786   }
    787   for (i = 0; i < plan->nlib_search_dirs; ++i) {
    788     if (plan->lib_search_dirs[i])
    789       driver_free(env, plan->lib_search_dirs[i],
    790                   plan->lib_search_dir_sizes[i]);
    791   }
    792   memset(plan, 0, sizeof(*plan));
    793 }