18#include "llvm/ADT/SmallString.h"
19#include "llvm/Support/Debug.h"
25#define GEN_PASS_DEF_LINEARSCANREGISTERALLOCATIONPASS
26#include "circt/Dialect/RTG/Transforms/RTGPasses.h.inc"
33#define DEBUG_TYPE "rtg-linear-scan-register-allocation"
44 LiveRange(Location loc,
unsigned start,
unsigned end)
45 : loc(loc), start(start), end(end) {}
56struct FixedLiveRange :
public LiveRange {
57 FixedLiveRange(Location loc, rtg::RegisterAttrInterface fixedReg,
58 unsigned start,
unsigned end)
59 : LiveRange(loc, start, end), fixedReg(fixedReg) {}
62 rtg::RegisterAttrInterface fixedReg;
66struct DependentLiveRange :
public LiveRange {
67 DependentLiveRange(Value value,
unsigned start,
unsigned end)
68 : LiveRange(value.getLoc(), start, end), value(value) {}
76struct VirtualLiveRange :
public LiveRange {
77 VirtualLiveRange(rtg::VirtualRegisterOp virtualReg,
unsigned start,
79 : LiveRange(virtualReg.getLoc(), start, end), virtualReg(virtualReg) {}
82 rtg::VirtualRegisterOp virtualReg;
84 SmallVector<DependentLiveRange> dependentRegs;
87struct RegisterAllocationResult {
100 explicit RegisterAllocationResult(Kind kind) : kind(kind) {}
103 std::variant<rtg::ConstraintOp, LiveRange> value;
106 Kind getKind()
const {
return kind; }
108 LiveRange getUser()
const {
109 assert(kind == Kind::InUse);
110 return std::get<LiveRange>(value);
113 rtg::ConstraintOp getConstraint()
const {
114 assert(kind == Kind::ConstraintViolation);
115 return std::get<rtg::ConstraintOp>(value);
118 bool isAvailable()
const {
return kind == Kind::Available; }
120 static RegisterAllocationResult available() {
121 return RegisterAllocationResult(Kind::Available);
124 static RegisterAllocationResult inUseBy(LiveRange liveRange) {
125 auto res = RegisterAllocationResult(Kind::InUse);
126 res.value = liveRange;
130 static RegisterAllocationResult
131 constraintViolation(rtg::ConstraintOp constraintOp) {
132 auto res = RegisterAllocationResult(Kind::ConstraintViolation);
133 res.value = constraintOp;
137 static RegisterAllocationResult fatalError() {
138 return RegisterAllocationResult(Kind::FatalError);
143struct LiveRangeCache {
144 LogicalResult populate(Region ®ion);
148 DenseMap<Operation *, unsigned> opIndices;
150 DenseMap<unsigned, Operation *> indexToOp;
152 SmallVector<VirtualLiveRange> virtualRanges;
155 SmallVector<FixedLiveRange> reservedRanges;
158class LinearScanRegisterAllocationPass
159 :
public circt::rtg::impl::LinearScanRegisterAllocationPassBase<
160 LinearScanRegisterAllocationPass> {
162 void runOnOperation()
override;
175 SmallVectorImpl<std::pair<Value, Attribute>> &worklist,
176 DenseMap<Value, Attribute> &visited) {
178 SmallVector<Attribute> operandAttrs;
179 for (
auto operand : user->getOperands()) {
180 if (operand == current) {
181 operandAttrs.push_back(currentAttr);
184 auto *defOp = operand.getDefiningOp();
185 if (!defOp || !defOp->hasTrait<OpTrait::ConstantLike>())
188 SmallVector<OpFoldResult> operandFoldResults;
189 if (failed(operand.getDefiningOp()->fold(operandFoldResults))) {
190 return operand.getDefiningOp()->emitError(
191 "folding constant like operation failed");
194 operandAttrs.push_back(cast<Attribute>(operandFoldResults[0]));
198 SmallVector<OpFoldResult> foldResults;
199 if (failed(user->fold(operandAttrs, foldResults))) {
200 LLVM_DEBUG(llvm::dbgs()
201 <<
" Failed to fold operation: " << *user <<
"\n");
202 return user->emitError(
"operation could not be folded");
205 for (
auto [result, foldResult] : llvm::zip(user->getResults(), foldResults)) {
206 Attribute resultAttr = dyn_cast<Attribute>(foldResult);
208 return user->emitError(
"fold result is not an attribute");
211 if (visited.insert({result, resultAttr}).second)
212 worklist.push_back({result, resultAttr});
222 Value value, rtg::RegisterAttrInterface reg,
223 SetVector<std::pair<Value, rtg::RegisterAttrInterface>> &operands) {
224 LLVM_DEBUG(llvm::dbgs()
225 <<
"Collecting transitive register allocation operands for value: "
226 << value <<
" with register: " << reg <<
"\n");
228 SmallVector<std::pair<Value, Attribute>> worklist;
229 DenseMap<Value, Attribute> visited;
230 worklist.push_back({value, reg});
231 visited.insert({value, reg});
233 while (!worklist.empty()) {
234 auto [current, currentAttr] = worklist.pop_back_val();
235 LLVM_DEBUG(llvm::dbgs() <<
" Processing current value: " << current
236 <<
" with attribute: " << currentAttr <<
"\n");
238 for (Operation *user : current.getUsers()) {
239 if (
auto regAllocOp =
240 dyn_cast<rtg::RegisterAllocationOpInterface>(user)) {
243 LLVM_DEBUG(llvm::dbgs() <<
" Found RegisterAllocationOpInterface: "
246 {current, cast_or_null<rtg::RegisterAttrInterface>(currentAttr)});
250 if (
auto constraintOp = dyn_cast<rtg::ConstraintOp>(user)) {
254 auto condAttr = dyn_cast<IntegerAttr>(currentAttr);
255 if (!condAttr || condAttr.getValue().isZero()) {
256 LLVM_DEBUG(llvm::dbgs() <<
" Constraint could not be satisfied\n");
257 return RegisterAllocationResult::constraintViolation(constraintOp);
262 LLVM_DEBUG(llvm::dbgs()
263 <<
" Following non-RegisterAllocationOpInterface: " << *user
269 return RegisterAllocationResult::fatalError();
271 for (
auto result : user->getResults()) {
272 if (visited.insert({result, Attribute()}).second)
273 worklist.push_back({result, Attribute()});
279 LLVM_DEBUG(llvm::dbgs() <<
"Collected " << operands.size()
280 <<
" transitive register allocation operands\n");
281 return RegisterAllocationResult::available();
285 const VirtualLiveRange &liveRange) {
286 LLVM_DEBUG(llvm::dbgs() <<
"Expiring old intervals for register starting at "
287 << liveRange.start <<
"\n");
288 LLVM_DEBUG(llvm::dbgs() <<
" Active intervals before expiration: "
289 << active.size() <<
"\n");
292 llvm::sort(active, [](
const FixedLiveRange &a,
const FixedLiveRange &b) {
293 return a.end < b.end;
296 for (
auto *iter = active.begin(); iter != active.end(); ++iter) {
297 const auto &activeRange = *iter;
298 if (activeRange.end >= liveRange.start) {
299 LLVM_DEBUG(llvm::dbgs() <<
" Keeping active interval ending at "
300 << activeRange.end <<
"\n");
304 LLVM_DEBUG(llvm::dbgs()
305 <<
" Expiring interval ending at " << activeRange.end <<
"\n");
306 active.erase(iter--);
309 LLVM_DEBUG(llvm::dbgs() <<
" Active intervals after expiration: "
310 << active.size() <<
"\n");
316 LiveRange &liveRange) {
317 bool hasUser =
false;
318 for (
auto *user : value.getUsers()) {
319 if (!isa<rtg::RegisterAllocationOpInterface>(user))
323 unsigned idx = cache.opIndices.at(user);
324 LLVM_DEBUG(llvm::dbgs()
325 <<
" User at index " << idx <<
": " << *user <<
"\n");
326 liveRange.start = std::min(liveRange.start, idx);
327 liveRange.end = std::max(liveRange.end, idx);
336static void createRange(LiveRangeCache &cache, 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();
365 LLVM_DEBUG(llvm::dbgs() <<
" Live range: [" << liveRange.start <<
", "
366 << liveRange.end <<
"]\n");
372 LLVM_DEBUG(llvm::dbgs() <<
"\nCollecting register live ranges...\n");
374 for (
auto op : region.getOps<rtg::ConstantOp>()) {
375 auto reg = dyn_cast<rtg::RegisterAttrInterface>(op.getValue());
379 unsigned maxIdx = 0, minIdx = cache.opIndices.size();
380 for (
auto *user : op->getUsers()) {
381 minIdx = std::min(minIdx, cache.opIndices.at(user));
382 maxIdx = std::max(maxIdx, cache.opIndices.at(user));
385 cache.reservedRanges.emplace_back(op.getLoc(), reg, minIdx, maxIdx);
387 LLVM_DEBUG(llvm::dbgs()
388 <<
" Added fixed register to active list: " << reg <<
"\n");
391 llvm::sort(cache.reservedRanges,
392 [](
const FixedLiveRange &a,
const FixedLiveRange &b) {
393 return a.start < b.start;
396 for (
auto op : region.getOps<rtg::VirtualRegisterOp>())
400 LLVM_DEBUG(llvm::dbgs() <<
"\nSorting " << cache.virtualRanges.size()
401 <<
" register ranges by start time\n");
402 llvm::sort(cache.virtualRanges, [](
const VirtualLiveRange &a,
403 const VirtualLiveRange &b) {
404 return a.start < b.start || (a.start == b.start && a.end > b.end);
414 const LiveRangeCache &cache, ArrayRef<FixedLiveRange> active,
415 rtg::VirtualRegisterOp virtualReg, rtg::RegisterAttrInterface reg,
416 DenseMap<Value, rtg::RegisterAttrInterface> &dependentRegValues) {
417 LLVM_DEBUG(llvm::dbgs() <<
"Trying register: " << reg <<
"\n");
419 SetVector<std::pair<Value, rtg::RegisterAttrInterface>> registers;
422 if (!res.isAvailable())
426 llvm::dbgs() <<
"Checking live range overlap with active registers\n");
428 for (
auto activeRange : active) {
429 if (activeRange.fixedReg == reg)
430 return RegisterAllocationResult::inUseBy(activeRange);
432 for (
auto [candidateReg, candidateRegAttr] : registers) {
433 if (candidateRegAttr == activeRange.fixedReg)
434 return RegisterAllocationResult::inUseBy(activeRange);
438 for (
auto [regVal, regAttr] : registers)
439 dependentRegValues[regVal] = regAttr;
441 LLVM_DEBUG(llvm::dbgs() <<
"Register: " << reg <<
" is available\n");
443 return RegisterAllocationResult::available();
450struct DiagnosticNote {
451 DiagnosticNote(Location loc) : loc(loc) {}
454 message += str.str();
459 SmallString<128> message;
466 const LiveRangeCache &cache, ArrayRef<FixedLiveRange> active,
467 VirtualLiveRange &liveRange,
468 DenseMap<Value, rtg::RegisterAttrInterface> &dependentRegValues) {
469 auto virtualReg = liveRange.virtualReg;
471 cast<rtg::VirtualRegisterConfigAttr>(virtualReg.getAllowedRegsAttr());
472 LLVM_DEBUG(llvm::dbgs() <<
" Allowed registers: "
473 << configAttr.getAllowedRegs().size() <<
"\n");
476 SmallVector<DiagnosticNote> diagnosticNotes;
480 for (
auto reg : configAttr.getAllowedRegs()) {
481 dependentRegValues.clear();
484 switch (res.getKind()) {
485 case RegisterAllocationResult::Kind::Available:
488 case RegisterAllocationResult::Kind::InUse:
489 diagnosticNotes.emplace_back(res.getUser().loc)
490 <<
"cannot choose '" << reg.getRegisterAssembly()
491 <<
"' because of overlapping live-range with this register";
493 case RegisterAllocationResult::Kind::ConstraintViolation:
494 diagnosticNotes.emplace_back(res.getConstraint().getLoc())
495 <<
"constraint would be violated when choosing '"
496 << reg.getRegisterAssembly() <<
"'";
498 case RegisterAllocationResult::Kind::FatalError:
505 auto diag = virtualReg.emitError(
506 "no register available for allocation within constraints");
507 if (
auto *startOp = cache.indexToOp.lookup(liveRange.start))
508 diag.attachNote(startOp->getLoc()) <<
"live range starts here";
509 if (
auto *endOp = cache.indexToOp.lookup(liveRange.end))
510 diag.attachNote(endOp->getLoc()) <<
"live range ends here";
511 for (
const auto ¬e : diagnosticNotes)
512 diag.attachNote(note.loc) << note.message;
520 const LiveRangeCache &cache,
521 const DenseMap<rtg::VirtualRegisterOp, rtg::RegisterAttrInterface>
522 &assignedRegisters) {
523 LLVM_DEBUG(llvm::dbgs()
524 <<
"\nReplacing virtual registers with fixed registers...\n");
527 for (
auto &liveRange : cache.virtualRanges) {
528 auto virtualReg = liveRange.virtualReg;
529 auto fixedReg = assignedRegisters.at(virtualReg);
531 LLVM_DEBUG(llvm::dbgs() <<
"Replacing virtual register " << virtualReg
532 <<
" with fixed register " << fixedReg <<
"\n");
534 OpBuilder builder(virtualReg);
536 rtg::ConstantOp::create(builder, virtualReg.getLoc(), fixedReg);
537 virtualReg.getResult().replaceAllUsesWith(newFixedReg);
538 operationPruner.
eraseNow(virtualReg);
545 LLVM_DEBUG(llvm::dbgs() <<
"Register allocation completed successfully!\n");
552 SmallVector<FixedLiveRange> active;
553 DenseMap<rtg::VirtualRegisterOp, rtg::RegisterAttrInterface>
558 for (
auto &fixedRange : cache.reservedRanges)
559 active.push_back(fixedRange);
561 for (
auto &virtualRange : cache.virtualRanges) {
562 LLVM_DEBUG(llvm::dbgs() <<
"Processing register range ["
563 << virtualRange.start <<
", " << virtualRange.end
564 <<
"] for " << virtualRange.virtualReg <<
"\n");
569 DenseMap<Value, rtg::RegisterAttrInterface> dependentRegValues;
575 assignedRegisters[virtualRange.virtualReg] = availableReg;
577 LLVM_DEBUG(llvm::dbgs() <<
" Assigned register " << availableReg
578 <<
" to virtual register\n");
580 active.emplace_back(virtualRange.loc, availableReg, virtualRange.start,
583 for (
auto &dependentRange : virtualRange.dependentRegs)
584 active.emplace_back(dependentRange.loc,
585 dependentRegValues.at(dependentRange.value),
586 dependentRange.start, dependentRange.end);
590 for (
auto &virtualRange : cache.virtualRanges) {
591 llvm::dbgs() <<
"Start: " << virtualRange.start
592 <<
", End: " << virtualRange.end <<
", Selected: "
593 << assignedRegisters.at(virtualRange.virtualReg) <<
"\n";
595 llvm::dbgs() <<
"\n";
606void LinearScanRegisterAllocationPass::runOnOperation() {
607 LLVM_DEBUG(llvm::dbgs() <<
"=== Processing "
608 << OpWithFlags(getOperation(),
609 OpPrintingFlags().skipRegions())
612 if (getOperation()->getNumRegions() != 1 ||
613 getOperation()->getRegion(0).getBlocks().size() != 1) {
614 getOperation()->emitError(
"expected a single region with a single block");
615 return signalPassFailure();
618 LiveRangeCache cache;
619 for (
auto segOp : getOperation()->getRegion(0).getOps<
rtg::SegmentOp>()) {
622 if (segOp.getKind() == rtg::SegmentKind::Text) {
623 if (failed(cache.populate(segOp.getRegion())))
624 return signalPassFailure();
627 return signalPassFailure();
634LogicalResult LiveRangeCache::populate(Region ®ion) {
635 for (
auto [i, op] :
llvm::enumerate(region.front())) {
639 LLVM_DEBUG(llvm::dbgs() <<
" Op " << i <<
": " << op <<
"\n");
642 LLVM_DEBUG(llvm::dbgs() <<
"Total operations in text segment: "
643 << opIndices.size() <<
"\n");
648void LiveRangeCache::clear() {
651 virtualRanges.clear();
652 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 void createRange(LiveRangeCache &cache, rtg::VirtualRegisterOp op)
Creates a live range for a virtual register operation.
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.
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
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.