Implememting named register intrinsics

This patch implements the infrastructure to use named register constructs in
programs that need access to specific registers (bare metal, kernels, etc).

So far, only the stack pointer is supported as a technology preview, but as it
is, the intrinsic can already support all non-allocatable registers from any
architecture.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@208104 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Renato Golin 2014-05-06 16:51:25 +00:00
parent b889448841
commit 22f779d1fd
29 changed files with 376 additions and 0 deletions

View File

@ -6804,6 +6804,51 @@ Note that calling this intrinsic does not prevent function inlining or
other aggressive transformations, so the value returned may not be that
of the obvious source-language caller.
.. _int_read_register:
.. _int_write_register:
'``llvm.read_register``' and '``llvm.write_register``' Intrinsics
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Syntax:
"""""""
::
declare i32 @llvm.read_register.i32(metadata)
declare i64 @llvm.read_register.i64(metadata)
declare void @llvm.write_register.i32(metadata, i32 @value)
declare void @llvm.write_register.i64(metadata, i64 @value)
!0 = metadata !{metadata !"sp\00"}
Overview:
"""""""""
The '``llvm.read_register``' and '``llvm.write_register``' intrinsics
provides access to the named register. The register must be valid on
the architecture being compiled to. The type needs to be compatible
with the register being read.
Semantics:
""""""""""
The '``llvm.read_register``' intrinsic returns the current value of the
register, where possible. The '``llvm.write_register``' intrinsic sets
the current value of the register, where possible.
This is useful to implement named register global variables that need
to always be mapped to a specific register, as is common practice on
bare-metal programs including OS kernels.
The compiler doesn't check for register availability or use of the used
register in surrounding code, including inline assembly. Because of that,
allocatable registers are not supported.
Warning: So far it only works with the stack pointer on selected
architectures (ARM, ARM64, x86_64 and AArch64). Significant amount of
work is needed to support other registers and even more so, allocatable
registers.
.. _int_stacksave:
'``llvm.stacksave``' Intrinsic

View File

@ -72,6 +72,11 @@ namespace ISD {
/// the parent's frame or return address, and so on.
FRAMEADDR, RETURNADDR,
/// READ_REGISTER, WRITE_REGISTER - This node represents llvm.register on
/// the DAG, which implements the named register global variables extension.
READ_REGISTER,
WRITE_REGISTER,
/// FRAME_TO_ARGS_OFFSET - This node represents offset from frame pointer to
/// first (possible) on-stack argument. This is needed for correct stack
/// adjustment during unwind.

View File

@ -242,6 +242,8 @@ private:
// Calls to these functions are generated by tblgen.
SDNode *Select_INLINEASM(SDNode *N);
SDNode *Select_READ_REGISTER(SDNode *N);
SDNode *Select_WRITE_REGISTER(SDNode *N);
SDNode *Select_UNDEF(SDNode *N);
void CannotYetSelect(SDNode *N);

View File

@ -250,6 +250,10 @@ def int_gcwrite : Intrinsic<[],
//
def int_returnaddress : Intrinsic<[llvm_ptr_ty], [llvm_i32_ty], [IntrNoMem]>;
def int_frameaddress : Intrinsic<[llvm_ptr_ty], [llvm_i32_ty], [IntrNoMem]>;
def int_read_register : Intrinsic<[llvm_anyint_ty], [llvm_metadata_ty],
[IntrNoMem], "llvm.read_register">;
def int_write_register : Intrinsic<[], [llvm_metadata_ty, llvm_anyint_ty],
[IntrNoMem], "llvm.write_register">;
// Note: we treat stacksave/stackrestore as writemem because we don't otherwise
// model their dependencies on allocas.

View File

@ -2214,6 +2214,13 @@ public:
return "__clear_cache";
}
/// Return the register ID of the name passed in. Used by named register
/// global variables extension. There is no target-independent behaviour
/// so the default action is to bail.
virtual unsigned getRegisterByName(const char* RegName) const {
report_fatal_error("Named registers not implemented for this target");
}
/// Return the type that should be used to zero or sign extend a
/// zeroext/signext integer argument or return value. FIXME: Most C calling
/// convention requires the return type to be promoted, but this is not true

View File

