commit 8fa98299090ab9d859b5b53d7cf0f2a046a8a44d
parent fd83f1e6567d8dc8866ec4be7512b51625564627
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 19 May 2026 19:46:02 -0700
Add hosted libc and runtime driver support
Diffstat:
11 files changed, 1831 insertions(+), 15 deletions(-)
diff --git a/doc/HOSTED.md b/doc/HOSTED.md
@@ -34,6 +34,7 @@ Rules:
- `cfree cc -c`, `-E`, dependency-only, and token-dump actions do not add it.
- `cfree ld` remains explicit and does not auto-add compiler runtime unless a
future deliberate option such as `--compiler-rt=auto` is added.
+- `-nostdlib` and `-nodefaultlibs` suppress this automatic runtime input.
- The runtime archive is placed late in the ordered link inputs, after user
objects and libraries, so demand-loaded helpers satisfy unresolved references.
@@ -68,12 +69,38 @@ support/
rt/
include/
lib/
+ cache/
```
The driver should be able to locate this support root from the installed binary
path. A command-line override such as `--support-dir DIR` should be available
for tests, development trees, and unusual packaging.
+The same resolver must also work from a development checkout. If the current
+tree contains `./rt/include/` and `./rt/lib/`, those directories are a valid
+support layout without an installed `support/` wrapper:
+
+```text
+./rt/
+ include/
+ lib/
+ build/
+```
+
+Support path resolution order:
+
+1. An explicit `--support-dir DIR`.
+2. A development tree rooted at the current working directory when
+ `./rt/include/` and `./rt/lib/` both exist.
+3. An installed support directory adjacent to the `cfree` executable.
+
+The resolved `rt/include/` directory is cfree's freestanding system include
+root. `cfree cc` should add it to preprocessing and compile actions as a system
+include directory before any configured SDK/sysroot. This applies to `-E`, `-M`,
+`-MM`, `-MD`, `-MMD`, `-c`, and compile-and-link actions. Hosted libc headers
+under `rt/include/libc/` are separate and should be added only by the hosted
+profile when libc is requested.
+
## Runtime Build Cache
`cfree cc` should compile and cache `libcfree_rt.a` for the target on demand.
@@ -86,6 +113,18 @@ support/cache/<target-triple>/
obj/
```
+In a development checkout the cache path is:
+
+```text
+rt/build/<target-key>/
+ libcfree_rt.a
+ obj/
+```
+
+`<target-key>` should match the runtime Makefile variant names where possible,
+for example `aarch64-linux`, `x86_64-linux`, `riscv64-linux`,
+`aarch64-apple-darwin`, and `x86_64-apple-darwin`.
+
The cache builder belongs in a dedicated driver module, for example
`driver/runtime.c` with `driver/runtime.h`.
@@ -93,11 +132,30 @@ Responsibilities:
- Resolve the support directory.
- Map a `CfreeTarget` to a stable target cache key/triple.
-- Check for an existing runtime archive.
-- Build missing or stale runtime objects for the target.
+- Check for an existing runtime archive in the resolved cache path.
+- Build missing or stale runtime objects from the resolved `rt/lib/` sources for
+ the target.
- Archive them into `libcfree_rt.a`.
- Return a concrete path to the archive for insertion into ordered link inputs.
+Runtime source builds should use only support-tree inputs:
+
+- Freestanding headers: `-isystem <support-rt>/include`.
+- Runtime-private common includes:
+ `-I<support-rt>/lib/include/common -I<support-rt>/lib/impl`.
+- ABI-specific includes such as `-I<support-rt>/lib/include/lp64_le`.
+- Target feature sources and flags selected from the target key, mirroring
+ `rt/Makefile`.
+
+For normal `cfree cc` compile actions, only the freestanding header include path
+is injected. The runtime-private `rt/lib/...` include paths are private to the
+runtime builder.
+
+For link actions, the returned archive path is appended to the ordered link
+inputs after user objects, explicit user archives, and ordinary `-l` libraries,
+but before hosted finalization objects such as `crtn.o` when a hosted profile is
+active.
+
Failure should produce diagnostics that name the target and the support path:
```text
diff --git a/driver/cc.c b/driver/cc.c
@@ -7,7 +7,9 @@
#include "cflags.h"
#include "driver.h"
+#include "hosted.h"
#include "lib_resolve.h"
+#include "runtime.h"
/* `cfree cc` — C compiler driver. With -c produces a single object;
* without -c compiles all C sources, links any .o/.a inputs alongside, and
@@ -90,6 +92,7 @@ typedef struct CcPendingLib {
typedef struct CcOptions {
DriverEnv* env;
+ const char* driver_path;
size_t argv_bound; /* upper bound on per-array list size */
int compile_only; /* -c */
@@ -107,6 +110,8 @@ typedef struct CcOptions {
size_t owned_output_path_size;
const char* entry; /* -e */
const char* linker_script; /* -T path */
+ const char* sysroot; /* --sysroot / -isysroot */
+ const char* support_dir; /* --support-dir */
/* -ffile-prefix-map=old=new entries; old is heap-owned (split out), new
* aliases argv. */
@@ -173,6 +178,11 @@ typedef struct CcOptions {
int pie;
int gc_sections;
const char* interp_path;
+ int no_stdlib;
+ int no_defaultlibs;
+ int no_startfiles;
+ int wants_hosted_libc;
+ DriverHostedPlan hosted;
} CcOptions;
static void cc_usage(void) {
@@ -197,12 +207,14 @@ void driver_help_cc(void) {
" cfree cc -shared [options] inputs... link a shared library\n"
" cfree cc -M|-MM [options] input.c print header deps; no "
"compile\n"
+ " --sysroot DIR / -isysroot DIR hosted SDK/sysroot\n"
+ " --support-dir DIR cfree support root\n"
"\n"
"(see source for the full GCC-subset flag reference)\n");
}
static int cc_alloc_arrays(CcOptions* o, int argc) {
- size_t bound = (size_t)argc;
+ size_t bound = (size_t)argc + 16u;
o->argv_bound = bound;
o->source_files =
driver_alloc_zeroed(o->env, bound * sizeof(*o->source_files));
@@ -235,7 +247,7 @@ static int cc_alloc_arrays(CcOptions* o, int argc) {
}
o->new_dtags = 1;
o->cur_link_mode = CFREE_LM_DEFAULT;
- if (driver_cflags_init(&o->cf, o->env, argc) != 0) {
+ if (driver_cflags_init(&o->cf, o->env, (int)bound) != 0) {
driver_errf(CC_TOOL, "out of memory");
return 1;
}
@@ -267,6 +279,7 @@ static void cc_options_release(CcOptions* o) {
driver_free(o->env, o->build_id_bytes, o->build_id_len);
if (o->owned_output_path)
driver_free(o->env, o->owned_output_path, o->owned_output_path_size);
+ driver_hosted_plan_fini(o->env, &o->hosted);
driver_cflags_fini(&o->cf, o->env);
driver_free(o->env, o->source_files, bound * sizeof(*o->source_files));
driver_free(o->env, o->source_langs, bound * sizeof(*o->source_langs));
@@ -296,6 +309,7 @@ static char* cc_dup_span(DriverEnv* env, const char* s, size_t n) {
}
static int cc_record_build_id(CcOptions* o, const char* val);
+static int cc_apply_hosted_profile(CcOptions* o);
/* Parse a single GCC-style -Wl,X[,Y...] pass-through argument. */
static int cc_record_wl(CcOptions* o, const char* arg) {
@@ -428,6 +442,68 @@ static void cc_push_link_item(CcOptions* o, uint8_t kind, uint32_t index) {
it->index = index;
}
+static void cc_insert_link_item(CcOptions* o, uint32_t pos, uint8_t kind,
+ uint32_t index) {
+ uint32_t i;
+ if (pos > o->nlink_items) pos = o->nlink_items;
+ for (i = o->nlink_items; i > pos; --i) {
+ o->link_items[i] = o->link_items[i - 1u];
+ }
+ o->link_items[pos].kind = kind;
+ o->link_items[pos].index = index;
+ o->nlink_items++;
+}
+
+static int cc_append_hosted_input(CcOptions* o, const DriverHostedInput* in,
+ uint32_t insert_pos, int insert) {
+ uint32_t index;
+ uint8_t kind;
+ switch ((DriverHostedInputKind)in->kind) {
+ case DRIVER_HOSTED_INPUT_OBJECT:
+ index = o->nobject_files++;
+ o->object_files[index] = in->path;
+ kind = CC_LINK_OBJECT;
+ break;
+ case DRIVER_HOSTED_INPUT_ARCHIVE: {
+ CcArchiveInput* ar = &o->archives[o->narchives++];
+ ar->path = in->path;
+ ar->whole_archive = 0;
+ ar->link_mode = CFREE_LM_DEFAULT;
+ ar->group_id = 0;
+ index = o->narchives - 1u;
+ kind = CC_LINK_ARCHIVE;
+ break;
+ }
+ case DRIVER_HOSTED_INPUT_DSO: {
+ CcDsoInput* d = &o->dsos[o->ndsos++];
+ d->path = in->path;
+ index = o->ndsos - 1u;
+ kind = CC_LINK_DSO;
+ break;
+ }
+ default:
+ driver_errf(CC_TOOL, "internal error: unknown hosted input kind");
+ return 1;
+ }
+ if (insert)
+ cc_insert_link_item(o, insert_pos, kind, index);
+ else
+ cc_push_link_item(o, kind, index);
+ return 0;
+}
+
+static void cc_insert_owned_archive(CcOptions* o, char* path, size_t path_size,
+ uint32_t insert_pos) {
+ CcArchiveInput* ar = &o->archives[o->narchives++];
+ ar->path = path;
+ ar->owned = 1;
+ ar->owned_size = path_size;
+ ar->whole_archive = 0;
+ ar->link_mode = CFREE_LM_STATIC;
+ ar->group_id = 0;
+ cc_insert_link_item(o, insert_pos, CC_LINK_ARCHIVE, o->narchives - 1u);
+}
+
static int cc_parse_u64(const char* s, uint64_t* out) {
uint64_t v = 0;
int any = 0;
@@ -654,6 +730,53 @@ static int cc_resolve_pending_libs(CcOptions* o) {
return 0;
}
+static int cc_apply_hosted_profile(CcOptions* o) {
+ DriverHostedRequest req;
+ uint32_t i;
+ uint32_t insert_pos = 0;
+ int link_action = !o->compile_only && !o->preprocess_only &&
+ !o->dump_tokens && o->dep_mode != CC_DEP_M &&
+ o->dep_mode != CC_DEP_MM;
+ if (!link_action || !o->wants_hosted_libc || o->shared) return 0;
+ if (o->no_stdlib || o->no_defaultlibs) {
+ driver_errf(CC_TOOL,
+ "-lc hosted expansion is disabled by -nostdlib/-nodefaultlibs");
+ return 1;
+ }
+ {
+ DriverHostedRequest z = {0};
+ req = z;
+ }
+ req.env = o->env;
+ req.tool = CC_TOOL;
+ req.target = o->target;
+ req.sysroot = o->sysroot;
+ req.support_dir = o->support_dir;
+ req.driver_path = o->driver_path;
+ req.static_link = o->static_link;
+ if (driver_hosted_resolve(&req, &o->hosted) != 0) return 1;
+ for (i = 0; i < o->hosted.nsystem_includes; ++i) {
+ o->cf.system_include_dirs[o->cf.nsystem_include_dirs++] =
+ o->hosted.system_includes[i];
+ }
+ for (i = 0; i < o->hosted.nbefore; ++i) {
+ if (o->no_startfiles) break;
+ if (cc_append_hosted_input(o, &o->hosted.before[i], insert_pos, 1) != 0)
+ return 1;
+ insert_pos++;
+ }
+ for (i = 0; i < o->hosted.nafter; ++i) {
+ if (cc_append_hosted_input(o, &o->hosted.after[i], 0, 0) != 0) return 1;
+ }
+ for (i = 0; i < o->hosted.nfinal; ++i) {
+ if (o->no_startfiles) break;
+ if (cc_append_hosted_input(o, &o->hosted.final[i], 0, 0) != 0) return 1;
+ }
+ if (!o->interp_path && o->hosted.interp_path)
+ o->interp_path = o->hosted.interp_path;
+ return 0;
+}
+
static int cc_apply_env(CcOptions* o) {
const char* sde = driver_getenv("SOURCE_DATE_EPOCH");
if (sde && cc_parse_u64(sde, &o->epoch) != 0) {
@@ -755,23 +878,55 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
}
if (driver_streq(a, "-ffreestanding") || driver_streq(a, "-fhosted") ||
driver_streq(a, "-fno-builtin") || driver_streq(a, "-pipe") ||
- driver_streq(a, "-pthread") || driver_streq(a, "-nostdlib") ||
- driver_streq(a, "-nodefaultlibs") || driver_streq(a, "-nostartfiles") ||
- driver_streq(a, "-nostdinc")) {
+ driver_streq(a, "-pthread") || driver_streq(a, "-nostdinc")) {
cc_log_ignored(a);
continue;
}
- if (driver_streq(a, "-isysroot") || driver_streq(a, "--sysroot") ||
- driver_streq(a, "-include") || driver_streq(a, "-iquote") ||
- driver_streq(a, "-idirafter")) {
+ if (driver_streq(a, "-nostdlib")) {
+ o->no_stdlib = 1;
+ cc_log_ignored(a);
+ continue;
+ }
+ if (driver_streq(a, "-nodefaultlibs")) {
+ o->no_defaultlibs = 1;
+ cc_log_ignored(a);
+ continue;
+ }
+ if (driver_streq(a, "-nostartfiles")) {
+ o->no_startfiles = 1;
+ cc_log_ignored(a);
+ continue;
+ }
+ if (driver_streq(a, "-isysroot") || driver_streq(a, "--sysroot")) {
if (++i >= argc) {
driver_errf(CC_TOOL, "%s requires an argument", a);
return 1;
}
- cc_log_ignored(a);
+ o->sysroot = argv[i];
continue;
}
if (driver_strneq(a, "--sysroot=", 10)) {
+ o->sysroot = a + 10;
+ continue;
+ }
+ if (driver_streq(a, "--support-dir")) {
+ if (++i >= argc) {
+ driver_errf(CC_TOOL, "--support-dir requires an argument");
+ return 1;
+ }
+ o->support_dir = argv[i];
+ continue;
+ }
+ if (driver_strneq(a, "--support-dir=", 14)) {
+ o->support_dir = a + 14;
+ continue;
+ }
+ if (driver_streq(a, "-include") || driver_streq(a, "-iquote") ||
+ driver_streq(a, "-idirafter")) {
+ if (++i >= argc) {
+ driver_errf(CC_TOOL, "%s requires an argument", a);
+ return 1;
+ }
cc_log_ignored(a);
continue;
}
@@ -975,6 +1130,10 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
driver_errf(CC_TOOL, "-l requires an argument");
return 1;
}
+ if (driver_streq(name, "c") && !o->no_stdlib && !o->no_defaultlibs) {
+ o->wants_hosted_libc = 1;
+ continue;
+ }
{
CcPendingLib* pl = &o->pending_libs[o->npending_libs++];
pl->name = name;
@@ -1140,6 +1299,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
}
}
}
+ if (cc_apply_hosted_profile(o) != 0) return 1;
return 0;
}
@@ -1696,7 +1856,8 @@ static int cc_run_compile_objs(DriverEnv* env, const CcOptions* o,
if (o->output_path) return cc_run_compile_obj(env, o, pp);
for (i = 0; i < o->nsource_files; ++i) {
size_t out_size = 0;
- char* out = cc_default_obj_path_for_name(env, o->source_files[i], &out_size);
+ char* out =
+ cc_default_obj_path_for_name(env, o->source_files[i], &out_size);
int rc;
if (!out) {
driver_errf(CC_TOOL, "out of memory");
@@ -2000,8 +2161,11 @@ out:
int driver_cc(int argc, char** argv) {
DriverEnv env;
CcOptions co = {0};
+ DriverRuntimeSupport runtime = {0};
CfreePreprocessOptions pp;
int rc;
+ int runtime_resolved = 0;
+ int link_action;
if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
driver_help_cc();
@@ -2010,6 +2174,7 @@ int driver_cc(int argc, char** argv) {
driver_env_init(&env);
co.env = &env;
+ co.driver_path = argv[0];
if (cc_parse(argc, argv, &co) != 0) {
cc_options_release(&co);
@@ -2017,6 +2182,50 @@ int driver_cc(int argc, char** argv) {
return 2;
}
+ link_action = !co.compile_only && !co.preprocess_only && !co.dump_tokens &&
+ co.dep_mode != CC_DEP_M && co.dep_mode != CC_DEP_MM;
+ if (driver_runtime_resolve(&env, co.support_dir, co.driver_path, &runtime) ==
+ 0) {
+ runtime_resolved = 1;
+ if (co.nsource_files || co.nsource_memory) {
+ if (driver_runtime_add_freestanding_headers(&runtime, &co.cf) != 0) {
+ driver_errf(CC_TOOL, "failed to add freestanding headers");
+ driver_runtime_support_fini(&env, &runtime);
+ cc_options_release(&co);
+ driver_env_fini(&env);
+ return 1;
+ }
+ }
+ } else if (co.support_dir || link_action || co.nsource_files ||
+ co.nsource_memory) {
+ driver_errf(CC_TOOL, "support dir not found");
+ cc_options_release(&co);
+ driver_env_fini(&env);
+ return 1;
+ }
+
+ if (link_action && !co.no_stdlib && !co.no_defaultlibs) {
+ char* rt_path = NULL;
+ size_t rt_path_size = 0;
+ uint32_t insert_pos;
+ if (!runtime_resolved) {
+ driver_errf(CC_TOOL, "support dir not found");
+ cc_options_release(&co);
+ driver_env_fini(&env);
+ return 1;
+ }
+ if (driver_runtime_ensure_archive(&env, &runtime, co.target, co.epoch,
+ &rt_path, &rt_path_size) != 0) {
+ driver_runtime_support_fini(&env, &runtime);
+ cc_options_release(&co);
+ driver_env_fini(&env);
+ return 1;
+ }
+ insert_pos = co.nlink_items;
+ if (co.hosted.nfinal <= insert_pos) insert_pos -= co.hosted.nfinal;
+ cc_insert_owned_archive(&co, rt_path, rt_path_size, insert_pos);
+ }
+
driver_cflags_fill_pp(&co.cf, &pp);
if (co.preprocess_only) {
@@ -2032,6 +2241,7 @@ int driver_cc(int argc, char** argv) {
}
cc_options_release(&co);
+ if (runtime_resolved) driver_runtime_support_fini(&env, &runtime);
driver_env_fini(&env);
return rc;
}
diff --git a/driver/cflags.c b/driver/cflags.c
@@ -4,7 +4,7 @@
#include <stdint.h>
int driver_cflags_init(DriverCflags* cf, DriverEnv* env, int argc_bound) {
- size_t bound = argc_bound > 0 ? (size_t)argc_bound : 1;
+ size_t bound = argc_bound > 0 ? (size_t)argc_bound + 4u : 4u;
cf->_cap = bound;
cf->include_dirs =
driver_alloc_zeroed(env, bound * sizeof(*cf->include_dirs));
diff --git a/driver/driver.h b/driver/driver.h
@@ -170,6 +170,13 @@ CfreeWriter *driver_stdout_writer(DriverEnv *);
* loops don't slurp file contents on a hit. */
int driver_path_exists(const char *path);
+/* Read a path's last modification time in nanoseconds since the Unix epoch.
+ * Returns 0 on success, nonzero on stat failure. */
+int driver_path_mtime_ns(const char *path, int64_t *out);
+
+/* Create a directory and any missing parents. Returns 0 on success. */
+int driver_mkdir_p(DriverEnv *, const char *path);
+
/* Set a linked binary output's final mode according to the active umask.
* Returns 0 on success, nonzero on chmod failure. */
int driver_mark_executable_output(const char *path);
diff --git a/driver/env.c b/driver/env.c
@@ -1275,6 +1275,67 @@ int driver_path_exists(const char *path) {
return stat(path, &sb) == 0;
}
+int driver_path_mtime_ns(const char *path, int64_t *out) {
+ struct stat sb;
+ int64_t sec;
+ int64_t nsec;
+
+ if (!path || !out)
+ return 1;
+ if (stat(path, &sb) != 0)
+ return 1;
+#if defined(__APPLE__)
+ sec = (int64_t)sb.st_mtimespec.tv_sec;
+ nsec = (int64_t)sb.st_mtimespec.tv_nsec;
+#elif defined(__linux__)
+ sec = (int64_t)sb.st_mtim.tv_sec;
+ nsec = (int64_t)sb.st_mtim.tv_nsec;
+#else
+ sec = (int64_t)sb.st_mtime;
+ nsec = 0;
+#endif
+ *out = sec * 1000000000LL + nsec;
+ return 0;
+}
+
+int driver_mkdir_p(DriverEnv *env, const char *path) {
+ size_t len;
+ char *buf;
+ size_t i;
+ struct stat sb;
+
+ if (!path || !path[0])
+ return 1;
+ len = strlen(path);
+ buf = (char *)driver_alloc(env, len + 1);
+ if (!buf)
+ return 1;
+ memcpy(buf, path, len + 1);
+
+ for (i = 1; i <= len; ++i) {
+ int at_end = (i == len);
+ if (!at_end && buf[i] != '/')
+ continue;
+ if (!at_end)
+ buf[i] = '\0';
+ if (buf[0] != '\0' && strcmp(buf, ".") != 0) {
+ if (mkdir(buf, 0755) != 0 && errno != EEXIST) {
+ driver_free(env, buf, len + 1);
+ return 1;
+ }
+ if (stat(buf, &sb) != 0 || !S_ISDIR(sb.st_mode)) {
+ driver_free(env, buf, len + 1);
+ return 1;
+ }
+ }
+ if (!at_end)
+ buf[i] = '/';
+ }
+
+ driver_free(env, buf, len + 1);
+ return 0;
+}
+
int driver_mark_executable_output(const char *path) {
mode_t mask;
mode_t mode;
diff --git a/driver/hosted.c b/driver/hosted.c
@@ -0,0 +1,621 @@
+#include "hosted.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+static char* hosted_join2(DriverEnv* env, const char* a, const char* b,
+ size_t* out_size) {
+ size_t alen = driver_strlen(a);
+ size_t blen = driver_strlen(b);
+ size_t slash = (alen > 0 && a[alen - 1] != '/') ? 1u : 0u;
+ size_t bytes = alen + slash + blen + 1u;
+ char* out = driver_alloc(env, bytes);
+ size_t off = 0;
+ if (!out) return NULL;
+ if (alen) {
+ driver_memcpy(out + off, a, alen);
+ off += alen;
+ }
+ if (slash) out[off++] = '/';
+ if (blen) {
+ driver_memcpy(out + off, b, blen);
+ off += blen;
+ }
+ out[off] = '\0';
+ if (out_size) *out_size = bytes;
+ return out;
+}
+
+static char* hosted_driver_dir(DriverEnv* env, const char* driver_path,
+ size_t* out_size) {
+ const char* slash = NULL;
+ const char* p;
+ size_t len;
+ char* out;
+ if (!driver_path || !driver_path[0]) driver_path = "build/cfree";
+ for (p = driver_path; *p; ++p) {
+ if (*p == '/') slash = p;
+ }
+ if (!slash) return hosted_join2(env, ".", "", out_size);
+ len = (size_t)(slash - driver_path);
+ if (len == 0) len = 1;
+ out = driver_alloc(env, len + 1u);
+ if (!out) return NULL;
+ driver_memcpy(out, driver_path, len);
+ out[len] = '\0';
+ *out_size = len + 1u;
+ return out;
+}
+
+static int hosted_dir_has_rt_layout(DriverEnv* env, const char* dir) {
+ char* inc;
+ char* lib;
+ size_t inc_size = 0;
+ size_t lib_size = 0;
+ int ok;
+ inc = hosted_join2(env, dir, "rt/include", &inc_size);
+ lib = hosted_join2(env, dir, "rt/lib", &lib_size);
+ if (!inc || !lib) {
+ if (inc) driver_free(env, inc, inc_size);
+ if (lib) driver_free(env, lib, lib_size);
+ return 0;
+ }
+ ok = driver_path_exists(inc) && driver_path_exists(lib);
+ driver_free(env, inc, inc_size);
+ driver_free(env, lib, lib_size);
+ return ok;
+}
+
+static char* hosted_resolve_support_root(const DriverHostedRequest* req,
+ size_t* out_size) {
+ DriverEnv* env = req->env;
+ if (req->support_dir && req->support_dir[0]) {
+ size_t n = driver_strlen(req->support_dir) + 1u;
+ char* out = driver_alloc(env, n);
+ if (!out) return NULL;
+ driver_memcpy(out, req->support_dir, n);
+ *out_size = n;
+ return out;
+ }
+ if (hosted_dir_has_rt_layout(env, ".")) {
+ char* out = driver_alloc(env, 2u);
+ if (!out) return NULL;
+ out[0] = '.';
+ out[1] = '\0';
+ *out_size = 2u;
+ return out;
+ }
+ {
+ size_t dir_size = 0;
+ char* dir = hosted_driver_dir(env, req->driver_path, &dir_size);
+ if (!dir) return NULL;
+ if (hosted_dir_has_rt_layout(env, dir)) {
+ *out_size = dir_size;
+ return dir;
+ }
+ {
+ size_t parent_size = 0;
+ char* parent = hosted_join2(env, dir, "..", &parent_size);
+ if (!parent) {
+ driver_free(env, dir, dir_size);
+ return NULL;
+ }
+ if (hosted_dir_has_rt_layout(env, parent)) {
+ driver_free(env, dir, dir_size);
+ *out_size = parent_size;
+ return parent;
+ }
+ driver_free(env, parent, parent_size);
+ }
+ {
+ size_t support_size = 0;
+ char* support = hosted_join2(env, dir, "support", &support_size);
+ driver_free(env, dir, dir_size);
+ if (!support) return NULL;
+ *out_size = support_size;
+ return support;
+ }
+ }
+}
+
+static int hosted_add_input(DriverHostedInput* items, uint32_t* n, uint32_t cap,
+ uint8_t kind, char* path, size_t path_size) {
+ DriverHostedInput* it;
+ if (*n >= cap) return 1;
+ it = &items[*n];
+ it->kind = kind;
+ it->path = path;
+ it->owned_path = path;
+ it->owned_size = path_size;
+ (*n)++;
+ return 0;
+}
+
+static int hosted_add_required(DriverHostedInput* items, uint32_t* n,
+ uint32_t cap, const DriverHostedRequest* req,
+ const char* base, const char* rel,
+ uint8_t kind) {
+ size_t size = 0;
+ char* path = hosted_join2(req->env, base, rel, &size);
+ if (!path) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (!driver_path_exists(path)) {
+ driver_errf(req->tool, "hosted profile missing required file: %s", path);
+ driver_free(req->env, path, size);
+ return 1;
+ }
+ if (hosted_add_input(items, n, cap, kind, path, size) != 0) {
+ driver_errf(req->tool, "too many hosted inputs");
+ driver_free(req->env, path, size);
+ return 1;
+ }
+ return 0;
+}
+
+static int hosted_add_existing_include(DriverHostedPlan* plan, DriverEnv* env,
+ const char* base, const char* rel) {
+ size_t size = 0;
+ char* path;
+ if (plan->nsystem_includes >= DRIVER_HOSTED_MAX_INCLUDES) return 1;
+ path = hosted_join2(env, base, rel, &size);
+ if (!path) return 1;
+ if (!driver_path_exists(path)) {
+ driver_free(env, path, size);
+ return 0;
+ }
+ plan->system_includes[plan->nsystem_includes] = path;
+ plan->owned_system_includes[plan->nsystem_includes] = path;
+ plan->owned_system_include_sizes[plan->nsystem_includes] = size;
+ plan->nsystem_includes++;
+ return 0;
+}
+
+static const char* hosted_linux_arch_suffix(CfreeArchKind arch) {
+ switch (arch) {
+ case CFREE_ARCH_ARM_64:
+ return "aarch64";
+ case CFREE_ARCH_X86_64:
+ return "x64";
+ case CFREE_ARCH_RV64:
+ return "rv64";
+ default:
+ return NULL;
+ }
+}
+
+static const char* hosted_glibc_interp(CfreeArchKind arch) {
+ switch (arch) {
+ case CFREE_ARCH_ARM_64:
+ return "/lib/ld-linux-aarch64.so.1";
+ case CFREE_ARCH_X86_64:
+ return "/lib64/ld-linux-x86-64.so.2";
+ case CFREE_ARCH_RV64:
+ return "/lib/ld-linux-riscv64-lp64d.so.1";
+ default:
+ return NULL;
+ }
+}
+
+static const char* hosted_musl_interp(CfreeArchKind arch) {
+ switch (arch) {
+ case CFREE_ARCH_ARM_64:
+ return "/lib/ld-musl-aarch64.so.1";
+ case CFREE_ARCH_X86_64:
+ return "/lib/ld-musl-x86_64.so.1";
+ case CFREE_ARCH_RV64:
+ return "/lib/ld-musl-riscv64.so.1";
+ default:
+ return NULL;
+ }
+}
+
+static int hosted_add_linux_shim(DriverHostedPlan* plan,
+ const DriverHostedRequest* req,
+ const char* support_root) {
+ const char* suffix = hosted_linux_arch_suffix(req->target.arch);
+ char rel[64];
+ char devrel[64];
+ if (!suffix) {
+ driver_errf(req->tool, "no hosted Linux profile for target architecture");
+ return 1;
+ }
+ rel[0] = '\0';
+ devrel[0] = '\0';
+ {
+ const char prefix[] = "rt/lib/cfree_hosted/linux-";
+ const char obj[] = ".o";
+ size_t off = 0;
+ size_t n = driver_strlen(prefix);
+ size_t s = driver_strlen(suffix);
+ driver_memcpy(rel + off, prefix, n);
+ off += n;
+ driver_memcpy(rel + off, suffix, s);
+ off += s;
+ driver_memcpy(rel + off, obj, sizeof(obj));
+ }
+ {
+ const char prefix[] = "build/cfree_hosted/linux-";
+ const char obj[] = ".o";
+ size_t off = 0;
+ size_t n = driver_strlen(prefix);
+ size_t s = driver_strlen(suffix);
+ driver_memcpy(devrel + off, prefix, n);
+ off += n;
+ driver_memcpy(devrel + off, suffix, s);
+ off += s;
+ driver_memcpy(devrel + off, obj, sizeof(obj));
+ }
+ {
+ size_t size = 0;
+ char* path = hosted_join2(req->env, support_root, rel, &size);
+ if (!path) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (driver_path_exists(path)) {
+ return hosted_add_input(plan->after, &plan->nafter,
+ DRIVER_HOSTED_MAX_AFTER,
+ DRIVER_HOSTED_INPUT_OBJECT, path, size);
+ }
+ driver_free(req->env, path, size);
+ }
+ {
+ size_t size = 0;
+ char* path = hosted_join2(req->env, support_root, devrel, &size);
+ if (!path) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (driver_path_exists(path)) {
+ return hosted_add_input(plan->after, &plan->nafter,
+ DRIVER_HOSTED_MAX_AFTER,
+ DRIVER_HOSTED_INPUT_OBJECT, path, size);
+ }
+ driver_errf(req->tool, "hosted profile missing required file: %s", path);
+ driver_free(req->env, path, size);
+ }
+ return 1;
+}
+
+static int hosted_add_macos_shim(DriverHostedPlan* plan,
+ const DriverHostedRequest* req,
+ const char* support_root) {
+ {
+ size_t size = 0;
+ char* path =
+ hosted_join2(req->env, support_root,
+ "rt/lib/cfree_hosted/libcfree_hosted_macos.a", &size);
+ if (!path) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (driver_path_exists(path)) {
+ return hosted_add_input(plan->after, &plan->nafter,
+ DRIVER_HOSTED_MAX_AFTER,
+ DRIVER_HOSTED_INPUT_ARCHIVE, path, size);
+ }
+ driver_free(req->env, path, size);
+ }
+ {
+ size_t size = 0;
+ char* path = hosted_join2(req->env, support_root,
+ "build/libcfree_hosted_macos.a", &size);
+ if (!path) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (driver_path_exists(path)) {
+ return hosted_add_input(plan->after, &plan->nafter,
+ DRIVER_HOSTED_MAX_AFTER,
+ DRIVER_HOSTED_INPUT_ARCHIVE, path, size);
+ }
+ driver_free(req->env, path, size);
+ }
+ {
+ size_t dir_size = 0;
+ size_t size = 0;
+ char* dir = hosted_driver_dir(req->env, req->driver_path, &dir_size);
+ char* path;
+ if (!dir) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ path = hosted_join2(req->env, dir, "libcfree_hosted_macos.a", &size);
+ driver_free(req->env, dir, dir_size);
+ if (!path) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (driver_path_exists(path)) {
+ return hosted_add_input(plan->after, &plan->nafter,
+ DRIVER_HOSTED_MAX_AFTER,
+ DRIVER_HOSTED_INPUT_ARCHIVE, path, size);
+ }
+ driver_errf(req->tool, "hosted profile missing required file: %s", path);
+ driver_free(req->env, path, size);
+ }
+ return 1;
+}
+
+static int hosted_resolve_darwin(const DriverHostedRequest* req,
+ DriverHostedPlan* plan,
+ const char* support_root) {
+ size_t size = 0;
+ char* libsystem = NULL;
+ if (!req->sysroot || !req->sysroot[0]) {
+ driver_errf(req->tool,
+ "Darwin hosted profile requires --sysroot; try: --sysroot "
+ "\"$(xcrun --sdk macosx --show-sdk-path)\"");
+ return 1;
+ }
+ plan->profile_name = "macos-libSystem";
+ if (hosted_add_existing_include(plan, req->env, support_root,
+ "rt/include/libc") != 0 ||
+ hosted_add_existing_include(plan, req->env, req->sysroot,
+ "usr/include") != 0) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (hosted_add_macos_shim(plan, req, support_root) != 0) {
+ return 1;
+ }
+ libsystem =
+ hosted_join2(req->env, req->sysroot, "usr/lib/libSystem.tbd", &size);
+ if (!libsystem) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (!driver_path_exists(libsystem)) {
+ driver_free(req->env, libsystem, size);
+ libsystem =
+ hosted_join2(req->env, req->sysroot, "usr/lib/libSystem.dylib", &size);
+ if (!libsystem) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ }
+ if (!driver_path_exists(libsystem)) {
+ driver_errf(req->tool, "hosted profile missing required file: %s",
+ libsystem);
+ driver_free(req->env, libsystem, size);
+ return 1;
+ }
+ if (hosted_add_input(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER,
+ DRIVER_HOSTED_INPUT_DSO, libsystem, size) != 0) {
+ driver_free(req->env, libsystem, size);
+ driver_errf(req->tool, "too many hosted inputs");
+ return 1;
+ }
+ return 0;
+}
+
+static int hosted_resolve_linux_musl_static(const DriverHostedRequest* req,
+ DriverHostedPlan* plan,
+ const char* support_root) {
+ plan->profile_name = "linux-musl-static";
+ if (hosted_add_existing_include(plan, req->env, support_root,
+ "rt/include/libc") != 0 ||
+ hosted_add_existing_include(plan, req->env, req->sysroot, "include") !=
+ 0) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (hosted_add_required(plan->before, &plan->nbefore,
+ DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot,
+ "lib/crt1.o", DRIVER_HOSTED_INPUT_OBJECT) != 0 ||
+ hosted_add_required(plan->before, &plan->nbefore,
+ DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot,
+ "lib/crti.o", DRIVER_HOSTED_INPUT_OBJECT) != 0)
+ return 1;
+ if (hosted_add_linux_shim(plan, req, support_root) != 0) return 1;
+ if (hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER,
+ req, req->sysroot, "lib/libc.a",
+ DRIVER_HOSTED_INPUT_ARCHIVE) != 0)
+ return 1;
+ if (hosted_add_required(plan->final, &plan->nfinal, DRIVER_HOSTED_MAX_FINAL,
+ req, req->sysroot, "lib/crtn.o",
+ DRIVER_HOSTED_INPUT_OBJECT) != 0)
+ return 1;
+ return 0;
+}
+
+static int hosted_resolve_linux_musl_dynamic(const DriverHostedRequest* req,
+ DriverHostedPlan* plan,
+ const char* support_root) {
+ const char* interp = hosted_musl_interp(req->target.arch);
+ if (!interp) {
+ driver_errf(req->tool, "no hosted musl profile for target architecture");
+ return 1;
+ }
+ plan->profile_name = "linux-musl-dynamic";
+ plan->interp_path = interp;
+ if (hosted_add_existing_include(plan, req->env, support_root,
+ "rt/include/libc") != 0 ||
+ hosted_add_existing_include(plan, req->env, req->sysroot, "include") !=
+ 0) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (hosted_add_required(plan->before, &plan->nbefore,
+ DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot,
+ "lib/Scrt1.o", DRIVER_HOSTED_INPUT_OBJECT) != 0 ||
+ hosted_add_required(plan->before, &plan->nbefore,
+ DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot,
+ "lib/crti.o", DRIVER_HOSTED_INPUT_OBJECT) != 0)
+ return 1;
+ if (hosted_add_linux_shim(plan, req, support_root) != 0) return 1;
+ if (hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER,
+ req, req->sysroot, "lib/libc.so",
+ DRIVER_HOSTED_INPUT_DSO) != 0)
+ return 1;
+ if (hosted_add_required(plan->final, &plan->nfinal, DRIVER_HOSTED_MAX_FINAL,
+ req, req->sysroot, "lib/crtn.o",
+ DRIVER_HOSTED_INPUT_OBJECT) != 0)
+ return 1;
+ return 0;
+}
+
+static int hosted_resolve_linux_glibc_dynamic(const DriverHostedRequest* req,
+ DriverHostedPlan* plan,
+ const char* support_root) {
+ const char* interp = hosted_glibc_interp(req->target.arch);
+ if (!interp) {
+ driver_errf(req->tool, "no hosted glibc profile for target architecture");
+ return 1;
+ }
+ plan->profile_name = "linux-glibc-dynamic";
+ plan->interp_path = interp;
+ if (hosted_add_existing_include(plan, req->env, support_root,
+ "rt/include/libc") != 0 ||
+ hosted_add_existing_include(plan, req->env, req->sysroot, "include") !=
+ 0) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ switch (req->target.arch) {
+ case CFREE_ARCH_ARM_64:
+ if (hosted_add_existing_include(plan, req->env, req->sysroot,
+ "include/aarch64-linux-gnu") != 0)
+ return 1;
+ break;
+ case CFREE_ARCH_X86_64:
+ if (hosted_add_existing_include(plan, req->env, req->sysroot,
+ "include/x86_64-linux-gnu") != 0)
+ return 1;
+ break;
+ case CFREE_ARCH_RV64:
+ if (hosted_add_existing_include(plan, req->env, req->sysroot,
+ "include/riscv64-linux-gnu") != 0)
+ return 1;
+ break;
+ default:
+ break;
+ }
+ if (hosted_add_required(plan->before, &plan->nbefore,
+ DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot,
+ "lib/Scrt1.o", DRIVER_HOSTED_INPUT_OBJECT) != 0 ||
+ hosted_add_required(plan->before, &plan->nbefore,
+ DRIVER_HOSTED_MAX_BEFORE, req, req->sysroot,
+ "lib/crti.o", DRIVER_HOSTED_INPUT_OBJECT) != 0)
+ return 1;
+ if (hosted_add_linux_shim(plan, req, support_root) != 0) return 1;
+ if (hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER,
+ req, req->sysroot, "lib/libc.so.6",
+ DRIVER_HOSTED_INPUT_DSO) != 0 ||
+ hosted_add_required(plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER,
+ req, req->sysroot, "lib/libc_nonshared.a",
+ DRIVER_HOSTED_INPUT_ARCHIVE) != 0)
+ return 1;
+ if (hosted_add_required(plan->final, &plan->nfinal, DRIVER_HOSTED_MAX_FINAL,
+ req, req->sysroot, "lib/crtn.o",
+ DRIVER_HOSTED_INPUT_OBJECT) != 0)
+ return 1;
+ return 0;
+}
+
+static int hosted_resolve_linux(const DriverHostedRequest* req,
+ DriverHostedPlan* plan,
+ const char* support_root) {
+ char* libc_a;
+ char* libc_so;
+ char* libc_so6;
+ char* libc_nonshared;
+ size_t sa = 0;
+ size_t ss = 0;
+ size_t ss6 = 0;
+ size_t sns = 0;
+ int has_libc_a;
+ int has_libc_so;
+ int has_libc_so6;
+ int has_glibc_nonshared;
+ if (!req->sysroot || !req->sysroot[0]) {
+ driver_errf(req->tool, "Linux hosted profile requires --sysroot");
+ return 1;
+ }
+ libc_a = hosted_join2(req->env, req->sysroot, "lib/libc.a", &sa);
+ libc_so = hosted_join2(req->env, req->sysroot, "lib/libc.so", &ss);
+ libc_so6 = hosted_join2(req->env, req->sysroot, "lib/libc.so.6", &ss6);
+ libc_nonshared =
+ hosted_join2(req->env, req->sysroot, "lib/libc_nonshared.a", &sns);
+ if (!libc_a || !libc_so || !libc_so6 || !libc_nonshared) {
+ if (libc_a) driver_free(req->env, libc_a, sa);
+ if (libc_so) driver_free(req->env, libc_so, ss);
+ if (libc_so6) driver_free(req->env, libc_so6, ss6);
+ if (libc_nonshared) driver_free(req->env, libc_nonshared, sns);
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ has_libc_a = driver_path_exists(libc_a);
+ has_libc_so = driver_path_exists(libc_so);
+ has_libc_so6 = driver_path_exists(libc_so6);
+ has_glibc_nonshared = driver_path_exists(libc_nonshared);
+ driver_free(req->env, libc_a, sa);
+ driver_free(req->env, libc_so, ss);
+ driver_free(req->env, libc_so6, ss6);
+ driver_free(req->env, libc_nonshared, sns);
+ if (!req->static_link && has_libc_so6 && has_glibc_nonshared)
+ return hosted_resolve_linux_glibc_dynamic(req, plan, support_root);
+ if (has_libc_a && !(has_libc_so6 && has_glibc_nonshared))
+ return hosted_resolve_linux_musl_static(req, plan, support_root);
+ if (!req->static_link && has_libc_so)
+ return hosted_resolve_linux_musl_dynamic(req, plan, support_root);
+ driver_errf(req->tool,
+ "no supported Linux hosted libc found under sysroot: %s",
+ req->sysroot);
+ return 1;
+}
+
+int driver_hosted_resolve(const DriverHostedRequest* req,
+ DriverHostedPlan* out) {
+ size_t support_size = 0;
+ char* support_root;
+ DriverHostedPlan zero = {0};
+ int rc;
+ if (!req || !out || !req->env || !req->tool) return 1;
+ *out = zero;
+ support_root = hosted_resolve_support_root(req, &support_size);
+ if (!support_root) {
+ driver_errf(req->tool, "out of memory");
+ return 1;
+ }
+ if (req->target.os == CFREE_OS_MACOS && req->target.obj == CFREE_OBJ_MACHO) {
+ rc = hosted_resolve_darwin(req, out, support_root);
+ } else if (req->target.os == CFREE_OS_LINUX &&
+ req->target.obj == CFREE_OBJ_ELF) {
+ rc = hosted_resolve_linux(req, out, support_root);
+ } else {
+ driver_errf(req->tool, "no hosted libc profile for target");
+ rc = 1;
+ }
+ driver_free(req->env, support_root, support_size);
+ if (rc != 0) driver_hosted_plan_fini(req->env, out);
+ return rc;
+}
+
+void driver_hosted_plan_fini(DriverEnv* env, DriverHostedPlan* plan) {
+ uint32_t i;
+ if (!env || !plan) return;
+ for (i = 0; i < plan->nbefore; ++i) {
+ if (plan->before[i].owned_path)
+ driver_free(env, plan->before[i].owned_path, plan->before[i].owned_size);
+ }
+ for (i = 0; i < plan->nafter; ++i) {
+ if (plan->after[i].owned_path)
+ driver_free(env, plan->after[i].owned_path, plan->after[i].owned_size);
+ }
+ for (i = 0; i < plan->nfinal; ++i) {
+ if (plan->final[i].owned_path)
+ driver_free(env, plan->final[i].owned_path, plan->final[i].owned_size);
+ }
+ for (i = 0; i < plan->nsystem_includes; ++i) {
+ if (plan->owned_system_includes[i])
+ driver_free(env, plan->owned_system_includes[i],
+ plan->owned_system_include_sizes[i]);
+ }
+ {
+ DriverHostedPlan zero = {0};
+ *plan = zero;
+ }
+}
diff --git a/driver/hosted.h b/driver/hosted.h
@@ -0,0 +1,56 @@
+#ifndef CFREE_DRIVER_HOSTED_H
+#define CFREE_DRIVER_HOSTED_H
+
+#include <stdint.h>
+
+#include "driver.h"
+
+typedef enum DriverHostedInputKind {
+ DRIVER_HOSTED_INPUT_OBJECT,
+ DRIVER_HOSTED_INPUT_ARCHIVE,
+ DRIVER_HOSTED_INPUT_DSO,
+} DriverHostedInputKind;
+
+typedef struct DriverHostedInput {
+ uint8_t kind; /* DriverHostedInputKind */
+ uint8_t pad[3];
+ const char* path;
+ char* owned_path;
+ size_t owned_size;
+} DriverHostedInput;
+
+#define DRIVER_HOSTED_MAX_BEFORE 4
+#define DRIVER_HOSTED_MAX_AFTER 6
+#define DRIVER_HOSTED_MAX_FINAL 2
+#define DRIVER_HOSTED_MAX_INCLUDES 4
+
+typedef struct DriverHostedPlan {
+ const char* profile_name;
+ const char* interp_path;
+ DriverHostedInput before[DRIVER_HOSTED_MAX_BEFORE];
+ uint32_t nbefore;
+ DriverHostedInput after[DRIVER_HOSTED_MAX_AFTER];
+ uint32_t nafter;
+ DriverHostedInput final[DRIVER_HOSTED_MAX_FINAL];
+ uint32_t nfinal;
+ const char* system_includes[DRIVER_HOSTED_MAX_INCLUDES];
+ char* owned_system_includes[DRIVER_HOSTED_MAX_INCLUDES];
+ size_t owned_system_include_sizes[DRIVER_HOSTED_MAX_INCLUDES];
+ uint32_t nsystem_includes;
+} DriverHostedPlan;
+
+typedef struct DriverHostedRequest {
+ DriverEnv* env;
+ const char* tool;
+ CfreeTarget target;
+ const char* sysroot;
+ const char* support_dir;
+ const char* driver_path;
+ int static_link;
+} DriverHostedRequest;
+
+int driver_hosted_resolve(const DriverHostedRequest* req,
+ DriverHostedPlan* out);
+void driver_hosted_plan_fini(DriverEnv* env, DriverHostedPlan* plan);
+
+#endif
diff --git a/driver/main.c b/driver/main.c
@@ -157,8 +157,13 @@ int driver_main(int argc, char** argv) {
return 2;
}
- rc = dispatch(argv[1], argc - 1, argv + 1);
- if (rc != -1) return rc;
+ {
+ char* tool_name = argv[1];
+ argv[1] = argv[0];
+ rc = dispatch(tool_name, argc - 1, argv + 1);
+ argv[1] = tool_name;
+ if (rc != -1) return rc;
+ }
driver_errf("cfree", "no such tool: %s", argv[1]);
driver_help_top();
diff --git a/driver/runtime.c b/driver/runtime.c
@@ -0,0 +1,652 @@
+#include "runtime.h"
+
+#include <cfree/archive.h>
+#include <cfree/compile.h>
+#include <cfree/core.h>
+#include <stdint.h>
+
+#include "lang/c/c.h"
+
+#define RT_TOOL "cc"
+
+typedef struct RuntimeVariant {
+ const char* key;
+ CfreeArchKind arch;
+ CfreeOSKind os;
+ CfreeObjFmt obj;
+ uint8_t ptr_size;
+ uint8_t ptr_align;
+ const char* abi_include;
+ uint8_t has_int128;
+ uint8_t ldbl128;
+ const char* const* sources;
+ uint32_t nsources;
+} RuntimeVariant;
+
+static const char* const kRtSrcX64[] = {
+ "int/int.c", "fp/fp.c",
+ "mem/mem.c", "atomic/atomic_freestanding.c",
+ "cfree/ifunc_init.c", "int64/int64.c",
+ "coro/x86_64.c", "coro/coro.c",
+};
+
+static const char* const kRtSrcAarch64Linux[] = {
+ "int/int.c", "fp/fp.c",
+ "mem/mem.c", "atomic/atomic_freestanding.c",
+ "cfree/ifunc_init.c", "int64/int64.c",
+ "coro/aarch64.c", "coro/coro.c",
+ "fp_tf/fp_tf.c", "fp_ti/fp_ti.c",
+ "coro/aarch64_elf.s",
+};
+
+static const char* const kRtSrcAarch64Darwin[] = {
+ "int/int.c",
+ "fp/fp.c",
+ "mem/mem.c",
+ "atomic/atomic_freestanding.c",
+ "cfree/ifunc_init.c",
+ "int64/int64.c",
+ "coro/aarch64.c",
+ "coro/coro.c",
+ "coro/aarch64_macho.s",
+};
+
+static const char* const kRtSrcRv64Linux[] = {
+ "int/int.c", "fp/fp.c",
+ "mem/mem.c", "atomic/atomic_freestanding.c",
+ "cfree/ifunc_init.c", "int64/int64.c",
+ "coro/riscv64.c", "coro/coro.c",
+ "fp_tf/fp_tf.c", "fp_ti/fp_ti.c",
+};
+
+static const char* const kRtSrcRv64Elf[] = {
+ "int/int.c", "fp/fp.c",
+ "mem/mem.c", "atomic/atomic_freestanding.c",
+ "cfree/ifunc_init.c", "int64/int64.c",
+ "coro/riscv64.c", "coro/coro.c",
+};
+
+static const RuntimeVariant kRtVariants[] = {
+ {"x86_64-linux", CFREE_ARCH_X86_64, CFREE_OS_LINUX, CFREE_OBJ_ELF, 8, 8,
+ "lib/include/lp64_le", 1, 0, kRtSrcX64,
+ (uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0]))},
+ {"x86_64-apple-darwin", CFREE_ARCH_X86_64, CFREE_OS_MACOS, CFREE_OBJ_MACHO,
+ 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcX64,
+ (uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0]))},
+ {"aarch64-linux", CFREE_ARCH_ARM_64, CFREE_OS_LINUX, CFREE_OBJ_ELF, 8, 8,
+ "lib/include/lp64_le", 1, 1, kRtSrcAarch64Linux,
+ (uint32_t)(sizeof(kRtSrcAarch64Linux) / sizeof(kRtSrcAarch64Linux[0]))},
+ {"aarch64-apple-darwin", CFREE_ARCH_ARM_64, CFREE_OS_MACOS, CFREE_OBJ_MACHO,
+ 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcAarch64Darwin,
+ (uint32_t)(sizeof(kRtSrcAarch64Darwin) / sizeof(kRtSrcAarch64Darwin[0]))},
+ {"riscv64-linux", CFREE_ARCH_RV64, CFREE_OS_LINUX, CFREE_OBJ_ELF, 8, 8,
+ "lib/include/lp64_le", 1, 1, kRtSrcRv64Linux,
+ (uint32_t)(sizeof(kRtSrcRv64Linux) / sizeof(kRtSrcRv64Linux[0]))},
+ {"riscv64-elf", CFREE_ARCH_RV64, CFREE_OS_FREESTANDING, CFREE_OBJ_ELF, 8, 8,
+ "lib/include/lp64_le", 1, 0, kRtSrcRv64Elf,
+ (uint32_t)(sizeof(kRtSrcRv64Elf) / sizeof(kRtSrcRv64Elf[0]))},
+};
+
+static char* rt_dup(DriverEnv* env, const char* s, size_t* out_size) {
+ size_t len = driver_strlen(s);
+ char* p = (char*)driver_alloc(env, len + 1u);
+ if (!p) return NULL;
+ driver_memcpy(p, s, len + 1u);
+ if (out_size) *out_size = len + 1u;
+ return p;
+}
+
+static char* rt_join(DriverEnv* env, const char* a, const char* b,
+ size_t* out_size) {
+ size_t la = driver_strlen(a);
+ size_t lb = driver_strlen(b);
+ int slash = la > 0 && a[la - 1u] != '/';
+ size_t n = la + (slash ? 1u : 0u) + lb + 1u;
+ char* p = (char*)driver_alloc(env, n);
+ if (!p) return NULL;
+ driver_memcpy(p, a, la);
+ if (slash) p[la++] = '/';
+ driver_memcpy(p + la, b, lb);
+ p[la + lb] = '\0';
+ if (out_size) *out_size = n;
+ return p;
+}
+
+static void rt_free_str(DriverEnv* env, char** p, size_t* n) {
+ if (*p) driver_free(env, *p, *n);
+ *p = NULL;
+ *n = 0;
+}
+
+static int rt_has_layout(const char* rt_root) {
+ char inc[4096];
+ char lib[4096];
+ size_t n = driver_strlen(rt_root);
+ if (n + 13u >= sizeof(inc)) return 0;
+ driver_memcpy(inc, rt_root, n);
+ inc[n] = '/';
+ driver_memcpy(inc + n + 1u, "include", 8u);
+ driver_memcpy(lib, rt_root, n);
+ lib[n] = '/';
+ driver_memcpy(lib + n + 1u, "lib", 4u);
+ return driver_path_exists(inc) && driver_path_exists(lib);
+}
+
+static int rt_set_layout(DriverEnv* env, DriverRuntimeSupport* out,
+ const char* support_root, const char* rt_root,
+ const char* cache_root) {
+ out->support_root = rt_dup(env, support_root, &out->support_root_size);
+ out->rt_root = rt_dup(env, rt_root, &out->rt_root_size);
+ out->include_dir = rt_join(env, rt_root, "include", &out->include_dir_size);
+ out->cache_root = rt_dup(env, cache_root, &out->cache_root_size);
+ if (!out->support_root || !out->rt_root || !out->include_dir ||
+ !out->cache_root) {
+ driver_runtime_support_fini(env, out);
+ return 1;
+ }
+ return 0;
+}
+
+static int rt_try_support_root(DriverEnv* env, const char* root,
+ DriverRuntimeSupport* out) {
+ char* rt_root;
+ char* cache_root;
+ size_t rt_root_size = 0;
+ size_t cache_root_size = 0;
+ int ok = 1;
+
+ if (rt_has_layout(root)) {
+ cache_root = rt_join(env, root, "build", &cache_root_size);
+ if (!cache_root) return 1;
+ ok = rt_set_layout(env, out, root, root, cache_root);
+ driver_free(env, cache_root, cache_root_size);
+ return ok;
+ }
+
+ rt_root = rt_join(env, root, "rt", &rt_root_size);
+ cache_root = rt_join(env, root, "cache", &cache_root_size);
+ if (!rt_root || !cache_root) ok = 0;
+ if (ok && rt_has_layout(rt_root))
+ ok = rt_set_layout(env, out, root, rt_root, cache_root) == 0;
+ else
+ ok = 0;
+ if (rt_root) driver_free(env, rt_root, rt_root_size);
+ if (cache_root) driver_free(env, cache_root, cache_root_size);
+ return ok ? 0 : 1;
+}
+
+static int rt_try_argv0_support(DriverEnv* env, const char* argv0,
+ DriverRuntimeSupport* out) {
+ const char* slash;
+ size_t dir_len;
+ char* dir;
+ char* support;
+ size_t dir_size;
+ size_t support_size;
+ int rc;
+
+ if (!argv0) return 1;
+ slash = argv0 + driver_strlen(argv0);
+ while (slash > argv0 && slash[-1] != '/') --slash;
+ if (slash == argv0) return 1;
+ dir_len = (size_t)(slash - argv0);
+ if (dir_len == 0) return 1;
+
+ dir_size = dir_len + 1u;
+ dir = (char*)driver_alloc(env, dir_size);
+ if (!dir) return 1;
+ driver_memcpy(dir, argv0, dir_len);
+ dir[dir_len] = '\0';
+
+ support = rt_join(env, dir, "support", &support_size);
+ driver_free(env, dir, dir_size);
+ if (!support) return 1;
+ rc = rt_try_support_root(env, support, out);
+ driver_free(env, support, support_size);
+ return rc;
+}
+
+static int rt_try_argv0_checkout_root(DriverEnv* env, const char* argv0,
+ DriverRuntimeSupport* out) {
+ const char* slash;
+ size_t dir_len;
+ char* dir;
+ char* parent;
+ size_t dir_size;
+ size_t parent_size;
+ int rc;
+
+ if (!argv0) return 1;
+ slash = argv0 + driver_strlen(argv0);
+ while (slash > argv0 && slash[-1] != '/') --slash;
+ if (slash == argv0) return 1;
+ dir_len = (size_t)(slash - argv0);
+ if (dir_len == 0) return 1;
+
+ dir_size = dir_len + 1u;
+ dir = (char*)driver_alloc(env, dir_size);
+ if (!dir) return 1;
+ driver_memcpy(dir, argv0, dir_len);
+ dir[dir_len] = '\0';
+
+ parent = rt_join(env, dir, "..", &parent_size);
+ driver_free(env, dir, dir_size);
+ if (!parent) return 1;
+ rc = rt_try_support_root(env, parent, out);
+ driver_free(env, parent, parent_size);
+ return rc;
+}
+
+int driver_runtime_resolve(DriverEnv* env, const char* explicit_support_dir,
+ const char* argv0, DriverRuntimeSupport* out) {
+ int rc;
+ DriverRuntimeSupport zero = {0};
+ *out = zero;
+
+ if (explicit_support_dir) {
+ rc = rt_try_support_root(env, explicit_support_dir, out);
+ } else if (rt_try_support_root(env, "rt", out) == 0) {
+ rc = 0;
+ } else if (rt_try_argv0_checkout_root(env, argv0, out) == 0) {
+ rc = 0;
+ } else {
+ rc = rt_try_argv0_support(env, argv0, out);
+ }
+ if (rc == 0) out->tool_path = argv0;
+ return rc;
+}
+
+void driver_runtime_support_fini(DriverEnv* env, DriverRuntimeSupport* s) {
+ rt_free_str(env, &s->cache_root, &s->cache_root_size);
+ rt_free_str(env, &s->include_dir, &s->include_dir_size);
+ rt_free_str(env, &s->rt_root, &s->rt_root_size);
+ rt_free_str(env, &s->support_root, &s->support_root_size);
+}
+
+int driver_runtime_add_freestanding_headers(const DriverRuntimeSupport* s,
+ DriverCflags* cf) {
+ uint32_t i;
+ if (!s || !s->include_dir || !cf) return 1;
+ for (i = cf->nsystem_include_dirs; i > 0; --i)
+ cf->system_include_dirs[i] = cf->system_include_dirs[i - 1u];
+ cf->system_include_dirs[0] = s->include_dir;
+ cf->nsystem_include_dirs++;
+ return 0;
+}
+
+static const RuntimeVariant* rt_variant_for_target(CfreeTarget target) {
+ uint32_t i;
+ for (i = 0; i < (uint32_t)(sizeof(kRtVariants) / sizeof(kRtVariants[0]));
+ ++i) {
+ const RuntimeVariant* v = &kRtVariants[i];
+ if (target.arch == v->arch && target.os == v->os && target.obj == v->obj &&
+ target.ptr_size == v->ptr_size && target.ptr_align == v->ptr_align)
+ return v;
+ }
+ return NULL;
+}
+
+static int rt_prepare_pp(DriverEnv* env, const DriverRuntimeSupport* support,
+ const RuntimeVariant* variant, int assembler,
+ CfreePreprocessOptions* pp, char*** owned_dirs,
+ size_t** owned_sizes, uint32_t* owned_count) {
+ static const char* const def_int128_1 = "1";
+ static const char* const def_int128_0 = "0";
+ char** dirs = NULL;
+ size_t* sizes = NULL;
+ const char** include_dirs;
+ const char** system_dirs;
+ CfreeDefine* defs;
+ uint32_t ninc = variant->ldbl128 ? 4u : 3u;
+ uint32_t nsys = 2u;
+ uint32_t ndefs = 1u + (variant->ldbl128 ? 1u : 0u) + (assembler ? 1u : 0u);
+ uint32_t i = 0;
+ uint32_t d = 0;
+ CfreePreprocessOptions z = {0};
+
+ *pp = z;
+ *owned_dirs = NULL;
+ *owned_sizes = NULL;
+ *owned_count = 0;
+
+ include_dirs = (const char**)driver_alloc_zeroed(
+ env, (size_t)ninc * sizeof(*include_dirs));
+ system_dirs = (const char**)driver_alloc_zeroed(
+ env, (size_t)nsys * sizeof(*system_dirs));
+ defs = (CfreeDefine*)driver_alloc_zeroed(env, (size_t)ndefs * sizeof(*defs));
+ dirs =
+ (char**)driver_alloc_zeroed(env, (size_t)(ninc + nsys) * sizeof(*dirs));
+ sizes =
+ (size_t*)driver_alloc_zeroed(env, (size_t)(ninc + nsys) * sizeof(*sizes));
+ if (!include_dirs || !system_dirs || !defs || !dirs || !sizes) {
+ if (include_dirs)
+ driver_free(env, (void*)include_dirs,
+ (size_t)ninc * sizeof(*include_dirs));
+ if (system_dirs)
+ driver_free(env, (void*)system_dirs, (size_t)nsys * sizeof(*system_dirs));
+ if (defs) driver_free(env, defs, (size_t)ndefs * sizeof(*defs));
+ if (dirs) driver_free(env, dirs, (size_t)(ninc + nsys) * sizeof(*dirs));
+ if (sizes) driver_free(env, sizes, (size_t)(ninc + nsys) * sizeof(*sizes));
+ return 1;
+ }
+
+ dirs[d] = rt_join(env, support->rt_root, "lib/include/common", &sizes[d]);
+ include_dirs[i++] = dirs[d++];
+ dirs[d] = rt_join(env, support->rt_root, "lib/impl", &sizes[d]);
+ include_dirs[i++] = dirs[d++];
+ dirs[d] = rt_join(env, support->rt_root, variant->abi_include, &sizes[d]);
+ include_dirs[i++] = dirs[d++];
+ if (variant->ldbl128) {
+ dirs[d] = rt_join(env, support->rt_root, "lib/include/lp64_le_ldbl128",
+ &sizes[d]);
+ include_dirs[i++] = dirs[d++];
+ }
+ dirs[d] = rt_join(env, support->rt_root, "include", &sizes[d]);
+ system_dirs[0] = dirs[d++];
+ dirs[d] = rt_join(env, support->rt_root, "include/libc", &sizes[d]);
+ system_dirs[1] = dirs[d++];
+ for (i = 0; i < d; ++i) {
+ if (!dirs[i]) {
+ uint32_t j;
+ for (j = 0; j < d; ++j)
+ if (dirs[j]) driver_free(env, dirs[j], sizes[j]);
+ driver_free(env, (void*)include_dirs,
+ (size_t)ninc * sizeof(*include_dirs));
+ driver_free(env, (void*)system_dirs, (size_t)nsys * sizeof(*system_dirs));
+ driver_free(env, defs, (size_t)ndefs * sizeof(*defs));
+ driver_free(env, dirs, (size_t)(ninc + nsys) * sizeof(*dirs));
+ driver_free(env, sizes, (size_t)(ninc + nsys) * sizeof(*sizes));
+ return 1;
+ }
+ }
+
+ defs[0].name = "HAS_INT128";
+ defs[0].body = variant->has_int128 ? def_int128_1 : def_int128_0;
+ i = 1;
+ if (variant->ldbl128) {
+ defs[i].name = "CFREERT_LDBL128";
+ defs[i].body = "1";
+ ++i;
+ }
+ if (assembler) {
+ defs[i].name = "__ASSEMBLER__";
+ defs[i].body = "1";
+ }
+
+ pp->include_dirs = include_dirs;
+ pp->ninclude_dirs = ninc;
+ pp->system_include_dirs = system_dirs;
+ pp->nsystem_include_dirs = nsys;
+ pp->defines = defs;
+ pp->ndefines = ndefs;
+ *owned_dirs = dirs;
+ *owned_sizes = sizes;
+ *owned_count = d;
+ return 0;
+}
+
+static void rt_free_pp(DriverEnv* env, CfreePreprocessOptions* pp,
+ char** owned_dirs, size_t* owned_sizes,
+ uint32_t owned_count) {
+ uint32_t i;
+ for (i = 0; i < owned_count; ++i)
+ if (owned_dirs[i]) driver_free(env, owned_dirs[i], owned_sizes[i]);
+ if (owned_dirs)
+ driver_free(env, owned_dirs, (size_t)owned_count * sizeof(*owned_dirs));
+ if (owned_sizes)
+ driver_free(env, owned_sizes, (size_t)owned_count * sizeof(*owned_sizes));
+ if (pp->include_dirs)
+ driver_free(env, (void*)pp->include_dirs,
+ (size_t)pp->ninclude_dirs * sizeof(*pp->include_dirs));
+ if (pp->system_include_dirs)
+ driver_free(
+ env, (void*)pp->system_include_dirs,
+ (size_t)pp->nsystem_include_dirs * sizeof(*pp->system_include_dirs));
+ if (pp->defines)
+ driver_free(env, (void*)pp->defines,
+ (size_t)pp->ndefines * sizeof(*pp->defines));
+}
+
+static int rt_compile_source(DriverEnv* env,
+ const DriverRuntimeSupport* support,
+ const RuntimeVariant* variant,
+ CfreeCompiler* compiler, const char* rel,
+ CfreeBytes* member, CfreeWriter** obj_writer) {
+ CfreeContext ctx = driver_env_to_context(env);
+ char* lib_path = NULL;
+ char* src_path = NULL;
+ size_t lib_path_size = 0;
+ size_t src_path_size = 0;
+ DriverLoad src_load = {0};
+ CfreeBytes input = {0};
+ CfreeWriter* writer = NULL;
+ CfreeWriter* pp_writer = NULL;
+ const uint8_t* obj_data;
+ size_t obj_len = 0;
+ CfreeStatus st;
+ int is_asm;
+ int preprocess_asm;
+ int rc = 1;
+
+ *obj_writer = NULL;
+ lib_path = rt_join(env, support->rt_root, "lib", &lib_path_size);
+ if (!lib_path) goto out;
+ src_path = rt_join(env, lib_path, rel, &src_path_size);
+ if (!src_path) goto out;
+ if (driver_load_bytes(ctx.file_io, RT_TOOL, src_path, &src_load, &input) != 0)
+ goto out;
+ if (cfree_writer_mem(ctx.heap, &writer) != CFREE_OK) goto out;
+
+ is_asm = driver_has_suffix(rel, ".s") || driver_has_suffix(rel, ".S");
+ preprocess_asm = driver_has_suffix(rel, ".S");
+ if (is_asm) {
+ CfreeAsmCompileOptions aopts = {0};
+ CfreeBytes asm_input = input;
+ if (preprocess_asm) {
+ CfreePreprocessOptions pp;
+ char** owned_dirs = NULL;
+ size_t* owned_sizes = NULL;
+ uint32_t owned_count = 0;
+ if (rt_prepare_pp(env, support, variant, 1, &pp, &owned_dirs,
+ &owned_sizes, &owned_count) != 0)
+ goto out;
+ if (cfree_writer_mem(ctx.heap, &pp_writer) != CFREE_OK) {
+ rt_free_pp(env, &pp, owned_dirs, owned_sizes, owned_count);
+ goto out;
+ }
+ st = cfree_c_preprocess(compiler, &pp, &input, pp_writer);
+ rt_free_pp(env, &pp, owned_dirs, owned_sizes, owned_count);
+ if (st != CFREE_OK || cfree_writer_status(pp_writer) != CFREE_OK)
+ goto out;
+ asm_input.data = cfree_writer_mem_bytes(pp_writer, &asm_input.len);
+ }
+ st = cfree_compile_asm_obj_emit(compiler, &aopts, &asm_input, writer);
+ } else {
+ CfreeCCompileOptions copts = {0};
+ char** owned_dirs = NULL;
+ size_t* owned_sizes = NULL;
+ uint32_t owned_count = 0;
+ if (rt_prepare_pp(env, support, variant, 0, &copts.preprocess, &owned_dirs,
+ &owned_sizes, &owned_count) != 0)
+ goto out;
+ st = cfree_compile_c_obj_emit(compiler, &copts, &input, writer);
+ rt_free_pp(env, &copts.preprocess, owned_dirs, owned_sizes, owned_count);
+ }
+ if (st != CFREE_OK || cfree_writer_status(writer) != CFREE_OK) {
+ driver_errf(RT_TOOL, "failed to build runtime source: %s", src_path);
+ goto out;
+ }
+
+ obj_data = cfree_writer_mem_bytes(writer, &obj_len);
+ member->name = driver_basename(rel);
+ member->data = obj_data;
+ member->len = obj_len;
+ *obj_writer = writer;
+ writer = NULL;
+ rc = 0;
+
+out:
+ if (pp_writer) cfree_writer_close(pp_writer);
+ if (writer) cfree_writer_close(writer);
+ driver_release_bytes(ctx.file_io, &src_load);
+ if (src_path) driver_free(env, src_path, src_path_size);
+ if (lib_path) driver_free(env, lib_path, lib_path_size);
+ return rc;
+}
+
+static int rt_build_archive(DriverEnv* env, const DriverRuntimeSupport* support,
+ const RuntimeVariant* variant, uint64_t epoch,
+ const char* archive_path) {
+ CfreeContext ctx = driver_env_to_context(env);
+ CfreeCompiler* compiler = NULL;
+ CfreeWriter* archive_writer = NULL;
+ CfreeBytes* members = NULL;
+ CfreeWriter** obj_writers = NULL;
+ CfreeArWriteOptions ar_opts = {0};
+ CfreeTarget target;
+ uint32_t i;
+ int rc = 1;
+
+ members = (CfreeBytes*)driver_alloc_zeroed(
+ env, (size_t)variant->nsources * sizeof(*members));
+ obj_writers = (CfreeWriter**)driver_alloc_zeroed(
+ env, (size_t)variant->nsources * sizeof(*obj_writers));
+ if (!members || !obj_writers) {
+ driver_errf(RT_TOOL, "out of memory");
+ goto out;
+ }
+
+ target.arch = variant->arch;
+ target.os = variant->os;
+ target.obj = variant->obj;
+ target.ptr_size = variant->ptr_size;
+ target.ptr_align = variant->ptr_align;
+ target.big_endian = 0;
+ target.pic = CFREE_PIC_NONE;
+ target.code_model = CFREE_CM_DEFAULT;
+
+ if (driver_compiler_new(target, &ctx, &compiler) != CFREE_OK) {
+ driver_errf(RT_TOOL, "failed to initialize compiler for runtime");
+ goto out;
+ }
+
+ for (i = 0; i < variant->nsources; ++i) {
+ if (rt_compile_source(env, support, variant, compiler, variant->sources[i],
+ &members[i], &obj_writers[i]) != 0)
+ goto out;
+ }
+
+ if (ctx.file_io->open_writer(ctx.file_io->user, archive_path,
+ &archive_writer) != CFREE_OK) {
+ driver_errf(RT_TOOL, "failed to open runtime archive: %s", archive_path);
+ goto out;
+ }
+
+ ar_opts.epoch = epoch;
+ ar_opts.long_names = 1;
+ if (cfree_ar_write(archive_writer, members, variant->nsources, &ar_opts) !=
+ CFREE_OK) {
+ driver_errf(RT_TOOL, "failed to write runtime archive: %s", archive_path);
+ goto out;
+ }
+ rc = 0;
+
+out:
+ if (archive_writer) cfree_writer_close(archive_writer);
+ if (compiler) driver_compiler_free(compiler);
+ if (obj_writers) {
+ for (i = 0; i < variant->nsources; ++i)
+ if (obj_writers[i]) cfree_writer_close(obj_writers[i]);
+ driver_free(env, obj_writers,
+ (size_t)variant->nsources * sizeof(*obj_writers));
+ }
+ if (members)
+ driver_free(env, members, (size_t)variant->nsources * sizeof(*members));
+ return rc;
+}
+
+static int rt_is_archive_stale(DriverEnv* env,
+ const DriverRuntimeSupport* support,
+ const RuntimeVariant* variant,
+ const char* archive_path) {
+ int64_t archive_mtime;
+ int64_t tool_mtime;
+ uint32_t i;
+ if (driver_path_mtime_ns(archive_path, &archive_mtime) != 0) return 1;
+ if (support->tool_path &&
+ driver_path_mtime_ns(support->tool_path, &tool_mtime) == 0 &&
+ tool_mtime > archive_mtime)
+ return 1;
+ for (i = 0; i < variant->nsources; ++i) {
+ char* lib_path;
+ char* src_path;
+ size_t lib_path_size = 0;
+ size_t src_path_size = 0;
+ int64_t src_mtime;
+ int stale = 0;
+ lib_path = rt_join(env, support->rt_root, "lib", &lib_path_size);
+ if (!lib_path) return 1;
+ src_path = rt_join(env, lib_path, variant->sources[i], &src_path_size);
+ driver_free(env, lib_path, lib_path_size);
+ if (!src_path) return 1;
+ if (driver_path_mtime_ns(src_path, &src_mtime) != 0 ||
+ src_mtime > archive_mtime)
+ stale = 1;
+ driver_free(env, src_path, src_path_size);
+ if (stale) return 1;
+ }
+ return 0;
+}
+
+int driver_runtime_ensure_archive(DriverEnv* env,
+ const DriverRuntimeSupport* support,
+ CfreeTarget target, uint64_t epoch,
+ char** out_path, size_t* out_path_size) {
+ const RuntimeVariant* variant = rt_variant_for_target(target);
+ char* cache_dir = NULL;
+ char* archive_path = NULL;
+ size_t cache_dir_size = 0;
+ size_t archive_path_size = 0;
+
+ *out_path = NULL;
+ *out_path_size = 0;
+
+ if (!variant) {
+ driver_errf(RT_TOOL, "compiler runtime is not available for this target");
+ return 1;
+ }
+ cache_dir = rt_join(env, support->cache_root, variant->key, &cache_dir_size);
+ if (!cache_dir) {
+ driver_errf(RT_TOOL, "out of memory");
+ return 1;
+ }
+ archive_path = rt_join(env, cache_dir, "libcfree_rt.a", &archive_path_size);
+ if (!archive_path) {
+ driver_free(env, cache_dir, cache_dir_size);
+ driver_errf(RT_TOOL, "out of memory");
+ return 1;
+ }
+
+ if (!driver_path_exists(archive_path) ||
+ rt_is_archive_stale(env, support, variant, archive_path)) {
+ if (driver_mkdir_p(env, cache_dir) != 0) {
+ driver_errf(RT_TOOL, "failed to create runtime cache: %s", cache_dir);
+ driver_free(env, archive_path, archive_path_size);
+ driver_free(env, cache_dir, cache_dir_size);
+ return 1;
+ }
+ if (rt_build_archive(env, support, variant, epoch, archive_path) != 0) {
+ driver_errf(RT_TOOL, "compiler runtime for %s could not be built",
+ variant->key);
+ driver_errf(RT_TOOL, "support dir: %s", support->support_root);
+ driver_free(env, archive_path, archive_path_size);
+ driver_free(env, cache_dir, cache_dir_size);
+ return 1;
+ }
+ }
+
+ driver_free(env, cache_dir, cache_dir_size);
+ *out_path = archive_path;
+ *out_path_size = archive_path_size;
+ return 0;
+}
diff --git a/driver/runtime.h b/driver/runtime.h
@@ -0,0 +1,31 @@
+#ifndef CFREE_DRIVER_RUNTIME_H
+#define CFREE_DRIVER_RUNTIME_H
+
+#include "cflags.h"
+#include "driver.h"
+
+typedef struct DriverRuntimeSupport {
+ char* support_root;
+ size_t support_root_size;
+ char* rt_root;
+ size_t rt_root_size;
+ char* include_dir;
+ size_t include_dir_size;
+ char* cache_root;
+ size_t cache_root_size;
+ const char* tool_path;
+} DriverRuntimeSupport;
+
+int driver_runtime_resolve(DriverEnv* env, const char* explicit_support_dir,
+ const char* argv0, DriverRuntimeSupport* out);
+void driver_runtime_support_fini(DriverEnv* env, DriverRuntimeSupport* s);
+
+int driver_runtime_add_freestanding_headers(const DriverRuntimeSupport* s,
+ DriverCflags* cf);
+
+int driver_runtime_ensure_archive(DriverEnv* env,
+ const DriverRuntimeSupport* support,
+ CfreeTarget target, uint64_t epoch,
+ char** out_path, size_t* out_path_size);
+
+#endif
diff --git a/test/driver/run.sh b/test/driver/run.sh
@@ -155,6 +155,121 @@ else
fail=$((fail + 1))
fi
+cat > "$work/implicit-header.c" <<'SRC'
+#include <stddef.h>
+#include <stdint.h>
+int f(void) { return (int)sizeof(size_t) + (int)UINT8_MAX; }
+SRC
+if "$CFREE" cc -target aarch64-linux -c "$work/implicit-header.c" \
+ -o "$work/implicit-header.o" \
+ > "$work/implicit-header.out" 2> "$work/implicit-header.err"; then
+ printf 'PASS %s\n' "cc-implicit-freestanding-headers"
+ pass=$((pass + 1))
+else
+ printf 'FAIL %s (cfree cc failed)\n' "cc-implicit-freestanding-headers"
+ sed 's/^/ | /' "$work/implicit-header.err"
+ fail=$((fail + 1))
+fi
+
+mkdir -p "$work/rt-support/rt"
+cp -R "$repo_root/rt/include" "$work/rt-support/rt/include"
+cp -R "$repo_root/rt/lib" "$work/rt-support/rt/lib"
+cat > "$work/rt-div.c" <<'SRC'
+#include <stdint.h>
+typedef unsigned __int128 u128;
+u128 div128(u128 a, u128 b) { return a / b; }
+void _start(void) {
+ volatile u128 x = div128((u128)9, (u128)3);
+ (void)x;
+ for (;;) {}
+}
+SRC
+if "$CFREE" cc --support-dir "$work/rt-support" -target aarch64-linux \
+ -e _start "$work/rt-div.c" -o "$work/rt-div" \
+ > "$work/rt-div.out" 2> "$work/rt-div.err" &&
+ [ -f "$work/rt-support/cache/aarch64-linux/libcfree_rt.a" ]; then
+ printf 'PASS %s\n' "cc-auto-builds-and-links-libcfree-rt"
+ pass=$((pass + 1))
+else
+ printf 'FAIL %s (cfree cc failed)\n' "cc-auto-builds-and-links-libcfree-rt"
+ sed 's/^/ | /' "$work/rt-div.err"
+ fail=$((fail + 1))
+fi
+
+if "$CFREE" cc -target x86_64-linux --sysroot "$work/no-such-sysroot" \
+ "$work/main.c" -o "$work/no-lc-sysroot" \
+ > "$work/no-lc-sysroot.out" 2> "$work/no-lc-sysroot.err"; then
+ printf 'PASS %s\n' "cc-no-lc-does-not-expand-hosted"
+ pass=$((pass + 1))
+else
+ printf 'FAIL %s (cfree cc failed)\n' "cc-no-lc-does-not-expand-hosted"
+ sed 's/^/ | /' "$work/no-lc-sysroot.err"
+ fail=$((fail + 1))
+fi
+
+if ! "$CFREE" cc -target x86_64-linux "$work/main.c" -lc \
+ -o "$work/hosted-no-sysroot" \
+ > "$work/hosted-no-sysroot.out" 2> "$work/hosted-no-sysroot.err" &&
+ grep -q "Linux hosted profile requires --sysroot" \
+ "$work/hosted-no-sysroot.err"; then
+ printf 'PASS %s\n' "cc-lc-requires-explicit-sysroot"
+ pass=$((pass + 1))
+else
+ printf 'FAIL %s (missing hosted sysroot diagnostic)\n' \
+ "cc-lc-requires-explicit-sysroot"
+ sed 's/^/ | /' "$work/hosted-no-sysroot.err"
+ fail=$((fail + 1))
+fi
+
+mkdir -p "$work/musl-sysroot/lib" "$work/support/rt/include/libc" \
+ "$work/support/rt/lib"
+: > "$work/musl-sysroot/lib/crt1.o"
+: > "$work/musl-sysroot/lib/crti.o"
+: > "$work/musl-sysroot/lib/crtn.o"
+: > "$work/musl-sysroot/lib/libc.a"
+if ! "$CFREE" cc -target x86_64-linux --sysroot "$work/musl-sysroot" \
+ --support-dir "$work/support" "$work/main.c" -lc \
+ -o "$work/hosted-missing-shim" \
+ > "$work/hosted-missing-shim.out" 2> "$work/hosted-missing-shim.err" &&
+ grep -q "hosted profile missing required file: .*linux-x64.o" \
+ "$work/hosted-missing-shim.err"; then
+ printf 'PASS %s\n' "cc-lc-reports-missing-hosted-shim"
+ pass=$((pass + 1))
+else
+ printf 'FAIL %s (missing hosted shim diagnostic)\n' \
+ "cc-lc-reports-missing-hosted-shim"
+ sed 's/^/ | /' "$work/hosted-missing-shim.err"
+ fail=$((fail + 1))
+fi
+
+macos_sdk=""
+if [ -n "${SDKROOT:-}" ] && [ -f "$SDKROOT/usr/lib/libSystem.tbd" ]; then
+ macos_sdk=$SDKROOT
+elif command -v xcrun >/dev/null 2>&1; then
+ sdk=$(xcrun --sdk macosx --show-sdk-path 2>/dev/null || true)
+ if [ -n "$sdk" ] && [ -f "$sdk/usr/lib/libSystem.tbd" ]; then
+ macos_sdk=$sdk
+ fi
+fi
+if [ -n "$macos_sdk" ] && [ -f "$repo_root/build/libcfree_hosted_macos.a" ]; then
+ cat > "$work/hosted-macos.c" <<'SRC'
+#include <stdio.h>
+int main(void) { return stdin == 0 || fflush(0) != 0; }
+SRC
+ if "$CFREE" cc -target aarch64-darwin --sysroot "$macos_sdk" \
+ --support-dir "$repo_root" "$work/hosted-macos.c" -lc \
+ -o "$work/hosted-macos" \
+ > "$work/hosted-macos.out" 2> "$work/hosted-macos.err"; then
+ printf 'PASS %s\n' "cc-lc-macos-requires-libSystem"
+ pass=$((pass + 1))
+ else
+ printf 'FAIL %s (cfree cc failed)\n' \
+ "cc-lc-macos-requires-libSystem"
+ sed 's/^/ | /' "$work/hosted-macos.err"
+ fail=$((fail + 1))
+ fi
+fi
+
cat > "$work/run-errno.c" <<'SRC'
#include <errno.h>
int main(void) {