Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions cli/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 30 additions & 4 deletions cli/Sources/Compiler/BuildPipeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] ?? [])
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
35 changes: 35 additions & 0 deletions cli/Sources/Compiler/WasmCompiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 <stdlib.h>
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 }
Expand Down Expand Up @@ -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 +
Expand Down Expand Up @@ -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)]) ]
Expand Down Expand Up @@ -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 {
Expand All @@ -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)]) ]
Expand Down