kit

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

commit fd83f1e6567d8dc8866ec4be7512b51625564627
parent da4ace5a31d7aba4dc1236405908b350ca25897e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 19 May 2026 18:23:06 -0700

Broaden driver compatibility

Diffstat:
Adoc/HOSTED.md | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/ar.c | 6++++++
Mdriver/cc.c | 855+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mdriver/env.c | 17+++++++++++++++++
Msrc/api/compile.c | 3++-
Atest/ar/cases/04-rcu-compat.expected | 2++
Atest/ar/cases/04-rcu-compat.sh | 8++++++++
Mtest/driver/run.sh | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 1147 insertions(+), 131 deletions(-)

diff --git a/doc/HOSTED.md b/doc/HOSTED.md @@ -0,0 +1,220 @@ +# Hosted Linking Plan + +This document describes the planned split between compiler support runtime +linking and hosted C library linking in the `cfree cc` driver. + +## Goals + +- Keep freestanding links freestanding by default. +- Always provide compiler runtime support needed by code generated by cfree. +- Add hosted C runtime and libc inputs only when the user explicitly asks for + libc, currently by passing `-lc`. +- Keep platform-specific hosted knowledge isolated in a small, pluggable driver + module. No platform paths should be scattered through `cc.c`, `ld.c`, or + linker internals. +- Make every platform path come from an explicit support layout or configured + sysroot/SDK, not from implicit host filesystem probing. + +## Conceptual Split + +There are two distinct runtime classes. + +### Compiler Runtime + +`libcfree_rt.a` is compiler support code: integer helpers, soft-float helpers, +memory helpers, atomics, coroutine/runtime helpers, and similar functions that +generated code or compiler lowering may require. + +This is not hosted libc. It should be linked automatically by `cfree cc` during +final link, independent of `-lc`. + +Rules: + +- `cfree cc` adds `libcfree_rt.a` for link actions. +- `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. +- The runtime archive is placed late in the ordered link inputs, after user + objects and libraries, so demand-loaded helpers satisfy unresolved references. + +### Hosted C Runtime And Libc + +Hosted linking means startup files, hosted ABI shim objects, libc, and dynamic +loader settings. + +This is opt-in. Passing `-lc` requests hosted libc expansion. Without `-lc`, +`cfree cc` must not add crt objects, hosted shims, libc, or platform dynamic +loader policy. + +Rules: + +- `-lc` is recognized specially by the `cc` driver. +- Other `-lfoo` inputs keep normal ordered library resolution. +- `-nostdlib` and `-nodefaultlibs` should suppress hosted expansion. The exact + policy can be either strict diagnostic or "treat `-lc` as a normal library"; + the preferred initial behavior is to avoid implicit hosted inputs. +- Hosted inputs must preserve link order: + startup objects before user objects, libc/runtime support after user objects, + and trailing finalization objects last. + +## Distribution Layout + +The intended distribution model is a single directory with the `cfree` binary at +the root and a sibling support directory: + +```text +cfree +support/ + rt/ + include/ + lib/ +``` + +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. + +## Runtime Build Cache + +`cfree cc` should compile and cache `libcfree_rt.a` for the target on demand. + +Proposed cache layout: + +```text +support/cache/<target-triple>/ + libcfree_rt.a + obj/ +``` + +The cache builder belongs in a dedicated driver module, for example +`driver/runtime.c` with `driver/runtime.h`. + +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. +- Archive them into `libcfree_rt.a`. +- Return a concrete path to the archive for insertion into ordered link inputs. + +Failure should produce diagnostics that name the target and the support path: + +```text +cc: compiler runtime for aarch64-linux not found and could not be built +cc: support dir: /path/to/support +``` + +## Hosted Resolver + +Hosted platform policy belongs in a separate driver module, for example +`driver/hosted.c` with `driver/hosted.h`. + +`cc.c` should not know about `libSystem`, musl crt names, glibc loader names, or +hosted shim paths. It should only pass target, configured roots, and the current +ordered input list to the hosted resolver. + +The hosted resolver returns concrete ordered additions and link options. + +Conceptual profile shape: + +```c +typedef struct HostedProfile { + const char* name; + CfreeOSKind os; + CfreeObjFmt obj; + const char* crt1; + const char* crti; + const char* crtn; + const char* libc; + const char* libc_nonshared; + const char* hosted_shim; + const char* interp; + bool static_link; + bool pie; +} HostedProfile; +``` + +The actual implementation may use a richer structured representation, but the +important property is locality: adding or changing hosted platform support +should mean editing `driver/hosted.c`, not the compiler driver or linker. + +## Initial Hosted Profiles + +### macOS / libSystem + +When `-lc` is present for a Darwin/Mach-O target: + +- Add `libcfree_hosted_macos.a`. +- Add the configured SDK/sysroot `libSystem` stub or dylib. +- Do not add libSystem without `-lc`. + +The SDK path should come from explicit configuration, for example `--sysroot` or +an equivalent support profile setting. The driver should not silently search +arbitrary host SDK locations. + +### Linux / musl + +When `-lc` is present for a Linux/ELF target and the configured sysroot contains +musl static libc: + +- Prefer static musl if `libc.a` is available. +- Add startup and finalization objects in normal musl order: + +```text +crt1.o crti.o user-inputs hosted-shim libc.a libcfree_rt.a crtn.o +``` + +For a future dynamic musl profile: + +```text +Scrt1.o crti.o user-inputs hosted-shim libc.so libcfree_rt.a crtn.o +``` + +and set the musl interpreter from the profile. + +### Linux / glibc + +glibc hosted linking is dynamic-only initially. + +When `-lc` is present for a Linux/ELF target and the configured sysroot contains +glibc: + +```text +Scrt1.o crti.o user-inputs hosted-shim libc.so.6 libc_nonshared.a +libcfree_rt.a crtn.o +``` + +The profile must set the dynamic loader explicitly, for example: + +```text +/lib/ld-linux-aarch64.so.1 +/lib64/ld-linux-x86-64.so.2 +/lib/ld-linux-riscv64-lp64d.so.1 +``` + +These values belong in the hosted profile table. + +## Linker Boundary + +The linker should consume explicit ordered inputs and explicit link options. +It should not infer hosted policy. + +Existing linker defaults for platform loaders or system libraries should be +treated as compatibility fallbacks only. The driver should pass explicit +interpreter and DSO inputs when hosted linking is requested. + +## Test Strategy + +Start with focused driver tests before broad libc conformance runs. + +- No `-lc`: no hosted inputs are added. +- Link mode through `cfree cc` adds `libcfree_rt.a` automatically. +- `-lc` on macOS expands through the macOS hosted profile. +- `-lc` on Linux/musl chooses static musl when `libc.a` exists. +- `-lc` on Linux/glibc chooses dynamic glibc and sets the profile interpreter. +- Missing sysroot/support files diagnose the missing concrete file and profile. +- Ordered inputs preserve normal linker behavior around archives and libc. + +The existing `test/libc` sysroot harnesses are good end-to-end validation once +the small resolver tests are green. diff --git a/driver/ar.c b/driver/ar.c @@ -25,6 +25,8 @@ * Each member is opened with cfree_obj_open and its globally- * defined symbols (CFREE_SB_GLOBAL with section != NONE) are * indexed; non-object members contribute no symbols. + * u update only if member is newer. Accepted as a compatibility + * modifier and currently treated as a no-op. * v verbose. With t, list each member as "<size>\t<name>" instead * of name-only. With x, print "x - <name>" per extracted member. * With p, prepend a "<archive>(<name>):\n" header to every member @@ -78,6 +80,8 @@ void driver_help_ar(void) { " rcs, crs). Globally-defined symbols (CFREE_SB_GLOBAL with\n" " a defined section) of each object member are indexed; non-\n" " object members contribute no symbols.\n" + " u Update-if-newer compatibility modifier; accepted as a\n" + " no-op.\n" " v Verbose. With t list <size>\\t<name>; with x/r/c print one\n" " line per affected member (\"x - name\", \"a - name\", or\n" " \"r - name\" for replacements); with p force the per-member\n" @@ -638,6 +642,8 @@ int driver_ar(int argc, char** argv) { case 'v': has_v = 1; break; + case 'u': + break; case 't': do_list = 1; break; diff --git a/driver/cc.c b/driver/cc.c @@ -1,13 +1,13 @@ -#include <stdint.h> - -#include "cflags.h" -#include "driver.h" #include "lang/c/c.h" -#include "lib_resolve.h" #include <cfree/compile.h> #include <cfree/core.h> #include <cfree/link.h> +#include <stdint.h> + +#include "cflags.h" +#include "driver.h" +#include "lib_resolve.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 @@ -48,6 +48,46 @@ typedef enum CcDepMode { CC_DEP_MMD, } CcDepMode; +typedef enum CcLinkItemKind { + CC_LINK_SOURCE_FILE, + CC_LINK_SOURCE_MEMORY, + CC_LINK_OBJECT, + CC_LINK_ARCHIVE, + CC_LINK_DSO, + CC_LINK_LIB, +} CcLinkItemKind; + +typedef struct CcLinkItem { + uint8_t kind; /* CcLinkItemKind */ + uint8_t pad[3]; + uint32_t index; +} CcLinkItem; + +typedef struct CcArchiveInput { + const char* path; + int owned; + size_t owned_size; + uint8_t whole_archive; + uint8_t link_mode; + uint8_t group_id; + uint8_t pad; +} CcArchiveInput; + +typedef struct CcDsoInput { + const char* path; + int owned; + size_t owned_size; +} CcDsoInput; + +typedef struct CcPendingLib { + const char* name; + uint8_t whole_archive; + uint8_t link_mode; + uint8_t group_id; + uint8_t resolved_kind; /* CcLinkItemKind: ARCHIVE or DSO */ + uint32_t resolved_index; +} CcPendingLib; + typedef struct CcOptions { DriverEnv* env; size_t argv_bound; /* upper bound on per-array list size */ @@ -63,6 +103,8 @@ typedef struct CcOptions { int target_set; /* did -target appear */ const char* output_path; /* -o */ int output_path_set; + char* owned_output_path; + size_t owned_output_path_size; const char* entry; /* -e */ const char* linker_script; /* -T path */ @@ -87,6 +129,7 @@ typedef struct CcOptions { /* Positional inputs split by suffix. */ const char** source_files; /* .c paths */ + CfreeLanguage* source_langs; uint32_t nsource_files; CfreeSourceInput* source_memory; /* "-" stdin slurp */ uint32_t nsource_memory; @@ -94,23 +137,22 @@ typedef struct CcOptions { size_t stdin_size; const char** object_files; /* .o/.obj paths */ uint32_t nobject_files; - const char** archives; /* .a paths + resolved -l names */ + CcArchiveInput* archives; /* .a paths + resolved -l names */ uint32_t narchives; - /* Sub-list of `archives[]` whose buffers are heap-owned (resolved -l). - * Their (path, size) sit in parallel arrays for free(). */ - char** owned_archives; - size_t* owned_archive_sizes; - uint32_t nowned_archives; - /* DSO inputs (.so / .dylib / .tbd). Always heap-owned by cc. */ - char** dsos; - size_t* dso_sizes; + CcDsoInput* dsos; uint32_t ndsos; /* -L search paths (argv-borrowed). */ const char** lib_search_paths; uint32_t nlib_search_paths; /* Pending -l names (resolved at end-of-parse). */ - const char** pending_libs; + CcPendingLib* pending_libs; uint32_t npending_libs; + CcLinkItem* link_items; + uint32_t nlink_items; + uint8_t cur_whole_archive; + uint8_t cur_link_mode; + uint8_t cur_group_id; + uint8_t next_group_id; /* -M family */ int dep_mode; /* CcDepMode */ @@ -127,6 +169,10 @@ typedef struct CcOptions { const char** rpaths; /* -Wl,-rpath=DIR (repeatable) */ uint32_t nrpaths; int new_dtags; /* 1=DT_RUNPATH (default), 0=DT_RPATH */ + int static_link; + int pie; + int gc_sections; + const char* interp_path; } CcOptions; static void cc_usage(void) { @@ -160,21 +206,19 @@ static int cc_alloc_arrays(CcOptions* o, int argc) { o->argv_bound = bound; o->source_files = driver_alloc_zeroed(o->env, bound * sizeof(*o->source_files)); + o->source_langs = + driver_alloc_zeroed(o->env, bound * sizeof(*o->source_langs)); o->source_memory = driver_alloc_zeroed(o->env, bound * sizeof(*o->source_memory)); o->object_files = driver_alloc_zeroed(o->env, bound * sizeof(*o->object_files)); o->archives = driver_alloc_zeroed(o->env, bound * sizeof(*o->archives)); - o->owned_archives = - driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_archives)); - o->owned_archive_sizes = - driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_archive_sizes)); o->dsos = driver_alloc_zeroed(o->env, bound * sizeof(*o->dsos)); - o->dso_sizes = driver_alloc_zeroed(o->env, bound * sizeof(*o->dso_sizes)); o->lib_search_paths = driver_alloc_zeroed(o->env, bound * sizeof(*o->lib_search_paths)); o->pending_libs = driver_alloc_zeroed(o->env, bound * sizeof(*o->pending_libs)); + o->link_items = driver_alloc_zeroed(o->env, bound * sizeof(*o->link_items)); o->dep_targets = driver_alloc_zeroed(o->env, bound * sizeof(*o->dep_targets)); o->path_map = driver_alloc_zeroed(o->env, bound * sizeof(*o->path_map)); o->owned_path_map_olds = @@ -182,16 +226,15 @@ static int cc_alloc_arrays(CcOptions* o, int argc) { o->owned_path_map_old_sizes = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_map_old_sizes)); o->rpaths = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpaths)); - if (!o->source_files || !o->source_memory || !o->object_files || - !o->archives || !o->owned_archives || !o->owned_archive_sizes || - !o->dsos || !o->dso_sizes || - !o->lib_search_paths || !o->pending_libs || !o->dep_targets || - !o->path_map || !o->owned_path_map_olds || !o->owned_path_map_old_sizes || - !o->rpaths) { + if (!o->source_files || !o->source_langs || !o->source_memory || + !o->object_files || !o->archives || !o->dsos || !o->lib_search_paths || + !o->pending_libs || !o->link_items || !o->dep_targets || !o->path_map || + !o->owned_path_map_olds || !o->owned_path_map_old_sizes || !o->rpaths) { driver_errf(CC_TOOL, "out of memory"); return 1; } o->new_dtags = 1; + o->cur_link_mode = CFREE_LM_DEFAULT; if (driver_cflags_init(&o->cf, o->env, argc) != 0) { driver_errf(CC_TOOL, "out of memory"); return 1; @@ -202,11 +245,16 @@ static int cc_alloc_arrays(CcOptions* o, int argc) { static void cc_options_release(CcOptions* o) { uint32_t i; size_t bound = o->argv_bound; - for (i = 0; i < o->nowned_archives; ++i) { - driver_free(o->env, o->owned_archives[i], o->owned_archive_sizes[i]); + for (i = 0; i < o->narchives; ++i) { + if (o->archives[i].owned) { + driver_free(o->env, (void*)o->archives[i].path, + o->archives[i].owned_size); + } } for (i = 0; i < o->ndsos; ++i) { - driver_free(o->env, o->dsos[i], o->dso_sizes[i]); + if (o->dsos[i].owned) { + driver_free(o->env, (void*)o->dsos[i].path, o->dsos[i].owned_size); + } } for (i = 0; i < o->npath_map; ++i) { if (o->owned_path_map_olds[i]) { @@ -217,19 +265,19 @@ static void cc_options_release(CcOptions* o) { if (o->stdin_buf) driver_free(o->env, o->stdin_buf, o->stdin_size); if (o->build_id_bytes) 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_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)); driver_free(o->env, o->source_memory, bound * sizeof(*o->source_memory)); driver_free(o->env, o->object_files, bound * sizeof(*o->object_files)); driver_free(o->env, o->archives, bound * sizeof(*o->archives)); - driver_free(o->env, o->owned_archives, bound * sizeof(*o->owned_archives)); - driver_free(o->env, o->owned_archive_sizes, - bound * sizeof(*o->owned_archive_sizes)); driver_free(o->env, o->dsos, bound * sizeof(*o->dsos)); - driver_free(o->env, o->dso_sizes, bound * sizeof(*o->dso_sizes)); driver_free(o->env, o->lib_search_paths, bound * sizeof(*o->lib_search_paths)); driver_free(o->env, o->pending_libs, bound * sizeof(*o->pending_libs)); + driver_free(o->env, o->link_items, bound * sizeof(*o->link_items)); driver_free(o->env, o->dep_targets, bound * sizeof(*o->dep_targets)); driver_free(o->env, o->path_map, bound * sizeof(*o->path_map)); driver_free(o->env, o->owned_path_map_olds, @@ -239,15 +287,41 @@ static void cc_options_release(CcOptions* o) { driver_free(o->env, o->rpaths, bound * sizeof(*o->rpaths)); } +static char* cc_dup_span(DriverEnv* env, const char* s, size_t n) { + char* buf = driver_alloc(env, n + 1u); + if (!buf) return NULL; + driver_memcpy(buf, s, n); + buf[n] = '\0'; + return buf; +} + +static int cc_record_build_id(CcOptions* o, const char* val); + /* Parse a single GCC-style -Wl,X[,Y...] pass-through argument. */ static int cc_record_wl(CcOptions* o, const char* arg) { const char* p = arg; + int expect_rpath = 0; + int expect_soname = 0; + int expect_interp = 0; while (*p) { const char* tok = p; size_t n = 0; while (p[n] && p[n] != ',') ++n; p = tok + n + (tok[n] == ',' ? 1 : 0); + if (expect_rpath || expect_soname || expect_interp) { + char* buf = cc_dup_span(o->env, tok, n); + if (!buf) { + driver_errf(CC_TOOL, "out of memory"); + return 1; + } + if (expect_rpath) o->rpaths[o->nrpaths++] = buf; + if (expect_soname) o->soname = buf; + if (expect_interp) o->interp_path = buf; + expect_rpath = expect_soname = expect_interp = 0; + continue; + } + if (n >= 8 && driver_strneq(tok, "-soname=", 8)) { char* buf; size_t bufsz = n - 8 + 1; @@ -261,6 +335,10 @@ static int cc_record_wl(CcOptions* o, const char* arg) { o->soname = buf; continue; } + if (n == 7 && driver_strneq(tok, "-soname", 7)) { + expect_soname = 1; + continue; + } if (n >= 7 && driver_strneq(tok, "-rpath=", 7)) { char* buf; size_t bufsz = n - 7 + 1; @@ -274,6 +352,22 @@ static int cc_record_wl(CcOptions* o, const char* arg) { o->rpaths[o->nrpaths++] = buf; continue; } + if (n == 6 && driver_strneq(tok, "-rpath", 6)) { + expect_rpath = 1; + continue; + } + if (n >= 16 && driver_strneq(tok, "-dynamic-linker=", 16)) { + o->interp_path = cc_dup_span(o->env, tok + 16, n - 16); + if (!o->interp_path) { + driver_errf(CC_TOOL, "out of memory"); + return 1; + } + continue; + } + if (n == 15 && driver_strneq(tok, "-dynamic-linker", 15)) { + expect_interp = 1; + continue; + } if (n == 18 && driver_strneq(tok, "--enable-new-dtags", 18)) { o->new_dtags = 1; continue; @@ -282,18 +376,58 @@ static int cc_record_wl(CcOptions* o, const char* arg) { o->new_dtags = 0; continue; } + if (n == 13 && driver_strneq(tok, "--gc-sections", 13)) { + o->gc_sections = 1; + continue; + } + if (n == 16 && driver_strneq(tok, "--no-gc-sections", 16)) { + o->gc_sections = 0; + continue; + } + if (n >= 11 && driver_strneq(tok, "--build-id=", 11)) { + char* buf = cc_dup_span(o->env, tok + 11, n - 11); + int rc; + if (!buf) { + driver_errf(CC_TOOL, "out of memory"); + return 1; + } + rc = cc_record_build_id(o, buf); + driver_free(o->env, buf, n - 11 + 1u); + if (rc != 0) return 1; + continue; + } + if (n == 10 && driver_strneq(tok, "--build-id", 10)) { + o->build_id_mode = CFREE_BUILDID_SHA256; + continue; + } driver_errf(CC_TOOL, "unsupported -Wl, token: %.*s", (int)n, tok); return 1; } + if (expect_rpath || expect_soname || expect_interp) { + driver_errf(CC_TOOL, "-Wl option requires another comma argument"); + return 1; + } return 0; } static int cc_is_c_source(const char* s) { - return driver_has_suffix(s, ".c") || driver_has_suffix(s, ".toy") || + return driver_has_suffix(s, ".c") || driver_has_suffix(s, ".S") || + driver_has_suffix(s, ".s") || driver_has_suffix(s, ".toy") || driver_has_suffix(s, ".wat") || driver_has_suffix(s, ".wasm"); } +static int cc_is_dso_input(const char* s) { + return driver_has_suffix(s, ".so") || driver_has_suffix(s, ".dylib") || + driver_has_suffix(s, ".tbd"); +} + +static void cc_push_link_item(CcOptions* o, uint8_t kind, uint32_t index) { + CcLinkItem* it = &o->link_items[o->nlink_items++]; + it->kind = kind; + it->index = index; +} + static int cc_parse_u64(const char* s, uint64_t* out) { uint64_t v = 0; int any = 0; @@ -420,6 +554,10 @@ static int cc_record_mcmodel(CcOptions* o, const char* val) { return 1; } +static void cc_log_ignored(const char* arg) { + driver_errf(CC_TOOL, "ignoring accepted compatibility flag: %s", arg); +} + static int cc_record_stdin(CcOptions* o) { CfreeSourceInput* in; if (o->stdin_buf) { @@ -435,21 +573,44 @@ static int cc_record_stdin(CcOptions* o) { in->bytes.data = o->stdin_buf; in->bytes.len = o->stdin_size; in->lang = CFREE_LANG_C; + cc_push_link_item(o, CC_LINK_SOURCE_MEMORY, o->nsource_memory - 1u); return 0; } -static int cc_classify_positional(CcOptions* o, const char* a) { +static CfreeLanguage cc_lang_for_path_or_forced(const char* path, + int forced_lang) { + if (forced_lang >= 0) return (CfreeLanguage)forced_lang; + return cfree_language_for_path(path); +} + +static int cc_classify_positional(CcOptions* o, const char* a, + int forced_lang) { if (driver_streq(a, "-")) return cc_record_stdin(o); - if (cc_is_c_source(a)) { + if (forced_lang >= 0 || cc_is_c_source(a)) { + o->source_langs[o->nsource_files] = + cc_lang_for_path_or_forced(a, forced_lang); o->source_files[o->nsource_files++] = a; + cc_push_link_item(o, CC_LINK_SOURCE_FILE, o->nsource_files - 1u); return 0; } if (driver_has_suffix(a, ".o") || driver_has_suffix(a, ".obj")) { o->object_files[o->nobject_files++] = a; + cc_push_link_item(o, CC_LINK_OBJECT, o->nobject_files - 1u); return 0; } if (driver_has_suffix(a, ".a")) { - o->archives[o->narchives++] = a; + CcArchiveInput* ar = &o->archives[o->narchives++]; + ar->path = a; + ar->whole_archive = o->cur_whole_archive; + ar->link_mode = o->cur_link_mode; + ar->group_id = o->cur_group_id; + cc_push_link_item(o, CC_LINK_ARCHIVE, o->narchives - 1u); + return 0; + } + if (cc_is_dso_input(a)) { + CcDsoInput* d = &o->dsos[o->ndsos++]; + d->path = a; + cc_push_link_item(o, CC_LINK_DSO, o->ndsos - 1u); return 0; } driver_errf(CC_TOOL, "input does not have a recognized suffix: %s", a); @@ -458,28 +619,36 @@ static int cc_classify_positional(CcOptions* o, const char* a) { static int cc_resolve_pending_libs(CcOptions* o) { uint32_t i; - LibResolveMode mode = - (o->target.obj == CFREE_OBJ_MACHO) ? LIB_RESOLVE_DYNAMIC_PREFER - : LIB_RESOLVE_STATIC_ONLY; for (i = 0; i < o->npending_libs; ++i) { + CcPendingLib* pl = &o->pending_libs[i]; char* p; size_t sz; LibResolveKind kind; - if (driver_lib_resolve(o->env, o->pending_libs[i], mode, - o->lib_search_paths, o->nlib_search_paths, &p, &sz, - &kind) != 0) { - driver_errf(CC_TOOL, "library not found: -l%s", o->pending_libs[i]); + LibResolveMode mode = (o->static_link || pl->link_mode == CFREE_LM_STATIC) + ? LIB_RESOLVE_STATIC_ONLY + : LIB_RESOLVE_DYNAMIC_PREFER; + if (driver_lib_resolve(o->env, pl->name, mode, o->lib_search_paths, + o->nlib_search_paths, &p, &sz, &kind) != 0) { + driver_errf(CC_TOOL, "library not found: -l%s", pl->name); return 1; } if (kind == LIB_RESOLVE_KIND_SHARED || kind == LIB_RESOLVE_KIND_TBD) { - o->dsos[o->ndsos] = p; - o->dso_sizes[o->ndsos] = sz; - o->ndsos++; + CcDsoInput* d = &o->dsos[o->ndsos++]; + d->path = p; + d->owned = 1; + d->owned_size = sz; + pl->resolved_kind = CC_LINK_DSO; + pl->resolved_index = o->ndsos - 1u; } else { - o->owned_archives[o->nowned_archives] = p; - o->owned_archive_sizes[o->nowned_archives] = sz; - o->nowned_archives++; - o->archives[o->narchives++] = p; + CcArchiveInput* ar = &o->archives[o->narchives++]; + ar->path = p; + ar->owned = 1; + ar->owned_size = sz; + ar->whole_archive = pl->whole_archive; + ar->link_mode = pl->link_mode; + ar->group_id = pl->group_id; + pl->resolved_kind = CC_LINK_ARCHIVE; + pl->resolved_index = o->narchives - 1u; } } return 0; @@ -494,8 +663,11 @@ static int cc_apply_env(CcOptions* o) { return 0; } +static char* cc_dep_default_target(DriverEnv* env, const CcOptions* o, + size_t* out_size); + static int cc_parse(int argc, char** argv, CcOptions* o) { - int x_lang_pinned = 0; + int forced_lang = -1; int i; if (cc_alloc_arrays(o, argc) != 0) return 1; @@ -511,15 +683,63 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { if (r > 0) continue; } - if (driver_streq(a, "-c")) { o->compile_only = 1; continue; } - if (driver_streq(a, "-E")) { o->preprocess_only = 1; continue; } - if (driver_streq(a, "--dump-tokens")) { o->dump_tokens = 1; continue; } - if (driver_streq(a, "-g")) { o->debug_info = 1; continue; } - if (driver_streq(a, "-O0")) { o->opt_level = 0; continue; } - if (driver_streq(a, "-O1")) { o->opt_level = 1; continue; } - if (driver_streq(a, "-O2")) { o->opt_level = 2; continue; } + if (driver_streq(a, "-c")) { + o->compile_only = 1; + continue; + } + if (driver_streq(a, "-v") || driver_streq(a, "-###")) { + cc_log_ignored(a); + continue; + } + if (driver_streq(a, "-E")) { + o->preprocess_only = 1; + continue; + } + if (driver_streq(a, "--dump-tokens")) { + o->dump_tokens = 1; + continue; + } + if (driver_streq(a, "-g")) { + o->debug_info = 1; + continue; + } + if (driver_streq(a, "-O0")) { + o->opt_level = 0; + continue; + } + if (driver_streq(a, "-O1")) { + o->opt_level = 1; + continue; + } + if (driver_streq(a, "-O2")) { + o->opt_level = 2; + continue; + } + if (driver_streq(a, "-O") || driver_streq(a, "-O3") || + driver_streq(a, "-Os") || driver_streq(a, "-Oz") || + driver_streq(a, "-Ofast")) { + o->opt_level = 2; + if (!driver_streq(a, "-O")) cc_log_ignored(a); + continue; + } - if (driver_streq(a, "-Werror")) { o->warnings_are_errors = 1; continue; } + if (driver_streq(a, "-Werror")) { + o->warnings_are_errors = 1; + continue; + } + if (driver_strneq(a, "-Werror=", 8)) { + o->warnings_are_errors = 1; + cc_log_ignored(a); + continue; + } + if (driver_streq(a, "-Wall") || driver_streq(a, "-Wextra") || + driver_streq(a, "-Wpedantic") || driver_streq(a, "-pedantic") || + driver_streq(a, "-pedantic-errors") || driver_streq(a, "-w") || + driver_strneq(a, "-Wno-", 5) || + (driver_strneq(a, "-W", 2) && !driver_strneq(a, "-Wl,", 4))) { + cc_log_ignored(a); + continue; + } if (driver_strneq(a, "-fmax-errors=", 13)) { uint64_t v; if (cc_parse_u64(a + 13, &v) != 0 || v > 0xFFFFFFFFu) { @@ -529,6 +749,32 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { o->max_errors = (uint32_t)v; continue; } + if (driver_strneq(a, "-std=", 5) || driver_streq(a, "-ansi")) { + cc_log_ignored(a); + continue; + } + 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")) { + 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 (++i >= argc) { + driver_errf(CC_TOOL, "%s requires an argument", a); + return 1; + } + cc_log_ignored(a); + continue; + } + if (driver_strneq(a, "--sysroot=", 10)) { + cc_log_ignored(a); + continue; + } if (driver_streq(a, "-fPIC") || driver_streq(a, "-fpic")) { o->target.pic = CFREE_PIC_PIC; @@ -538,9 +784,70 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { o->target.pic = CFREE_PIC_PIE; continue; } - if (driver_streq(a, "-static")) { o->target.pic = CFREE_PIC_NONE; continue; } - if (driver_streq(a, "-pie")) { o->target.pic = CFREE_PIC_PIE; continue; } - if (driver_streq(a, "-no-pie")) { o->target.pic = CFREE_PIC_NONE; continue; } + if (driver_streq(a, "-fno-PIC") || driver_streq(a, "-fno-pic") || + driver_streq(a, "-fno-PIE") || driver_streq(a, "-fno-pie")) { + o->target.pic = CFREE_PIC_NONE; + continue; + } + if (driver_streq(a, "-static")) { + o->target.pic = CFREE_PIC_NONE; + o->static_link = 1; + o->cur_link_mode = CFREE_LM_STATIC; + continue; + } + if (driver_streq(a, "-pie")) { + o->target.pic = CFREE_PIC_PIE; + o->pie = 1; + continue; + } + if (driver_streq(a, "-no-pie")) { + o->target.pic = CFREE_PIC_NONE; + continue; + } + if (driver_streq(a, "-Bstatic")) { + o->cur_link_mode = CFREE_LM_STATIC; + continue; + } + if (driver_streq(a, "-Bdynamic")) { + o->cur_link_mode = CFREE_LM_DYNAMIC; + continue; + } + if (driver_streq(a, "--as-needed")) { + o->cur_link_mode = CFREE_LM_AS_NEEDED; + continue; + } + if (driver_streq(a, "--no-as-needed")) { + o->cur_link_mode = CFREE_LM_DYNAMIC; + continue; + } + if (driver_streq(a, "--whole-archive")) { + o->cur_whole_archive = 1; + continue; + } + if (driver_streq(a, "--no-whole-archive")) { + o->cur_whole_archive = 0; + continue; + } + if (driver_streq(a, "--start-group")) { + if (o->cur_group_id != 0) { + driver_errf(CC_TOOL, "nested --start-group is not supported"); + return 1; + } + if (o->next_group_id == UINT8_MAX) { + driver_errf(CC_TOOL, "too many --start-group occurrences"); + return 1; + } + o->cur_group_id = ++o->next_group_id; + continue; + } + if (driver_streq(a, "--end-group")) { + if (o->cur_group_id == 0) { + driver_errf(CC_TOOL, "--end-group without --start-group"); + return 1; + } + o->cur_group_id = 0; + continue; + } if (driver_streq(a, "-shared")) { o->shared = 1; @@ -566,24 +873,35 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { } if (driver_streq(a, "-o")) { - if (++i >= argc) { driver_errf(CC_TOOL, "-o requires an argument"); return 1; } - if (o->output_path_set) { driver_errf(CC_TOOL, "duplicate -o"); return 1; } + if (++i >= argc) { + driver_errf(CC_TOOL, "-o requires an argument"); + return 1; + } o->output_path = argv[i]; o->output_path_set = 1; continue; } if (driver_streq(a, "-e")) { - if (++i >= argc) { driver_errf(CC_TOOL, "-e requires an argument"); return 1; } + if (++i >= argc) { + driver_errf(CC_TOOL, "-e requires an argument"); + return 1; + } o->entry = argv[i]; continue; } if (driver_streq(a, "-T")) { - if (++i >= argc) { driver_errf(CC_TOOL, "-T requires an argument"); return 1; } + if (++i >= argc) { + driver_errf(CC_TOOL, "-T requires an argument"); + return 1; + } o->linker_script = argv[i]; continue; } if (driver_streq(a, "-target")) { - if (++i >= argc) { driver_errf(CC_TOOL, "-target requires an argument"); return 1; } + if (++i >= argc) { + driver_errf(CC_TOOL, "-target requires an argument"); + return 1; + } if (driver_target_from_triple(argv[i], &o->target) != 0) { driver_errf(CC_TOOL, "unrecognized target triple: %s", argv[i]); return 1; @@ -591,46 +909,128 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { o->target_set = 1; continue; } + if (driver_strneq(a, "--target=", 9)) { + if (driver_target_from_triple(a + 9, &o->target) != 0) { + driver_errf(CC_TOOL, "unrecognized target triple: %s", a + 9); + return 1; + } + o->target_set = 1; + continue; + } + if (driver_streq(a, "--target")) { + if (++i >= argc) { + driver_errf(CC_TOOL, "--target requires an argument"); + return 1; + } + if (driver_target_from_triple(argv[i], &o->target) != 0) { + driver_errf(CC_TOOL, "unrecognized target triple: %s", argv[i]); + return 1; + } + o->target_set = 1; + continue; + } + if (driver_streq(a, "-Xlinker")) { + if (++i >= argc) { + driver_errf(CC_TOOL, "-Xlinker requires an argument"); + return 1; + } + if (cc_record_wl(o, argv[i]) != 0) return 1; + continue; + } if (driver_streq(a, "-x")) { - if (++i >= argc) { driver_errf(CC_TOOL, "-x requires an argument"); return 1; } - if (!driver_streq(argv[i], "c")) { + if (++i >= argc) { + driver_errf(CC_TOOL, "-x requires an argument"); + return 1; + } + if (driver_streq(argv[i], "none")) { + forced_lang = -1; + continue; + } + if (driver_streq(argv[i], "c")) { + forced_lang = CFREE_LANG_C; + continue; + } + if (driver_streq(argv[i], "assembler") || + driver_streq(argv[i], "assembler-with-cpp")) { + forced_lang = CFREE_LANG_ASM; + continue; + } + { driver_errf(CC_TOOL, "unsupported -x language: %s", argv[i]); return 1; } - x_lang_pinned = 1; - continue; } if (driver_strneq(a, "-L", 2)) { const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); - if (!dir) { driver_errf(CC_TOOL, "-L requires an argument"); return 1; } + if (!dir) { + driver_errf(CC_TOOL, "-L requires an argument"); + return 1; + } o->lib_search_paths[o->nlib_search_paths++] = dir; continue; } if (driver_strneq(a, "-l", 2)) { const char* name = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); - if (!name) { driver_errf(CC_TOOL, "-l requires an argument"); return 1; } - o->pending_libs[o->npending_libs++] = name; + if (!name) { + driver_errf(CC_TOOL, "-l requires an argument"); + return 1; + } + { + CcPendingLib* pl = &o->pending_libs[o->npending_libs++]; + pl->name = name; + pl->whole_archive = o->cur_whole_archive; + pl->link_mode = o->cur_link_mode; + pl->group_id = o->cur_group_id; + cc_push_link_item(o, CC_LINK_LIB, o->npending_libs - 1u); + } continue; } - if (driver_streq(a, "-M")) { o->dep_mode = CC_DEP_M; continue; } - if (driver_streq(a, "-MM")) { o->dep_mode = CC_DEP_MM; continue; } - if (driver_streq(a, "-MD")) { o->dep_mode = CC_DEP_MD; continue; } - if (driver_streq(a, "-MMD")) { o->dep_mode = CC_DEP_MMD; continue; } - if (driver_streq(a, "-MP")) { o->dep_phony = 1; continue; } + if (driver_streq(a, "-M")) { + o->dep_mode = CC_DEP_M; + continue; + } + if (driver_streq(a, "-MM")) { + o->dep_mode = CC_DEP_MM; + continue; + } + if (driver_streq(a, "-MD")) { + o->dep_mode = CC_DEP_MD; + continue; + } + if (driver_streq(a, "-MMD")) { + o->dep_mode = CC_DEP_MMD; + continue; + } + if (driver_streq(a, "-MP")) { + o->dep_phony = 1; + continue; + } if (driver_streq(a, "-MF")) { - if (++i >= argc) { driver_errf(CC_TOOL, "-MF requires an argument"); return 1; } + if (++i >= argc) { + driver_errf(CC_TOOL, "-MF requires an argument"); + return 1; + } o->dep_file = argv[i]; continue; } if (driver_streq(a, "-MT") || driver_streq(a, "-MQ")) { - if (++i >= argc) { driver_errf(CC_TOOL, "%s requires an argument", a); return 1; } + if (++i >= argc) { + driver_errf(CC_TOOL, "%s requires an argument", a); + return 1; + } o->dep_targets[o->ndep_targets++] = argv[i]; continue; } + if (driver_streq(a, "--")) { + for (++i; i < argc; ++i) { + if (cc_classify_positional(o, argv[i], forced_lang) != 0) return 1; + } + break; + } if (driver_streq(a, "-")) { - if (cc_classify_positional(o, a) != 0) return 1; + if (cc_classify_positional(o, a, forced_lang) != 0) return 1; continue; } if (a[0] == '-' && a[1] != '\0') { @@ -638,16 +1038,16 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { return 1; } - if (cc_classify_positional(o, a) != 0) return 1; + if (cc_classify_positional(o, a, forced_lang) != 0) return 1; } - (void)x_lang_pinned; if (cc_apply_env(o) != 0) return 1; if (cc_resolve_pending_libs(o) != 0) return 1; { uint32_t total_sources = o->nsource_files + o->nsource_memory; - uint32_t total_link = o->nobject_files + o->narchives; + uint32_t total_link = + o->nobject_files + o->narchives + o->ndsos + o->npending_libs; if (total_sources == 0 && total_link == 0) { driver_errf(CC_TOOL, "no input files"); @@ -662,30 +1062,41 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { driver_errf(CC_TOOL, "--dump-tokens is incompatible with -c/-E"); return 1; } - if (o->shared && (o->compile_only || o->preprocess_only || o->dump_tokens)) { + if (o->shared && + (o->compile_only || o->preprocess_only || o->dump_tokens)) { driver_errf(CC_TOOL, "-shared is incompatible with -c/-E/--dump-tokens"); return 1; } - if (!o->shared && (o->soname || o->nrpaths)) { - driver_errf(CC_TOOL, "-Wl,-soname / -Wl,-rpath require -shared"); + if (!o->shared && o->soname) { + driver_errf(CC_TOOL, "-Wl,-soname requires -shared"); + return 1; + } + if (o->cur_group_id != 0) { + driver_errf(CC_TOOL, "missing --end-group"); return 1; } if (o->compile_only) { - if (total_sources != 1 || total_link != 0) { - driver_errf(CC_TOOL, "-c requires exactly one C source and no .o/.a inputs"); + if (total_sources == 0 || total_link != 0) { + driver_errf(CC_TOOL, "-c requires source inputs and no link inputs"); + return 1; + } + if (o->output_path && total_sources > 1) { + driver_errf(CC_TOOL, "-o cannot be used with -c and multiple sources"); return 1; } } if (o->preprocess_only) { if (total_sources != 1 || total_link != 0) { - driver_errf(CC_TOOL, "-E requires exactly one C source and no .o/.a inputs"); + driver_errf(CC_TOOL, + "-E requires exactly one C source and no .o/.a inputs"); return 1; } } if (o->dump_tokens) { if (total_sources != 1 || total_link != 0) { - driver_errf(CC_TOOL, - "--dump-tokens requires exactly one C source and no .o/.a inputs"); + driver_errf( + CC_TOOL, + "--dump-tokens requires exactly one C source and no .o/.a inputs"); return 1; } } @@ -706,10 +1117,27 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { driver_errf(CC_TOOL, "-MD/-MMD currently requires -c"); return 1; } - if (!o->output_path && !dep_only) { - driver_errf(CC_TOOL, "-o is required"); + if (dep_with_compile && total_sources != 1) { + driver_errf(CC_TOOL, "-MD/-MMD currently requires exactly one source"); return 1; } + if (!o->output_path && !dep_only) { + if (o->compile_only) { + if (total_sources == 1) { + o->owned_output_path = + cc_dep_default_target(o->env, o, &o->owned_output_path_size); + if (!o->owned_output_path) { + driver_errf(CC_TOOL, "out of memory"); + return 1; + } + o->output_path = o->owned_output_path; + } + } else if (o->preprocess_only) { + /* stdout */ + } else { + o->output_path = "a.out"; + } + } } } return 0; @@ -749,10 +1177,18 @@ static int cc_preprocess(DriverEnv* env, const CcOptions* o, if (cc_load_single_source(&ctx, o, &input, &fd, &loaded) != 0) goto out; - if (ctx.file_io->open_writer(ctx.file_io->user, o->output_path, &writer) != - CFREE_OK) { - driver_errf(CC_TOOL, "failed to open output: %s", o->output_path); - goto out; + if (o->output_path) { + if (ctx.file_io->open_writer(ctx.file_io->user, o->output_path, &writer) != + CFREE_OK) { + driver_errf(CC_TOOL, "failed to open output: %s", o->output_path); + goto out; + } + } else { + writer = driver_stdout_writer(env); + if (!writer) { + driver_errf(CC_TOOL, "out of memory"); + goto out; + } } if (driver_compiler_new(o->target, &ctx, &compiler) != CFREE_OK) { @@ -760,8 +1196,8 @@ static int cc_preprocess(DriverEnv* env, const CcOptions* o, goto out; } - rc = cfree_c_preprocess(compiler, pp_opts, &input, writer) == CFREE_OK ? 0 - : 1; + rc = + cfree_c_preprocess(compiler, pp_opts, &input, writer) == CFREE_OK ? 0 : 1; out: if (compiler) driver_compiler_free(compiler); @@ -914,11 +1350,17 @@ static char* cc_dep_default_target(DriverEnv* env, const CcOptions* o, size_t slash = 0; size_t k; for (k = srclen; k > 0; --k) { - if (src[k - 1] == '.') { dot = k - 1; break; } + if (src[k - 1] == '.') { + dot = k - 1; + break; + } if (src[k - 1] == '/') break; } for (k = dot; k > 0; --k) { - if (src[k - 1] == '/') { slash = k; break; } + if (src[k - 1] == '/') { + slash = k; + break; + } } { size_t name_len = dot - slash; @@ -935,13 +1377,50 @@ static char* cc_dep_default_target(DriverEnv* env, const CcOptions* o, } } +static char* cc_default_obj_path_for_name(DriverEnv* env, const char* src, + size_t* out_size) { + size_t srclen = driver_strlen(src); + size_t dot = srclen; + size_t slash = 0; + size_t k; + char* buf; + for (k = srclen; k > 0; --k) { + if (src[k - 1] == '.') { + dot = k - 1; + break; + } + if (src[k - 1] == '/') break; + } + for (k = dot; k > 0; --k) { + if (src[k - 1] == '/') { + slash = k; + break; + } + } + { + size_t name_len = dot - slash; + size_t bufsz = name_len + 3; + buf = driver_alloc(env, bufsz); + if (!buf) return NULL; + driver_memcpy(buf, src + slash, name_len); + buf[name_len] = '.'; + buf[name_len + 1] = 'o'; + buf[name_len + 2] = '\0'; + *out_size = bufsz; + return buf; + } +} + static char* cc_dep_default_path(DriverEnv* env, const char* out_path, size_t* out_size) { size_t len = driver_strlen(out_path); size_t dot = len; size_t k; for (k = len; k > 0; --k) { - if (out_path[k - 1] == '.') { dot = k - 1; break; } + if (out_path[k - 1] == '.') { + dot = k - 1; + break; + } if (out_path[k - 1] == '/') break; } { @@ -1055,7 +1534,10 @@ static int cc_dep_finish(DriverEnv* env, const CfreeContext* ctx, ntargets = o->ndep_targets; if (ntargets == 0) { owned_target = cc_dep_default_target(env, o, &owned_target_size); - if (!owned_target) { driver_errf(CC_TOOL, "out of memory"); goto out; } + if (!owned_target) { + driver_errf(CC_TOOL, "out of memory"); + goto out; + } one_target[0] = owned_target; targets = one_target; ntargets = 1; @@ -1063,7 +1545,8 @@ static int cc_dep_finish(DriverEnv* env, const CfreeContext* ctx, dep_w = cc_dep_open_writer(env, ctx, o, &owned_path, &owned_size); if (!dep_w) { - driver_errf(CC_TOOL, "failed to open dep output: %s", + driver_errf( + CC_TOOL, "failed to open dep output: %s", o->dep_file ? o->dep_file : (owned_path ? owned_path : "<stdout>")); goto out; } @@ -1107,7 +1590,10 @@ static int cc_run_deps_only(DriverEnv* env, const CcOptions* o, if (cc_load_single_source(&ctx, o, &input, &fd, &loaded) != 0) goto out; discard = cc_discard_writer_new(env); - if (!discard) { driver_errf(CC_TOOL, "out of memory"); goto out; } + if (!discard) { + driver_errf(CC_TOOL, "out of memory"); + goto out; + } if (driver_compiler_new(o->target, &ctx, &compiler) != CFREE_OK) { driver_errf(CC_TOOL, "failed to initialize compiler"); @@ -1125,8 +1611,9 @@ out: return rc; } -static int cc_run_compile_obj(DriverEnv* env, const CcOptions* o, - const CfreePreprocessOptions* pp) { +static int cc_run_compile_one(DriverEnv* env, const CcOptions* o, + const CfreePreprocessOptions* pp, int is_memory, + uint32_t index, const char* out_path) { CfreeContext ctx = driver_env_to_context(env); CfreeCompiler* compiler = NULL; CfreeWriter* obj_w = NULL; @@ -1136,11 +1623,23 @@ static int cc_run_compile_obj(DriverEnv* env, const CcOptions* o, int loaded = 0; int rc = 1; - if (cc_load_single_source(&ctx, o, &input, &fd, &loaded) != 0) goto out; + if (is_memory) { + input = o->source_memory[index].bytes; + } else { + if (ctx.file_io->read_all(ctx.file_io->user, o->source_files[index], &fd) != + CFREE_OK) { + driver_errf(CC_TOOL, "failed to read: %s", o->source_files[index]); + goto out; + } + loaded = 1; + input.name = o->source_files[index]; + input.data = fd.data; + input.len = fd.size; + } - if (ctx.file_io->open_writer(ctx.file_io->user, o->output_path, &obj_w) != + if (ctx.file_io->open_writer(ctx.file_io->user, out_path, &obj_w) != CFREE_OK) { - driver_errf(CC_TOOL, "failed to open output: %s", o->output_path); + driver_errf(CC_TOOL, "failed to open output: %s", out_path); goto out; } @@ -1151,7 +1650,8 @@ static int cc_run_compile_obj(DriverEnv* env, const CcOptions* o, cc_fill_c_opts(o, pp, &copts); { - CfreeLanguage lang = cfree_language_for_path(o->source_files[0]); + CfreeLanguage lang = + is_memory ? o->source_memory[index].lang : o->source_langs[index]; CfreeStatus st; if (lang == CFREE_LANG_C) { st = cfree_compile_c_obj_emit(compiler, &copts, &input, obj_w); @@ -1184,6 +1684,35 @@ out: return rc; } +static int cc_run_compile_obj(DriverEnv* env, const CcOptions* o, + const CfreePreprocessOptions* pp) { + return cc_run_compile_one(env, o, pp, o->nsource_memory == 1, 0, + o->output_path); +} + +static int cc_run_compile_objs(DriverEnv* env, const CcOptions* o, + const CfreePreprocessOptions* pp) { + uint32_t i; + 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); + int rc; + if (!out) { + driver_errf(CC_TOOL, "out of memory"); + return 1; + } + rc = cc_run_compile_one(env, o, pp, 0, i, out); + driver_free(env, out, out_size); + if (rc != 0) return rc; + } + for (i = 0; i < o->nsource_memory; ++i) { + const char* out = "<stdin>.o"; + if (cc_run_compile_one(env, o, pp, 1, i, out) != 0) return 1; + } + return 0; +} + /* exe path: compile every C source via a single CfreeCompiler, load * .o/.a/script inputs, and call cfree_link_exe or cfree_link_shared. The * compiler owns the per-source CfreeObjBuilders for the lifetime of the @@ -1203,6 +1732,7 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, CfreeBytes* obj_in = NULL; CfreeLinkArchiveInput* arch_in = NULL; CfreeBytes* dso_in = NULL; + CfreeLinkInputOrder* order = NULL; CfreeObjBuilder** objs = NULL; CfreeLinkScript* script = NULL; CfreeCCompileOptions copts; @@ -1216,29 +1746,50 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, } if (o->nsource_files) { - src_bytes = - driver_alloc_zeroed(env, o->nsource_files * sizeof(*src_bytes)); + src_bytes = driver_alloc_zeroed(env, o->nsource_files * sizeof(*src_bytes)); src_lf = driver_alloc_zeroed(env, o->nsource_files * sizeof(*src_lf)); - if (!src_bytes || !src_lf) { driver_errf(CC_TOOL, "out of memory"); goto out; } + if (!src_bytes || !src_lf) { + driver_errf(CC_TOOL, "out of memory"); + goto out; + } } if (nsrc) { objs = driver_alloc_zeroed(env, nsrc * sizeof(*objs)); - if (!objs) { driver_errf(CC_TOOL, "out of memory"); goto out; } + if (!objs) { + driver_errf(CC_TOOL, "out of memory"); + goto out; + } } if (o->nobject_files) { obj_lf = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_lf)); obj_in = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_in)); - if (!obj_lf || !obj_in) { driver_errf(CC_TOOL, "out of memory"); goto out; } + if (!obj_lf || !obj_in) { + driver_errf(CC_TOOL, "out of memory"); + goto out; + } } if (o->narchives) { arch_lf = driver_alloc_zeroed(env, o->narchives * sizeof(*arch_lf)); arch_in = driver_alloc_zeroed(env, o->narchives * sizeof(*arch_in)); - if (!arch_lf || !arch_in) { driver_errf(CC_TOOL, "out of memory"); goto out; } + if (!arch_lf || !arch_in) { + driver_errf(CC_TOOL, "out of memory"); + goto out; + } } if (o->ndsos) { dso_lf = driver_alloc_zeroed(env, o->ndsos * sizeof(*dso_lf)); dso_in = driver_alloc_zeroed(env, o->ndsos * sizeof(*dso_in)); - if (!dso_lf || !dso_in) { driver_errf(CC_TOOL, "out of memory"); goto out; } + if (!dso_lf || !dso_in) { + driver_errf(CC_TOOL, "out of memory"); + goto out; + } + } + if (o->nlink_items) { + order = driver_alloc_zeroed(env, o->nlink_items * sizeof(*order)); + if (!order) { + driver_errf(CC_TOOL, "out of memory"); + goto out; + } } for (i = 0; i < o->nsource_files; ++i) { @@ -1253,15 +1804,16 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, goto out; } for (i = 0; i < o->narchives; ++i) { - if (driver_load_bytes(io, CC_TOOL, o->archives[i], &arch_lf[i], + if (driver_load_bytes(io, CC_TOOL, o->archives[i].path, &arch_lf[i], &arch_in[i].bytes) != 0) goto out; - arch_in[i].link_mode = CFREE_LM_DEFAULT; - arch_in[i].whole_archive = 0; - arch_in[i].group_id = 0; + arch_in[i].link_mode = o->archives[i].link_mode; + arch_in[i].whole_archive = o->archives[i].whole_archive; + arch_in[i].group_id = o->archives[i].group_id; } for (i = 0; i < o->ndsos; ++i) { - if (driver_load_bytes(io, CC_TOOL, o->dsos[i], &dso_lf[i], &dso_in[i]) != 0) + if (driver_load_bytes(io, CC_TOOL, o->dsos[i].path, &dso_lf[i], + &dso_in[i]) != 0) goto out; } @@ -1285,7 +1837,7 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, cc_fill_c_opts(o, pp, &copts); for (i = 0; i < o->nsource_files; ++i) { - CfreeLanguage lang = cfree_language_for_path(o->source_files[i]); + CfreeLanguage lang = o->source_langs[i]; CfreeStatus st; if (lang == CFREE_LANG_C) { st = cfree_compile_c_obj(compiler, &copts, &src_bytes[i], &objs[i]); @@ -1340,6 +1892,45 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, inputs.build_id_mode = o->build_id_mode; inputs.build_id_bytes = o->build_id_bytes; inputs.build_id_len = o->build_id_len; + if (order) { + uint32_t oi; + for (oi = 0; oi < o->nlink_items; ++oi) { + const CcLinkItem* item = &o->link_items[oi]; + CfreeLinkInputOrder* dst = &order[oi]; + switch ((CcLinkItemKind)item->kind) { + case CC_LINK_SOURCE_FILE: + dst->kind = CFREE_LINK_INPUT_OBJ; + dst->index = item->index; + break; + case CC_LINK_SOURCE_MEMORY: + dst->kind = CFREE_LINK_INPUT_OBJ; + dst->index = o->nsource_files + item->index; + break; + case CC_LINK_OBJECT: + dst->kind = CFREE_LINK_INPUT_OBJ_BYTES; + dst->index = item->index; + break; + case CC_LINK_ARCHIVE: + dst->kind = CFREE_LINK_INPUT_ARCHIVE; + dst->index = item->index; + break; + case CC_LINK_DSO: + dst->kind = CFREE_LINK_INPUT_DSO; + dst->index = item->index; + break; + case CC_LINK_LIB: { + const CcPendingLib* pl = &o->pending_libs[item->index]; + dst->kind = (pl->resolved_kind == CC_LINK_DSO) + ? CFREE_LINK_INPUT_DSO + : CFREE_LINK_INPUT_ARCHIVE; + dst->index = pl->resolved_index; + break; + } + } + } + inputs.order = order; + inputs.norder = o->nlink_items; + } if (o->shared) { CfreeSharedLinkOptions z = {0}; @@ -1355,12 +1946,16 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, s.nrpaths = o->nrpaths; } s.allow_undefined = 1; + s.gc_sections = o->gc_sections; rc = cfree_link_shared(compiler, &s, out_w) == CFREE_OK ? 0 : 1; } else { CfreeExeLinkOptions z = {0}; CfreeExeLinkOptions link_opts; link_opts = z; link_opts.inputs = inputs; + link_opts.gc_sections = o->gc_sections; + link_opts.pie = o->pie; + link_opts.interp_path = o->interp_path; rc = cfree_link_exe(compiler, &link_opts, out_w) == CFREE_OK ? 0 : 1; } } @@ -1369,8 +1964,7 @@ out: if (out_w) cfree_writer_close(out_w); if (rc == 0 && o->output_path) { if (driver_mark_executable_output(o->output_path) != 0) { - driver_errf(CC_TOOL, "failed to set executable mode: %s", - o->output_path); + driver_errf(CC_TOOL, "failed to set executable mode: %s", o->output_path); rc = 1; } } @@ -1393,6 +1987,7 @@ out: if (arch_lf) driver_free(env, arch_lf, o->narchives * sizeof(*arch_lf)); if (dso_in) driver_free(env, dso_in, o->ndsos * sizeof(*dso_in)); if (dso_lf) driver_free(env, dso_lf, o->ndsos * sizeof(*dso_lf)); + if (order) driver_free(env, order, o->nlink_items * sizeof(*order)); if (obj_in) driver_free(env, obj_in, o->nobject_files * sizeof(*obj_in)); if (obj_lf) driver_free(env, obj_lf, o->nobject_files * sizeof(*obj_lf)); if (src_lf) driver_free(env, src_lf, o->nsource_files * sizeof(*src_lf)); @@ -1431,7 +2026,7 @@ int driver_cc(int argc, char** argv) { } else if (co.dep_mode == CC_DEP_M || co.dep_mode == CC_DEP_MM) { rc = cc_run_deps_only(&env, &co, &pp); } else if (co.compile_only) { - rc = cc_run_compile_obj(&env, &co, &pp); + rc = cc_run_compile_objs(&env, &co, &pp); } else { rc = cc_run_link_exe(&env, &co, &pp); } diff --git a/driver/env.c b/driver/env.c @@ -1578,11 +1578,28 @@ out: return ok; } +static int *driver_hosted_errno_location(void) { return &errno; } +static FILE *driver_hosted_stdin(void) { return stdin; } +static FILE *driver_hosted_stdout(void) { return stdout; } +static FILE *driver_hosted_stderr(void) { return stderr; } + void *driver_dlsym_resolver(void *user, const char *name) { void *p; (void)user; if (!name) return NULL; + if (strcmp(name, "__cfree_errno_location") == 0 || + (name[0] == '_' && strcmp(name + 1, "__cfree_errno_location") == 0)) + return (void *)&driver_hosted_errno_location; + if (strcmp(name, "__cfree_stdin") == 0 || + (name[0] == '_' && strcmp(name + 1, "__cfree_stdin") == 0)) + return (void *)&driver_hosted_stdin; + if (strcmp(name, "__cfree_stdout") == 0 || + (name[0] == '_' && strcmp(name + 1, "__cfree_stdout") == 0)) + return (void *)&driver_hosted_stdout; + if (strcmp(name, "__cfree_stderr") == 0 || + (name[0] == '_' && strcmp(name + 1, "__cfree_stderr") == 0)) + return (void *)&driver_hosted_stderr; p = dlsym(RTLD_DEFAULT, name); /* On Mach-O hosts the linker hands us C names with a leading underscore * (obj_format_c_mangle), but dlsym(RTLD_DEFAULT) expects the diff --git a/src/api/compile.c b/src/api/compile.c @@ -36,7 +36,8 @@ CfreeLanguage cfree_language_for_path(const char* path) { if (path[i] == '/') return CFREE_LANG_C; if (path[i] == '.') { const char* ext = path + i + 1; - if (ext[0] == 's' && ext[1] == '\0') return CFREE_LANG_ASM; + if ((ext[0] == 's' || ext[0] == 'S') && ext[1] == '\0') + return CFREE_LANG_ASM; if (ext[0] == 't' && ext[1] == 'o' && ext[2] == 'y' && ext[3] == '\0') return CFREE_LANG_TOY; if (ext[0] == 'w' && ext[1] == 'a' && ext[2] == 't' && ext[3] == '\0') diff --git a/test/ar/cases/04-rcu-compat.expected b/test/ar/cases/04-rcu-compat.expected @@ -0,0 +1,2 @@ +a.o +b.o diff --git a/test/ar/cases/04-rcu-compat.sh b/test/ar/cases/04-rcu-compat.sh @@ -0,0 +1,8 @@ +# Lua and many traditional makefiles use `ar rcu`. cfree does not track +# member mtimes for update-if-newer semantics, but it should accept `u` +# as a compatibility modifier. + +printf 'aaaa' > a.o +printf 'bbbb' > b.o +"$CFREE" ar rcu lib.a a.o b.o +"$CFREE" ar t lib.a diff --git a/test/driver/run.sh b/test/driver/run.sh @@ -29,6 +29,10 @@ int main(void) { return 0; } int _start(void) { return 0; } SRC +cat > "$work/other.c" <<'SRC' +int other(void) { return 0; } +SRC + pass=0 fail=0 @@ -55,6 +59,21 @@ else fail=$((fail + 1)) fi +rm -f "$work/a.out" +if (cd "$work" && umask 077 && "$CFREE" cc main.c) \ + > "$work/cc-link-default.out" 2> "$work/cc-link-default.err"; then + if [ -f "$work/a.out" ]; then + check_mode "cc-link-default-output" "$work/a.out" 700 + else + printf 'FAIL %s (a.out not created)\n' "cc-link-default-output" + fail=$((fail + 1)) + fi +else + printf 'FAIL cc-link-default-output (cfree cc failed)\n' + sed 's/^/ | /' "$work/cc-link-default.err" + fail=$((fail + 1)) +fi + if (umask 077; "$CFREE" cc -c "$work/main.c" -o "$work/main.o") \ > "$work/cc-c.out" 2> "$work/cc-c.err"; then : > "$work/ld-exe" @@ -73,6 +92,154 @@ else fail=$((fail + 1)) fi +rm -f "$work/main.o" +if (cd "$work" && "$CFREE" cc -c main.c) \ + > "$work/cc-c-default.out" 2> "$work/cc-c-default.err"; then + if [ -f "$work/main.o" ]; then + printf 'PASS %s\n' "cc-c-default-output" + pass=$((pass + 1)) + else + printf 'FAIL %s (main.o not created)\n' "cc-c-default-output" + fail=$((fail + 1)) + fi +else + printf 'FAIL cc-c-default-output (cfree cc -c failed)\n' + sed 's/^/ | /' "$work/cc-c-default.err" + fail=$((fail + 1)) +fi + +rm -f "$work/main.o" "$work/other.o" +if (cd "$work" && "$CFREE" cc -c main.c other.c) \ + > "$work/cc-c-multi.out" 2> "$work/cc-c-multi.err"; then + if [ -f "$work/main.o" ] && [ -f "$work/other.o" ]; then + printf 'PASS %s\n' "cc-c-multi-default-output" + pass=$((pass + 1)) + else + printf 'FAIL %s (expected main.o and other.o)\n' "cc-c-multi-default-output" + fail=$((fail + 1)) + fi +else + printf 'FAIL cc-c-multi-default-output (cfree cc -c failed)\n' + sed 's/^/ | /' "$work/cc-c-multi.err" + fail=$((fail + 1)) +fi + +if "$CFREE" cc -E "$work/main.c" > "$work/cc-E-default.out" 2> "$work/cc-E-default.err"; then + if [ -s "$work/cc-E-default.out" ]; then + printf 'PASS %s\n' "cc-E-default-stdout" + pass=$((pass + 1)) + else + printf 'FAIL %s (stdout was empty)\n' "cc-E-default-stdout" + fail=$((fail + 1)) + fi +else + printf 'FAIL cc-E-default-stdout (cfree cc -E failed)\n' + sed 's/^/ | /' "$work/cc-E-default.err" + fail=$((fail + 1)) +fi + +if "$CFREE" cc -Wall -Wextra -std=c11 -ffreestanding -c "$work/main.c" \ + -o "$work/ignored.o" > "$work/cc-ignored.out" 2> "$work/cc-ignored.err"; then + if grep -q "ignoring accepted compatibility flag: -Wall" "$work/cc-ignored.err" && + grep -q "ignoring accepted compatibility flag: -std=c11" "$work/cc-ignored.err"; then + printf 'PASS %s\n' "cc-ignored-compat-flags" + pass=$((pass + 1)) + else + printf 'FAIL %s (missing ignore log)\n' "cc-ignored-compat-flags" + sed 's/^/ | /' "$work/cc-ignored.err" + fail=$((fail + 1)) + fi +else + printf 'FAIL cc-ignored-compat-flags (cfree cc failed)\n' + sed 's/^/ | /' "$work/cc-ignored.err" + fail=$((fail + 1)) +fi + +cat > "$work/run-errno.c" <<'SRC' +#include <errno.h> +int main(void) { + errno = 42; + return errno; +} +SRC + +"$CFREE" run -I"$repo_root/rt/include" -I"$repo_root/rt/include/libc" \ + "$work/run-errno.c" > "$work/run-errno.out" 2> "$work/run-errno.err" +run_errno_status=$? +if [ "$run_errno_status" -eq 42 ]; then + printf 'PASS %s\n' "run-hosted-errno-shim" + pass=$((pass + 1)) +else + printf 'FAIL %s (status %s, want 42)\n' \ + "run-hosted-errno-shim" "$run_errno_status" + sed 's/^/ | /' "$work/run-errno.err" + fail=$((fail + 1)) +fi + +cat > "$work/run-main.c" <<'SRC' +int add42(int); +int main(void) { return add42(0); } +SRC +cat > "$work/run-lib.c" <<'SRC' +int add42(int x) { return x + 42; } +SRC + +if "$CFREE" cc -I"$repo_root/rt/include" -I"$repo_root/rt/include/libc" \ + -c "$work/run-lib.c" -o "$work/run-lib.o" \ + > "$work/run-lib.out" 2> "$work/run-lib.err" && + "$CFREE" ar rcs "$work/librun.a" "$work/run-lib.o" \ + > "$work/run-ar.out" 2> "$work/run-ar.err"; then + "$CFREE" run -I"$repo_root/rt/include" -I"$repo_root/rt/include/libc" \ + "$work/run-main.c" "$work/librun.a" \ + > "$work/run-archive.out" 2> "$work/run-archive.err" + run_status=$? + if [ "$run_status" -eq 42 ]; then + printf 'PASS %s\n' "run-source-archive-demand" + pass=$((pass + 1)) + else + printf 'FAIL %s (status %s, want 42)\n' \ + "run-source-archive-demand" "$run_status" + sed 's/^/ | /' "$work/run-archive.err" + fail=$((fail + 1)) + fi +else + printf 'FAIL run-source-archive-demand (setup failed)\n' + sed 's/^/ | /' "$work/run-lib.err" "$work/run-ar.err" + fail=$((fail + 1)) +fi + +cat > "$work/order-main.c" <<'SRC' +int foo(void); +int _start(void) { return foo(); } +SRC +cat > "$work/order-foo.c" <<'SRC' +int foo(void) { return 0; } +SRC + +if "$CFREE" cc -target x86_64-linux -c "$work/order-main.c" -o "$work/order-main.o" \ + > "$work/order-main.out" 2> "$work/order-main.err" && + "$CFREE" cc -target x86_64-linux -c "$work/order-foo.c" -o "$work/order-foo.o" \ + > "$work/order-foo.out" 2> "$work/order-foo.err" && + "$CFREE" ar rc "$work/libfoo.a" "$work/order-foo.o" \ + > "$work/order-ar.out" 2> "$work/order-ar.err"; then + if "$CFREE" cc -target x86_64-linux -L"$work" "$work/order-main.o" -lfoo \ + -o "$work/order-right" > "$work/order-right.out" 2> "$work/order-right.err" && + ! "$CFREE" cc -target x86_64-linux -L"$work" -lfoo "$work/order-main.o" \ + -o "$work/order-wrong" > "$work/order-wrong.out" 2> "$work/order-wrong.err"; then + printf 'PASS %s\n' "cc-link-archive-order" + pass=$((pass + 1)) + else + printf 'FAIL %s (archive order was not enforced)\n' "cc-link-archive-order" + sed 's/^/ right| /' "$work/order-right.err" + sed 's/^/ wrong| /' "$work/order-wrong.err" + fail=$((fail + 1)) + fi +else + printf 'FAIL cc-link-archive-order (setup failed)\n' + sed 's/^/ | /' "$work/order-main.err" "$work/order-foo.err" "$work/order-ar.err" + fail=$((fail + 1)) +fi + total=$((pass + fail)) if [ "$fail" -gt 0 ]; then printf '\ndriver: %d/%d passed\n' "$pass" "$total"