Is there something like cargo check
for spm? Or swift build
is fast enough that there is no need to perform a check-only process during development?
My (limited) understanding of the Swift build process is that compiler errors are produced at many levels of the compilation process, so there isn’t actually that much work left to do after all possible warnings/errors have been produced. Plus, IDEs are able to compile in the background and surface live issues in real time which replaces manually running a check separately from the build.
Diagnostics are emitted from parsing, type checking, SILGen and the mandatory SIL passes. A "check-only" build could run those passes, but skip performance optimization and code generation. If your project has multiple modules, you also need .swiftmodule files, but a good approximation would be to only emit .swiftmodule files and not executables or shared libraries. However, I don't know if swiftpm is set up to do that today.
this is a little bit of a tangent, but are there settings that can be used to reduce the amount of memory used by the compiler, possibly at the expense of compilation speed?
the use case i am imagining is similar to OP’s, in that i would only be using the compiler to generate documentation, and i would not actually be using the compiled binaries at all.
I would guess that a debug build with -disable-batch-mode will use less memory, but I’ll let you test this theory ;-)
I can confirm this. In my observations, emitting only the .swiftmodule files and skipping object files is significantly faster for certain modules. SwiftSyntax, for example, takes ~25 seconds to do a full compile on my M1 Max, but emitting just the module only takes 5 seconds, since SwiftPM is using -experimental-skip-non-inlinable-function-bodies
for its emit-module job.
When generating docs with SwiftPM, is its build planning sophisticated enough that it can only ask for modules to be emitted and not object files? I haven't poked at it that deeply.
OP was specifically interested in seeing all diagnostics, which precludes body skipping. However, emitting a module should still be faster even without body skipping, because you can skip LLVM code generation and optimization.
One extreme idea would be to run two jobs per module—one with body skipping to unblock dependents faster and a second to do full type checking but not generate any outputs—but I imagine there's not a noticeable enough performance difference between those two modes for it to be worthwhile?
swiftc main.swift -typecheck
If your project imports other modules, you need to specify modules folder with -I
and -L
options. You may also want to specify -gnone -Onone
. If your project is a library, add -parse-as-library
.
-typecheck alone won’t emit all diagnostics though, for example the following code is well-typed, but it has some problems discovered later in SIL lowering:
func f() -> Int {
var x: Int // uninitialized
if x == 0 {
return 3
}
// missing return on false branch
}
Out of curiosity of wanting to understand how Swift actually works what are the mandatory SIL passes?
Mandatory passes diagnose problems that require dataflow analysis to detect — they would be awkward to implement on type checked AST directly. They also implement transformations that are needed for acceptable performance at -Onone.
They’re defined in swift/lib/SILOptimizer/Mandatory at main · swiftlang/swift · GitHub.
A few important ones:
- Diagnosing unreachable code
- Diagnosing accesses to uninitialized memory locations
- Diagnosing invalid uses of move-only types
- Promoting heap-allocated ‘var’ boxes to the stack, which should be most of them
- Some forms of constant propagation and folding
- Inlining @_transparent functions, like Int.+, that we want even at -Onone
As an alternative, are there some suggested flags to disable/reduce optimization level and skip linking phase in order to reduce build time?
Xcode is pretty good at give realtime-ish feedback but less so for VSCode. It kinda works but not really when working with a mono repo with multiple packages and making cross packages edits.
If the goal is to run the compiler to the point of producing all diagnostics, but no further, then -emit-sil
should do the job. This will run typechecking, SIL generation, and the mandatory SIL passes, but no performance optimization or LLVM- or machine-level code generation.
tried with swift build -Xswiftc -emit-sil
but seems like actually dumping all those output to stdout makes it slower than just swift build
. > /dev/null
doesn't help as well.