@scriptc/compiler 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +5 -13
  2. package/dist/backend/cc.d.ts +23 -2
  3. package/dist/backend/cc.js +250 -9
  4. package/dist/backend/cc.js.map +1 -1
  5. package/dist/backend/emission/emit-async.d.ts +1 -0
  6. package/dist/backend/emission/emit-async.js +27 -1
  7. package/dist/backend/emission/emit-async.js.map +1 -1
  8. package/dist/backend/emission/emit-exprs.js +109 -18
  9. package/dist/backend/emission/emit-exprs.js.map +1 -1
  10. package/dist/backend/emission/emitter.d.ts +1 -0
  11. package/dist/backend/emission/emitter.js +25 -3
  12. package/dist/backend/emission/emitter.js.map +1 -1
  13. package/dist/backend/llvm/emitter.js +152 -0
  14. package/dist/backend/llvm/emitter.js.map +1 -1
  15. package/dist/backend/mangle.d.ts +1 -0
  16. package/dist/backend/mangle.js +3 -0
  17. package/dist/backend/mangle.js.map +1 -1
  18. package/dist/coverage/surface-manifest.js +1 -0
  19. package/dist/coverage/surface-manifest.js.map +1 -1
  20. package/dist/diagnostics/diagnostic.d.ts +23 -1
  21. package/dist/diagnostics/diagnostic.js +34 -0
  22. package/dist/diagnostics/diagnostic.js.map +1 -1
  23. package/dist/frontend/lowering/lower-calls.d.ts +3 -1
  24. package/dist/frontend/lowering/lower-calls.js +196 -33
  25. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  26. package/dist/frontend/lowering/lower-containers.d.ts +6 -5
  27. package/dist/frontend/lowering/lower-containers.js +15 -11
  28. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  29. package/dist/frontend/lowering/lower-exprs.d.ts +11 -1
  30. package/dist/frontend/lowering/lower-exprs.js +366 -25
  31. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  32. package/dist/frontend/lowering/lower-server.d.ts +5 -0
  33. package/dist/frontend/lowering/lower-server.js +105 -55
  34. package/dist/frontend/lowering/lower-server.js.map +1 -1
  35. package/dist/frontend/lowering/lower-stmts.js +71 -9
  36. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  37. package/dist/frontend/lowering/lowerer.d.ts +24 -5
  38. package/dist/frontend/lowering/lowerer.js +28 -4
  39. package/dist/frontend/lowering/lowerer.js.map +1 -1
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +84 -5
  42. package/dist/index.js.map +1 -1
  43. package/dist/ir/nodes.d.ts +52 -19
  44. package/dist/ir/nodes.js +2 -2
  45. package/dist/ir/nodes.js.map +1 -1
  46. package/dist/ir/validate.js +67 -9
  47. package/dist/ir/validate.js.map +1 -1
  48. package/dist/library/profile.d.ts +33 -0
  49. package/dist/library/profile.js +178 -4
  50. package/dist/library/profile.js.map +1 -1
  51. package/dist/library/sidecar.d.ts +3 -2
  52. package/dist/library/sidecar.js +7 -3
  53. package/dist/library/sidecar.js.map +1 -1
  54. package/package.json +2 -2
  55. package/surface-manifest.json +2 -1
@@ -2444,18 +2444,10 @@ declare module "node:https" {
2444
2444
  export * from "https";
2445
2445
  }
2446
2446
 
