diff --git a/cli/Package.resolved b/cli/Package.resolved index 1d80178..ca3abc1 100644 --- a/cli/Package.resolved +++ b/cli/Package.resolved @@ -10,6 +10,42 @@ "version" : "1.8.1" } }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "0442cb5a3f98ab802acb777929fdb446bda11a34", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "a0cb0954ecb21e4e31b0070e6ed5674e8556685a", + "version" : "1.6.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log", + "state" : { + "revision" : "9c6fb14227f55d8f711ce3847dc2f419fb0ecacb", + "version" : "1.15.1" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio", + "state" : { + "revision" : "a931f2c1de8dd49381ce3bf2e279d033f68d8865", + "version" : "2.102.0" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", diff --git a/cli/Sources/Compiler/BuildPipeline.swift b/cli/Sources/Compiler/BuildPipeline.swift index 2c1fbfe..dd1244e 100644 --- a/cli/Sources/Compiler/BuildPipeline.swift +++ b/cli/Sources/Compiler/BuildPipeline.swift @@ -1264,7 +1264,12 @@ public struct BuildPipeline { rejectedExports: rejectedExports.map { (name: $0.0, reason: $0.1) }) } - let candidates = wasmSources.map { + PATCH_SWIFTUI_ONLY=1 skips the default (function-thunk) + // module entirely. Its generated JSON-ABI wrappers force T2 (~11.7 MB); with + // no primary module the SwiftUI guest is promoted to be `module.wasm`. + let swiftUIOnly = ProcessInfo.processInfo.environment["PATCH_SWIFTUI_ONLY"] != nil + if swiftUIOnly { FileHandle.standardError.write(Data("PATCH_SWIFTUI_ONLY: skipping the default function module; shipping the SwiftUI guest only\n".utf8)) } + let candidates = swiftUIOnly ? [] : wasmSources.map { WasmConvergence.Candidate(functionID: $0.lastPathComponent, sourceFile: $0, exports: exportsBySource[$0] ?? []) } @@ -1375,7 +1380,7 @@ public struct BuildPipeline { // Lowers a declarative UIKit cell's construction to a guest `uikit_configure` // export + ships it. Gated by the SAME `swiftui` toggle (the UIKit path is part // of the same view-patching feature) and skipped without the toolchain. - if swiftUIOn, !(outcome.toolchainUnavailable) { + if ProcessInfo.processInfo.environment["PATCH_UIKIT"] != "0", swiftUIOn, !(outcome.toolchainUnavailable) { if let uk = try runUIKitLowering( sourceDir: sourceDir, buildDir: buildDir, compiler: compiler, defaultModule: moduleURL) { @@ -1599,6 +1604,16 @@ public struct BuildPipeline { /// module is reported but not promoted — never a regression, never a broken module). /// Copies (not moves) so the original guest path stays valid for reporting. @discardableResult + /read a packaging tier from an env var (`t0` | `t1` | `t2`). + static func envTier(_ name: String) -> PackagingTier? { + switch ProcessInfo.processInfo.environment[name]?.lowercased() { + case "t0", "t0embedded", "embedded": return .t0Embedded + case "t1", "t1stdlib", "stdlib": return .t1Stdlib + case "t2", "t2foundation", "foundation": return .t2Foundation + default: return nil + } + } + static func promoteGuestToPrimary(guest: URL, primary: URL) -> Bool { let fm = FileManager.default guard fm.fileExists(atPath: guest.path), @@ -2019,8 +2034,16 @@ public struct BuildPipeline { WasmConvergence.Candidate(functionID: $0.lastPathComponent, sourceFile: $0, exports: exportsBySource[$0] ?? []) } + the guest IR is Foundation-free by design (see + // SwiftUIGuestEmitter.irResourceNames), so start it at T0 and let the + // convergence loop escalate on a real compile failure. Upstream pins T2, + // which is the ~11.7 MB floor. PATCH_SWIFTUI_START_TIER / _MAX_TIER + // (t0|t1|t2) override per run for measurement. + let guestStart = Self.envTier("PATCH_SWIFTUI_START_TIER") ?? .t0Embedded + let guestMax = Self.envTier("PATCH_SWIFTUI_MAX_TIER") + log("guest module convergence: start=\(guestStart.rawValue) max=\(guestMax?.rawValue ?? "none")") let outcome = try convergence.converge(candidates, outputModule: swModuleURL, - startTier: .t2Foundation) + startTier: guestStart, maxTier: guestMax) lastOutcome = outcome // SHIP iff a module compiled AND it actually carries view exports. A subtle // trap: the convergence loop is FILE-level — on a wrapper compile error it @@ -2175,8 +2198,11 @@ public struct BuildPipeline { WasmConvergence.Candidate(functionID: $0.lastPathComponent, sourceFile: $0, exports: exportsBySource[$0] ?? []) } + same as the SwiftUI guest — start embedded, escalate on failure. let outcome = try WasmConvergence(compiler: compiler).converge( - candidates, outputModule: ukModuleURL, startTier: .t2Foundation) + candidates, outputModule: ukModuleURL, + startTier: Self.envTier("PATCH_UIKIT_START_TIER") ?? .t0Embedded, + maxTier: Self.envTier("PATCH_UIKIT_MAX_TIER")) let shippedExports = outcome.compiled.flatMap { $0.exports } .filter { $0 != "patch_malloc" && $0 != "patch_free" } guard outcome.moduleURL != nil, !shippedExports.isEmpty else { diff --git a/cli/Sources/Compiler/WasmCompiler.swift b/cli/Sources/Compiler/WasmCompiler.swift index e9130e1..fe3bde4 100644 --- a/cli/Sources/Compiler/WasmCompiler.swift +++ b/cli/Sources/Compiler/WasmCompiler.swift @@ -177,6 +177,13 @@ public struct SwiftWasmCompiler: WasmCompiling { try fm.createDirectory(at: dst.deletingLastPathComponent(), withIntermediateDirectories: true) try file.contents.write(to: dst, atomically: true, encoding: .utf8) } + embedded Swift on wasm leaves `_swift_stdlib_strtod_clocale` + // (what `Double(String)` lowers to) to the platform; the full runtime defines + // it at T1/T2, so the shim is written ONLY at T0 to avoid a duplicate symbol. + if tier == .t0Embedded { + let shim = work.appendingPathComponent("Sources/\(CHeaderBridge.cTargetName)/swift_stdlib_shims.c") + try Self.embeddedStdlibShimSource.write(to: shim, atomically: true, encoding: .utf8) + } try packageManifest(exports: exportedSymbols).write( to: work.appendingPathComponent("Package.swift"), atomically: true, encoding: .utf8) @@ -427,6 +434,18 @@ public struct SwiftWasmCompiler: WasmCompiling { /// treats nil as "no guard verdict" — the existing toolchain error handling wins). /// Minimal hand-rolled parser: WASM is `\0asm` + version, then sections; the /// import section (id 2) is a vec of (module: name, field: name, desc). + /C shim for the one Swift runtime hook embedded wasm needs + /// from the platform. wasi-libc's `strtod` is the C-locale parse. + static let embeddedStdlibShimSource = """ + #include + const char *_swift_stdlib_strtod_clocale(const char *nptr, double *outResult) { + char *end = NULL; + *outResult = strtod(nptr, &end); + return end; + } + + """ + static func unsatisfiableEnvImports(_ bytes: [UInt8]) -> [String]? { guard bytes.count > 8, bytes[0] == 0x00, bytes[1] == 0x61, bytes[2] == 0x73, bytes[3] == 0x6d else { return nil } @@ -510,6 +529,7 @@ public struct SwiftWasmCompiler: WasmCompiling { private func realSourceManifest(moduleName: String, exports: [String], fusion: Bool = false) -> String { var flags = ["-Xclang-linker", "-mexec-model=reactor", "-Xlinker", "--allow-undefined"] for symbol in exports { flags += ["-Xlinker", "--export-if-defined=\(symbol)"] } + if tier == .t0Embedded && !fusion { flags += ["-Xlinker", "-lswiftUnicodeDataTables"] } let flagList = flags.map { "\"\($0)\"" }.joined(separator: ", ") // BREAKTHROUGH #9 — T0 EMBEDDED real-source compile. When the tier is // `.t0Embedded`, build the real-module target with the Embedded feature + @@ -537,6 +557,11 @@ public struct SwiftWasmCompiler: WasmCompiling { swiftSettings: [ .swiftLanguageMode(.v5), .enableExperimentalFeature("Embedded"), + the guest IR guards its Codable conformances + // behind `#if !FRONTIER_EMBEDDED` (see Conformance.swift) but + // upstream never defined it, so every T0 compile failed on + // `Codable is unavailable in embedded Swift`. + .define("FRONTIER_EMBEDDED"), .unsafeFlags(["-wmo"]) ], linkerSettings: [ .unsafeFlags([\(flagList)]) ] @@ -608,6 +633,11 @@ public struct SwiftWasmCompiler: WasmCompiling { for symbol in exports { flags += ["-Xlinker", "--export-if-defined=\(symbol)"] } + embedded Swift's String comparison/normalization calls + // `_swift_stdlib_getNormData` & co, which live in the SDK's static + // `libswiftUnicodeDataTables.a`. Without it `--allow-undefined` leaves them + // as `env.*` imports the SDK runtime cannot satisfy (instantiate fails). + if tier == .t0Embedded { flags += ["-Xlinker", "-lswiftUnicodeDataTables"] } let flagList = flags.map { "\"\($0)\"" }.joined(separator: ", ") if tier == .t0Embedded { @@ -627,6 +657,11 @@ public struct SwiftWasmCompiler: WasmCompiling { dependencies: ["\(CHeaderBridge.cTargetName)"], swiftSettings: [ .enableExperimentalFeature("Embedded"), + the guest IR guards its Codable conformances + // behind `#if !FRONTIER_EMBEDDED` (see Conformance.swift) but + // upstream never defined it, so every T0 compile failed on + // `Codable is unavailable in embedded Swift`. + .define("FRONTIER_EMBEDDED"), .unsafeFlags(["-wmo"]) ], linkerSettings: [ .unsafeFlags([\(flagList)]) ]