Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new macro @outline, and use it in @assert. #57122

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Compiler/test/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2309,3 +2309,42 @@ g_noinline_invoke(x) = f_noinline_invoke(x)
let src = code_typed1(g_noinline_invoke, (Union{Symbol,Nothing},))
@test !any(@nospecialize(x)->isa(x,GlobalRef), src.code)
end

@testset "@outline" begin
@testset "basic" begin
@test @outline(2) == 2
@test @outline(2 + 2) == 4

x = 10
@test @outline(x + 1) == 11
@test @outline(x + x) == 20

negate(x) = -x
@test @outline(negate(+(1, 2))) == -3
end

@testset "throw exception" begin
@test_throws BoundsError((), 1) @outline(throw(BoundsError((), 1)))
a = []
@test_throws BoundsError(a, 1) @outline(throw(BoundsError(a, 1)))

@test_throws AssertionError("false") @outline @assert false
@test_throws AssertionError("violated") @outline @assert false "violated"

x = 10
@test_throws AssertionError("x == 0") @outline @assert x == 0
@test_throws AssertionError("x: 10") @outline @assert x == 0 "x: $x"
end

@testset "in a function" begin
function get_first(tup)
if isempty(tup)
@outline(throw(BoundsError(tup, 1)))
end
return first(tup)
end
@test get_first((1,)) == 1
@test get_first((1,2)) == 1
@test_throws BoundsError((), 1) get_first(())
end
end
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ New library functions
* `insertdims(array; dims)` allows to insert singleton dimensions into an array which is the inverse operation to `dropdims`. ([#45793])
* The new `Fix` type is a generalization of `Fix1/Fix2` for fixing a single argument ([#54653]).
* `Sys.detectwsl()` allows to testing if Julia is running inside WSL at runtime. ([#57069])
* `@outline expr` moves `expr` out to a separate, noinlined function -- a common performance optimization for error-throwing parts of code. ([#57122])

New library features
--------------------
Expand Down
6 changes: 3 additions & 3 deletions base/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,14 @@ macro assert(ex, msgs...)
# message is an expression needing evaluating
# N.B. To reduce the risk of invalidation caused by the complex callstack involved
# with `string`, use `inferencebarrier` here to hide this `string` from the compiler.
msg = :(Main.Base.inferencebarrier(Main.Base.string)($(esc(msg))))
msg = :($Main.Base.inferencebarrier($Main.Base.string)($(msg)))
elseif isdefined(Main, :Base) && isdefined(Main.Base, :string) && applicable(Main.Base.string, msg)
msg = Main.Base.string(msg)
else
# string() might not be defined during bootstrap
msg = :(Main.Base.inferencebarrier(_assert_tostring)($(Expr(:quote,msg))))
msg = :($Main.Base.inferencebarrier($_assert_tostring)($(Expr(:quote,msg))))
end
return :($(esc(ex)) ? $(nothing) : throw(AssertionError($msg)))
return esc(:($(ex) ? $(nothing) : $Base.@outline($throw($AssertionError($msg)))))
end

# this may be overridden in contexts where `string(::Expr)` doesn't work
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,7 @@ export
@simd,
@inline,
@noinline,
@outline,
@nospecialize,
@specialize,
@polly,
Expand Down
43 changes: 43 additions & 0 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,49 @@ macro noinline(x)
return annotate_meta_def_or_block(x, :noinline)
end

"""
@outline expr
Outline an expression into its own function, and call that function.
This macro introduces a "function barrier", which can be helpful in some code optimization
scenarios. The expr is extracted into an outlined function, which is marked `@noinline`.
Outlining an expr can be used to make a function smaller, e.g. by outlining an unlikely
branch, which could help with runtime performance by improving instruction cache locality,
and could help with compilation performance since two smaller functions can sometimes compile
faster than one larger function. Finally, outlining can be useful for type-stability, by
outlining a type unstable block within a hot loop, where the outlined function could be type
stable.
A common use case is to `@outline` the code that throws exceptions, since this should be a
rare case, but it can introduce a lot of complexity to the generated code, which can
sometimes harm the compiler's ability to optimize.
# Examples
```julia
function getindex(container, index)
if index < 1 || index > length(container)
# Outline this throw, since constructing a BoundsError requires boxing
# the arguments, which produces a lot of code.
@outline throw(BoundsError(container, index))
end
return container.data[index]
end
```
"""
macro outline(expr)
vars = esc.(_free_vars(expr))
quote
@noinline outline($(vars...)) = $(esc(expr))

outline($(vars...))
end
end
_free_vars(s::Symbol) = [s]
_free_vars(_) = []
_free_vars(e::Expr) = isempty(e.args) ? [] : unique!(mapreduce(_free_vars, vcat, e.args))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think unique! is not defined yet so this fails to compile for me

although, what happens if you use outline as a zero-arg closure and don't pass the vars in explicitly at all? like

macro outline2(expr)
    local fname = gensym(:outlined_expr)
    quote
        @noinline $(fname)() = $(esc(expr))
        $(fname)()
    end
end

comparing the @code_native of

foo1() = @outline rand() > 0.5 || throw(AssertionError("abc"))
foo2() = @outline2 rand() > 0.5 || throw(AssertionError("abc"))

it seems a lot simpler, but I'm not an expert at these things so maybe I'm missing something


"""
Base.@constprop setting [ex]
Expand Down
Loading