https://github.com/zatrazz updated 
https://github.com/llvm/llvm-project/pull/202582

>From 639b612cc48b7a6843e69b8320028915432b74f6 Mon Sep 17 00:00:00 2001
From: Adhemerval Zanella <[email protected]>
Date: Thu, 4 Jun 2026 15:24:16 -0300
Subject: [PATCH 1/4] [AArch64] Add llvm.aarch64.hvc intrinsic

Add a new LLVM intrinsic llvm.aarch64.hvc alongside the existing
llvm.aarch64.break and llvm.aarch64.hlt, wired to the HVC (Hypervisor Call)
instruction in the AArch64 backend.

The intrinsic models the MSVC __hvc semantics: the first operand is the
16-bit instruction immediate, followed by up to four register arguments
passed in X0-X3, and it returns the value left in X0. It is custom-lowered
in LowerINTRINSIC_W_CHAIN, which copies the arguments into X0-X3, emits an
AArch64ISD::HVC node with a caller-saved clobber mask, and reads the result
back from X0. Unlike hlt/break it is not IntrNoReturn, since the hypervisor
can return control to the caller, and it may read and write memory.
---
 llvm/include/llvm/IR/IntrinsicsAArch64.td     |  7 +++
 .../Target/AArch64/AArch64ISelLowering.cpp    | 49 ++++++++++++++-
 llvm/lib/Target/AArch64/AArch64InstrInfo.td   | 14 +++++
 llvm/test/CodeGen/AArch64/arm64-hvc.ll        | 63 +++++++++++++++++++
 .../AArch64/Neoverse/V1-misc-instructions.s   |  2 +-
 5 files changed, 131 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/arm64-hvc.ll

diff --git a/llvm/include/llvm/IR/IntrinsicsAArch64.td 
b/llvm/include/llvm/IR/IntrinsicsAArch64.td
index 6078e1438ceee..60d217df36776 100644
--- a/llvm/include/llvm/IR/IntrinsicsAArch64.td
+++ b/llvm/include/llvm/IR/IntrinsicsAArch64.td
@@ -72,6 +72,13 @@ 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 (an unused argument is passed as
+// undef).  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_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 7df8aa045dbf9..d8423ae182351 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -1628,9 +1628,10 @@ AArch64TargetLowering::AArch64TargetLowering(const 
TargetMachine &TM,
     setOperationAction(ISD::TRUNCATE_USAT_U, VT, Legal);
   }
 
-  if (Subtarget->hasSME()) {
-    setOperationAction(ISD::INTRINSIC_W_CHAIN, MVT::Other, Custom);
-  }
+  // Custom lower the __hvc/__svc MSVC intrinsics (and, for SME, the MOPS
+  // tagging intrinsics).  The dispatch for INTRINSIC_W_CHAIN uses the chain
+  // (MVT::Other) result type.
+  setOperationAction(ISD::INTRINSIC_W_CHAIN, MVT::Other, Custom);
 
   // FIXME: Move lowering for more nodes here if those are common between
   // SVE and SME.
