Skip to content

Commit 34a94d8

Browse files
committed
implement new macro @consistent_overlay instead of @assume_effects
1 parent 8657232 commit 34a94d8

File tree

4 files changed

+121
-46
lines changed

4 files changed

+121
-46
lines changed

base/experimental.jl

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ function show_error_hints(io, ex, args...)
318318
isnothing(hinters) && return
319319
for handler in hinters
320320
try
321-
Base.invokelatest(handler, io, ex, args...)
321+
@invokelatest handler(io, ex, args...)
322322
catch err
323323
tn = typeof(handler).name
324324
@error "Hint-handler $handler for $(typeof(ex)) in $(tn.module) caused an error"
@@ -330,17 +330,97 @@ end
330330
include("opaque_closure.jl")
331331

332332
"""
333-
Experimental.@overlay mt [function def]
333+
Base.Experimental.@overlay mt [function def]
334334
335335
Define a method and add it to the method table `mt` instead of to the global method table.
336336
This can be used to implement a method override mechanism. Regular compilation will not
337337
consider these methods, and you should customize the compilation flow to look in these
338338
method tables (e.g., using [`Core.Compiler.OverlayMethodTable`](@ref)).
339+
340+
!!! note
341+
Please be aware that when defining overlay methods using `@overlay`, it is not necessary
342+
to have an original method that corresponds exactly in terms of how the method dispatches.
343+
This means that the method overlay mechanism enabled by `@overlay` is not implemented by
344+
replacing the methods themselves, but through an additional and prioritized method
345+
lookup during the method dispatch.
346+
347+
Considering this, it is important to understand that in compilations using an overlay
348+
method table like the following, the method dispatched by `callx(x)` is not the regular
349+
method `callx(::Float64)`, but the overlay method `callx(x::Real)`:
350+
```julia
351+
callx(::Real) = :real
352+
@overlay SOME_OVERLAY_MT callx(::Real) = :overlay_real
353+
callx(::Float64) = :float64
354+
355+
# some overlay callsite
356+
let x::Float64
357+
callx(x) #> :overlay_real
358+
end
359+
```
339360
"""
340361
macro overlay(mt, def)
341-
def = macroexpand(__module__, def) # to expand @inline, @generated, etc
342-
is_function_def(def) || error("@overlay requires a function definition")
343-
return esc(overlay_def!(mt, def))
362+
inner = Base.unwrap_macrocalls(def)
363+
is_function_def(inner) || error("@overlay requires a function definition")
364+
overlay_def!(mt, inner)
365+
return esc(def)
366+
end
367+
368+
"""
369+
Base.Experimental.@consistent_overlay mt [function def]
370+
371+
This macro operates almost identically to [`Base.Experimental.@overlay`](@ref), defining a
372+
new overlay method. The key difference with this macro is that it informs the compiler that
373+
the invocation of the overlay method it defines is `:consistent` with a regular,
374+
non-overlayed method call.
375+
376+
More formally, when evaluating a generic function call ``f(x)`` at a specific world age
377+
``i``, if a regular method call ``fᵢ(x)`` is redirected to an overlay method call ``fᵢ′(x)``
378+
defined by this macro, it must be ensured that ``fᵢ(x) ≡ fᵢ′(x)``.
379+
380+
For a detailed definition of `:consistent`-cy, consult the corresponding section in
381+
[`Base.@assume_effects`](@ref).
382+
383+
!!! note
384+
Note that the requirements for `:consistent`-cy include not only that the return values
385+
are egal, but also that the manner of termination is the same.
386+
However, it's important to aware that when they throw exceptions, the exceptions
387+
themselves don't necessarily have to be egal as explained in the note of `:consistent`.
388+
In other words, if ``fᵢ(x)`` throws an exception, ``fᵢ′(x)`` is required to also throw
389+
one, but the exact exceptions may differ.
390+
391+
!!! note
392+
Please note that the `:consistent`-cy requirement applies not to method itself but to
393+
_method invocation_. This means that for the use of `@consistent_overlay`, it is
394+
necessary for method invocations with the native regular compilation and those with
395+
a compilation with overlay method table to be `:consistent`.
396+
397+
For example, it is important to understand that, `@consistent_overlay` can be used like
398+
the following:
399+
```julia
400+
callsin(x::Real) = x < 0 ? error(x) : sin(x)
401+
@consistent_overlay SOME_OVERLAY_MT callsin(x::Float64) =
402+
x < 0 ? error_somehow(x) : sin(x)
403+
```
404+
However, be aware that this `@consistent_overlay` will immediately become invalid if a
405+
new method for `callsin` is defined subsequently, such as:
406+
```julia
407+
callsin(x::Float64) = cos(x)
408+
```
409+
410+
This specifically implies that the use of `@consistent_overlay` should be restricted as
411+
much as possible to cases where a regular method with a concrete signature is replaced
412+
by an overlay method with the same concrete signature.
413+
414+
This constraint is closely related to the note in [`Base.Experimental.@overlay`](@ref);
415+
you are advised to consult that as well.
416+
"""
417+
macro consistent_overlay(mt, def)
418+
inner = Base.unwrap_macrocalls(def)
419+
is_function_def(inner) || error("@consistent_overlay requires a function definition")
420+
overlay_def!(mt, inner)
421+
override = Core.Compiler.EffectsOverride(; consistent_overlay=true)
422+
Base.pushmeta!(def::Expr, Base.form_purity_expr(override))
423+
return esc(def)
344424
end
345425

