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
transforms: (print) Add a lowering to printf #1142
Conversation
if isinstance(global_name, str): | ||
global_name = StringAttr(global_name) | ||
if isinstance(global_name, StringAttr): | ||
if isinstance(global_name, (StringAttr, str)): | ||
global_name = SymbolRefAttr(global_name) |
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.
This is a trivial simplification, I hope it's okay to have in this PR?
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 can not comment, because I don't understand?
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #1142 +/- ##
==========================================
+ Coverage 87.23% 87.27% +0.04%
==========================================
Files 143 145 +2
Lines 21248 21336 +88
Branches 3206 3215 +9
==========================================
+ Hits 18536 18622 +86
+ Misses 2211 2209 -2
- Partials 501 505 +4
☔ View full report in Codecov by Sentry. |
@@ -0,0 +1,20 @@ | |||
// RUN: xdsl-opt %s -p print-to-printf | mlir-opt --test-lower-to-llvm | mlir-translate --mlir-to-llvmir | filecheck %s | |||
// this tests straight to llvmir, because I feel that that's a reasonable thing to do. |
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.
It is in addition to one that doesn't send straight to llvmir :) Also please rephrase the comment to remove the "I", it's a team project
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.
Yep! fixed (and added another test)
args.append(new_val.result) | ||
format_str += "%li" | ||
elif part.typ == builtin.f32: | ||
# f32 must be promoted to f64 before printing |
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 wonder whether this should be done as a separate rewrite, seems kind of dodgy to me. We could borrow python's behaviour of there being some default print format and a trait for conversion to printf-supported types.
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.
hmmm, interesting point. I think this should still happen here though, because the type printing code shouldn't be bothered with the intricacies of the printf
function?
Also, we plan on supporting both printf
based lowering and a putc
based one, so we need to be careful how we generalize here...
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.
@superlopuh I think what you are saying is that you would like to lower to a printfOp first and then from printfOp to putchar?
Because then it makes more sense to me to change the current println op?
Could you be more precise with what you mean with "default print format?"
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 mean in general it would be good to know what the semantics are of what is printable and how. I'd rather just fail to print certain types than have implicit conversions for the time being, hopefully clients can work around in the near future.
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.
PrintableType
interface is on the horizon, I want to be able to print memrefs, etc!
("test", "test"), | ||
("@hello world", "hello_world"), | ||
("something.with.dots", "something_with_dots"), | ||
("this is 💩", "this_is"), |
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.
lol
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'm thinking maybe we should be using safe escaping like this: https://stackoverflow.com/questions/1695183/how-can-i-percent-encode-url-parameters-in-python so things like emoji are still kept around. I have a feeling this might bite us later if we're doing printing of fairly standard things like "(" and ")"
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.
Emojis will be kept around in the actual string, this builds the global symbol name (and only part of it)
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.
Can you add "(" and ")" as test cases to see what happens? Preferably end-to-end, like what would this do:
print("(")
print("a")
print(")")
Would it be the hash that resolves the difference between the two?
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.
Given:
print.println "("
print.println ")"
We get:
module {
func.func @main() {
%0 = llvm.mlir.addressof @_900229d109e2354708da1b4fe903c1ef0e741ab8 : !llvm.ptr
llvm.call @printf(%0) : (!llvm.ptr) -> ()
%1 = llvm.mlir.addressof @_a35077a93b180088463ecc1cdf83178d99a53a66 : !llvm.ptr
llvm.call @printf(%1) : (!llvm.ptr) -> ()
return
}
llvm.func @printf(!llvm.ptr, ...)
// this is the "(" string
llvm.mlir.global internal constant @_900229d109e2354708da1b4fe903c1ef0e741ab8(dense<[40, 10, 0]> : tensor<3xi8>) {addr_space = 0 : i32} : !llvm.array<3 x i8>
// this is ")"
llvm.mlir.global internal constant @_a35077a93b180088463ecc1cdf83178d99a53a66(dense<[41, 10, 0]> : tensor<3xi8>) {addr_space = 0 : i32} : !llvm.array<3 x i8>
}
As you can see, the symbol names are no longer useful in identifying the string, but that's okay for me. They are still unique though! And we have relatively strong guarantees for that for as long as the string is not crafted maliciously. And in the worst case scenario is that some printing breaks. This is an acceptable tradeoff imo.
The prefix is just added so that one can (sometimes) tell what string is printed, and I couldn't think of a better way that guaranteed no symbol name clashes.
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.
Just a sidecomment (that does not even help here, AFAIU):
A classic, hacky trick in c++ to get unique numbers is to malloc a single byte and use the pointer to it as the unique number. Maybe you can do something similar here?
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 added a pytest test for this, I feel it's useful to test this behavior!
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.
Why not add them here
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.
If I added them here, the test would have to check the exact hashes, and would therefore test an implementation detail.
Instead, I want to test the contract. So that key(x) == key(x)
and key(x) != key(y)
, even for non-legal characters.
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 can do that in pytest much easier then I can in filecheck
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.
Mostly a few nits on spelling (your craving for beer is somewhat worrying 🙃 ).
Looks good to me in general!
args.append(new_val.result) | ||
format_str += "%li" | ||
elif part.typ == builtin.f32: | ||
# f32 must be promoted to f64 before printing |
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.
@superlopuh I think what you are saying is that you would like to lower to a printfOp first and then from printfOp to putchar?
Because then it makes more sense to me to change the current println op?
Could you be more precise with what you mean with "default print format?"
if isinstance(global_name, str): | ||
global_name = StringAttr(global_name) | ||
if isinstance(global_name, StringAttr): | ||
if isinstance(global_name, (StringAttr, str)): | ||
global_name = SymbolRefAttr(global_name) |
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 can not comment, because I don't understand?
("test", "test"), | ||
("@hello world", "hello_world"), | ||
("something.with.dots", "something_with_dots"), | ||
("this is 💩", "this_is"), |
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.
Can you add "(" and ")" as test cases to see what happens? Preferably end-to-end, like what would this do:
print("(")
print("a")
print(")")
Would it be the hash that resolves the difference between the two?
args.append(new_val.result) | ||
format_str += "%li" | ||
elif part.typ == builtin.f32: | ||
# f32 must be promoted to f64 before printing |
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 mean in general it would be good to know what the semantics are of what is printable and how. I'd rather just fail to print certain types than have implicit conversions for the time being, hopefully clients can work around in the near future.
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.
LGTM modulo other comments, if you don't think it's critical I think we can merge, just would like to avoid silent gotchas in the future
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.
LGTM, nice to see this evolve 👍
Did you already run lowered code and does it work?
Thanks!
Yep! Already debugged devito lowerings with it! |
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.
Thank you for working on this @AntonLydike!
This adds the `print-to-printf` pass to lower println ops to printf calls
Now that Toy lowers to print_format, we can compile it to LLVM, leveraging the printf flow added in #1142 .
This is a working, minimal lowering to
printf
!I have some hacky stuff happening in the ModulePass, let me know if this is undesireable!