Skip to content

Commit 3ae482d

Browse files
aviateskdghosef
authored andcommitted
supports @inline/@noinline annotations within a function body (JuliaLang#41312)
* supports `@inline`/`@noinline` annotations within a function body Separated from JuliaLang#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](JuliaLang#40754 (comment)), this commit implements the easiest solution. Co-authored-by: Joseph Tan <[email protected]> * Update base/expr.jl * Update base/expr.jl * Update NEWS.md Co-authored-by: Joseph Tan <[email protected]>
1 parent a49e352 commit 3ae482d

File tree

3 files changed

+155
-10
lines changed

3 files changed

+155
-10
lines changed

NEWS.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ New language features
66
---------------------
77

88
* `Module(:name, false, false)` can be used to create a `module` that does not import `Core`. ([#40110])
9+
* `@inline` and `@noinline` annotations may now be used in function bodies. ([#41312])
910

1011
Language changes
1112
----------------

base/expr.jl

+37-10
Original file line numberDiff line numberDiff line change
@@ -188,19 +188,32 @@ Give a hint to the compiler that this function is worth inlining.
188188
Small functions typically do not need the `@inline` annotation,
189189
as the compiler does it automatically. By using `@inline` on bigger functions,
190190
an extra nudge can be given to the compiler to inline it.
191-
This is shown in the following example:
191+
192+
`@inline` can be applied immediately before the definition or in its function body.
192193
193194
```julia
194-
@inline function bigfunction(x)
195-
#=
196-
Function Definition
197-
=#
195+
# annotate long-form definition
196+
@inline function longdef(x)
197+
...
198+
end
199+
200+
# annotate short-form definition
201+
@inline shortdef(x) = ...
202+
203+
# annotate anonymous function that a `do` block creates
204+
f() do
205+
@inline
206+
...
198207
end
199208
```
209+
210+
!!! compat "Julia 1.8"
211+
The usage within a function body requires at least Julia 1.8.
200212
"""
201213
macro inline(ex)
202214
esc(isa(ex, Expr) ? pushmeta!(ex, :inline) : ex)
203215
end
216+
macro inline() Expr(:meta, :inline) end
204217

205218
"""
206219
@noinline
@@ -209,22 +222,36 @@ Give a hint to the compiler that it should not inline a function.
209222
210223
Small functions are typically inlined automatically.
211224
By using `@noinline` on small functions, auto-inlining can be
212-
prevented. This is shown in the following example:
225+
prevented.
226+
227+
`@noinline` can be applied immediately before the definition or in its function body.
213228
214229
```julia
215-
@noinline function smallfunction(x)
216-
#=
217-
Function Definition
218-
=#
230+
# annotate long-form definition
231+
@noinline function longdef(x)
232+
...
233+
end
234+
235+
# annotate short-form definition
236+
@noinline shortdef(x) = ...
237+
238+
# annotate anonymous function that a `do` block creates
239+
f() do
240+
@noinline
241+
...
219242
end
220243
```
221244
245+
!!! compat "Julia 1.8"
246+
The usage within a function body requires at least Julia 1.8.
247+
222248
!!! note
223249
If the function is trivial (for example returning a constant) it might get inlined anyway.
224250
"""
225251
macro noinline(ex)
226252
esc(isa(ex, Expr) ? pushmeta!(ex, :noinline) : ex)
227253
end
254+
macro noinline() Expr(:meta, :noinline) end
228255

229256
"""
230257
@pure ex

test/compiler/inline.jl

+117
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,120 @@ end
380380
using Base.Experimental: @opaque
381381
f_oc_getfield(x) = (@opaque ()->x)()
382382
@test fully_eliminated(f_oc_getfield, Tuple{Int})
383+
384+
# check if `x` is a statically-resolved call of a function whose name is `sym`
385+
isinvoke(@nospecialize(x), sym::Symbol) = isinvoke(x, mi->mi.def.name===sym)
386+
function isinvoke(@nospecialize(x), pred)
387+
if Meta.isexpr(x, :invoke)
388+
return pred(x.args[1]::Core.MethodInstance)
389+
end
390+
return false
391+
end
392+
code_typed1(args...; kwargs...) = (firstfirst)(code_typed(args...; kwargs...))::Core.CodeInfo
393+
394+
@testset "@inline/@noinline annotation before definition" begin
395+
m = Module()
396+
@eval m begin
397+
@inline function _def_inline(x)
398+
# this call won't be resolved and thus will prevent inlining to happen if we don't
399+
# annotate `@inline` at the top of this function body
400+
return unresolved_call(x)
401+
end
402+
def_inline(x) = _def_inline(x)
403+
@noinline _def_noinline(x) = x # obviously will be inlined otherwise
404+
def_noinline(x) = _def_noinline(x)
405+
406+
# test that they don't conflict with other "before-definition" macros
407+
@inline Base.@aggressive_constprop function _def_inline_noconflict(x)
408+
# this call won't be resolved and thus will prevent inlining to happen if we don't
409+
# annotate `@inline` at the top of this function body
410+
return unresolved_call(x)
411+
end
412+
def_inline_noconflict(x) = _def_inline_noconflict(x)
413+
@noinline Base.@aggressive_constprop _def_noinline_noconflict(x) = x # obviously will be inlined otherwise
414+
def_noinline_noconflict(x) = _def_noinline_noconflict(x)
415+
end
416+
417+
let ci = code_typed1(m.def_inline, (Int,))
418+
@test all(ci.code) do x
419+
!isinvoke(x, :_def_inline)
420+
end
421+
end
422+
let ci = code_typed1(m.def_noinline, (Int,))
423+
@test any(ci.code) do x
424+
isinvoke(x, :_def_noinline)
425+
end
426+
end
427+
# test that they don't conflict with other "before-definition" macros
428+
let ci = code_typed1(m.def_inline_noconflict, (Int,))
429+
@test all(ci.code) do x
430+
!isinvoke(x, :_def_inline_noconflict)
431+
end
432+
end
433+
let ci = code_typed1(m.def_noinline_noconflict, (Int,))
434+
@test any(ci.code) do x
435+
isinvoke(x, :_def_noinline_noconflict)
436+
end
437+
end
438+
end
439+
440+
@testset "@inline/@noinline annotation within a function body" begin
441+
m = Module()
442+
@eval m begin
443+
function _body_inline(x)
444+
@inline
445+
# this call won't be resolved and thus will prevent inlining to happen if we don't
446+
# annotate `@inline` at the top of this function body
447+
return unresolved_call(x)
448+
end
449+
body_inline(x) = _body_inline(x)
450+
function _body_noinline(x)
451+
@noinline
452+
return x # obviously will be inlined otherwise
453+
end
454+
body_noinline(x) = _body_noinline(x)
455+
456+
# test annotations for `do` blocks
457+
@inline simple_caller(a) = a()
458+
function do_inline(x)
459+
simple_caller() do
460+
@inline
461+
# this call won't be resolved and thus will prevent inlining to happen if we don't
462+
# annotate `@inline` at the top of this anonymous function body
463+
return unresolved_call(x)
464+
end
465+
end
466+
function do_noinline(x)
467+
simple_caller() do
468+
@noinline
469+
return x # obviously will be inlined otherwise
470+
end
471+
end
472+
end
473+
474+
let ci = code_typed1(m.body_inline, (Int,))
475+
@test all(ci.code) do x
476+
!isinvoke(x, :_body_inline)
477+
end
478+
end
479+
let ci = code_typed1(m.body_noinline, (Int,))
480+
@test any(ci.code) do x
481+
isinvoke(x, :_body_noinline)
482+
end
483+
end
484+
# test annotations for `do` blocks
485+
let ci = code_typed1(m.do_inline, (Int,))
486+
# what we test here is that both `simple_caller` and the anonymous function that the
487+
# `do` block creates should inlined away, and as a result there is only the unresolved call
488+
@test all(ci.code) do x
489+
!isinvoke(x, :simple_caller) &&
490+
!isinvoke(x, mi->startswith(string(mi.def.name), '#'))
491+
end
492+
end
493+
let ci = code_typed1(m.do_noinline, (Int,))
494+
# the anonymous function that the `do` block created shouldn't be inlined here
495+
@test any(ci.code) do x
496+
isinvoke(x, mi->startswith(string(mi.def.name), '#'))
497+
end
498+
end
499+
end

0 commit comments

Comments
 (0)