@@ -6576,6 +6577,48 @@ SDValue 
AArch64TargetLowering::LowerINTRINSIC_W_CHAIN(SDValue Op,
   switch (IntNo) {
   default:
     return SDValue(); // Don't custom lower most intrinsics.
+  case Intrinsic::aarch64_hvc: {
+    // The MSVC __hvc intrinsic takes the 16-bit instruction immediate as its
+    // first operand and four further operands passed in X0-X3 (an unused
+    // argument is passed as undef); it returns the value left in X0.  Model 
the
+    // sequence as a small call: copy the provided arguments into the argument
+    // registers, emit the HVC node with a caller-saved clobber mask, then read
+    // the result back from X0.
+    SDValue Chain = Op.getOperand(0);
+    unsigned Imm = Op.getConstantOperandVal(2);
+
+    static const MCPhysReg ArgGPRs[] = {AArch64::X0, AArch64::X1, AArch64::X2,
+                                        AArch64::X3};
+    const AArch64RegisterInfo *TRI = Subtarget->getRegisterInfo();
+
+    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());
+    Ops.push_back(DAG.getRegisterMask(
+        TRI->getCallPreservedMask(DAG.getMachineFunction(), CallingConv::C)));
+    if (Glue.getNode())
+      Ops.push_back(Glue);
+
+    SDValue Node = DAG.getNode(AArch64ISD::HVC, 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 a7e97f23fa62b..8f227153d3e8a 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -3759,6 +3759,14 @@ def : Pat<(AArch64call texternalsym:$func), (BL 
texternalsym:$func)>;
 
//===----------------------------------------------------------------------===//
 // Exception generation instructions.
 
//===----------------------------------------------------------------------===//
+
+// Node for the HVC exception-generating call.  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]>;
+
 let isTrap = 1 in {
 def BRK   : ExceptionGeneration<0b001, 0b00, "brk",
                                 [(int_aarch64_break timm32_0_65535:$imm)]>;
@@ -3768,9 +3776,15 @@ 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)]>;
+// HVC passes up to four arguments in X0-X3 and returns a value in X0, so it is
+// selected from the AArch64hvc node (built by LowerINTRINSIC_W_CHAIN) rather
+// than a plain intrinsic pattern.  It clobbers the caller-saved registers via 
a
+// regmask attached to the node.
+let mayLoad = 1, mayStore = 1, Defs = [X0] in
 def HVC   : ExceptionGeneration<0b000, 0b10, "hvc">;
 def SMC   : ExceptionGeneration<0b000, 0b11, "smc">, Requires<[HasEL3]>;
 def SVC   : ExceptionGeneration<0b000, 0b01, "svc">;
+def : Pat<(AArch64hvc timm32_0_65535:$imm), (HVC 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..fe7ee0acc9e03
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/arm64-hvc.ll
@@ -0,0 +1,63 @@
+; 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 undef, i64 undef, i64 undef, i64 
undef)
+  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 undef, i64 undef)
+  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 undef, i64 undef)
+  call i64 @g()
+  ret i64 %r
+}
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..0edd3a0f0ee2a 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,7 +39,7 @@ 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

>From 0f410a9c2b16b608e94739e28c4f6060544e7eb2 Mon Sep 17 00:00:00 2001
From: Adhemerval Zanella <[email protected]>
Date: Thu, 4 Jun 2026 15:24:17 -0300
Subject: [PATCH 2/4] [AArch64] Add the __hvc MS intrinsic

Add support for the __hvc MS intrinsic on AArch64, lowered via the
llvm.aarch64.hvc intrinsic. It generates an HVC (Hypervisor Call)
instruction with a 16-bit immediate operand plus up to four arguments passed
in X0-X3 and returns the X0 result, matching the MSVC prototype
"unsigned int __hvc(unsigned int, ...)". Sema restricts the call to at most
five arguments (the immediate plus X0-X3).
---
 clang/include/clang/Basic/BuiltinsAArch64.td  |  1 +
 clang/lib/CodeGen/TargetBuiltins/ARM.cpp      | 23 +++++++++++++++++++
 clang/lib/Headers/intrin.h                    |  1 +
 clang/lib/Sema/SemaARM.cpp                    |  6 +++++
 .../test/CodeGen/arm64-microsoft-intrinsics.c | 13 +++++++++++
 clang/test/Sema/builtins-microsoft-arm64.c    |  8 +++++++
 6 files changed, 52 insertions(+)

diff --git a/clang/include/clang/Basic/BuiltinsAArch64.td 
b/clang/include/clang/Basic/BuiltinsAArch64.td
index 15257f3db5b41..64380554b0509 100644
--- a/clang/include/clang/Basic/BuiltinsAArch64.td
+++ b/clang/include/clang/Basic/BuiltinsAArch64.td
@@ -408,4 +408,5 @@ 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, ...)">;
 }
diff --git a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp 
b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
index ba6c571d40f68..e18e06c7816b5 100644
--- a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
@@ -5263,6 +5263,29 @@ Value *CodeGenFunction::EmitAArch64BuiltinExpr(unsigned 
BuiltinID,
     return ConstantInt::get(Builder.getInt32Ty(), 0);
   }
 
+  if (BuiltinID == AArch64::BI__hvc) {
+    // The first argument is the instruction immediate; 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 undef and dropped during lowering.
+    SmallVector<Value *, 5> Args{EmitScalarExpr(E->getArg(0))};
+    for (unsigned I = 1, N = E->getNumArgs(); I < N; ++I) {
+      Value *Arg = EmitScalarExpr(E->getArg(I));
+      if (Arg->getType()->isPointerTy())
+        Arg = Builder.CreatePtrToInt(Arg, Int64Ty);
+      else
+        Arg = Builder.CreateIntCast(
+            Arg, Int64Ty, E->getArg(I)->getType()->isSignedIntegerType());
+      Args.push_back(Arg);
+    }
+    while (Args.size() < 5)
+      Args.push_back(llvm::UndefValue::get(Int64Ty));
+    Value *Call =
+        Builder.CreateCall(CGM.getIntrinsic(Intrinsic::aarch64_hvc), 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..f8a7cbb4f8192 100644
--- a/clang/lib/Headers/intrin.h
+++ b/clang/lib/Headers/intrin.h
@@ -448,6 +448,7 @@ unsigned int _CountTrailingZeros(unsigned long);
 unsigned int _CountTrailingZeros64(unsigned __int64);
 
 unsigned int __hlt(unsigned int, ...);
+unsigned int __hvc(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 5e7504fab416d..368e155471e0f 100644
--- a/clang/lib/Sema/SemaARM.cpp
+++ b/clang/lib/Sema/SemaARM.cpp
@@ -1189,6 +1189,12 @@ bool SemaARM::CheckAArch64BuiltinFunctionCall(const 
TargetInfo &TI,
   if (BuiltinID == AArch64::BI__hlt)
     return SemaRef.BuiltinConstantArgRange(TheCall, 0, 0, 0xffff);
 
+  if (BuiltinID == AArch64::BI__hvc)
+    // 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.
+    return SemaRef.checkArgCountAtMost(TheCall, 5) ||
+           SemaRef.BuiltinConstantArgRange(TheCall, 0, 0, 0xffff);
+
   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..de969419da773 100644
--- a/clang/test/CodeGen/arm64-microsoft-intrinsics.c
+++ b/clang/test/CodeGen/arm64-microsoft-intrinsics.c
@@ -147,6 +147,19 @@ 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 undef, i64 undef, i64 
undef, i64 undef)
+// 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 undef, i64 undef)
+// CHECK-MSVC: trunc i64 [[HVC_R]] to i32
+// CHECK-LINUX: error: call to undeclared function '__hvc'
+
 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..bdeef6a5dc881 100644
--- a/clang/test/Sema/builtins-microsoft-arm64.c
+++ b/clang/test/Sema/builtins-microsoft-arm64.c
@@ -14,6 +14,14 @@ void check__hlt() {
   __hlt(65536); // expected-error-re {{argument value {{.*}} is outside the 
valid range}}
 }
 
+void check__hvc(unsigned int x) {
+  __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}}
+}
+
 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}}

>From 4a248b517dbf5cb2392ee8f41ce2a2793f04eb21 Mon Sep 17 00:00:00 2001
From: Adhemerval Zanella <[email protected]>
Date: Thu, 28 May 2026 13:51:44 -0300
Subject: [PATCH 3/4] [AArch64] Add the llvm.aarch64.svc intrinsic

Add the llvm.aarch64.svc intrinsic alongside llvm.aarch64.hvc, wired to the
SVC (Supervisor Call) instruction in the AArch64 backend. Like hvc, it takes
the 16-bit instruction immediate plus up to four register arguments passed in
X0-X3, returns the value left in X0, and is custom-lowered as a small call
with a caller-saved clobber mask. It is not IntrNoReturn since the supervisor
can return control to the caller.
---
 llvm/include/llvm/IR/IntrinsicsAArch64.td     |  4 ++
 .../Target/AArch64/AArch64ISelLowering.cpp    | 19 +++---
 llvm/lib/Target/AArch64/AArch64InstrInfo.td   | 15 +++--
 llvm/test/CodeGen/AArch64/arm64-svc.ll        | 63 +++++++++++++++++++
 .../AArch64/Neoverse/V1-misc-instructions.s   |  2 +-
 5 files changed, 89 insertions(+), 14 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/arm64-svc.ll

