# Why does \`throw\` break type inference?

**URL:** <https://discourse.julialang.org/t/why-does-throw-break-type-inference/83934>\
**Category:** Performance\
**Created:** [July 8, 2022, 8:52am UTC](https://discourse.julialang.org/t/why-does-throw-break-type-inference/83934 "2022-07-08T08:52:24Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![yiyuezhuo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yiyuezhuo/32/27101_2.png) [@yiyuezhuo](https://discourse.julialang.org/u/yiyuezhuo)\
**Post date:** [July 8, 2022, 8:52am UTC](https://discourse.julialang.org/t/why-does-throw-break-type-inference/83934/1 "2022-07-08T08:52:24Z")

</div>

I’m researching SSA-IR, and I found that most types of Julia functions are determined by compile-time, as they’re converted to `invoke` type from `call` type with a `MethodInstance` in `code_typed`. But there are still some `call` left there. Some of them are `Core.Builtin`, so I guess it’s unnecessary to “optimize” them, as they’re not even generic functions. And others come with some `throw` statements. The following code demonstrates it:

```julia
julia> function g()
           println(string(1, "2"))
           throw(string(2, "3"))
           println(string(3, "4"))
       end
g (generic function with 1 method)

julia> @code_typed g()
CodeInfo(
1 ─ %1 = Main.string(1, "2")::Any
│ Main.println(%1)::Any
│ %3 = Main.string(2, "3")::Any
│ Main.throw(%3)::Union{}
└── unreachable
) => Union{}

julia> function gg()
                  println(string(1, "2"))
                  println(string(3, "4"))
              end
gg (generic function with 1 method)

julia> @code_typed gg()
CodeInfo(
1 ─ %1 = invoke Base.print_to_string(1::Int64, "2"::Vararg{Any})::String
│ invoke Main.println(%1::String)::Any
│ %3 = invoke Base.print_to_string(3::Int64, "4"::Vararg{Any})::String
│ %4 = invoke Main.println(%3::String)::Nothing
└── return %4
) => Nothing

julia> @code_typed optimize=false g()
CodeInfo(
1 ─ %1 = Main.string(1, "2")::Any
│ Main.println(%1)::Any
│ %3 = Main.string(2, "3")::Any
│ Main.throw(%3)::Union{}
│ Core.Const(:(Main.string(3, "4")))::Union{}
│ Core.Const(:(Main.println(%5)))::Union{}
└── Core.Const(:(return %6))::Union{}
) => Union{}

julia> @code_typed optimize=false gg()
CodeInfo(
1 ─ %1 = Main.string(1, "2")::String
│ Main.println(%1)::Any
│ %3 = Main.string(3, "4")::String
│ %4 = Main.println(%3)::Core.Const(nothing)
└── return %4
) => Nothing

```

I see no reason why the first `string` call in the `g` is not specialized like what happened in the `gg` call. Maybe Julia considers it “unnecessary” as well since there’s an ensured `throw`?

---

<div class="post-metadata">

**Author:** ![Liozou](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/liozou/32/35118_2.png) [@Liozou](https://discourse.julialang.org/u/Liozou)\
**Post date:** [July 8, 2022, 9:17am UTC](https://discourse.julialang.org/t/why-does-throw-break-type-inference/83934/2 "2022-07-08T09:17:10Z")

</div>

This is not exactly the case anymore in julia v1.8:

```julia
julia> @code_typed g()
CodeInfo(
1 ─ %1 = invoke Base.print_to_string(1::Int64, "2"::Vararg{Any})::String
│ Main.println(%1)::Any
│ %3 = Main.string(2, "3")::Any
│ Main.throw(%3)::Union{}
└── unreachable
) => Union{}

```

But in general, I think that any code path systematically leading to an exception is generally not fully optimized (for example the second string), because the runtime cost of throwing an exception is very high anyway. And avoiding useless optimizations allows the compiler to run fast, which is crucial to reduce latency.

---

<div class="post-metadata">

**Author:** ![Liozou](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/liozou/32/35118_2.png) [@Liozou](https://discourse.julialang.org/u/Liozou)\
**Post date:** [July 8, 2022, 9:25am UTC](https://discourse.julialang.org/t/why-does-throw-break-type-inference/83934/3 "2022-07-08T09:25:01Z")

</div>

I think [PR #35982](https://github.com/JuliaLang/julia/pull/35982) might be of interest to you. You can also have a look at the `LazyString` mechanism introduced in v1.8 in [PR 33711](https://github.com/JuliaLang/julia/pull/33711), which is specifically made to make the compiler not to eagerly optimize the string computation in a `throw` block.
