Skip to main content

Phase 11. Async colouring

FieldValue
MEPMEP-49 §Phases · Phase 11
StatusLANDED
Started2026-05-28 14:02 (GMT+7)
Landed2026-05-28 14:02 (GMT+7)
Tracking issue
Tracking PR

Gate

TestPhase11Async: 7 fixtures green on Swift 6.0+, macOS 15. Gate builds each fixture and compares stdout to .expected.

Goal-alignment audit

Swift's strict concurrency model requires every async call to be explicitly awaited, and every function that calls an async function must itself be async. Phase 11 ships the colour pass that propagates async upward through the call graph, plus lowering for AsyncExpr, AwaitExpr, and the __await_all__ built-in.

Sub-phases

#ScopeStatusCommit
11.0Async colour pass over aotir.Program: propagate async upward through call graphLANDEDmep/0049-phase-11
11.1AsyncExprTask<T, Never> { body }; AwaitExprawait fut.valueLANDEDmep/0049-phase-11
11.2__await_all__(list)mochiAwaitAll(list)LANDEDmep/0049-phase-11
11.3SE-0413 typed throws; Result/throws bridgeDEFERRED
11.4try await at combined async-throwing call sitesDEFERRED

Sub-phase 11.0 -- Async colour pass

Decisions made (11.0)

ColourMap: a map[string]Colour in transpiler3/swift/colour/colour.go. Colour is an int (Blue = 0 = synchronous, Red = 1 = async).

Seeding: a function is initially Red if its body contains any AsyncExpr or AwaitExpr AST node (detected via recursive walk of aotir.Stmt/aotir.Expr).

Propagation: after seeding, the pass iterates to fixpoint: if function A calls function B and B is Red, A becomes Red. This ensures async propagates up the entire call chain.

main is Red if any function is Red: when the colour pass marks main as Red (because it directly or transitively calls an async function), the lowerer emits:

@main
struct MochiOut {
static func main() async {
// ...
}
}

Sub-phase 11.1 -- AsyncExpr and AwaitExpr lowering

Decisions made (11.1)

AsyncExprTask<T, Never>: a Mochi async { expr } becomes a Swift unstructured task:

Task<Int64, Never> {
return expr
}

The return type is inferred from the AsyncExpr.Type field in the aotir IR.

AwaitExprawait fut.value: a Mochi await futureVar becomes:

await futureVar.value

Task.value is the async property that suspends until the task completes and returns its result.

async func keyword on FuncDecl: functions marked Red in ColourMap get IsAsync: true on their sxtree.FuncDecl node. The FuncDecl.SwiftString emitter adds async before -> in the signature.

Sub-phase 11.2 -- mochiAwaitAll

Decisions made (11.2)

__await_all__(list) built-in: Mochi parallel-await of a list of tasks is lowered by the call-expression handler: when e.Func == "__await_all__", emit mochiAwaitAll(arg).

mochiAwaitAll<T: Sendable>([Task<T, Never>]) async -> [T]: defined in Async.swift. Iterates the task list sequentially with await task.value, collecting results. Sequential rather than concurrent collection is correct because the tasks are already running; this just harvests results in order.

Files changed

FilePurpose
transpiler3/swift/colour/colour.goAnalyse(prog): seeds AsyncExpr/AwaitExpr, propagates Red upward to fixpoint
transpiler3/swift/lower/lower.goChecks ColourMap; emits async func; lowers AsyncExpr, AwaitExpr, __await_all__
transpiler3/swift/sxtree/nodes.goIsAsync bool field on FuncDecl
transpiler3/swift/runtime/Sources/MochiRuntime/Async.swiftmochiAwaitAll<T: Sendable>([Task<T, Never>]) async -> [T]
transpiler3/swift/build/phase11_test.goTestPhase11Async: 7 fixtures
tests/transpiler3/swift/fixtures/phase11-async/7 fixture directories

Test set

  • TestPhase11Async -- 7 fixtures: async_all, async_basic, async_bool, async_chain, async_string, async_sum, async_two.

Deferred work

  • SE-0413 typed throws: fun foo(): T throws Efunc foo() throws(E) -> T. Deferred to Phase 11.3.
  • Result/throws bridge: Result.get(), Result(catching:). Deferred.
  • try await at combined async-throwing call sites. Deferred to Phase 11.4.
  • async let for concurrent bindings. Deferred.
  • withTaskGroup for dynamic parallel maps. Deferred.
  • Deadlock detection in the colour pass (cycle detection). Deferred.