From 6eb20a8b96dbe96e76cca876eefe637dbf551696 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 22 Jun 2021 17:49:57 +0900 Subject: [PATCH 1/4] supports `@inline`/`@noinline` annotations within a function body Separated from #40754 for the sake of easier review. The primary motivation for this change is to annotate `@inline`/`@noinline` to anonymous functions created from `do` block: ```julia f() do @inline # makes this anonymous function to be inlined ... # function body end ``` We can extend the grammar so that we have special "declaration-macro" supports for `do`-block functions like: ```julia f() @inline do # makes this anonymous function to be inlined ... # function body end ``` but I'm not sure which one is better. Following [the earlier discussion](https://github.com/JuliaLang/julia/pull/40754#issuecomment-839401667), this commit implements the easiest solution. Co-authored-by: Joseph Tan --- NEWS.md | 1 + base/expr.jl | 47 ++++++++++++---- test/compiler/inline.jl | 117 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 10 deletions(-) diff --git a/NEWS.md b/NEWS.md index 6961710affa5a..53552fb1f50b1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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]) Language changes ---------------- diff --git a/base/expr.jl b/base/expr.jl index 84b521543111b..c4c5e8da39b27 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -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. """ macro inline(ex) esc(isa(ex, Expr) ? pushmeta!(ex, :inline) : ex) end +macro inline() Expr(:meta, :inline) end """ @noinline @@ -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. + !!! 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 """ @pure ex diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 1ffd012acb755..48906ab3e632c 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -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...) = (first∘first)(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 From 8cc001ba280b64fd78646795d158cb7038cfa517 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 23 Jun 2021 08:24:59 +0900 Subject: [PATCH 2/4] Update base/expr.jl --- base/expr.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/expr.jl b/base/expr.jl index c4c5e8da39b27..c8d7a4d25ef06 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -207,8 +207,8 @@ f() do end ``` -!!! compat "Julia 1.7" - The usage within a function body requires at least Julia 1.7. +!!! compat "Julia 1.8" + The usage within a function body requires at least Julia 1.8. """ macro inline(ex) esc(isa(ex, Expr) ? pushmeta!(ex, :inline) : ex) From 5b4235ac33f25c94b4f675daeb3c22f63351fe46 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 23 Jun 2021 08:25:06 +0900 Subject: [PATCH 3/4] Update base/expr.jl --- base/expr.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/expr.jl b/base/expr.jl index c8d7a4d25ef06..9df363714679e 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -242,8 +242,8 @@ f() do end ``` -!!! compat "Julia 1.7" - The usage within a function body requires at least Julia 1.7. +!!! compat "Julia 1.8" + The usage within a function body requires at least Julia 1.8. !!! note If the function is trivial (for example returning a constant) it might get inlined anyway. From 4ed744dbc366bbce0e00fcc7d6e5f199f5ee8a97 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 23 Jun 2021 08:28:59 +0900 Subject: [PATCH 4/4] Update NEWS.md --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 53552fb1f50b1..e4885c4c34d51 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,7 +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]) +* `@inline` and `@noinline` annotations may now be used in function bodies. ([#41312]) Language changes ----------------