LLVM IRには、いくつかの特殊な構文が存在し、これらをサポートすることでより多くのコードを生成できるようになる。 これらについては、clang側からIntrinsic関数を呼び出すことでLLVM IRを生成し、llcに渡してテストすることができる。

FRAMEADDR
関数のフレームポインタを取得する。C言語では、__builtin_frame_address()により取得することができる。
func_frame_addr.cpp
void* display_frameaddress() { return (void *)__builtin_frame_address(0); }
上記のプログラムからLLVMの中間表現を作ってみると、以下のようになった。
llvm.frameaddressが使用されている。llvm.frameaddressはスタックフレームのフレームポインタの値を返す。以下のウェブサイトに使用が説明されている。
https://llvm.org/docs/LangRef.html#llvm-frameaddress-intrinsic
./bin/clang ../myriscvx-tests/tests/func_frame_return_addr.cpp -c -emit-llvm -o - | ./bin/llvm-dis -o -
define dso_local i8* @_Z20display_frameaddressv() #0 {
entry:
%0 = call i8* @llvm.frameaddress(i32 0)
ret i8* %0
}
このframeaddressは、LLVM IRノードのFRAMEADDRに相当する。このFRAMEADDRを処理するために、MYRISCVXISelLowering.cppに処理を付け加える。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
//@MYRISCVXTargetLowering { MYRISCVXTargetLowering::MYRISCVXTargetLowering(const MYRISCVXTargetMachine &TM, const MYRISCVXSubtarget &STI) ... setOperationAction(ISD::FRAMEADDR, MVT::Other, Custom); ... }
まず、setOperationAction()にてISD::FRAMEADDRの処理をカスタム関数に投げるように設定する。そして、実際にカスタム関数を作成して命令生成のためのノードを構築する。
SDValue MYRISCVXTargetLowering:: LowerOperation(SDValue Op, SelectionDAG &DAG) const { switch (Op.getOpcode()) { ... case ISD::FRAMEADDR : return lowerFRAMEADDR(Op, DAG); ... } } ... SDValue MYRISCVXTargetLowering:: lowerFRAMEADDR(SDValue Op, SelectionDAG &DAG) const { // check the depth assert((cast<ConstantSDNode>(Op.getOperand(0))->getZExtValue() == 0) && "Frame address can only be determined for current frame."); MachineFrameInfo &MFI = DAG.getMachineFunction().getFrameInfo(); MFI.setFrameAddressIsTaken(true); EVT VT = Op.getValueType(); SDLoc DL(Op); SDValue FrameAddr = DAG.getCopyFromReg( DAG.getEntryNode(), DL, MYRISCVX::FP, VT); return FrameAddr; }
lowerFRAMEADDRでは、フレームポインタレジスタFPの値を読み取り、getCopyFromReg()でノードを作成し、そのノードを返す。
- コンパイル結果
_Z20display_frameaddressv: # @_Z20display_frameaddressv .cfi_startproc # %bb.0: # %entry addi x10, x8, 0 ret
フレームポインタレジスタFP(x8)の値を、戻地理レジスタx10にコピーしてから戻る。正しく動作しているようだ。
RETURNADDR
関数の戻りアドレスを取得する。つまり、関数実行中の戻りアドレス、レジスタで言うとraレジスタの中身を戻す。C言語では、__builtin_return_address()により取得することができる。
func_return_addr.cpp
extern int fn(); int display_returnaddress() { int a = (int)__builtin_return_address(0); fn(); return a; }
どのようなLLVM中間表現が生成されているか確認してみる。
./bin/clang func_return_addr.cpp -c -emit-llvm --target=riscv32-unknown-elf -o - | ./bin/llvm-dis -o -
define dso_local i32 @_Z21display_returnaddressv() #0 {
entry:
%a = alloca i32, align 4
%0 = call i8* @llvm.returnaddress(i32 0)
%1 = ptrtoint i8* %0 to i32
store i32 %1, i32* %a, align 4
%call = call i32 @_Z2fnv()
%2 = load i32, i32* %a, align 4
ret i32 %2
}
llvm.returnaddressというノードが生成されており、frameaddress同様、戻りアドレスを示すノードになる。
このノードは、LLVM IRのRETURNADDRに相当する。このノードを処理するための実装を加える。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
//@MYRISCVXTargetLowering { MYRISCVXTargetLowering::MYRISCVXTargetLowering(const MYRISCVXTargetMachine &TM, const MYRISCVXSubtarget &STI) ... setOperationAction(ISD::RETURNADDR, MVT::Other, Custom); ... } SDValue MYRISCVXTargetLowering:: LowerOperation(SDValue Op, SelectionDAG &DAG) const { switch (Op.getOpcode()) { ... case ISD::RETURNADDR : return lowerRETURNADDR(Op, DAG); } }
SDValue MYRISCVXTargetLowering::lowerRETURNADDR(SDValue Op,
SelectionDAG &DAG) const {
if (verifyReturnAddressArgumentIsConstant(Op, DAG))
return SDValue();
// check the depth
assert((cast<ConstantSDNode>(Op.getOperand(0))->getZExtValue() == 0) &&
"Return address can be determined only for current frame.");
MachineFunction &MF = DAG.getMachineFunction();
MachineFrameInfo &MFI = MF.getFrameInfo();
MVT VT = Op.getSimpleValueType();
unsigned RA = MYRISCVX::RA;
MFI.setReturnAddressIsTaken(true);
// Return RA, which contains the return address. Mark it an implicit live-in.
unsigned Reg = MF.addLiveIn(RA, getRegClassFor(VT));
return DAG.getCopyFromReg(DAG.getEntryNode(), SDLoc(Op), Reg, VT);
}
FRAMEADDR同様、raレジスタをgetCopyFromRegでノードに取り込み、ノードを返すという仕組みになっている。
実際にコンパイルしてみる。
./bin/clang func_return_addr.cpp -c -emit-llvm --target=riscv32-unknown-elf -o func_return_addr.rv32.bc ./bin/llc -mtriple=myriscvx32 -filetype=asm -target-abi=lp func_return_addr.rv32.bc -o
_Z21display_returnaddressv: # @_Z21display_returnaddressv .cfi_startproc # %bb.0: # %entry addi x2, x2, -16 .cfi_def_cfa_offset 16 sw x2, 12(x2) # 4-byte Folded Spill .cfi_offset 2, -4 sw x1, 8(x2) addi x2, x2, 0 jal _Z2fnv addi x2, x2, 0 lw x10, 8(x2) lw x2, 12(x2) # 4-byte Folded Reload addi x2, x2, 16 ret
むだなアセンブリ命令が多くて分かりにくいが、要点は、最初のsw x1, 8(x2)でraレジスタの値をスタックに退避し、lw x10, 8(x2)で戻り値レジスタに書き戻している。これで、現在の戻りアドレスを参照することができる。