kit

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

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:
Mdriver/cmd/build.c | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mdriver/cmd/cc.c | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mdriver/cmd/ld.c | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mdriver/driver.h | 16++++++++++++----
Mdriver/env/macos.c | 25++++++++++++++++++++++---
Mdriver/lib/hosted.c | 4++++
Mdriver/lib/lib_resolve.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/lib/lib_resolve.h | 13+++++++++++++
Mdriver/lib/link_inputs.c | 92++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mdriver/lib/link_inputs.h | 21+++++++++++++++++++++
Mdriver/lib/runtime.c | 12++++++++++++
Mdriver/lib/target.c | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kit/core.h | 2++
Minclude/kit/os.h | 2+-
Minclude/kit/target.h | 9+++++----
Mlang/c/parse/parse_type.c | 29+++++++++++++++++++++++++++++
Mlang/cpp/pp/pp.c | 2++
Mmk/rt.mk | 20++++++++++++++++++++
Mmk/test.mk | 8++++++++
Msrc/api/core.c | 2+-
Msrc/api/target.c | 41+++++++++++++++++++++++++++++++++++++++++
Msrc/obj/macho/emit.c | 4++--
Msrc/obj/macho/link.c | 2+-
Msrc/obj/macho/macho.h | 29+++++++++++++++++++++++++++++
Msrc/obj/registry.c | 30+++++++++++++++++++++++++++++-
Msrc/os/macos/hosted.c | 53+++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/os/macos/os.c | 14++++++++++++++
Msrc/os/registry.c | 6++++++
Mtest/api/abi_classify_test.c | 4++++
Mtest/api/target_test.c | 22++++++++++++++++++++++
Mtest/driver/run.sh | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/smoke/ios_app.c | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/smoke/ios_app.sh | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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