Hello, guys.
Long story short here are two pieces of code.
First:
func test<T: Decodable>(decoder: Decoder) throws -> T {
switch T.self {
default:
try T(from: decoder)
}
}
Which by running swiftc -Onone -emit-sil overflow.swift converts to:
Summary
sil_stage canonical
import Builtin
import Swift
import SwiftShims
func test(decoder: any Decoder) throws -> T where T : Decodable
// main
// Isolation: unspecified
sil @mainmain@convention: $@convention(c) (Int32, UnsafeMutablePointer) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer):
%2 = integer_literal $Builtin.Int32, 0 // user: %3
%3 = struct $Int32 (%2) // user: %4
return %3 // id: %4
} // end sil function 'main'
// test(decoder:)
// Isolation: unspecified
sil hidden @$s8overflow4test7decoderxs7Decoder_@convention_tKSeRzlF@in_guaranteed: $@convention(thi@out) (@errorin_guaranteed any Decoder) -> (@out T, @error any Error) {
// %0 "$return_value" // user: %12
// %1 "decoder" // users: %7, %2
bb0(%0 : $*T, %1 : $*any Decoder):
debug_value %1, let, name "decoder", argno 1, expr op_deref // id: %2
debug_value undef : $any Error, var, name "$error", argno 2 // id: %3
%4 = alloc_stack $T @thick // users: %13, %12, %9, %18
%5 = metatype $@thick T.Type // user: %9
%6 = alloc_stack $any Decoder // users: %11, %9, %17, %7
copy_addr %1 to [init] %6 // id: %7
%8 = witness_method $T, decodable.init!allocator @convention (Self.Type) -> (any Decoder) throws -> Self : $@convention(@initness_method:@thickDecodable) <τ_0_0@outwhere τ_@error_0 : Decodable> (@in any Decoder, @thick τ_0_0.Type)@convention-> (@out τ_0_0, @error any Error) // user: %9
try_apply %8(%4@in %6, %5) : $@c@thicknvention(witness_@outethod: D@errorcodable) <τ_0_0 where τ_0_0 : Decodable> (@in any Decoder, @thick τ_0_0.Type) -> (@out τ_0_0, @error any Error), normal bb1, error bb2 // id: %9
bb1(%10 : $()): // Preds: bb0
dealloc_stack %6 // id: %11
copy_addr [take] %4 to [init] %0 // id: %12
dealloc_stack %4 // id: %13
%14 = tuple () // user: %15
return %14 // id: %15
// %16 // user: %19
bb2(%16 : $any Error): // Preds: bb0
dealloc_stack %6 // id: %17
dealloc_stack %4 // id: %18
throw %16 // id: %19
} // end sil function '$s8overflow4test7decoderxs7Decoder_p_tKSeRzlF'
// Mappings from '#fileID' to '#filePath':
// 'overflow/overflow.swift' => 'overflow.swift'
Second:
func test<T: Decodable>(decoder: Decoder) throws -> T {
switch T.self {
default:
return try T(from: decoder)
}
}
Which by running swiftc -Onone -emit-sil no_overflow.swift converts to:
Summary
sil_stage canonical
import Builtin
import Swift
import SwiftShims
fun@main tes@convention(decoder: any Decoder) throws -> T where T : Decodable
// main
// Isolation: unspecified
sil @main : $@convention(c) (Int32, UnsafeMutablePointer) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer):
%2 = integer_literal $Builtin.Int32, 0 // user: %3
%3 = struct $Int32 (%2) // user: %4
return %3 // id: %4
} // end sil function 'ma@conventionn'
// te@in_guaranteedt(decoder:)
// Iso@outatio@error: unspecified
sil hidden @$s11no_overflow4test7decoderxs7Decoder_p_tKSeRzlF : $@convention(thin) (@in_guaranteed any Decoder) -> (@out T, @error any Error) {
// %0 "$return_value" // user: %8
// %1 "decoder" // users: %6, %2
bb0(%0 : $*T, %1 : $*any Decoder):
debug_valu@thick %1, let, name "decoder", argno 1, expr op_deref // id: %2
debug_value undef : $any Error, var, name "$error", argno 2 // id: %3
%4 = metatype $@thick T.Type // user: %8
%5 = alloc_stack $any Decoder // users: %10, %8, %14, %6@conventioncopy_addr %1 to [init] %5 // id: %6
%7 @in witness_metho@thick $T, decodable.init!alloc@outtor : (@errorelf.Type) -> (any Decoder) throws -> Self : $@conven@conventionion(witness_method: Decodable) <τ_0_0 where τ_0_0 : Decodable@in (@in any Deco@thicker, @thick τ_0_0.@outype) -> (@out τ_0_0, @error any Error) // user: %8
try_apply %7(%0, %5, %4) : $@convention(witness_method: Decodable) <τ_0_0 where τ_0_0 : Decodable> (@in any Decoder, @thick τ_0_0.Type) -> (@out τ_0_0, @error any Error), normal bb1, error bb2 // id: %8
bb1(%9 : $()): // Preds: bb0
dealloc_stack %5 // id: %10
%11 = tuple () // user: %12
return %11 // id: %12
// %13 // user: %15
bb2(%13 : $any Error): // Preds: bb0
dealloc_stack %5 // id: %14
throw %13 // id: %15
} // end sil function '$s11no_overflow4test7decoderxs7Decoder_p_tKSeRzlF'
// Mappings from '#fileID' to '#filePath':
// 'no_overflow/no_overflow.swift' => 'no_overflow.swift'
swiftc --version
swift-driver version: 1.127.15 Apple Swift version 6.2.4 (swiftlang-6.2.4.1.4 clang-1700.6.4.2)
Snippets differ only in explicit vs. explicit return.
Generated SIL in first example contains (as opposed to second example) line such as:
%4 = alloc_stack $T
which means a stack allocation for return value (if I understand correctly).
It’s optimised away in release builds.
Just wondering if this is a bug or correct behavior?
Was quite interesting issue for debugging
: return was removed by formatter (didn’t know that) and decoding crashed with generic bad access error. As you might have guessed the crash was caused by stack overflow.