-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
elide code marked with @boundscheck(...)
.
#14474
Conversation
Just added some tests. Seems that my attempt to only eliminate bounds checks one level deep isn't working yet (see the final test which is currently failing). |
04cce56
to
2793ca7
Compare
@@ -32,7 +32,8 @@ function choosetests(choices = []) | |||
"nullable", "meta", "profile", "libgit2", "docs", "markdown", | |||
"base64", "serialize", "functors", "misc", "threads", | |||
"enums", "cmdlineargs", "i18n", "workspace", "libdl", "int", | |||
"checked", "intset", "floatfuncs", "compile", "parallel", "inline" | |||
"checked", "intset", "floatfuncs", "compile", "parallel", "inline", | |||
"boundscheck" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
checkbounds
is already exported. Maybe it would be good to extend that namespace?
The tests should nominally pass now, but that is because of the final commented out test. The issue is definitely related to inlining, so I could use some guidance. |
I see that inlining actually happens before the AST even gets passed to codegen. I'll see if I can add what I need. |
The latest commit tries to inject the appropriate metadata during inlining, but at the moment it is double injecting which causes the tests to fail. |
// inbounds rule is either of top two values on inbounds stack are true | ||
bool inbounds = !ctx->inbounds.empty() && ctx->inbounds.back(); | ||
if (ctx->inbounds.size() > 1) | ||
inbounds ^= ctx->inbounds[ctx->inbounds.size()-2]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be |=
?
Ok, I think this is nominally working now, but it produces a lot of boundscheck "noise" in the AST. For example,
At the very least, I'd like to get rid of the clearly unnecessary pairs like:
A further refinement would be to only inject these inbounds annotations when they have already appeared earlier in the method AST. |
PR updated with what I think are the outstanding issues. |
Now failing
I'm not sure what's special about 10,000, but I am guessing these extra inbounds expressions in the AST are causing additional memory use. |
That's a really weird test! We could do some extra filtering of useless expressions after inlining. For example inlining currently filters out empty meta expressions. |
The number on that test is probably too low, it was mostly to make sure that code path didn't try to print every element of the array. Not sure I can offer much technical review here but wanted to say thank you to @blakejohnson for working on it. |
Cleaning up the |
Hmm... I guess that last commit wasn't so successful. I'll have to think of another way to eliminate some of the AST noise. Any other comments on the overall approach would be appreciated before I go any further. |
I don't know codegen well enough to offer a useful review, but what I see looks good so far. 💯 on the effort! |
I guess we have to be fairly careful about what inbounds/boundscheck metadata we remove, because type inference caches ASTs, so it might be valid to remove the metadata when a method is called by itself, but then invalid when that same method gets inlined into a caller. Consequently, the elimination pass I added uses a rather conservative heuristic. |
I have been thinking that maybe we should separate the new inbounds/boundscheck compiler functionality from the use of it in So, focusing on just the compiler functionality... I've dealt with the issue that @StefanKarpinski raised by requiring that code containing function checkbounds(::Type{Bool}, sz::Dims, I...)
@_inline_meta
@boundscheck begin
...
end
end because the function getindex(A::Array, I)
@_inline_meta
...
@boundcheck checkbounds(A, I)
... Which isn't bad, but if the former way worked it would be nice because it requires less code decoration. If we wanted this to work, we'd need an extra piece of machinery to tell the inlining pass that a piece of code should not change the Thoughts? |
@JeffBezanson's 7de4648 immediately solves the deprecation issue with |
Very nice! Hey, is this working now? At first glance it looks really promising! |
Well, it is "working" in the sense of the demo at the top, i.e. it will remove blocks of code marked as unsafe_getindex(A::Array, i1::Real, I::Real...) = @inbounds return arrayref(A, to_index(i1), to_indexes(I...)...) still skips boundschecking. Whether or not it is sufficient for extending bounds check removal to other types depends on the issue I tried to explain a few days ago. I'd appreciate your two cents on that, @timholy. |
3bb35b7
to
d45d8cc
Compare
@blakejohnson, sorry to be slow to respond on such an important issue. To me, it looks like the restrictions you describe (bounds-check removal only for inlined code, needing to annotate the call site for
CC also @simonster, who has thought quite a bit about these issues. |
That aligns pretty closely with my thinking, @timholy. The only thing I'd add at this point is that having a mechanism to propagate an inbounds context to a further layer of inlining seems very convenient, and saves us from having to re-write a bunch of code in |
Oh, well, looks like you found a simpler solution than I was envisioning! |
Of course, once this works for bounds checking, I want to use the same mechanism for |
Trying to actually use the new mechanisms in A = rand(3,5); x = 1:size(A,1); y = (1:size(A,2))';
broadcast!(Base.MulFun(), Array{Int64,2}(5,3), x, y) and it bails in codegen when Update: Looks like I am creating some malformed BasicBlocks. |
Another fun way to trigger the segfault: bf = Base.Broadcast.gen_broadcast_function(Base.Broadcast.gen_broadcast_body_cartesian, 2, 2, *)
code_llvm(bf, (Array{Int64,2},UnitRange{Int64},Array{Int64,2})) |
Ok, greatly reduced problem case: cb(x) = x > 0 || throw("cb:error")
function B1()
y = [1,2,3] # remove this line and the segfault goes away
@inbounds begin
@boundscheck cb(0)
end
return 0
end
code_llvm(B1, ()) # produces segfault |
Write in a way which makes it more explicit what is inlined vs not inlined.
For when you want to pass `@inbounds` to inlined methods.
First for AbstractArrays.
I've made the one change Jeff suggested. @StefanKarpinski do you want some more time to take a look? Or should I merge it? |
This change looks sane to me; since @JeffBezanson is ok with it, go ahead and merge once tests pass. |
lgtm @JeffBezanson should we try to prevent this from being able to break type inference if used incorrectly? we could prohibit assignment (and fix the tests accordingly), but i'm thinking maybe we should just say that's part of the contract of marking something as
|
I think we can merge this now, but @vtjnash you have a good point. We could maybe enforce in the boundscheck macro that the expression must be a simple function call. Although somewhat humorously the tests rely on assignments inside boundscheck expressions :) |
I see what you are getting at, @vtjnash. I guess ideally a |
elide code marked with `@boundscheck(...)`.
Congratulations on merging this, Blake. I found your work on such core compiler code inspirational. |
@boundscheck cond(0) | ||
end | ||
return 0 | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why B1
and B2
are not used/called in a test, is it an oversight?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Those were reduced test cases that previously caused segfaults.
Thanks, John. |
@@ -139,12 +139,17 @@ These symbols appear in the ``head`` field of ``Expr``\s in lowered form. | |||
``leave`` | |||
pop exception handlers. ``args[1]`` is the number of handlers to pop. | |||
|
|||
``boundscheck`` | |||
``inbounds`` | |||
controls turning bounds checks on or off. A stack is maintained; if the | |||
first argument of this expression is true or false (``true`` means bounds | |||
checks are enabled), it is pushed onto the stack. If the first argument is | |||
``:pop``, the stack is popped. | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct or should it be "bounds checks are disabled"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, true
means bounds checks are disabled.
This is my attempt to implement @JeffBezanson's suggestion in #7799, which is to say that this teaches the compiler how to elide blocks marked with the
@boundscheck
macro, with the simple heuristic of allowing aninbounds
context to propagate through one layer of inlining.I haven't yet marked code that does bounds checking with the
@boundscheck
macro. But, a quick demo:Then,
Comments from Jeff or @vtjnash would be appreciated. I don't grok the details of how inlining works in the compiler yet, so I'm not sure I have properly implemented the inbounds heuristic.
Remaining TODOs:
@boundscheck
@boundscheck
macro--check-bounds=yes
cmd line option, but still have a way to run the tests where we want to override it