Skip to content

Commit dc2403e

Browse files
jmolloyJames Molloy
andauthored
[circt][arc] Add bufferized array handling (#10391)
* [circt][arc] Add bufferized array handling Previously, !hw.array types were lowered directly to !llvm.array types. This is the obvious lowering, but unfortunately it produces very poor code and can in some cases bog down LLVM quadratically. LLVM's ArrayType is aggressively SROA'd - it is really assumed to be more like a tuple-type - so no matter how we try to avoid it (e.g. HWSpillCache), LLVM will expand each array into individual scalars. This creates a large number of instructions, but worse - it creates very difficult scheduling problems with high connectivity. LLVM's pre-regalloc scheduler has been seen to take hours on a problem that should be fairly simple. This PR fixes this issue by introducing an !arc.arrayref<T> type. This mirrors !hw.array<T>, except that it has buffer semantics rather than value semantics. That is, !arc.arrayref is to memref as !hw.array is to tensor. I did heavily look into using the exising bufferization passes in MLIR, but these are (a) quite hard to use, and (b) heavily tailored to the "tensor" and "memref" types. In the end it felt like much less complexity to implement our own barebones bufferizer. Each !hw.array op is translatable to an !arrayref op, except that the arrayref versions take a destination buffer to modify as an operand. They are mostly in destination passing style. The translation to LLVMIR dialect is trivial (!arrayref -> !llvm.ptr). There are other optimizations we can do to reduce the compile time (and runtime) of array-heavy code but they are left for further PRs: * Aggressive reduction of arc.arrayref.alloc ops (better bufferization). * Optimization of hw.array_creates where array permutations/shuffles have been expanded. * Proper handling of nested arrays. * Add dereferenceable(N) attribute * Apply review comments: * Inherit ArrayRefType from ShapedType * Attribute:$size -> uint64_t:$size * Add traits and predicates to all ops for better verification * Add missing rewriter.modifyOpInPlace * Fix erroeous UpperCamelCasing * Add support for nested arrays and arrays within structs. We support these by never converting a nested array, or any struct containing an array. Only "raw" arrays are converted to ArrayRefs. Introduces `arc.arrayref.from_array` and `arc.arrayref.to_array` as casters because hw.array is now expected to be legal in some scenarios. * Switch on by default! (yay!) * Fix integration test: * Explicitly declare Struct*Op as legal. * Plant a local copy of the array in StateReadOp. Assisted-by: Gemini:experimental to identify the StateReadOp bug. * Review comments: * DenseI64ElementsAttr -> ArrayAttr (again) * Handle OOB load, store and slice. * Remove extra alloca in slice lowering. * Replace Load/Store with destructured insertvalue/extractvalue * Act on review comments * Add type verifier to disallow !arc.arrayref<0xT> --------- Co-authored-by: James Molloy <jmolloy@google.com>
1 parent 857e7c9 commit dc2403e

13 files changed

Lines changed: 1524 additions & 10 deletions

File tree

include/circt/Dialect/Arc/ArcOps.td

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,8 @@ def RootOutputOp : ArcOp<"root_output", [
551551
// Storage Access
552552
//===----------------------------------------------------------------------===//
553553

554-
def AllocatableType : AnyTypeOf<[StateType, MemoryType, StorageType]>;
554+
def AllocatableType : AnyTypeOf<[StateType, MemoryType, StorageType,
555+
ArrayRefType]>;
555556

556557
def StorageGetOp : ArcOp<"storage.get", [Pure]> {
557558
let summary = "Access an allocated state, memory, or storage slice";
@@ -1134,4 +1135,146 @@ def RuntimeModelOp : ArcOp<"runtime.model", [
11341135
}];
11351136
}
11361137

1138+
//===----------------------------------------------------------------------===//
1139+
// ArrayRef ops
1140+
//===----------------------------------------------------------------------===//
1141+
//
1142+
// Ops that deal with bufferized arrays. The !hw.array type has value semantics;
1143+
// the !arc.arrayref type is the equivalent but with reference semantics.
1144+
1145+
def ArrayRefAllocOp : ArcOp<"arrayref.alloc", []> {
1146+
let summary = "array allocation";
1147+
let description = [{
1148+
Allocates an array on the stack. If `init` is set, the array is initialized
1149+
to this value. Otherwise the array's contents are undefined.
1150+
}];
1151+
let arguments = (ins OptionalAttr<ArrayAttr>:$init);
1152+
let results = (outs ArrayRefType:$output);
1153+
let assemblyFormat = "attr-dict (`init` `(` $init^ `)`)? `:` type($output)";
1154+
let hasVerifier = 1;
1155+
}
1156+
1157+
def ArrayRefGetOp : ArcOp<"arrayref.get", [
1158+
MemoryEffects<[MemRead]>,
1159+
TypesMatchWith<"array element type matches the result type", "input", "value",
1160+
"cast<ArrayRefType>($_self).getElementType()">
1161+
]> {
1162+
let summary = "array get";
1163+
let description = [{
1164+
Loads a single value from an arrayref.
1165+
}];
1166+
let arguments = (ins ArrayRefType:$input, Index:$index);
1167+
let results = (outs AnyType:$value);
1168+
let assemblyFormat = [{
1169+
$input `[` $index `]` attr-dict `:` type($input) `->` type($value)
1170+
}];
1171+
}
1172+
1173+
def ArrayRefInjectOp : ArcOp<"arrayref.inject", [
1174+
MemoryEffects<[MemRead, MemWrite]>,
1175+
AllTypesMatch<["input", "output"]>,
1176+
TypesMatchWith<"element type matches input element type", "input", "element",
1177+
"cast<ArrayRefType>($_self).getElementType()">
1178+
]> {
1179+
let summary = "array inject";
1180+
let description = [{
1181+
Stores a single value into an arrayref.
1182+
}];
1183+
let arguments = (ins ArrayRefType:$input, Index:$index, AnyType:$element);
1184+
let results = (outs ArrayRefType:$output);
1185+
let assemblyFormat = [{
1186+
$input `[` $index `]` `,` $element attr-dict `:` type($input) `,` type($element) `->` type($output)
1187+
}];
1188+
}
1189+
1190+
def ArrayRefSliceOp : ArcOp<"arrayref.slice", [
1191+
Pure,
1192+
AllElementTypesMatch<["input", "output"]>
1193+
]> {
1194+
let summary = "array slice";
1195+
let description = [{
1196+
Slices an array into a sub-array, returning a view into the original array.
1197+
}];
1198+
let arguments = (ins ArrayRefType:$input, Index:$lowIndex);
1199+
let results = (outs ArrayRefType:$output);
1200+
let assemblyFormat = [{
1201+
$input`[`$lowIndex`]` attr-dict `:` functional-type($input, $output)
1202+
}];
1203+
}
1204+
1205+
def ArrayRefCopyOp : ArcOp<"arrayref.copy", [
1206+
MemoryEffects<[MemRead, MemWrite]>,
1207+
SameOperandsAndResultType,
1208+
]> {
1209+
let summary = "array copy";
1210+
let description = [{
1211+
Copies the content of one array into another array. Returns the input array.
1212+
}];
1213+
let arguments = (ins ArrayRefType:$input, ArrayRefType:$source);
1214+
let results = (outs ArrayRefType:$output);
1215+
let assemblyFormat = [{
1216+
$input `=` $source attr-dict `:` type($input)
1217+
}];
1218+
}
1219+
1220+
def ArrayRefCreateOp : ArcOp<"arrayref.create", [
1221+
MemoryEffects<[MemWrite]>,
1222+
AllTypesMatch<["input", "output"]>,
1223+
TypesMatchWith<"operand types match element type",
1224+
"output", "elements", "SmallVector<Type, 2>("
1225+
"cast<ArrayRefType>($_self).getNumElements(), "
1226+
"cast<ArrayRefType>($_self).getElementType())">
1227+
]> {
1228+
let summary = "array create";
1229+
let description = [{
1230+
Creates an array from a list of values. The values are ordered from
1231+
most significant to least significant (the same as `hw.array_create`).
1232+
1233+
The `input` array is populated with `elements` and is returned.
1234+
}];
1235+
let arguments = (ins ArrayRefType:$input, Variadic<AnyType>:$elements);
1236+
let results = (outs ArrayRefType:$output);
1237+
let assemblyFormat = [{
1238+
$input `=` $elements attr-dict `:` type($output)
1239+
}];
1240+
}
1241+
1242+
def ArrayRefFromArrayOp : ArcOp<"arrayref.from_array", [
1243+
MemoryEffects<[MemWrite]>,
1244+
AllTypesMatch<["input", "output"]>,
1245+
TypesMatchWith<"array type matches output type",
1246+
"array", "output",
1247+
"ArrayRefType::get("
1248+
"cast<hw::ArrayType>($_self).getElementType(), "
1249+
"cast<hw::ArrayType>($_self).getNumElements())">
1250+
]> {
1251+
let summary = "arrayref from array";
1252+
let description = [{
1253+
Populates an arrayref with the elements of an array.
1254+
}];
1255+
let arguments = (ins ArrayRefType:$input, ArrayType:$array);
1256+
let results = (outs ArrayRefType:$output);
1257+
let assemblyFormat = [{
1258+
$input `=` $array attr-dict `:` type($output) `,` type($array)
1259+
}];
1260+
}
1261+
1262+
def ArrayRefToArrayOp : ArcOp<"arrayref.to_array", [
1263+
TypesMatchWith<"array type matches result type",
1264+
"input", "result",
1265+
"hw::ArrayType::get("
1266+
"cast<ArrayRefType>($_self).getElementType(), "
1267+
"cast<ArrayRefType>($_self).getNumElements())">
1268+
]> {
1269+
let summary = "arrayref to array";
1270+
let description = [{
1271+
Creates an array from an arrayref.
1272+
}];
1273+
let arguments = (ins ArrayRefType:$input);
1274+
let results = (outs ArrayType:$result);
1275+
let assemblyFormat = [{
1276+
$input attr-dict `:` functional-type($input, $result)
1277+
}];
1278+
}
1279+
11371280
#endif // CIRCT_DIALECT_ARC_ARCOPS_TD

include/circt/Dialect/Arc/ArcPasses.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ def LowerClocksToFuncs : Pass<"arc-lower-clocks-to-funcs", "mlir::ModuleOp"> {
177177
let dependentDialects = ["mlir::func::FuncDialect", "mlir::scf::SCFDialect"];
178178
}
179179

180+
def LowerArrays : Pass<"arc-lower-arrays", "mlir::ModuleOp"> {
181+
let summary = "Lower arrays to use bufferized arrayrefs";
182+
let dependentDialects = ["arc::ArcDialect"];
183+
}
184+
180185
def LowerLUT : Pass<"arc-lower-lut", "arc::DefineOp"> {
181186
let summary = "Lowers arc.lut into a comb and hw only representation.";
182187
let dependentDialects = ["hw::HWDialect", "comb::CombDialect"];

include/circt/Dialect/Arc/ArcTypes.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,25 @@
99
#ifndef CIRCT_DIALECT_ARC_ARCTYPES_H
1010
#define CIRCT_DIALECT_ARC_ARCTYPES_H
1111

12+
#include "circt/Dialect/HW/HWTypeInterfaces.h"
1213
#include "mlir/IR/BuiltinAttributes.h"
1314
#include "mlir/IR/BuiltinTypes.h"
1415
#include "mlir/IR/Types.h"
1516

1617
#define GET_TYPEDEF_CLASSES
1718
#include "circt/Dialect/Arc/ArcTypes.h.inc"
1819

20+
namespace circt {
21+
namespace arc {
22+
23+
/// Compute the bit width a type will have when allocated as part of the
24+
/// simulator's storage. This includes any padding and alignment that may be
25+
/// necessary once the type has been mapped to LLVM. The idea is for this
26+
/// function to be conservative, such that we provide sufficient storage bytes
27+
/// for any type.
28+
std::optional<uint64_t> computeLLVMBitWidth(mlir::Type type);
29+
30+
} // namespace arc
31+
} // namespace circt
32+
1933
#endif // CIRCT_DIALECT_ARC_ARCTYPES_H

include/circt/Dialect/Arc/ArcTypes.td

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111

1212
include "circt/Dialect/Arc/ArcDialect.td"
1313
include "mlir/IR/AttrTypeBase.td"
14+
include "mlir/IR/BuiltinTypeInterfaces.td"
1415
include "mlir/IR/OpBase.td"
16+
include "circt/Dialect/HW/HWTypeInterfaces.td"
1517

16-
class ArcTypeDef<string name> : TypeDef<ArcDialect, name> { }
18+
class ArcTypeDef<string name, list<Trait> traits = []> :
19+
TypeDef<ArcDialect, name, traits> { }
1720

1821
def StateType : ArcTypeDef<"State"> {
1922
let mnemonic = "state";
@@ -56,4 +59,42 @@ def SimModelInstance : ArcTypeDef<"SimModelInstance"> {
5659
let assemblyFormat = "`<` $model `>`";
5760
}
5861

62+
def ArrayRefType : ArcTypeDef<"ArrayRef", [
63+
DeclareTypeInterfaceMethods<BitWidthTypeInterface>,
64+
DeclareTypeInterfaceMethods<ShapedTypeInterface>
65+
]> {
66+
let summary = "array reference";
67+
let description = [{
68+
An array reference is a reference (pointer) to an array of values.
69+
}];
70+
let mnemonic = "arrayref";
71+
let parameters = (ins
72+
"::mlir::Type":$elementType,
73+
"uint64_t":$size
74+
);
75+
76+
let builders = [
77+
TypeBuilderWithInferredContext<(ins "Type":$elementType, "uint64_t":$size), [{
78+
auto *ctx = elementType.getContext();
79+
return $_get(ctx, elementType, size);
80+
}]>
81+
];
82+
83+
let assemblyFormat = "`<` $size `` custom<XInDimList>($elementType) `>`";
84+
85+
let genVerifyDecl = 1;
86+
87+
let extraClassDeclaration = [{
88+
size_t getNumElements() const;
89+
90+
std::optional<size_t> getByteWidth() const {
91+
std::optional<size_t> bitWidth = getBitWidth();
92+
if (!bitWidth.has_value()) {
93+
return std::nullopt;
94+
}
95+
return (*bitWidth + 7) / 8;
96+
}
97+
}];
98+
}
99+
59100
#endif // CIRCT_DIALECT_ARC_ARCTYPES_TD

include/circt/Tools/arcilator/pipelines.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ struct ArcToLLVMOptions : mlir::PassPipelineOptions<ArcToLLVMOptions> {
126126
Option<std::string> traceFileName{
127127
*this, "trace-file", llvm::cl::desc("Output file for signal traces"),
128128
llvm::cl::init("")};
129+
Option<bool> bufferizeArrays{
130+
*this, "bufferize-arrays",
131+
llvm::cl::desc("Bufferize arrays before lowering to LLVM"),
132+
llvm::cl::init(true)};
129133
};
130134
void populateArcToLLVMPipeline(mlir::OpPassManager &pm,
131135
const ArcToLLVMOptions &options = {});

0 commit comments

Comments
 (0)