|
| 1 | +//====- LoweringPrepareArm64CXXABI.cpp - Arm64 ABI specific code -----====// |
| 2 | +// |
| 3 | +// Part of the LLVM Project, |
| 4 | +// under the Apache License v2.0 with LLVM Exceptions. |
| 5 | +// See https://llvm.org/LICENSE.txt for license information. |
| 6 | +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 7 | +// |
| 8 | +//===------------------------------------------------------------------===// |
| 9 | +// |
| 10 | +// This file provides ARM64 C++ ABI specific code that is used during LLVMIR |
| 11 | +// lowering prepare. |
| 12 | +// |
| 13 | +//===------------------------------------------------------------------===// |
| 14 | + |
| 15 | +#include "../IR/MissingFeatures.h" |
| 16 | +#include "LoweringPrepareItaniumCXXABI.h" |
| 17 | +#include "clang/CIR/Dialect/IR/CIRTypes.h" |
| 18 | + |
| 19 | +#include <assert.h> |
| 20 | + |
| 21 | +using cir::AArch64ABIKind; |
| 22 | + |
| 23 | +namespace { |
| 24 | +class LoweringPrepareAArch64CXXABI : public LoweringPrepareItaniumCXXABI { |
| 25 | +public: |
| 26 | + LoweringPrepareAArch64CXXABI(AArch64ABIKind k) : Kind(k) {} |
| 27 | + mlir::Value lowerVAArg(cir::CIRBaseBuilderTy &builder, |
| 28 | + mlir::cir::VAArgOp op) override; |
| 29 | + |
| 30 | +private: |
| 31 | + AArch64ABIKind Kind; |
| 32 | + mlir::Value lowerAAPCSVAArg(cir::CIRBaseBuilderTy &builder, |
| 33 | + mlir::cir::VAArgOp op); |
| 34 | + bool isDarwinPCS() const { return Kind == AArch64ABIKind::DarwinPCS; } |
| 35 | + mlir::Value lowerMSVAArg(cir::CIRBaseBuilderTy &builder, |
| 36 | + mlir::cir::VAArgOp op) { |
| 37 | + llvm_unreachable("MSVC ABI not supported yet"); |
| 38 | + } |
| 39 | + mlir::Value lowerDarwinVAArg(cir::CIRBaseBuilderTy &builder, |
| 40 | + mlir::cir::VAArgOp op) { |
| 41 | + llvm_unreachable("Darwin ABI not supported yet"); |
| 42 | + } |
| 43 | +}; |
| 44 | +} // namespace |
| 45 | + |
| 46 | +cir::LoweringPrepareCXXABI * |
| 47 | +cir::LoweringPrepareCXXABI::createAArch64ABI(AArch64ABIKind k) { |
| 48 | + return new LoweringPrepareAArch64CXXABI(k); |
| 49 | +} |
| 50 | + |
| 51 | +mlir::Value |
| 52 | +LoweringPrepareAArch64CXXABI::lowerAAPCSVAArg(cir::CIRBaseBuilderTy &builder, |
| 53 | + mlir::cir::VAArgOp op) { |
| 54 | + auto loc = op->getLoc(); |
| 55 | + auto valist = op->getOperand(0); |
| 56 | + auto opResTy = op.getType(); |
| 57 | + // front end should not produce non-scalar type of VAArgOp |
| 58 | + bool isSupportedType = |
| 59 | + opResTy.isa<mlir::cir::IntType, mlir::cir::SingleType, |
| 60 | + mlir::cir::PointerType, mlir::cir::BoolType, |
| 61 | + mlir::cir::DoubleType, mlir::cir::ArrayType>(); |
| 62 | + |
| 63 | + // Homogenous Aggregate type not supported and indirect arg |
| 64 | + // passing not supported yet. And for these supported types, |
| 65 | + // we should not have alignment greater than 8 problem. |
| 66 | + assert(isSupportedType); |
| 67 | + assert(!cir::MissingFeatures::classifyArgumentTypeForAArch64()); |
| 68 | + // indirect arg passing would expect one more level of pointer dereference. |
| 69 | + assert(!cir::MissingFeatures::handleAArch64Indirect()); |
| 70 | + assert(!cir::MissingFeatures::supportgetCoerceToTypeForAArch64()); |
| 71 | + // we don't convert to LLVM Type here as we are lowering to CIR here. |
| 72 | + // so baseTy is the just type of the result of va_arg. |
| 73 | + // but it depends on arg type indirectness and coercion defined by ABI. |
| 74 | + auto baseTy = opResTy; |
| 75 | + |
| 76 | + if (baseTy.isa<mlir::cir::ArrayType>()) { |
| 77 | + llvm_unreachable("ArrayType VAArg loweing NYI"); |
| 78 | + } |
| 79 | + // numRegs may not be 1 if ArrayType is supported. |
| 80 | + unsigned numRegs = 1; |
| 81 | + |
| 82 | + if (Kind == AArch64ABIKind::AAPCSSoft) { |
| 83 | + llvm_unreachable("AAPCSSoft cir.var_arg lowering NYI"); |
| 84 | + } |
| 85 | + bool IsFPR = mlir::cir::isAnyFloatingPointType(opResTy); |
| 86 | + |
| 87 | + // The AArch64 va_list type and handling is specified in the Procedure Call |
| 88 | + // Standard, section B.4: |
| 89 | + // |
| 90 | + // struct { |
| 91 | + // void *__stack; |
| 92 | + // void *__gr_top; |
| 93 | + // void *__vr_top; |
| 94 | + // int __gr_offs; |
| 95 | + // int __vr_offs; |
| 96 | + // }; |
| 97 | + auto curInsertionP = builder.saveInsertionPoint(); |
| 98 | + auto currentBlock = builder.getInsertionBlock(); |
| 99 | + auto boolTy = builder.getBoolTy(); |
| 100 | + |
| 101 | + auto maybeRegBlock = builder.createBlock(builder.getBlock()->getParent()); |
| 102 | + auto inRegBlock = builder.createBlock(builder.getBlock()->getParent()); |
| 103 | + auto onStackBlock = builder.createBlock(builder.getBlock()->getParent()); |
| 104 | + |
| 105 | + //======================================= |
| 106 | + // Find out where argument was passed |
| 107 | + //======================================= |
| 108 | + |
| 109 | + // If v/gr_offs >= 0 we're already using the stack for this type of |
| 110 | + // argument. We don't want to keep updating regOffs (in case it overflows, |
| 111 | + // though anyone passing 2GB of arguments, each at most 16 bytes, deserves |
| 112 | + // whatever they get). |
| 113 | + |
| 114 | + assert(!cir::MissingFeatures::handleAArch64Indirect()); |
| 115 | + assert(!cir::MissingFeatures::supportTySizeQueryForAArch64()); |
| 116 | + assert(!cir::MissingFeatures::supportTyAlignQueryForAArch64()); |
| 117 | + // indirectness, type size and type alignment all |
| 118 | + // decide regSize, but they are all ABI defined |
| 119 | + // thus need ABI lowering query system. |
| 120 | + int regSize = 8; |
| 121 | + int regTopIndex; |
| 122 | + mlir::Value regOffsP; |
| 123 | + mlir::cir::LoadOp regOffs; |
| 124 | + |
| 125 | + builder.restoreInsertionPoint(curInsertionP); |
| 126 | + // 3 is the field number of __gr_offs, 4 is the field number of __vr_offs |
| 127 | + if (!IsFPR) { |
| 128 | + regOffsP = builder.createGetMemberOp(loc, valist, "gr_offs", 3); |
| 129 | + regOffs = builder.create<mlir::cir::LoadOp>(loc, regOffsP); |
| 130 | + regTopIndex = 1; |
| 131 | + regSize = llvm::alignTo(regSize, 8); |
| 132 | + } else { |
| 133 | + regOffsP = builder.createGetMemberOp(loc, valist, "vr_offs", 4); |
| 134 | + regOffs = builder.create<mlir::cir::LoadOp>(loc, regOffsP); |
| 135 | + regTopIndex = 2; |
| 136 | + regSize = 16 * numRegs; |
| 137 | + } |
| 138 | + |
| 139 | + //======================================= |
| 140 | + // Find out where argument was passed |
| 141 | + //======================================= |
| 142 | + |
| 143 | + // If regOffs >= 0 we're already using the stack for this type of |
| 144 | + // argument. We don't want to keep updating regOffs (in case it overflows, |
| 145 | + // though anyone passing 2GB of arguments, each at most 16 bytes, deserves |
| 146 | + // whatever they get). |
| 147 | + auto zeroValue = builder.create<mlir::cir::ConstantOp>( |
| 148 | + loc, regOffs.getType(), mlir::cir::IntAttr::get(regOffs.getType(), 0)); |
| 149 | + auto usingStack = builder.create<mlir::cir::CmpOp>( |
| 150 | + loc, boolTy, mlir::cir::CmpOpKind::ge, regOffs, zeroValue); |
| 151 | + builder.create<mlir::cir::BrCondOp>(loc, usingStack, onStackBlock, |
| 152 | + maybeRegBlock); |
| 153 | + |
| 154 | + auto contBlock = currentBlock->splitBlock(op); |
| 155 | + |
| 156 | + // Otherwise, at least some kind of argument could go in these registers, the |
| 157 | + // question is whether this particular type is too big. |
| 158 | + builder.setInsertionPointToEnd(maybeRegBlock); |
| 159 | + |
| 160 | + // Integer arguments may need to correct register alignment (for example a |
| 161 | + // "struct { __int128 a; };" gets passed in x_2N, x_{2N+1}). In this case we |
| 162 | + // align __gr_offs to calculate the potential address. |
| 163 | + if (!IsFPR) { |
| 164 | + assert(!cir::MissingFeatures::handleAArch64Indirect()); |
| 165 | + assert(!cir::MissingFeatures::supportTyAlignQueryForAArch64()); |
| 166 | + } |
| 167 | + |
| 168 | + // Update the gr_offs/vr_offs pointer for next call to va_arg on this va_list. |
| 169 | + // The fact that this is done unconditionally reflects the fact that |
| 170 | + // allocating an argument to the stack also uses up all the remaining |
| 171 | + // registers of the appropriate kind. |
| 172 | + auto regSizeValue = builder.create<mlir::cir::ConstantOp>( |
| 173 | + loc, regOffs.getType(), |
| 174 | + mlir::cir::IntAttr::get(regOffs.getType(), regSize)); |
| 175 | + auto newOffset = builder.create<mlir::cir::BinOp>( |
| 176 | + loc, regOffs.getType(), mlir::cir::BinOpKind::Add, regOffs, regSizeValue); |
| 177 | + builder.createStore(loc, newOffset, regOffsP); |
| 178 | + // Now we're in a position to decide whether this argument really was in |
| 179 | + // registers or not. |
| 180 | + auto inRegs = builder.create<mlir::cir::CmpOp>( |
| 181 | + loc, boolTy, mlir::cir::CmpOpKind::le, newOffset, zeroValue); |
| 182 | + builder.create<mlir::cir::BrCondOp>(loc, inRegs, inRegBlock, onStackBlock); |
| 183 | + |
| 184 | + //======================================= |
| 185 | + // Argument was in registers |
| 186 | + //======================================= |
| 187 | + // Now we emit the code for if the argument was originally passed in |
| 188 | + // registers. First start the appropriate block: |
| 189 | + builder.setInsertionPointToEnd(inRegBlock); |
| 190 | + auto regTopP = builder.createGetMemberOp( |
| 191 | + loc, valist, IsFPR ? "vr_top" : "gr_top", regTopIndex); |
| 192 | + auto regTop = builder.create<mlir::cir::LoadOp>(loc, regTopP); |
| 193 | + auto i8Ty = mlir::IntegerType::get(builder.getContext(), 8); |
| 194 | + auto i8PtrTy = mlir::cir::PointerType::get(builder.getContext(), i8Ty); |
| 195 | + auto castRegTop = builder.createBitcast(regTop, i8PtrTy); |
| 196 | + // On big-endian platforms, the value will be right-aligned in its stack slot. |
| 197 | + // and we also need to think about other ABI lowering concerns listed below. |
| 198 | + assert(!cir::MissingFeatures::handleBigEndian()); |
| 199 | + assert(!cir::MissingFeatures::handleAArch64Indirect()); |
| 200 | + assert(!cir::MissingFeatures::supportisHomogeneousAggregateQueryForAArch64()); |
| 201 | + assert(!cir::MissingFeatures::supportTySizeQueryForAArch64()); |
| 202 | + assert(!cir::MissingFeatures::supportTyAlignQueryForAArch64()); |
| 203 | + |
| 204 | + auto resAsInt8P = builder.create<mlir::cir::PtrStrideOp>( |
| 205 | + loc, castRegTop.getType(), castRegTop, regOffs); |
| 206 | + auto resAsVoidP = builder.createBitcast(resAsInt8P, regTop.getType()); |
| 207 | + builder.create<mlir::cir::BrOp>(loc, mlir::ValueRange{resAsVoidP}, contBlock); |
| 208 | + |
| 209 | + //======================================= |
| 210 | + // Argument was on the stack |
| 211 | + //======================================= |
| 212 | + builder.setInsertionPointToEnd(onStackBlock); |
| 213 | + auto stackP = builder.createGetMemberOp(loc, valist, "stack", 0); |
| 214 | + |
| 215 | + auto onStackPtr = builder.create<mlir::cir::LoadOp>(loc, stackP); |
| 216 | + auto ptrDiffTy = |
| 217 | + mlir::cir::IntType::get(builder.getContext(), 64, /*signed=*/false); |
| 218 | + |
| 219 | + // On big-endian platforms, the value will be right-aligned in its stack slot |
| 220 | + // Also, the consideration involves type size and alignment, arg indirectness |
| 221 | + // which are all ABI defined thus need ABI lowering query system. |
| 222 | + // The implementation we have now supports most common cases which assumes |
| 223 | + // no indirectness, no alignment greater than 8, and little endian. |
| 224 | + assert(!cir::MissingFeatures::handleBigEndian()); |
| 225 | + assert(!cir::MissingFeatures::handleAArch64Indirect()); |
| 226 | + assert(!cir::MissingFeatures::supportTyAlignQueryForAArch64()); |
| 227 | + assert(!cir::MissingFeatures::supportTySizeQueryForAArch64()); |
| 228 | + |
| 229 | + auto eight = builder.create<mlir::cir::ConstantOp>( |
| 230 | + loc, ptrDiffTy, mlir::cir::IntAttr::get(ptrDiffTy, 8)); |
| 231 | + auto castStack = builder.createBitcast(onStackPtr, i8PtrTy); |
| 232 | + // Write the new value of __stack for the next call to va_arg |
| 233 | + auto newStackAsi8Ptr = builder.create<mlir::cir::PtrStrideOp>( |
| 234 | + loc, castStack.getType(), castStack, eight); |
| 235 | + auto newStack = builder.createBitcast(newStackAsi8Ptr, onStackPtr.getType()); |
| 236 | + builder.createStore(loc, newStack, stackP); |
| 237 | + builder.create<mlir::cir::BrOp>(loc, mlir::ValueRange{onStackPtr}, contBlock); |
| 238 | + |
| 239 | + // generate additional instructions for end block |
| 240 | + builder.setInsertionPoint(op); |
| 241 | + contBlock->addArgument(onStackPtr.getType(), loc); |
| 242 | + auto resP = contBlock->getArgument(0); |
| 243 | + assert(resP.getType().isa<mlir::cir::PointerType>()); |
| 244 | + auto opResPTy = mlir::cir::PointerType::get(builder.getContext(), opResTy); |
| 245 | + auto castResP = builder.createBitcast(resP, opResPTy); |
| 246 | + auto res = builder.create<mlir::cir::LoadOp>(loc, castResP); |
| 247 | + // there would be another level of ptr dereference if indirect arg passing |
| 248 | + assert(!cir::MissingFeatures::handleAArch64Indirect()); |
| 249 | + return res.getResult(); |
| 250 | +} |
| 251 | + |
| 252 | +mlir::Value |
| 253 | +LoweringPrepareAArch64CXXABI::lowerVAArg(cir::CIRBaseBuilderTy &builder, |
| 254 | + mlir::cir::VAArgOp op) { |
| 255 | + return Kind == AArch64ABIKind::Win64 ? lowerMSVAArg(builder, op) |
| 256 | + : isDarwinPCS() ? lowerDarwinVAArg(builder, op) |
| 257 | + : lowerAAPCSVAArg(builder, op); |
| 258 | +} |
0 commit comments