346426
function overlay_def!(mt, @nospecialize ex)

base/expr.jl

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,6 @@ The following `setting`s are supported.
505505
- `:inaccessiblememonly`
506506
- `:noub`
507507
- `:noub_if_noinbounds`
508-
- `:consistent_overlay`
509508
- `:foldable`
510509
- `:removable`
511510
- `:total`
@@ -674,29 +673,6 @@ The `:noub` setting asserts that the method will not execute any undefined behav
674673
any other effect assertions (such as `:consistent` or `:effect_free`) as well, but we do
675674
not model this, and they assume the absence of undefined behavior.
676675
677-
---
678-
## `:consistent_overlay`
679-
680-
The `:consistent_overlay` setting asserts that any overlayed methods potentially called by
681-
the method are `:consistent` with their original, non-overlayed counterparts. For the exact
682-
definition of `:consistent`, refer to the earlier explanation.
683-
684-
More formally, when evaluating a generic function call ``f(x)`` at a specific world-age ``i``,
685-
and the regular method call ``fᵢ(x)`` is redirected to an overlay method ``fᵢ′(x)``, this
686-
setting requires that ``fᵢ(x) ≡ fᵢ′(x)``.
687-
688-
!!! note
689-
Note that the requirements for `:consistent`-cy include not only that the return values
690-
are egal, but also that the manner of termination is the same.
691-
However, it's important to aware that when they throw exceptions, the exceptions
692-
themselves don't necessarily have to be egal as explained in the note of `:consistent`.
693-
In other words, if ``fᵢ(x)`` throws an exception, this settings requires ``fᵢ′(x)`` to
694-
also raise one, but the exact exceptions may differ.
695-
696-
!!! note
697-
This setting isn't supported at the callsite; it has to be applied at the definition
698-
site. Also, given its nature, it's expected to be used together with `Base.Experimental.@overlay`.
699-
700676
---
701677
## `:foldable`
702678
@@ -761,7 +737,7 @@ macro assume_effects(args...)
761737
lastex = args[end]
762738
override = compute_assumed_settings(args[begin:end-1])
763739
if is_function_def(unwrap_macrocalls(lastex))
764-
return esc(pushmeta!(lastex, form_purity_expr(override)))
740+
return esc(pushmeta!(lastex::Expr, form_purity_expr(override)))
765741
elseif isexpr(lastex, :macrocall) && lastex.args[1] === Symbol("@ccall")
766742
lastex.args[1] = GlobalRef(Base, Symbol("@ccall_effects"))
767743
insert!(lastex.args, 3, Core.Compiler.encode_effects_override(override))
@@ -773,8 +749,6 @@ macro assume_effects(args...)
773749
return Expr(:meta, form_purity_expr(override′))
774750
else
775751
# call site annotation case
776-
override.consistent_overlay &&
777-
throw(ArgumentError("Callsite `@assume_effects :consistent_overlay` is not supported"))
778752
return Expr(:block,
779753
form_purity_expr(override),
780754
Expr(:local, Expr(:(=), :val, esc(lastex))),
@@ -819,8 +793,6 @@ function compute_assumed_setting(override::EffectsOverride, @nospecialize(settin
819793
return EffectsOverride(override; noub = val)
820794
elseif setting === :noub_if_noinbounds
821795
return EffectsOverride(override; noub_if_noinbounds = val)
822-
elseif setting === :consistent_overlay
823-
return EffectsOverride(override; consistent_overlay = val)
824796
elseif setting === :foldable
825797
consistent = effect_free = terminates_globally = noub = val
826798
return EffectsOverride(override; consistent, effect_free, terminates_globally, noub)

src/method.c

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -470,16 +470,27 @@ jl_code_info_t *jl_new_code_info_from_ir(jl_expr_t *ir)
470470
li->constprop = 2;
471471
else if (jl_is_expr(ma) && ((jl_expr_t*)ma)->head == jl_purity_sym) {
472472
if (jl_expr_nargs(ma) == NUM_EFFECTS_OVERRIDES) {
473-
li->purity.overrides.ipo_consistent = jl_unbox_bool(jl_exprarg(ma, 0));
474-
li->purity.overrides.ipo_effect_free = jl_unbox_bool(jl_exprarg(ma, 1));
475-
li->purity.overrides.ipo_nothrow = jl_unbox_bool(jl_exprarg(ma, 2));
476-
li->purity.overrides.ipo_terminates_globally = jl_unbox_bool(jl_exprarg(ma, 3));
477-
li->purity.overrides.ipo_terminates_locally = jl_unbox_bool(jl_exprarg(ma, 4));
478-
li->purity.overrides.ipo_notaskstate = jl_unbox_bool(jl_exprarg(ma, 5));
479-
li->purity.overrides.ipo_inaccessiblememonly = jl_unbox_bool(jl_exprarg(ma, 6));
480-
li->purity.overrides.ipo_noub = jl_unbox_bool(jl_exprarg(ma, 7));
481-
li->purity.overrides.ipo_noub_if_noinbounds = jl_unbox_bool(jl_exprarg(ma, 8));
482-
li->purity.overrides.ipo_consistent_overlay = jl_unbox_bool(jl_exprarg(ma, 9));
473+
// N.B. this code allows multiple :purity expressions to be present in a single `:meta` node
474+
int8_t consistent = jl_unbox_bool(jl_exprarg(ma, 0));
475+
if (consistent) li->purity.overrides.ipo_consistent = consistent;
476+
int8_t effect_free = jl_unbox_bool(jl_exprarg(ma, 1));
477+
if (effect_free) li->purity.overrides.ipo_effect_free = effect_free;
478+
int8_t nothrow = jl_unbox_bool(jl_exprarg(ma, 2));
479+
if (nothrow) li->purity.overrides.ipo_nothrow = nothrow;
480+
int8_t terminates_globally = jl_unbox_bool(jl_exprarg(ma, 3));
481+
if (terminates_globally) li->purity.overrides.ipo_terminates_globally = terminates_globally;
482+
int8_t terminates_locally = jl_unbox_bool(jl_exprarg(ma, 4));
483+
if (terminates_locally) li->purity.overrides.ipo_terminates_locally = terminates_locally;
484+
int8_t notaskstate = jl_unbox_bool(jl_exprarg(ma, 5));
485+
if (notaskstate) li->purity.overrides.ipo_notaskstate = notaskstate;
486+
int8_t inaccessiblememonly = jl_unbox_bool(jl_exprarg(ma, 6));
487+
if (inaccessiblememonly) li->purity.overrides.ipo_inaccessiblememonly = inaccessiblememonly;
488+
int8_t noub = jl_unbox_bool(jl_exprarg(ma, 7));
489+
if (noub) li->purity.overrides.ipo_noub = noub;
490+
int8_t noub_if_noinbounds = jl_unbox_bool(jl_exprarg(ma, 8));
491+
if (noub_if_noinbounds) li->purity.overrides.ipo_noub_if_noinbounds = noub_if_noinbounds;
492+
int8_t consistent_overlay = jl_unbox_bool(jl_exprarg(ma, 9));
493+
if (consistent_overlay) li->purity.overrides.ipo_consistent_overlay = consistent_overlay;
483494
}
484495
}
485496
else

test/compiler/AbstractInterpreter.jl

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ CC.transform_result_for_cache(::AbsIntOnlyInterp2, ::Core.MethodInstance, ::CC.W
2121
# OverlayMethodTable
2222
# ==================
2323

24-
using Base.Experimental: @MethodTable, @overlay
24+
using Base.Experimental: @MethodTable, @overlay, @consistent_overlay
2525

2626
# @overlay method with return type annotation
2727
@MethodTable RT_METHOD_DEF
@@ -147,17 +147,29 @@ end
147147
raise_on_gpu1(x) = error(x)
148148
@overlay OVERLAY_MT @noinline raise_on_gpu1(x) = #=do something with GPU=# error(x)
149149
raise_on_gpu2(x) = error(x)
150-
@overlay OVERLAY_MT @noinline Base.@assume_effects :consistent_overlay raise_on_gpu2(x) = #=do something with GPU=# error(x)
150+
@consistent_overlay OVERLAY_MT @noinline raise_on_gpu2(x) = #=do something with GPU=# error(x)
151+
raise_on_gpu3(x) = error(x)
152+
@consistent_overlay OVERLAY_MT @noinline Base.@assume_effects :foldable raise_on_gpu3(x) = #=do something with GPU=# error_on_gpu(x)
151153
cpu_factorial(x::Int) = myfactorial(x, error)
152154
gpu_factorial1(x::Int) = myfactorial(x, raise_on_gpu1)
153155
gpu_factorial2(x::Int) = myfactorial(x, raise_on_gpu2)
156+
gpu_factorial3(x::Int) = myfactorial(x, raise_on_gpu3)
154157

155158
@test Base.infer_effects(cpu_factorial, (Int,); interp=MTOverlayInterp()) |> Core.Compiler.is_nonoverlayed
156159
@test Base.infer_effects(gpu_factorial1, (Int,); interp=MTOverlayInterp()) |> !Core.Compiler.is_nonoverlayed
157160
@test Base.infer_effects(gpu_factorial2, (Int,); interp=MTOverlayInterp()) |> Core.Compiler.is_consistent_overlay
161+
let effects = Base.infer_effects(gpu_factorial3, (Int,); interp=MTOverlayInterp())
162+
# check if `@consistent_overlay` together works with `@assume_effects`
163+
# N.B. the overlaid `raise_on_gpu3` is not :foldable otherwise since `error_on_gpu` is (intetionally) undefined.
164+
@test Core.Compiler.is_consistent_overlay(effects)
165+
@test Core.Compiler.is_foldable(effects)
166+
end
158167
@test Base.infer_return_type(; interp=MTOverlayInterp()) do
159168
Val(gpu_factorial2(3))
160169
end == Val{6}
170+
@test Base.infer_return_type(; interp=MTOverlayInterp()) do
171+
Val(gpu_factorial3(3))
172+
end == Val{6}
161173

162174
# GPUCompiler needs accurate inference through kwfunc with the overlay of `Core.throw_inexacterror`
163175
# https://github.com/JuliaLang/julia/issues/48097

0 commit comments

Comments
 (0)