Skip to content

Commit

Permalink
[llvm][IR] Add dso_local_equivalent Constant
Browse files Browse the repository at this point in the history
The `dso_local_equivalent` constant is a wrapper for functions that represents a
value which is functionally equivalent to the global passed to this. That is, if
this accepts a function, calling this constant should have the same effects as
calling the function directly. This could be a direct reference to the function,
the `@plt` modifier on X86/AArch64, a thunk, or anything that's equivalent to the
resolved function as a call target.

When lowered, the returned address must have a constant offset at link time from
some other symbol defined within the same binary. The address of this value is
also insignificant. The name is leveraged from `dso_local` where use of a function
or variable is resolved to a symbol in the same linkage unit.

In this patch:
- Addition of `dso_local_equivalent` and handling it
- Update Constant::needsRelocation() to strip constant inbound GEPs and take
  advantage of `dso_local_equivalent` for relative references

This is useful for the [Relative VTables C++ ABI](https://reviews.llvm.org/D72959)
which makes vtables readonly. This works by replacing the dynamic relocations for
function pointers in them with static relocations that represent the offset between
the vtable and virtual functions. If a function is externally defined,
`dso_local_equivalent` can be used as a generic wrapper for the function to still
allow for this static offset calculation to be done.

See [RFC](http://lists.llvm.org/pipermail/llvm-dev/2020-August/144469.html) for more details.

Differential Revision: https://reviews.llvm.org/D77248
  • Loading branch information
PiJoules committed Nov 19, 2020
1 parent 2f3adc5 commit a97f628
Show file tree
Hide file tree
Showing 19 changed files with 395 additions and 11 deletions.
37 changes: 37 additions & 0 deletions llvm/docs/LangRef.rst
Expand Up @@ -3783,6 +3783,43 @@ long as the original value is reconstituted before the ``indirectbr`` or
Finally, some targets may provide defined semantics when using the value
as the operand to an inline assembly, but that is target specific.

.. _dso_local_equivalent:

DSO Local Equivalent
--------------------

``dso_local_equivalent @func``

A '``dso_local_equivalent``' constant represents a function which is
functionally equivalent to a given function, but is always defined in the
current linkage unit. The resulting pointer has the same type as the underlying
function. The resulting pointer is permitted, but not required, to be different
from a pointer to the function, and it may have different values in different
translation units.

The target function may not have ``extern_weak`` linkage.

``dso_local_equivalent`` can be implemented as such:

- If the function has local linkage, hidden visibility, or is
``dso_local``, ``dso_local_equivalent`` can be implemented as simply a pointer
to the function.
- ``dso_local_equivalent`` can be implemented with a stub that tail-calls the
function. Many targets support relocations that resolve at link time to either
a function or a stub for it, depending on if the function is defined within the
linkage unit; LLVM will use this when available. (This is commonly called a
"PLT stub".) On other targets, the stub may need to be emitted explicitly.

This can be used wherever a ``dso_local`` instance of a function is needed without
needing to explicitly make the original function ``dso_local``. An instance where
this can be used is for static offset calculations between a function and some other
``dso_local`` symbol. This is especially useful for the Relative VTables C++ ABI,
where dynamic relocations for function pointers in VTables can be replaced with
static relocations for offsets between the VTable and virtual functions which
may not be ``dso_local``.

This is currently only supported for ELF binary formats.

.. _constantexprs:

Constant Expressions
Expand Down
6 changes: 5 additions & 1 deletion llvm/include/llvm/Analysis/ConstantFolding.h
Expand Up @@ -25,6 +25,7 @@ template <typename T> class ArrayRef;
class CallBase;
class Constant;
class ConstantExpr;
class DSOLocalEquivalent;
class DataLayout;
class Function;
class GlobalValue;
Expand All @@ -34,8 +35,11 @@ class Type;

/// If this constant is a constant offset from a global, return the global and
/// the constant. Because of constantexprs, this function is recursive.
/// If the global is part of a dso_local_equivalent constant, return it through
/// `Equiv` if it is provided.
bool IsConstantOffsetFromGlobal(Constant *C, GlobalValue *&GV, APInt &Offset,
const DataLayout &DL);
const DataLayout &DL,
DSOLocalEquivalent **DSOEquiv = nullptr);

/// ConstantFoldInstruction - Try to constant fold the specified instruction.
/// If successful, the constant result is returned, if not, null is returned.
Expand Down
5 changes: 4 additions & 1 deletion llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
Expand Up @@ -38,7 +38,7 @@ class TargetLoweringObjectFileELF : public TargetLoweringObjectFile {
const TargetMachine *TM = nullptr;

public:
TargetLoweringObjectFileELF() = default;
TargetLoweringObjectFileELF();
~TargetLoweringObjectFileELF() override = default;

void Initialize(MCContext &Ctx, const TargetMachine &TM) override;
Expand Down Expand Up @@ -97,6 +97,9 @@ class TargetLoweringObjectFileELF : public TargetLoweringObjectFile {
const GlobalValue *RHS,
const TargetMachine &TM) const override;

const MCExpr *lowerDSOLocalEquivalent(const DSOLocalEquivalent *Equiv,
const TargetMachine &TM) const override;

MCSection *getSectionForCommandLines() const override;
};

Expand Down
36 changes: 36 additions & 0 deletions llvm/include/llvm/IR/Constants.h
Expand Up @@ -888,6 +888,42 @@ struct OperandTraits<BlockAddress> :

DEFINE_TRANSPARENT_OPERAND_ACCESSORS(BlockAddress, Value)

/// Wrapper for a function that represents a value that
/// functionally represents the original function. This can be a function,
/// global alias to a function, or an ifunc.
class DSOLocalEquivalent final : public Constant {
friend class Constant;

DSOLocalEquivalent(GlobalValue *GV);

void *operator new(size_t s) { return User::operator new(s, 1); }

void destroyConstantImpl();
Value *handleOperandChangeImpl(Value *From, Value *To);

public:
/// Return a DSOLocalEquivalent for the specified global value.
static DSOLocalEquivalent *get(GlobalValue *GV);

/// Transparently provide more efficient getOperand methods.
DECLARE_TRANSPARENT_OPERAND_ACCESSORS(Value);

GlobalValue *getGlobalValue() const {
return cast<GlobalValue>(Op<0>().get());
}

/// Methods for support type inquiry through isa, cast, and dyn_cast:
static bool classof(const Value *V) {
return V->getValueID() == DSOLocalEquivalentVal;
}
};

template <>
struct OperandTraits<DSOLocalEquivalent>
: public FixedNumOperandTraits<DSOLocalEquivalent, 1> {};

DEFINE_TRANSPARENT_OPERAND_ACCESSORS(DSOLocalEquivalent, Value)

//===----------------------------------------------------------------------===//
/// A constant value that is initialized with an expression using
/// other constant values.
Expand Down
17 changes: 17 additions & 0 deletions llvm/include/llvm/IR/Value.def
Expand Up @@ -23,6 +23,11 @@
#error "Missing macro definition of HANDLE_VALUE*"
#endif

// If the LLVM_C_API macro is set, then values handled via HANDLE_*_EXCLUDE_LLVM_C_API will not be expanded in areas the HANDLE_* macro is used. If it is not set, then HANDLE_*_EXCLUDE_LLVM_C_API values are handled normally as their HANDLE_* counterparts.
#ifndef LLVM_C_API
#define LLVM_C_API 0
#endif

#ifndef HANDLE_MEMORY_VALUE
#define HANDLE_MEMORY_VALUE(ValueName) HANDLE_VALUE(ValueName)
#endif
Expand Down Expand Up @@ -55,6 +60,15 @@
#define HANDLE_CONSTANT_MARKER(MarkerName, ValueName)
#endif

#ifndef HANDLE_CONSTANT_EXCLUDE_LLVM_C_API
#define HANDLE_CONSTANT_EXCLUDE_LLVM_C_API(ValueName) HANDLE_CONSTANT(ValueName)
#endif

#if LLVM_C_API
#undef HANDLE_CONSTANT_EXCLUDE_LLVM_C_API
#define HANDLE_CONSTANT_EXCLUDE_LLVM_C_API(ValueName)
#endif

// Having constant first makes the range check for isa<Constant> faster
// and smaller by one operation.

Expand All @@ -65,6 +79,7 @@ HANDLE_GLOBAL_VALUE(GlobalIFunc)
HANDLE_GLOBAL_VALUE(GlobalVariable)
HANDLE_CONSTANT(BlockAddress)
HANDLE_CONSTANT(ConstantExpr)
HANDLE_CONSTANT_EXCLUDE_LLVM_C_API(DSOLocalEquivalent)

// ConstantAggregate.
HANDLE_CONSTANT(ConstantArray)
Expand Down Expand Up @@ -114,3 +129,5 @@ HANDLE_INSTRUCTION(Instruction)
#undef HANDLE_INLINE_ASM_VALUE
#undef HANDLE_VALUE
#undef HANDLE_CONSTANT_MARKER
#undef HANDLE_CONSTANT_EXCLUDE_LLVM_C_API
#undef LLVM_C_API
13 changes: 13 additions & 0 deletions llvm/include/llvm/Target/TargetLoweringObjectFile.h
Expand Up @@ -38,6 +38,7 @@ class Module;
class SectionKind;
class StringRef;
class TargetMachine;
class DSOLocalEquivalent;

class TargetLoweringObjectFile : public MCObjectFileInfo {
/// Name-mangler for global names.
Expand All @@ -47,6 +48,7 @@ class TargetLoweringObjectFile : public MCObjectFileInfo {
bool SupportIndirectSymViaGOTPCRel = false;
bool SupportGOTPCRelWithOffset = true;
bool SupportDebugThreadLocalLocation = true;
bool SupportDSOLocalEquivalentLowering = false;

/// PersonalityEncoding, LSDAEncoding, TTypeEncoding - Some encoding values
/// for EH.
Expand Down Expand Up @@ -180,6 +182,17 @@ class TargetLoweringObjectFile : public MCObjectFileInfo {
return nullptr;
}

/// Target supports a native lowering of a dso_local_equivalent constant
/// without needing to replace it with equivalent IR.
bool supportDSOLocalEquivalentLowering() const {
return SupportDSOLocalEquivalentLowering;
}

virtual const MCExpr *lowerDSOLocalEquivalent(const DSOLocalEquivalent *Equiv,
const TargetMachine &TM) const {
return nullptr;
}

/// Target supports replacing a data "PC"-relative access to a symbol
/// through another symbol, by accessing the later via a GOT entry instead?
bool supportIndirectSymViaGOTPCRel() const {
Expand Down
21 changes: 18 additions & 3 deletions llvm/lib/Analysis/ConstantFolding.cpp
Expand Up @@ -295,22 +295,36 @@ Constant *FoldBitCast(Constant *C, Type *DestTy, const DataLayout &DL) {
/// If this constant is a constant offset from a global, return the global and
/// the constant. Because of constantexprs, this function is recursive.
bool llvm::IsConstantOffsetFromGlobal(Constant *C, GlobalValue *&GV,
APInt &Offset, const DataLayout &DL) {
APInt &Offset, const DataLayout &DL,
DSOLocalEquivalent **DSOEquiv) {
if (DSOEquiv)
*DSOEquiv = nullptr;

// Trivial case, constant is the global.
if ((GV = dyn_cast<GlobalValue>(C))) {
unsigned BitWidth = DL.getIndexTypeSizeInBits(GV->getType());
Offset = APInt(BitWidth, 0);
return true;
}

if (auto *FoundDSOEquiv = dyn_cast<DSOLocalEquivalent>(C)) {
if (DSOEquiv)
*DSOEquiv = FoundDSOEquiv;
GV = FoundDSOEquiv->getGlobalValue();
unsigned BitWidth = DL.getIndexTypeSizeInBits(GV->getType());
Offset = APInt(BitWidth, 0);
return true;
}

// Otherwise, if this isn't a constant expr, bail out.
auto *CE = dyn_cast<ConstantExpr>(C);
if (!CE) return false;

// Look through ptr->int and ptr->ptr casts.
if (CE->getOpcode() == Instruction::PtrToInt ||
CE->getOpcode() == Instruction::BitCast)
return IsConstantOffsetFromGlobal(CE->getOperand(0), GV, Offset, DL);
return IsConstantOffsetFromGlobal(CE->getOperand(0), GV, Offset, DL,
DSOEquiv);

// i32* getelementptr ([5 x i32]* @a, i32 0, i32 5)
auto *GEP = dyn_cast<GEPOperator>(CE);
Expand All @@ -321,7 +335,8 @@ bool llvm::IsConstantOffsetFromGlobal(Constant *C, GlobalValue *&GV,
APInt TmpOffset(BitWidth, 0);

// If the base isn't a global+constant, we aren't either.
if (!IsConstantOffsetFromGlobal(CE->getOperand(0), GV, TmpOffset, DL))
if (!IsConstantOffsetFromGlobal(CE->getOperand(0), GV, TmpOffset, DL,
DSOEquiv))
return false;

// Otherwise, add any offset that our operands provide.
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/AsmParser/LLLexer.cpp
Expand Up @@ -724,6 +724,7 @@ lltok::Kind LLLexer::LexIdentifier() {
KEYWORD(vscale);
KEYWORD(x);
KEYWORD(blockaddress);
KEYWORD(dso_local_equivalent);

// Metadata types.
KEYWORD(distinct);
Expand Down
33 changes: 33 additions & 0 deletions llvm/lib/AsmParser/LLParser.cpp
Expand Up @@ -3491,6 +3491,39 @@ bool LLParser::parseValID(ValID &ID, PerFunctionState *PFS) {
return false;
}

case lltok::kw_dso_local_equivalent: {
// ValID ::= 'dso_local_equivalent' @foo
Lex.Lex();

ValID Fn;

if (parseValID(Fn))
return true;

if (Fn.Kind != ValID::t_GlobalID && Fn.Kind != ValID::t_GlobalName)
return error(Fn.Loc,
"expected global value name in dso_local_equivalent");

// Try to find the function (but skip it if it's forward-referenced).
GlobalValue *GV = nullptr;
if (Fn.Kind == ValID::t_GlobalID) {
if (Fn.UIntVal < NumberedVals.size())
GV = NumberedVals[Fn.UIntVal];
} else if (!ForwardRefVals.count(Fn.StrVal)) {
GV = M->getNamedValue(Fn.StrVal);
}

assert(GV && "Could not find a corresponding global variable");

if (!GV->getValueType()->isFunctionTy())
return error(Fn.Loc, "expected a function, alias to function, or ifunc "
"in dso_local_equivalent");

ID.ConstantVal = DSOLocalEquivalent::get(GV);
ID.Kind = ValID::t_Constant;
return false;
}

case lltok::kw_trunc:
case lltok::kw_zext:
case lltok::kw_sext:
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/AsmParser/LLToken.h
Expand Up @@ -360,6 +360,7 @@ enum Kind {
kw_extractvalue,
kw_insertvalue,
kw_blockaddress,
kw_dso_local_equivalent,

kw_freeze,

Expand Down
18 changes: 14 additions & 4 deletions llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
Expand Up @@ -2341,6 +2341,9 @@ const MCExpr *AsmPrinter::lowerConstant(const Constant *CV) {
if (const BlockAddress *BA = dyn_cast<BlockAddress>(CV))
return MCSymbolRefExpr::create(GetBlockAddressSymbol(BA), Ctx);

if (const auto *Equiv = dyn_cast<DSOLocalEquivalent>(CV))
return getObjFileLowering().lowerDSOLocalEquivalent(Equiv, TM);

const ConstantExpr *CE = dyn_cast<ConstantExpr>(CV);
if (!CE) {
llvm_unreachable("Unknown constant value to lower!");
Expand Down Expand Up @@ -2437,18 +2440,25 @@ const MCExpr *AsmPrinter::lowerConstant(const Constant *CV) {
case Instruction::Sub: {
GlobalValue *LHSGV;
APInt LHSOffset;
DSOLocalEquivalent *DSOEquiv;
if (IsConstantOffsetFromGlobal(CE->getOperand(0), LHSGV, LHSOffset,
getDataLayout())) {
getDataLayout(), &DSOEquiv)) {
GlobalValue *RHSGV;
APInt RHSOffset;
if (IsConstantOffsetFromGlobal(CE->getOperand(1), RHSGV, RHSOffset,
getDataLayout())) {
const MCExpr *RelocExpr =
getObjFileLowering().lowerRelativeReference(LHSGV, RHSGV, TM);
if (!RelocExpr)
if (!RelocExpr) {
const MCExpr *LHSExpr =
MCSymbolRefExpr::create(getSymbol(LHSGV), Ctx);
if (DSOEquiv &&
getObjFileLowering().supportDSOLocalEquivalentLowering())
LHSExpr =
getObjFileLowering().lowerDSOLocalEquivalent(DSOEquiv, TM);
RelocExpr = MCBinaryExpr::createSub(
MCSymbolRefExpr::create(getSymbol(LHSGV), Ctx),
MCSymbolRefExpr::create(getSymbol(RHSGV), Ctx), Ctx);
LHSExpr, MCSymbolRefExpr::create(getSymbol(RHSGV), Ctx), Ctx);
}
int64_t Addend = (LHSOffset - RHSOffset).getSExtValue();
if (Addend != 0)
RelocExpr = MCBinaryExpr::createAdd(
Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
Expand Up @@ -1514,6 +1514,9 @@ SDValue SelectionDAGBuilder::getValueImpl(const Value *V) {
if (const BlockAddress *BA = dyn_cast<BlockAddress>(C))
return DAG.getBlockAddress(BA, VT);

if (const auto *Equiv = dyn_cast<DSOLocalEquivalent>(C))
return getValue(Equiv->getGlobalValue());

VectorType *VecTy = cast<VectorType>(V->getType());

// Now that we know the number and type of the elements, get that number of
Expand Down
19 changes: 19 additions & 0 deletions llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
Expand Up @@ -105,6 +105,11 @@ static void GetObjCImageInfo(Module &M, unsigned &Version, unsigned &Flags,
// ELF
//===----------------------------------------------------------------------===//

TargetLoweringObjectFileELF::TargetLoweringObjectFileELF()
: TargetLoweringObjectFile() {
SupportDSOLocalEquivalentLowering = true;
}

void TargetLoweringObjectFileELF::Initialize(MCContext &Ctx,
const TargetMachine &TgtM) {
TargetLoweringObjectFile::Initialize(Ctx, TgtM);
Expand Down Expand Up @@ -1007,6 +1012,20 @@ const MCExpr *TargetLoweringObjectFileELF::lowerRelativeReference(
MCSymbolRefExpr::create(TM.getSymbol(RHS), getContext()), getContext());
}

const MCExpr *TargetLoweringObjectFileELF::lowerDSOLocalEquivalent(
const DSOLocalEquivalent *Equiv, const TargetMachine &TM) const {
assert(supportDSOLocalEquivalentLowering());

const auto *GV = Equiv->getGlobalValue();

// A PLT entry is not needed for dso_local globals.
if (GV->isDSOLocal() || GV->isImplicitDSOLocal())
return MCSymbolRefExpr::create(TM.getSymbol(GV), getContext());

return MCSymbolRefExpr::create(TM.getSymbol(GV), PLTRelativeVariantKind,
getContext());
}

MCSection *TargetLoweringObjectFileELF::getSectionForCommandLines() const {
// Use ".GCC.command.line" since this feature is to support clang's
// -frecord-gcc-switches which in turn attempts to mimic GCC's switch of the
Expand Down

0 comments on commit a97f628

Please sign in to comment.