Skip to content
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

Merged
merged 20 commits into from Jun 16, 2023
Merged

Conversation

AntonLydike
Copy link
Collaborator

This is a working, minimal lowering to printf!

I have some hacky stuff happening in the ModulePass, let me know if this is undesireable!

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)
Copy link
Collaborator Author

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?

Copy link
Collaborator

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
Copy link

codecov bot commented Jun 15, 2023

Codecov Report

Patch coverage: 85.71% and project coverage change: +0.04 🎉

Comparison is base (85b16da) 87.23% compared to head (b6c500a) 87.27%.

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     
Impacted Files Coverage Δ
xdsl/dialects/llvm.py 89.45% <0.00%> (+2.14%) ⬆️
xdsl/dialects/print.py 80.00% <ø> (ø)
xdsl/transforms/print_to_println.py 82.60% <82.60%> (ø)
tests/dialects/test_print.py 100.00% <100.00%> (ø)
xdsl/xdsl_opt_main.py 96.61% <100.00%> (+0.01%) ⬆️

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

xdsl/transforms/print_to_println.py Outdated Show resolved Hide resolved
@@ -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.
Copy link
Member

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

Copy link
Collaborator Author

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)

xdsl/transforms/print_to_println.py Outdated Show resolved Hide resolved
args.append(new_val.result)
format_str += "%li"
elif part.typ == builtin.f32:
# f32 must be promoted to f64 before printing
Copy link
Member

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.

Copy link
Collaborator Author

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...

Copy link
Collaborator

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?"

Copy link
Member

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.

Copy link
Collaborator Author

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"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol

Copy link
Member

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 ")"

Copy link
Collaborator Author

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)

Copy link
Member

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator

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?

Copy link
Collaborator Author

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!

Copy link
Member

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

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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

Copy link
Collaborator

@JosseVanDelm JosseVanDelm left a 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!

xdsl/xdsl_opt_main.py Outdated Show resolved Hide resolved
xdsl/transforms/print_to_println.py Outdated Show resolved Hide resolved
xdsl/transforms/print_to_println.py Outdated Show resolved Hide resolved
xdsl/transforms/print_to_println.py Outdated Show resolved Hide resolved
args.append(new_val.result)
format_str += "%li"
elif part.typ == builtin.f32:
# f32 must be promoted to f64 before printing
Copy link
Collaborator

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)
Copy link
Collaborator

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?

tests/dialects/test_print.py Outdated Show resolved Hide resolved
("test", "test"),
("@hello world", "hello_world"),
("something.with.dots", "something_with_dots"),
("this is 💩", "this_is"),
Copy link
Member

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
Copy link
Member

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.

Copy link
Member

@superlopuh superlopuh left a 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

Copy link
Collaborator

@webmiche webmiche left a 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?

@AntonLydike
Copy link
Collaborator Author

LGTM, nice to see this evolve +1

Thanks!

Did you already run lowered code and does it work?

Yep! Already debugged devito lowerings with it!

Copy link
Collaborator

@JosseVanDelm JosseVanDelm left a 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!

@AntonLydike AntonLydike merged commit 056a98f into main Jun 16, 2023
12 checks passed
@AntonLydike AntonLydike deleted the anton/print-to-println branch June 16, 2023 09:42
cigarichard pushed a commit to cigarichard/xdsl that referenced this pull request Jun 17, 2023
This adds the `print-to-printf` pass to lower println ops to printf calls
superlopuh added a commit that referenced this pull request Jan 25, 2024
Now that Toy lowers to print_format, we can compile it to LLVM,
leveraging the printf flow added in #1142 .
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
transformations Changes or adds a transformatio
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants