Yes.
I don’t anticipate the pretty format to be backward compatible in most cases, but it is possible to implement a backward compatible custom parser, look for ParseResult parseVersionedOp
in the revision:
static ParseResult parseVersionedOp(OpAsmParser &parser,
OperationState &state) {
if (auto version =
parser.getDialectVersion("test").dyn_cast_or_null<IntegerAttr>()) {
if (version.getInt() < 42)
return parser.parseKeyword("deprecated_syntax");
}
return parser.parseKeyword("current_version");
}
And the test shows that both of these IR are parsed, current:
dialect_versions { test = 42 }
test.versionedB current_version
And old one:
dialect_versions { test = 41 }
test.versionedB deprecated_syntax
But what’s more interesting is the post-parsing hook: this will enable use-cases involving the generic printer to have an upgrade path when the dialect definition changes. This is more interesting than the syntax in my opinion.
No: that is a limitation, even if you version your dialect you are limited by the underlying serialization. The generic format hasn’t changed in a while but it isn’t a guarantee that it won’t. Also even in the generic format we’re dependent on the attribute/type parsers.
Also, someone interested in versioning may have to be careful about what other dialects they serialize as they may not be versioned with the same guarantees.
How so? I’m not sure I perceive the fundamental difference between ASCII and binary.
The bitcode format in LLVM has advantages in terms of size and speed sometimes, but otherwise the main advantage is the backward compatibility. But LLVM does not have a generic printer…
One thing with a “bitcode” is that because it isn’t human-readable, it is much easier to incremental upgrade it: you can add new “code” for new version of an entry, etc.
With a “pretty” printer readability is important and you can’t really change the generic printer while keeping it backward compatible all the time.
I’m not sure I understand the question? The way it works right now is that dialects opt in with a hook on OpAsmDialectInterface
(I reformulated in the original post in case it wasn’t clear).
So the TestDialect
for example implements:
Attribute getProducerVersion() const final {
return Builder(getDialect()->getContext()).getI32IntegerAttr(42);
}
And any IR that has entities from the test dialect will be emitted with this at the beginning: dialect_versions { test = 42 : i32 }
.
Note that the test dialect uses an IntegerAttr
but a dialect can use any attribute (including a custom one) to model their version scheme.
Dialects that don’t implement this interface method won’t trigger an entry in the dialect_versions
dictionary.
Makes sense, I suspect with this proposal you’ll already be able to implement what you want, even though we can have some core APIs to make it easier.
If you want to implement it yourself (after this proposal lands in some form), you can:
- Have a field in your dialect
targetVersionDeployment
.
- Before emission you can:
getContext()->getLoadedDialect<MyDialect>()->setTargetVersion(deploymentVersion)
.
- In the printer for this dialect, you have access to the
targetVersionDeployment
.
That said I suspect that most transformation could be done on the IR itself: before emission you may have to “downgrade” your IR internally (renaming op / attributes). That may be a problem in terms of the verifier though: if you want to support multiple IR version in memory and have the verifier be aware of this, we’re getting into deeper concerns than what I looked at here, which is just the serialization boundary.
This is a good point, someone really motivated could design their assembly with this in mind.
At some point, to get these constraints, I suspect you have to get into a “close ecosystem” for your dialects (not reuse things from upstream like Affine, Memref, etc.) and you may be better off with a custom serialization entirely (proto-based, flatbuffer-based, YAML, XML, whatever…).