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

supports @inline/@noinline annotations within a function body #41312

Merged
merged 4 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ New language features
---------------------

* `Module(:name, false, false)` can be used to create a `module` that does not import `Core`. ([#40110])
* `@inline` and `@noinline` annotations may now be used in function bodies. ([#40754])
aviatesk marked this conversation as resolved.
Show resolved Hide resolved

Language changes
----------------
Expand Down
47 changes: 37 additions & 10 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -188,19 +188,32 @@ Give a hint to the compiler that this function is worth inlining.
Small functions typically do not need the `@inline` annotation,
as the compiler does it automatically. By using `@inline` on bigger functions,
an extra nudge can be given to the compiler to inline it.
This is shown in the following example:
`@inline` can be applied immediately before the definition or in its function body.
```julia
@inline function bigfunction(x)
#=
Function Definition
=#
# annotate long-form definition
@inline function longdef(x)
...
end
# annotate short-form definition
@inline shortdef(x) = ...
# annotate anonymous function that a `do` block creates
f() do
@inline
...
end
```
!!! compat "Julia 1.7"
The usage within a function body requires at least Julia 1.7.
aviatesk marked this conversation as resolved.
Show resolved Hide resolved
"""
macro inline(ex)
esc(isa(ex, Expr) ? pushmeta!(ex, :inline) : ex)
end
macro inline() Expr(:meta, :inline) end

"""
@noinline
Expand All @@ -209,22 +222,36 @@ Give a hint to the compiler that it should not inline a function.
Small functions are typically inlined automatically.
By using `@noinline` on small functions, auto-inlining can be
prevented. This is shown in the following example:
prevented.
`@noinline` can be applied immediately before the definition or in its function body.
```julia
@noinline function smallfunction(x)
#=
Function Definition
=#
# annotate long-form definition
@noinline function longdef(x)
...
end
# annotate short-form definition
@noinline shortdef(x) = ...
# annotate anonymous function that a `do` block creates
f() do
@noinline
...
end
```
!!! compat "Julia 1.7"
The usage within a function body requires at least Julia 1.7.
aviatesk marked this conversation as resolved.
Show resolved Hide resolved
!!! note
If the function is trivial (for example returning a constant) it might get inlined anyway.
"""
macro noinline(ex)
esc(isa(ex, Expr) ? pushmeta!(ex, :noinline) : ex)
end
macro noinline() Expr(:meta, :noinline) end
Copy link
Member

Choose a reason for hiding this comment

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

Does this mean we can delete Core.@_noinline_meta() also?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I think we can unify them. Will do that in a follow up PR though.


"""
@pure ex
Expand Down
117 changes: 117 additions & 0 deletions test/compiler/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,120 @@ end
using Base.Experimental: @opaque
f_oc_getfield(x) = (@opaque ()->x)()
@test fully_eliminated(f_oc_getfield, Tuple{Int})

# check if `x` is a statically-resolved call of a function whose name is `sym`
isinvoke(@nospecialize(x), sym::Symbol) = isinvoke(x, mi->mi.def.name===sym)
function isinvoke(@nospecialize(x), pred)
if Meta.isexpr(x, :invoke)
return pred(x.args[1]::Core.MethodInstance)
end
return false
end
code_typed1(args...; kwargs...) = (firstfirst)(code_typed(args...; kwargs...))::Core.CodeInfo

@testset "@inline/@noinline annotation before definition" begin
m = Module()
@eval m begin
@inline function _def_inline(x)
# this call won't be resolved and thus will prevent inlining to happen if we don't
# annotate `@inline` at the top of this function body
return unresolved_call(x)
end
def_inline(x) = _def_inline(x)
@noinline _def_noinline(x) = x # obviously will be inlined otherwise
def_noinline(x) = _def_noinline(x)

# test that they don't conflict with other "before-definition" macros
@inline Base.@aggressive_constprop function _def_inline_noconflict(x)
# this call won't be resolved and thus will prevent inlining to happen if we don't
# annotate `@inline` at the top of this function body
return unresolved_call(x)
end
def_inline_noconflict(x) = _def_inline_noconflict(x)
@noinline Base.@aggressive_constprop _def_noinline_noconflict(x) = x # obviously will be inlined otherwise
def_noinline_noconflict(x) = _def_noinline_noconflict(x)
end

let ci = code_typed1(m.def_inline, (Int,))
@test all(ci.code) do x
!isinvoke(x, :_def_inline)
end
end
let ci = code_typed1(m.def_noinline, (Int,))
@test any(ci.code) do x
isinvoke(x, :_def_noinline)
end
end
# test that they don't conflict with other "before-definition" macros
let ci = code_typed1(m.def_inline_noconflict, (Int,))
@test all(ci.code) do x
!isinvoke(x, :_def_inline_noconflict)
end
end
let ci = code_typed1(m.def_noinline_noconflict, (Int,))
@test any(ci.code) do x
isinvoke(x, :_def_noinline_noconflict)
end
end
end

@testset "@inline/@noinline annotation within a function body" begin
m = Module()
@eval m begin
function _body_inline(x)
@inline
# this call won't be resolved and thus will prevent inlining to happen if we don't
# annotate `@inline` at the top of this function body
return unresolved_call(x)
end
body_inline(x) = _body_inline(x)
function _body_noinline(x)
@noinline
return x # obviously will be inlined otherwise
end
body_noinline(x) = _body_noinline(x)

# test annotations for `do` blocks
@inline simple_caller(a) = a()
function do_inline(x)
simple_caller() do
@inline
# this call won't be resolved and thus will prevent inlining to happen if we don't
# annotate `@inline` at the top of this anonymous function body
return unresolved_call(x)
end
end
function do_noinline(x)
simple_caller() do
@noinline
return x # obviously will be inlined otherwise
end
end
end

let ci = code_typed1(m.body_inline, (Int,))
@test all(ci.code) do x
!isinvoke(x, :_body_inline)
end
end
let ci = code_typed1(m.body_noinline, (Int,))
@test any(ci.code) do x
isinvoke(x, :_body_noinline)
end
end
# test annotations for `do` blocks
let ci = code_typed1(m.do_inline, (Int,))
# what we test here is that both `simple_caller` and the anonymous function that the
# `do` block creates should inlined away, and as a result there is only the unresolved call
@test all(ci.code) do x
!isinvoke(x, :simple_caller) &&
!isinvoke(x, mi->startswith(string(mi.def.name), '#'))
end
end
let ci = code_typed1(m.do_noinline, (Int,))
# the anonymous function that the `do` block created shouldn't be inlined here
@test any(ci.code) do x
isinvoke(x, mi->startswith(string(mi.def.name), '#'))
end
end
end