-
Notifications
You must be signed in to change notification settings - Fork 299
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
[FIRRTL] Add a convention attribute to modules #4665
Conversation
@@ -28,6 +28,8 @@ constexpr const char *rawAnnotations = "rawAnnotations"; | |||
// Annotation Class Names | |||
//===----------------------------------------------------------------------===// | |||
|
|||
constexpr const char *conventionAnnoClass = | |||
"firrtl.linkage.Convention"; |
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.
@seldridge @jackkoenig I'm adding this annotation to mfc before adding it to chisel. Any suggestions on a good class name?
1aa91f4
to
9786ab7
Compare
|
||
| Property | Type | Description | | ||
| ---------- | ------ | --------------------------- | | ||
| class | string | `firrtl.linkage.Convention` | |
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.
do we want this to be the level of the annotation, or do we want something more blanket firrtl.annotations.PublicModule
?
which will act as a proxy for a public module
that might get added to firrtl spec?
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.
High level: this seems fine.
Providing more fine-grained control and doing it by adding a per-module/extmodule attribute (controlled by an annotation that is immediately removed) seems sound.
I don't know what to call this. "Linkage" seems a bit esoteric as opposed to something more pedestrian like: "Lowering Style".
There is an alternative of going more directly into a single annotation, "Public", which controls if module ports are lowered or not. That precludes more fine-grained lowering styles (it's all or nothing) which we don't have a use case for today.
(ins DefaultValuedAttr<AnnotationArrayAttr, "{}">:$annotations); | ||
let arguments = (ins | ||
ConventionAttr:$convention, | ||
DefaultValuedAttr<AnnotationArrayAttr, "{}">:$annotations |
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.
Does declaring this with a default value of "internal" simplify some of the parsing logic later around checking for an empty attribute?
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.
As far as I can tell, DefaultValuedAttr
doesn't help here, because we skip default builders and we have a custom asm printer/parser.
42ac8cb
to
a8c6449
Compare
The problem with the public/private concept, is that it brings a lot of baggage with it. We can't constant prop across a public module's ports, or delete any ports, have uninferred widths or resets, or change anything about the module's interface. This is a problem for our use case in unit testing; we want to be able to test modules with uninferred resets. public/private primarily is a statement of visibility between compilation units, which is distinct from the convention used. I think we could eventually have many different supported conventions (e.g. bundle splitting on flow, 1d-vector-preservation). I also think, very soon, we will want to be able to specify the visibility/linkage of a module (public vs private) in addition to it's convention, and that these two orthogonal mechanisms can work very well together. It might be interesting to take a look at LLVM's linkage and calling convention support and how they work together. |
buildModule(builder, result, name, ports, annotations); | ||
result.addAttribute("convention", convention); |
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 we add "convention" attribute within buildModule
function?
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 code is out here because mem modules don't have a settable convention attribute, but still build using the buildModule helper. If mem modules grow a convention attr, we can share mode code, but I am punting on mem modules for 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.
There are equivalent concerns for mem modules, but the implementation is burried in annotations and multiple passes. An issue, at least, should be filed for unifying memory module lowering with this. (e.g. memories may flatten types, they may split up memories on field boundaries, etc).
Separating linkage and visibility make sense. If the convention attribute is mandatory, can we implement custom parser instead of using attr-dict?
Also what's expected behaivor of "public internal" module? |
Separating visibility from calling convention sounds good to me, especially if we want to change one and not the other (as you say). Even if some go together, nice to separate the concepts, love the direction of this PR. Just to be clear, calling convention is usually not about a changed (high-level) interface/signature, but the mechanisms used to implement it (e.g., pass aggregates as such or scalarized (this PR), or pass arguments on stack vs register and how/where). Which may be explicitly modeled during lowering or in the backend IR, of course. If this idea works differently (perhaps a documented guaranteed equivalence between one signature with one convention and another with the known-to-be-compatible signature, and what that means?), it'd be good to specify this. Put another way: Which signatures are legal to use with which signatures, across the pairs of calling conventions. Do we anticipate any calling conventions that cannot be represented with equivalent signatures at the FIRRTL level? Little unsure how this helps the mentioned driving use case, however:
Interesting! Are these tests going to be built as part of the design, then, or do we need a new (visibility?) variety that captures what it seems is being asked for here: expose the module for visibility (symbol-wise), but loosen (some?) of the interface-must-not-change requirements (allowing const-prop, uninferred resets)? "visible but safe to optimize as if it wasn't"? Can ports be removed, must widths be inferred? (What happens if a constant is propagated through but a unit test drives using a different value?) From a different angle, since maybe there's missing context: how does a unit test interact with these, I guess in a "white box"-style approach somehow whereby it's okay to not know the interface of the module under test (until it's been built to a lower form)? Does it expect to "link" from an interface w/an uninferred reset to an implementation with resets (/widths/etc.?) inferred? Maybe we should discuss some of this offline 😇 .
EDIT: Oh, I see, the proposed annotation is calling it the linkage convention. Hmm. nit: Calling convention and linkage are not the same thing, and I don't think this is about linkage. Calling convention seems like a good fit, IMO. Whether what we have now re:public/private is more visibility or linkage is hard to say, since we don't have a notion of "linking" yet-- much of the linkage types is specifying how things are resolved/combined when linked together (and how that's indicated in the output file format)., so I'm inclined to call it visibility for now until linking is a thing. Linkage concept for us, might specify whether two modules linked with the same name can be "deduplicated" or if having the same name is a collision, or if they're "hidden" and can be renamed, so on. I don't think we likely want things like weak or appending 😄 , at least not for modules. Hopefully we won't grow the complexity of LLVM's linkage 😄 . |
36a7bdd
to
44215b5
Compare
In a few places I was using linkage to refer to both visibility and calling convention together, despite knowing better (in my mind, linkage is visibility + details). I was trying to put a word to "how and when and module can be instantiated", and misappropriated the word. I pushed a few changes to align on "module instantiation convention" and "visibility". |
My intent is that the convention at the instantiation site would exactly match the target module's convention, just as you say Will, but it is interesting that for some signatures, different conventions might result in compatible lowerings... Something to meditate on I guess? Anyways, I haven't added a convention attribute to the instance op in this PR. |
Alright, I'm onboard with the linkage description. This is very, very related to the concept that Generally, I think "linkage" is the right way to think about this. Public/private was something that we kind of invented for FIRRTL and I agree it doesn't align correctly with the concept here. We've gotten into issues with this before where we use a public symbol to mean one thing which runs against what normal MLIR conventions would mean. |
That would be a module that is public, and uses the internal calling convention / instantiation convention. In the future, having a public module with internal convention is probably too dangerous to allow, because the lowering of internal convention changes depending on compiler flags, which may vary across compilation units. For now though, I think it's okay to allow. |
I think modelling calling convention separately from linkage makes sense, but alright. They're still not the same thing 😄 , even if not all combinations are possible or one implies certain types of the other. Language linkage can imply a certain calling convention, although FWIW that's not what 6.6 is about, it's very clearly about how things link together and in what namespaces they reside? 9.11 (in n4917 anyway) does have this note:
So if by Mostly just want to be sure we're clear what this is. Calling it linkage works for me, and doesn't need to map to LLVM or C++ concepts other than it helps to re-use concepts folks know. Getting this into the FIRRTL spec sounds great. I was a bit off thinking this was meant to be calling convention alone, when it appears to be a combination of the two (+visibility(?), which is reasonably part of linkage as mentioned). What makes it possible for modules to be visible externally (for unit testing) without inferred resets? Is that a new (not included) "linkage" kind? Or is the plan to have unit-tests target private (for now?) modules that have the unit-test-compatible linkage type (which in this PR seem about whether types are lowered, not whether widths/resets/etc are inferred?). Don't mean to say it needs to all be figured out before moving forward, just want clear and consistent concepts. Ideally with the context needed to understand this included or available. Anyway this is getting away from the details of this PR, which I think seems great. The utility of this for unit-testing use-case is unclear, but that is perhaps a discussion to be had elsewhere. |
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.
Some minor suggestions.
|
||
```json | ||
{ | ||
"class": "firrtl.linkage.Convention", |
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 doesn't match the documentation above. However it's called, they should match. Sorry for the noise on re:feedback.
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.
Thanks yes, totally forgot to change these. I promise to eventually eradicate the word linkage from this PR. I still need a real class name that we can use in chisel, suggestions anyone?
@@ -28,6 +28,7 @@ constexpr const char *rawAnnotations = "rawAnnotations"; | |||
// Annotation Class Names | |||
//===----------------------------------------------------------------------===// | |||
|
|||
constexpr const char *conventionAnnoClass = "firrtl.linkage.Convention"; |
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.
(similarly, re:matching everywhere)
return parseFModuleLikeOp(parser, result, /*hasSSAIdentifiers=*/true); | ||
if (parseFModuleLikeOp(parser, result, /*hasSSAIdentifiers=*/true)) | ||
return failure(); | ||
if (!result.attributes.get("convention")) |
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 how this is done elsewhere, but consider hoisting the attribute name to some global location.
44215b5
to
f5f7204
Compare
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
| ---------- | ------ | --------------------------- | | ||
| class | string | `firrtl.module.Convention` | | ||
| convention | string | `scalarize` or `internal` | | ||
| target | string | Reference target | |
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 seems like one should have target strings for modules, but the Chisel way is to have this information on instances. @seldridge should comment on implementability on the chisel side.
| Property | Type | Description | | ||
| ---------- | ------ | --------------------------- | | ||
| class | string | `firrtl.module.Convention` | | ||
| convention | string | `scalarize` or `internal` | |
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 internal necessary? Should the default be "whatever we feel like" and only deviations from that be explicit?
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.
dropped internal 👍
@@ -442,31 +436,26 @@ struct TypeLoweringVisitor : public FIRRTLVisitor<TypeLoweringVisitor, bool> { | |||
// Cache some attributes | |||
const AttrCache &cache; | |||
|
|||
// Keep track of public modules. | |||
const llvm::DenseSet<FModuleLike> &publicModuleSet; | |||
const llvm::DenseMap<FModuleLike, Convention> &conventionTable; |
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.
Instead of a side map, can't you just ask the module when you need to know?
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.
Since this pass is mutating module annotations concurrently across multiple threads, accessing another module's attributes directly can crash mfc. In an earlier issue, @uenoku had to switch to a side-table to track visibility, for this reason.
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 thought on this pass the module op mutation was done serially and only the module bodies done in parallel.
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.
Ah, yes, it was depending on parallel multation resulting in the same type. We've generally split module update form module body update to solve this, but it's fine for now. When module type lowering is split out to it's own pass, this whole pass can become a fmodule pass instead of a circuit pass.
|
||
// Set true if the lowering failed. | ||
bool encounteredError = false; | ||
}; | ||
} // namespace | ||
|
||
/// Return aggregate preservation mode for the module. If the module has a | ||
/// public linkage, then it is not allowed to preserve aggregate values on ports | ||
/// unless `preservePublicTypes` flag is disabled. | ||
/// scalarized linkage, then we may not preserve it's aggregate ports. | ||
PreserveAggregate::PreserveMode | ||
TypeLoweringVisitor::getPreservatinoModeForModule(FModuleLike module) { |
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.
Specifically, here: module.getConvention();
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
d6a09e8
to
6be2874
Compare
6be2874
to
8f4aed4
Compare
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.
8f4aed4
to
cf4f0b7
Compare
cf4f0b7
to
acd0945
Compare
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
This annotation is being handled in mfc via: llvm/circt#4665 The convention lets users define how ports with aggregate types are lowered to verilog.
The convention attribute give us module-by-module control over how a module's ports should be lowered. When a module uses the "scalarized" convention, its ports are guaranteed be fully scalarized in the output verilog. When a module uses the "internal" convention, then it's ports will be lowered according to the configured aggregate preservation mode.
A module's convention is internal by default, but can be set using a new annotation, or through the firtool command line options
--scalarize-top-module
and--scalarize-ext-modules
.Enabling both
--scalarize-top-module
and--scalarize-ext-modules
together (which is the default) should be a drop-in replacement of the older--preserve-public-types
option, which has been removed.Changes
convention
attribute toFModuleLike
objects. This property is eitherscalarized
orinternal
.LowerTypesPass
.FModule
andFExtModule
have new builders that let you pass a convention through. Otherwise, they default tointernal
convention.FMemModule
always uses theinternal
convention, for now.scalarized
.scalarized
convention, the pass must scalarize it's ports, regardless of the aggregate preservation mode.internal
convention, the pass lowers ports based on the configured aggregate preservation mode.preservePublicTypes
option is removed.--scalarize-top-module
and--scalarize-ext-modules
.--scalarize-top-module
is enabled, FIRParser will mark the main module with thescalarized
convention. Otherwise, the top module will use theinternal
convention.--scalarize-ext-modules
is enabled, FIRParser will mark any ext modules with thescalarized
convention.firrtl.module.Convention
(please suggest a better name!)