diff --git a/llvm/include/llvm/IR/IntrinsicsAArch64.td 
b/llvm/include/llvm/IR/IntrinsicsAArch64.td
index 60d217df36776..b5fe59934f57d 100644
--- a/llvm/include/llvm/IR/IntrinsicsAArch64.td
+++ b/llvm/include/llvm/IR/IntrinsicsAArch64.td
@@ -79,6 +79,10 @@ 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 d8423ae182351..e029483603b24 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -6577,13 +6577,14 @@ 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 __hvc intrinsic takes the 16-bit instruction immediate as its
-    // first operand and four further operands passed in X0-X3 (an unused
-    // argument is passed as undef); it returns the value left in X0.  Model 
the
-    // sequence as a small call: copy the provided arguments into the argument
-    // registers, emit the HVC node with a caller-saved clobber mask, then read
-    // the result back from X0.
+    // The MSVC __svc/__hvc intrinsics take the 16-bit instruction immediate as
+    // their first operand and four further operands passed in X0-X3 (an unused
+    // argument is passed as undef); they return the value left in X0.  Model
+    // the sequence as a small call: copy the provided arguments into the
+    // argument registers, emit the SVC/HVC node with a caller-saved clobber
+    // mask, then read the result back from X0.
     SDValue Chain = Op.getOperand(0);
     unsigned Imm = Op.getConstantOperandVal(2);
 
@@ -6611,8 +6612,10 @@ SDValue 
AArch64TargetLowering::LowerINTRINSIC_W_CHAIN(SDValue Op,
     if (Glue.getNode())
       Ops.push_back(Glue);
 
-    SDValue Node = DAG.getNode(AArch64ISD::HVC, DL,
-                               DAG.getVTList(MVT::Other, MVT::Glue), Ops);
+    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);
 
diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.td 
b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
index 8f227153d3e8a..f7c492f1bb878 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -3760,12 +3760,15 @@ def : Pat<(AArch64call texternalsym:$func), (BL 
texternalsym:$func)>;
 // Exception generation instructions.
 
//===----------------------------------------------------------------------===//
 
-// Node for the HVC exception-generating call.  The single operand is the
+// 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",
@@ -3776,15 +3779,17 @@ 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)]>;
-// HVC passes up to four arguments in X0-X3 and returns a value in X0, so it is
-// selected from the AArch64hvc node (built by LowerINTRINSIC_W_CHAIN) rather
-// than a plain intrinsic pattern.  It clobbers the caller-saved registers via 
a
-// regmask attached to the node.
+// HVC and SVC pass up to four arguments in X0-X3 and return a value in X0, so
+// they are selected from the AArch64hvc/AArch64svc nodes (built by
+// LowerINTRINSIC_W_CHAIN) rather than a plain intrinsic pattern.  They clobber
+// the caller-saved registers via a regmask attached to the node.
 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-svc.ll 
b/llvm/test/CodeGen/AArch64/arm64-svc.ll
new file mode 100644
index 0000000000000..886565c5cc78a
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/arm64-svc.ll
@@ -0,0 +1,63 @@
+; 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 undef, i64 undef, i64 undef, i64 
undef)
+  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 undef, i64 undef)
+  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 undef, i64 undef)
+  call i64 @g()
+  ret i64 %r
+}
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 0edd3a0f0ee2a..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
@@ -43,7 +43,7 @@ sysl x16, #5, c11, c8, #5
 # 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:

>From ee0b3f47187c6644a248b8a4f2b7b364aec23f8f Mon Sep 17 00:00:00 2001
From: Adhemerval Zanella <[email protected]>
Date: Thu, 28 May 2026 13:52:04 -0300
Subject: [PATCH 4/4] [AArch64] Add the __svc MS intrinsic

