| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| //===- AVRShift.cpp - Shift Expansion Pass --------------------------------===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| /// \file | ||
| /// Expand 32-bit shift instructions (shl, lshr, ashr) to inline loops, just | ||
| /// like avr-gcc. This must be done in IR because otherwise the type legalizer | ||
| /// will turn 32-bit shifts into (non-existing) library calls such as __ashlsi3. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "AVR.h" | ||
| #include "llvm/IR/IRBuilder.h" | ||
| #include "llvm/IR/InstIterator.h" | ||
|
|
||
| using namespace llvm; | ||
|
|
||
| namespace { | ||
|
|
||
| class AVRShiftExpand : public FunctionPass { | ||
| public: | ||
| static char ID; | ||
|
|
||
| AVRShiftExpand() : FunctionPass(ID) {} | ||
|
|
||
| bool runOnFunction(Function &F) override; | ||
|
|
||
| StringRef getPassName() const override { return "AVR Shift Expansion"; } | ||
|
|
||
| private: | ||
| void expand(BinaryOperator *BI); | ||
| }; | ||
|
|
||
| } // end of anonymous namespace | ||
|
|
||
| char AVRShiftExpand::ID = 0; | ||
|
|
||
| INITIALIZE_PASS(AVRShiftExpand, "avr-shift-expand", "AVR Shift Expansion", | ||
| false, false) | ||
|
|
||
| Pass *llvm::createAVRShiftExpandPass() { return new AVRShiftExpand(); } | ||
|
|
||
| bool AVRShiftExpand::runOnFunction(Function &F) { | ||
| SmallVector<BinaryOperator *, 1> ShiftInsts; | ||
| auto &Ctx = F.getContext(); | ||
| for (Instruction &I : instructions(F)) { | ||
| if (!I.isShift()) | ||
| // Only expand shift instructions (shl, lshr, ashr). | ||
| continue; | ||
| if (I.getType() != Type::getInt32Ty(Ctx)) | ||
| // Only expand plain i32 types. | ||
| continue; | ||
| if (isa<ConstantInt>(I.getOperand(1))) | ||
| // Only expand when the shift amount is not known. | ||
| // Known shift amounts are (currently) better expanded inline. | ||
| continue; | ||
| ShiftInsts.push_back(cast<BinaryOperator>(&I)); | ||
| } | ||
|
|
||
| // The expanding itself needs to be done separately as expand() will remove | ||
| // these instructions. Removing instructions while iterating over a basic | ||
| // block is not a great idea. | ||
| for (auto *I : ShiftInsts) { | ||
| expand(I); | ||
| } | ||
|
|
||
| // Return whether this function expanded any shift instructions. | ||
| return ShiftInsts.size() > 0; | ||
| } | ||
|
|
||
| void AVRShiftExpand::expand(BinaryOperator *BI) { | ||
| auto &Ctx = BI->getContext(); | ||
| IRBuilder<> Builder(BI); | ||
| Type *Int32Ty = Type::getInt32Ty(Ctx); | ||
| Type *Int8Ty = Type::getInt8Ty(Ctx); | ||
| Value *Int8Zero = ConstantInt::get(Int8Ty, 0); | ||
|
|
||
| // Split the current basic block at the point of the existing shift | ||
| // instruction and insert a new basic block for the loop. | ||
| BasicBlock *BB = BI->getParent(); | ||
| Function *F = BB->getParent(); | ||
| BasicBlock *EndBB = BB->splitBasicBlock(BI, "shift.done"); | ||
| BasicBlock *LoopBB = BasicBlock::Create(Ctx, "shift.loop", F, EndBB); | ||
|
|
||
| // Truncate the shift amount to i8, which is trivially lowered to a single | ||
| // AVR register. | ||
| Builder.SetInsertPoint(&BB->back()); | ||
| Value *ShiftAmount = Builder.CreateTrunc(BI->getOperand(1), Int8Ty); | ||
|
|
||
| // Replace the unconditional branch that splitBasicBlock created with a | ||
| // conditional branch. | ||
| Value *Cmp1 = Builder.CreateICmpEQ(ShiftAmount, Int8Zero); | ||
| Builder.CreateCondBr(Cmp1, EndBB, LoopBB); | ||
| BB->back().eraseFromParent(); | ||
|
|
||
| // Create the loop body starting with PHI nodes. | ||
| Builder.SetInsertPoint(LoopBB); | ||
| PHINode *ShiftAmountPHI = Builder.CreatePHI(Int8Ty, 2); | ||
| ShiftAmountPHI->addIncoming(ShiftAmount, BB); | ||
| PHINode *ValuePHI = Builder.CreatePHI(Int32Ty, 2); | ||
| ValuePHI->addIncoming(BI->getOperand(0), BB); | ||
|
|
||
| // Subtract the shift amount by one, as we're shifting one this loop | ||
| // iteration. | ||
| Value *ShiftAmountSub = | ||
| Builder.CreateSub(ShiftAmountPHI, ConstantInt::get(Int8Ty, 1)); | ||
| ShiftAmountPHI->addIncoming(ShiftAmountSub, LoopBB); | ||
|
|
||
| // Emit the actual shift instruction. The difference is that this shift | ||
| // instruction has a constant shift amount, which can be emitted inline | ||
| // without a library call. | ||
| Value *ValueShifted; | ||
| switch (BI->getOpcode()) { | ||
| case Instruction::Shl: | ||
| ValueShifted = Builder.CreateShl(ValuePHI, ConstantInt::get(Int32Ty, 1)); | ||
| break; | ||
| case Instruction::LShr: | ||
| ValueShifted = Builder.CreateLShr(ValuePHI, ConstantInt::get(Int32Ty, 1)); | ||
| break; | ||
| case Instruction::AShr: | ||
| ValueShifted = Builder.CreateAShr(ValuePHI, ConstantInt::get(Int32Ty, 1)); | ||
| break; | ||
| default: | ||
| llvm_unreachable("asked to expand an instruction that is not a shift"); | ||
| } | ||
| ValuePHI->addIncoming(ValueShifted, LoopBB); | ||
|
|
||
| // Branch to either the loop again (if there is more to shift) or to the | ||
| // basic block after the loop (if all bits are shifted). | ||
| Value *Cmp2 = Builder.CreateICmpEQ(ShiftAmountSub, Int8Zero); | ||
| Builder.CreateCondBr(Cmp2, EndBB, LoopBB); | ||
|
|
||
| // Collect the resulting value. This is necessary in the IR but won't produce | ||
| // any actual instructions. | ||
| Builder.SetInsertPoint(BI); | ||
| PHINode *Result = Builder.CreatePHI(Int32Ty, 2); | ||
| Result->addIncoming(BI->getOperand(0), BB); | ||
| Result->addIncoming(ValueShifted, LoopBB); | ||
|
|
||
| // Replace the original shift instruction. | ||
| BI->replaceAllUsesWith(Result); | ||
| BI->eraseFromParent(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| ; RUN: not --crash llc -O0 < %s -march=avr 2>&1 | FileCheck %s | ||
|
|
||
| define void @foo() { | ||
| entry: | ||
| ; CHECK: Invalid register name "r28". | ||
| %val1 = call i8 @llvm.read_register.i8(metadata !0) | ||
| ret void | ||
| } | ||
|
|
||
| declare i8 @llvm.read_register.i8(metadata) | ||
|
|
||
| !0 = !{!"r28"} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| ; RUN: llc -O0 < %s -march=avr | FileCheck %s | ||
|
|
||
| ; CHECK-LABEL: read_sp: | ||
| ; CHECK: in r24, 61 | ||
| ; CHECK: in r25, 62 | ||
| define i16 @read_sp() { | ||
| entry: | ||
| %sp = call i16 @llvm.read_register.i16(metadata !0) | ||
| ret i16 %sp | ||
| } | ||
|
|
||
| ; CHECK-LABEL: read_r0: | ||
| ; CHECK: mov r24, r0 | ||
| define i8 @read_r0() { | ||
| entry: | ||
| %r0 = call i8 @llvm.read_register.i8(metadata !1) | ||
| ret i8 %r0 | ||
| } | ||
|
|
||
| ; CHECK-LABEL: read_r1: | ||
| ; CHECK: mov r24, r1 | ||
| define i8 @read_r1() { | ||
| entry: | ||
| %r1 = call i8 @llvm.read_register.i8(metadata !2) | ||
| ret i8 %r1 | ||
| } | ||
|
|
||
| ; CHECK-LABEL: read_r1r0: | ||
| ; CHECK: mov r24, r0 | ||
| ; CHECK: mov r25, r1 | ||
| define i16 @read_r1r0() { | ||
| entry: | ||
| %r1r0 = call i16 @llvm.read_register.i16(metadata !1) | ||
| ret i16 %r1r0 | ||
| } | ||
|
|
||
| declare i16 @llvm.read_register.i16(metadata) | ||
| declare i8 @llvm.read_register.i8(metadata) | ||
|
|
||
| !0 = !{!"sp"} | ||
| !1 = !{!"r0"} | ||
| !2 = !{!"r1"} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py | ||
| ; RUN: opt -avr-shift-expand -S %s -o - | FileCheck %s | ||
|
|
||
| ; The avr-shift-expand pass expands large shifts with a non-constant shift | ||
| ; amount to a loop. These loops avoid generating a (non-existing) builtin such | ||
| ; as __ashlsi3. | ||
|
|
||
| target datalayout = "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8" | ||
| target triple = "avr" | ||
|
|
||
| define i32 @shl(i32 %value, i32 %amount) addrspace(1) { | ||
| ; CHECK-LABEL: @shl( | ||
| ; CHECK-NEXT: [[TMP1:%.*]] = trunc i32 [[AMOUNT:%.*]] to i8 | ||
| ; CHECK-NEXT: [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0 | ||
| ; CHECK-NEXT: br i1 [[TMP2]], label [[SHIFT_DONE:%.*]], label [[SHIFT_LOOP:%.*]] | ||
| ; CHECK: shift.loop: | ||
| ; CHECK-NEXT: [[TMP3:%.*]] = phi i8 [ [[TMP1]], [[TMP0:%.*]] ], [ [[TMP5:%.*]], [[SHIFT_LOOP]] ] | ||
| ; CHECK-NEXT: [[TMP4:%.*]] = phi i32 [ [[VALUE:%.*]], [[TMP0]] ], [ [[TMP6:%.*]], [[SHIFT_LOOP]] ] | ||
| ; CHECK-NEXT: [[TMP5]] = sub i8 [[TMP3]], 1 | ||
| ; CHECK-NEXT: [[TMP6]] = shl i32 [[TMP4]], 1 | ||
| ; CHECK-NEXT: [[TMP7:%.*]] = icmp eq i8 [[TMP5]], 0 | ||
| ; CHECK-NEXT: br i1 [[TMP7]], label [[SHIFT_DONE]], label [[SHIFT_LOOP]] | ||
| ; CHECK: shift.done: | ||
| ; CHECK-NEXT: [[TMP8:%.*]] = phi i32 [ [[VALUE]], [[TMP0]] ], [ [[TMP6]], [[SHIFT_LOOP]] ] | ||
| ; CHECK-NEXT: ret i32 [[TMP8]] | ||
| ; | ||
| %result = shl i32 %value, %amount | ||
| ret i32 %result | ||
| } | ||
|
|
||
| define i32 @lshr(i32 %value, i32 %amount) addrspace(1) { | ||
| ; CHECK-LABEL: @lshr( | ||
| ; CHECK-NEXT: [[TMP1:%.*]] = trunc i32 [[AMOUNT:%.*]] to i8 | ||
| ; CHECK-NEXT: [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0 | ||
| ; CHECK-NEXT: br i1 [[TMP2]], label [[SHIFT_DONE:%.*]], label [[SHIFT_LOOP:%.*]] | ||
| ; CHECK: shift.loop: | ||
| ; CHECK-NEXT: [[TMP3:%.*]] = phi i8 [ [[TMP1]], [[TMP0:%.*]] ], [ [[TMP5:%.*]], [[SHIFT_LOOP]] ] | ||
| ; CHECK-NEXT: [[TMP4:%.*]] = phi i32 [ [[VALUE:%.*]], [[TMP0]] ], [ [[TMP6:%.*]], [[SHIFT_LOOP]] ] | ||
| ; CHECK-NEXT: [[TMP5]] = sub i8 [[TMP3]], 1 | ||
| ; CHECK-NEXT: [[TMP6]] = lshr i32 [[TMP4]], 1 | ||
| ; CHECK-NEXT: [[TMP7:%.*]] = icmp eq i8 [[TMP5]], 0 | ||
| ; CHECK-NEXT: br i1 [[TMP7]], label [[SHIFT_DONE]], label [[SHIFT_LOOP]] | ||
| ; CHECK: shift.done: | ||
| ; CHECK-NEXT: [[TMP8:%.*]] = phi i32 [ [[VALUE]], [[TMP0]] ], [ [[TMP6]], [[SHIFT_LOOP]] ] | ||
| ; CHECK-NEXT: ret i32 [[TMP8]] | ||
| ; | ||
| %result = lshr i32 %value, %amount | ||
| ret i32 %result | ||
| } | ||
|
|
||
| define i32 @ashr(i32 %0, i32 %1) addrspace(1) { | ||
| ; CHECK-LABEL: @ashr( | ||
| ; CHECK-NEXT: [[TMP3:%.*]] = trunc i32 [[TMP1:%.*]] to i8 | ||
| ; CHECK-NEXT: [[TMP4:%.*]] = icmp eq i8 [[TMP3]], 0 | ||
| ; CHECK-NEXT: br i1 [[TMP4]], label [[SHIFT_DONE:%.*]], label [[SHIFT_LOOP:%.*]] | ||
| ; CHECK: shift.loop: | ||
| ; CHECK-NEXT: [[TMP5:%.*]] = phi i8 [ [[TMP3]], [[TMP2:%.*]] ], [ [[TMP7:%.*]], [[SHIFT_LOOP]] ] | ||
| ; CHECK-NEXT: [[TMP6:%.*]] = phi i32 [ [[TMP0:%.*]], [[TMP2]] ], [ [[TMP8:%.*]], [[SHIFT_LOOP]] ] | ||
| ; CHECK-NEXT: [[TMP7]] = sub i8 [[TMP5]], 1 | ||
| ; CHECK-NEXT: [[TMP8]] = ashr i32 [[TMP6]], 1 | ||
| ; CHECK-NEXT: [[TMP9:%.*]] = icmp eq i8 [[TMP7]], 0 | ||
| ; CHECK-NEXT: br i1 [[TMP9]], label [[SHIFT_DONE]], label [[SHIFT_LOOP]] | ||
| ; CHECK: shift.done: | ||
| ; CHECK-NEXT: [[TMP10:%.*]] = phi i32 [ [[TMP0]], [[TMP2]] ], [ [[TMP8]], [[SHIFT_LOOP]] ] | ||
| ; CHECK-NEXT: ret i32 [[TMP10]] | ||
| ; | ||
| %3 = ashr i32 %0, %1 | ||
| ret i32 %3 | ||
| } | ||
|
|
||
| ; This function is not modified because it is not an i32. | ||
| define i40 @shl40(i40 %value, i40 %amount) addrspace(1) { | ||
| ; CHECK-LABEL: @shl40( | ||
| ; CHECK-NEXT: [[RESULT:%.*]] = shl i40 [[VALUE:%.*]], [[AMOUNT:%.*]] | ||
| ; CHECK-NEXT: ret i40 [[RESULT]] | ||
| ; | ||
| %result = shl i40 %value, %amount | ||
| ret i40 %result | ||
| } | ||
|
|
||
| ; This function isn't either, although perhaps it should. | ||
| define i24 @shl24(i24 %value, i24 %amount) addrspace(1) { | ||
| ; CHECK-LABEL: @shl24( | ||
| ; CHECK-NEXT: [[RESULT:%.*]] = shl i24 [[VALUE:%.*]], [[AMOUNT:%.*]] | ||
| ; CHECK-NEXT: ret i24 [[RESULT]] | ||
| ; | ||
| %result = shl i24 %value, %amount | ||
| ret i24 %result | ||
| } |