commit f5e26d02b0f89dfa410e54faed53cf944961bae3
parent 255010e4f290bdd252a9889554a939427c26701e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 07:29:17 -0700
Add iOS simulator driver support
Diffstat:
33 files changed, 1221 insertions(+), 99 deletions(-)
diff --git a/driver/cmd/build.c b/driver/cmd/build.c
@@ -112,6 +112,7 @@ typedef struct BuildOptions {
int shared; /* computed: kind==lib && dynamic */
int static_link; /* -static */
int pie; /* -pie */
+ int pic_explicit; /* -fPIC/-fPIE/-static/-pie/-no-pie */
int function_sections; /* -ffunction-sections */
int data_sections; /* -fdata-sections */
int auto_var_init; /* -ftrivial-auto-var-init= (KitAutoVarInit) */
@@ -154,6 +155,8 @@ typedef struct BuildOptions {
DriverLinkInputSet inputs;
} BuildOptions;
+static int build_record_framework(BuildOptions* o, const char* name);
+
/* ===================================================================== */
/* small parse helpers */
/* ===================================================================== */
@@ -362,6 +365,12 @@ static int build_is_global_flag(const char* a) {
driver_streq(a, "-fsyntax-only") || driver_strneq(a, "-fPIC", 5) ||
driver_strneq(a, "-fpic", 5) || driver_strneq(a, "-fPIE", 5) ||
driver_strneq(a, "-fpie", 5) ||
+ driver_streq(a, "-arch") || driver_streq(a, "-platform_version") ||
+ driver_streq(a, "-macosx_version_min") ||
+ driver_streq(a, "-ios_version_min") ||
+ driver_streq(a, "-iphoneos_version_min") ||
+ driver_streq(a, "-ios_simulator_version_min") ||
+ driver_streq(a, "-iphonesimulator_version_min") ||
driver_strneq(a, "-fvisibility=", 13) ||
driver_streq(a, "-ffunction-sections") ||
driver_streq(a, "-fdata-sections") ||
@@ -375,7 +384,8 @@ static int build_is_global_flag(const char* a) {
driver_streq(a, "-shared") || driver_streq(a, "-pie") ||
driver_streq(a, "-no-pie") || driver_streq(a, "-target") ||
driver_strneq(a, "--target", 8) || driver_strneq(a, "-l", 2) ||
- driver_strneq(a, "-L", 2) || driver_streq(a, "-e") ||
+ driver_strneq(a, "-L", 2) || driver_strneq(a, "-F", 2) ||
+ driver_streq(a, "-framework") || driver_streq(a, "-e") ||
driver_streq(a, "-T") ||
driver_streq(a, "-Ttext") || driver_strneq(a, "-Ttext=", 7) ||
driver_streq(a, "--gc-sections") ||
@@ -536,15 +546,18 @@ static int build_parse(int argc, char** argv, BuildOptions* o) {
}
if (driver_streq(a, "-fPIC") || driver_streq(a, "-fpic")) {
o->target.pic = KIT_PIC_PIC;
+ o->pic_explicit = 1;
continue;
}
if (driver_streq(a, "-fPIE") || driver_streq(a, "-fpie")) {
o->target.pic = KIT_PIC_PIE;
+ o->pic_explicit = 1;
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 = KIT_PIC_NONE;
+ o->pic_explicit = 1;
continue;
}
if (driver_streq(a, "-fvisibility=hidden")) {
@@ -646,6 +659,7 @@ static int build_parse(int argc, char** argv, BuildOptions* o) {
if (driver_streq(a, "-static")) {
o->static_link = 1;
o->target.pic = KIT_PIC_NONE;
+ o->pic_explicit = 1;
o->inputs.cur_link_mode = KIT_LM_STATIC;
continue;
}
@@ -661,11 +675,13 @@ static int build_parse(int argc, char** argv, BuildOptions* o) {
if (driver_streq(a, "-pie")) {
o->target.pic = KIT_PIC_PIE;
o->pie = 1;
+ o->pic_explicit = 1;
continue;
}
if (driver_streq(a, "-no-pie")) {
o->target.pic = KIT_PIC_NONE;
o->pie = 0;
+ o->pic_explicit = 1;
continue;
}
@@ -679,6 +695,16 @@ static int build_parse(int argc, char** argv, BuildOptions* o) {
o->inputs.lib_search_paths[o->inputs.nlib_search_paths++] = dir;
continue;
}
+ if (driver_strneq(a, "-F", 2)) {
+ const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
+ if (!dir) {
+ driver_errf(o->tool, "-F requires an argument");
+ return 1;
+ }
+ o->inputs.framework_search_paths[o->inputs.nframework_search_paths++] =
+ dir;
+ continue;
+ }
if (driver_strneq(a, "-l", 2)) {
const char* name = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
if (!name) {
@@ -701,6 +727,14 @@ static int build_parse(int argc, char** argv, BuildOptions* o) {
}
continue;
}
+ if (driver_streq(a, "-framework")) {
+ if (++i >= argc) {
+ driver_errf(o->tool, "-framework requires an argument");
+ return 1;
+ }
+ if (build_record_framework(o, argv[i]) != 0) return 1;
+ continue;
+ }
if (driver_streq(a, "-e")) {
if (++i >= argc) {
driver_errf(o->tool, "-e requires an argument");
@@ -954,25 +988,47 @@ static int build_parse(int argc, char** argv, BuildOptions* o) {
}
/* Target. */
+ {
+ int dr = driver_darwin_platform_try_consume(
+ o->tool, argc, argv, &i, &o->target, o->pic_explicit);
+ if (dr < 0) return 1;
+ if (dr > 0) continue;
+ }
if (driver_streq(a, "-target") || driver_streq(a, "--target")) {
+ KitTargetSpec t;
+ uint8_t pic;
if (++i >= argc) {
driver_errf(o->tool, "%.*s requires an argument",
KIT_SLICE_ARG(kit_slice_cstr(a)));
return 1;
}
- if (driver_target_from_triple(argv[i], &o->target) != 0) {
+ if (driver_target_from_triple(argv[i], &t) != 0) {
driver_errf(o->tool, "unrecognized target triple: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(argv[i])));
return 1;
}
+ pic = o->target.pic;
+ o->target = t;
+ if (o->pic_explicit)
+ o->target.pic = pic;
+ else
+ o->target.pic = driver_default_pic(o->target.obj, o->target.os);
continue;
}
if (driver_strneq(a, "--target=", 9)) {
- if (driver_target_from_triple(a + 9, &o->target) != 0) {
+ KitTargetSpec t;
+ uint8_t pic;
+ if (driver_target_from_triple(a + 9, &t) != 0) {
driver_errf(o->tool, "unrecognized target triple: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(a + 9)));
return 1;
}
+ pic = o->target.pic;
+ o->target = t;
+ if (o->pic_explicit)
+ o->target.pic = pic;
+ else
+ o->target.pic = driver_default_pic(o->target.obj, o->target.os);
continue;
}
{
@@ -2171,7 +2227,8 @@ static int build_run_per_source(BuildOptions* o, KitCompiler* compiler,
static int build_validate(BuildOptions* o) {
uint32_t total_link = o->inputs.nobject_files + o->inputs.narchives +
- o->inputs.ndsos + o->inputs.npending_libs;
+ o->inputs.ndsos + o->inputs.npending_libs +
+ o->inputs.npending_frameworks;
if (o->nsources == 0 && total_link == 0) {
driver_errf(o->tool, "no input files");
@@ -2202,7 +2259,8 @@ static int build_validate(BuildOptions* o) {
}
if (o->target.obj == KIT_OBJ_WASM) {
uint32_t total_link = o->inputs.nobject_files + o->inputs.narchives +
- o->inputs.ndsos + o->inputs.npending_libs;
+ o->inputs.ndsos + o->inputs.npending_libs +
+ o->inputs.npending_frameworks;
if (total_link) {
driver_errf(o->tool,
"build-exe -target wasm32-none accepts only source files "
@@ -2307,6 +2365,19 @@ static int build_apply_env(BuildOptions* o) {
return 0;
}
+static int build_record_framework(BuildOptions* o, const char* name) {
+ DriverPendingFramework* pf;
+ if (!name || !name[0]) {
+ driver_errf(o->tool, "-framework requires an argument");
+ return 1;
+ }
+ pf = &o->inputs.pending_frameworks[o->inputs.npending_frameworks++];
+ pf->name = name;
+ driver_link_inputs_push(&o->inputs, DRIVER_LINK_FRAMEWORK,
+ o->inputs.npending_frameworks - 1u);
+ return 0;
+}
+
static int build_main(int argc, char** argv, int kind, const char* tool,
const KitDriverExtension* ext) {
DriverEnv env;
@@ -2372,6 +2443,11 @@ static int build_main(int argc, char** argv, int kind, const char* tool,
rc = 1;
goto done;
}
+ if (driver_link_inputs_append_sysroot_framework_dirs(&o.inputs, &o.sysroot,
+ o.target) != 0) {
+ rc = 1;
+ goto done;
+ }
build_enable_hosted_for_sysroot(&o);
build_apply_default_hosted_profile(&o);
if (build_apply_hosted_profile(&o) != 0) {
@@ -2400,8 +2476,8 @@ static int build_main(int argc, char** argv, int kind, const char* tool,
&o.inputs, &rt, o.target, o.hosted.nfinal, o.hosted.nafter);
driver_runtime_archive_fini(&env, &rt);
}
- } else if (o.inputs.npending_libs) {
- driver_errf(tool, "-l is only valid for build-exe");
+ } else if (o.inputs.npending_libs || o.inputs.npending_frameworks) {
+ driver_errf(tool, "-l/-framework are only valid for build-exe");
rc = 1;
goto done;
}
@@ -2494,9 +2570,14 @@ void driver_help_build_exe(void) {
" -o PATH Output (default a.out / a.exe)\n"
" -O0 -O1 -O2 -g Optimization / debug info\n"
" -target TRIPLE Cross-compile target\n"
+ " -arch ARCH Darwin-style target architecture\n"
+ " -platform_version P MIN SDK\n"
+ " Darwin-style target platform\n"
" -flto Link-time optimization for source inputs\n"
" -static Fully static executable\n"
" -l NAME -L DIR Link a library / add a search dir\n"
+ " -framework NAME -F DIR\n"
+ " Link a Darwin framework / add a search dir\n"
" -e SYM -T script.ld Entry symbol / linker script\n"
" -Wl,... Linker pass-through\n"
" --group [flags] -- sources... Scope compile flags to sources\n"
diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c
@@ -33,7 +33,7 @@
* SOURCE_DATE_EPOCH (env)
* -e symbol -T script.ld
* -static -pie -no-pie
- * -l name -L dir
+ * -l name -L dir -framework name -F dir
* -x c|asm|s|asm-cpp|S|wasm|wat
* - (stdin source)
* .c/.cc -> source; .o/.obj -> object inputs; .a -> archive inputs.
@@ -280,6 +280,18 @@ static void cc_options_release(CcOptions* o) {
}
static int cc_apply_hosted_profile(CcOptions* o);
+static int cc_record_framework(CcOptions* o, const char* name) {
+ DriverPendingFramework* pf;
+ if (!name || !name[0]) {
+ driver_errf(CC_TOOL, "-framework requires an argument");
+ return 1;
+ }
+ pf = &o->inputs.pending_frameworks[o->inputs.npending_frameworks++];
+ pf->name = name;
+ driver_link_inputs_push(&o->inputs, DRIVER_LINK_FRAMEWORK,
+ o->inputs.npending_frameworks - 1u);
+ return 0;
+}
/* Routed through the shared driver_path_is_source authority (canonical
* extension registry, headers excluded), so adding a frontend extension reaches
@@ -968,6 +980,10 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
continue;
}
if (driver_strneq(a, "-Wl,", 4)) {
+ if (driver_strneq(a, "-Wl,-framework,", 15)) {
+ if (cc_record_framework(o, a + 15) != 0) return 1;
+ continue;
+ }
if (driver_link_flags_record_wl(&o->link, a + 4) != 0) return 1;
continue;
}
@@ -1083,6 +1099,20 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
driver_errf(CC_TOOL, "-Xlinker requires an argument");
return 1;
}
+ if (driver_streq(argv[i], "-framework")) {
+ if (++i >= argc) {
+ driver_errf(CC_TOOL, "-framework requires an argument");
+ return 1;
+ }
+ if (driver_streq(argv[i], "-Xlinker")) {
+ if (++i >= argc) {
+ driver_errf(CC_TOOL, "-framework requires an argument");
+ return 1;
+ }
+ }
+ if (cc_record_framework(o, argv[i]) != 0) return 1;
+ continue;
+ }
if (driver_link_flags_record_wl(&o->link, argv[i]) != 0) return 1;
continue;
}
@@ -1121,6 +1151,16 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
o->inputs.lib_search_paths[o->inputs.nlib_search_paths++] = dir;
continue;
}
+ if (driver_strneq(a, "-F", 2)) {
+ const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
+ if (!dir) {
+ driver_errf(CC_TOOL, "-F requires an argument");
+ return 1;
+ }
+ o->inputs.framework_search_paths[o->inputs.nframework_search_paths++] =
+ dir;
+ continue;
+ }
if (driver_strneq(a, "-l", 2)) {
const char* name = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
if (!name) {
@@ -1143,6 +1183,14 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
}
continue;
}
+ if (driver_streq(a, "-framework")) {
+ if (++i >= argc) {
+ driver_errf(CC_TOOL, "-framework requires an argument");
+ return 1;
+ }
+ if (cc_record_framework(o, argv[i]) != 0) return 1;
+ continue;
+ }
if (driver_streq(a, "-M")) {
o->dep_mode = CC_DEP_M;
@@ -1207,11 +1255,15 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
if (driver_link_inputs_append_windows_lib_dirs(&o->inputs, &o->sysroot,
o->target) != 0)
return 1;
+ if (driver_link_inputs_append_sysroot_framework_dirs(&o->inputs, &o->sysroot,
+ o->target) != 0)
+ return 1;
{
uint32_t total_sources = o->nsource_files + o->nsource_memory;
uint32_t total_link = o->inputs.nobject_files + o->inputs.narchives +
- o->inputs.ndsos + o->inputs.npending_libs;
+ o->inputs.ndsos + o->inputs.npending_libs +
+ o->inputs.npending_frameworks;
if (total_sources == 0 && total_link == 0) {
driver_errf(CC_TOOL, "no input files");
diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c
@@ -23,9 +23,11 @@
* --support-dir DIR kit support root for compiler rt
* --sysroot DIR / -syslibroot DIR hosted C runtime/sysroot root
* -L dir library search path (-l targets)
+ * -F dir framework search path
* -l c enable hosted CRT/libc expansion
* -l name resolves via -L (.so preferred unless
* -Bstatic/-static)
+ * -framework name resolves via -F / SDK framework dirs
* -static / -pie / -no-pie target.pic
* -shared emit a shared library / dylib
* -r / --relocatable emit a relocatable partial-link object
@@ -118,6 +120,8 @@ typedef struct LdOptions {
const char** lib_dirs; /* -L */
uint32_t nlib_dirs;
+ const char** framework_dirs; /* -F */
+ uint32_t nframework_dirs;
int lib_dirs_precollected; /* first pass collected argv -L / --library-path */
char** owned_paths; /* sysroot-expanded argv/search paths */
size_t* owned_path_sizes;
@@ -293,6 +297,8 @@ static int ld_alloc_arrays(LdOptions* o, int argc) {
o->dsos = driver_alloc_zeroed(o->env, bound * sizeof(*o->dsos));
o->order = driver_alloc_zeroed(o->env, bound * sizeof(*o->order));
o->lib_dirs = driver_alloc_zeroed(o->env, bound * sizeof(*o->lib_dirs));
+ o->framework_dirs =
+ driver_alloc_zeroed(o->env, bound * sizeof(*o->framework_dirs));
o->owned_paths = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_paths));
o->owned_path_sizes =
driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_sizes));
@@ -300,8 +306,8 @@ static int ld_alloc_arrays(LdOptions* o, int argc) {
o->rpaths = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpaths));
o->rpath_links = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpath_links));
if (!o->object_files || !o->archives || !o->dsos || !o->order ||
- !o->lib_dirs || !o->owned_paths || !o->owned_path_sizes || !o->rpaths ||
- !o->rpath_links) {
+ !o->lib_dirs || !o->framework_dirs || !o->owned_paths ||
+ !o->owned_path_sizes || !o->rpaths || !o->rpath_links) {
driver_errf(LD_TOOL, "out of memory");
return 1;
}
@@ -582,6 +588,15 @@ static int ld_add_lib_dir(LdOptions* o, const char* dir) {
return 0;
}
+static int ld_add_framework_dir(LdOptions* o, const char* dir) {
+ if (o->nframework_dirs >= o->argv_bound) {
+ driver_errf(LD_TOOL, "too many framework search paths");
+ return 1;
+ }
+ o->framework_dirs[o->nframework_dirs++] = dir;
+ return 0;
+}
+
static int ld_add_sysroot_libdir(LdOptions* o) {
char* path;
size_t size;
@@ -601,6 +616,28 @@ static int ld_add_sysroot_libdir(LdOptions* o) {
return 0;
}
+static int ld_target_uses_framework_dirs(KitTargetSpec target) {
+ return target.obj == KIT_OBJ_MACHO &&
+ (target.os == KIT_OS_MACOS || target.os == KIT_OS_IOS ||
+ target.os == KIT_OS_IOS_SIMULATOR);
+}
+
+static int ld_add_sysroot_framework_dirs(LdOptions* o) {
+ char* path;
+ size_t size;
+ const char* owned;
+ if (!ld_target_uses_framework_dirs(o->target)) return 0;
+ if (!o->sysroot || !o->sysroot[0]) return 0;
+ path = ld_join2(o->env, o->sysroot, "System/Library/Frameworks", &size);
+ if (ld_own_path(o, path, size, &owned) != 0) return 1;
+ if (ld_add_framework_dir(o, owned) != 0) return 1;
+ path =
+ ld_join2(o->env, o->sysroot, "System/Library/PrivateFrameworks", &size);
+ if (ld_own_path(o, path, size, &owned) != 0) return 1;
+ if (ld_add_framework_dir(o, owned) != 0) return 1;
+ return 0;
+}
+
static int ld_sysroot_rewrite_path(LdOptions* o, const char* path,
const char** out) {
const char* tail;
@@ -701,6 +738,30 @@ static int ld_resolve_library(LdOptions* o, const char* name,
o->nlib_dirs, out_path, out_size, out_kind);
}
+static int ld_resolve_framework(LdOptions* o, const char* name,
+ char** out_path, size_t* out_size) {
+ LibResolveKind kind;
+ return driver_framework_resolve(o->env, name, o->framework_dirs,
+ o->nframework_dirs, out_path, out_size,
+ &kind);
+}
+
+static int ld_push_framework(LdOptions* o, const char* name) {
+ char* resolved;
+ size_t resolved_size;
+ if (!name || !name[0]) {
+ driver_errf(LD_TOOL, "-framework requires an argument");
+ return 1;
+ }
+ if (ld_resolve_framework(o, name, &resolved, &resolved_size) != 0) {
+ driver_errf(LD_TOOL, "cannot find -framework %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(name)));
+ return 1;
+ }
+ ld_push_dso(o, resolved, 1, resolved_size);
+ return 0;
+}
+
static int ld_tok_eq(const char* tok, size_t n, const char* lit) {
size_t ln = driver_strlen(lit);
return n == ln && driver_strneq(tok, lit, ln);
@@ -729,40 +790,6 @@ static int ld_span_to_owned_cstr(LdOptions* o, const char* s, size_t n,
return ld_own_path(o, buf, bytes, out);
}
-static int ld_set_arch(LdOptions* o, const char* arch) {
- KitArchKind k;
- uint8_t ptr_size;
- if (!arch) {
- driver_errf(LD_TOOL, "-arch requires an argument");
- return 1;
- }
- if (driver_streq(arch, "arm64e")) arch = "arm64";
- if (driver_arch_from_name(arch, &k, &ptr_size) != 0) {
- driver_errf(LD_TOOL, "unsupported -arch value: %.*s",
- KIT_SLICE_ARG(kit_slice_cstr(arch)));
- return 1;
- }
- o->target.arch = k;
- o->target.ptr_size = ptr_size;
- o->target.ptr_align = ptr_size;
- return 0;
-}
-
-static void ld_set_platform(LdOptions* o, const char* platform) {
- if (!platform) return;
- if (driver_streq(platform, "macos")) {
- o->target.os = KIT_OS_MACOS;
- o->target.obj = KIT_OBJ_MACHO;
- if (!o->pic_explicit)
- o->target.pic = driver_default_pic(o->target.obj, o->target.os);
- } else if (driver_streq(platform, "linux")) {
- o->target.os = KIT_OS_LINUX;
- o->target.obj = KIT_OBJ_ELF;
- if (!o->pic_explicit)
- o->target.pic = driver_default_pic(o->target.obj, o->target.os);
- }
-}
-
static int ld_set_target_triple(LdOptions* o, const char* triple) {
KitTargetSpec t;
uint8_t pic;
@@ -1026,6 +1053,8 @@ static int ld_parse_wl(LdOptions* o, const char* arg) {
int expect_section_start = 0;
int expect_cref = 0;
int expect_orphan = 0;
+ int expect_framework = 0;
+ int expect_framework_dir = 0;
while (*p) {
const char* tok = p;
size_t n = 0;
@@ -1034,11 +1063,20 @@ static int ld_parse_wl(LdOptions* o, const char* arg) {
if (expect_rpath || expect_soname || expect_interp || expect_z ||
expect_emulation || expect_map || expect_symbols || expect_defsym ||
- expect_section_start || expect_cref || expect_orphan) {
+ expect_section_start || expect_cref || expect_orphan ||
+ expect_framework || expect_framework_dir) {
if (expect_defsym) {
if (ld_record_defsym(o, tok, n) != 0) return 1;
} else if (expect_section_start) {
if (ld_record_section_start(o, tok, n) != 0) return 1;
+ } else if (expect_framework) {
+ const char* owned;
+ if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1;
+ if (ld_push_framework(o, owned) != 0) return 1;
+ } else if (expect_framework_dir) {
+ const char* owned;
+ if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1;
+ if (ld_add_framework_dir(o, owned) != 0) return 1;
} else if (expect_cref) {
const char* owned;
if (n == 0) {
@@ -1086,7 +1124,8 @@ static int ld_parse_wl(LdOptions* o, const char* arg) {
}
expect_rpath = expect_soname = expect_interp = expect_z =
expect_emulation = expect_map = expect_symbols = expect_defsym =
- expect_section_start = expect_cref = expect_orphan = 0;
+ expect_section_start = expect_cref = expect_orphan =
+ expect_framework = expect_framework_dir = 0;
continue;
}
@@ -1226,6 +1265,20 @@ static int ld_parse_wl(LdOptions* o, const char* arg) {
expect_rpath = 1;
continue;
}
+ if (ld_tok_eq(tok, n, "-framework")) {
+ expect_framework = 1;
+ continue;
+ }
+ if (ld_tok_eq(tok, n, "-F")) {
+ expect_framework_dir = 1;
+ continue;
+ }
+ if (n > 2 && tok[0] == '-' && tok[1] == 'F') {
+ const char* owned;
+ if (ld_span_to_owned_cstr(o, tok + 2, n - 2u, &owned) != 0) return 1;
+ if (ld_add_framework_dir(o, owned) != 0) return 1;
+ continue;
+ }
if (ld_tok_prefix(tok, n, "-rpath=")) {
const char* owned;
if (ld_span_to_owned_cstr(o, tok + 7, n - 7u, &owned) != 0) return 1;
@@ -1333,7 +1386,8 @@ static int ld_parse_wl(LdOptions* o, const char* arg) {
}
if (expect_rpath || expect_soname || expect_interp || expect_z ||
expect_emulation || expect_map || expect_symbols || expect_defsym ||
- expect_section_start || expect_cref || expect_orphan) {
+ expect_section_start || expect_cref || expect_orphan ||
+ expect_framework || expect_framework_dir) {
driver_errf(LD_TOOL, "-Wl option requires another comma argument");
return 1;
}
@@ -2091,6 +2145,12 @@ static int ld_parse(int argc, char** argv, LdOptions* o) {
if (ld_set_target_triple(o, argv[i] + 9) != 0) return 1;
continue;
}
+ {
+ int dr = driver_darwin_platform_try_consume(
+ LD_TOOL, argc, argv, &i, &o->target, o->pic_explicit);
+ if (dr < 0) return 1;
+ if (dr > 0) continue;
+ }
if (ld_is_cc_driver_noop(argv[i], driver_strlen(argv[i]))) {
continue;
}
@@ -2172,9 +2232,21 @@ static int ld_parse(int argc, char** argv, LdOptions* o) {
if (ld_add_lib_dir(o, rewritten) != 0) return 1;
continue;
}
+ if (driver_strneq(a, "-F", 2)) {
+ const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
+ const char* rewritten;
+ if (!dir) {
+ driver_errf(LD_TOOL, "-F requires an argument");
+ return 1;
+ }
+ if (ld_sysroot_rewrite_path(o, dir, &rewritten) != 0) return 1;
+ if (ld_add_framework_dir(o, rewritten) != 0) return 1;
+ continue;
+ }
}
o->lib_dirs_precollected = 1;
if (ld_add_sysroot_libdir(o) != 0) return 1;
+ if (ld_add_sysroot_framework_dirs(o) != 0) return 1;
/* Pre-populate the hosted lib dirs so user -l flags are resolved
* against the correct sysroot even when they appear before -lc. */
if (has_lc && !o->explicit_crt_start) {
@@ -2290,33 +2362,11 @@ static int ld_parse(int argc, char** argv, LdOptions* o) {
if (ld_parse_pe_subsystem(o, argv[i]) != 0) return 1;
continue;
}
- if (driver_streq(a, "-arch")) {
- if (++i >= argc) {
- driver_errf(LD_TOOL, "-arch requires an argument");
- return 1;
- }
- if (ld_set_arch(o, argv[i]) != 0) return 1;
- continue;
- }
- if (driver_streq(a, "-platform_version")) {
- if (i + 3 >= argc) {
- driver_errf(LD_TOOL, "-platform_version requires three arguments");
- return 1;
- }
- ld_set_platform(o, argv[i + 1]);
- i += 3;
- continue;
- }
- if (driver_streq(a, "-macosx_version_min")) {
- if (++i >= argc) {
- driver_errf(LD_TOOL, "-macosx_version_min requires an argument");
- return 1;
- }
- o->target.os = KIT_OS_MACOS;
- o->target.obj = KIT_OBJ_MACHO;
- if (!o->pic_explicit)
- o->target.pic = driver_default_pic(o->target.obj, o->target.os);
- continue;
+ {
+ int dr = driver_darwin_platform_try_consume(
+ LD_TOOL, argc, argv, &i, &o->target, o->pic_explicit);
+ if (dr < 0) return 1;
+ if (dr > 0) continue;
}
if (driver_streq(a, "-target") || driver_streq(a, "--target")) {
if (++i >= argc) {
@@ -2576,6 +2626,19 @@ static int ld_parse(int argc, char** argv, LdOptions* o) {
}
continue;
}
+ if (driver_strneq(a, "-F", 2)) {
+ const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
+ const char* rewritten;
+ if (!dir) {
+ driver_errf(LD_TOOL, "-F requires an argument");
+ return 1;
+ }
+ if (!o->lib_dirs_precollected) {
+ if (ld_sysroot_rewrite_path(o, dir, &rewritten) != 0) return 1;
+ if (ld_add_framework_dir(o, rewritten) != 0) return 1;
+ }
+ continue;
+ }
if (driver_strneq(a, "-l", 2)) {
const char* name = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
@@ -2660,6 +2723,14 @@ static int ld_parse(int argc, char** argv, LdOptions* o) {
}
continue;
}
+ if (driver_streq(a, "-framework")) {
+ if (++i >= argc) {
+ driver_errf(LD_TOOL, "-framework requires an argument");
+ return 1;
+ }
+ if (ld_push_framework(o, argv[i]) != 0) return 1;
+ continue;
+ }
if (driver_streq(a, "-static")) {
o->target.pic = KIT_PIC_NONE;
@@ -3038,6 +3109,7 @@ static void ld_options_release(LdOptions* o) {
driver_free(o->env, o->dsos, bound * sizeof(*o->dsos));
driver_free(o->env, o->order, bound * sizeof(*o->order));
driver_free(o->env, o->lib_dirs, bound * sizeof(*o->lib_dirs));
+ driver_free(o->env, o->framework_dirs, bound * sizeof(*o->framework_dirs));
/* owned_paths/owned_path_sizes grow independently of argv_bound. */
driver_free(o->env, o->owned_paths,
o->cap_owned_paths * sizeof(*o->owned_paths));
diff --git a/driver/driver.h b/driver/driver.h
@@ -145,10 +145,11 @@ int driver_argv_wants_help(int argc, char** argv, int accept_short_h);
* KitTargetSpec. Recognized arches: x86_64/amd64, i386/i486/i586/i686, aarch64/
* arm64, arm/armv7, riscv64, riscv32, wasm32, wasm64. Recognized OSes (scanned
* across the remaining components, so vendor tokens like `pc`/`apple`/
- * `unknown` are skipped): linux, darwin/macos, windows/win32, wasi, none/
- * freestanding. Sets arch/os/obj/ptr_size/ptr_align/big_endian; pic and
- * code_model are left at their defaults. Returns 0 on success, nonzero on
- * unrecognized arch or NULL inputs. */
+ * `unknown` are skipped): linux, darwin/macos, ios/iphoneos,
+ * iphonesimulator/ios-simulator, windows/win32, wasi, none/freestanding. Sets
+ * arch/os/obj/ptr_size/ptr_align/big_endian; pic and code_model are left at
+ * their defaults. Returns 0 on success, nonzero on unrecognized arch or NULL
+ * inputs. */
int driver_target_from_triple(const char* triple, KitTargetSpec* out);
/* Render a canonical driver target triple into `buf`. Returns 0 on success,
@@ -222,6 +223,13 @@ void driver_target_features_fini(DriverTargetFeatures*, DriverEnv*);
int driver_target_features_try_consume(DriverTargetFeatures*, DriverEnv*,
const char* tool, int argc, char** argv,
int* i);
+/* Consume Darwin linker target spellings shared by ld/build-exe:
+ * -arch, -platform_version, and the *_version_min compatibility flags.
+ * Returns 1 when a flag was consumed, 0 when argv[*i] is unrelated, and -1
+ * after emitting a diagnostic. */
+int driver_darwin_platform_try_consume(const char* tool, int argc, char** argv,
+ int* i, KitTargetSpec* target,
+ int pic_explicit);
int driver_target_options(const DriverTargetFeatures*, const char* tool,
KitTargetSpec, KitTargetOptions* out);
KitStatus driver_target_new(const KitContext*, KitTargetSpec,
diff --git a/driver/env/macos.c b/driver/env/macos.c
@@ -179,16 +179,35 @@ int driver_self_exe_path(DriverEnv* env, char** out, size_t* out_size) {
* forbids calling into it from the env TU). */
int driver_default_hosted_dirs(DriverEnv* env, KitTargetSpec target,
DriverHostedDirs* out) {
- static const char* const candidates[] = {
+ static const char* const macos_candidates[] = {
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk",
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/"
"Developer/SDKs/MacOSX.sdk",
};
+ static const char* const ios_candidates[] = {
+ "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/"
+ "Developer/SDKs/iPhoneOS.sdk",
+ };
+ static const char* const ios_sim_candidates[] = {
+ "/Applications/Xcode.app/Contents/Developer/Platforms/"
+ "iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk",
+ };
+ const char* const* candidates = macos_candidates;
+ size_t ncandidates = sizeof(macos_candidates) / sizeof(macos_candidates[0]);
const char* sdk = NULL;
size_t i;
(void)env;
- if (target.os != KIT_OS_MACOS) return 1;
- for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) {
+ if (target.os == KIT_OS_IOS) {
+ candidates = ios_candidates;
+ ncandidates = sizeof(ios_candidates) / sizeof(ios_candidates[0]);
+ } else if (target.os == KIT_OS_IOS_SIMULATOR) {
+ candidates = ios_sim_candidates;
+ ncandidates =
+ sizeof(ios_sim_candidates) / sizeof(ios_sim_candidates[0]);
+ } else if (target.os != KIT_OS_MACOS) {
+ return 1;
+ }
+ for (i = 0; i < ncandidates; ++i) {
if (driver_path_exists(candidates[i])) {
sdk = candidates[i];
break;
diff --git a/driver/lib/hosted.c b/driver/lib/hosted.c
@@ -76,6 +76,10 @@ int driver_hosted_dirs_resolve(const DriverHostedRequest* req,
KitTargetSpec host = driver_host_target();
if (req->target.os == host.os && req->target.arch == host.arch)
(void)driver_default_hosted_dirs(req->env, req->target, out);
+ else if (host.os == KIT_OS_MACOS &&
+ (req->target.os == KIT_OS_IOS ||
+ req->target.os == KIT_OS_IOS_SIMULATOR))
+ (void)driver_default_hosted_dirs(req->env, req->target, out);
}
return 0;
}
diff --git a/driver/lib/lib_resolve.c b/driver/lib/lib_resolve.c
@@ -102,3 +102,68 @@ int driver_lib_resolve(DriverEnv* env, const char* name, LibResolveMode mode,
search_dirs, nsearch_dirs, out_path,
out_size, out_kind);
}
+
+static char* compose_framework_path(DriverEnv* env, const char* dir,
+ const char* name, const char* leaf_suffix,
+ size_t* out_size) {
+ size_t nlen = driver_strlen(name);
+ size_t slen = driver_strlen(leaf_suffix);
+ size_t fw_leaf_size = nlen + 10u + 1u; /* ".framework" + NUL */
+ size_t bin_leaf_size = nlen + slen + 1u;
+ char* fw_leaf = driver_alloc(env, fw_leaf_size);
+ char* bin_leaf = driver_alloc(env, bin_leaf_size);
+ char* fw_dir = NULL;
+ size_t fw_dir_size = 0;
+ char* out = NULL;
+ if (!fw_leaf || !bin_leaf) goto out;
+
+ driver_memcpy(fw_leaf, name, nlen);
+ driver_memcpy(fw_leaf + nlen, ".framework", 10u);
+ fw_leaf[nlen + 10u] = '\0';
+
+ driver_memcpy(bin_leaf, name, nlen);
+ if (slen) driver_memcpy(bin_leaf + nlen, leaf_suffix, slen);
+ bin_leaf[nlen + slen] = '\0';
+
+ fw_dir = driver_path_join(env, dir, fw_leaf, &fw_dir_size);
+ if (!fw_dir) goto out;
+ out = driver_path_join(env, fw_dir, bin_leaf, out_size);
+
+out:
+ if (fw_dir) driver_free(env, fw_dir, fw_dir_size);
+ if (fw_leaf) driver_free(env, fw_leaf, fw_leaf_size);
+ if (bin_leaf) driver_free(env, bin_leaf, bin_leaf_size);
+ return out;
+}
+
+int driver_framework_resolve(DriverEnv* env, const char* name,
+ const char* const* search_dirs,
+ uint32_t nsearch_dirs, char** out_path,
+ size_t* out_size, LibResolveKind* out_kind) {
+ uint32_t i;
+ if (!env || !name || !out_path || !out_size) return 1;
+ for (i = 0; i < nsearch_dirs; ++i) {
+ size_t size = 0;
+ char* cand = compose_framework_path(env, search_dirs[i], name, ".tbd",
+ &size);
+ if (!cand) return 1;
+ if (driver_path_exists(cand)) {
+ *out_path = cand;
+ *out_size = size;
+ if (out_kind) *out_kind = LIB_RESOLVE_KIND_TBD;
+ return 0;
+ }
+ driver_free(env, cand, size);
+
+ cand = compose_framework_path(env, search_dirs[i], name, "", &size);
+ if (!cand) return 1;
+ if (driver_path_exists(cand)) {
+ *out_path = cand;
+ *out_size = size;
+ if (out_kind) *out_kind = LIB_RESOLVE_KIND_SHARED;
+ return 0;
+ }
+ driver_free(env, cand, size);
+ }
+ return 1;
+}
diff --git a/driver/lib/lib_resolve.h b/driver/lib/lib_resolve.h
@@ -65,4 +65,17 @@ int driver_lib_resolve_for_os(DriverEnv* env, const char* name,
uint32_t nsearch_dirs, char** out_path,
size_t* out_size, LibResolveKind* out_kind);
+/* Resolve an Apple framework name against `-F`-style search directories.
+ * Probes:
+ * <dir>/<name>.framework/<name>.tbd
+ * <dir>/<name>.framework/<name>
+ *
+ * On success, returns 0 and writes a heap-allocated path. Frameworks are
+ * always routed as DSO inputs; out_kind is TBD for the SDK text stub form and
+ * SHARED for the real framework binary form. */
+int driver_framework_resolve(DriverEnv* env, const char* name,
+ const char* const* search_dirs,
+ uint32_t nsearch_dirs, char** out_path,
+ size_t* out_size, LibResolveKind* out_kind);
+
#endif
diff --git a/driver/lib/link_inputs.c b/driver/lib/link_inputs.c
@@ -16,11 +16,16 @@ int driver_link_inputs_init(DriverLinkInputSet* set, DriverEnv* env,
set->dsos = driver_alloc_zeroed(env, bound * sizeof(*set->dsos));
set->lib_search_paths =
driver_alloc_zeroed(env, bound * sizeof(*set->lib_search_paths));
+ set->framework_search_paths =
+ driver_alloc_zeroed(env, bound * sizeof(*set->framework_search_paths));
set->pending_libs =
driver_alloc_zeroed(env, bound * sizeof(*set->pending_libs));
+ set->pending_frameworks =
+ driver_alloc_zeroed(env, bound * sizeof(*set->pending_frameworks));
set->link_items = driver_alloc_zeroed(env, bound * sizeof(*set->link_items));
if (!set->object_files || !set->archives || !set->dsos ||
- !set->lib_search_paths || !set->pending_libs || !set->link_items) {
+ !set->lib_search_paths || !set->framework_search_paths ||
+ !set->pending_libs || !set->pending_frameworks || !set->link_items) {
driver_errf(tool, "out of memory");
return 1;
}
@@ -42,6 +47,10 @@ void driver_link_inputs_fini(DriverLinkInputSet* set) {
if (set->owned_sysroot_lib_dir)
driver_free(env, set->owned_sysroot_lib_dir,
set->owned_sysroot_lib_dir_size);
+ for (i = 0; i < set->nowned_sysroot_framework_dirs; ++i)
+ if (set->owned_sysroot_framework_dirs[i])
+ driver_free(env, set->owned_sysroot_framework_dirs[i],
+ set->owned_sysroot_framework_dir_sizes[i]);
if (set->object_files)
driver_free(env, set->object_files, bound * sizeof(*set->object_files));
if (set->archives)
@@ -50,8 +59,14 @@ void driver_link_inputs_fini(DriverLinkInputSet* set) {
if (set->lib_search_paths)
driver_free(env, set->lib_search_paths,
bound * sizeof(*set->lib_search_paths));
+ if (set->framework_search_paths)
+ driver_free(env, set->framework_search_paths,
+ bound * sizeof(*set->framework_search_paths));
if (set->pending_libs)
driver_free(env, set->pending_libs, bound * sizeof(*set->pending_libs));
+ if (set->pending_frameworks)
+ driver_free(env, set->pending_frameworks,
+ bound * sizeof(*set->pending_frameworks));
if (set->link_items)
driver_free(env, set->link_items, bound * sizeof(*set->link_items));
}
@@ -264,6 +279,14 @@ uint32_t driver_link_inputs_build_order(const DriverLinkInputSet* set,
}
break;
}
+ case DRIVER_LINK_FRAMEWORK: {
+ const DriverPendingFramework* pf =
+ &set->pending_frameworks[item->index];
+ ord = &order[norder++];
+ ord->kind = KIT_LINK_INPUT_DSO;
+ ord->index = pf->resolved_index;
+ break;
+ }
}
}
return norder;
@@ -309,6 +332,28 @@ int driver_link_inputs_resolve_pending(DriverLinkInputSet* set,
pl->resolved_index = set->narchives - 1u;
}
}
+ for (i = 0; i < set->npending_frameworks; ++i) {
+ DriverPendingFramework* pf = &set->pending_frameworks[i];
+ char* p;
+ size_t sz;
+ LibResolveKind kind;
+ if (driver_framework_resolve(set->env, pf->name,
+ set->framework_search_paths,
+ set->nframework_search_paths, &p, &sz,
+ &kind) != 0) {
+ driver_errf(set->tool, "framework not found: -framework %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(pf->name)));
+ return 1;
+ }
+ (void)kind;
+ {
+ DriverDsoInput* d = &set->dsos[set->ndsos++];
+ d->path = p;
+ d->owned = 1;
+ d->owned_size = sz;
+ pf->resolved_index = set->ndsos - 1u;
+ }
+ }
return 0;
}
@@ -356,6 +401,51 @@ int driver_link_inputs_append_windows_lib_dirs(DriverLinkInputSet* set,
return 0;
}
+static int driver_target_uses_framework_dirs(KitTargetSpec target) {
+ return target.obj == KIT_OBJ_MACHO &&
+ (target.os == KIT_OS_MACOS || target.os == KIT_OS_IOS ||
+ target.os == KIT_OS_IOS_SIMULATOR);
+}
+
+static int driver_link_inputs_add_owned_framework_dir(DriverLinkInputSet* set,
+ const char* sysroot,
+ const char* sub) {
+ char* joined;
+ size_t size;
+ if (set->nowned_sysroot_framework_dirs >= 2u) return 0;
+ joined = driver_path_join(set->env, sysroot, sub, &size);
+ if (!joined) {
+ driver_errf(set->tool, "out of memory");
+ return 1;
+ }
+ set->owned_sysroot_framework_dirs[set->nowned_sysroot_framework_dirs] =
+ joined;
+ set->owned_sysroot_framework_dir_sizes[set->nowned_sysroot_framework_dirs] =
+ size;
+ set->nowned_sysroot_framework_dirs++;
+ set->framework_search_paths[set->nframework_search_paths++] = joined;
+ return 0;
+}
+
+int driver_link_inputs_append_sysroot_framework_dirs(DriverLinkInputSet* set,
+ const char** sysroot,
+ KitTargetSpec target) {
+ const char* sr = *sysroot;
+ if (!driver_target_uses_framework_dirs(target)) return 0;
+ if (!sr || !sr[0]) {
+ sr = driver_getenv("KIT_SYSROOT");
+ if (!sr || !sr[0]) return 0;
+ *sysroot = sr;
+ }
+ if (driver_link_inputs_add_owned_framework_dir(
+ set, sr, "System/Library/Frameworks") != 0)
+ return 1;
+ if (driver_link_inputs_add_owned_framework_dir(
+ set, sr, "System/Library/PrivateFrameworks") != 0)
+ return 1;
+ return 0;
+}
+
int driver_link_inputs_apply_hosted(DriverLinkInputSet* set,
DriverHostedPlan* plan, DriverCflags* cf,
DriverLinkFlags* link, KitTargetSpec target,
diff --git a/driver/lib/link_inputs.h b/driver/lib/link_inputs.h
@@ -32,6 +32,7 @@ typedef enum DriverLinkKind {
DRIVER_LINK_ARCHIVE, /* index into archives */
DRIVER_LINK_DSO, /* index into dsos */
DRIVER_LINK_LIB, /* index into pending_libs (resolved later) */
+ DRIVER_LINK_FRAMEWORK, /* index into pending_frameworks */
} DriverLinkKind;
typedef struct DriverLinkItem {
@@ -65,6 +66,11 @@ typedef struct DriverPendingLib {
uint32_t resolved_index;
} DriverPendingLib;
+typedef struct DriverPendingFramework {
+ const char* name;
+ uint32_t resolved_index;
+} DriverPendingFramework;
+
typedef struct DriverLinkInputSet {
DriverEnv* env;
const char* tool;
@@ -79,8 +85,12 @@ typedef struct DriverLinkInputSet {
uint32_t ndsos;
const char** lib_search_paths; /* -L dirs (+ hosted/sysroot dirs) */
uint32_t nlib_search_paths;
+ const char** framework_search_paths; /* -F dirs (+ SDK framework dirs) */
+ uint32_t nframework_search_paths;
DriverPendingLib* pending_libs; /* -l names, resolved at end-of-parse */
uint32_t npending_libs;
+ DriverPendingFramework* pending_frameworks; /* -framework names */
+ uint32_t npending_frameworks;
DriverLinkItem* link_items; /* ordered link sequence */
uint32_t nlink_items;
@@ -94,6 +104,9 @@ typedef struct DriverLinkInputSet {
* in effect (see driver_link_inputs_append_windows_lib_dirs). */
char* owned_sysroot_lib_dir;
size_t owned_sysroot_lib_dir_size;
+ char* owned_sysroot_framework_dirs[2];
+ size_t owned_sysroot_framework_dir_sizes[2];
+ uint32_t nowned_sysroot_framework_dirs;
} DriverLinkInputSet;
/* Allocate the parallel arrays (each sized `bound`) and seed defaults
@@ -168,6 +181,14 @@ int driver_link_inputs_append_windows_lib_dirs(DriverLinkInputSet* set,
const char** sysroot,
KitTargetSpec target);
+/* For Mach-O SDK targets, append `<sysroot>/System/Library/Frameworks` and
+ * `<sysroot>/System/Library/PrivateFrameworks` to the framework search path.
+ * The sysroot is `*sysroot` if set, else KIT_SYSROOT (which is written back
+ * through `*sysroot`). No-op for non-Apple-Mach-O targets or no sysroot. */
+int driver_link_inputs_append_sysroot_framework_dirs(DriverLinkInputSet* set,
+ const char** sysroot,
+ KitTargetSpec target);
+
/* Apply a hosted libc profile: resolve `plan` for the target/sysroot, add its
* system includes + defines to `cf`, add its lib search dirs to the set, and
* (when `link_action`) insert its crt inputs and adopt its interpreter into
diff --git a/driver/lib/runtime.c b/driver/lib/runtime.c
@@ -218,6 +218,10 @@ static const RuntimeVariant kRtVariants[] = {
"lib/include/lp64_le", 1, 0, kRtSrcX64,
(uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0])),
KIT_FLOAT_ABI_DEFAULT, NULL, NULL},
+ {"x86_64-apple-ios-simulator", KIT_ARCH_X86_64, KIT_OS_IOS_SIMULATOR,
+ KIT_OBJ_MACHO, 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcX64,
+ (uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0])),
+ KIT_FLOAT_ABI_DEFAULT, NULL, NULL},
{"x86_64-elf", KIT_ARCH_X86_64, KIT_OS_FREESTANDING, KIT_OBJ_ELF, 8, 8,
"lib/include/lp64_le", 1, 0, kRtSrcX64,
(uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0])),
@@ -242,6 +246,14 @@ static const RuntimeVariant kRtVariants[] = {
"lib/include/lp64_le", 1, 0, kRtSrcAarch64Darwin,
(uint32_t)(sizeof(kRtSrcAarch64Darwin) / sizeof(kRtSrcAarch64Darwin[0])),
KIT_FLOAT_ABI_DEFAULT, NULL, NULL},
+ {"aarch64-apple-ios", KIT_ARCH_ARM_64, KIT_OS_IOS, KIT_OBJ_MACHO, 8, 8,
+ "lib/include/lp64_le", 1, 0, kRtSrcAarch64Darwin,
+ (uint32_t)(sizeof(kRtSrcAarch64Darwin) / sizeof(kRtSrcAarch64Darwin[0])),
+ KIT_FLOAT_ABI_DEFAULT, NULL, NULL},
+ {"aarch64-apple-ios-simulator", KIT_ARCH_ARM_64, KIT_OS_IOS_SIMULATOR,
+ KIT_OBJ_MACHO, 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcAarch64Darwin,
+ (uint32_t)(sizeof(kRtSrcAarch64Darwin) / sizeof(kRtSrcAarch64Darwin[0])),
+ KIT_FLOAT_ABI_DEFAULT, NULL, NULL},
{"aarch64-windows", KIT_ARCH_ARM_64, KIT_OS_WINDOWS, KIT_OBJ_COFF, 8, 8,
"lib/include/llp64_le", 1, 0, kRtSrcAarch64Windows,
(uint32_t)(sizeof(kRtSrcAarch64Windows) / sizeof(kRtSrcAarch64Windows[0])),
diff --git a/driver/lib/target.c b/driver/lib/target.c
@@ -262,6 +262,99 @@ int driver_target_features_try_consume(DriverTargetFeatures* tf, DriverEnv* env,
return 0;
}
+static int darwin_platform_set_arch(const char* tool, KitTargetSpec* target,
+ const char* arch) {
+ KitArchKind k;
+ uint8_t ptr_size;
+ if (!arch) {
+ driver_errf(tool, "-arch requires an argument");
+ return 1;
+ }
+ if (driver_streq(arch, "arm64e")) arch = "arm64";
+ if (driver_arch_from_name(arch, &k, &ptr_size) != 0) {
+ driver_errf(tool, "unsupported -arch value: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(arch)));
+ return 1;
+ }
+ target->arch = k;
+ target->ptr_size = ptr_size;
+ target->ptr_align = ptr_size;
+ return 0;
+}
+
+static void darwin_platform_set_os(KitTargetSpec* target, const char* platform,
+ int pic_explicit) {
+ if (!platform) return;
+ if (driver_streq(platform, "macos")) {
+ target->os = KIT_OS_MACOS;
+ target->obj = KIT_OBJ_MACHO;
+ } else if (driver_streq(platform, "ios")) {
+ target->os = KIT_OS_IOS;
+ target->obj = KIT_OBJ_MACHO;
+ } else if (driver_streq(platform, "ios-simulator") ||
+ driver_streq(platform, "iossimulator")) {
+ target->os = KIT_OS_IOS_SIMULATOR;
+ target->obj = KIT_OBJ_MACHO;
+ } else if (driver_streq(platform, "linux")) {
+ target->os = KIT_OS_LINUX;
+ target->obj = KIT_OBJ_ELF;
+ } else {
+ return;
+ }
+ if (!pic_explicit) target->pic = driver_default_pic(target->obj, target->os);
+}
+
+int driver_darwin_platform_try_consume(const char* tool, int argc, char** argv,
+ int* i, KitTargetSpec* target,
+ int pic_explicit) {
+ const char* a;
+ if (!argv || !i || !target || *i < 0 || *i >= argc) return 0;
+ a = argv[*i];
+ if (driver_streq(a, "-arch")) {
+ if (++(*i) >= argc) {
+ driver_errf(tool, "-arch requires an argument");
+ return -1;
+ }
+ return darwin_platform_set_arch(tool, target, argv[*i]) == 0 ? 1 : -1;
+ }
+ if (driver_streq(a, "-platform_version")) {
+ if (*i + 3 >= argc) {
+ driver_errf(tool, "-platform_version requires three arguments");
+ return -1;
+ }
+ darwin_platform_set_os(target, argv[*i + 1], pic_explicit);
+ *i += 3;
+ return 1;
+ }
+ if (driver_streq(a, "-macosx_version_min")) {
+ if (++(*i) >= argc) {
+ driver_errf(tool, "-macosx_version_min requires an argument");
+ return -1;
+ }
+ darwin_platform_set_os(target, "macos", pic_explicit);
+ return 1;
+ }
+ if (driver_streq(a, "-ios_version_min") ||
+ driver_streq(a, "-iphoneos_version_min")) {
+ if (++(*i) >= argc) {
+ driver_errf(tool, "%s requires an argument", a);
+ return -1;
+ }
+ darwin_platform_set_os(target, "ios", pic_explicit);
+ return 1;
+ }
+ if (driver_streq(a, "-ios_simulator_version_min") ||
+ driver_streq(a, "-iphonesimulator_version_min")) {
+ if (++(*i) >= argc) {
+ driver_errf(tool, "%s requires an argument", a);
+ return -1;
+ }
+ darwin_platform_set_os(target, "ios-simulator", pic_explicit);
+ return 1;
+ }
+ return 0;
+}
+
int driver_target_options(const DriverTargetFeatures* tf, const char* tool,
KitTargetSpec target, KitTargetOptions* out) {
(void)tool;
diff --git a/include/kit/core.h b/include/kit/core.h
@@ -142,6 +142,8 @@ typedef enum KitOSKind {
KIT_OS_FREESTANDING,
KIT_OS_LINUX,
KIT_OS_MACOS,
+ KIT_OS_IOS,
+ KIT_OS_IOS_SIMULATOR,
KIT_OS_WINDOWS,
KIT_OS_FREEBSD,
KIT_OS_WASI,
diff --git a/include/kit/os.h b/include/kit/os.h
@@ -42,7 +42,7 @@ typedef struct KitOsHostedInput {
#define KIT_OS_HOSTED_MAX_AFTER 16
#define KIT_OS_HOSTED_MAX_FINAL 2
#define KIT_OS_HOSTED_MAX_INCLUDES 4
-#define KIT_OS_HOSTED_MAX_DEFINES 20
+#define KIT_OS_HOSTED_MAX_DEFINES 32
#define KIT_OS_HOSTED_MAX_LIB_SEARCH_DIRS 8
#define KIT_OS_HOSTED_MAX_INCDIRS 4
#define KIT_OS_HOSTED_MAX_LIBDIRS 8
diff --git a/include/kit/target.h b/include/kit/target.h
@@ -41,10 +41,11 @@ KIT_API bool kit_arch_from_name(const char* name, KitArchKind* arch_out,
/* Parse a target triple string (`<arch>[-<vendor>]-<os>[-<env>]`) into a
* KitTargetSpec. Recognized arches: see kit_arch_from_name. Recognized OSes are
* scanned across the remaining components (so vendor tokens like
- * pc/apple/unknown are skipped): linux, darwin/macos, windows/win32,
- * freebsd[<ver>], wasi, none/freestanding. An unrecognized or absent OS token
- * resolves to a freestanding ELF/WASM target by design (covers bare-metal /
- * vendor-only triples like "riscv64-unknown-elf").
+ * pc/apple/unknown are skipped): linux, darwin/macos, ios/iphoneos,
+ * iphonesimulator/ios-simulator, windows/win32, freebsd[<ver>], wasi,
+ * none/freestanding. An unrecognized or absent OS token resolves to a
+ * freestanding ELF/WASM target by design (covers bare-metal / vendor-only
+ * triples like "riscv64-unknown-elf").
*
* Sets arch/os/obj/ptr_size/ptr_align/big_endian/os_version_major, and derives
* pic via kit_target_default_pic; code_model is left at KIT_CM_DEFAULT. The
diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c
@@ -114,10 +114,31 @@ int starts_attr(const Parser* p) {
return p->cur.kind == TOK_IDENT && tok_ident(&p->cur) == p->sym_attribute;
}
+static void skip_balanced_parens(Parser* p);
+
+static int slice_has_prefix(KitSlice s, KitSlice prefix) {
+ return s.len >= prefix.len && memcmp(s.s, prefix.s, prefix.len) == 0;
+}
+
+static int starts_darwin_availability(const Parser* p) {
+ KitSlice s;
+ if (p->cur.kind != TOK_IDENT) return 0;
+ s = kit_sym_str(p->pool->c, tok_ident(&p->cur));
+ return slice_has_prefix(s, KIT_SLICE_LIT("__AVAILABILITY")) ||
+ slice_has_prefix(s, KIT_SLICE_LIT("__API_AVAILABLE")) ||
+ slice_has_prefix(s, KIT_SLICE_LIT("__API_DEPRECATED")) ||
+ slice_has_prefix(s, KIT_SLICE_LIT("__API_UNAVAILABLE"));
+}
+
static int starts_asm_label(const Parser* p) {
return is_kw(p, &p->cur, KW_ASM) || is_kw(p, &p->cur, KW_BUILTIN_ASM);
}
+static void parse_and_discard_darwin_availability(Parser* p) {
+ advance(p);
+ if (is_punct(&p->cur, '(')) skip_balanced_parens(p);
+}
+
static Sym parse_asm_label(Parser* p) {
Sym label = 0;
advance(p); /* asm / __asm / __asm__ */
@@ -161,6 +182,10 @@ static void parse_attrs_and_asm_into(Parser* p, Attr** attrs_out,
if (asm_label_out) *asm_label_out = label;
continue;
}
+ if (starts_darwin_availability(p)) {
+ parse_and_discard_darwin_availability(p);
+ continue;
+ }
break;
}
}
@@ -175,6 +200,10 @@ static void parse_and_discard_attrs_or_asm(Parser* p) {
(void)parse_asm_label(p);
continue;
}
+ if (starts_darwin_availability(p)) {
+ parse_and_discard_darwin_availability(p);
+ continue;
+ }
break;
}
}
diff --git a/lang/cpp/pp/pp.c b/lang/cpp/pp/pp.c
@@ -829,6 +829,8 @@ static void pp_register_os_predefined(Pp* pp, KitTargetSpec target) {
break;
case KIT_OS_LINUX:
case KIT_OS_MACOS:
+ case KIT_OS_IOS:
+ case KIT_OS_IOS_SIMULATOR:
case KIT_OS_FREEBSD:
case KIT_OS_FREESTANDING:
case KIT_OS_WASI:
diff --git a/mk/rt.mk b/mk/rt.mk
@@ -16,10 +16,13 @@ RT_VARIANTS = \
x86_64-linux \
x86_64-freebsd \
x86_64-apple-darwin \
+ x86_64-apple-ios-simulator \
x86_64-elf \
aarch64-linux \
aarch64-freebsd \
aarch64-apple-darwin \
+ aarch64-apple-ios \
+ aarch64-apple-ios-simulator \
aarch64-elf \
riscv64-linux \
riscv64-freebsd \
@@ -97,6 +100,11 @@ RT_x86_64-apple-darwin_ABI = lp64
RT_x86_64-apple-darwin_INT128 = 1
RT_x86_64-apple-darwin_CORO = x86_64
+RT_x86_64-apple-ios-simulator_TARGET = x86_64-apple-ios-simulator
+RT_x86_64-apple-ios-simulator_ABI = lp64
+RT_x86_64-apple-ios-simulator_INT128 = 1
+RT_x86_64-apple-ios-simulator_CORO = x86_64
+
RT_x86_64-elf_TARGET = x86_64-unknown-elf
RT_x86_64-elf_ABI = lp64
RT_x86_64-elf_INT128 = 1
@@ -122,6 +130,18 @@ RT_aarch64-apple-darwin_INT128 = 1
RT_aarch64-apple-darwin_CORO = aarch64
RT_EXTRA_SRCS_aarch64-apple-darwin = rt/lib/coro/aarch64_macho.s
+RT_aarch64-apple-ios_TARGET = aarch64-apple-ios
+RT_aarch64-apple-ios_ABI = lp64
+RT_aarch64-apple-ios_INT128 = 1
+RT_aarch64-apple-ios_CORO = aarch64
+RT_EXTRA_SRCS_aarch64-apple-ios = rt/lib/coro/aarch64_macho.s
+
+RT_aarch64-apple-ios-simulator_TARGET = aarch64-apple-ios-simulator
+RT_aarch64-apple-ios-simulator_ABI = lp64
+RT_aarch64-apple-ios-simulator_INT128 = 1
+RT_aarch64-apple-ios-simulator_CORO = aarch64
+RT_EXTRA_SRCS_aarch64-apple-ios-simulator = rt/lib/coro/aarch64_macho.s
+
RT_aarch64-elf_TARGET = aarch64-unknown-elf
RT_aarch64-elf_ABI = lp64
RT_aarch64-elf_INT128 = 1
diff --git a/mk/test.mk b/mk/test.mk
@@ -131,6 +131,7 @@ TEST_TARGETS = \
test-smoke-arm32 \
test-smoke-x64 \
test-smoke-x64-macos \
+ test-smoke-ios-app \
test-toy \
test-wasm \
test-wasm-c \
@@ -1226,6 +1227,13 @@ test-smoke-x64:
test-smoke-x64-macos:
bash test/smoke/x64_macos.sh
+# test-smoke-ios-app: C-only UIKit smoke for the iOS simulator. It compiles
+# test/smoke/ios_app.c with kit, links UIKit/Foundation/CoreGraphics SDK stubs
+# into an arm64 iOS simulator Mach-O, bundles and ad-hoc signs a minimal .app.
+# Set KIT_IOS_SIM_LAUNCH=1 with a booted simulator to install and launch it.
+test-smoke-ios-app:
+ bash test/smoke/ios_app.sh
+
# test-smoke-rv64: phase-2 counterpart of test-smoke-x64. Builds a
# tiny freestanding riscv64 ELF with clang --target=riscv64-linux-gnu
# and runs it through test/lib/exec_target.sh, proving the rv64 lane
diff --git a/src/api/core.c b/src/api/core.c
@@ -66,7 +66,7 @@ KitStatus kit_target_new(const KitContext* ctx, const KitTargetOptions* opts,
* non-Windows aarch64, wasm), else aliases double. Replicates
* kit_target_long_double_is_binary128 exactly. */
if (t->spec.arch == KIT_ARCH_RV64 ||
- (t->spec.arch == KIT_ARCH_ARM_64 && t->spec.os != KIT_OS_MACOS &&
+ (t->spec.arch == KIT_ARCH_ARM_64 && t->spec.obj != KIT_OBJ_MACHO &&
t->spec.os != KIT_OS_WINDOWS) ||
t->spec.arch == KIT_ARCH_WASM) {
t->spec.long_double_format = (uint8_t)KIT_LDBL_BINARY128;
diff --git a/src/api/target.c b/src/api/target.c
@@ -27,6 +27,21 @@ static bool triple_tok_prefix(const char* s, size_t n, const char* lit) {
return n >= l && memcmp(s, lit, l) == 0;
}
+static bool triple_tok_is_ios(const char* s, size_t n) {
+ if (triple_tok_eq(s, n, "iphoneos")) return true;
+ if (!triple_tok_prefix(s, n, "ios")) return false;
+ if (n == 3) return true;
+ return (s[3] >= '0' && s[3] <= '9') || s[3] == '.';
+}
+
+static bool triple_tok_is_ios_sim(const char* s, size_t n) {
+ return triple_tok_eq(s, n, "simulator") ||
+ triple_tok_eq(s, n, "iossim") ||
+ triple_tok_eq(s, n, "ios-simulator") ||
+ triple_tok_eq(s, n, "iossimulator") ||
+ triple_tok_eq(s, n, "iphonesimulator");
+}
+
static bool riscv_arch_tok(const char* s, size_t n, const char* base) {
size_t l = strlen(base);
size_t i;
@@ -179,6 +194,26 @@ bool kit_target_from_triple(const char* triple, KitTargetSpec* out) {
os_set = 1;
break;
}
+ if (triple_tok_is_ios_sim(parts[i], plen[i])) {
+ t.os = KIT_OS_IOS_SIMULATOR;
+ t.obj = KIT_OBJ_MACHO;
+ os_set = 1;
+ break;
+ }
+ if (triple_tok_is_ios(parts[i], plen[i])) {
+ int sim = 0;
+ int j;
+ for (j = i + 1; j < np; ++j) {
+ if (triple_tok_is_ios_sim(parts[j], plen[j])) {
+ sim = 1;
+ break;
+ }
+ }
+ t.os = sim ? KIT_OS_IOS_SIMULATOR : KIT_OS_IOS;
+ t.obj = KIT_OBJ_MACHO;
+ os_set = 1;
+ break;
+ }
if (triple_tok_eq(parts[i], plen[i], "windows") ||
triple_tok_eq(parts[i], plen[i], "win32")) {
t.os = KIT_OS_WINDOWS;
@@ -274,6 +309,12 @@ bool kit_target_to_triple(KitTargetSpec target, char* buf, size_t cap) {
case KIT_OS_MACOS:
os = "apple-darwin";
break;
+ case KIT_OS_IOS:
+ os = "apple-ios";
+ break;
+ case KIT_OS_IOS_SIMULATOR:
+ os = "apple-ios-simulator";
+ break;
case KIT_OS_WINDOWS:
os = "windows";
break;
diff --git a/src/obj/macho/emit.c b/src/obj/macho/emit.c
@@ -737,12 +737,12 @@ void emit_macho(Compiler* c, ObjBuilder* ob, Writer* w) {
wr_u32(w, 0); /* reserved3 */
}
- /* LC_BUILD_VERSION — platform=PLATFORM_MACOS(1), minos/sdk=14.0.0,
+ /* LC_BUILD_VERSION — platform follows the target OS, minos/sdk=14.0.0,
* ntools=0. The exact min-version isn't load-bearing for MH_OBJECT,
* but Apple's `ld` warns when it's missing. */
wr_u32(w, LC_BUILD_VERSION);
wr_u32(w, build_version_size);
- wr_u32(w, 1); /* platform: PLATFORM_MACOS */
+ wr_u32(w, macho_platform_for_target(c->target));
wr_u32(w, (14u << 16) | 0); /* minos: 14.0.0 */
wr_u32(w, (14u << 16) | 0); /* sdk: 14.0.0 */
wr_u32(w, 0); /* ntools */
diff --git a/src/obj/macho/link.c b/src/obj/macho/link.c
@@ -2524,7 +2524,7 @@ void link_emit_macho(LinkImage* img, Writer* w) {
/* LC_BUILD_VERSION */
objbb_u32(&lc, LC_BUILD_VERSION);
objbb_u32(&lc, 24);
- objbb_u32(&lc, 1); /* PLATFORM_MACOS */
+ objbb_u32(&lc, macho_platform_for_target(img->c->target));
objbb_u32(&lc, (12u << 16) | 0); /* minos 12.0.0 */
objbb_u32(&lc, (12u << 16) | 0); /* sdk 12.0.0 */
objbb_u32(&lc, 0); /* ntools */
diff --git a/src/obj/macho/macho.h b/src/obj/macho/macho.h
@@ -69,6 +69,35 @@
#define LC_DYLD_CHAINED_FIXUPS (0x34u | LC_REQ_DYLD)
#define LC_MAIN (0x28u | LC_REQ_DYLD)
+/* ---- LC_BUILD_VERSION platform values ---- */
+#define PLATFORM_MACOS 1u
+#define PLATFORM_IOS 2u
+#define PLATFORM_IOSSIMULATOR 7u
+
+static inline u32 macho_platform_for_target(KitTargetSpec target) {
+ switch (target.os) {
+ case KIT_OS_IOS:
+ return PLATFORM_IOS;
+ case KIT_OS_IOS_SIMULATOR:
+ return PLATFORM_IOSSIMULATOR;
+ case KIT_OS_MACOS:
+ default:
+ return PLATFORM_MACOS;
+ }
+}
+
+static inline KitOSKind macho_os_for_platform(u32 platform) {
+ switch (platform) {
+ case PLATFORM_IOS:
+ return KIT_OS_IOS;
+ case PLATFORM_IOSSIMULATOR:
+ return KIT_OS_IOS_SIMULATOR;
+ case PLATFORM_MACOS:
+ default:
+ return KIT_OS_MACOS;
+ }
+}
+
/* ---- VM protection bits (segment maxprot / initprot) ---- */
#define VM_PROT_READ 0x1u
#define VM_PROT_WRITE 0x2u
diff --git a/src/obj/registry.c b/src/obj/registry.c
@@ -501,6 +501,7 @@ static KitStatus detect_coff(const u8* d, size_t len, KitTargetSpec* out) {
#if KIT_OBJ_MACHO_ENABLED
static KitStatus detect_macho(const u8* d, size_t len, KitTargetSpec* out) {
u32 magic, cputype;
+ KitOSKind os = KIT_OS_MACOS;
int swap;
if (len < 8) return KIT_MALFORMED;
magic = (u32)d[0] | ((u32)d[1] << 8) | ((u32)d[2] << 16) | ((u32)d[3] << 24);
@@ -525,7 +526,34 @@ static KitStatus detect_macho(const u8* d, size_t len, KitTargetSpec* out) {
}
detect_target_defaults(out);
out->obj = KIT_OBJ_MACHO;
- out->os = KIT_OS_MACOS;
+ if (!swap && len >= MACHO_HDR64_SIZE) {
+ u32 ncmds = (u32)d[16] | ((u32)d[17] << 8) | ((u32)d[18] << 16) |
+ ((u32)d[19] << 24);
+ u32 sizeofcmds = (u32)d[20] | ((u32)d[21] << 8) | ((u32)d[22] << 16) |
+ ((u32)d[23] << 24);
+ u64 pos = MACHO_HDR64_SIZE;
+ u64 end = pos + sizeofcmds;
+ if (end <= len) {
+ u32 i;
+ for (i = 0; i < ncmds && pos + 16u <= end; ++i) {
+ u32 cmd = (u32)d[pos] | ((u32)d[pos + 1] << 8) |
+ ((u32)d[pos + 2] << 16) | ((u32)d[pos + 3] << 24);
+ u32 cmdsize = (u32)d[pos + 4] | ((u32)d[pos + 5] << 8) |
+ ((u32)d[pos + 6] << 16) |
+ ((u32)d[pos + 7] << 24);
+ if (cmdsize < 8u || pos + cmdsize > end) break;
+ if (cmd == LC_BUILD_VERSION && cmdsize >= 24u) {
+ u32 platform = (u32)d[pos + 8] | ((u32)d[pos + 9] << 8) |
+ ((u32)d[pos + 10] << 16) |
+ ((u32)d[pos + 11] << 24);
+ os = macho_os_for_platform(platform);
+ break;
+ }
+ pos += cmdsize;
+ }
+ }
+ }
+ out->os = os;
/* Resolve the arch through the Mach-O format's cputype reverse map. The
* registry models only the link/codegen arches (ARM64 / X86_64); the
diff --git a/src/os/macos/hosted.c b/src/os/macos/hosted.c
@@ -1,12 +1,52 @@
#include "os/hosted_internal.h"
-static int hosted_add_darwin_defines(KitOsHostedPlan* plan) {
+static const char* darwin_sdk_name(KitOSKind os) {
+ switch (os) {
+ case KIT_OS_IOS:
+ return "iphoneos";
+ case KIT_OS_IOS_SIMULATOR:
+ return "iphonesimulator";
+ case KIT_OS_MACOS:
+ default:
+ return "macosx";
+ }
+}
+
+static const char* darwin_profile_name(KitOSKind os) {
+ switch (os) {
+ case KIT_OS_IOS:
+ return "ios-libSystem";
+ case KIT_OS_IOS_SIMULATOR:
+ return "ios-simulator-libSystem";
+ case KIT_OS_MACOS:
+ default:
+ return "macos-libSystem";
+ }
+}
+
+static int hosted_add_darwin_defines(KitOsHostedPlan* plan,
+ KitTargetSpec target) {
if (kit_os_hosted_add_clang_compat_defines(plan) != 0 ||
kit_os_hosted_add_define(plan, "__APPLE__", "1") != 0 ||
kit_os_hosted_add_define(plan, "__APPLE_CC__", "6000") != 0 ||
kit_os_hosted_add_define(plan, "__MACH__", "1") != 0 ||
kit_os_hosted_add_define(plan, "__DYNAMIC__", "1") != 0)
return 1;
+ if (target.os == KIT_OS_IOS || target.os == KIT_OS_IOS_SIMULATOR) {
+ if (kit_os_hosted_add_define(
+ plan, "__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__",
+ "120000") != 0 ||
+ kit_os_hosted_add_define(plan,
+ "__ENVIRONMENT_OS_VERSION_MIN_REQUIRED__",
+ "120000") != 0)
+ return 1;
+ }
+ if (target.os == KIT_OS_IOS_SIMULATOR) {
+ if (kit_os_hosted_add_define(plan, "__APPLE_EMBEDDED_SIMULATOR__", "1") !=
+ 0 ||
+ kit_os_hosted_add_define(plan, "TARGET_IPHONE_SIMULATOR", "1") != 0)
+ return 1;
+ }
return 0;
}
@@ -16,13 +56,14 @@ static int hosted_resolve_darwin(const KitOsHostedRequest* req,
if (dirs->nlibdirs == 0) {
kit_os_hosted_errf(
req->host, req->host_user, req->tool,
- "Darwin hosted profile requires a macOS SDK; pass --sysroot, set "
- "KIT_SYSROOT, or install the Command Line Tools (try: --sysroot "
- "\"$(xcrun --sdk macosx --show-sdk-path)\")");
+ "Darwin hosted profile requires an Apple SDK; pass --sysroot, set "
+ "KIT_SYSROOT, or install Xcode/Command Line Tools (try: --sysroot "
+ "\"$(xcrun --sdk %s --show-sdk-path)\")",
+ darwin_sdk_name(req->target.os));
return 1;
}
- plan->profile_name = "macos-libSystem";
- if (hosted_add_darwin_defines(plan) != 0 ||
+ plan->profile_name = darwin_profile_name(req->target.os);
+ if (hosted_add_darwin_defines(plan, req->target) != 0 ||
kit_os_hosted_add_incdirs(plan, req->host, req->host_user, dirs) != 0) {
kit_os_hosted_errf(req->host, req->host_user, req->tool, "out of memory");
return 1;
diff --git a/src/os/macos/os.c b/src/os/macos/os.c
@@ -8,3 +8,17 @@ const KitOsImpl kit_os_macos_impl = {
.hosted = &kit_os_macos_hosted_ops,
.emu = NULL,
};
+
+const KitOsImpl kit_os_ios_impl = {
+ .kind = KIT_OS_IOS,
+ .name = "ios",
+ .hosted = &kit_os_macos_hosted_ops,
+ .emu = NULL,
+};
+
+const KitOsImpl kit_os_ios_simulator_impl = {
+ .kind = KIT_OS_IOS_SIMULATOR,
+ .name = "ios-simulator",
+ .hosted = &kit_os_macos_hosted_ops,
+ .emu = NULL,
+};
diff --git a/src/os/registry.c b/src/os/registry.c
@@ -3,6 +3,8 @@
#if KIT_OBJ_MACHO_ENABLED
extern const KitOsImpl kit_os_macos_impl;
+extern const KitOsImpl kit_os_ios_impl;
+extern const KitOsImpl kit_os_ios_simulator_impl;
#endif
#if KIT_OBJ_ELF_ENABLED || KIT_EMU_ENABLED
extern const KitOsImpl kit_os_linux_impl;
@@ -19,6 +21,10 @@ const KitOsImpl* kit_os_lookup(KitOSKind kind) {
#if KIT_OBJ_MACHO_ENABLED
case KIT_OS_MACOS:
return &kit_os_macos_impl;
+ case KIT_OS_IOS:
+ return &kit_os_ios_impl;
+ case KIT_OS_IOS_SIMULATOR:
+ return &kit_os_ios_simulator_impl;
#endif
#if KIT_OBJ_ELF_ENABLED || KIT_EMU_ENABLED
case KIT_OS_LINUX:
diff --git a/test/api/abi_classify_test.c b/test/api/abi_classify_test.c
@@ -96,6 +96,10 @@ static const char* os_name(KitOSKind o) {
return "linux";
case KIT_OS_MACOS:
return "macos";
+ case KIT_OS_IOS:
+ return "ios";
+ case KIT_OS_IOS_SIMULATOR:
+ return "ios-simulator";
case KIT_OS_WINDOWS:
return "windows";
case KIT_OS_FREESTANDING:
diff --git a/test/api/target_test.c b/test/api/target_test.c
@@ -63,6 +63,28 @@ static void check_target_triple_parse(void) {
EXPECT(t.arch == KIT_ARCH_RV32 && t.os == KIT_OS_FREESTANDING &&
t.obj == KIT_OBJ_ELF,
"Rust riscv32imac none triple maps to freestanding rv32");
+
+ memset(&t, 0, sizeof t);
+ EXPECT(kit_target_from_triple("arm64-apple-ios17.0", &t),
+ "arm64 iOS triple parses");
+ EXPECT(t.arch == KIT_ARCH_ARM_64 && t.os == KIT_OS_IOS &&
+ t.obj == KIT_OBJ_MACHO,
+ "arm64 iOS triple maps to Mach-O iOS");
+
+ memset(&t, 0, sizeof t);
+ EXPECT(kit_target_from_triple("arm64-apple-ios17.0-simulator", &t),
+ "arm64 iOS simulator triple parses");
+ EXPECT(t.arch == KIT_ARCH_ARM_64 && t.os == KIT_OS_IOS_SIMULATOR &&
+ t.obj == KIT_OBJ_MACHO,
+ "arm64 iOS simulator triple maps to Mach-O iOS simulator");
+
+ {
+ char buf[64];
+ EXPECT(kit_target_to_triple(t, buf, sizeof buf),
+ "iOS simulator target renders");
+ EXPECT(strcmp(buf, "aarch64-apple-ios-simulator") == 0,
+ "iOS simulator target renders canonical triple");
+ }
}
static void check_x64_defaults_and_isa(void) {
diff --git a/test/driver/run.sh b/test/driver/run.sh
@@ -571,6 +571,87 @@ else
not_ok "macho-function-sections-atoms" "$work/macho-many-setup.diag"
fi
+# ---- Mach-O LC_BUILD_VERSION platform for iOS targets ----
+macho_build_platform() {
+ obj=$1
+ segcmd_size=$(od -An -tu4 -j 36 -N 4 "$obj" 2>/dev/null | tr -d '[:space:]')
+ if [ -z "$segcmd_size" ]; then
+ return 1
+ fi
+ od -An -tu4 -j $((32 + segcmd_size + 8)) -N 4 "$obj" 2>/dev/null |
+ tr -d '[:space:]'
+}
+cat > "$work/macho-ios.c" <<'SRC'
+int f(void) { return 7; }
+SRC
+if "$KIT" cc -target arm64-apple-ios -c "$work/macho-ios.c" \
+ -o "$work/macho-ios.o" \
+ > "$work/macho-ios-cc.out" 2> "$work/macho-ios-cc.err" &&
+ "$KIT" cc -target arm64-apple-ios-simulator -c "$work/macho-ios.c" \
+ -o "$work/macho-ios-sim.o" \
+ > "$work/macho-ios-sim-cc.out" 2> "$work/macho-ios-sim-cc.err"; then
+ ios_platform=$(macho_build_platform "$work/macho-ios.o")
+ sim_platform=$(macho_build_platform "$work/macho-ios-sim.o")
+ if [ "$ios_platform" = "2" ] && [ "$sim_platform" = "7" ]; then
+ ok "macho-ios-build-version-platforms"
+ else
+ { printf 'ios platform=%s (want 2)\n' "$ios_platform"
+ printf 'sim platform=%s (want 7)\n' "$sim_platform"; } \
+ > "$work/macho-ios-platform.diag"
+ not_ok "macho-ios-build-version-platforms" \
+ "$work/macho-ios-platform.diag"
+ fi
+else
+ { sed 's/^/ios: /' "$work/macho-ios-cc.err"
+ sed 's/^/sim: /' "$work/macho-ios-sim-cc.err"; } \
+ > "$work/macho-ios-setup.diag"
+ not_ok "macho-ios-build-version-platforms" "$work/macho-ios-setup.diag"
+fi
+
+# ---- Darwin framework linking through cc and ld ----
+iossim_sdk=
+if [ "$(uname -s 2>/dev/null)" = "Darwin" ]; then
+ iossim_sdk=$(xcrun --sdk iphonesimulator --show-sdk-path 2>/dev/null || true)
+fi
+if [ -z "$iossim_sdk" ]; then
+ skip_test "darwin-framework-link" "no iPhoneSimulator SDK"
+else
+ cat > "$work/framework-main.c" <<'SRC'
+int main(void) { return 0; }
+SRC
+ if "$KIT" cc -target arm64-apple-ios-simulator --sysroot "$iossim_sdk" \
+ -lc -framework Foundation "$work/framework-main.c" \
+ -o "$work/framework-cc" \
+ > "$work/framework-cc.out" 2> "$work/framework-cc.err" &&
+ "$KIT" cc -target arm64-apple-ios-simulator --sysroot "$iossim_sdk" \
+ -c "$work/framework-main.c" -o "$work/framework-main.o" \
+ > "$work/framework-obj.out" 2> "$work/framework-obj.err" &&
+ "$KIT" ld -target arm64-apple-ios-simulator --sysroot "$iossim_sdk" \
+ -lc -framework Foundation "$work/framework-main.o" \
+ -o "$work/framework-ld" \
+ > "$work/framework-ld.out" 2> "$work/framework-ld.err" &&
+ "$KIT" ld -arch arm64 -platform_version ios-simulator 12.0 17.0 \
+ --sysroot "$iossim_sdk" -lc -framework Foundation \
+ "$work/framework-main.o" -o "$work/framework-ld-platform" \
+ > "$work/framework-ld-platform.out" \
+ 2> "$work/framework-ld-platform.err" &&
+ "$KIT" build-exe -arch arm64 \
+ -platform_version ios-simulator 12.0 17.0 \
+ --sysroot "$iossim_sdk" -lc -framework Foundation \
+ "$work/framework-main.c" -o "$work/framework-build" \
+ > "$work/framework-build.out" 2> "$work/framework-build.err"; then
+ ok "darwin-framework-link"
+ else
+ { sed 's/^/cc: /' "$work/framework-cc.err"
+ sed 's/^/obj: /' "$work/framework-obj.err"
+ sed 's/^/ld: /' "$work/framework-ld.err"
+ sed 's/^/ldp: /' "$work/framework-ld-platform.err"
+ sed 's/^/bld: /' "$work/framework-build.err"; } \
+ > "$work/framework-link.diag"
+ not_ok "darwin-framework-link" "$work/framework-link.diag"
+ fi
+fi
+
# ---- run: JIT compile a source + archive on demand, exit status is the result ----
cat > "$work/run-main.c" <<'SRC'
int add42(int);
diff --git a/test/smoke/ios_app.c b/test/smoke/ios_app.c
@@ -0,0 +1,95 @@
+#include <CoreGraphics/CGGeometry.h>
+#include <objc/message.h>
+#include <objc/objc.h>
+#include <objc/runtime.h>
+
+extern int UIApplicationMain(int argc, char** argv, id principal_class_name,
+ id delegate_class_name);
+
+static SEL kit_sel(const char* name) {
+ return sel_registerName(name);
+}
+
+static id kit_msg_id(id receiver, const char* name) {
+ return ((id(*)(id, SEL))objc_msgSend)(receiver, kit_sel(name));
+}
+
+static id kit_msg_id_cstr(id receiver, const char* name, const char* arg) {
+ return ((id(*)(id, SEL, const char*))objc_msgSend)(receiver, kit_sel(name),
+ arg);
+}
+
+static id kit_msg_id_rect(id receiver, const char* name, CGRect arg) {
+ return ((id(*)(id, SEL, CGRect))objc_msgSend)(receiver, kit_sel(name), arg);
+}
+
+static CGRect kit_msg_rect(id receiver, const char* name) {
+ return ((CGRect(*)(id, SEL))objc_msgSend)(receiver, kit_sel(name));
+}
+
+static void kit_msg_void(id receiver, const char* name) {
+ ((void (*)(id, SEL))objc_msgSend)(receiver, kit_sel(name));
+}
+
+static void kit_msg_void_id(id receiver, const char* name, id arg) {
+ ((void (*)(id, SEL, id))objc_msgSend)(receiver, kit_sel(name), arg);
+}
+
+static BOOL kit_app_did_finish_launching(id self, SEL op, id app,
+ id options) {
+ (void)op;
+ (void)app;
+ (void)options;
+
+ id screen = kit_msg_id((id)objc_getClass("UIScreen"), "mainScreen");
+ CGRect bounds = kit_msg_rect(screen, "bounds");
+ id window = kit_msg_id_rect(
+ kit_msg_id((id)objc_getClass("UIWindow"), "alloc"), "initWithFrame:",
+ bounds);
+ id view_controller =
+ kit_msg_id(kit_msg_id((id)objc_getClass("UIViewController"), "alloc"),
+ "init");
+ id view = kit_msg_id(view_controller, "view");
+ id color = kit_msg_id((id)objc_getClass("UIColor"), "whiteColor");
+ Ivar window_ivar = class_getInstanceVariable(object_getClass(self), "_window");
+
+ kit_msg_void_id(view, "setBackgroundColor:", color);
+ kit_msg_void_id(window, "setRootViewController:", view_controller);
+ kit_msg_void(window, "makeKeyAndVisible");
+ (void)kit_msg_id(window, "retain");
+ if (window_ivar) object_setIvar(self, window_ivar, window);
+ return YES;
+}
+
+static const char* kit_delegate_class_name(void) {
+ return "KitCSmokeAppDelegate";
+}
+
+static void kit_register_delegate_class(void) {
+ const char* name = kit_delegate_class_name();
+ Class superclass = (Class)objc_getClass("UIResponder");
+ Class delegate = objc_allocateClassPair(superclass, name, 0);
+ if (!delegate) return;
+
+ (void)class_addIvar(delegate, "_window", sizeof(id),
+ sizeof(id) == 8 ? 3 : 2, "@");
+ (void)class_addMethod(
+ delegate, kit_sel("application:didFinishLaunchingWithOptions:"),
+ (IMP)kit_app_did_finish_launching, "B@:@@");
+ objc_registerClassPair(delegate);
+}
+
+int main(int argc, char** argv) {
+ id pool = kit_msg_id(kit_msg_id((id)objc_getClass("NSAutoreleasePool"),
+ "alloc"),
+ "init");
+ id delegate_name = kit_msg_id_cstr((id)objc_getClass("NSString"),
+ "stringWithUTF8String:",
+ kit_delegate_class_name());
+ int rc;
+
+ kit_register_delegate_class();
+ rc = UIApplicationMain(argc, argv, (id)0, delegate_name);
+ kit_msg_void(pool, "drain");
+ return rc;
+}
diff --git a/test/smoke/ios_app.sh b/test/smoke/ios_app.sh
@@ -0,0 +1,169 @@
+#!/usr/bin/env bash
+# Build a minimal C-only UIKit app for the iOS simulator. The app uses the
+# Objective-C runtime C API instead of Objective-C syntax, so it exercises kit's
+# C frontend, iOS hosted profile, Mach-O linker, SDK C headers, and UIKit stub
+# linking without requiring an Objective-C frontend.
+
+set -u
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+KIT="${KIT:-$ROOT/build/kit}"
+BUILD_DIR="$ROOT/build/test/smoke-ios-app"
+APP_ID="${KIT_IOS_APP_ID:-dev.kit.CSmoke}"
+APP_NAME="KitCSmoke"
+TARGET="arm64-apple-ios-simulator"
+SIM_TIMEOUT="${KIT_IOS_SIM_TIMEOUT:-30}"
+
+mkdir -p "$BUILD_DIR"
+
+KIT_KIT_DIR="$ROOT/test/lib"
+# shellcheck source=../lib/kit_sh_kit.sh
+. "$ROOT/test/lib/kit_sh_kit.sh"
+kit_report_init
+
+work="$BUILD_DIR"
+
+kit_run_timeout() {
+ local seconds=$1
+ shift
+ /usr/bin/perl -e 'alarm shift @ARGV; exec @ARGV' "$seconds" "$@"
+}
+
+if [ "$(uname -s 2>/dev/null)" != "Darwin" ]; then
+ skip_test "build" "iOS simulator app build requires a Darwin host"
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+if [ ! -x "$KIT" ]; then
+ skip_test "build" "$KIT not built (run 'make bin')"
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+
+SDK="$(xcrun --sdk iphonesimulator --show-sdk-path 2>/dev/null || true)"
+if [ -z "$SDK" ]; then
+ skip_test "build" "no iPhoneSimulator SDK (xcrun --show-sdk-path empty)"
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+
+INCLUDE_DIR="$BUILD_DIR/include"
+rm -rf "$INCLUDE_DIR"
+mkdir -p "$INCLUDE_DIR"
+ln -s "$SDK/System/Library/Frameworks/CoreGraphics.framework/Headers" \
+ "$INCLUDE_DIR/CoreGraphics"
+ln -s "$SDK/System/Library/Frameworks/CoreFoundation.framework/Headers" \
+ "$INCLUDE_DIR/CoreFoundation"
+
+EXE="$BUILD_DIR/$APP_NAME"
+if ! "$KIT" cc -target "$TARGET" --sysroot "$SDK" -I "$INCLUDE_DIR" -lc -lobjc \
+ "$ROOT/test/smoke/ios_app.c" \
+ -framework UIKit -framework Foundation -framework CoreGraphics \
+ -o "$EXE" >"$BUILD_DIR/build.out" 2>"$BUILD_DIR/build.err"; then
+ not_ok "build app executable" "$BUILD_DIR/build.err"
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+ok "build app executable"
+
+if ! otool -l "$EXE" >"$BUILD_DIR/otool.out" 2>"$BUILD_DIR/otool.err"; then
+ not_ok "inspect app executable" "$BUILD_DIR/otool.err"
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+if grep -A4 LC_BUILD_VERSION "$BUILD_DIR/otool.out" | grep -q "platform 7"; then
+ ok "Mach-O platform ios-simulator"
+else
+ not_ok "Mach-O platform ios-simulator" "$BUILD_DIR/otool.out"
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+
+APP="$BUILD_DIR/$APP_NAME.app"
+rm -rf "$APP"
+mkdir -p "$APP"
+cp "$EXE" "$APP/$APP_NAME"
+cat >"$APP/Info.plist" <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>$APP_NAME</string>
+ <key>CFBundleIdentifier</key>
+ <string>$APP_ID</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$APP_NAME</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSupportedPlatforms</key>
+ <array>
+ <string>iPhoneSimulator</string>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>MinimumOSVersion</key>
+ <string>12.0</string>
+</dict>
+</plist>
+EOF
+
+if ! plutil -lint "$APP/Info.plist" >"$BUILD_DIR/plutil.out" \
+ 2>"$BUILD_DIR/plutil.err"; then
+ not_ok "bundle Info.plist" "$BUILD_DIR/plutil.err"
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+ok "bundle Info.plist"
+
+if ! codesign -s - --force "$APP" >"$BUILD_DIR/codesign.out" \
+ 2>"$BUILD_DIR/codesign.err"; then
+ not_ok "codesign simulator app" "$BUILD_DIR/codesign.err"
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+ok "codesign simulator app"
+
+if [ "${KIT_IOS_SIM_LAUNCH:-0}" != "1" ]; then
+ skip_test "launch simulator app" "set KIT_IOS_SIM_LAUNCH=1 with a booted simulator"
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+
+kit_run_timeout "$SIM_TIMEOUT" xcrun simctl install booted "$APP" \
+ >"$BUILD_DIR/sim-install.out" 2>"$BUILD_DIR/sim-install.err"
+sim_rc=$?
+if [ "$sim_rc" -ne 0 ]; then
+ if [ "$sim_rc" -eq 142 ]; then
+ echo "simctl install timed out after ${SIM_TIMEOUT}s" \
+ >"$BUILD_DIR/sim-install.err"
+ fi
+ if [ "${KIT_IOS_SIM_REQUIRE_LAUNCH:-0}" = "1" ]; then
+ not_ok "install simulator app" "$BUILD_DIR/sim-install.err"
+ else
+ skip_test "install simulator app" "no booted simulator accepting installs"
+ fi
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+ok "install simulator app"
+
+kit_run_timeout "$SIM_TIMEOUT" xcrun simctl launch booted "$APP_ID" \
+ >"$BUILD_DIR/sim-launch.out" 2>"$BUILD_DIR/sim-launch.err"
+sim_rc=$?
+if [ "$sim_rc" -ne 0 ]; then
+ if [ "$sim_rc" -eq 142 ]; then
+ echo "simctl launch timed out after ${SIM_TIMEOUT}s" \
+ >"$BUILD_DIR/sim-launch.err"
+ fi
+ if [ "${KIT_IOS_SIM_REQUIRE_LAUNCH:-0}" = "1" ]; then
+ not_ok "launch simulator app" "$BUILD_DIR/sim-launch.err"
+ else
+ skip_test "launch simulator app" "simulator launch failed"
+ fi
+ kit_summary test-smoke-ios-app; kit_exit
+fi
+ok "launch simulator app"
+
+kit_run_timeout "$SIM_TIMEOUT" xcrun simctl terminate booted "$APP_ID" \
+ >"$BUILD_DIR/sim-terminate.out" 2>"$BUILD_DIR/sim-terminate.err" || true
+
+kit_summary test-smoke-ios-app
+kit_exit