2447
- /* node:http2 — the COMPATIBILITY slice (SEMANTICS.md divergence 57):
2448
- * createSecureServer({ allowHTTP1: true, cert, key }) is the https
2449
- * server without an eager handler — ALPN advertises http/1.1 ONLY, so
2450
- * h2-capable clients negotiate down and every connection serves
2451
- * HTTP/1.1 (h2-only clients fail the handshake; no multiplexing, no
2452
- * server push). The handler arrives via server.on("request", ...);
2453
- * Http2ServerRequest/Response ARE the http req/res surface (exactly
2454
- * what Node hands an allowHTTP1 server's HTTP/1.1 connections), with
2455
- * the h2-only members (stream, session) answering undefined behind a
2456
- * '?.' guard and fencing unguarded. 'sessionError' registers and never
2457
- * fires (no h2 session ever exists). h2 sessions themselves — connect,
2458
- * the h2c createServer — have no lowering. */
2447
+ /* node:http2 — the compatibility surface: allowHTTP1 servers negotiate
2448
+ * h2 or HTTP/1.1 and expose the shared request/response API. HTTP/2
2449
+ * requests retain their backing stream; server session failures surface
2450
+ * through sessionError. */
2459
2451
  declare module "http2" {
2460
2452
  import { Socket } from "net";
2461
2453
  export interface Http2Stream {
@@ -2529,7 +2521,7 @@ declare module "http2" {
2529
2521
  on(event: "request", listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void): void;
2530
2522
  on(event: "error", listener: (err: Error) => void): void;
2531
2523
  on(event: "close", listener: () => void): void;
2532
- on(event: "sessionError", listener: () => void): void;
2524
+ on(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void;
2533
2525
  /* The ALPN=h2 server's surface (createSecureServer WITHOUT
2534
2526
  * allowHTTP1 — the real h2-over-TLS stack): per-stream and
2535
2527
  * per-session listeners, exactly the h2c server's. */
@@ -208,11 +208,31 @@ export interface CcDriver {
208
208
  argv: string[];
209
209
  /** The SCRIPTC_TARGET triple, or null for a host-native build. */
210
210
  target: string | null;
211
+ /** The spelling handed to `zig cc -target` (and the relocatable-merge
212
+ * link). Identical to `target` except for the mobile triples, whose
213
+ * canonical LLVM spellings map to zig's own (`aarch64-apple-ios` →
214
+ * `aarch64-ios.15.0`), pinning the minimum OS version in the same
215
+ * breath. Null for a host-native build. */
216
+ zigTarget: string | null;
211
217
  /** Extra compile args the produced platform demands. */
212
218
  targetArgs: string[];
213
219
  /** Platform libraries appended after every object/archive input. */
214
220
  linkArgs: string[];
215
221
  }
222
+ export declare const IPHONEOS_MIN_VERSION = "15.0";
223
+ export declare const ANDROID_MIN_API = 26;
224
+ export declare function isIosTarget(target: string | null): boolean;
225
+ export declare function isAndroidTarget(target: string | null): boolean;
226
+ export declare function isMobileTarget(target: string | null): boolean;
227
+ /** The canonical mobile triple SCRIPTC_TARGET selects, or null when the
228
+ * environment names none. Pure string inspection — safe to consult before
229
+ * any toolchain discovery runs. */
230
+ export declare function mobileLibraryTarget(env?: NodeJS.ProcessEnv): string | null;
231
+ /** The admission verdict for a mobile-family triple: null when the spelling
232
+ * and host pairing are supported, otherwise the refusal text (the same text
233
+ * resolveCc throws and compileLibrary reports as SC3002). Pure string/host
234
+ * inspection — no discovery, no subprocess. */
235
+ export declare function mobileTargetRefusal(target: string, hostPlatform?: NodeJS.Platform): string | null;
216
236
  export declare function resolveCc(env?: NodeJS.ProcessEnv, hostPlatform?: NodeJS.Platform): CcDriver;
217
237
  /** Musl intentionally has no predefined libc macro. The explicit Zig target
218
238
  * is therefore the source of truth for selecting its small runtime shim. */
@@ -259,8 +279,9 @@ export interface LibArchiveOptions {
259
279
  * sanitizer ABI in instrumented builds) keep their global binding. Windows
260
280
  * embedders additionally link advapi32, iphlpapi, and ws2_32. Omitted = the
261
281
  * classic archive, byte-for-byte. Admitted for darwin/linux/win32 native
262
- * hosts, linux and windows cross triples from any host, and macos cross
263
- * triples from a darwin host (compileLibrary owns the refusal fence). */
282
+ * hosts, linux/android/windows cross triples from any host, and macos/ios
283
+ * cross triples from a darwin host (compileLibrary owns the refusal
284
+ * fence). */
264
285
  localizeSymbols?: readonly string[];
265
286
  /** Thread-instanced state (the profile's abi.instance_per_thread): every
266
287
  * TU of the archive compiles with -DSCR_THREAD_INSTANCES, moving the
@@ -1,6 +1,6 @@
1
- import { execFile } from "node:child_process";
1
+ import { execFile, spawnSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
- import { constants as fsConstants } from "node:fs";
3
+ import { constants as fsConstants, existsSync, readdirSync } from "node:fs";
4
4
  import { createRequire } from "node:module";
5
5
  import { access, chmod, copyFile, link, mkdir, mkdtemp, readdir, readFile, realpath, rename, rm, stat, unlink, utimes, writeFile } from "node:fs/promises";
6
6
  import { availableParallelism, homedir, tmpdir } from "node:os";
@@ -176,6 +176,181 @@ export function runtimeSrcDir() {
176
176
  const require = createRequire(import.meta.url);
177
177
  return join(dirname(require.resolve("@scriptc/runtime/package.json")), "src");
178
178
  }
179
+ /* ── mobile targets (library mode) ─────────────────────────────────────────
180
+ * Three mobile triples are admitted, and only for LIBRARY-MODE archive
181
+ * builds — the consuming pattern is an embedding app linking the archive,
182
+ * never a standalone executable (compile()/compileC refuse the executable
183
+ * lane with the pointer to --lib):
184
+ *
185
+ * aarch64-apple-ios device archives; zig `aarch64-ios.15.0`
186
+ * aarch64-apple-ios-simulator simulator archives; zig
187
+ * `aarch64-ios.15.0-simulator`
188
+ * aarch64-linux-android zig `aarch64-linux-android.26`
189
+ *
190
+ * The minimum-version floors are part of the target contract: iOS archives
191
+ * build for iOS 15.0 (IPHONEOS_MIN_VERSION), Android archives for API level
192
+ * 26 (ANDROID_MIN_API) — LC_BUILD_VERSION minos and the bionic stub level
193
+ * both come from the pinned zig spelling above, so an embedder's deployment
194
+ * target at or above the floor links cleanly.
195
+ *
196
+ * Zig bundles no Apple or bionic libc, so both families compile against an
197
+ * explicit sysroot discovered here and spelled into targetArgs (where every
198
+ * cache tier already keys it):
199
+ *
200
+ * iOS darwin hosts only — `xcrun --show-sdk-path` selects the
201
+ * iPhoneOS/iPhoneSimulator SDK; compiles add `-isysroot <sdk>`
202
+ * plus `-isystem <sdk>/usr/include` (zig's driver manages libc
203
+ * header search itself and would otherwise find no headers).
204
+ * Android any host with an NDK — ANDROID_NDK_ROOT/ANDROID_NDK_HOME, or
205
+ * the newest ndk/<version> under ANDROID_HOME/ANDROID_SDK_ROOT
206
+ * or the platform-default SDK location; compiles add the NDK
207
+ * sysroot's generic and per-triple include directories.
208
+ *
209
+ * Library archives never link, so the sysroot's LIBRARIES are the
210
+ * embedder's side of the contract: Xcode links iOS archives against the
211
+ * selected SDK, and Gradle/NDK builds link Android archives against the
212
+ * API-26+ bionic stubs. */
213
+ export const IPHONEOS_MIN_VERSION = "15.0";
214
+ export const ANDROID_MIN_API = 26;
215
+ const MOBILE_LIBRARY_TARGETS = [
216
+ "aarch64-apple-ios",
217
+ "aarch64-apple-ios-simulator",
218
+ "aarch64-linux-android",
219
+ ];
220
+ export function isIosTarget(target) {
221
+ return target === "aarch64-apple-ios" || target === "aarch64-apple-ios-simulator";
222
+ }
223
+ export function isAndroidTarget(target) {
224
+ return target === "aarch64-linux-android";
225
+ }
226
+ export function isMobileTarget(target) {
227
+ return isIosTarget(target) || isAndroidTarget(target);
228
+ }
229
+ /** The canonical mobile triple SCRIPTC_TARGET selects, or null when the
230
+ * environment names none. Pure string inspection — safe to consult before
231
+ * any toolchain discovery runs. */
232
+ export function mobileLibraryTarget(env = process.env) {
233
+ const target = env["SCRIPTC_TARGET"] ?? "";
234
+ return isMobileTarget(target) ? target : null;
235
+ }
236
+ /** The admission verdict for a mobile-family triple: null when the spelling
237
+ * and host pairing are supported, otherwise the refusal text (the same text
238
+ * resolveCc throws and compileLibrary reports as SC3002). Pure string/host
239
+ * inspection — no discovery, no subprocess. */
240
+ export function mobileTargetRefusal(target, hostPlatform = process.platform) {
241
+ if (isIosTarget(target)) {
242
+ return hostPlatform === "darwin"
243
+ ? null
244
+ : `${target} library archives build on macOS hosts only (the Apple iOS SDK sysroot and Mach-O symbol localization live there); this host is ${hostPlatform}`;
245
+ }
246
+ if (isAndroidTarget(target))
247
+ return null;
248
+ // A near-miss mobile spelling must refuse with the supported set named,
249
+ // never reach zig with no sysroot wired (the compile would fail on the
250
+ // first libc header) or produce an artifact for an unverified device
251
+ // class.
252
+ if (/(?:^|-)(?:ios|tvos|watchos|visionos|android)/.test(target)) {
253
+ return `unsupported mobile target '${target}' (supported: ${MOBILE_LIBRARY_TARGETS.join(", ")})`;
254
+ }
255
+ return null;
256
+ }
257
+ /** The Apple SDK root for one mobile platform, discovered through xcrun the
258
+ * way Xcode's own build system selects it. Memoized per SDK name and
259
+ * selection environment: production rediscovers per process, and the two
260
+ * selection variables (DEVELOPER_DIR/SDKROOT) are already mutable-input
261
+ * keys that disable persistent caching. */
262
+ const appleSdkMemos = new Map();
263
+ function appleSdkRoot(sdk, env) {
264
+ const key = [sdk, env["PATH"] ?? "", env["DEVELOPER_DIR"] ?? "", env["SDKROOT"] ?? ""].join("\0");
265
+ const memo = appleSdkMemos.get(key);
266
+ if (memo !== undefined)
267
+ return memo;
268
+ const probe = spawnSync("xcrun", ["--sdk", sdk, "--show-sdk-path"], {
269
+ encoding: "utf8",
270
+ env,
271
+ });
272
+ const path = probe.status === 0 ? (probe.stdout ?? "").trim() : "";
273
+ if (path === "" || !existsSync(join(path, "usr", "include"))) {
274
+ throw new Error(`the ${sdk} SDK was not found (xcrun --sdk ${sdk} --show-sdk-path failed) — ` +
275
+ `iOS targets need Xcode with the ${sdk === "iphoneos" ? "iPhoneOS" : "iPhoneSimulator"} SDK installed`);
276
+ }
277
+ appleSdkMemos.set(key, path);
278
+ return path;
279
+ }
280
+ /** Compare dotted-numeric NDK version directory names, newest first. */
281
+ function compareNdkVersionsDesc(a, b) {
282
+ const as = a.split(".").map((s) => Number.parseInt(s, 10));
283
+ const bs = b.split(".").map((s) => Number.parseInt(s, 10));
284
+ for (let i = 0; i < Math.max(as.length, bs.length); i++) {
285
+ const d = (bs[i] ?? 0) - (as[i] ?? 0);
286
+ if (d !== 0 && Number.isFinite(d))
287
+ return d;
288
+ }
289
+ return a < b ? 1 : a > b ? -1 : 0;
290
+ }
291
+ /** The Android NDK sysroot for the selected environment: an explicit
292
+ * ANDROID_NDK_ROOT/ANDROID_NDK_HOME wins; otherwise the newest ndk/<version>
293
+ * under ANDROID_HOME, ANDROID_SDK_ROOT, or the platform-default SDK
294
+ * location. The prebuilt host directory is discovered rather than guessed —
295
+ * the NDK ships exactly one per host OS. Memoized per selection
296
+ * environment. */
297
+ const ndkSysrootMemos = new Map();
298
+ function androidNdkSysroot(env) {
299
+ const key = ["ANDROID_NDK_ROOT", "ANDROID_NDK_HOME", "ANDROID_HOME", "ANDROID_SDK_ROOT"]
300
+ .map((name) => env[name] ?? "")
301
+ .join("\0");
302
+ const memo = ndkSysrootMemos.get(key);
303
+ if (memo !== undefined)
304
+ return memo;
305
+ const ndkRoots = [];
306
+ const explicit = [env["ANDROID_NDK_ROOT"], env["ANDROID_NDK_HOME"]]
307
+ .find((root) => root !== undefined && root !== "");
308
+ if (explicit !== undefined) {
309
+ ndkRoots.push(explicit);
310
+ }
311
+ else {
312
+ const sdkRoots = [
313
+ env["ANDROID_HOME"],
314
+ env["ANDROID_SDK_ROOT"],
315
+ process.platform === "darwin"
316
+ ? join(homedir(), "Library", "Android", "sdk")
317
+ : process.platform === "win32"
318
+ ? join(env["LOCALAPPDATA"] ?? join(homedir(), "AppData", "Local"), "Android", "Sdk")
319
+ : join(homedir(), "Android", "Sdk"),
320
+ ].filter((root) => root !== undefined && root !== "");
321
+ for (const root of sdkRoots) {
322
+ let versions = [];
323
+ try {
324
+ versions = readdirSync(join(root, "ndk")).filter((name) => /^\d/.test(name));
325
+ }
326
+ catch {
327
+ continue;
328
+ }
329
+ versions.sort(compareNdkVersionsDesc);
330
+ ndkRoots.push(...versions.map((version) => join(root, "ndk", version)));
331
+ }
332
+ }
333
+ for (const ndk of ndkRoots) {
334
+ const prebuilt = join(ndk, "toolchains", "llvm", "prebuilt");
335
+ let hosts = [];
336
+ try {
337
+ hosts = readdirSync(prebuilt).sort();
338
+ }
339
+ catch {
340
+ continue;
341
+ }
342
+ for (const host of hosts) {
343
+ const sysroot = join(prebuilt, host, "sysroot");
344
+ if (existsSync(join(sysroot, "usr", "include", "aarch64-linux-android"))) {
345
+ ndkSysrootMemos.set(key, sysroot);
346
+ return sysroot;
347
+ }
348
+ }
349
+ }
350
+ throw new Error("no Android NDK sysroot was found — install an NDK (sdkmanager 'ndk;<version>') and/or set " +
351
+ "ANDROID_NDK_ROOT to it (ANDROID_HOME with an ndk/ directory also works). " +
352
+ "aarch64-linux-android compiles against the NDK's bionic headers.");
353
+ }
179
354
  /** Resolve native platform flags independently of the machine running tests,
180
355
  * so the host-Linux contract remains pinned on every development host. */
181
356
  function nativePlatformArgs(platform) {
@@ -191,22 +366,70 @@ export function resolveCc(env = process.env, hostPlatform = process.platform) {
191
366
  if (target !== "") {
192
367
  throw new Error(`SCRIPTC_TARGET=${target} requires SCRIPTC_CC=zigcc — the default clang path has no cross-target sysroots.`);
193
368
  }
194
- return { argv: ["clang"], target: null, ...hostArgs };
369
+ return { argv: ["clang"], target: null, zigTarget: null, ...hostArgs };
195
370
  }
196
371
  if (cc !== "zigcc") {
197
372
  throw new Error(`unknown SCRIPTC_CC '${cc}' (supported: clang, zigcc)`);
198
373
  }
199
374
  if (target === "")
200
- return { argv: ["zig", "cc"], target: null, ...hostArgs };
375
+ return { argv: ["zig", "cc"], target: null, zigTarget: null, ...hostArgs };
201
376
  if (target.includes("wasi") && target !== "wasm32-wasi") {
202
377
  throw new Error(`unsupported WASI target '${target}' (supported: wasm32-wasi)`);
203
378
  }
379
+ const mobileRefusal = mobileTargetRefusal(target, hostPlatform);
380
+ if (mobileRefusal !== null)
381
+ throw new Error(mobileRefusal);
382
+ if (isIosTarget(target)) {
383
+ // Library-mode-only target (compile()/compileC own the executable-lane
384
+ // refusal). Zig bundles no Apple libc: the compile rides the selected
385
+ // SDK sysroot, with the libc header directory spelled explicitly
386
+ // because zig's driver manages libc search itself and consults no
387
+ // -isysroot for it. The zig spelling pins the iOS 15.0 floor into
388
+ // every object's LC_BUILD_VERSION minos.
389
+ const simulator = target === "aarch64-apple-ios-simulator";
390
+ const sdk = appleSdkRoot(simulator ? "iphonesimulator" : "iphoneos", env);
391
+ const zigTarget = `aarch64-ios.${IPHONEOS_MIN_VERSION}${simulator ? "-simulator" : ""}`;
392
+ return {
393
+ argv: ["zig", "cc"],
394
+ target,
395
+ zigTarget,
396
+ targetArgs: ["-target", zigTarget, "-isysroot", sdk, "-isystem", join(sdk, "usr", "include")],
397
+ linkArgs: [],
398
+ };
399
+ }
400
+ if (isAndroidTarget(target)) {
401
+ // Library-mode-only target. Zig bundles no bionic: compiles ride the
402
+ // NDK sysroot's generic and per-triple include directories. Bionic
403
+ // supports _GNU_SOURCE like glibc (and hides some POSIX declarations
404
+ // without it); the zig spelling pins the API 26 floor, which the NDK's
405
+ // versioned stub libraries enforce at the embedder's link. API 26
406
+ // bionic carries everything the library-lane units call (arc4random_buf
407
+ // included), so no shim TU joins the archive.
408
+ const sysroot = androidNdkSysroot(env);
409
+ const zigTarget = `aarch64-linux-android.${ANDROID_MIN_API}`;
410
+ return {
411
+ argv: ["zig", "cc"],
412
+ target,
413
+ zigTarget,
414
+ targetArgs: [
415
+ "-target",
416
+ zigTarget,
417
+ "-D_GNU_SOURCE",
418
+ "-isystem",
419
+ join(sysroot, "usr", "include"),
420
+ "-isystem",
421
+ join(sysroot, "usr", "include", "aarch64-linux-android"),
422
+ ],
423
+ linkArgs: ["-lm"],
424
+ };
425
+ }
204
426
  const linux = target.includes("linux");
205
427
  const wasi = target.includes("wasi");
206
428
  const musl = target.includes("linux-musl");
207
429
  return {
208
430
  argv: ["zig", "cc"],
209
431
  target,
432
+ zigTarget: target,
210
433
  targetArgs: [
211
434
  "-target",
212
435
  target,
@@ -237,6 +460,11 @@ export function targetPlatform(driver) {
237
460
  return process.platform;
238
461
  if (driver.target.includes("wasi"))
239
462
  return "wasi";
463
+ // iOS is a darwin-family target: Mach-O objects, ld64 localization,
464
+ // POSIX path/EOL semantics. Android falls to the linux arm below —
465
+ // bionic is a linux libc and its archives are ordinary ELF.
466
+ if (isIosTarget(driver.target))
467
+ return "darwin";
240
468
  if (driver.target.includes("linux"))
241
469
  return "linux";
242
470
  if (driver.target.includes("windows"))
@@ -1079,9 +1307,12 @@ export async function compileLibArchive(opts) {
1079
1307
  * private extern, and -r without -keep_private_externs writes
1080
1308
  * private externs out as non-external symbols. Apple ASan's
1081
1309
  * image-registration COMMON remains shared so the final Mach-O
1082
- * image registers its globals once. ld64 reads every macOS
1083
- * architecture, so darwin-host macos cross triples ride the
1084
- * same arm; compileLibrary refuses macos targets elsewhere.
1310
+ * image registers its globals once. ld64 reads every Apple
1311
+ * platform's objects — macOS architectures and the iOS/
1312
+ * iOS-simulator triples alike (LC_BUILD_VERSION platform and
1313
+ * minos survive the merge) — so darwin-host macos AND ios
1314
+ * targets ride the same arm; compileLibrary refuses those
1315
+ * targets elsewhere.
1085
1316
  * ELF, native linux host — ld -r merges with --force-group-allocation
1086
1317
  * (ASan's instrumented globals ride ELF section groups whose
1087
1318
  * signatures repeat across archives sharing runtime objects;
@@ -1146,11 +1377,13 @@ async function localizeLibraryObjects(driver, arArgv, buildDir, programObject, s
1146
1377
  // Cross ELF: the cross driver's own lld performs the relocatable merge
1147
1378
  // (with the staging archive's member semantics); demotion and section-
1148
1379
  // group resolution happen in process. -nostdlib keeps zig from feeding
1149
- // libc/compiler-rt inputs into the merge.
1380
+ // libc/compiler-rt inputs into the merge. Android rides this arm
1381
+ // unchanged: bionic archives are ordinary aarch64 ELF64, and the merge
1382
+ // uses the driver's zig spelling (which pins the API floor).
1150
1383
  await run([
1151
1384
  driver.argv[0] ?? "zig",
1152
1385
  ...driver.argv.slice(1),
1153
- "-target", driver.target,
1386
+ "-target", driver.zigTarget ?? driver.target,
1154
1387
  "-nostdlib",
1155
1388
  "-r", programObject, staging,
1156
1389
  "-o", combined,
@@ -2330,6 +2563,14 @@ export async function compileC(opts) {
2330
2563
  const http = (opts.http ?? false) || nativeFetch || netIsland;
2331
2564
  const tls = (opts.tls ?? false) || nativeFetch || netIsland;
2332
2565
  const driver = resolveCc();
2566
+ // Mobile triples produce library archives, never standalone executables:
2567
+ // the executable-lane runtime (event loop, sockets, child processes) is
2568
+ // not verified on those device classes. compile() reports the SC3002
2569
+ // diagnostic first; this is the backstop for direct compileC callers.
2570
+ if (isMobileTarget(driver.target)) {
2571
+ throw new Error(`SCRIPTC_TARGET=${driver.target} builds library-mode static archives only — ` +
2572
+ `compile with a library profile (SCRIPTC_CC=zigcc scriptc build --lib --profile <profile.json>) and link the archive from the app project.`);
2573
+ }
2333
2574
  const runtimeSources = targetPlatform(driver) === "wasi"
2334
2575
  ? RUNTIME_SOURCES.filter((source) => source !== "scr_child.c")
2335
2576
  : RUNTIME_SOURCES;