Thanks for using Compiler Explorer
Sponsors
Jakt
C++
Ada
Analysis
Android Java
Android Kotlin
Assembly
C
C3
Carbon
C++ (Circle)
CIRCT
Clean
CMake
CMakeScript
COBOL
C++ for OpenCL
MLIR
Cppx
Cppx-Blue
Cppx-Gold
Cpp2-cppfront
Crystal
C#
CUDA C++
D
Dart
Elixir
Erlang
Fortran
F#
GLSL
Go
Haskell
HLSL
Hook
Hylo
IL
ispc
Java
Julia
Kotlin
LLVM IR
LLVM MIR
Modula-2
Nim
Objective-C
Objective-C++
OCaml
Odin
OpenCL C
Pascal
Pony
Python
Racket
Ruby
Rust
Snowball
Scala
Slang
Solidity
Spice
SPIR-V
Swift
LLVM TableGen
Toit
TypeScript Native
V
Vala
Visual Basic
Vyper
WASM
Zig
Javascript
GIMPLE
Ygen
swift source #1
Output
Compile to binary object
Link to binary
Execute the code
Intel asm syntax
Demangle identifiers
Verbose demangling
Filters
Unused labels
Library functions
Directives
Comments
Horizontal whitespace
Debug intrinsics
Compiler
x86-64 swiftc 3.1.1
x86-64 swiftc 4.0.2
x86-64 swiftc 4.0.3
x86-64 swiftc 4.1
x86-64 swiftc 4.1.1
x86-64 swiftc 4.1.2
x86-64 swiftc 4.2
x86-64 swiftc 5.0
x86-64 swiftc 5.1
x86-64 swiftc 5.10
x86-64 swiftc 5.2
x86-64 swiftc 5.3
x86-64 swiftc 5.4
x86-64 swiftc 5.5
x86-64 swiftc 5.6
x86-64 swiftc 5.7
x86-64 swiftc 5.8
x86-64 swiftc 5.9
x86-64 swiftc nightly
Options
Source code
// Example to illustrate problems with Swift's stack usage in non-optimized // builds. // // The functions below use switch/case statements, but this appears to apply // to any branching structure; for example, rewriting them as if/else-if // doesn't resolve the issue. struct Value { enum Error: Swift.Error { case fieldTooBig } var a: Int = 0 var b: Int = 0 var c: Int = 0 } ////////////////////////////////////////////////////////////////////////// // Combined: The body of each case is written as normal. // // This is what I expect the "typical" code would look like; it's nothing // fancy, just some functions with switch/case statements that use some // local stack space in each of the cases. The problem is that in debug // builds, codegen allocates space for *all* the cases (all the basic // blocks, if I had to guess) without considering that they might be // mutually exclusive, so if the number of cases is large and/or the // local variables are large, one could exhaust the stack fairly quickly; // especially in a recursive situation or in a background thread where // the default stack size is much smaller. func modifyValueCombined1(_ value: inout Value, fieldNumber: Int) throws { // sub rsp, 216 switch fieldNumber { case 0: let delta = Int.random(in: -10...10) try modifyField(&value.a, delta) default: preconditionFailure() } } func modifyValueCombined2(_ value: inout Value, fieldNumber: Int) throws { // sub rsp, 328 switch fieldNumber { case 0: let delta = Int.random(in: -10...10) try modifyField(&value.a, delta) case 1: let delta = Int.random(in: -20...20) try modifyField(&value.b, delta) default: preconditionFailure() } } func modifyValueCombined3(_ value: inout Value, fieldNumber: Int) throws { // sub rsp, 424 switch fieldNumber { case 0: let delta = Int.random(in: -10...10) try modifyField(&value.a, delta) case 1: let delta = Int.random(in: -20...20) try modifyField(&value.b, delta) case 2: let delta = Int.random(in: -30...30) try modifyField(&value.c, delta) default: preconditionFailure() } } ////////////////////////////////////////////////////////////////////////// // Isolated: The body of each case is in its own closure which is executed // immediately. // // This moves the local storage of each case into its own frame that's // only active when that case is executed, so the outer function's stack // usage is no longer proportional to the sum of the usage of all the // cases. // // However, error handling still uses a small amount of local stack space // per case, so this still grows linearly as cases are added (in debug // builds). func modifyValueIsolated1(_ value: inout Value, fieldNumber: Int) throws { // sub rsp, 136 switch fieldNumber { case 0: try { let delta = Int.random(in: -10...10) try modifyField(&value.a, delta) }() default: preconditionFailure() } } func modifyValueIsolated2(_ value: inout Value, fieldNumber: Int) throws { // sub rsp, 168 switch fieldNumber { case 0: try { let delta = Int.random(in: -10...10) try modifyField(&value.a, delta) }() case 1: try { let delta = Int.random(in: -20...20) try modifyField(&value.b, delta) }() default: preconditionFailure() } } func modifyValueIsolated3(_ value: inout Value, fieldNumber: Int) throws { // sub rsp, 184 switch fieldNumber { case 0: try { let delta = Int.random(in: -10...10) try modifyField(&value.a, delta) }() case 1: try { let delta = Int.random(in: -20...20) try modifyField(&value.b, delta) }() case 2: try { let delta = Int.random(in: -30...30) try modifyField(&value.c, delta) }() default: preconditionFailure() } } ////////////////////////////////////////////////////////////////////////// // Isolated, no throws: The same as above, but with errors removed to show // that the stack usage becomes constant. However, this is an infeasible // in most situations. func modifyValueIsolatedNoThrows1(_ value: inout Value, fieldNumber: Int) { // sub rsp, 104 switch fieldNumber { case 0: { let delta = Int.random(in: -10...10) modifyFieldNoThrows(&value.a, delta) }() default: preconditionFailure() } } func modifyValueIsolatedNoThrows2(_ value: inout Value, fieldNumber: Int) { // sub rsp, 104 switch fieldNumber { case 0: { let delta = Int.random(in: -10...10) modifyFieldNoThrows(&value.a, delta) }() case 1: { let delta = Int.random(in: -20...20) modifyFieldNoThrows(&value.b, delta) }() default: preconditionFailure() } } func modifyValueIsolatedNoThrows3(_ value: inout Value, fieldNumber: Int) { // sub rsp, 104 switch fieldNumber { case 0: { let delta = Int.random(in: -10...10) modifyFieldNoThrows(&value.a, delta) }() case 1: { let delta = Int.random(in: -20...20) modifyFieldNoThrows(&value.b, delta) }() case 2: { let delta = Int.random(in: -30...30) modifyFieldNoThrows(&value.c, delta) }() default: preconditionFailure() } } ////////////////////////////////////////////////////////////////////////// // Helper functions func modifyField(_ value: inout Int, _ delta: Int) throws { value += delta if value > 100 { throw Value.Error.fieldTooBig } } func modifyFieldNoThrows(_ value: inout Int, _ delta: Int) { value += delta }
Become a Patron
Sponsor on GitHub
Donate via PayPal
Source on GitHub
Mailing list
Installed libraries
Wiki
Report an issue
How it works
Contact the author
CE on Mastodon
CE on Bluesky
About the author
Statistics
Changelog
Version tree