-
Notifications
You must be signed in to change notification settings - Fork 182
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
[xls][mlir] Add LiteralOp to XLS MLIR Dialect. #1866
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -484,6 +484,33 @@ Type getI1TypeOf(Type type) { | |
|
||
namespace mlir::xls { | ||
|
||
LogicalResult LiteralOp::verifyRegions() { | ||
Block& b = getInitializerBlock(); | ||
|
||
// Verify return op & typing: | ||
auto ret = dyn_cast<YieldOp>(b.getTerminator()); | ||
if (!ret) { | ||
schilkp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return emitOpError( | ||
"initializer region must be terminated by an xls.yield op"); | ||
} | ||
if (ret->getNumOperands() != 1) { | ||
return emitOpError("initializer region must return exactly one value"); | ||
} | ||
if (ret->getOperand(0).getType() != getType()) { | ||
return emitOpError("initializer region type ") | ||
<< ret->getOperand(0).getType() << " does not match literal type " | ||
<< getType(); | ||
} | ||
// Verify that operations inside are allowed inside a literal region: | ||
for (Operation& op : b) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you make it a trait, you should be able to use llvm all_of kind of check here with trait checking. |
||
auto iface = dyn_cast<LiteralMemberOpInterface>(op); | ||
if (!iface || !iface.isPermissibleInsideLiteral()) { | ||
return op.emitError() << "op not permissible inside literal region"; | ||
} | ||
} | ||
return success(); | ||
} | ||
|
||
LogicalResult TupleOp::inferReturnTypes( | ||
MLIRContext* context, std::optional<Location> location, ValueRange operands, | ||
DictionaryAttr attributes, OpaqueProperties properties, RegionRange regions, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -562,7 +562,12 @@ def Xls_SendOp : Xls_Op<"send", [ | |
// Array operations | ||
//===----------------------------------------------------------------------===// | ||
|
||
def Xls_ArrayOp : Xls_NonScalarizableNaryOp<"array", [Pure, SameTypeOperands, TensorArrayTypeFungible]> { | ||
def Xls_ArrayOp : Xls_NonScalarizableNaryOp<"array", [ | ||
Pure, | ||
SameTypeOperands, | ||
TensorArrayTypeFungible, | ||
DeclareOpInterfaceMethods<LiteralMemberOpInterface> | ||
]> { | ||
let summary = "Array creation operation"; | ||
let description = [{ | ||
Constructs an array of its operands. | ||
|
@@ -721,8 +726,12 @@ def Xls_ArrayConcatOp : Xls_Op<"array_concat", [Pure, TensorArrayTypeFungible]> | |
// Tuple operations | ||
//===----------------------------------------------------------------------===// | ||
|
||
def Xls_TupleOp : Xls_Op<"tuple", [Pure, DeclareOpInterfaceMethods<InferTypeOpInterface>, | ||
TensorArrayTypeFungible]> { | ||
def Xls_TupleOp : Xls_Op<"tuple", [ | ||
Pure, | ||
DeclareOpInterfaceMethods<InferTypeOpInterface>, | ||
DeclareOpInterfaceMethods<LiteralMemberOpInterface>, | ||
TensorArrayTypeFungible, | ||
]> { | ||
let summary = "Tuple operation"; | ||
let description = [{ | ||
Constructs a tuple of its operands. | ||
|
@@ -1048,10 +1057,16 @@ def Xls_MapOp : Xls_Op<"map", []> { | |
// TODO(jmolloy): DynamicCountedFor (but the XLS code generator cannot handle | ||
// it). | ||
|
||
def Xls_YieldOp : Xls_Op<"yield", [Pure, ReturnLike, Terminator]> { | ||
def Xls_YieldOp : Xls_Op<"yield", [ | ||
Pure, | ||
ReturnLike, | ||
Terminator, | ||
DeclareOpInterfaceMethods<LiteralMemberOpInterface> | ||
]> { | ||
let summary = "for return op"; | ||
let description = [{ | ||
Yields an SSA value from the enclosing ForOp, EprocOp, or SprocOp region. | ||
Yields an SSA value from the enclosing LiteralOp, ForOp, EprocOp, or | ||
SprocOp region. | ||
}]; | ||
|
||
let arguments = (ins Variadic<AnyType>:$results); | ||
|
@@ -1159,7 +1174,10 @@ def Xls_CountedForOp : Xls_Op<"counted_for", | |
// Sequencing operations | ||
//===----------------------------------------------------------------------===// | ||
|
||
def Xls_AfterAllOp : Xls_Op<"after_all", [Pure]> { | ||
def Xls_AfterAllOp : Xls_Op<"after_all", [ | ||
Pure, | ||
DeclareOpInterfaceMethods<LiteralMemberOpInterface> | ||
]> { | ||
let summary = "Constructs partial orderings among channel operations"; | ||
let description = [{ | ||
Used to construct partial orderings among channel operations. | ||
|
@@ -1177,6 +1195,13 @@ def Xls_AfterAllOp : Xls_Op<"after_all", [Pure]> { | |
build($_builder, $_state, TokenType::get($_builder.getContext()), ::ValueRange{}); | ||
}]> | ||
]; | ||
let extraClassDeclaration = [{ | ||
bool isPermissibleInsideLiteral() { | ||
// xls.after_all is only permissible inside literal regions if it is | ||
// used to create a new token with no prior dependencies. | ||
return getOperands().empty(); | ||
} | ||
}]; | ||
let assemblyFormat = [{ | ||
$inputs attr-dict `:` custom<VariadicSameOperandsAndResultType>(ref($inputs), type($inputs), type($result)) | ||
}]; | ||
|
@@ -1200,7 +1225,11 @@ def Xls_ConstantTensorOp : Xls_Op<"constant_tensor", [ConstantLike, Pure]> { | |
let hasFolder = 1; | ||
} | ||
|
||
def Xls_ConstantScalarOp : Xls_Op<"constant_scalar", [ConstantLike, Pure]> { | ||
def Xls_ConstantScalarOp : Xls_Op<"constant_scalar", [ | ||
ConstantLike, | ||
Pure, | ||
DeclareOpInterfaceMethods<LiteralMemberOpInterface> | ||
]> { | ||
let summary = "Constant scalar operation"; | ||
let description = [{ | ||
Produces an output from a constant value. | ||
|
@@ -1225,6 +1254,48 @@ def Xls_ConstantScalarOp : Xls_Op<"constant_scalar", [ConstantLike, Pure]> { | |
let hasFolder = 1; | ||
} | ||
|
||
def Xls_LiteralOp : Xls_Op<"literal", [ | ||
ConstantLike, | ||
Pure, | ||
IsolatedFromAbove, | ||
SingleBlockImplicitTerminator<"YieldOp"> | ||
]> { | ||
let summary = "Complex Literal"; | ||
let description = [{ | ||
Produces a complex (non-bits) constant value from a region containing | ||
simple constants or tuple/array construction operations. | ||
|
||
The initializer region must be terminated by a `xls.yield` op, which returns | ||
the constructed value. | ||
|
||
Besides this yield, only the following operations are permissible inside | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be computationally cheaper to just have a helper function with the TypeIDs of these ops rather than using traits (you'd be doing pointer comparison based on name rather than doing a linear scan per op through trait list). If these are this well known and we don't need to support any other dialects/unknown supported op types. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not an unreasonable suggestion, but personally I like that this is all having tablegen as the source of truth, and I'm not majorly worried about linear scans of literal regions in the verifier (they'll be small). |
||
the initializer region: | ||
|
||
- `xls.constant_scalar` | ||
- `xls.after_all` (with no operands) | ||
- `xls.tuple` | ||
- `xls.array` | ||
|
||
The `LiteralMemberOpInterface` interface is used to determine if a given | ||
incarnation of an operation is permissible inside a literal region. | ||
}]; | ||
let regions = (region | ||
SizedRegion<1>:$initializer | ||
); | ||
let results = (outs | ||
Xls_BitsOrTuple:$result | ||
); | ||
let hasRegionVerifier = 1; | ||
let extraClassDeclaration = [{ | ||
Block &getInitializerBlock() { | ||
return getInitializer().front(); | ||
} | ||
}]; | ||
let assemblyFormat = [{ | ||
attr-dict `:` type($result) $initializer | ||
}]; | ||
} | ||
|
||
//===----------------------------------------------------------------------===// | ||
// DSLX operations | ||
//===----------------------------------------------------------------------===// | ||
|
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 make this a trait and only add when true?
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 considered this, but as it stands right now the decision for if an op is permissible inside a literal is dynamic:
XLS IR allows for token literals:
According to Eric this is equivalent to generating a new token with an empty
join()
in DSLX, resp.after_all
in XLS IR without operands. I am assuming the reason this is supported is because it allows the literal IR op to construct complex types with tokens nested inside.James and I agreed that for the MLIR side we would not introduce an extra op for this, and simply allow an
xls.after_all
with empty operands inside literal regions. This, in turn, means that forAfterAllOp
, this can't really be a trait and needs some (simple) logic:Hence I went with the interface because it still allowed me to have this operation property included in a (halfway) declarative fashion in the ODS.
If you prefer, I can of course refactor this into a helper func which checks op IDs + checks
after_all
for an empty operand list.Let me 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.
That rationale makes sense to me.