Add support for the __svc MS intrinsic on AArch64, lowered via the
llvm.aarch64.svc intrinsic and mirroring __hvc. It generates an SVC
(Supervisor Call) instruction with a 16-bit immediate operand plus up to four
arguments passed in X0-X3 and returns the X0 result, matching the MSVC
prototype "unsigned int __svc(unsigned int, ...)". Sema restricts the call to
at most five arguments.
---
 clang/include/clang/Basic/BuiltinsAArch64.td    |  1 +
 clang/lib/CodeGen/TargetBuiltins/ARM.cpp        |  7 ++++---
 clang/lib/Headers/intrin.h                      |  1 +
 clang/lib/Sema/SemaARM.cpp                      |  2 +-
 clang/test/CodeGen/arm64-microsoft-intrinsics.c | 13 +++++++++++++
 clang/test/Sema/builtins-microsoft-arm64.c      |  8 ++++++++
 6 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/Basic/BuiltinsAArch64.td 
b/clang/include/clang/Basic/BuiltinsAArch64.td
index 64380554b0509..4c49c874133fb 100644
--- a/clang/include/clang/Basic/BuiltinsAArch64.td
+++ b/clang/include/clang/Basic/BuiltinsAArch64.td
@@ -409,4 +409,5 @@ 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/lib/CodeGen/TargetBuiltins/ARM.cpp 
b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
index e18e06c7816b5..587616981b645 100644
--- a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
@@ -5263,7 +5263,9 @@ Value *CodeGenFunction::EmitAArch64BuiltinExpr(unsigned 
BuiltinID,
     return ConstantInt::get(Builder.getInt32Ty(), 0);
   }
 
-  if (BuiltinID == AArch64::BI__hvc) {
+  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; 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
@@ -5280,8 +5282,7 @@ Value *CodeGenFunction::EmitAArch64BuiltinExpr(unsigned 
BuiltinID,
     }
     while (Args.size() < 5)
       Args.push_back(llvm::UndefValue::get(Int64Ty));
-    Value *Call =
-        Builder.CreateCall(CGM.getIntrinsic(Intrinsic::aarch64_hvc), Args);
+    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);
   }
diff --git a/clang/lib/Headers/intrin.h b/clang/lib/Headers/intrin.h
index f8a7cbb4f8192..7dcb5a526afb2 100644
--- a/clang/lib/Headers/intrin.h
+++ b/clang/lib/Headers/intrin.h
@@ -449,6 +449,7 @@ 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 368e155471e0f..2a1cd13f54653 100644
--- a/clang/lib/Sema/SemaARM.cpp
+++ b/clang/lib/Sema/SemaARM.cpp
@@ -1189,7 +1189,7 @@ bool SemaARM::CheckAArch64BuiltinFunctionCall(const 
TargetInfo &TI,
   if (BuiltinID == AArch64::BI__hlt)
     return SemaRef.BuiltinConstantArgRange(TheCall, 0, 0, 0xffff);
 
-  if (BuiltinID == AArch64::BI__hvc)
+  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.
     return SemaRef.checkArgCountAtMost(TheCall, 5) ||
diff --git a/clang/test/CodeGen/arm64-microsoft-intrinsics.c 
b/clang/test/CodeGen/arm64-microsoft-intrinsics.c
index de969419da773..afd4187a49501 100644
--- a/clang/test/CodeGen/arm64-microsoft-intrinsics.c
+++ b/clang/test/CodeGen/arm64-microsoft-intrinsics.c
@@ -160,6 +160,19 @@ unsigned int check__hvc(unsigned int a, unsigned int b) {
 // 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 undef, i64 undef, i64 
undef, i64 undef)
+// 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 undef, i64 undef)
+// 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 bdeef6a5dc881..95d48f16a8355 100644
--- a/clang/test/Sema/builtins-microsoft-arm64.c
+++ b/clang/test/Sema/builtins-microsoft-arm64.c
@@ -22,6 +22,14 @@ void check__hvc(unsigned int x) {
   __hvc(1, 2, 3, 4, 5, 6); // expected-error {{too many arguments to function 
call, expected at most 5, have 6}}
 }
 
+void check__svc(unsigned int x) {
+  __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}}
+}
+
 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}}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to