[clang] 3ff83ec - [AArch64] Add __hvc and __svc MS intrinsics (#202582)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 11 10:46:59 PDT 2026
Author: Adhemerval Zanella
Date: 2026-08-11T10:46:52-07:00
New Revision: 3ff83ecc5e0963124c525d662ae30fc464d3c4ce
URL: https://github.com/llvm/llvm-project/commit/3ff83ecc5e0963124c525d662ae30fc464d3c4ce
DIFF: https://github.com/llvm/llvm-project/commit/3ff83ecc5e0963124c525d662ae30fc464d3c4ce.diff
LOG: [AArch64] Add __hvc and __svc MS intrinsics (#202582)
This series implements two additional Microsoft `intrin.h` intrinsics in
clang so MSVC-compatible code can build with clang.
| Target | Intrinsic | Description |
|--------|-----------|-------------|
| AArch64 | `__svc` | Supervisor Call (`SVC #imm`) |
| AArch64 | `__hvc` | Hypervisor Call (`HVC #imm`) |
`__hvc` and `__svc` each require a new LLVM intrinsic
(`llvm.aarch64.hvc` / `llvm.aarch64.svc`) to carry the immediate operand
down to instruction selection. The 16-bit immediate is encoded in the
instruction, up to four additional arguments are passed in X0-X3, and the
result is read back from X0. It should match MSVC calling convention.
Added:
llvm/test/CodeGen/AArch64/arm64-hvc.ll
llvm/test/CodeGen/AArch64/arm64-svc.ll
Modified:
clang/include/clang/Basic/BuiltinsAArch64.td
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/lib/CodeGen/TargetBuiltins/ARM.cpp
clang/lib/Headers/intrin.h
clang/lib/Sema/SemaARM.cpp
clang/test/CodeGen/arm64-microsoft-intrinsics.c
clang/test/Sema/builtins-microsoft-arm64.c
llvm/include/llvm/IR/IntrinsicsAArch64.td
llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
llvm/lib/Target/AArch64/AArch64InstrInfo.td
llvm/test/tools/llvm-mca/AArch64/Neoverse/V1-misc-instructions.s
Removed:
################################################################################
diff --git a/clang/include/clang/Basic/BuiltinsAArch64.td b/clang/include/clang/Basic/BuiltinsAArch64.td
index 15257f3db5b41..4c49c874133fb 100644
--- a/clang/include/clang/Basic/BuiltinsAArch64.td
+++ b/clang/include/clang/Basic/BuiltinsAArch64.td
@@ -408,4 +408,6 @@ let Attributes = [NoThrow, RequireDeclaration], Languages = "ALL_MS_LANGUAGES",
let Attributes = [NoThrow, RequireDeclaration], Languages = "ALL_MS_LANGUAGES", Header = "intrin.h" in {
def __hlt : AArch64NoPrefixTargetLibBuiltin<"unsigned int (unsigned int, ...)">;
+ def __hvc : AArch64NoPrefixTargetLibBuiltin<"unsigned int (unsigned int, ...)">;
+ def __svc : AArch64NoPrefixTargetLibBuiltin<"unsigned int (unsigned int, ...)">;
}
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8b0e322fd4885..fc30db9f16887 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -9646,6 +9646,9 @@ def err_atomic_builtin_cannot_be_const : Error<
def err_atomic_builtin_must_be_pointer_intfltptr : Error<
"address argument to atomic builtin must be a pointer to integer,"
" floating-point or pointer (%0 invalid)">;
+def err_aarch64_svc_hvc_invalid_arg_type : Error<
+ "%ordinal0 argument to %1 must have integer, floating-point, or pointer "
+ "type (%2 invalid)">;
def err_atomic_builtin_pointer_size : Error<
"address argument to atomic builtin must be a pointer to 1,2,4,8 or 16 byte "
"type (%0 invalid)">;
diff --git a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
index f7c37704d908d..45b0f3d0143e5 100644
--- a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
@@ -5258,11 +5258,46 @@ Value *CodeGenFunction::EmitAArch64BuiltinExpr(unsigned BuiltinID,
Function *F = CGM.getIntrinsic(Intrinsic::aarch64_hlt);
Builder.CreateCall(F, {EmitScalarExpr(E->getArg(0))});
- // Return 0 for convenience, even though MSVC returns some other undefined
- // value.
+ // FIXME: MSVC documents __hlt as taking further arguments in X0-X3 and
+ // returning the value in X0, like __hvc/__svc below. This ignores the
+ // extra arguments and returns 0.
return ConstantInt::get(Builder.getInt32Ty(), 0);
}
+ if (BuiltinID == AArch64::BI__hvc || BuiltinID == AArch64::BI__svc) {
+ unsigned IID = BuiltinID == AArch64::BI__svc ? Intrinsic::aarch64_svc
+ : Intrinsic::aarch64_hvc;
+ // The first argument is the instruction immediate; it must be a constant
+ // (ImmArg on the intrinsic, encoded in the instruction). The remaining
+ // arguments (at most four, enforced by Sema) are passed in X0-X3, widened
+ // to 64 bits. The intrinsic takes exactly four register operands, so any
+ // unused trailing ones are passed as poison and dropped during lowering.
+ SmallVector<Value *, 5> Args{Builder.getInt32(
+ GetIntegerConstantValue<uint32_t>(E->getArg(0), getContext()))};
+ for (unsigned I = 1, N = E->getNumArgs(); I < N; ++I) {
+ Value *Arg = EmitScalarExpr(E->getArg(I));
+ llvm::Type *ArgTy = Arg->getType();
+ if (ArgTy->isPointerTy())
+ Arg = Builder.CreatePtrToInt(Arg, Int64Ty);
+ else if (ArgTy->isFloatingPointTy())
+ // Reinterpret the bits into the integer register, matching MSVC (e.g.
+ // "fmov x0, d0" for a double).
+ Arg = Builder.CreateZExtOrTrunc(
+ Builder.CreateBitCast(
+ Arg, Builder.getIntNTy(ArgTy->getPrimitiveSizeInBits())),
+ Int64Ty);
+ else
+ Arg = Builder.CreateIntCast(
+ Arg, Int64Ty, E->getArg(I)->getType()->isSignedIntegerType());
+ Args.push_back(Arg);
+ }
+ while (Args.size() < 5)
+ Args.push_back(llvm::PoisonValue::get(Int64Ty));
+ Value *Call = Builder.CreateCall(CGM.getIntrinsic(IID), Args);
+ // MSVC returns unsigned int, i.e. the low 32 bits of the X0 result.
+ return Builder.CreateTrunc(Call, Int32Ty);
+ }
+
if (BuiltinID == NEON::BI__builtin_neon_vcvth_bf16_f32)
return Builder.CreateFPTrunc(
Builder.CreateBitCast(EmitScalarExpr(E->getArg(0)),
diff --git a/clang/lib/Headers/intrin.h b/clang/lib/Headers/intrin.h
index 4cb8cac960bcf..7dcb5a526afb2 100644
--- a/clang/lib/Headers/intrin.h
+++ b/clang/lib/Headers/intrin.h
@@ -448,6 +448,8 @@ unsigned int _CountTrailingZeros(unsigned long);
unsigned int _CountTrailingZeros64(unsigned __int64);
unsigned int __hlt(unsigned int, ...);
+unsigned int __hvc(unsigned int, ...);
+unsigned int __svc(unsigned int, ...);
void __cdecl __prefetch(const void *);
void __cdecl __prefetch2(const void *, unsigned char);
diff --git a/clang/lib/Sema/SemaARM.cpp b/clang/lib/Sema/SemaARM.cpp
index f549016bb17ae..f38a35ec08fb0 100644
--- a/clang/lib/Sema/SemaARM.cpp
+++ b/clang/lib/Sema/SemaARM.cpp
@@ -1193,6 +1193,25 @@ bool SemaARM::CheckAArch64BuiltinFunctionCall(const TargetInfo &TI,
if (BuiltinID == AArch64::BI__hlt)
return SemaRef.BuiltinConstantArgRange(TheCall, 0, 0, 0xffff);
+ if (BuiltinID == AArch64::BI__hvc || BuiltinID == AArch64::BI__svc) {
+ // The immediate is the instruction number; the remaining arguments (at most
+ // four) are passed in X0-X3, so the call takes at most five arguments.
+ if (SemaRef.checkArgCountAtMost(TheCall, 5) ||
+ SemaRef.BuiltinConstantArgRange(TheCall, 0, 0, 0xffff))
+ return true;
+ const FunctionDecl *FD = TheCall->getDirectCallee();
+ for (unsigned I = 1, N = TheCall->getNumArgs(); I < N; ++I) {
+ const Expr *Arg = TheCall->getArg(I);
+ QualType Ty = Arg->getType();
+ if (!Ty->isIntegerType() && !Ty->isAnyPointerType() &&
+ !Ty->isBlockPointerType() && !Ty->isFloatingType())
+ return Diag(Arg->getBeginLoc(),
+ diag::err_aarch64_svc_hvc_invalid_arg_type)
+ << I + 1 << FD << Ty << Arg->getSourceRange();
+ }
+ return false;
+ }
+
if (CheckNeonBuiltinFunctionCall(TI, BuiltinID, TheCall))
return true;
diff --git a/clang/test/CodeGen/arm64-microsoft-intrinsics.c b/clang/test/CodeGen/arm64-microsoft-intrinsics.c
index e6a415a0d8805..bf2dcf5ce2afd 100644
--- a/clang/test/CodeGen/arm64-microsoft-intrinsics.c
+++ b/clang/test/CodeGen/arm64-microsoft-intrinsics.c
@@ -147,6 +147,32 @@ void check__hlt() {
// CHECK-MSVC: call void @llvm.aarch64.hlt(i32 0)
// CHECK-LINUX: error: call to undeclared function '__hlt'
+unsigned int check__hvc(unsigned int a, unsigned int b) {
+ __hvc(0);
+ return __hvc(1, a, b);
+}
+
+// CHECK-MSVC-LABEL: define {{.*}} i32 @check__hvc(
+// CHECK-MSVC: call i64 @llvm.aarch64.hvc(i32 0, i64 poison, i64 poison, i64 poison, i64 poison)
+// CHECK-MSVC: [[HVC_A:%.*]] = zext i32 {{.*}} to i64
+// CHECK-MSVC: [[HVC_B:%.*]] = zext i32 {{.*}} to i64
+// CHECK-MSVC: [[HVC_R:%.*]] = call i64 @llvm.aarch64.hvc(i32 1, i64 [[HVC_A]], i64 [[HVC_B]], i64 poison, i64 poison)
+// CHECK-MSVC: trunc i64 [[HVC_R]] to i32
+// CHECK-LINUX: error: call to undeclared function '__hvc'
+
+unsigned int check__svc(unsigned int a, unsigned int b) {
+ __svc(0);
+ return __svc(1, a, b);
+}
+
+// CHECK-MSVC-LABEL: define {{.*}} i32 @check__svc(
+// CHECK-MSVC: call i64 @llvm.aarch64.svc(i32 0, i64 poison, i64 poison, i64 poison, i64 poison)
+// CHECK-MSVC: [[SVC_A:%.*]] = zext i32 {{.*}} to i64
+// CHECK-MSVC: [[SVC_B:%.*]] = zext i32 {{.*}} to i64
+// CHECK-MSVC: [[SVC_R:%.*]] = call i64 @llvm.aarch64.svc(i32 1, i64 [[SVC_A]], i64 [[SVC_B]], i64 poison, i64 poison)
+// CHECK-MSVC: trunc i64 [[SVC_R]] to i32
+// CHECK-LINUX: error: call to undeclared function '__svc'
+
unsigned __int64 check__getReg(void) {
unsigned volatile __int64 reg;
reg = __getReg(18);
diff --git a/clang/test/Sema/builtins-microsoft-arm64.c b/clang/test/Sema/builtins-microsoft-arm64.c
index 22163ab3fa851..2093adbb349a4 100644
--- a/clang/test/Sema/builtins-microsoft-arm64.c
+++ b/clang/test/Sema/builtins-microsoft-arm64.c
@@ -14,6 +14,28 @@ void check__hlt() {
__hlt(65536); // expected-error-re {{argument value {{.*}} is outside the valid range}}
}
+struct NonScalar { int a, b; };
+
+void check__hvc(unsigned int x, double d, void *p, struct NonScalar s) {
+ __hvc(-1); // expected-error-re {{argument value {{.*}} is outside the valid range}}
+ __hvc(65536); // expected-error-re {{argument value {{.*}} is outside the valid range}}
+ __hvc(x); // expected-error {{argument to '__hvc' must be a constant integer}}
+ __hvc(1, 2, 3, 4, 5); // no-error: immediate plus four register arguments
+ __hvc(1, 2, 3, 4, 5, 6); // expected-error {{too many arguments to function call, expected at most 5, have 6}}
+ __hvc(1, d, p, x); // no-error: floating-point, pointer and integer arguments
+ __hvc(1, s); // expected-error {{2nd argument to '__hvc' must have integer, floating-point, or pointer type}}
+}
+
+void check__svc(unsigned int x, double d, void *p, struct NonScalar s) {
+ __svc(-1); // expected-error-re {{argument value {{.*}} is outside the valid range}}
+ __svc(65536); // expected-error-re {{argument value {{.*}} is outside the valid range}}
+ __svc(x); // expected-error {{argument to '__svc' must be a constant integer}}
+ __svc(1, 2, 3, 4, 5); // no-error: immediate plus four register arguments
+ __svc(1, 2, 3, 4, 5, 6); // expected-error {{too many arguments to function call, expected at most 5, have 6}}
+ __svc(1, d, p, x); // no-error: floating-point, pointer and integer arguments
+ __svc(1, s); // expected-error {{2nd argument to '__svc' must have integer, floating-point, or pointer type}}
+}
+
void check__getReg(void) {
__getReg(-1); // expected-error-re {{argument value {{.*}} is outside the valid range}}
__getReg(32); // expected-error-re {{argument value {{.*}} is outside the valid range}}
diff --git a/llvm/include/llvm/IR/IntrinsicsAArch64.td b/llvm/include/llvm/IR/IntrinsicsAArch64.td
index 29f9742532d33..074f26cad2132 100644
--- a/llvm/include/llvm/IR/IntrinsicsAArch64.td
+++ b/llvm/include/llvm/IR/IntrinsicsAArch64.td
@@ -72,6 +72,19 @@ def int_aarch64_break : Intrinsic<[], [llvm_i32_ty],
def int_aarch64_hlt : Intrinsic<[], [llvm_i32_ty],
[IntrNoMem, IntrHasSideEffects, IntrNoReturn, IntrCold, ImmArg<ArgIndex<0>>]>;
+// The first operand is the 16-bit instruction immediate. The remaining four
+// operands are the values passed in X0-X3 following the Microsoft __hvc calling
+// convention (this is a Microsoft software convention, not an architectural
+// requirement); an unused argument is passed as poison. The result is the
+// value left in X0.
+def int_aarch64_hvc : Intrinsic<[llvm_i64_ty],
+ [llvm_i32_ty, llvm_i64_ty, llvm_i64_ty, llvm_i64_ty, llvm_i64_ty],
+ [IntrHasSideEffects, ImmArg<ArgIndex<0>>]>;
+
+def int_aarch64_svc : Intrinsic<[llvm_i64_ty],
+ [llvm_i32_ty, llvm_i64_ty, llvm_i64_ty, llvm_i64_ty, llvm_i64_ty],
+ [IntrHasSideEffects, ImmArg<ArgIndex<0>>]>;
+
def int_aarch64_prefetch : Intrinsic<[],
[llvm_ptr_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty],
[IntrInaccessibleMemOrArgMemOnly, IntrWillReturn, ReadOnly<ArgIndex<0>>,
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 6003db1c72449..dbdbb733fd64a 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -1616,9 +1616,7 @@ AArch64TargetLowering::AArch64TargetLowering(const TargetMachine &TM,
setOperationAction(ISD::TRUNCATE_USAT_U, VT, Legal);
}
- if (Subtarget->hasSME()) {
- setOperationAction(ISD::INTRINSIC_W_CHAIN, MVT::Other, Custom);
- }
+ setOperationAction(ISD::INTRINSIC_W_CHAIN, MVT::Other, Custom);
// FIXME: Move lowering for more nodes here if those are common between
// SVE and SME.
@@ -6616,6 +6614,47 @@ SDValue AArch64TargetLowering::LowerINTRINSIC_W_CHAIN(SDValue Op,
switch (IntNo) {
default:
return SDValue(); // Don't custom lower most intrinsics.
+ case Intrinsic::aarch64_svc:
+ case Intrinsic::aarch64_hvc: {
+ // The MSVC __svc/__hvc intrinsic takes the 16-bit instruction immediate as
+ // their first operand and four further operands passed in X0-X3 (an unused
+ // argument is passed as poison) and returns the value left in X0. Matching
+ // MSVC, the instruction is not treated as clobbering the caller-saved
+ // registers; only X0 (the result) is defined.
+ SDValue Chain = Op.getOperand(0);
+ unsigned Imm = Op.getConstantOperandVal(2);
+
+ static const MCPhysReg ArgGPRs[] = {AArch64::X0, AArch64::X1, AArch64::X2,
+ AArch64::X3};
+
+ SDValue Glue;
+ SmallVector<SDValue, 4> RegOps;
+ for (unsigned I = 0; I < std::size(ArgGPRs); ++I) {
+ SDValue Arg = Op.getOperand(3 + I);
+ if (Arg.isUndef())
+ continue;
+ Chain = DAG.getCopyToReg(Chain, DL, ArgGPRs[I], Arg, Glue);
+ Glue = Chain.getValue(1);
+ RegOps.push_back(DAG.getRegister(ArgGPRs[I], MVT::i64));
+ }
+
+ SmallVector<SDValue, 8> Ops;
+ Ops.push_back(Chain);
+ Ops.push_back(DAG.getTargetConstant(Imm, DL, MVT::i32));
+ Ops.append(RegOps.begin(), RegOps.end());
+ if (Glue.getNode())
+ Ops.push_back(Glue);
+
+ unsigned Opc =
+ IntNo == Intrinsic::aarch64_svc ? AArch64ISD::SVC : AArch64ISD::HVC;
+ SDValue Node =
+ DAG.getNode(Opc, DL, DAG.getVTList(MVT::Other, MVT::Glue), Ops);
+ Chain = Node.getValue(0);
+ Glue = Node.getValue(1);
+
+ SDValue Result = DAG.getCopyFromReg(Chain, DL, AArch64::X0, MVT::i64, Glue);
+ return DAG.getMergeValues({Result.getValue(0), Result.getValue(1)}, DL);
+ }
case Intrinsic::aarch64_mops_memset_tag: {
auto Node = cast<MemIntrinsicSDNode>(Op.getNode());
SDValue Chain = Node->getChain();
diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.td b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
index da4ae5bc29675..22f4b2b2e138c 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -3802,6 +3802,17 @@ def : Pat<(AArch64call texternalsym:$func), (BL texternalsym:$func)>;
//===----------------------------------------------------------------------===//
// Exception generation instructions.
//===----------------------------------------------------------------------===//
+
+// Nodes for the HVC/SVC exception-generating calls. The single operand is the
+// 16-bit instruction immediate; the argument registers, clobber mask and glue
+// are attached as variadic operands during lowering.
+def AArch64hvc : SDNode<"AArch64ISD::HVC", SDTypeProfile<0, 1, [SDTCisInt<0>]>,
+ [SDNPHasChain, SDNPOptInGlue, SDNPOutGlue, SDNPVariadic,
+ SDNPMayLoad, SDNPMayStore]>;
+def AArch64svc : SDNode<"AArch64ISD::SVC", SDTypeProfile<0, 1, [SDTCisInt<0>]>,
+ [SDNPHasChain, SDNPOptInGlue, SDNPOutGlue, SDNPVariadic,
+ SDNPMayLoad, SDNPMayStore]>;
+
let isTrap = 1 in {
def BRK : ExceptionGeneration<0b001, 0b00, "brk",
[(int_aarch64_break timm32_0_65535:$imm)]>;
@@ -3811,9 +3822,18 @@ def DCPS2 : ExceptionGeneration<0b101, 0b10, "dcps2">;
def DCPS3 : ExceptionGeneration<0b101, 0b11, "dcps3">, Requires<[HasEL3]>;
def HLT : ExceptionGeneration<0b010, 0b00, "hlt",
[(int_aarch64_hlt timm32_0_65535:$imm)]>;
+// Following the Microsoft __hvc/__svc calling convention (a software
+// convention, not an architectural rule), HVC and SVC pass up to four arguments
+// in X0-X3 and return a value in X0. They are therefore selected from the
+// AArch64hvc/AArch64svc nodes (built by LowerINTRINSIC_W_CHAIN) rather than a
+// plain intrinsic pattern, and define X0 for the result.
+let mayLoad = 1, mayStore = 1, Defs = [X0] in
def HVC : ExceptionGeneration<0b000, 0b10, "hvc">;
def SMC : ExceptionGeneration<0b000, 0b11, "smc">, Requires<[HasEL3]>;
+let mayLoad = 1, mayStore = 1, Defs = [X0] in
def SVC : ExceptionGeneration<0b000, 0b01, "svc">;
+def : Pat<(AArch64hvc timm32_0_65535:$imm), (HVC timm32_0_65535:$imm)>;
+def : Pat<(AArch64svc timm32_0_65535:$imm), (SVC timm32_0_65535:$imm)>;
// DCPSn defaults to an immediate operand of zero if unspecified.
def : InstAlias<"dcps1", (DCPS1 0)>;
diff --git a/llvm/test/CodeGen/AArch64/arm64-hvc.ll b/llvm/test/CodeGen/AArch64/arm64-hvc.ll
new file mode 100644
index 0000000000000..5b931c2dcc112
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/arm64-hvc.ll
@@ -0,0 +1,78 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=aarch64-none-eabi | FileCheck %s
+
+declare i64 @llvm.aarch64.hvc(i32 immarg, i64, i64, i64, i64) nounwind
+declare i64 @g() nounwind
+
+; No register arguments; the immediate is encoded in the instruction.
+define i64 @hvc_no_args() nounwind {
+; CHECK-LABEL: hvc_no_args:
+; CHECK: // %bb.0:
+; CHECK-NEXT: hvc #0x2
+; CHECK-NEXT: ret
+ %r = call i64 @llvm.aarch64.hvc(i32 2, i64 poison, i64 poison, i64 poison, i64 poison)
+ ret i64 %r
+}
+
+; Register arguments are passed in X0-X3 (here swapped to force the moves),
+; and the result is read back from X0.
+define i64 @hvc_two_args(i64 %a, i64 %b) nounwind {
+; CHECK-LABEL: hvc_two_args:
+; CHECK: // %bb.0:
+; CHECK-NEXT: mov x8, x0
+; CHECK-NEXT: mov x0, x1
+; CHECK-NEXT: mov x1, x8
+; CHECK-NEXT: hvc #0x1
+; CHECK-NEXT: ret
+ %r = call i64 @llvm.aarch64.hvc(i32 1, i64 %b, i64 %a, i64 poison, i64 poison)
+ ret i64 %r
+}
+
+; Up to four register arguments.
+define i64 @hvc_four_args(i64 %a, i64 %b, i64 %c, i64 %d) nounwind {
+; CHECK-LABEL: hvc_four_args:
+; CHECK: // %bb.0:
+; CHECK-NEXT: hvc #0x7
+; CHECK-NEXT: ret
+ %r = call i64 @llvm.aarch64.hvc(i32 7, i64 %a, i64 %b, i64 %c, i64 %d)
+ ret i64 %r
+}
+
+; The argument side effects must be evaluated and the result kept live across
+; the following call (the hvc clobbers the caller-saved registers).
+define i64 @hvc_side_effects() nounwind {
+; CHECK-LABEL: hvc_side_effects:
+; CHECK: // %bb.0:
+; CHECK-NEXT: stp x30, x19, [sp, #-16]! // 16-byte Folded Spill
+; CHECK-NEXT: bl g
+; CHECK-NEXT: mov x19, x0
+; CHECK-NEXT: bl g
+; CHECK-NEXT: mov x1, x0
+; CHECK-NEXT: mov x0, x19
+; CHECK-NEXT: hvc #0x3
+; CHECK-NEXT: mov x19, x0
+; CHECK-NEXT: bl g
+; CHECK-NEXT: mov x0, x19
+; CHECK-NEXT: ldp x30, x19, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: ret
+ %x = call i64 @g()
+ %y = call i64 @g()
+ %r = call i64 @llvm.aarch64.hvc(i32 3, i64 %x, i64 %y, i64 poison, i64 poison)
+ call i64 @g()
+ ret i64 %r
+}
+
+; Registers other than X0 are preserved across the call: matching MSVC, the
+; intrinsic does not clobber the caller-saved registers.
+define i64 @hvc_preserves_regs(i64 %a, i64 %b, i64 %c) nounwind {
+; CHECK-LABEL: hvc_preserves_regs:
+; CHECK: // %bb.0:
+; CHECK-NEXT: hvc #0x9
+; CHECK-NEXT: add x8, x1, x2
+; CHECK-NEXT: add x0, x8, x0
+; CHECK-NEXT: ret
+ %r = call i64 @llvm.aarch64.hvc(i32 9, i64 poison, i64 poison, i64 poison, i64 poison)
+ %t0 = add i64 %b, %c
+ %t1 = add i64 %t0, %r
+ ret i64 %t1
+}
diff --git a/llvm/test/CodeGen/AArch64/arm64-svc.ll b/llvm/test/CodeGen/AArch64/arm64-svc.ll
new file mode 100644
index 0000000000000..9dbba8e75dfeb
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/arm64-svc.ll
@@ -0,0 +1,78 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=aarch64-none-eabi | FileCheck %s
+
+declare i64 @llvm.aarch64.svc(i32 immarg, i64, i64, i64, i64) nounwind
+declare i64 @g() nounwind
+
+; No register arguments; the immediate is encoded in the instruction.
+define i64 @svc_no_args() nounwind {
+; CHECK-LABEL: svc_no_args:
+; CHECK: // %bb.0:
+; CHECK-NEXT: svc #0x2
+; CHECK-NEXT: ret
+ %r = call i64 @llvm.aarch64.svc(i32 2, i64 poison, i64 poison, i64 poison, i64 poison)
+ ret i64 %r
+}
+
+; Register arguments are passed in X0-X3 (here swapped to force the moves),
+; and the result is read back from X0.
+define i64 @svc_two_args(i64 %a, i64 %b) nounwind {
+; CHECK-LABEL: svc_two_args:
+; CHECK: // %bb.0:
+; CHECK-NEXT: mov x8, x0
+; CHECK-NEXT: mov x0, x1
+; CHECK-NEXT: mov x1, x8
+; CHECK-NEXT: svc #0x1
+; CHECK-NEXT: ret
+ %r = call i64 @llvm.aarch64.svc(i32 1, i64 %b, i64 %a, i64 poison, i64 poison)
+ ret i64 %r
+}
+
+; Up to four register arguments.
+define i64 @svc_four_args(i64 %a, i64 %b, i64 %c, i64 %d) nounwind {
+; CHECK-LABEL: svc_four_args:
+; CHECK: // %bb.0:
+; CHECK-NEXT: svc #0x7
+; CHECK-NEXT: ret
+ %r = call i64 @llvm.aarch64.svc(i32 7, i64 %a, i64 %b, i64 %c, i64 %d)
+ ret i64 %r
+}
+
+; The argument side effects must be evaluated and the result kept live across
+; the following call (the svc clobbers the caller-saved registers).
+define i64 @svc_side_effects() nounwind {
+; CHECK-LABEL: svc_side_effects:
+; CHECK: // %bb.0:
+; CHECK-NEXT: stp x30, x19, [sp, #-16]! // 16-byte Folded Spill
+; CHECK-NEXT: bl g
+; CHECK-NEXT: mov x19, x0
+; CHECK-NEXT: bl g
+; CHECK-NEXT: mov x1, x0
+; CHECK-NEXT: mov x0, x19
+; CHECK-NEXT: svc #0x3
+; CHECK-NEXT: mov x19, x0
+; CHECK-NEXT: bl g
+; CHECK-NEXT: mov x0, x19
+; CHECK-NEXT: ldp x30, x19, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: ret
+ %x = call i64 @g()
+ %y = call i64 @g()
+ %r = call i64 @llvm.aarch64.svc(i32 3, i64 %x, i64 %y, i64 poison, i64 poison)
+ call i64 @g()
+ ret i64 %r
+}
+
+; Registers other than X0 are preserved across the call: matching MSVC, the
+; intrinsic does not clobber the caller-saved registers.
+define i64 @svc_preserves_regs(i64 %a, i64 %b, i64 %c) nounwind {
+; CHECK-LABEL: svc_preserves_regs:
+; CHECK: // %bb.0:
+; CHECK-NEXT: svc #0x9
+; CHECK-NEXT: add x8, x1, x2
+; CHECK-NEXT: add x0, x8, x0
+; CHECK-NEXT: ret
+ %r = call i64 @llvm.aarch64.svc(i32 9, i64 poison, i64 poison, i64 poison, i64 poison)
+ %t0 = add i64 %b, %c
+ %t1 = add i64 %t0, %r
+ ret i64 %t1
+}
diff --git a/llvm/test/tools/llvm-mca/AArch64/Neoverse/V1-misc-instructions.s b/llvm/test/tools/llvm-mca/AArch64/Neoverse/V1-misc-instructions.s
index 37975ab269d10..fc2710ae216bd 100644
--- a/llvm/test/tools/llvm-mca/AArch64/Neoverse/V1-misc-instructions.s
+++ b/llvm/test/tools/llvm-mca/AArch64/Neoverse/V1-misc-instructions.s
@@ -39,11 +39,11 @@ sysl x16, #5, c11, c8, #5
# CHECK-NEXT: 1 1 0.13 U dcps3
# CHECK-NEXT: 1 1 0.13 * * U dmb sy
# CHECK-NEXT: 1 1 0.13 U hlt #0x7a67
-# CHECK-NEXT: 1 1 0.13 U hvc #0xecb9
+# CHECK-NEXT: 1 1 0.13 * * U hvc #0xecb9
# CHECK-NEXT: 1 1 0.13 * * U isb
# CHECK-NEXT: 1 1 0.13 * * U pssbb
# CHECK-NEXT: 1 1 0.13 U smc #0x7e57
-# CHECK-NEXT: 1 1 0.13 U svc #0x89cb
+# CHECK-NEXT: 1 1 0.13 * * U svc #0x89cb
# CHECK-NEXT: 1 1 0.13 U sysl x16, #5, c11, c8, #5
# CHECK: Resources:
More information about the cfe-commits
mailing list