diff --git a/lib/SILOptimizer/Transforms/SILMem2Reg.cpp b/lib/SILOptimizer/Transforms/SILMem2Reg.cpp index 3c1d0269a0aaa..8a7ca3b98523e 100644 --- a/lib/SILOptimizer/Transforms/SILMem2Reg.cpp +++ b/lib/SILOptimizer/Transforms/SILMem2Reg.cpp @@ -156,7 +156,7 @@ class MemoryToRegisters { SILBuilder B; /// Check if the AllocStackInst \p ASI is only written into. - bool isWriteOnlyAllocation(AllocStackInst *ASI); + bool isWriteOnlyAllocation(SingleValueInstruction *addr); /// Promote all of the AllocStacks in a single basic block in one /// linear scan. Note: This function deletes all of the users of the @@ -227,7 +227,7 @@ static bool isAddressForLoad(SILInstruction *I, SILBasicBlock *&singleBlock, /// Returns true if \p I is a dead struct_element_addr or tuple_element_addr. static bool isDeadAddrProjection(SILInstruction *I) { if (!isa(I) && !isa(I) && - !isa(I)) + !isa(I) && !isa(I)) return false; // Recursively search for uses which are dead themselves. @@ -241,8 +241,8 @@ static bool isDeadAddrProjection(SILInstruction *I) { /// Returns true if this AllocStacks is captured. /// Sets \p inSingleBlock to true if all uses of \p ASI are in a single block. -static bool isCaptured(AllocStackInst *ASI, bool &inSingleBlock) { - +static bool isCaptured(SingleValueInstruction *ASI, bool &inSingleBlock) { + SILBasicBlock *singleBlock = ASI->getParent(); // For all users of the AllocStack instruction. @@ -262,9 +262,10 @@ static bool isCaptured(AllocStackInst *ASI, bool &inSingleBlock) { if (SI->getDest() == ASI) continue; - // Deallocation is also okay, as are DebugValueAddr. We will turn - // the latter into DebugValue. - if (isa(II) || isa(II)) + // Deallocation is also okay, as are EndAccess and DebugValueAddr. We will + // turn the latter into DebugValue. + if (isa(II) || isa(II) || + isa(II)) continue; // Destroys of loadable types can be rewritten as releases, so @@ -273,6 +274,13 @@ static bool isCaptured(AllocStackInst *ASI, bool &inSingleBlock) { if (DAI->getOperand()->getType().isLoadable(*DAI->getFunction())) continue; + // Begin access is OK if it isn't captured. + if (auto *beginAccess = dyn_cast(II)) { + if (isCaptured(beginAccess, inSingleBlock)) + return true; + continue; + } + // Other instructions are assumed to capture the AllocStack. LLVM_DEBUG(llvm::dbgs() << "*** AllocStack is captured by: " << *II); return true; @@ -284,9 +292,9 @@ static bool isCaptured(AllocStackInst *ASI, bool &inSingleBlock) { } /// Returns true if the AllocStack is only stored into. -bool MemoryToRegisters::isWriteOnlyAllocation(AllocStackInst *ASI) { +bool MemoryToRegisters::isWriteOnlyAllocation(SingleValueInstruction *addr) { // For all users of the AllocStack: - for (auto UI = ASI->use_begin(), E = ASI->use_end(); UI != E; ++UI) { + for (auto UI = addr->use_begin(), E = addr->use_end(); UI != E; ++UI) { SILInstruction *II = UI->getUser(); // It is okay to store into this AllocStack. @@ -300,11 +308,16 @@ bool MemoryToRegisters::isWriteOnlyAllocation(AllocStackInst *ASI) { // If we haven't already promoted the AllocStack, we may see // DebugValueAddr uses. - if (isa(II)) + if (isa(II) || isDeadAddrProjection(II) || + isa(II)) continue; - if (isDeadAddrProjection(II)) - continue; + // Begin access is OK if it is write only. + if (auto *beginAccess = dyn_cast(II)) { + if (isWriteOnlyAllocation(beginAccess)) + continue; + return false; + } // Can't do anything else with it. LLVM_DEBUG(llvm::dbgs() << "*** AllocStack has non-write use: " << *II); @@ -343,7 +356,7 @@ static bool isLoadFromStack(SILInstruction *I, AllocStackInst *ASI) { ValueBase *op = I->getOperand(0); while (op != ASI) { if (!isa(op) && !isa(op) && - !isa(op)) + !isa(op) && !isa(op)) return false; op = cast(op)->getOperand(0); @@ -358,7 +371,7 @@ static void collectLoads(SILInstruction *I, SmallVectorImpl &Loads) return; } if (!isa(I) && !isa(I) && - !isa(I)) + !isa(I) && !isa(I)) return; // Recursively search for other loads in the instruction's uses. @@ -375,9 +388,13 @@ static void replaceLoad(LoadInst *LI, SILValue val, AllocStackInst *ASI) { while (op != ASI) { assert(isa(op) || isa(op) || - isa(op)); + isa(op) || isa(op)); auto *Inst = cast(op); - projections.push_back(Projection(Inst)); + + // We don't want to project these instructions but we do want to follow + // their operand. + if (!isa(op)) + projections.push_back(Projection(Inst)); op = Inst->getOperand(0); } @@ -421,11 +438,17 @@ static void replaceLoad(LoadInst *LI, SILValue val, AllocStackInst *ASI) { while (op != ASI && op->use_empty()) { assert(isa(op) || isa(op) || - isa(op)); + isa(op) || isa(op)); auto *Inst = cast(op); - SILValue next = Inst->getOperand(0); - Inst->eraseFromParent(); - op = next; + op = Inst->getOperand(0); + if (Inst->use_empty()) + Inst->eraseFromParent(); + if (auto singleUse = Inst->getSingleUse()) { + if (isa(singleUse->getUser())) { + singleUse->getUser()->eraseFromParent(); + Inst->eraseFromParent(); + } + } } } @@ -596,10 +619,10 @@ void MemoryToRegisters::removeSingleBlockAllocation(AllocStackInst *ASI) { SILValue RunningVal = SILValue(); // For all instructions in the block. - for (auto BBI = BB->begin(), E = BB->end(); BBI != E;) { - SILInstruction *Inst = &*BBI; - ++BBI; - + SmallVector allInstInBlock; + for (auto &i : *BB) + allInstInBlock.push_back(&i); + for (auto *Inst : allInstInBlock) { // Remove instructions that we are loading from. Replace the loaded value // with our running value. if (isLoadFromStack(Inst, ASI)) { @@ -665,7 +688,7 @@ void MemoryToRegisters::removeSingleBlockAllocation(AllocStackInst *ASI) { SILNode *Node = Inst; while (isa(Node) || isa(Node) || - isa(Node)) { + isa(Node) || isa(Node)) { auto *I = cast(Node); if (!I->use_empty()) break; Node = I->getOperand(0); diff --git a/test/SILOptimizer/mem2reg.sil b/test/SILOptimizer/mem2reg.sil index 3f398c97586b3..d11a66cd88038 100644 --- a/test/SILOptimizer/mem2reg.sil +++ b/test/SILOptimizer/mem2reg.sil @@ -492,3 +492,96 @@ bb3: %11 = tuple () return %11 : $() } + +// CHECK-LABEL: sil @single_block_with_access +// CHECK: bb0 +// CHECK: [[IL:%.*]] = integer_literal +// CHECK: [[X:%.*]] = struct $Int ([[IL]] +// CHECK: return [[X]] +// CHECK-LABEL: end sil function 'single_block_with_access' +sil @single_block_with_access : $@convention(thin) () -> Int { +bb0: + %1 = integer_literal $Builtin.Int64, 0 // user: %2 + %2 = struct $Int (%1 : $Builtin.Int64) // user: %4 + %3 = alloc_stack $Int // users: %8, %5, %4 + store %2 to %3 : $*Int // id: %4 + %5 = begin_access [read] [dynamic] [no_nested_conflict] %3 : $*Int // users: %6, %7 + %6 = load %5 : $*Int // user: %9 + end_access %5 : $*Int // id: %7 + dealloc_stack %3 : $*Int // id: %8 + return %6 : $Int // id: %9 +} + +// CHECK-LABEL: sil @multi_block_with_access +// CHECK-NOT: alloc_stack +// CHECK-NOT: begin_access + +// CHECK: bb0(%0 : $Bool): +// CHECK: [[IL:%.*]] = integer_literal +// CHECK-NEXT: [[X:%.*]] = struct $Int ([[IL]] +// CHECK-NEXT: [[COND:%.*]] = struct_extract %0 +// CHECK-NEXT: cond_br [[COND]] + +// CHECK: bb2: +// CHECK-NEXT: [[IL2:%.*]] = integer_literal +// CHECK-NEXT: [[XVAL:%.*]] = struct_extract [[X]] +// CHECK-NEXT: integer_literal +// CHECK-NEXT: [[ADD1:%.*]] = builtin "sadd_with_overflow_Int64"([[XVAL]] : {{.*}}, [[IL2]] +// CHECK-NEXT: tuple_extract +// CHECK-NEXT: cond_fail +// CHECK-NEXT: br + +// CHECK: bb3: +// CHECK-NEXT: [[XVAL2:%.*]] = struct_extract [[X]] +// CHECK-NEXT: [[IL3:%.*]] = integer_literal +// CHECK-NEXT: integer_literal +// CHECK-NEXT: [[ADD2:%.*]] = builtin "sadd_with_overflow_Int64"([[XVAL2]] : {{.*}}, [[IL3]] +// CHECK-NEXT: [[SUM_VAL:%.*]] = tuple_extract [[ADD2]] +// CHECK-NEXT: tuple_extract +// CHECK-NEXT: cond_fail +// CHECK-NEXT: [[OUT:%.*]] = struct $Int ([[SUM_VAL]] +// CHECK-NEXT: return [[OUT]] + +// CHECK: } // end sil function 'multi_block_with_access' +sil @multi_block_with_access : $@convention(thin) (Bool) -> Int { +bb0(%0 : $Bool): + %2 = integer_literal $Builtin.Int64, 0 // user: %3 + %3 = struct $Int (%2 : $Builtin.Int64) // user: %5 + %4 = alloc_stack $Int // users: %26, %10, %22, %5 + store %3 to %4 : $*Int // id: %5 + %6 = struct_extract %0 : $Bool, #Bool._value // user: %7 + cond_br %6, bb2, bb1 // id: %7 + +bb1: // Preds: bb0 + br bb3 // id: %8 + +bb2: // Preds: bb0 + %9 = integer_literal $Builtin.Int64, 1 // user: %14 + %10 = begin_access [modify] [static] [no_nested_conflict] %4 : $*Int // users: %20, %19, %11 + %11 = struct_element_addr %10 : $*Int, #Int._value // user: %12 + %12 = load %11 : $*Builtin.Int64 // user: %14 + %13 = integer_literal $Builtin.Int1, -1 // user: %14 + %14 = builtin "sadd_with_overflow_Int64"(%12 : $Builtin.Int64, %9 : $Builtin.Int64, %13 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) // users: %16, %15 + %15 = tuple_extract %14 : $(Builtin.Int64, Builtin.Int1), 0 // user: %18 + %16 = tuple_extract %14 : $(Builtin.Int64, Builtin.Int1), 1 // user: %17 + cond_fail %16 : $Builtin.Int1, "arithmetic overflow" // id: %17 + %18 = struct $Int (%15 : $Builtin.Int64) // user: %19 + store %18 to %10 : $*Int // id: %19 + end_access %10 : $*Int // id: %20 + br bb3 // id: %21 + +bb3: // Preds: bb1 bb2 + %22 = begin_access [read] [static] [no_nested_conflict] %4 : $*Int // users: %25, %23 + %23 = struct_element_addr %22 : $*Int, #Int._value // user: %24 + %24 = load %23 : $*Builtin.Int64 // user: %29 + end_access %22 : $*Int // id: %25 + dealloc_stack %4 : $*Int // id: %26 + %27 = integer_literal $Builtin.Int64, 2 // user: %29 + %28 = integer_literal $Builtin.Int1, -1 // user: %29 + %29 = builtin "sadd_with_overflow_Int64"(%24 : $Builtin.Int64, %27 : $Builtin.Int64, %28 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) // users: %31, %30 + %30 = tuple_extract %29 : $(Builtin.Int64, Builtin.Int1), 0 // user: %33 + %31 = tuple_extract %29 : $(Builtin.Int64, Builtin.Int1), 1 // user: %32 + cond_fail %31 : $Builtin.Int1, "arithmetic overflow" // id: %32 + %33 = struct $Int (%30 : $Builtin.Int64) // user: %34 + return %33 : $Int // id: %34 +}