18#include "llvm/Support/Debug.h"
24#define GEN_PASS_DEF_LINEARSCANREGISTERALLOCATIONPASS
25#include "circt/Dialect/RTG/Transforms/RTGPasses.h.inc"
32#define DEBUG_TYPE "rtg-linear-scan-register-allocation"
43 LiveRange(Location loc,
unsigned start,
unsigned end)
44 : loc(loc), start(start), end(end) {}
55struct FixedLiveRange :
public LiveRange {
56 FixedLiveRange(Location loc, rtg::RegisterAttrInterface fixedReg,
57 unsigned start,
unsigned end)
58 : LiveRange(loc, start, end), fixedReg(fixedReg) {}
61 rtg::RegisterAttrInterface fixedReg;
65struct DependentLiveRange :
public LiveRange {
66 DependentLiveRange(Value value,
unsigned start,
unsigned end)
67 : LiveRange(value.getLoc(), start, end), value(value) {}
75struct VirtualLiveRange :
public LiveRange {
76 VirtualLiveRange(rtg::VirtualRegisterOp virtualReg,
unsigned start,
78 : LiveRange(virtualReg.getLoc(), start, end), virtualReg(virtualReg) {}
81 rtg::VirtualRegisterOp virtualReg;
83 SmallVector<DependentLiveRange> dependentRegs;
86struct RegisterAllocationResult {
99 explicit RegisterAllocationResult(Kind kind) : kind(kind) {}
102 std::variant<rtg::ConstraintOp, LiveRange *> value;
105 Kind getKind()
const {
return kind; }
107 LiveRange *getUser()
const {
108 assert(kind == Kind::InUse);
109 return std::get<LiveRange *>(value);
112 rtg::ConstraintOp getConstraint()
const {
113 assert(kind == Kind::ConstraintViolation);
114 return std::get<rtg::ConstraintOp>(value);
117 bool isAvailable()
const {
return kind == Kind::Available; }
119 static RegisterAllocationResult available() {
120 return RegisterAllocationResult(Kind::Available);
123 static RegisterAllocationResult inUseBy(LiveRange &liveRange) {
124 auto res = RegisterAllocationResult(Kind::InUse);
125 res.value = &liveRange;
129 static RegisterAllocationResult
130 constraintViolation(rtg::ConstraintOp constraintOp) {
131 auto res = RegisterAllocationResult(Kind::ConstraintViolation);
132 res.value = constraintOp;
136 static RegisterAllocationResult fatalError() {
137 return RegisterAllocationResult(Kind::FatalError);
142struct LiveRangeCache {
143 LogicalResult populate(Region ®ion);
147 DenseMap<Operation *, unsigned> opIndices;
149 DenseMap<unsigned, Operation *> indexToOp;
151 SmallVector<VirtualLiveRange> virtualRanges;
154 SmallVector<FixedLiveRange> reservedRanges;
157class LinearScanRegisterAllocationPass
158 :
public circt::rtg::impl::LinearScanRegisterAllocationPassBase<
159 LinearScanRegisterAllocationPass> {
161 void runOnOperation()
override;
174 SmallVectorImpl<std::pair<Value, Attribute>> &worklist,
175 DenseMap<Value, Attribute> &visited) {
177 SmallVector<Attribute> operandAttrs;
178 for (
auto operand : user->getOperands()) {
179 if (operand == current) {
180 operandAttrs.push_back(currentAttr);
183 auto *defOp = operand.getDefiningOp();
184 if (!defOp || !defOp->hasTrait<OpTrait::ConstantLike>())
187 SmallVector<OpFoldResult> operandFoldResults;
188 if (failed(operand.getDefiningOp()->fold(operandFoldResults))) {
189 return operand.getDefiningOp()->emitError(
190 "folding constant like operation failed");
193 operandAttrs.push_back(cast<Attribute>(operandFoldResults[0]));
197 SmallVector<OpFoldResult> foldResults;
198 if (failed(user->fold(operandAttrs, foldResults))) {
199 LLVM_DEBUG(llvm::dbgs()
200 <<
" Failed to fold operation: " << *user <<
"\n");
201 return user->emitError(
"operation could not be folded");
204 for (
auto [result, foldResult] : llvm::zip(user->getResults(), foldResults)) {
205 Attribute resultAttr = dyn_cast<Attribute>(foldResult);
207 return user->emitError(
"fold result is not an attribute");
210 if (visited.insert({result, resultAttr}).second)
211 worklist.push_back({result, resultAttr});
221 Value value, rtg::RegisterAttrInterface reg,
222 SetVector<std::pair<Value, rtg::RegisterAttrInterface>> &operands) {
223 LLVM_DEBUG(llvm::dbgs()
224 <<
"Collecting transitive register allocation operands for value: "
225 << value <<
" with register: " << reg <<
"\n");
227 SmallVector<std::pair<Value, Attribute>> worklist;
228 DenseMap<Value, Attribute> visited;
229 worklist.push_back({value, reg});
230 visited.insert({value, reg});
232 while (!worklist.empty()) {
233 auto [current, currentAttr] = worklist.pop_back_val();
234 LLVM_DEBUG(llvm::dbgs() <<
" Processing current value: " << current
235 <<
" with attribute: " << currentAttr <<
"\n");
237 for (Operation *user : current.getUsers()) {
238 if (
auto regAllocOp =
239 dyn_cast<rtg::RegisterAllocationOpInterface>(user)) {
242 LLVM_DEBUG(llvm::dbgs() <<
" Found RegisterAllocationOpInterface: "
245 {current, cast_or_null<rtg::RegisterAttrInterface>(currentAttr)});
249 if (
auto constraintOp = dyn_cast<rtg::ConstraintOp>(user)) {
253 auto condAttr = dyn_cast<IntegerAttr>(currentAttr);
254 if (!condAttr || condAttr.getValue().isZero()) {
255 LLVM_DEBUG(llvm::dbgs() <<
" Constraint could not be satisfied\n");
256 return RegisterAllocationResult::constraintViolation(constraintOp);
261 LLVM_DEBUG(llvm::dbgs()
262 <<
" Following non-RegisterAllocationOpInterface: " << *user
268 return RegisterAllocationResult::fatalError();
270 for (
auto result : user->getResults()) {
271 if (visited.insert({result, Attribute()}).second)
272 worklist.push_back({result, Attribute()});
278 LLVM_DEBUG(llvm::dbgs() <<
"Collected " << operands.size()
279 <<
" transitive register allocation operands\n");
280 return RegisterAllocationResult::available();
284 const VirtualLiveRange &liveRange) {
285 LLVM_DEBUG(llvm::dbgs() <<
"Expiring old intervals for register starting at "
286 << liveRange.start <<
"\n");
287 LLVM_DEBUG(llvm::dbgs() <<
" Active intervals before expiration: "
288 << active.size() <<
"\n");
291 llvm::sort(active, [](
const FixedLiveRange &a,
const FixedLiveRange &b) {
292 return a.end < b.end;
295 for (
auto *iter = active.begin(); iter != active.end(); ++iter) {
296 const auto &activeRange = *iter;
297 if (activeRange.end >= liveRange.start) {
298 LLVM_DEBUG(llvm::dbgs() <<
" Keeping active interval ending at "
299 << activeRange.end <<
"\n");
303 LLVM_DEBUG(llvm::dbgs()
304 <<
" Expiring interval ending at " << activeRange.end <<
"\n");
305 active.erase(iter--);
308 LLVM_DEBUG(llvm::dbgs() <<
" Active intervals after expiration: "
309 << active.size() <<
"\n");
315 LiveRange &liveRange) {
316 bool hasUser =
false;
317 for (
auto *user : value.getUsers()) {
318 if (!isa<rtg::RegisterAllocationOpInterface>(user))
322 unsigned idx = cache.opIndices.at(user);
323 LLVM_DEBUG(llvm::dbgs()
324 <<
" User at index " << idx <<
": " << *user <<
"\n");
325 liveRange.start = std::min(liveRange.start, idx);
326 liveRange.end = std::max(liveRange.end, idx);
336 rtg::VirtualRegisterOp op) {
337 LLVM_DEBUG(llvm::dbgs() <<
"Processing virtual register: " << op <<
"\n");
340 cache.virtualRanges.emplace_back(op, cache.opIndices.size(), 0);
342 SetVector<std::pair<Value, rtg::RegisterAttrInterface>> registers;
345 if (!res.isAvailable())
350 for (
auto [reg, regAttr] : registers) {
352 liveRange.dependentRegs.emplace_back(reg, cache.opIndices.size(), 0);
360 LLVM_DEBUG(llvm::dbgs()
361 <<
" No RegisterAllocationOpInterface users, removing range\n");
362 cache.virtualRanges.pop_back();
366 LLVM_DEBUG(llvm::dbgs() <<
" Live range: [" << liveRange.start <<
", "
367 << liveRange.end <<
"]\n");
374 LLVM_DEBUG(llvm::dbgs() <<
"\nCollecting register live ranges...\n");
376 for (
auto op : region.getOps<rtg::ConstantOp>()) {
377 auto reg = dyn_cast<rtg::RegisterAttrInterface>(op.getValue());
381 unsigned maxIdx = 0, minIdx = cache.opIndices.size();
382 for (
auto *user : op->getUsers()) {
383 minIdx = std::min(minIdx, cache.opIndices.at(user));
384 maxIdx = std::max(maxIdx, cache.opIndices.at(user));
387 cache.reservedRanges.emplace_back(op.getLoc(), reg, minIdx, maxIdx);
389 LLVM_DEBUG(llvm::dbgs()
390 <<
" Added fixed register to active list: " << reg <<
"\n");
393 llvm::sort(cache.reservedRanges,
394 [](
const FixedLiveRange &a,
const FixedLiveRange &b) {
395 return a.start < b.start;
398 for (
auto op : region.getOps<rtg::VirtualRegisterOp>()) {
405 LLVM_DEBUG(llvm::dbgs() <<
"\nSorting " << cache.virtualRanges.size()
406 <<
" register ranges by start time\n");
407 llvm::sort(cache.virtualRanges, [](
const VirtualLiveRange &a,
408 const VirtualLiveRange &b) {
409 return a.start < b.start || (a.start == b.start && a.end > b.end);
419 const LiveRangeCache &cache, ArrayRef<FixedLiveRange> active,
420 rtg::VirtualRegisterOp virtualReg, rtg::RegisterAttrInterface reg,
421 DenseMap<Value, rtg::RegisterAttrInterface> &dependentRegValues) {
422 LLVM_DEBUG(llvm::dbgs() <<
" Trying register: " << reg <<
"\n");
424 SetVector<std::pair<Value, rtg::RegisterAttrInterface>> registers;
427 if (!res.isAvailable())
430 for (
auto activeRange : active) {
431 if (activeRange.fixedReg == reg)
432 return RegisterAllocationResult::inUseBy(activeRange);
434 for (
auto [candidateReg, candidateRegAttr] : registers) {
435 if (candidateRegAttr == activeRange.fixedReg)
436 return RegisterAllocationResult::inUseBy(activeRange);
440 LLVM_DEBUG(llvm::dbgs() <<
" Found " << registers.size()
441 <<
" transitive register allocation operands\n");
443 for (
auto [regVal, regAttr] : registers)
444 dependentRegValues[regVal] = regAttr;
446 return RegisterAllocationResult::available();
452 const LiveRangeCache &cache, ArrayRef<FixedLiveRange> active,
453 VirtualLiveRange &liveRange,
454 DenseMap<Value, rtg::RegisterAttrInterface> &dependentRegValues) {
455 auto virtualReg = liveRange.virtualReg;
457 cast<rtg::VirtualRegisterConfigAttr>(virtualReg.getAllowedRegsAttr());
458 LLVM_DEBUG(llvm::dbgs() <<
" Allowed registers: "
459 << configAttr.getAllowedRegs().size() <<
"\n");
463 auto diag = virtualReg.emitError(
464 "no register available for allocation within constraints");
465 if (
auto *startOp = cache.indexToOp.lookup(liveRange.start))
466 diag.attachNote(startOp->getLoc()) <<
"live range starts here";
467 if (
auto *endOp = cache.indexToOp.lookup(liveRange.end))
468 diag.attachNote(endOp->getLoc()) <<
"live range ends here";
472 for (
auto reg : configAttr.getAllowedRegs()) {
473 dependentRegValues.clear();
476 switch (res.getKind()) {
477 case RegisterAllocationResult::Kind::Available:
481 case RegisterAllocationResult::Kind::InUse:
482 diag.attachNote(res.getUser()->loc)
483 <<
"cannot choose '" << reg.getRegisterAssembly()
484 <<
"' because of overlapping live-range with this register";
486 case RegisterAllocationResult::Kind::ConstraintViolation:
487 diag.attachNote(res.getConstraint().getLoc())
488 <<
"constraint would be violated when choosing '"
489 << reg.getRegisterAssembly() <<
"'";
491 case RegisterAllocationResult::Kind::FatalError:
504 const LiveRangeCache &cache,
505 const DenseMap<rtg::VirtualRegisterOp, rtg::RegisterAttrInterface>
506 &assignedRegisters) {
507 LLVM_DEBUG(llvm::dbgs()
508 <<
"\nReplacing virtual registers with fixed registers...\n");
511 for (
auto &liveRange : cache.virtualRanges) {
512 auto virtualReg = liveRange.virtualReg;
513 auto fixedReg = assignedRegisters.at(virtualReg);
515 LLVM_DEBUG(llvm::dbgs() <<
"Replacing virtual register " << virtualReg
516 <<
" with fixed register " << fixedReg <<
"\n");
518 OpBuilder builder(virtualReg);
520 rtg::ConstantOp::create(builder, virtualReg.getLoc(), fixedReg);
521 virtualReg.getResult().replaceAllUsesWith(newFixedReg);
522 operationPruner.
eraseNow(virtualReg);
529 LLVM_DEBUG(llvm::dbgs() <<
"Register allocation completed successfully!\n");
536 SmallVector<FixedLiveRange> active;
537 DenseMap<rtg::VirtualRegisterOp, rtg::RegisterAttrInterface>
542 for (
auto &fixedRange : cache.reservedRanges)
543 active.push_back(fixedRange);
545 for (
auto &virtualRange : cache.virtualRanges) {
546 LLVM_DEBUG(llvm::dbgs() <<
"Processing register range ["
547 << virtualRange.start <<
", " << virtualRange.end
548 <<
"] for " << virtualRange.virtualReg <<
"\n");
553 DenseMap<Value, rtg::RegisterAttrInterface> dependentRegValues;
559 assignedRegisters[virtualRange.virtualReg] = availableReg;
561 LLVM_DEBUG(llvm::dbgs() <<
" Assigned register " << availableReg
562 <<
" to virtual register\n");
564 active.emplace_back(virtualRange.loc, availableReg, virtualRange.start,
567 for (
auto &dependentRange : virtualRange.dependentRegs)
568 active.emplace_back(dependentRange.loc,
569 dependentRegValues.at(dependentRange.value),
570 dependentRange.start, dependentRange.end);
574 for (
auto &virtualRange : cache.virtualRanges) {
575 llvm::dbgs() <<
"Start: " << virtualRange.start
576 <<
", End: " << virtualRange.end <<
", Selected: "
577 << assignedRegisters.at(virtualRange.virtualReg) <<
"\n";
579 llvm::dbgs() <<
"\n";
590void LinearScanRegisterAllocationPass::runOnOperation() {
591 LLVM_DEBUG(llvm::dbgs() <<
"=== Processing "
592 << OpWithFlags(getOperation(),
593 OpPrintingFlags().skipRegions())
596 if (getOperation()->getNumRegions() != 1 ||
597 getOperation()->getRegion(0).getBlocks().size() != 1) {
598 getOperation()->emitError(
"expected a single region with a single block");
599 return signalPassFailure();
602 LiveRangeCache cache;
603 for (
auto segOp : getOperation()->getRegion(0).getOps<
rtg::SegmentOp>()) {
606 if (segOp.getKind() == rtg::SegmentKind::Text) {
607 if (failed(cache.populate(segOp.getRegion())))
608 return signalPassFailure();
611 return signalPassFailure();
618LogicalResult LiveRangeCache::populate(Region ®ion) {
619 for (
auto [i, op] :
llvm::enumerate(region.front())) {
623 LLVM_DEBUG(llvm::dbgs() <<
" Op " << i <<
": " << op <<
"\n");
626 LLVM_DEBUG(llvm::dbgs() <<
"Total operations in text segment: "
627 << opIndices.size() <<
"\n");
632void LiveRangeCache::clear() {
635 virtualRanges.clear();
636 reservedRanges.clear();
assert(baseType &&"element must be base type")
static rtg::RegisterAttrInterface findAvailableRegister(const LiveRangeCache &cache, ArrayRef< FixedLiveRange > active, VirtualLiveRange &liveRange, DenseMap< Value, rtg::RegisterAttrInterface > &dependentRegValues)
Finds an available register for the given virtual register from its allowed register set.
static RegisterAllocationResult collectTransitiveRegisterAllocationOperands(Value value, rtg::RegisterAttrInterface reg, SetVector< std::pair< Value, rtg::RegisterAttrInterface > > &operands)
static LogicalResult foldAndEnqueueResults(Operation *user, Value current, Attribute currentAttr, SmallVectorImpl< std::pair< Value, Attribute > > &worklist, DenseMap< Value, Attribute > &visited)
Folds an operation and adds its results to the worklist.
static void expireOldInterval(SmallVector< FixedLiveRange > &active, const VirtualLiveRange &liveRange)
static LogicalResult computeLiveRanges(LiveRangeCache &cache, Region ®ion)
Computes live ranges for all virtual registers and identifies reserved (fixed) registers.
static LogicalResult allocateVirtualRegistersInCache(LiveRangeCache &cache)
Performs the main register allocation using linear scan algorithm.
static void materializeAllocation(const LiveRangeCache &cache, const DenseMap< rtg::VirtualRegisterOp, rtg::RegisterAttrInterface > &assignedRegisters)
Replaces all virtual register operations with their allocated fixed registers and removes unused oper...
static RegisterAllocationResult isRegisterAvailable(const LiveRangeCache &cache, ArrayRef< FixedLiveRange > active, rtg::VirtualRegisterOp virtualReg, rtg::RegisterAttrInterface reg, DenseMap< Value, rtg::RegisterAttrInterface > &dependentRegValues)
Checks if a specific register is available for allocation to a virtual register.
static bool updateLiveRangeFromUsers(const LiveRangeCache &cache, Value value, LiveRange &liveRange)
Updates the live range based on users of a value.
static VirtualLiveRange * createRange(LiveRangeCache &cache, rtg::VirtualRegisterOp op)
Creates a live range for a virtual register operation.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Utility that tracks operations that have potentially become unused and allows them to be cleaned up a...
void eraseNow(Operation *op)
Erase an operation immediately, and remove it from the set of ops to be removed later.