Replies: 17 comments
-
|
Thank you @urso for this proposal. It's interesting, and overall useful. I need to read it again in more detail and digest some of it, but overall, I'm in favour of what you're doing. I'll just address some points (which came to my mind immediately) here and write another response after understanding it all in more detail.
I think we can figure out a way to merge your trait into the existing one and make it all more neat.
I'd used it in an earlier version of
I'm happy that I'm not the only one with that opinion :) .. I'm, even now, working on making it easier. And that's also the reason I suggest that I need a little more time before I can confidently request for (merge) your derives for Overall, thank you again for what you're doing. Let's discuss more and merge it all soon, at least the
It looks really cool :-)
Can you elaborate more? My whole idea was to have them be reusable. For example, a |
Beta Was this translation helpful? Give feedback.
-
+1 supporting the generic format out of the box. At least it gets you started with your own IR very easily. But the generic format is also super verbose. It would still be nice to use the What I have had in mind of for Op as e.g. the
Yeah, I thought so.
Fair. I think I will handle this in the asmfmt parser in the derive macro then. I also wanted to split literals on whitespace and maybe some symbolic characters, such that input like
I'm glad to see improvements in that area :)
Don't get me wrong. I still think we need something like In my mind Parsers and parser combinators on the other hand carry some configuration so to say. This is true for the I initially tried using I later opted to introduce Still having The Having I think But this also makes me wonder if Assuming or making use of the new return position impl trait feature (in stable with rust 1.75): For types we would require users to implement That trait works well for types and attributes as is. For types and attributes it is a must that this interface is implemented. This is what allows us to integrate those types with the code generated from the macros + register the parsers in a shared table mapping object id names to parsers. For Operations we currently require a parser that also accepts a The other exception is The good thing about MLIR is that although the IR is open and extensible, the format of common object types like Types, Attributes, Ops, Regions is still limited. This reduces the need for a fully generalized parsing approach. A set of 'smaller' but reusable parsing functions with concrete types might be good enough. At least this is the approach I've chosen for the printers. |
Beta Was this translation helpful? Give feedback.
-
These two are related. The way to parse any operation is to call and pass on the control to the and for this, above, to work, the outer Regarding your suggestions and questions on the |
Beta Was this translation helpful? Give feedback.
-
|
I pushed a couple of simplifications / improvements (IMO) to When you get time, may I ask that you update your branch so that I can play around with Thank you again. |
Beta Was this translation helpful? Give feedback.
-
Sure. I just rebased the branch on top of your latest changes (Diff) |
Beta Was this translation helpful? Give feedback.
-
I see. This is the part I wonder about. My impression is that this way the Op parser has to participate in building the SSA. Normally I would assume parsing operates like this: step 4 would optionally validate that lhs and rhs are compatible, but IR validation could also be postponed to a second stage. Checking the MLIR sources, this is exactly how the MLIR AsmParser operates:
The way MLIR does it is pretty much what I have in mind. Have the op parser concentrate on parsing the body only. I don't think we should have downstream developers help in building the IR. All the name tracking and instruction building (adding ops and assigning ops to the IR) should be internal to pliron. Another thing that I like about the MLIR approach is that the MLIR Due to MLIR always guaranteeing they support the generic format (instead of pushing that decision on the Op-developer) it would allow users to eventually convert a file to the generic format in case of breaking changes in the ASM format of an op. Then I as user can take my file (that I use in CI), print the generic format using the old code, read the generic format and print the new format using the new code. Maybe not as big of an issue in general, but for me as developer putting instruction files into the repo for CI it is a nice feature to have. |
Beta Was this translation helpful? Give feedback.
-
We could do this, but the interface would prevent us from having MLIR doesn't have this problem (i.e., they can parse such I agree about your concern that users shouldn't be dealing with registering / checking SSA names. The problem is easily solvable for operands by providing an
From what I understand, what we have is strictly more general than this. an In summary, it appears to me that having the |
Beta Was this translation helpful? Give feedback.
-
This commit tries to address the problem. It isn't 100% satisfactory, but it is nearly there. |
Beta Was this translation helpful? Give feedback.
-
I see. I wasn't aware that Op parsing in MLIR is not context free. Alternatively to requiring It seems like it doesn't really matter if users implement a trait on their Op as we do register a parser function with the dialect. But in the end it doesn't really matter if we have the generic Parsable trait that accepts Args or more type directed traits like It seems that the Op parsing will only be driven by
Nice. The
Well, it is easy to integrate and use Parsable. Especially if It is just the for the actual parser functions/directives I added that the |
Beta Was this translation helpful? Give feedback.
-
That would be ideal. At the moment I don't because the current syntax allows for an One solution could be to have the custom parser return any other results (what weren't passed to it when it was called) back so that we an merge them all and call this function. That doesn't seem nice at all. The other solution would be to restrict that any results must be parsable by
I didn't understand this. Can you point me to a place in your code to illustrate? Maybe that's where I can begin looking at your code, to get started. All |
Beta Was this translation helpful? Give feedback.
-
I see. As a consequence of that I introduced a When using the asm format configuration we will basically prepend The discussions about Parsable got me thinking and I will spend some time to overhaul the Printers a little. In the end I would like to end up with uniform declarative asm format syntax that will work for both, parsers and printers.
By boxing I don't mean that I need to wrap anything. It is the
Regarding parsing there is not much code. There are just a few test parsers in |
Beta Was this translation helpful? Give feedback.
-
I suppose you mean this ? The code change goes against what you're saying. If we do that, then custom printers will be forced to have their results always printed ahead of the A few more comments on your branch
|
Beta Was this translation helpful? Give feedback.
-
Oops, I didn't intend to commit this. I indeed removed it from Operation::fmt already when I pushed that commit.
Absolutely. It is still very much work in progress. But I will definitely split the PR up in smaller pieces.
I understand. I actually didn't mean to keep them. It was me testing if the helper function type check and work correctly without changing all the existing Ops to use the derive macros. In case you want to discuss on the code I did open a draft PR: #20 Actually I opened it against my own local fork, but for some reason github decided to create the PR against your repository. Sorry for that. I'm planning to eventually split the PR into smaller PRs once it stabilizes a little more. |
Beta Was this translation helpful? Give feedback.
-
Thank you. I didn't do a detailed review, but I like the direction in which the whole thing is going. So can I pause for the moment now and just wait until you ping me back, saying that it's ready for review? Is there anything specific that you want me to comment on or help you with now? |
Beta Was this translation helpful? Give feedback.
-
|
I think we can pause the conversation for now. I will focus on the printers and maybe ask you for guidance when I'm about to split up the PR. |
Beta Was this translation helpful? Give feedback.
-
|
Hi, besides me adding more tests I'm happy with the proc_macros for I started to split the work into PRs. Let's start with the new traits I introduced: #21 |
Beta Was this translation helpful? Give feedback.
-
|
This mostly works now, see: https://github.com/vaivaswatha/pliron/wiki/Declarative-Syntax-Specification. So closing. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi,
I was looking for a project that allows me to represent my rather high level IR in SSA like form including support for printing and debugging the SSA. Think MLIR like and eventually found pliron. Also its still early in development the project looks interesting to me.
Although pliron allows me to store my IR, we don't get printers/parser for free yet. Unfortunately I'm not too lazy to write printers and parsers for my IR, yet I want them.
Long story short, I tried to implement proc macro to declare Ops, Types, Attributes, Parsers, and Printers in the hope that it makes my live (and maybe others) easier in the future.
It is still an early POC, but before I maybe waste too much time on it (or too much of your time by opening a huge cryptic PR) I would like to bounce of my ideas, make it a proposal and see if the project is interested in incorporating the changes I have in mind.
Also because I wonder if some of the code in my POC goes into a different direction as the rest of the code base does.
I'm happy to help making required changes to make it easier for users to create printers and parsers.
Proposal: derive Printable/Parsable from declarative assembly format
Note: Many of the things I'm proposing are available as code. Although I still might want to change a few things in my POC. You can check out the changes in the Diff.
When using MLIR it is possible to configure the printing and parsing of types, attributes, ops using the
assemblyFormatsetting. This setting provides a small MLIR specific DDL to configure how the current object shall be printed.If no format is given some objects (e.g. Op) will use a generic format.
The idea is that we will provide a derive proc-macro for Printable and Parsable. Both of them accept a declarative assembly format that is compatible with the
assemblyFormatsetting used by MLIR.In case the assembly format setting is missing we will fallback to a generic format for types/attributes/operations. This way users will get printers/parsers for free, but are able to refine the format with minimal effort.
Some examples how the configuration might look like (assuming we have
Type,Attributederive macros and so on):Create integer type with generic format:
The printer will create an output string like
testing.integer<sign=true, width=32, align=8>.Using
asm_formatsetting we can configure the format explicitely. The generic format is equivalent to the follow piece of code:Similar to MLIR we also want to support directives. For example the
function_typedirective can be used like so:The use of the Printable/Parsable derive macros is optional. Users are still allowed to implement their own printers and parsers. One might also choose to generate the
Printableparser only.Next to the
Parsablederive we might also provide aNotParsablederive macro for users that are not interested in that functionality.Proposal: derive Op, Type, Attribute implementations
Note: For now I had implemented deriving the objects using proc_macros out of necessity. When deriving the printers/parsers we need to know what kind of IR object we are going to have. The trick I'm using is to check the macro attributes for
type_name,attribute_name, orir_kindsettings. a pure proc macro "implementing" an operation can splice in the#[ir_kind="op"]into the token stream and this way give the Parsable/Printable derive macros some additional information.Operations always create a struct like
struct MyOp { op: Ptr<Operation> }. The currentdeclare_opcreate the struct for our users.Using proc macros an Op shall be declared like so:
The
declare_opmacro will rewrite thestruct MyOptostruct MyOp{ op: Ptr<Operation> }and add the#[ir_kind="op"]attribute for other derive macros to pick up. The template fordeclare_opreads similar to this:Similar to
declare_opwill we support adeclare_typeand adeclare_attributemacroNote: in TableGen one uses
def Name, maybe we want to renamedeclare_Xtodef_x?.A type can be declared like so:
Attribute example:
Note: The POC currently uses an
AttributeandTypederive macro. The limitation is that theParsable/Printablederivers must understand the settings of the other macros to implement some heuristics on what kind of object they are dealing with. Havingdeclaremacros allows us to splice in a common#[ir_kind="..."]attribute. Plus it feels much more consistent with thedeclare_opmacro.POC: Asm format implementation/backend
Feel free to ignore this section if you are not interested in the details on how printing/parsing helpers are implemented in the POC.
This section is more about giving you hints and an explanation to the changes I made to the project to get this working.
The Printable/Parsable derive macros parse the
asm_formatattribute and generate some code based on that definition.We want to keep the code generated minimal. Which is why we provide all functionality via the
asmfmtmodule.The asmfmt module provides printers and parsers (and combinators) which allows users to reuse the primitives used by asmfmt in custom code.
The POC has no common
objectsthat support both printing and parsing. Instead we have a set of functions with similar names in each module. For exampleparsers::literalandprinters::literal. This is maybe not ideal, but allows us to play with the interfaces until the code (and interfaces) have stabilized a little. e.g. within the POC parsing is not yet really implemented at all.Later for common object types we might consider to introduce structs that both implement the printer and parser interfaces.
Directives are implemented via macro_rules. Originally I started with functions for the directives, but hit some limitations dealing with fields that might implement Printable, Display, or both. By using macros we give the code some flexibility to handle different cases. Directives always have the suffix
_directive.For example the
function_typedirective:Here we use the
print_varmacro that uses autoref specialization to give us a printer for types that implementPrintable, or a collection ofPrintables, or types that only implementDisplay.Printers
The core concept of the
asmfmt::printersmodule is that we have a composable set of printers.A printer (generator) is a function that returns a
impl Printable. Currently the code uses a more or less functional approach. We usePrinterFnas wrapper to create our set of printers. For example theliteralprinter can be implemented like so:We also provide some combinators like
concat,list_with_sep, oriter_with_sep.The
list_with_sepanditer_with_sepcombinator replace thePrintableListinterface, which has actually been removed. I did struggle integratingPrintableListinto the set of combinator and found it easier to maintain composability of printers by relying on a single interface only. In my mind I found thePrintableis very capable to deal with all our needs.Using the combinators the
operatorsdirective in MLIR assemblyFormat can be implemented like this:The
attr-dictdirective primitive becomes:By providing all assembly format primitives as combinators we allow users to writing custom printers reusing most of the primitives that we have already.
For example MLIR generic op format implementation used by the Printable derive macro as fallback.
Parsers
Honestly, I really struggled with the parser interface. The combine::Parser as is was hard to work with. It seems to be doable by using some of the combine parser combinators, but the error returns are a really difficult to work with.
The Parsable trait makes it easier to handle errors and gives us all the state that we are interested in, but unfortunately it is difficult to declare reusable primitives similar to the
asmfmt::printers. Especially becauseParsabledoes not really allow me to pass aselfparameter into the parser. I tried to trickArgto do that for me, but the code did become quite painful.I also struggled with the bound
'ainfn parse<'a>. Not having the bound on the trait did make my approach prototyping parsers viaFnfail.As a workaround to have a functioning parser I introduced yet another parser trait :(
This is definitely not ideal as we now have 3 traits in the system that tell us on how to declare and compose a parser. I halso had to name the method
parse_nextbecause the compiler didn't like me mixing all the traits in a single consuming module :(The
parsersmodule provides afrom_parseablewrapper to create a parser from aParsabletype, so we can mix the two.Based on that we can prototype the
literalparser like so:For example in the tests (Note: I should put them into its own file). We have an a custom
IntegerTypewithout asm_format like:The derive macro creates this Parsable implementation (currently all generated code is printed to console when compiling):
POC: Proc macro implementations
Feel free to ignore this section if you are not interested in the details on how printing/parsing helpers are implemented in the POC.
This section is just an intro to the derive macro implementations to make it easier to get to know the changes.
You can find the derive macros in the
derivefolder. All macros follow the same patterns of: parse input, verify input, produce output from template. All derive implementations return asyn::Resultwhich is translated into a compiler error if verification/parsing failed.derive/src/asmfmt.rsImplements the parsing of the assembly format DSL, producing a common representation to be used by the Printable/Parsable derive macros.Note: As I struggled with
combinequite a bit I opted to usewinnowhere.plirons Crate.toml was updated to importpliron_derive. Plussrc/lib.rswas update such thatplironcan be used as a modue name withinpliron(this allows us to use the derive macros inplironitself):Because the assembly format directives are implemented as macro_rules we ensure that the macros can be found within the pliron project by declaring the internal macros like so:
Follow ups?
derive Verify
This proposal focuses on Printer/Parsable. But once the machinery is in place we might consider a derive macro for
Verifyas well.Indentation support?
The indentation support seems to require us to explicitly use helpers to create properly indented new lines. When using
asm_formatwith a\nliteral that might break the output and indentation level.Maybe we have to parse and handle that case in the
literalhelper. Parse the string for newlines and append the proper set of spaces after a newline.For formatting/output I wonder if you have considered
pretty? Although not sure if we really want to build an intermediate "document" representation before formatting the output.I recently have had a similar problem and did find the
indent-display[https://docs.rs/indent-display/0.1.1/indent_display/#traits] cargo quite interesting. That one splits the lines on\nand applies the proper indentation. This mixes somewhat well with types that implement Display and still want to produce a multiline output. Plus "pop_indent" is implicitly provided via RAII.Just some thoughts. I'm thinking to add the newline + indentation support to
asmfmt::literalonly.Parsers?
Honestly working with combine + the Parsable trait and the different Result return types (some supporting Try, others not...) was a big pain and caused me much headaches. It also seems like loads of boxing is going on here.
This is what forced me to introduce the AsmParser trait, just to make some progress and pin down the types.
I don't have much experience with combine yet, but honestly I find the parser trait of winnow to be much easier to work with. In the derive macro I started with
combine,but got frustrated with it and switch over.I also wonder if it would make sense to introduce a lexer and have the parser operate on tokens. Having a lexer would trim down the lexical grammar limiting what can be done in parsers (and you don't have to think about spaces). MLIR for example follows that approach. Plus they have a list of simple parsers to be combined, which is not too dissimilar from the approach used in the
asmfmtmodule.Beta Was this translation helpful? Give feedback.
All reactions