@ -1265,6 +1265,13 @@ void SelectionDAGLegalize::LegalizeOp(SDNode *Node) {
if (Action == TargetLowering::Legal)
Action = TargetLowering::Custom;
break;
case ISD::READ_REGISTER:
case ISD::WRITE_REGISTER:
// Named register is legal in the DAG, but blocked by register name
// selection if not implemented by target (to chose the correct register)
// They'll be converted to Copy(To/From)Reg.
Action = TargetLowering::Legal;
break;
case ISD::DEBUGTRAP:
Action = TLI.getOperationAction(Node->getOpcode(), Node->getValueType(0));
if (Action == TargetLowering::Expand) {

View File

@ -4627,6 +4627,22 @@ SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I, unsigned Intrinsic) {
setValue(&I, DAG.getNode(ISD::FRAMEADDR, sdl, TLI->getPointerTy(),
getValue(I.getArgOperand(0))));
return nullptr;
case Intrinsic::read_register: {
Value *Reg = I.getArgOperand(0);
SDValue RegName = DAG.getMDNode(cast<MDNode>(Reg));
EVT VT = TM.getTargetLowering()->getValueType(I.getType());
setValue(&I, DAG.getNode(ISD::READ_REGISTER, sdl, VT, RegName));
return nullptr;
}
case Intrinsic::write_register: {
Value *Reg = I.getArgOperand(0);
Value *RegValue = I.getArgOperand(1);
SDValue Chain = getValue(RegValue).getOperand(0);
SDValue RegName = DAG.getMDNode(cast<MDNode>(Reg));
DAG.setRoot(DAG.getNode(ISD::WRITE_REGISTER, sdl, MVT::Other, Chain,
RegName, getValue(RegValue)));
return nullptr;
}
case Intrinsic::setjmp:
return &"_setjmp"[!TLI->usesUnderscoreSetJmp()];
case Intrinsic::longjmp:

View File

@ -93,6 +93,8 @@ std::string SDNode::getOperationName(const SelectionDAG *G) const {
case ISD::GLOBAL_OFFSET_TABLE: return "GLOBAL_OFFSET_TABLE";
case ISD::RETURNADDR: return "RETURNADDR";
case ISD::FRAMEADDR: return "FRAMEADDR";
case ISD::READ_REGISTER: return "READ_REGISTER";
case ISD::WRITE_REGISTER: return "WRITE_REGISTER";
case ISD::FRAME_TO_ARGS_OFFSET: return "FRAME_TO_ARGS_OFFSET";
case ISD::EH_RETURN: return "EH_RETURN";
case ISD::EH_SJLJ_SETJMP: return "EH_SJLJ_SETJMP";

View File

@ -1807,6 +1807,34 @@ SDNode *SelectionDAGISel::Select_INLINEASM(SDNode *N) {
return New.getNode();
}
SDNode
*SelectionDAGISel::Select_READ_REGISTER(SDNode *Op) {
SDLoc dl(Op);
MDNodeSDNode *MD = dyn_cast<MDNodeSDNode>(Op->getOperand(0));
const MDString *RegStr = dyn_cast<MDString>(MD->getMD()->getOperand(0));
unsigned Reg = getTargetLowering()->getRegisterByName(
RegStr->getString().data());
SDValue New = CurDAG->getCopyFromReg(
CurDAG->getEntryNode(), dl, Reg, Op->getValueType(0));
New->setNodeId(-1);
return New.getNode();
}
SDNode
*SelectionDAGISel::Select_WRITE_REGISTER(SDNode *Op) {
SDLoc dl(Op);
MDNodeSDNode *MD = dyn_cast<MDNodeSDNode>(Op->getOperand(1));
const MDString *RegStr = dyn_cast<MDString>(MD->getMD()->getOperand(0));
unsigned Reg = getTargetLowering()->getRegisterByName(
RegStr->getString().data());
SDValue New = CurDAG->getCopyToReg(
CurDAG->getEntryNode(), dl, Reg, Op->getOperand(2));
New->setNodeId(-1);
return New.getNode();
}
SDNode *SelectionDAGISel::Select_UNDEF(SDNode *N) {
return CurDAG->SelectNodeTo(N, TargetOpcode::IMPLICIT_DEF,N->getValueType(0));
}
@ -2399,6 +2427,8 @@ SelectCodeCommon(SDNode *NodeToMatch, const unsigned char *MatcherTable,
NodeToMatch->getOperand(0));
return nullptr;
case ISD::INLINEASM: return Select_INLINEASM(NodeToMatch);
case ISD::READ_REGISTER: return Select_READ_REGISTER(NodeToMatch);
case ISD::WRITE_REGISTER: return Select_WRITE_REGISTER(NodeToMatch);
case ISD::UNDEF: return Select_UNDEF(NodeToMatch);
}

View File

@ -15,6 +15,7 @@
#include "AArch64.h"
#include "AArch64ISelLowering.h"
#include "AArch64MachineFunctionInfo.h"
#include "AArch64Subtarget.h"
#include "AArch64TargetMachine.h"
#include "AArch64TargetObjectFile.h"
#include "Utils/AArch64BaseInfo.h"
@ -2406,6 +2407,17 @@ SDValue AArch64TargetLowering::LowerFRAMEADDR(SDValue Op, SelectionDAG &DAG)
return FrameAddr;
}
// FIXME? Maybe this could be a TableGen attribute on some registers and
// this table could be generated automatically from RegInfo.
unsigned AArch64TargetLowering::getRegisterByName(const char* RegName) const {
unsigned Reg = StringSwitch<unsigned>(RegName)
.Case("sp", AArch64::XSP)
.Default(0);
if (Reg)
return Reg;
report_fatal_error("Invalid register name global variable");
}
SDValue
AArch64TargetLowering::LowerGlobalAddressELFLarge(SDValue Op,
SelectionDAG &DAG) const {

View File

@ -350,6 +350,8 @@ public:
SDValue PerformDAGCombine(SDNode *N,DAGCombinerInfo &DCI) const override;
unsigned getRegisterByName(const char* RegName) const;
/// isFMAFasterThanFMulAndFAdd - Return true if an FMA operation is faster
/// than a pair of fmul and fadd instructions. fmuladd intrinsics will be
/// expanded to FMAs when this method returns true, otherwise fmuladd is

View File

@ -3784,6 +3784,17 @@ SDValue ARMTargetLowering::LowerFRAMEADDR(SDValue Op, SelectionDAG &DAG) const {
return FrameAddr;
}
// FIXME? Maybe this could be a TableGen attribute on some registers and
// this table could be generated automatically from RegInfo.
unsigned ARMTargetLowering::getRegisterByName(const char* RegName) const {
unsigned Reg = StringSwitch<unsigned>(RegName)
.Case("sp", ARM::SP)
.Default(0);
if (Reg)
return Reg;
report_fatal_error("Invalid register name global variable");
}
/// ExpandBITCAST - If the target supports VFP, this function is called to
/// expand a bit convert where either the source or destination type is i64 to
/// use a VMOVDRR or VMOVRRD node. This should not be done when the non-i64

View File

@ -461,6 +461,8 @@ namespace llvm {
SDValue LowerFSINCOS(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerDivRem(SDValue Op, SelectionDAG &DAG) const;
unsigned getRegisterByName(const char* RegName) const;
/// isFMAFasterThanFMulAndFAdd - Return true if an FMA operation is faster
/// than a pair of fmul and fadd instructions. fmuladd intrinsics will be
/// expanded to FMAs when this method returns true, otherwise fmuladd is

View File

@ -3384,6 +3384,17 @@ SDValue ARM64TargetLowering::LowerFRAMEADDR(SDValue Op,
return FrameAddr;
}
// FIXME? Maybe this could be a TableGen attribute on some registers and
// this table could be generated automatically from RegInfo.
unsigned ARM64TargetLowering::getRegisterByName(const char* RegName) const {
unsigned Reg = StringSwitch<unsigned>(RegName)
.Case("sp", ARM64::SP)
.Default(0);
if (Reg)
return Reg;
report_fatal_error("Invalid register name global variable");
}
SDValue ARM64TargetLowering::LowerRETURNADDR(SDValue Op,
SelectionDAG &DAG) const {
MachineFunction &MF = DAG.getMachineFunction();

View File

@ -388,6 +388,7 @@ private:
ConstraintType
getConstraintType(const std::string &Constraint) const override;
unsigned getRegisterByName(const char* RegName) const;
/// Examine constraint string and operand type and determine a weight value.
/// The operand object must already have been set up with the operand type.

View File

@ -22,6 +22,7 @@
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/VariadicFunction.h"
#include "llvm/CodeGen/IntrinsicLowering.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
@ -12726,6 +12727,18 @@ SDValue X86TargetLowering::LowerFRAMEADDR(SDValue Op, SelectionDAG &DAG) const {
return FrameAddr;
}
// FIXME? Maybe this could be a TableGen attribute on some registers and
// this table could be generated automatically from RegInfo.
unsigned X86TargetLowering::getRegisterByName(const char* RegName) const {
unsigned Reg = StringSwitch<unsigned>(RegName)
.Case("esp", X86::ESP)
.Case("rsp", X86::RSP)
.Default(0);
if (Reg)
return Reg;
report_fatal_error("Invalid register name global variable");
}
SDValue X86TargetLowering::LowerFRAME_TO_ARGS_OFFSET(SDValue Op,
SelectionDAG &DAG) const {
const X86RegisterInfo *RegInfo =

View File

@ -786,6 +786,8 @@ namespace llvm {
return nullptr; // nothing to do, move along.
}
unsigned getRegisterByName(const char* RegName) const;
/// createFastISel - This method returns a target specific FastISel object,
/// or null if the target does not support "fast" ISel.
FastISel *createFastISel(FunctionLoweringInfo &funcInfo,

View File

@ -0,0 +1,13 @@
; RUN: not llc < %s -mtriple=aarch64-linux-gnueabi 2>&1 | FileCheck %s
define i32 @get_stack() nounwind {
entry:
; FIXME: Include an allocatable-specific error message
; CHECK: Invalid register name global variable
%sp = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %sp
}
declare i32 @llvm.read_register.i32(metadata) nounwind
!0 = metadata !{metadata !"x5\00"}

View File

@ -0,0 +1,12 @@
; RUN: not llc < %s -mtriple=aarch64-linux-gnueabi 2>&1 | FileCheck %s
define i32 @get_stack() nounwind {
entry:
; CHECK: Invalid register name global variable
%sp = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %sp
}
declare i32 @llvm.read_register.i32(metadata) nounwind
!0 = metadata !{metadata !"notareg\00"}

View File

@ -0,0 +1,24 @@
; RUN: llc < %s -mtriple=aarch64-linux-gnueabi | FileCheck %s
define i64 @get_stack() nounwind {
entry:
; CHECK-LABEL: get_stack:
; CHECK: mov x0, sp
%sp = call i64 @llvm.read_register.i64(metadata !0)
ret i64 %sp
}
define void @set_stack(i64 %val) nounwind {
entry:
; CHECK-LABEL: set_stack:
; CHECK: mov sp, x0
call void @llvm.write_register.i64(metadata !0, i64 %val)
ret void
}
declare i64 @llvm.read_register.i64(metadata) nounwind
declare void @llvm.write_register.i64(metadata, i64) nounwind
; register unsigned long current_stack_pointer asm("sp");
; CHECK-NOT: .asciz "sp"
!0 = metadata !{metadata !"sp\00"}

View File

@ -0,0 +1,14 @@
; RUN: not llc < %s -mtriple=arm-apple-darwin 2>&1 | FileCheck %s
; RUN: not llc < %s -mtriple=arm-linux-gnueabi 2>&1 | FileCheck %s
define i32 @get_stack() nounwind {
entry:
; FIXME: Include an allocatable-specific error message
; CHECK: Invalid register name global variable
%sp = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %sp
}
declare i32 @llvm.read_register.i32(metadata) nounwind
!0 = metadata !{metadata !"r5\00"}

View File

@ -0,0 +1,13 @@
; RUN: not llc < %s -mtriple=arm-apple-darwin 2>&1 | FileCheck %s
; RUN: not llc < %s -mtriple=arm-linux-gnueabi 2>&1 | FileCheck %s
define i32 @get_stack() nounwind {
entry:
; CHECK: Invalid register name global variable
%sp = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %sp
}
declare i32 @llvm.read_register.i32(metadata) nounwind
!0 = metadata !{metadata !"notareg\00"}

View File

@ -0,0 +1,25 @@
; RUN: llc < %s -mtriple=arm-apple-darwin | FileCheck %s
; RUN: llc < %s -mtriple=arm-linux-gnueabi | FileCheck %s
define i32 @get_stack() nounwind {
entry:
; CHECK-LABEL: get_stack:
; CHECK: mov r0, sp
%sp = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %sp
}
define void @set_stack(i32 %val) nounwind {
entry:
; CHECK-LABEL: set_stack:
; CHECK: mov sp, r0
call void @llvm.write_register.i32(metadata !0, i32 %val)
ret void
}
declare i32 @llvm.read_register.i32(metadata) nounwind
declare void @llvm.write_register.i32(metadata, i32) nounwind
; register unsigned long current_stack_pointer asm("sp");
; CHECK-NOT: .asciz "sp"
!0 = metadata !{metadata !"sp\00"}

View File

@ -0,0 +1,14 @@
; RUN: not llc < %s -mtriple=arm64-apple-darwin 2>&1 | FileCheck %s
; RUN: not llc < %s -mtriple=arm64-linux-gnueabi 2>&1 | FileCheck %s
define i32 @get_stack() nounwind {
entry:
; FIXME: Include an allocatable-specific error message
; CHECK: Invalid register name global variable
%sp = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %sp
}
declare i32 @llvm.read_register.i32(metadata) nounwind
!0 = metadata !{metadata !"x5\00"}

View File

@ -0,0 +1,13 @@
; RUN: not llc < %s -mtriple=arm64-apple-darwin 2>&1 | FileCheck %s
; RUN: not llc < %s -mtriple=arm64-linux-gnueabi 2>&1 | FileCheck %s
define i32 @get_stack() nounwind {
entry:
; CHECK: Invalid register name global variable
%sp = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %sp
}
declare i32 @llvm.read_register.i32(metadata) nounwind
!0 = metadata !{metadata !"notareg\00"}

View File

@ -0,0 +1,24 @@
; RUN: llc < %s -mtriple=arm64-linux-gnu | FileCheck %s
define i64 @get_stack() nounwind {
entry:
; CHECK-LABEL: get_stack:
; CHECK: mov x0, sp
%sp = call i64 @llvm.read_register.i64(metadata !0)
ret i64 %sp
}
define void @set_stack(i64 %val) nounwind {
entry:
; CHECK-LABEL: set_stack:
; CHECK: mov sp, x0
call void @llvm.write_register.i64(metadata !0, i64 %val)
ret void
}
declare i64 @llvm.read_register.i64(metadata) nounwind
declare void @llvm.write_register.i64(metadata, i64) nounwind
; register unsigned long current_stack_pointer asm("sp");
; CHECK-NOT: .asciz "sp"
!0 = metadata !{metadata !"sp\00"}

View File

@ -0,0 +1,14 @@
; RUN: not llc < %s -mtriple=x86_64-apple-darwin 2>&1 | FileCheck %s
; RUN: not llc < %s -mtriple=x86_64-linux-gnueabi 2>&1 | FileCheck %s
define i32 @get_stack() nounwind {
entry:
; FIXME: Include an allocatable-specific error message
; CHECK: Invalid register name global variable
%sp = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %sp
}
declare i32 @llvm.read_register.i32(metadata) nounwind
!0 = metadata !{metadata !"eax\00"}

View File

@ -0,0 +1,13 @@
; RUN: not llc < %s -mtriple=x86_64-apple-darwin 2>&1 | FileCheck %s
; RUN: not llc < %s -mtriple=x86_64-linux-gnueabi 2>&1 | FileCheck %s
define i32 @get_stack() nounwind {
entry:
; CHECK: Invalid register name global variable
%sp = call i32 @llvm.read_register.i32(metadata !0)
ret i32 %sp
}
declare i32 @llvm.read_register.i32(metadata) nounwind
!0 = metadata !{metadata !"notareg\00"}

View File

@ -0,0 +1,25 @@
; RUN: llc < %s -mtriple=x86_64-apple-darwin | FileCheck %s
; RUN: llc < %s -mtriple=x86_64-linux-gnueabi | FileCheck %s
define i64 @get_stack() nounwind {
entry:
; CHECK-LABEL: get_stack:
; CHECK: movq %rsp, %rax
%sp = call i64 @llvm.read_register.i64(metadata !0)
ret i64 %sp
}
define void @set_stack(i64 %val) nounwind {
entry:
; CHECK-LABEL: set_stack:
; CHECK: movq %rdi, %rsp
call void @llvm.write_register.i64(metadata !0, i64 %val)
ret void
}
declare i64 @llvm.read_register.i64(metadata) nounwind
declare void @llvm.write_register.i64(metadata, i64) nounwind
; register unsigned long current_stack_pointer asm("rsp");
; CHECK-NOT: .asciz "rsp"
!0 = metadata !{metadata !"rsp\00"}