Skip to content

Commit e5ce8c2

Browse files
authored
Don't throw HasBoxedVariableError by default (#99)
1 parent 7153ead commit e5ce8c2

File tree

4 files changed

+163
-10
lines changed

4 files changed

+163
-10
lines changed

docs/src/reference/api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,9 @@ See also:
3131
* [`Transducers.ThreadedEx`](https://juliafolds.github.io/Transducers.jl/dev/reference/manual/#Transducers.ThreadedEx)
3232
* [`Transducers.DistributedEx`](https://juliafolds.github.io/Transducers.jl/dev/reference/manual/#Transducers.DistributedEx)
3333
* [How is a parallel `@floop` executed? What is the scheduling strategy?](@ref faq-exeuctor)
34+
35+
## `FLoops.assistant`
36+
37+
```@docs
38+
FLoops.assistant
39+
```

src/checkboxes.jl

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,93 @@ else
1010
const has_boxed_variables = has_boxed_variables_g
1111
end
1212

13-
function verify_no_boxes(f::F) where {F}
14-
has_boxed_variables(f) && throw(HasBoxedVariableError(f))
13+
function verify_no_boxes(f::F, context) where {F}
14+
has_boxed_variables(f) && handle_boxed_variables(f, context())
15+
return
16+
end
17+
18+
baremodule AssistantMode
19+
using Base: @enum, UInt8
20+
@enum Kind::UInt8 begin
21+
ignore
22+
warn
23+
warn_always
24+
error
25+
end
26+
end
27+
28+
const ASSISTANT_MODE = Ref(AssistantMode.warn)
29+
30+
struct SetAssistantResult
31+
old::AssistantMode.Kind
32+
new::AssistantMode.Kind
33+
end
34+
35+
"""
36+
FLoops.assistant(mode::Symbol)
37+
FLoops.assistant(enable::Bool)
38+
39+
Set assistant mode; i.e., what to do when FLoops.jl finds a problematic usage
40+
pattern.
41+
42+
Assistant modes:
43+
44+
* `:ignore`: do nothing
45+
* `:warn`: print warning once
46+
* `:warn_always`: print warning always
47+
* `:error`: throw an error
48+
49+
`FLoops.assistant(false)` is equivalent to `FLoops.assistant(:ignore)` and
50+
`FLoops.assistant(true)` is equivalent to `FLoops.assistant(:warn)`.
51+
"""
52+
assistant
53+
function assistant(mode::Symbol)
54+
m = nothing
55+
if isdefined(AssistantMode, mode)
56+
m = getproperty(AssistantMode, mode)
57+
end
58+
if !(m isa AssistantMode.Kind)
59+
error(
60+
"invalid mode: ",
61+
mode,
62+
" (must be one of `:ignore`, `:warn`, `:warn_always`, and `:error`)",
63+
)
64+
end
65+
p = ASSISTANT_MODE[]
66+
ASSISTANT_MODE[] = m
67+
return SetAssistantResult(p, m)
68+
end
69+
70+
assistant(enable::Bool) = enable ? assistant(:warn) : assistant(:ignore)
71+
72+
function Base.show(io::IO, ::MIME"text/plain", result::SetAssistantResult)
73+
printstyled(io, "FLoops.assistant"; color = :blue)
74+
print(io, ":")
75+
print(io, "\n old mode: ", result.old)
76+
print(io, "\n new mode: ", result.new)
77+
end
78+
79+
function handle_boxed_variables(f, context)
80+
mode = ASSISTANT_MODE[]
81+
mode == AssistantMode.ignore && return
82+
83+
err = HasBoxedVariableError(f)
84+
if mode == AssistantMode.error
85+
throw(err)
86+
elseif mode == AssistantMode.warn || mode == AssistantMode.warn_always
87+
ctx = context.ctx::MacroContext
88+
@warn(
89+
"Correctness and/or performance problem detected",
90+
error = err,
91+
_module = ctx.module_,
92+
_file = string(something(ctx.source.file, "none")),
93+
_line = ctx.source.line,
94+
_id = Symbol(context.id, mode == AssistantMode.warn ? :_once : :_always),
95+
maxlog = mode == AssistantMode.warn ? 1 : nothing,
96+
)
97+
else
98+
error("unknown mode: ", mode)
99+
end
15100
return
16101
end
17102

@@ -65,6 +150,18 @@ function Base.showerror(io::IO, err::HasBoxedVariableError)
65150
color = :light_black,
66151
)
67152
# "To ignore this error, pass `allow_boxing = Val(true)` to the executor."
153+
154+
println(io)
155+
printstyled(io, "NOTE:", bold = true, color = :light_black)
156+
printstyled(
157+
io,
158+
" To disable this ",
159+
ASSISTANT_MODE[] == AssistantMode.error ? "error" : "warning",
160+
", call `";
161+
color = :light_black,
162+
)
163+
printstyled(io, "FLoops.assistant(false)"; bold = true, color = :light_black)
164+
printstyled(io, "`."; color = :light_black)
68165
end
69166

70167
function _make_closure_with_a_box()
@@ -94,5 +191,5 @@ end
94191
const _verify_no_boxes = verify_no_boxes
95192
else
96193
# Since `Core.Box` is internal, we can't rely on that it exists.
97-
const _verify_no_boxes = identity
194+
const _verify_no_boxes = donothing
98195
end

src/reduce.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ function as_parallel_loop(ctx::MacroContext, rf_arg, coll, body0::Expr, simd, ex
773773

774774
body2, info = transform_loop_body(body1, accs_symbols)
775775

776-
@gensym oninit_function reducing_function combine_function result
776+
@gensym oninit_function reducing_function combine_function result context_function
777777
if ctx.module_ === Main
778778
# Ref: https://github.com/JuliaLang/julia/issues/39895
779779
oninit_function = Symbol(:__, oninit_function)
@@ -832,7 +832,8 @@ function as_parallel_loop(ctx::MacroContext, rf_arg, coll, body0::Expr, simd, ex
832832
$(combine_bodies...)
833833
return ($(accs_symbols...),)
834834
end
835-
$_verify_no_boxes($reducing_function)
835+
$context_function() = (; ctx = $ctx, id = $(QuoteNode(gensym(:floop_id))))
836+
$_verify_no_boxes($reducing_function, $context_function)
836837
$result = $_fold(
837838
$wheninit(
838839
$oninit_function,

test/FLoopsTests/src/test_checkboxes.jl

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
module TestCheckboxes
22

33
using FLoops:
4+
FLoops,
5+
HasBoxedVariableError,
6+
MacroContext,
47
_box_detection_works,
58
_make_closure_with_a_box,
69
_make_closure_without_a_box,
710
has_boxed_variables,
8-
verify_no_boxes,
9-
HasBoxedVariableError
11+
verify_no_boxes
1012
using Test
1113

14+
function with_assistant(f, mode)
15+
result = FLoops.assistant(mode)
16+
try
17+
f()
18+
finally
19+
FLoops.assistant(Symbol(result.old))
20+
end
21+
end
22+
1223
function _make_closure_with_two_boxes()
1324
local a
1425
local b = 1
@@ -20,10 +31,48 @@ function test__box_detection_works()
2031
@test _box_detection_works()
2132
end
2233

23-
function test_verify_no_boxes()
24-
@test (verify_no_boxes(_make_closure_without_a_box()); true)
34+
dummy_context() = (ctx = MacroContext(LineNumberNode(0), @__MODULE__), id = :dummy)
35+
36+
function test_assistant()
37+
@test (verify_no_boxes(_make_closure_without_a_box(), dummy_context); true)
38+
2539
f = _make_closure_with_a_box()
26-
@test_throws HasBoxedVariableError(f) verify_no_boxes(f)
40+
with_assistant(:error) do
41+
@test_throws HasBoxedVariableError(f) verify_no_boxes(f, dummy_context)
42+
end
43+
with_assistant(true) do
44+
@test_logs (:warn, r"Correctness .*") verify_no_boxes(f, dummy_context)
45+
end
46+
with_assistant(:warn) do
47+
@test_logs (:warn, r"Correctness .*") verify_no_boxes(f, dummy_context)
48+
end
49+
with_assistant(:warn_always) do
50+
@test_logs (:warn, r"Correctness .*") verify_no_boxes(f, dummy_context)
51+
end
52+
with_assistant(:ignore) do
53+
@test_logs verify_no_boxes(f, dummy_context)
54+
end
55+
with_assistant(false) do
56+
@test_logs verify_no_boxes(f, dummy_context)
57+
end
58+
59+
result = with_assistant(:error) do
60+
FLoops.assistant(:warn)
61+
end
62+
msg = sprint(show, "text/plain", result)
63+
@test occursin("FLoops.assistant", msg)
64+
@test occursin("old mode: error", msg)
65+
@test occursin("new mode: warn", msg)
66+
67+
err = try
68+
FLoops.assistant(:INVALID_MODE)
69+
nothing
70+
catch e
71+
e
72+
end
73+
@test err isa Exception
74+
msg = sprint(showerror, err)
75+
@test occursin("invalid mode:", msg)
2776
end
2877

2978
function test_HasBoxedVariableError()

0 commit comments

Comments
 (0)