llvm-project/llvm/lib/IR/User.cpp
Alexis Engelke 6813f8f037
[IR] Don't store switch case values as operands
SwitchInst case values must be ConstantInt, which have no use list.
Therefore it is not necessary to store these as Use, instead store them
more efficiently as a simple array of pointers after the uses, similar
to how PHINode stores basic blocks.

After this change, the successors of all terminators are stored
consecutively in the operand list. This is preparatory work for
improving the performance of successor access.

Add new C API functions so that switch case values remain accessible
from bindings for other languages.

While this could also be achieved by merely changing the order of
operands (i.e., first all successors, then all constants), doing so
would increase the asymptotic runtime of addCase from O(1) to O(n)
(i.e., adding n cases would be O(n^2)), because it would need to shift
all constants by one slot. Having null/invalid operands is also a bad
idea and would cause much more breakage.

Pull Request: https://github.com/llvm/llvm-project/pull/170984
2025-12-11 18:38:39 +01:00

220 lines
7.8 KiB
C++

//===-- User.cpp - Implement the User class -------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "llvm/IR/User.h"
#include "llvm/IR/Constant.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/IntrinsicInst.h"
using namespace llvm;
namespace llvm {
class BasicBlock;
}
//===----------------------------------------------------------------------===//
// User Class
//===----------------------------------------------------------------------===//
bool User::replaceUsesOfWith(Value *From, Value *To) {
bool Changed = false;
if (From == To) return Changed; // Duh what?
assert((!isa<Constant>(this) || isa<GlobalValue>(this)) &&
"Cannot call User::replaceUsesOfWith on a constant!");
for (unsigned i = 0, E = getNumOperands(); i != E; ++i)
if (getOperand(i) == From) { // Is This operand is pointing to oldval?
// The side effects of this setOperand call include linking to
// "To", adding "this" to the uses list of To, and
// most importantly, removing "this" from the use list of "From".
setOperand(i, To);
Changed = true;
}
if (auto DVI = dyn_cast_or_null<DbgVariableIntrinsic>(this)) {
if (is_contained(DVI->location_ops(), From)) {
DVI->replaceVariableLocationOp(From, To);
Changed = true;
}
}
return Changed;
}
//===----------------------------------------------------------------------===//
// User allocHungoffUses Implementation
//===----------------------------------------------------------------------===//
void User::allocHungoffUses(unsigned N, bool WithExtraValues) {
assert(HasHungOffUses && "alloc must have hung off uses");
static_assert(alignof(Use) >= alignof(Value *),
"Alignment is insufficient for 'hung-off-uses' pieces");
// Allocate the array of Uses
size_t size = N * sizeof(Use);
if (WithExtraValues)
size += N * sizeof(Value *);
Use *Begin = static_cast<Use*>(::operator new(size));
Use *End = Begin + N;
setOperandList(Begin);
for (; Begin != End; Begin++)
new (Begin) Use(this);
}
void User::growHungoffUses(unsigned NewNumUses, bool WithExtraValues) {
assert(HasHungOffUses && "realloc must have hung off uses");
unsigned OldNumUses = getNumOperands();
// We don't support shrinking the number of uses. We wouldn't have enough
// space to copy the old uses in to the new space.
assert(NewNumUses > OldNumUses && "realloc must grow num uses");
Use *OldOps = getOperandList();
allocHungoffUses(NewNumUses, WithExtraValues);
Use *NewOps = getOperandList();
// Now copy from the old operands list to the new one.
std::copy(OldOps, OldOps + OldNumUses, NewOps);
// If the User has extra values (phi basic blocks, switch case values), then
// we need to copy these, too.
if (WithExtraValues) {
auto *OldPtr = reinterpret_cast<char *>(OldOps + OldNumUses);
auto *NewPtr = reinterpret_cast<char *>(NewOps + NewNumUses);
std::copy(OldPtr, OldPtr + (OldNumUses * sizeof(Value *)), NewPtr);
}
Use::zap(OldOps, OldOps + OldNumUses, true);
}
// This is a private struct used by `User` to track the co-allocated descriptor
// section.
struct DescriptorInfo {
intptr_t SizeInBytes;
};
ArrayRef<const uint8_t> User::getDescriptor() const {
auto MutableARef = const_cast<User *>(this)->getDescriptor();
return {MutableARef.begin(), MutableARef.end()};
}
MutableArrayRef<uint8_t> User::getDescriptor() {
assert(HasDescriptor && "Don't call otherwise!");
assert(!HasHungOffUses && "Invariant!");
auto *DI = reinterpret_cast<DescriptorInfo *>(getIntrusiveOperands()) - 1;
assert(DI->SizeInBytes != 0 && "Should not have had a descriptor otherwise!");
return MutableArrayRef<uint8_t>(
reinterpret_cast<uint8_t *>(DI) - DI->SizeInBytes, DI->SizeInBytes);
}
bool User::isDroppable() const {
if (auto *II = dyn_cast<IntrinsicInst>(this)) {
switch (II->getIntrinsicID()) {
default:
return false;
case Intrinsic::assume:
case Intrinsic::pseudoprobe:
case Intrinsic::experimental_noalias_scope_decl:
return true;
}
}
return false;
}
//===----------------------------------------------------------------------===//
// User operator new Implementations
//===----------------------------------------------------------------------===//
void *User::allocateFixedOperandUser(size_t Size, unsigned Us,
unsigned DescBytes) {
assert(Us < (1u << NumUserOperandsBits) && "Too many operands");
static_assert(sizeof(DescriptorInfo) % sizeof(void *) == 0, "Required below");
unsigned DescBytesToAllocate =
DescBytes == 0 ? 0 : (DescBytes + sizeof(DescriptorInfo));
assert(DescBytesToAllocate % sizeof(void *) == 0 &&
"We need this to satisfy alignment constraints for Uses");
uint8_t *Storage = static_cast<uint8_t *>(
::operator new(Size + sizeof(Use) * Us + DescBytesToAllocate));
Use *Start = reinterpret_cast<Use *>(Storage + DescBytesToAllocate);
Use *End = Start + Us;
User *Obj = reinterpret_cast<User *>(End);
Obj->NumUserOperands = Us;
Obj->HasHungOffUses = false;
Obj->HasDescriptor = DescBytes != 0;
for (; Start != End; Start++)
new (Start) Use(Obj);
if (DescBytes != 0) {
auto *DescInfo = reinterpret_cast<DescriptorInfo *>(Storage + DescBytes);
DescInfo->SizeInBytes = DescBytes;
}
return Obj;
}
void *User::operator new(size_t Size, IntrusiveOperandsAllocMarker allocTrait) {
return allocateFixedOperandUser(Size, allocTrait.NumOps, 0);
}
void *User::operator new(size_t Size,
IntrusiveOperandsAndDescriptorAllocMarker allocTrait) {
return allocateFixedOperandUser(Size, allocTrait.NumOps,
allocTrait.DescBytes);
}
void *User::operator new(size_t Size, HungOffOperandsAllocMarker) {
// Allocate space for a single Use*
void *Storage = ::operator new(Size + sizeof(Use *));
Use **HungOffOperandList = static_cast<Use **>(Storage);
User *Obj = reinterpret_cast<User *>(HungOffOperandList + 1);
Obj->NumUserOperands = 0;
Obj->HasHungOffUses = true;
Obj->HasDescriptor = false;
*HungOffOperandList = nullptr;
return Obj;
}
//===----------------------------------------------------------------------===//
// User operator delete Implementation
//===----------------------------------------------------------------------===//
// Repress memory sanitization, due to use-after-destroy by operator
// delete. Bug report 24578 identifies this issue.
LLVM_NO_SANITIZE_MEMORY_ATTRIBUTE void User::operator delete(void *Usr) {
// Hung off uses use a single Use* before the User, while other subclasses
// use a Use[] allocated prior to the user.
User *Obj = static_cast<User *>(Usr);
if (Obj->HasHungOffUses) {
assert(!Obj->HasDescriptor && "not supported!");
Use **HungOffOperandList = static_cast<Use **>(Usr) - 1;
// drop the hung off uses.
Use::zap(*HungOffOperandList, *HungOffOperandList + Obj->NumUserOperands,
/* Delete */ true);
::operator delete(HungOffOperandList);
} else if (Obj->HasDescriptor) {
Use *UseBegin = static_cast<Use *>(Usr) - Obj->NumUserOperands;
Use::zap(UseBegin, UseBegin + Obj->NumUserOperands, /* Delete */ false);
auto *DI = reinterpret_cast<DescriptorInfo *>(UseBegin) - 1;
uint8_t *Storage = reinterpret_cast<uint8_t *>(DI) - DI->SizeInBytes;
::operator delete(Storage);
} else {
Use *Storage = static_cast<Use *>(Usr) - Obj->NumUserOperands;
Use::zap(Storage, Storage + Obj->NumUserOperands,
/* Delete */ false);
::operator delete(Storage);
}
}