Reland "[arm64] Protect return addresses stored on stack"

This is a reland of 137bfe47c9af56dcf8466e2736579616e51b86df

Original change's description:
> [arm64] Protect return addresses stored on stack
> 
> This change uses the Arm v8.3 pointer authentication instructions in
> order to protect return addresses stored on the stack.  The generated
> code signs the return address before storing on the stack and
> authenticates it after loading it. This also changes the stack frame
> iterator in order to authenticate stored return addresses and re-sign
> them when needed, as well as the deoptimizer in order to sign saved
> return addresses when creating new frames. This offers a level of
> protection against ROP attacks.
> 
> This functionality is enabled with the v8_control_flow_integrity flag
> that this CL introduces.
> 
> The code size effect of this change is small for Octane (up to 2% in
> some cases but mostly much lower) and negligible for larger benchmarks,
> however code size measurements are rather noisy. The performance impact
> on current cores (where the instructions are NOPs) is single digit,
> around 1-2% for ARES-6 and Octane, and tends to be smaller for big
> cores than for little cores.
> 
> Bug: v8:10026
> Change-Id: I0081f3938c56e2f24d8227e4640032749f4f8368
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1373782
> Commit-Queue: Georgia Kouveli <georgia.kouveli@arm.com>
> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
> Reviewed-by: Georg Neis <neis@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#66239}

Bug: v8:10026
Change-Id: Id1adfa2e6c713f6977d69aa467986e48fe67b3c2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2051958
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Commit-Queue: Georgia Kouveli <georgia.kouveli@arm.com>
Cr-Commit-Position: refs/heads/master@{#66254}
diff --git a/BUILD.gn b/BUILD.gn
index 9999cc0..a0ffefb 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -214,6 +214,10 @@
 
   # Disable all snapshot compression.
   v8_enable_snapshot_compression = true
+
+  # Enable control-flow integrity features, such as pointer authentication for
+  # ARM64.
+  v8_control_flow_integrity = false
 }
 
 # Derived defaults.
@@ -273,6 +277,9 @@
 assert(v8_current_cpu != "x86" || !v8_untrusted_code_mitigations,
        "Untrusted code mitigations are unsupported on ia32")
 
+assert(v8_current_cpu == "arm64" || !v8_control_flow_integrity,
+       "Control-flow integrity is only supported on arm64")
+
 assert(
     !v8_enable_pointer_compression || !v8_enable_shared_ro_heap,
     "Pointer compression is not supported with shared read-only heap enabled")
@@ -507,6 +514,9 @@
   if (v8_enable_snapshot_compression) {
     defines += [ "V8_SNAPSHOT_COMPRESSION" ]
   }
+  if (v8_control_flow_integrity) {
+    defines += [ "V8_ENABLE_CONTROL_FLOW_INTEGRITY" ]
+  }
 }
 
 config("toolchain") {
@@ -549,6 +559,12 @@
   }
   if (v8_current_cpu == "arm64") {
     defines += [ "V8_TARGET_ARCH_ARM64" ]
+    if (v8_control_flow_integrity) {
+      # TODO(v8:10026): Enable this in src/build.
+      if (current_cpu == "arm64") {
+        cflags += [ "-mbranch-protection=pac-ret" ]
+      }
+    }
   }
 
   # Mips64el/mipsel simulators.
@@ -2243,6 +2259,7 @@
     "src/execution/microtask-queue.h",
     "src/execution/off-thread-isolate.cc",
     "src/execution/off-thread-isolate.h",
+    "src/execution/pointer-authentication.h",
     "src/execution/protectors-inl.h",
     "src/execution/protectors.cc",
     "src/execution/protectors.h",
@@ -3024,6 +3041,10 @@
     "src/zone/zone.h",
   ]
 
+  if (!v8_control_flow_integrity) {
+    sources += [ "src/execution/pointer-authentication-dummy.h" ]
+  }
+
   if (v8_enable_third_party_heap) {
     sources += v8_third_party_heap_files
   }
@@ -3181,6 +3202,9 @@
       "src/regexp/arm64/regexp-macro-assembler-arm64.h",
       "src/wasm/baseline/arm64/liftoff-assembler-arm64.h",
     ]
+    if (v8_control_flow_integrity) {
+      sources += [ "src/execution/arm64/pointer-authentication-arm64.h" ]
+    }
     if (is_win) {
       sources += [
         "src/diagnostics/unwinding-info-win64.cc",
diff --git a/src/builtins/arm64/builtins-arm64.cc b/src/builtins/arm64/builtins-arm64.cc
index ddcf3af4..39d159f 100644
--- a/src/builtins/arm64/builtins-arm64.cc
+++ b/src/builtins/arm64/builtins-arm64.cc
@@ -1187,7 +1187,7 @@
   // the frame (that is done below).
   __ Bind(&push_stack_frame);
   FrameScope frame_scope(masm, StackFrame::MANUAL);
-  __ Push(lr, fp, cp, closure);
+  __ Push<TurboAssembler::kSignLR>(lr, fp, cp, closure);
   __ Add(fp, sp, StandardFrameConstants::kFixedFrameSizeFromFp);
 
   // Reset code age.
@@ -1672,7 +1672,7 @@
 
   // Restore fp, lr.
   __ Mov(sp, fp);
-  __ Pop(fp, lr);
+  __ Pop<TurboAssembler::kAuthLR>(fp, lr);
 
   __ LoadEntryFromBuiltinIndex(builtin);
   __ Jump(builtin);
@@ -2053,7 +2053,7 @@
 namespace {
 
 void EnterArgumentsAdaptorFrame(MacroAssembler* masm) {
-  __ Push(lr, fp);
+  __ Push<TurboAssembler::kSignLR>(lr, fp);
   __ Mov(x11, StackFrame::TypeToMarker(StackFrame::ARGUMENTS_ADAPTOR));
   __ Push(x11, x1);  // x1: function
   __ SmiTag(x11, x0);  // x0: number of arguments.
@@ -2069,7 +2069,7 @@
   // then drop the parameters and the receiver.
   __ Ldr(x10, MemOperand(fp, ArgumentsAdaptorFrameConstants::kLengthOffset));
   __ Mov(sp, fp);
-  __ Pop(fp, lr);
+  __ Pop<TurboAssembler::kAuthLR>(fp, lr);
 
   // Drop actual parameters and receiver.
   __ SmiUntag(x10);
@@ -3675,9 +3675,9 @@
   // DirectCEntry places the return address on the stack (updated by the GC),
   // making the call GC safe. The irregexp backend relies on this.
 
-  __ Poke(lr, 0);  // Store the return address.
+  __ Poke<TurboAssembler::kSignLR>(lr, 0);  // Store the return address.
   __ Blr(x10);     // Call the C++ function.
-  __ Peek(lr, 0);  // Return to calling code.
+  __ Peek<TurboAssembler::kAuthLR>(lr, 0);  // Return to calling code.
   __ AssertFPCRState();
   __ Ret();
 }
diff --git a/src/codegen/arm64/macro-assembler-arm64-inl.h b/src/codegen/arm64/macro-assembler-arm64-inl.h
index 0128ec0..962533d 100644
--- a/src/codegen/arm64/macro-assembler-arm64-inl.h
+++ b/src/codegen/arm64/macro-assembler-arm64-inl.h
@@ -1075,6 +1075,166 @@
 
 void TurboAssembler::jmp(Label* L) { B(L); }
 
+template <TurboAssembler::StoreLRMode lr_mode>
+void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1,
+                          const CPURegister& src2, const CPURegister& src3) {
+  DCHECK(AreSameSizeAndType(src0, src1, src2, src3));
+  DCHECK_IMPLIES((lr_mode == kSignLR), ((src0 == lr) || (src1 == lr) ||
+                                        (src2 == lr) || (src3 == lr)));
+  DCHECK_IMPLIES((lr_mode == kDontStoreLR), ((src0 != lr) && (src1 != lr) &&
+                                             (src2 != lr) && (src3 != lr)));
+
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  if (lr_mode == kSignLR) {
+    Paciasp();
+  }
+#endif
+
+  int count = 1 + src1.is_valid() + src2.is_valid() + src3.is_valid();
+  int size = src0.SizeInBytes();
+  DCHECK_EQ(0, (size * count) % 16);
+
+  PushHelper(count, size, src0, src1, src2, src3);
+}
+
+template <TurboAssembler::StoreLRMode lr_mode>
+void TurboAssembler::Push(const Register& src0, const VRegister& src1) {
+  DCHECK_IMPLIES((lr_mode == kSignLR), ((src0 == lr) || (src1 == lr)));
+  DCHECK_IMPLIES((lr_mode == kDontStoreLR), ((src0 != lr) && (src1 != lr)));
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  if (lr_mode == kSignLR) {
+    Paciasp();
+  }
+#endif
+
+  int size = src0.SizeInBytes() + src1.SizeInBytes();
+  DCHECK_EQ(0, size % 16);
+
+  // Reserve room for src0 and push src1.
+  str(src1, MemOperand(sp, -size, PreIndex));
+  // Fill the gap with src0.
+  str(src0, MemOperand(sp, src1.SizeInBytes()));
+}
+
+template <TurboAssembler::LoadLRMode lr_mode>
+void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1,
+                         const CPURegister& dst2, const CPURegister& dst3) {
+  // It is not valid to pop into the same register more than once in one
+  // instruction, not even into the zero register.
+  DCHECK(!AreAliased(dst0, dst1, dst2, dst3));
+  DCHECK(AreSameSizeAndType(dst0, dst1, dst2, dst3));
+  DCHECK(dst0.is_valid());
+
+  int count = 1 + dst1.is_valid() + dst2.is_valid() + dst3.is_valid();
+  int size = dst0.SizeInBytes();
+  DCHECK_EQ(0, (size * count) % 16);
+
+  PopHelper(count, size, dst0, dst1, dst2, dst3);
+
+  DCHECK_IMPLIES((lr_mode == kAuthLR), ((dst0 == lr) || (dst1 == lr) ||
+                                        (dst2 == lr) || (dst3 == lr)));
+  DCHECK_IMPLIES((lr_mode == kDontLoadLR), ((dst0 != lr) && (dst1 != lr)) &&
+                                               (dst2 != lr) && (dst3 != lr));
+
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  if (lr_mode == kAuthLR) {
+    Autiasp();
+  }
+#endif
+}
+
+template <TurboAssembler::StoreLRMode lr_mode>
+void TurboAssembler::Poke(const CPURegister& src, const Operand& offset) {
+  DCHECK_IMPLIES((lr_mode == kSignLR), (src == lr));
+  DCHECK_IMPLIES((lr_mode == kDontStoreLR), (src != lr));
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  if (lr_mode == kSignLR) {
+    Paciasp();
+  }
+#endif
+
+  if (offset.IsImmediate()) {
+    DCHECK_GE(offset.ImmediateValue(), 0);
+  } else if (emit_debug_code()) {
+    Cmp(xzr, offset);
+    Check(le, AbortReason::kStackAccessBelowStackPointer);
+  }
+
+  Str(src, MemOperand(sp, offset));
+}
+
+template <TurboAssembler::LoadLRMode lr_mode>
+void TurboAssembler::Peek(const CPURegister& dst, const Operand& offset) {
+  if (offset.IsImmediate()) {
+    DCHECK_GE(offset.ImmediateValue(), 0);
+  } else if (emit_debug_code()) {
+    Cmp(xzr, offset);
+    Check(le, AbortReason::kStackAccessBelowStackPointer);
+  }
+
+  Ldr(dst, MemOperand(sp, offset));
+
+  DCHECK_IMPLIES((lr_mode == kAuthLR), (dst == lr));
+  DCHECK_IMPLIES((lr_mode == kDontLoadLR), (dst != lr));
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  if (lr_mode == kAuthLR) {
+    Autiasp();
+  }
+#endif
+}
+
+template <TurboAssembler::StoreLRMode lr_mode>
+void TurboAssembler::PushCPURegList(CPURegList registers) {
+  DCHECK_IMPLIES((lr_mode == kDontStoreLR), !registers.IncludesAliasOf(lr));
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  if (lr_mode == kSignLR && registers.IncludesAliasOf(lr)) {
+    Paciasp();
+  }
+#endif
+
+  int size = registers.RegisterSizeInBytes();
+  DCHECK_EQ(0, (size * registers.Count()) % 16);
+
+  // Push up to four registers at a time.
+  while (!registers.IsEmpty()) {
+    int count_before = registers.Count();
+    const CPURegister& src0 = registers.PopHighestIndex();
+    const CPURegister& src1 = registers.PopHighestIndex();
+    const CPURegister& src2 = registers.PopHighestIndex();
+    const CPURegister& src3 = registers.PopHighestIndex();
+    int count = count_before - registers.Count();
+    PushHelper(count, size, src0, src1, src2, src3);
+  }
+}
+
+template <TurboAssembler::LoadLRMode lr_mode>
+void TurboAssembler::PopCPURegList(CPURegList registers) {
+  int size = registers.RegisterSizeInBytes();
+  DCHECK_EQ(0, (size * registers.Count()) % 16);
+
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  bool contains_lr = registers.IncludesAliasOf(lr);
+  DCHECK_IMPLIES((lr_mode == kDontLoadLR), !contains_lr);
+#endif
+
+  // Pop up to four registers at a time.
+  while (!registers.IsEmpty()) {
+    int count_before = registers.Count();
+    const CPURegister& dst0 = registers.PopLowestIndex();
+    const CPURegister& dst1 = registers.PopLowestIndex();
+    const CPURegister& dst2 = registers.PopLowestIndex();
+    const CPURegister& dst3 = registers.PopLowestIndex();
+    int count = count_before - registers.Count();
+    PopHelper(count, size, dst0, dst1, dst2, dst3);
+  }
+
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  if (lr_mode == kAuthLR && contains_lr) {
+    Autiasp();
+  }
+#endif
+}
+
 void TurboAssembler::Push(Handle<HeapObject> handle) {
   UseScratchRegisterScope temps(this);
   Register tmp = temps.AcquireX();
diff --git a/src/codegen/arm64/macro-assembler-arm64.cc b/src/codegen/arm64/macro-assembler-arm64.cc
index 874aec2..6099f6b 100644
--- a/src/codegen/arm64/macro-assembler-arm64.cc
+++ b/src/codegen/arm64/macro-assembler-arm64.cc
@@ -60,7 +60,7 @@
   list.Remove(exclusion);
   list.Align();
 
-  PushCPURegList(list);
+  PushCPURegList<kSignLR>(list);
 
   int bytes = list.Count() * kXRegSizeInBits / 8;
 
@@ -84,7 +84,7 @@
   list.Remove(exclusion);
   list.Align();
 
-  PopCPURegList(list);
+  PopCPURegList<kAuthLR>(list);
   bytes += list.Count() * kXRegSizeInBits / 8;
 
   return bytes;
@@ -1046,17 +1046,6 @@
 // Abstracted stack operations.
 
 void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1,
-                          const CPURegister& src2, const CPURegister& src3) {
-  DCHECK(AreSameSizeAndType(src0, src1, src2, src3));
-
-  int count = 1 + src1.is_valid() + src2.is_valid() + src3.is_valid();
-  int size = src0.SizeInBytes();
-  DCHECK_EQ(0, (size * count) % 16);
-
-  PushHelper(count, size, src0, src1, src2, src3);
-}
-
-void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1,
                           const CPURegister& src2, const CPURegister& src3,
                           const CPURegister& src4, const CPURegister& src5,
                           const CPURegister& src6, const CPURegister& src7) {
@@ -1071,21 +1060,6 @@
 }
 
 void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1,
-                         const CPURegister& dst2, const CPURegister& dst3) {
-  // It is not valid to pop into the same register more than once in one
-  // instruction, not even into the zero register.
-  DCHECK(!AreAliased(dst0, dst1, dst2, dst3));
-  DCHECK(AreSameSizeAndType(dst0, dst1, dst2, dst3));
-  DCHECK(dst0.is_valid());
-
-  int count = 1 + dst1.is_valid() + dst2.is_valid() + dst3.is_valid();
-  int size = dst0.SizeInBytes();
-  DCHECK_EQ(0, (size * count) % 16);
-
-  PopHelper(count, size, dst0, dst1, dst2, dst3);
-}
-
-void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1,
                          const CPURegister& dst2, const CPURegister& dst3,
                          const CPURegister& dst4, const CPURegister& dst5,
                          const CPURegister& dst6, const CPURegister& dst7) {
@@ -1103,48 +1077,6 @@
   PopHelper(count - 4, size, dst4, dst5, dst6, dst7);
 }
 
-void TurboAssembler::Push(const Register& src0, const VRegister& src1) {
-  int size = src0.SizeInBytes() + src1.SizeInBytes();
-  DCHECK_EQ(0, size % 16);
-
-  // Reserve room for src0 and push src1.
-  str(src1, MemOperand(sp, -size, PreIndex));
-  // Fill the gap with src0.
-  str(src0, MemOperand(sp, src1.SizeInBytes()));
-}
-
-void TurboAssembler::PushCPURegList(CPURegList registers) {
-  int size = registers.RegisterSizeInBytes();
-  DCHECK_EQ(0, (size * registers.Count()) % 16);
-
-  // Push up to four registers at a time.
-  while (!registers.IsEmpty()) {
-    int count_before = registers.Count();
-    const CPURegister& src0 = registers.PopHighestIndex();
-    const CPURegister& src1 = registers.PopHighestIndex();
-    const CPURegister& src2 = registers.PopHighestIndex();
-    const CPURegister& src3 = registers.PopHighestIndex();
-    int count = count_before - registers.Count();
-    PushHelper(count, size, src0, src1, src2, src3);
-  }
-}
-
-void TurboAssembler::PopCPURegList(CPURegList registers) {
-  int size = registers.RegisterSizeInBytes();
-  DCHECK_EQ(0, (size * registers.Count()) % 16);
-
-  // Pop up to four registers at a time.
-  while (!registers.IsEmpty()) {
-    int count_before = registers.Count();
-    const CPURegister& dst0 = registers.PopLowestIndex();
-    const CPURegister& dst1 = registers.PopLowestIndex();
-    const CPURegister& dst2 = registers.PopLowestIndex();
-    const CPURegister& dst3 = registers.PopLowestIndex();
-    int count = count_before - registers.Count();
-    PopHelper(count, size, dst0, dst1, dst2, dst3);
-  }
-}
-
 void MacroAssembler::PushMultipleTimes(CPURegister src, Register count) {
   UseScratchRegisterScope temps(this);
   Register temp = temps.AcquireSameSizeAs(count);
@@ -1249,28 +1181,6 @@
   }
 }
 
-void TurboAssembler::Poke(const CPURegister& src, const Operand& offset) {
-  if (offset.IsImmediate()) {
-    DCHECK_GE(offset.ImmediateValue(), 0);
-  } else if (emit_debug_code()) {
-    Cmp(xzr, offset);
-    Check(le, AbortReason::kStackAccessBelowStackPointer);
-  }
-
-  Str(src, MemOperand(sp, offset));
-}
-
-void TurboAssembler::Peek(const CPURegister& dst, const Operand& offset) {
-  if (offset.IsImmediate()) {
-    DCHECK_GE(offset.ImmediateValue(), 0);
-  } else if (emit_debug_code()) {
-    Cmp(xzr, offset);
-    Check(le, AbortReason::kStackAccessBelowStackPointer);
-  }
-
-  Ldr(dst, MemOperand(sp, offset));
-}
-
 void TurboAssembler::PokePair(const CPURegister& src1, const CPURegister& src2,
                               int offset) {
   DCHECK(AreSameSizeAndType(src1, src2));
@@ -1286,50 +1196,61 @@
 }
 
 void MacroAssembler::PushCalleeSavedRegisters() {
-  // Ensure that the macro-assembler doesn't use any scratch registers.
-  InstructionAccurateScope scope(this);
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  Paciasp();
+#endif
 
-  MemOperand tos(sp, -2 * static_cast<int>(kXRegSize), PreIndex);
+  {
+    // Ensure that the macro-assembler doesn't use any scratch registers.
+    InstructionAccurateScope scope(this);
 
-  stp(d14, d15, tos);
-  stp(d12, d13, tos);
-  stp(d10, d11, tos);
-  stp(d8, d9, tos);
+    MemOperand tos(sp, -2 * static_cast<int>(kXRegSize), PreIndex);
 
-  STATIC_ASSERT(
-      EntryFrameConstants::kCalleeSavedRegisterBytesPushedBeforeFpLrPair ==
-      8 * kSystemPointerSize);
+    stp(d14, d15, tos);
+    stp(d12, d13, tos);
+    stp(d10, d11, tos);
+    stp(d8, d9, tos);
 
-  stp(x29, x30, tos);  // fp, lr
+    STATIC_ASSERT(
+        EntryFrameConstants::kCalleeSavedRegisterBytesPushedBeforeFpLrPair ==
+        8 * kSystemPointerSize);
+    stp(x29, x30, tos);  // fp, lr
 
-  STATIC_ASSERT(
-      EntryFrameConstants::kCalleeSavedRegisterBytesPushedAfterFpLrPair ==
-      10 * kSystemPointerSize);
+    STATIC_ASSERT(
+        EntryFrameConstants::kCalleeSavedRegisterBytesPushedAfterFpLrPair ==
+        10 * kSystemPointerSize);
 
-  stp(x27, x28, tos);
-  stp(x25, x26, tos);
-  stp(x23, x24, tos);
-  stp(x21, x22, tos);
-  stp(x19, x20, tos);
+    stp(x27, x28, tos);
+    stp(x25, x26, tos);
+    stp(x23, x24, tos);
+    stp(x21, x22, tos);
+    stp(x19, x20, tos);
+  }
 }
 
 void MacroAssembler::PopCalleeSavedRegisters() {
-  // Ensure that the macro-assembler doesn't use any scratch registers.
-  InstructionAccurateScope scope(this);
+  {
+    // Ensure that the macro-assembler doesn't use any scratch registers.
+    InstructionAccurateScope scope(this);
 
-  MemOperand tos(sp, 2 * kXRegSize, PostIndex);
+    MemOperand tos(sp, 2 * kXRegSize, PostIndex);
 
-  ldp(x19, x20, tos);
-  ldp(x21, x22, tos);
-  ldp(x23, x24, tos);
-  ldp(x25, x26, tos);
-  ldp(x27, x28, tos);
-  ldp(x29, x30, tos);
+    ldp(x19, x20, tos);
+    ldp(x21, x22, tos);
+    ldp(x23, x24, tos);
+    ldp(x25, x26, tos);
+    ldp(x27, x28, tos);
+    ldp(x29, x30, tos);
 
-  ldp(d8, d9, tos);
-  ldp(d10, d11, tos);
-  ldp(d12, d13, tos);
-  ldp(d14, d15, tos);
+    ldp(d8, d9, tos);
+    ldp(d10, d11, tos);
+    ldp(d12, d13, tos);
+    ldp(d14, d15, tos);
+  }
+
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  Autiasp();
+#endif
 }
 
 void TurboAssembler::AssertSpAligned() {
@@ -2017,18 +1938,22 @@
   // GC, since the callee function will return to it.
 
   UseScratchRegisterScope temps(this);
-  Register scratch1 = temps.AcquireX();
+  temps.Exclude(x16, x17);
 
   Label return_location;
-  Adr(scratch1, &return_location);
-  Poke(scratch1, 0);
+  Adr(x17, &return_location);
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  Add(x16, sp, kSystemPointerSize);
+  Pacia1716();
+#endif
+  Poke(x17, 0);
 
   if (emit_debug_code()) {
-    // Verify that the slot below fp[kSPOffset]-8 points to the return location.
-    Register scratch2 = temps.AcquireX();
-    Ldr(scratch2, MemOperand(fp, ExitFrameConstants::kSPOffset));
-    Ldr(scratch2, MemOperand(scratch2, -static_cast<int64_t>(kXRegSize)));
-    Cmp(scratch2, scratch1);
+    // Verify that the slot below fp[kSPOffset]-8 points to the signed return
+    // location.
+    Ldr(x16, MemOperand(fp, ExitFrameConstants::kSPOffset));
+    Ldr(x16, MemOperand(x16, -static_cast<int64_t>(kXRegSize)));
+    Cmp(x16, x17);
     Check(eq, AbortReason::kReturnAddressNotFoundInFrame);
   }
 
@@ -2096,8 +2021,7 @@
 
   // Restore caller's frame pointer and return address now as they will be
   // overwritten by the copying loop.
-  Ldr(lr, MemOperand(fp, StandardFrameConstants::kCallerPCOffset));
-  Ldr(fp, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
+  RestoreFPAndLR();
 
   // Now copy callee arguments to the caller frame going backwards to avoid
   // callee arguments corruption (source and destination areas could overlap).
@@ -2309,7 +2233,7 @@
   TryConvertDoubleToInt64(result, double_input, &done);
 
   // If we fell through then inline version didn't succeed - call stub instead.
-  Push(lr, double_input);
+  Push<TurboAssembler::kSignLR>(lr, double_input);
 
   // DoubleToI preserves any registers it needs to clobber.
   if (stub_mode == StubCallMode::kCallWasmRuntimeStub) {
@@ -2322,7 +2246,8 @@
   Ldr(result, MemOperand(sp, 0));
 
   DCHECK_EQ(xzr.SizeInBytes(), double_input.SizeInBytes());
-  Pop(xzr, lr);  // xzr to drop the double input on the stack.
+  // Pop into xzr here to drop the double input on the stack:
+  Pop<TurboAssembler::kAuthLR>(xzr, lr);
 
   Bind(&done);
   // Keep our invariant that the upper 32 bits are zero.
@@ -2330,7 +2255,7 @@
 }
 
 void TurboAssembler::Prologue() {
-  Push(lr, fp, cp, x1);
+  Push<TurboAssembler::kSignLR>(lr, fp, cp, x1);
   Add(fp, sp, StandardFrameConstants::kFixedFrameSizeFromFp);
 }
 
@@ -2341,7 +2266,7 @@
     Register type_reg = temps.AcquireX();
     Mov(type_reg, StackFrame::TypeToMarker(type));
     // type_reg pushed twice for alignment.
-    Push(lr, fp, type_reg, type_reg);
+    Push<TurboAssembler::kSignLR>(lr, fp, type_reg, type_reg);
     const int kFrameSize =
         TypedFrameConstants::kFixedFrameSizeFromFp + kSystemPointerSize;
     Add(fp, sp, kFrameSize);
@@ -2354,7 +2279,7 @@
              type == StackFrame::WASM_EXIT) {
     Register type_reg = temps.AcquireX();
     Mov(type_reg, StackFrame::TypeToMarker(type));
-    Push(lr, fp);
+    Push<TurboAssembler::kSignLR>(lr, fp);
     Mov(fp, sp);
     Push(type_reg, padreg);
     // sp[3] : lr
@@ -2368,7 +2293,7 @@
 
     // Users of this frame type push a context pointer after the type field,
     // so do it here to keep the stack pointer aligned.
-    Push(lr, fp, type_reg, cp);
+    Push<TurboAssembler::kSignLR>(lr, fp, type_reg, cp);
 
     // The context pointer isn't part of the fixed frame, so add an extra slot
     // to account for it.
@@ -2385,7 +2310,7 @@
   // Drop the execution stack down to the frame pointer and restore
   // the caller frame pointer and return address.
   Mov(sp, fp);
-  Pop(fp, lr);
+  Pop<TurboAssembler::kAuthLR>(fp, lr);
 }
 
 void MacroAssembler::ExitFramePreserveFPRegs() {
@@ -2415,7 +2340,7 @@
          frame_type == StackFrame::BUILTIN_EXIT);
 
   // Set up the new stack frame.
-  Push(lr, fp);
+  Push<TurboAssembler::kSignLR>(lr, fp);
   Mov(fp, sp);
   Mov(scratch, StackFrame::TypeToMarker(frame_type));
   Push(scratch, xzr);
@@ -2498,7 +2423,7 @@
   //   fp -> fp[0]: CallerFP (old fp)
   //         fp[...]: The rest of the frame.
   Mov(sp, fp);
-  Pop(fp, lr);
+  Pop<TurboAssembler::kAuthLR>(fp, lr);
 }
 
 void MacroAssembler::LoadGlobalProxy(Register dst) {
@@ -2751,25 +2676,19 @@
 
 void TurboAssembler::SaveRegisters(RegList registers) {
   DCHECK_GT(NumRegs(registers), 0);
-  CPURegList regs(lr);
-  for (int i = 0; i < Register::kNumRegisters; ++i) {
-    if ((registers >> i) & 1u) {
-      regs.Combine(Register::XRegFromCode(i));
-    }
-  }
-
+  CPURegList regs(CPURegister::kRegister, kXRegSizeInBits, registers);
+  // If we were saving LR, we might need to sign it.
+  DCHECK(!regs.IncludesAliasOf(lr));
+  regs.Align();
   PushCPURegList(regs);
 }
 
 void TurboAssembler::RestoreRegisters(RegList registers) {
   DCHECK_GT(NumRegs(registers), 0);
-  CPURegList regs(lr);
-  for (int i = 0; i < Register::kNumRegisters; ++i) {
-    if ((registers >> i) & 1u) {
-      regs.Combine(Register::XRegFromCode(i));
-    }
-  }
-
+  CPURegList regs(CPURegister::kRegister, kXRegSizeInBits, registers);
+  // If we were saving LR, we might need to sign it.
+  DCHECK(!regs.IncludesAliasOf(lr));
+  regs.Align();
   PopCPURegList(regs);
 }
 
@@ -2924,11 +2843,11 @@
 
   // Record the actual write.
   if (lr_status == kLRHasNotBeenSaved) {
-    Push(padreg, lr);
+    Push<TurboAssembler::kSignLR>(padreg, lr);
   }
   CallRecordWriteStub(object, offset, remembered_set_action, fp_mode);
   if (lr_status == kLRHasNotBeenSaved) {
-    Pop(lr, padreg);
+    Pop<TurboAssembler::kAuthLR>(lr, padreg);
   }
 
   Bind(&done);
@@ -3183,7 +3102,7 @@
   // Preserve all caller-saved registers as well as NZCV.
   // PushCPURegList asserts that the size of each list is a multiple of 16
   // bytes.
-  PushCPURegList(saved_registers);
+  PushCPURegList<kDontSignLR>(saved_registers);
   PushCPURegList(kCallerSavedV);
 
   // We can use caller-saved registers as scratch values (except for argN).
@@ -3236,7 +3155,7 @@
   }
 
   PopCPURegList(kCallerSavedV);
-  PopCPURegList(saved_registers);
+  PopCPURegList<kDontAuthLR>(saved_registers);
 
   TmpList()->set_list(old_tmp_list);
   FPTmpList()->set_list(old_fp_tmp_list);
@@ -3274,6 +3193,35 @@
   Mov(kSpeculationPoisonRegister, -1);
 }
 
+void TurboAssembler::RestoreFPAndLR() {
+  static_assert(StandardFrameConstants::kCallerFPOffset + kSystemPointerSize ==
+                    StandardFrameConstants::kCallerPCOffset,
+                "Offsets must be consecutive for ldp!");
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  // Make sure we can use x16 and x17.
+  UseScratchRegisterScope temps(this);
+  temps.Exclude(x16, x17);
+  // We can load the return address directly into x17.
+  Add(x16, fp, StandardFrameConstants::kCallerSPOffset);
+  Ldp(fp, x17, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
+  Autia1716();
+  Mov(lr, x17);
+#else
+  Ldp(fp, lr, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
+#endif
+}
+
+void TurboAssembler::StoreReturnAddressInWasmExitFrame(Label* return_location) {
+  UseScratchRegisterScope temps(this);
+  temps.Exclude(x16, x17);
+  Adr(x17, return_location);
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  Add(x16, fp, WasmExitFrameConstants::kCallingPCOffset + kSystemPointerSize);
+  Pacia1716();
+#endif
+  Str(x17, MemOperand(fp, WasmExitFrameConstants::kCallingPCOffset));
+}
+
 }  // namespace internal
 }  // namespace v8
 
diff --git a/src/codegen/arm64/macro-assembler-arm64.h b/src/codegen/arm64/macro-assembler-arm64.h
index 735536c..965ec20 100644
--- a/src/codegen/arm64/macro-assembler-arm64.h
+++ b/src/codegen/arm64/macro-assembler-arm64.h
@@ -783,20 +783,33 @@
   // The stack pointer must be aligned to 16 bytes on entry and the total size
   // of the specified registers must also be a multiple of 16 bytes.
   //
-  // Other than the registers passed into Pop, the stack pointer and (possibly)
-  // the system stack pointer, these methods do not modify any other registers.
+  // Other than the registers passed into Pop, the stack pointer, (possibly)
+  // the system stack pointer and (possibly) the link register, these methods
+  // do not modify any other registers.
+  //
+  // Some of the methods take an optional LoadLRMode or StoreLRMode template
+  // argument, which specifies whether we need to sign the link register at the
+  // start of the operation, or authenticate it at the end of the operation,
+  // when control flow integrity measures are enabled.
+  // When the mode is kDontLoadLR or kDontStoreLR, LR must not be passed as an
+  // argument to the operation.
+  enum LoadLRMode { kAuthLR, kDontAuthLR, kDontLoadLR };
+  enum StoreLRMode { kSignLR, kDontSignLR, kDontStoreLR };
+  template <StoreLRMode lr_mode = kDontStoreLR>
   void Push(const CPURegister& src0, const CPURegister& src1 = NoReg,
             const CPURegister& src2 = NoReg, const CPURegister& src3 = NoReg);
   void Push(const CPURegister& src0, const CPURegister& src1,
             const CPURegister& src2, const CPURegister& src3,
             const CPURegister& src4, const CPURegister& src5 = NoReg,
             const CPURegister& src6 = NoReg, const CPURegister& src7 = NoReg);
+  template <LoadLRMode lr_mode = kDontLoadLR>
   void Pop(const CPURegister& dst0, const CPURegister& dst1 = NoReg,
            const CPURegister& dst2 = NoReg, const CPURegister& dst3 = NoReg);
   void Pop(const CPURegister& dst0, const CPURegister& dst1,
            const CPURegister& dst2, const CPURegister& dst3,
            const CPURegister& dst4, const CPURegister& dst5 = NoReg,
            const CPURegister& dst6 = NoReg, const CPURegister& dst7 = NoReg);
+  template <StoreLRMode lr_mode = kDontStoreLR>
   void Push(const Register& src0, const VRegister& src1);
 
   // This is a convenience method for pushing a single Handle<Object>.
@@ -838,7 +851,15 @@
   // kSRegSizeInBits are supported.
   //
   // Otherwise, (Push|Pop)(CPU|X|W|D|S)RegList is preferred.
+  //
+  // The methods take an optional LoadLRMode or StoreLRMode template argument.
+  // When control flow integrity measures are enabled and the link register is
+  // included in 'registers', passing kSignLR to PushCPURegList will sign the
+  // link register before pushing the list, and passing kAuthLR to
+  // PopCPURegList will authenticate it after popping the list.
+  template <StoreLRMode lr_mode = kDontStoreLR>
   void PushCPURegList(CPURegList registers);
+  template <LoadLRMode lr_mode = kDontLoadLR>
   void PopCPURegList(CPURegList registers);
 
   // Calculate how much stack space (in bytes) are required to store caller
@@ -1042,10 +1063,18 @@
 
   // Poke 'src' onto the stack. The offset is in bytes. The stack pointer must
   // be 16 byte aligned.
+  // When the optional template argument is kSignLR and control flow integrity
+  // measures are enabled, we sign the link register before poking it onto the
+  // stack. 'src' must be lr in this case.
+  template <StoreLRMode lr_mode = kDontStoreLR>
   void Poke(const CPURegister& src, const Operand& offset);
 
   // Peek at a value on the stack, and put it in 'dst'. The offset is in bytes.
   // The stack pointer must be aligned to 16 bytes.
+  // When the optional template argument is kAuthLR and control flow integrity
+  // measures are enabled, we authenticate the link register after peeking the
+  // value. 'dst' must be lr in this case.
+  template <LoadLRMode lr_mode = kDontLoadLR>
   void Peek(const CPURegister& dst, const Operand& offset);
 
   // Poke 'src1' and 'src2' onto the stack. The values written will be adjacent
@@ -1297,6 +1326,12 @@
   void DecompressAnyTagged(const Register& destination,
                            const MemOperand& field_operand);
 
+  // Restore FP and LR from the values stored in the current frame. This will
+  // authenticate the LR when pointer authentication is enabled.
+  void RestoreFPAndLR();
+
+  void StoreReturnAddressInWasmExitFrame(Label* return_location);
+
  protected:
   // The actual Push and Pop implementations. These don't generate any code
   // other than that required for the push or pop. This allows
@@ -1625,21 +1660,27 @@
     tbx(vd, vn, vn2, vn3, vn4, vm);
   }
 
+  // For the 'lr_mode' template argument of the following methods, see
+  // PushCPURegList/PopCPURegList.
+  template <StoreLRMode lr_mode = kDontStoreLR>
   inline void PushSizeRegList(
       RegList registers, unsigned reg_size,
       CPURegister::RegisterType type = CPURegister::kRegister) {
-    PushCPURegList(CPURegList(type, reg_size, registers));
+    PushCPURegList<lr_mode>(CPURegList(type, reg_size, registers));
   }
+  template <LoadLRMode lr_mode = kDontLoadLR>
   inline void PopSizeRegList(
       RegList registers, unsigned reg_size,
       CPURegister::RegisterType type = CPURegister::kRegister) {
-    PopCPURegList(CPURegList(type, reg_size, registers));
+    PopCPURegList<lr_mode>(CPURegList(type, reg_size, registers));
   }
+  template <StoreLRMode lr_mode = kDontStoreLR>
   inline void PushXRegList(RegList regs) {
-    PushSizeRegList(regs, kXRegSizeInBits);
+    PushSizeRegList<lr_mode>(regs, kXRegSizeInBits);
   }
+  template <LoadLRMode lr_mode = kDontLoadLR>
   inline void PopXRegList(RegList regs) {
-    PopSizeRegList(regs, kXRegSizeInBits);
+    PopSizeRegList<lr_mode>(regs, kXRegSizeInBits);
   }
   inline void PushWRegList(RegList regs) {
     PushSizeRegList(regs, kWRegSizeInBits);
@@ -1681,6 +1722,9 @@
   // Floating-point registers are pushed before general-purpose registers, and
   // thus get higher addresses.
   //
+  // When control flow integrity measures are enabled, this method signs the
+  // link register before pushing it.
+  //
   // Note that registers are not checked for invalid values. Use this method
   // only if you know that the GC won't try to examine the values on the stack.
   void PushCalleeSavedRegisters();
@@ -1691,6 +1735,9 @@
   // thus come from higher addresses.
   // Floating-point registers are popped after general-purpose registers, and
   // thus come from higher addresses.
+  //
+  // When control flow integrity measures are enabled, this method
+  // authenticates the link register after popping it.
   void PopCalleeSavedRegisters();
 
   // Helpers ------------------------------------------------------------------
diff --git a/src/compiler/backend/arm64/code-generator-arm64.cc b/src/compiler/backend/arm64/code-generator-arm64.cc
index c95bf98..46a3612 100644
--- a/src/compiler/backend/arm64/code-generator-arm64.cc
+++ b/src/compiler/backend/arm64/code-generator-arm64.cc
@@ -291,7 +291,7 @@
         frame()->DidAllocateDoubleRegisters() ? kSaveFPRegs : kDontSaveFPRegs;
     if (must_save_lr_) {
       // We need to save and restore lr if the frame was elided.
-      __ Push(lr, padreg);
+      __ Push<TurboAssembler::kSignLR>(lr, padreg);
       unwinding_info_writer_->MarkLinkRegisterOnTopOfStack(__ pc_offset(), sp);
     }
     if (mode_ == RecordWriteMode::kValueIsEphemeronKey) {
@@ -307,7 +307,7 @@
                              save_fp_mode);
     }
     if (must_save_lr_) {
-      __ Pop(padreg, lr);
+      __ Pop<TurboAssembler::kAuthLR>(padreg, lr);
       unwinding_info_writer_->MarkPopLinkRegisterFromTopOfStack(__ pc_offset());
     }
   }
@@ -496,18 +496,14 @@
 
 void CodeGenerator::AssembleDeconstructFrame() {
   __ Mov(sp, fp);
-  __ Pop(fp, lr);
+  __ Pop<TurboAssembler::kAuthLR>(fp, lr);
 
   unwinding_info_writer_.MarkFrameDeconstructed(__ pc_offset());
 }
 
 void CodeGenerator::AssemblePrepareTailCall() {
   if (frame_access_state()->has_frame()) {
-    static_assert(
-        StandardFrameConstants::kCallerFPOffset + kSystemPointerSize ==
-            StandardFrameConstants::kCallerPCOffset,
-        "Offsets must be consecutive for ldp!");
-    __ Ldp(fp, lr, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
+    __ RestoreFPAndLR();
   }
   frame_access_state()->SetFrameAccessToSP();
 }
@@ -779,10 +775,7 @@
       Label return_location;
       if (linkage()->GetIncomingDescriptor()->IsWasmCapiFunction()) {
         // Put the return address in a stack slot.
-        Register scratch = x8;
-        __ Adr(scratch, &return_location);
-        __ Str(scratch,
-               MemOperand(fp, WasmExitFrameConstants::kCallingPCOffset));
+        __ StoreReturnAddressInWasmExitFrame(&return_location);
       }
 
       if (instr->InputAt(0)->IsImmediate()) {
@@ -2812,7 +2805,7 @@
     if (call_descriptor->IsJSFunctionCall()) {
       __ Prologue();
     } else {
-      __ Push(lr, fp);
+      __ Push<TurboAssembler::kSignLR>(lr, fp);
       __ Mov(fp, sp);
     }
     unwinding_info_writer_.MarkFrameConstructed(__ pc_offset());
@@ -2962,7 +2955,7 @@
   // TODO(palfia): TF save list is not in sync with
   // CPURegList::GetCalleeSaved(): x30 is missing.
   // DCHECK(saves.list() == CPURegList::GetCalleeSaved().list());
-  __ PushCPURegList(saves);
+  __ PushCPURegList<TurboAssembler::kSignLR>(saves);
 
   if (returns != 0) {
     __ Claim(returns);
@@ -2981,7 +2974,7 @@
   // Restore registers.
   CPURegList saves = CPURegList(CPURegister::kRegister, kXRegSizeInBits,
                                 call_descriptor->CalleeSavedRegisters());
-  __ PopCPURegList(saves);
+  __ PopCPURegList<TurboAssembler::kAuthLR>(saves);
 
   // Restore fp registers.
   CPURegList saves_fp = CPURegList(CPURegister::kVRegister, kDRegSizeInBits,
diff --git a/src/compiler/backend/arm64/unwinding-info-writer-arm64.cc b/src/compiler/backend/arm64/unwinding-info-writer-arm64.cc
index 3747019..c8a570a 100644
--- a/src/compiler/backend/arm64/unwinding-info-writer-arm64.cc
+++ b/src/compiler/backend/arm64/unwinding-info-writer-arm64.cc
@@ -9,6 +9,9 @@
 namespace internal {
 namespace compiler {
 
+// TODO(v8:10026): When using CFI, we need to generate unwinding info to tell
+// the unwinder that return addresses are signed.
+
 void UnwindingInfoWriter::BeginInstructionBlock(int pc_offset,
                                                 const InstructionBlock* block) {
   if (!enabled()) return;
diff --git a/src/debug/arm64/debug-arm64.cc b/src/debug/arm64/debug-arm64.cc
index 96cd8a7..251856e 100644
--- a/src/debug/arm64/debug-arm64.cc
+++ b/src/debug/arm64/debug-arm64.cc
@@ -38,7 +38,7 @@
   __ Ldr(x1, MemOperand(fp, StandardFrameConstants::kFunctionOffset));
 
   __ Mov(sp, fp);
-  __ Pop(fp, lr);  // Frame, Return address.
+  __ Pop<TurboAssembler::kAuthLR>(fp, lr);
 
   __ LoadTaggedPointerField(
       x0, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset));
diff --git a/src/deoptimizer/arm/deoptimizer-arm.cc b/src/deoptimizer/arm/deoptimizer-arm.cc
index becdc93..5ca6807 100644
--- a/src/deoptimizer/arm/deoptimizer-arm.cc
+++ b/src/deoptimizer/arm/deoptimizer-arm.cc
@@ -262,6 +262,8 @@
   UNREACHABLE();
 }
 
+void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
+
 #undef __
 
 }  // namespace internal
diff --git a/src/deoptimizer/arm64/deoptimizer-arm64.cc b/src/deoptimizer/arm64/deoptimizer-arm64.cc
index 300e65a..1a13377 100644
--- a/src/deoptimizer/arm64/deoptimizer-arm64.cc
+++ b/src/deoptimizer/arm64/deoptimizer-arm64.cc
@@ -9,6 +9,7 @@
 #include "src/codegen/safepoint-table.h"
 #include "src/deoptimizer/deoptimizer.h"
 #include "src/execution/frame-constants.h"
+#include "src/execution/pointer-authentication.h"
 
 namespace v8 {
 namespace internal {
@@ -288,6 +289,9 @@
   __ Ldr(continuation, MemOperand(last_output_frame,
                                   FrameDescription::continuation_offset()));
   __ Ldr(lr, MemOperand(last_output_frame, FrameDescription::pc_offset()));
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+  __ Autiasp();
+#endif
   __ Br(continuation);
 }
 
@@ -297,6 +301,14 @@
 }
 
 void FrameDescription::SetCallerPc(unsigned offset, intptr_t value) {
+  // TODO(v8:10026): check that the pointer is still in the list of allowed
+  // builtins.
+  Address new_context =
+      static_cast<Address>(GetTop()) + offset + kPCOnStackSize;
+  uint64_t old_context = GetTop() + GetFrameSize();
+  PointerAuthentication::ReplaceContext(reinterpret_cast<Address*>(&value),
+                                        old_context, new_context);
+
   SetFrameSlot(offset, value);
 }
 
@@ -309,6 +321,12 @@
   UNREACHABLE();
 }
 
+void FrameDescription::SetPc(intptr_t pc) {
+  // TODO(v8:10026): we should only accept a specific list of allowed builtins
+  // here.
+  pc_ = PointerAuthentication::SignPCWithSP(pc, GetTop());
+}
+
 #undef __
 
 }  // namespace internal
diff --git a/src/deoptimizer/deoptimizer.cc b/src/deoptimizer/deoptimizer.cc
index 56431d4..2e29ac9 100644
--- a/src/deoptimizer/deoptimizer.cc
+++ b/src/deoptimizer/deoptimizer.cc
@@ -14,6 +14,7 @@
 #include "src/codegen/register-configuration.h"
 #include "src/diagnostics/disasm.h"
 #include "src/execution/frames-inl.h"
+#include "src/execution/pointer-authentication.h"
 #include "src/execution/v8threads.h"
 #include "src/handles/global-handles.h"
 #include "src/heap/heap-inl.h"
@@ -252,7 +253,10 @@
           int trampoline_pc = safepoint.trampoline_pc();
           DCHECK_IMPLIES(code == topmost_, safe_to_deopt_);
           // Replace the current pc on the stack with the trampoline.
-          it.frame()->set_pc(code.raw_instruction_start() + trampoline_pc);
+          // TODO(v8:10026): avoid replacing a signed pointer.
+          Address* pc_addr = it.frame()->pc_address();
+          Address new_pc = code.raw_instruction_start() + trampoline_pc;
+          PointerAuthentication::ReplacePC(pc_addr, new_pc, kSystemPointerSize);
         }
       }
     }
@@ -690,6 +694,13 @@
     caller_fp_ = Memory<intptr_t>(fp_address);
     caller_pc_ =
         Memory<intptr_t>(fp_address + CommonFrameConstants::kCallerPCOffset);
+    // Sign caller_pc_ with caller_frame_top_ to be consistent with everything
+    // else here.
+    uint64_t sp = stack_fp_ + StandardFrameConstants::kCallerSPOffset;
+    // TODO(v8:10026): avoid replacing a signed pointer.
+    PointerAuthentication::ReplaceContext(
+        reinterpret_cast<Address*>(&caller_pc_), sp, caller_frame_top_);
+
     input_frame_context_ = Memory<intptr_t>(
         fp_address + CommonFrameConstants::kContextOrFrameTypeOffset);
 
diff --git a/src/deoptimizer/deoptimizer.h b/src/deoptimizer/deoptimizer.h
index beb2a9a..58c6556 100644
--- a/src/deoptimizer/deoptimizer.h
+++ b/src/deoptimizer/deoptimizer.h
@@ -712,7 +712,7 @@
   void SetTop(intptr_t top) { top_ = top; }
 
   intptr_t GetPc() const { return pc_; }
-  void SetPc(intptr_t pc) { pc_ = pc; }
+  void SetPc(intptr_t pc);
 
   intptr_t GetFp() const { return fp_; }
   void SetFp(intptr_t fp) { fp_ = fp; }
diff --git a/src/deoptimizer/ia32/deoptimizer-ia32.cc b/src/deoptimizer/ia32/deoptimizer-ia32.cc
index 2fd424a..fe14a3b 100644
--- a/src/deoptimizer/ia32/deoptimizer-ia32.cc
+++ b/src/deoptimizer/ia32/deoptimizer-ia32.cc
@@ -217,6 +217,8 @@
   UNREACHABLE();
 }
 
+void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
+
 #undef __
 
 }  // namespace internal
diff --git a/src/deoptimizer/mips/deoptimizer-mips.cc b/src/deoptimizer/mips/deoptimizer-mips.cc
index bf82e26..a7c3740 100644
--- a/src/deoptimizer/mips/deoptimizer-mips.cc
+++ b/src/deoptimizer/mips/deoptimizer-mips.cc
@@ -236,6 +236,8 @@
   UNREACHABLE();
 }
 
+void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
+
 #undef __
 
 }  // namespace internal
diff --git a/src/deoptimizer/mips64/deoptimizer-mips64.cc b/src/deoptimizer/mips64/deoptimizer-mips64.cc
index a1138d2..127c0f9 100644
--- a/src/deoptimizer/mips64/deoptimizer-mips64.cc
+++ b/src/deoptimizer/mips64/deoptimizer-mips64.cc
@@ -236,6 +236,8 @@
   UNREACHABLE();
 }
 
+void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
+
 #undef __
 
 }  // namespace internal
diff --git a/src/deoptimizer/ppc/deoptimizer-ppc.cc b/src/deoptimizer/ppc/deoptimizer-ppc.cc
index 1d05968..8e872e0 100644
--- a/src/deoptimizer/ppc/deoptimizer-ppc.cc
+++ b/src/deoptimizer/ppc/deoptimizer-ppc.cc
@@ -258,6 +258,8 @@
   SetFrameSlot(offset, value);
 }
 
+void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
+
 #undef __
 }  // namespace internal
 }  // namespace v8
diff --git a/src/deoptimizer/s390/deoptimizer-s390.cc b/src/deoptimizer/s390/deoptimizer-s390.cc
index 63ea22f..9833457 100644
--- a/src/deoptimizer/s390/deoptimizer-s390.cc
+++ b/src/deoptimizer/s390/deoptimizer-s390.cc
@@ -254,6 +254,8 @@
   UNREACHABLE();
 }
 
+void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
+
 #undef __
 
 }  // namespace internal
diff --git a/src/deoptimizer/x64/deoptimizer-x64.cc b/src/deoptimizer/x64/deoptimizer-x64.cc
index 724062c..e41bf88 100644
--- a/src/deoptimizer/x64/deoptimizer-x64.cc
+++ b/src/deoptimizer/x64/deoptimizer-x64.cc
@@ -221,18 +221,10 @@
 }
 
 void FrameDescription::SetCallerPc(unsigned offset, intptr_t value) {
-  if (kPCOnStackSize == 2 * kSystemPointerSize) {
-    // Zero out the high-32 bit of PC for x32 port.
-    SetFrameSlot(offset + kSystemPointerSize, 0);
-  }
   SetFrameSlot(offset, value);
 }
 
 void FrameDescription::SetCallerFp(unsigned offset, intptr_t value) {
-  if (kFPOnStackSize == 2 * kSystemPointerSize) {
-    // Zero out the high-32 bit of FP for x32 port.
-    SetFrameSlot(offset + kSystemPointerSize, 0);
-  }
   SetFrameSlot(offset, value);
 }
 
@@ -241,6 +233,8 @@
   UNREACHABLE();
 }
 
+void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
+
 #undef __
 
 }  // namespace internal
diff --git a/src/diagnostics/unwinder.cc b/src/diagnostics/unwinder.cc
index 64adf17..c08fe20 100644
--- a/src/diagnostics/unwinder.cc
+++ b/src/diagnostics/unwinder.cc
@@ -7,6 +7,7 @@
 #include "include/v8.h"
 #include "src/common/globals.h"
 #include "src/execution/frame-constants.h"
+#include "src/execution/pointer-authentication.h"
 
 namespace v8 {
 
@@ -87,8 +88,9 @@
     caller_pc_offset = i::EntryFrameConstants::kDirectCallerPCOffset;
   }
 #endif
-  return reinterpret_cast<void*>(
-      Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset));
+  i::Address ret_addr =
+      Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset);
+  return reinterpret_cast<void*>(i::PointerAuthentication::StripPAC(ret_addr));
 }
 
 void* GetReturnAddressFromFP(void* fp, void* pc,
@@ -99,8 +101,9 @@
     caller_pc_offset = i::EntryFrameConstants::kDirectCallerPCOffset;
   }
 #endif
-  return reinterpret_cast<void*>(
-      Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset));
+  i::Address ret_addr =
+      Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset);
+  return reinterpret_cast<void*>(i::PointerAuthentication::StripPAC(ret_addr));
 }
 
 void* GetCallerFPFromFP(void* fp, void* pc,
diff --git a/src/execution/arm64/pointer-authentication-arm64.h b/src/execution/arm64/pointer-authentication-arm64.h
new file mode 100644
index 0000000..c54a59f
--- /dev/null
+++ b/src/execution/arm64/pointer-authentication-arm64.h
@@ -0,0 +1,164 @@
+// Copyright 2019 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_EXECUTION_ARM64_POINTER_AUTHENTICATION_ARM64_H_
+#define V8_EXECUTION_ARM64_POINTER_AUTHENTICATION_ARM64_H_
+
+#include "src/execution/pointer-authentication.h"
+
+#include "src/common/globals.h"
+#include "src/execution/arm64/simulator-arm64.h"
+
+// TODO(v8:10026): Replace hints with instruction aliases, when supported.
+#define AUTIA1716 "hint #12"
+#define PACIA1716 "hint #8"
+#define XPACLRI "hint #7"
+
+namespace v8 {
+namespace internal {
+
+// The following functions execute on the host and therefore need a different
+// path based on whether we are simulating arm64 or not.
+
+// clang-format fails to detect this file as C++, turn it off.
+// clang-format off
+
+// Authenticate the address stored in {pc_address}. {offset_from_sp} is the
+// offset between {pc_address} and the pointer used as a context for signing.
+V8_INLINE Address PointerAuthentication::AuthenticatePC(
+    Address* pc_address, unsigned offset_from_sp) {
+  uint64_t sp = reinterpret_cast<uint64_t>(pc_address) + offset_from_sp;
+  uint64_t pc = reinterpret_cast<uint64_t>(*pc_address);
+#ifdef USE_SIMULATOR
+  pc = Simulator::AuthPAC(pc, sp, Simulator::kPACKeyIA,
+                          Simulator::kInstructionPointer);
+#else
+  asm volatile(
+      "  mov x17, %[pc]\n"
+      "  mov x16, %[stack_ptr]\n"
+      "  " AUTIA1716 "\n"
+      "  ldr xzr, [x17]\n"
+      "  mov %[pc], x17\n"
+      : [pc] "+r"(pc)
+      : [stack_ptr] "r"(sp)
+      : "x16", "x17");
+#endif
+  return pc;
+}
+
+// Strip Pointer Authentication Code (PAC) from {pc} and return the raw value.
+V8_INLINE Address PointerAuthentication::StripPAC(Address pc) {
+#ifdef USE_SIMULATOR
+  return Simulator::StripPAC(pc, Simulator::kInstructionPointer);
+#else
+  asm volatile(
+      "  mov x16, lr\n"
+      "  mov lr, %[pc]\n"
+      "  " XPACLRI "\n"
+      "  mov %[pc], lr\n"
+      "  mov lr, x16\n"
+      : [pc] "+r"(pc)
+      :
+      : "x16", "lr");
+  return pc;
+#endif
+}
+
+// Sign {pc} using {sp}.
+V8_INLINE Address PointerAuthentication::SignPCWithSP(Address pc, Address sp) {
+#ifdef USE_SIMULATOR
+  return Simulator::AddPAC(pc, sp, Simulator::kPACKeyIA,
+                           Simulator::kInstructionPointer);
+#else
+  asm volatile(
+      "  mov x17, %[pc]\n"
+      "  mov x16, %[sp]\n"
+      "  " PACIA1716 "\n"
+      "  mov %[pc], x17\n"
+      : [pc] "+r"(pc)
+      : [sp] "r"(sp)
+      : "x16", "x17");
+  return pc;
+#endif
+}
+
+// Authenticate the address stored in {pc_address} and replace it with
+// {new_pc}, after signing it. {offset_from_sp} is the offset between
+// {pc_address} and the pointer used as a context for signing.
+V8_INLINE void PointerAuthentication::ReplacePC(Address* pc_address,
+                                                Address new_pc,
+                                                int offset_from_sp) {
+  uint64_t sp = reinterpret_cast<uint64_t>(pc_address) + offset_from_sp;
+  uint64_t old_pc = reinterpret_cast<uint64_t>(*pc_address);
+#ifdef USE_SIMULATOR
+  uint64_t auth_old_pc = Simulator::AuthPAC(old_pc, sp, Simulator::kPACKeyIA,
+                                            Simulator::kInstructionPointer);
+  uint64_t raw_old_pc =
+      Simulator::StripPAC(old_pc, Simulator::kInstructionPointer);
+  // Verify that the old address is authenticated.
+  CHECK_EQ(auth_old_pc, raw_old_pc);
+  new_pc = Simulator::AddPAC(new_pc, sp, Simulator::kPACKeyIA,
+                             Simulator::kInstructionPointer);
+#else
+  // Only store newly signed address after we have verified that the old
+  // address is authenticated.
+  asm volatile(
+      "  mov x17, %[new_pc]\n"
+      "  mov x16, %[sp]\n"
+      "  " PACIA1716 "\n"
+      "  mov %[new_pc], x17\n"
+      "  mov x17, %[old_pc]\n"
+      "  " AUTIA1716 "\n"
+      "  ldr xzr, [x17]\n"
+      : [new_pc] "+&r"(new_pc)
+      : [sp] "r"(sp), [old_pc] "r"(old_pc)
+      : "x16", "x17");
+#endif
+  *pc_address = new_pc;
+}
+
+// Authenticate the address stored in {pc_address} based on {old_context} and
+// replace it with the same address signed with {new_context} instead.
+V8_INLINE void PointerAuthentication::ReplaceContext(Address* pc_address,
+                                                     Address old_context,
+                                                     Address new_context) {
+  uint64_t old_signed_pc = static_cast<uint64_t>(*pc_address);
+  uint64_t new_pc;
+#ifdef USE_SIMULATOR
+  uint64_t auth_pc =
+      Simulator::AuthPAC(old_signed_pc, old_context, Simulator::kPACKeyIA,
+                         Simulator::kInstructionPointer);
+  uint64_t raw_pc =
+      Simulator::StripPAC(auth_pc, Simulator::kInstructionPointer);
+  // Verify that the old address is authenticated.
+  CHECK_EQ(raw_pc, auth_pc);
+  new_pc = Simulator::AddPAC(raw_pc, new_context, Simulator::kPACKeyIA,
+                             Simulator::kInstructionPointer);
+#else
+  // Only store newly signed address after we have verified that the old
+  // address is authenticated.
+  asm volatile(
+      "  mov x17, %[old_pc]\n"
+      "  mov x16, %[old_ctx]\n"
+      "  " AUTIA1716 "\n"
+      "  mov x16, %[new_ctx]\n"
+      "  " PACIA1716 "\n"
+      "  mov %[new_pc], x17\n"
+      "  mov x17, %[old_pc]\n"
+      "  mov x16, %[old_ctx]\n"
+      "  " AUTIA1716 "\n"
+      "  ldr xzr, [x17]\n"
+      : [new_pc] "=&r"(new_pc)
+      : [old_pc] "r"(old_signed_pc), [old_ctx] "r"(old_context),
+        [new_ctx] "r"(new_context)
+      : "x16", "x17");
+#endif
+  *pc_address = new_pc;
+}
+
+// clang-format on
+
+}  // namespace internal
+}  // namespace v8
+#endif  // V8_EXECUTION_ARM64_POINTER_AUTHENTICATION_ARM64_H_
diff --git a/src/execution/frames-inl.h b/src/execution/frames-inl.h
index f1b979b..0c095a30 100644
--- a/src/execution/frames-inl.h
+++ b/src/execution/frames-inl.h
@@ -9,6 +9,7 @@
 #include "src/execution/frame-constants.h"
 #include "src/execution/frames.h"
 #include "src/execution/isolate.h"
+#include "src/execution/pointer-authentication.h"
 #include "src/objects/objects-inl.h"
 
 namespace v8 {
@@ -69,6 +70,16 @@
   return iterator_->handler();
 }
 
+inline Address StackFrame::callee_pc() const {
+  return state_.callee_pc_address ? ReadPC(state_.callee_pc_address)
+                                  : kNullAddress;
+}
+
+inline Address StackFrame::pc() const { return ReadPC(pc_address()); }
+
+inline Address StackFrame::ReadPC(Address* pc_address) {
+  return PointerAuthentication::AuthenticatePC(pc_address, kSystemPointerSize);
+}
 
 inline Address* StackFrame::ResolveReturnAddressLocation(Address* pc_address) {
   if (return_address_location_resolver_ == nullptr) {
diff --git a/src/execution/frames.cc b/src/execution/frames.cc
index 996f8c9..98bcdbb 100644
--- a/src/execution/frames.cc
+++ b/src/execution/frames.cc
@@ -491,16 +491,18 @@
 
 void StackFrame::IteratePc(RootVisitor* v, Address* pc_address,
                            Address* constant_pool_address, Code holder) {
-  Address pc = *pc_address;
+  Address old_pc = ReadPC(pc_address);
   DCHECK(ReadOnlyHeap::Contains(holder) ||
-         holder.GetHeap()->GcSafeCodeContains(holder, pc));
-  unsigned pc_offset = static_cast<unsigned>(pc - holder.InstructionStart());
+         holder.GetHeap()->GcSafeCodeContains(holder, old_pc));
+  unsigned pc_offset =
+      static_cast<unsigned>(old_pc - holder.InstructionStart());
   Object code = holder;
   v->VisitRootPointer(Root::kTop, nullptr, FullObjectSlot(&code));
   if (code == holder) return;
   holder = Code::unchecked_cast(code);
-  pc = holder.InstructionStart() + pc_offset;
-  *pc_address = pc;
+  Address pc = holder.InstructionStart() + pc_offset;
+  // TODO(v8:10026): avoid replacing a signed pointer.
+  PointerAuthentication::ReplacePC(pc_address, pc, kSystemPointerSize);
   if (FLAG_enable_embedded_constant_pool && constant_pool_address) {
     *constant_pool_address = holder.constant_pool();
   }
@@ -521,6 +523,7 @@
       kSystemPointerSize);
   intptr_t marker = Memory<intptr_t>(
       state->fp + CommonFrameConstants::kContextOrFrameTypeOffset);
+  Address pc = StackFrame::ReadPC(state->pc_address);
   if (!iterator->can_access_heap_objects_) {
     // TODO(titzer): "can_access_heap_objects" is kind of bogus. It really
     // means that we are being called from the profiler, which can interrupt
@@ -535,15 +538,13 @@
     if (!StackFrame::IsTypeMarker(marker)) {
       if (maybe_function.IsSmi()) {
         return NATIVE;
-      } else if (IsInterpreterFramePc(iterator->isolate(), *(state->pc_address),
-                                      state)) {
+      } else if (IsInterpreterFramePc(iterator->isolate(), pc, state)) {
         return INTERPRETED;
       } else {
         return OPTIMIZED;
       }
     }
   } else {
-    Address pc = *(state->pc_address);
     // If the {pc} does not point into WebAssembly code we can rely on the
     // returned {wasm_code} to be null and fall back to {GetContainingCode}.
     wasm::WasmCodeRefScope code_ref_scope;
diff --git a/src/execution/frames.h b/src/execution/frames.h
index 3ffdee4..2eb8c8d 100644
--- a/src/execution/frames.h
+++ b/src/execution/frames.h
@@ -215,9 +215,7 @@
   // Accessors.
   Address sp() const { return state_.sp; }
   Address fp() const { return state_.fp; }
-  Address callee_pc() const {
-    return state_.callee_pc_address ? *state_.callee_pc_address : kNullAddress;
-  }
+  inline Address callee_pc() const;
   Address caller_sp() const { return GetCallerStackPointer(); }
 
   // If this frame is optimized and was dynamically aligned return its old
@@ -225,8 +223,7 @@
   // up one word and become unaligned.
   Address UnpaddedFP() const;
 
-  Address pc() const { return *pc_address(); }
-  void set_pc(Address pc) { *pc_address() = pc; }
+  inline Address pc() const;
 
   Address constant_pool() const { return *constant_pool_address(); }
   void set_constant_pool(Address constant_pool) {
@@ -265,6 +262,8 @@
   static void SetReturnAddressLocationResolver(
       ReturnAddressLocationResolver resolver);
 
+  static inline Address ReadPC(Address* pc_address);
+
   // Resolves pc_address through the resolution address function if one is set.
   static inline Address* ResolveReturnAddressLocation(Address* pc_address);
 
diff --git a/src/execution/pointer-authentication-dummy.h b/src/execution/pointer-authentication-dummy.h
new file mode 100644
index 0000000..32a10dc
--- /dev/null
+++ b/src/execution/pointer-authentication-dummy.h
@@ -0,0 +1,56 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_EXECUTION_POINTER_AUTHENTICATION_DUMMY_H_
+#define V8_EXECUTION_POINTER_AUTHENTICATION_DUMMY_H_
+
+#include "src/execution/pointer-authentication.h"
+
+#include "include/v8.h"
+#include "src/base/macros.h"
+#include "src/common/globals.h"
+
+namespace v8 {
+namespace internal {
+
+// Dummy implementation of the PointerAuthentication class methods, to be used
+// when CFI is not enabled.
+
+// Load return address from {pc_address} and return it.
+V8_INLINE Address PointerAuthentication::AuthenticatePC(
+    Address* pc_address, unsigned offset_from_sp) {
+  USE(offset_from_sp);
+  return *pc_address;
+}
+
+// Return {pc} unmodified.
+V8_INLINE Address PointerAuthentication::StripPAC(Address pc) { return pc; }
+
+// Return {pc} unmodified.
+V8_INLINE Address PointerAuthentication::SignPCWithSP(Address pc, Address sp) {
+  USE(sp);
+  return pc;
+}
+
+// Store {new_pc} to {pc_address} without signing.
+V8_INLINE void PointerAuthentication::ReplacePC(Address* pc_address,
+                                                Address new_pc,
+                                                int offset_from_sp) {
+  USE(offset_from_sp);
+  *pc_address = new_pc;
+}
+
+// Do nothing.
+V8_INLINE void PointerAuthentication::ReplaceContext(Address* pc_address,
+                                                     Address old_context,
+                                                     Address new_context) {
+  USE(pc_address);
+  USE(old_context);
+  USE(new_context);
+}
+
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_EXECUTION_POINTER_AUTHENTICATION_DUMMY_H_
diff --git a/src/execution/pointer-authentication.h b/src/execution/pointer-authentication.h
new file mode 100644
index 0000000..f2d6377
--- /dev/null
+++ b/src/execution/pointer-authentication.h
@@ -0,0 +1,65 @@
+// Copyright 2019 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_EXECUTION_POINTER_AUTHENTICATION_H_
+#define V8_EXECUTION_POINTER_AUTHENTICATION_H_
+
+#include "include/v8.h"
+#include "src/base/macros.h"
+#include "src/common/globals.h"
+
+namespace v8 {
+namespace internal {
+
+class PointerAuthentication : public AllStatic {
+ public:
+  // When CFI is enabled, authenticate the address stored in {pc_address} and
+  // return the authenticated address. {offset_from_sp} is the offset between
+  // {pc_address} and the pointer used as a context for signing.
+  // When CFI is not enabled, simply load return address from {pc_address} and
+  // return it.
+  V8_INLINE static Address AuthenticatePC(Address* pc_address,
+                                          unsigned offset_from_sp);
+
+  // When CFI is enabled, strip Pointer Authentication Code (PAC) from {pc} and
+  // return the raw value.
+  // When CFI is not enabled, return {pc} unmodified.
+  V8_INLINE static Address StripPAC(Address pc);
+
+  // When CFI is enabled, sign {pc} using {sp} and return the signed value.
+  // When CFI is not enabled, return {pc} unmodified.
+  V8_INLINE static Address SignPCWithSP(Address pc, Address sp);
+
+  // When CFI is enabled, authenticate the address stored in {pc_address} and
+  // replace it with {new_pc}, after signing it. {offset_from_sp} is the offset
+  // between {pc_address} and the pointer used as a context for signing.
+  // When CFI is not enabled, store {new_pc} to {pc_address} without signing.
+  V8_INLINE static void ReplacePC(Address* pc_address, Address new_pc,
+                                  int offset_from_sp);
+
+  // When CFI is enabled, authenticate the address stored in {pc_address} based
+  // on {old_context} and replace it with the same address signed with
+  // {new_context} instead.
+  // When CFI is not enabled, do nothing.
+  V8_INLINE static void ReplaceContext(Address* pc_address, Address old_context,
+                                       Address new_context);
+};
+
+}  // namespace internal
+}  // namespace v8
+
+#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
+
+#ifndef V8_TARGET_ARCH_ARM64
+#error "V8_ENABLE_CONTROL_FLOW_INTEGRITY should imply V8_TARGET_ARCH_ARM64"
+#endif
+#include "src/execution/arm64/pointer-authentication-arm64.h"
+
+#else
+
+#include "src/execution/pointer-authentication-dummy.h"
+
+#endif
+
+#endif  // V8_EXECUTION_POINTER_AUTHENTICATION_H_
diff --git a/src/regexp/arm64/regexp-macro-assembler-arm64.cc b/src/regexp/arm64/regexp-macro-assembler-arm64.cc
index 5665881..ba5d51d 100644
--- a/src/regexp/arm64/regexp-macro-assembler-arm64.cc
+++ b/src/regexp/arm64/regexp-macro-assembler-arm64.cc
@@ -740,7 +740,8 @@
   DCHECK_EQ(11, kCalleeSaved.Count());
   registers_to_retain.Combine(lr);
 
-  __ PushCPURegList(registers_to_retain);
+  DCHECK(registers_to_retain.IncludesAliasOf(lr));
+  __ PushCPURegList<TurboAssembler::kSignLR>(registers_to_retain);
   __ PushCPURegList(argument_registers);
 
   // Set frame pointer in place.
@@ -1035,7 +1036,7 @@
   __ Mov(sp, fp);
 
   // Restore registers.
-  __ PopCPURegList(registers_to_retain);
+  __ PopCPURegList<TurboAssembler::kAuthLR>(registers_to_retain);
 
   __ Ret();
 
@@ -1585,14 +1586,14 @@
 
 
 void RegExpMacroAssemblerARM64::RestoreLinkRegister() {
-  __ Pop(lr, xzr);
+  __ Pop<TurboAssembler::kAuthLR>(padreg, lr);
   __ Add(lr, lr, Operand(masm_->CodeObject()));
 }
 
 
 void RegExpMacroAssemblerARM64::SaveLinkRegister() {
   __ Sub(lr, lr, Operand(masm_->CodeObject()));
-  __ Push(xzr, lr);
+  __ Push<TurboAssembler::kSignLR>(lr, padreg);
 }
 
 
diff --git a/src/regexp/regexp-macro-assembler.cc b/src/regexp/regexp-macro-assembler.cc
index 30a9955..7fd5549 100644
--- a/src/regexp/regexp-macro-assembler.cc
+++ b/src/regexp/regexp-macro-assembler.cc
@@ -6,6 +6,7 @@
 
 #include "src/codegen/assembler.h"
 #include "src/execution/isolate-inl.h"
+#include "src/execution/pointer-authentication.h"
 #include "src/execution/simulator.h"
 #include "src/regexp/regexp-stack.h"
 #include "src/strings/unicode-inl.h"
@@ -149,9 +150,10 @@
     Address* return_address, Code re_code, Address* subject,
     const byte** input_start, const byte** input_end) {
   DisallowHeapAllocation no_gc;
+  Address old_pc = PointerAuthentication::AuthenticatePC(return_address, 0);
+  DCHECK_LE(re_code.raw_instruction_start(), old_pc);
+  DCHECK_LE(old_pc, re_code.raw_instruction_end());
 
-  DCHECK(re_code.raw_instruction_start() <= *return_address);
-  DCHECK(*return_address <= re_code.raw_instruction_end());
   StackLimitCheck check(isolate);
   bool js_has_overflowed = check.JsHasOverflowed();
 
@@ -193,9 +195,11 @@
   }
 
   if (*code_handle != re_code) {  // Return address no longer valid
-    intptr_t delta = code_handle->address() - re_code.address();
     // Overwrite the return address on the stack.
-    *return_address += delta;
+    intptr_t delta = code_handle->address() - re_code.address();
+    Address new_pc = old_pc + delta;
+    // TODO(v8:10026): avoid replacing a signed pointer.
+    PointerAuthentication::ReplacePC(return_address, new_pc, 0);
   }
 
   // If we continue, we need to update the subject string addresses.
diff --git a/test/cctest/cctest.h b/test/cctest/cctest.h
index 8a5a5a6..4db0dea 100644
--- a/test/cctest/cctest.h
+++ b/test/cctest/cctest.h
@@ -36,6 +36,7 @@
 #include "src/codegen/register-configuration.h"
 #include "src/debug/debug-interface.h"
 #include "src/execution/isolate.h"
+#include "src/execution/simulator.h"
 #include "src/flags/flags.h"
 #include "src/heap/factory.h"
 #include "src/init/v8.h"
@@ -735,4 +736,65 @@
   DISALLOW_COPY_AND_ASSIGN(TestPlatform);
 };
 
+#if defined(USE_SIMULATOR)
+class SimulatorHelper {
+ public:
+  inline bool Init(v8::Isolate* isolate) {
+    simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
+                     ->thread_local_top()
+                     ->simulator_;
+    // Check if there is active simulator.
+    return simulator_ != nullptr;
+  }
+
+  inline void FillRegisters(v8::RegisterState* state) {
+#if V8_TARGET_ARCH_ARM
+    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
+    state->sp = reinterpret_cast<void*>(
+        simulator_->get_register(v8::internal::Simulator::sp));
+    state->fp = reinterpret_cast<void*>(
+        simulator_->get_register(v8::internal::Simulator::r11));
+    state->lr = reinterpret_cast<void*>(
+        simulator_->get_register(v8::internal::Simulator::lr));
+#elif V8_TARGET_ARCH_ARM64
+    if (simulator_->sp() == 0 || simulator_->fp() == 0) {
+      // It's possible that the simulator is interrupted while it is updating
+      // the sp or fp register. ARM64 simulator does this in two steps:
+      // first setting it to zero and then setting it to a new value.
+      // Bailout if sp/fp doesn't contain the new value.
+      return;
+    }
+    state->pc = reinterpret_cast<void*>(simulator_->pc());
+    state->sp = reinterpret_cast<void*>(simulator_->sp());
+    state->fp = reinterpret_cast<void*>(simulator_->fp());
+    state->lr = reinterpret_cast<void*>(simulator_->lr());
+#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
+    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
+    state->sp = reinterpret_cast<void*>(
+        simulator_->get_register(v8::internal::Simulator::sp));
+    state->fp = reinterpret_cast<void*>(
+        simulator_->get_register(v8::internal::Simulator::fp));
+#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
+    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
+    state->sp = reinterpret_cast<void*>(
+        simulator_->get_register(v8::internal::Simulator::sp));
+    state->fp = reinterpret_cast<void*>(
+        simulator_->get_register(v8::internal::Simulator::fp));
+    state->lr = reinterpret_cast<void*>(simulator_->get_lr());
+#elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X
+    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
+    state->sp = reinterpret_cast<void*>(
+        simulator_->get_register(v8::internal::Simulator::sp));
+    state->fp = reinterpret_cast<void*>(
+        simulator_->get_register(v8::internal::Simulator::fp));
+    state->lr = reinterpret_cast<void*>(
+        simulator_->get_register(v8::internal::Simulator::ra));
+#endif
+  }
+
+ private:
+  v8::internal::Simulator* simulator_;
+};
+#endif  // USE_SIMULATOR
+
 #endif  // ifndef CCTEST_H_
diff --git a/test/cctest/test-assembler-arm64.cc b/test/cctest/test-assembler-arm64.cc
index 0665f7a..52107c6 100644
--- a/test/cctest/test-assembler-arm64.cc
+++ b/test/cctest/test-assembler-arm64.cc
@@ -12227,7 +12227,8 @@
       case PushPopByFour:
         // Push high-numbered registers first (to the highest addresses).
         for (i = reg_count; i >= 4; i -= 4) {
-          __ Push(r[i-1], r[i-2], r[i-3], r[i-4]);
+          __ Push<TurboAssembler::kDontSignLR>(r[i - 1], r[i - 2], r[i - 3],
+                                               r[i - 4]);
         }
         // Finish off the leftovers.
         switch (i) {
@@ -12240,7 +12241,7 @@
         }
         break;
       case PushPopRegList:
-        __ PushSizeRegList(list, reg_size);
+        __ PushSizeRegList<TurboAssembler::kDontSignLR>(list, reg_size);
         break;
     }
 
@@ -12251,7 +12252,8 @@
       case PushPopByFour:
         // Pop low-numbered registers first (from the lowest addresses).
         for (i = 0; i <= (reg_count-4); i += 4) {
-          __ Pop(r[i], r[i+1], r[i+2], r[i+3]);
+          __ Pop<TurboAssembler::kDontAuthLR>(r[i], r[i + 1], r[i + 2],
+                                              r[i + 3]);
         }
         // Finish off the leftovers.
         switch (reg_count - i) {
@@ -12264,7 +12266,7 @@
         }
         break;
       case PushPopRegList:
-        __ PopSizeRegList(list, reg_size);
+        __ PopSizeRegList<TurboAssembler::kDontAuthLR>(list, reg_size);
         break;
     }
   }
@@ -12597,8 +12599,8 @@
   __ PopXRegList(0);
   // Don't push/pop x18 (platform register) or xzr (for alignment)
   RegList all_regs = 0xFFFFFFFF & ~(x18.bit() | x31.bit());
-  __ PushXRegList(all_regs);
-  __ PopXRegList(all_regs);
+  __ PushXRegList<TurboAssembler::kDontSignLR>(all_regs);
+  __ PopXRegList<TurboAssembler::kDontAuthLR>(all_regs);
   __ Drop(12);
 
   END();
@@ -14597,13 +14599,13 @@
 
   __ Bind(&test);
   __ Mov(x0, 0x0);
-  __ Push(lr, xzr);
+  __ Push<TurboAssembler::kDontSignLR>(lr, xzr);
   {
     Assembler::BlockConstPoolScope scope(&masm);
     int offset = (function.pos() - __ pc_offset()) / kInstrSize;
     __ near_call(offset, RelocInfo::NONE);
   }
-  __ Pop(xzr, lr);
+  __ Pop<TurboAssembler::kDontAuthLR>(xzr, lr);
   END();
 
   RUN();
diff --git a/test/cctest/test-sampler-api.cc b/test/cctest/test-sampler-api.cc
index 3c8f352..7197101 100644
--- a/test/cctest/test-sampler-api.cc
+++ b/test/cctest/test-sampler-api.cc
@@ -7,7 +7,6 @@
 #include <map>
 #include <string>
 #include "include/v8.h"
-#include "src/execution/simulator.h"
 #include "src/flags/flags.h"
 #include "test/cctest/cctest.h"
 
@@ -31,68 +30,6 @@
 };
 
 
-#if defined(USE_SIMULATOR)
-class SimulatorHelper {
- public:
-  inline bool Init(v8::Isolate* isolate) {
-    simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
-                     ->thread_local_top()
-                     ->simulator_;
-    // Check if there is active simulator.
-    return simulator_ != nullptr;
-  }
-
-  inline void FillRegisters(v8::RegisterState* state) {
-#if V8_TARGET_ARCH_ARM
-    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
-    state->sp = reinterpret_cast<void*>(
-        simulator_->get_register(v8::internal::Simulator::sp));
-    state->fp = reinterpret_cast<void*>(
-        simulator_->get_register(v8::internal::Simulator::r11));
-    state->lr = reinterpret_cast<void*>(
-        simulator_->get_register(v8::internal::Simulator::lr));
-#elif V8_TARGET_ARCH_ARM64
-    if (simulator_->sp() == 0 || simulator_->fp() == 0) {
-      // It's possible that the simulator is interrupted while it is updating
-      // the sp or fp register. ARM64 simulator does this in two steps:
-      // first setting it to zero and then setting it to a new value.
-      // Bailout if sp/fp doesn't contain the new value.
-      return;
-    }
-    state->pc = reinterpret_cast<void*>(simulator_->pc());
-    state->sp = reinterpret_cast<void*>(simulator_->sp());
-    state->fp = reinterpret_cast<void*>(simulator_->fp());
-    state->lr = reinterpret_cast<void*>(simulator_->lr());
-#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
-    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
-    state->sp = reinterpret_cast<void*>(
-        simulator_->get_register(v8::internal::Simulator::sp));
-    state->fp = reinterpret_cast<void*>(
-        simulator_->get_register(v8::internal::Simulator::fp));
-#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
-    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
-    state->sp = reinterpret_cast<void*>(
-        simulator_->get_register(v8::internal::Simulator::sp));
-    state->fp = reinterpret_cast<void*>(
-        simulator_->get_register(v8::internal::Simulator::fp));
-    state->lr = reinterpret_cast<void*>(simulator_->get_lr());
-#elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X
-    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
-    state->sp = reinterpret_cast<void*>(
-        simulator_->get_register(v8::internal::Simulator::sp));
-    state->fp = reinterpret_cast<void*>(
-        simulator_->get_register(v8::internal::Simulator::fp));
-    state->lr = reinterpret_cast<void*>(
-        simulator_->get_register(v8::internal::Simulator::ra));
-#endif
-  }
-
- private:
-  v8::internal::Simulator* simulator_;
-};
-#endif  // USE_SIMULATOR
-
-
 class SamplingTestHelper {
  public:
   struct CodeEventEntry {
diff --git a/test/cctest/test-unwinder-code-pages.cc b/test/cctest/test-unwinder-code-pages.cc
index 6177be6..fc023e4 100644
--- a/test/cctest/test-unwinder-code-pages.cc
+++ b/test/cctest/test-unwinder-code-pages.cc
@@ -591,6 +591,80 @@
   CHECK(v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
 }
 
+#ifdef USE_SIMULATOR
+// TODO(v8:10026): Make this also work without the simulator. The part that
+// needs modifications is getting the RegisterState.
+class UnwinderTestHelper {
+ public:
+  explicit UnwinderTestHelper(const std::string& test_function)
+      : isolate_(CcTest::isolate()) {
+    CHECK(!instance_);
+    instance_ = this;
+    v8::HandleScope scope(isolate_);
+    v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
+    global->Set(v8_str("TryUnwind"),
+                v8::FunctionTemplate::New(isolate_, TryUnwind));
+    LocalContext env(isolate_, nullptr, global);
+    CompileRun(v8_str(test_function.c_str()));
+  }
+
+  ~UnwinderTestHelper() { instance_ = nullptr; }
+
+ private:
+  static void TryUnwind(const v8::FunctionCallbackInfo<v8::Value>& args) {
+    instance_->DoTryUnwind();
+  }
+
+  void DoTryUnwind() {
+    // Set up RegisterState.
+    v8::RegisterState register_state;
+    SimulatorHelper simulator_helper;
+    if (!simulator_helper.Init(isolate_)) return;
+    simulator_helper.FillRegisters(&register_state);
+    // At this point, the PC will point to a Redirection object, which is not
+    // in V8 as far as the unwinder is concerned. To make this work, point to
+    // the return address, which is in V8, instead.
+    register_state.pc = register_state.lr;
+
+    JSEntryStubs entry_stubs = isolate_->GetJSEntryStubs();
+    MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
+    size_t pages_length =
+        isolate_->CopyCodePages(arraysize(code_pages), code_pages);
+    CHECK_LE(pages_length, arraysize(code_pages));
+
+    void* stack_base = reinterpret_cast<void*>(0xffffffffffffffffL);
+    bool unwound = v8::Unwinder::TryUnwindV8Frames(
+        entry_stubs, pages_length, code_pages, &register_state, stack_base);
+    // Check that we have successfully unwound past js_entry_sp.
+    CHECK(unwound);
+    CHECK_GT(register_state.sp,
+             reinterpret_cast<void*>(CcTest::i_isolate()->js_entry_sp()));
+  }
+
+  v8::Isolate* isolate_;
+
+  static UnwinderTestHelper* instance_;
+};
+
+UnwinderTestHelper* UnwinderTestHelper::instance_;
+
+TEST(Unwind_TwoNestedFunctions_CodePagesAPI) {
+  i::FLAG_allow_natives_syntax = true;
+  const char* test_script =
+      "function test_unwinder_api_inner() {"
+      "  TryUnwind();"
+      "  return 0;"
+      "}"
+      "function test_unwinder_api_outer() {"
+      "  return test_unwinder_api_inner();"
+      "}"
+      "%NeverOptimizeFunction(test_unwinder_api_inner);"
+      "%NeverOptimizeFunction(test_unwinder_api_outer);"
+      "test_unwinder_api_outer();";
+
+  UnwinderTestHelper helper(test_script);
+}
+#endif
 }  // namespace test_unwinder_code_pages
 }  // namespace internal
 }  // namespace v8
diff --git a/test/cctest/test-unwinder.cc b/test/cctest/test-unwinder.cc
index ffe46f4..59c8708 100644
--- a/test/cctest/test-unwinder.cc
+++ b/test/cctest/test-unwinder.cc
@@ -7,6 +7,7 @@
 #include "src/api/api-inl.h"
 #include "src/builtins/builtins.h"
 #include "src/execution/isolate.h"
+#include "src/execution/pointer-authentication.h"
 #include "src/heap/spaces.h"
 #include "src/objects/code-inl.h"
 #include "test/cctest/cctest.h"
@@ -38,6 +39,11 @@
   CHECK_NULL(register_state.pc);
 }
 
+void StorePc(uintptr_t stack[], int index, uintptr_t pc) {
+  Address sp = reinterpret_cast<Address>(&stack[index]) + kSystemPointerSize;
+  stack[index] = PointerAuthentication::SignPCWithSP(pc, sp);
+}
+
 TEST(Unwind_BuiltinPCInMiddle_Success) {
   LocalContext env;
   v8::Isolate* isolate = env->GetIsolate();
@@ -49,7 +55,7 @@
   uintptr_t stack[3];
   void* stack_base = stack + arraysize(stack);
   stack[0] = reinterpret_cast<uintptr_t>(stack + 2);  // saved FP (rbp).
-  stack[1] = 202;  // Return address into C++ code.
+  StorePc(stack, 1, 202);  // Return address into C++ code.
   stack[2] = 303;  // The SP points here in the caller's frame.
 
   register_state.sp = stack;
@@ -93,9 +99,9 @@
   stack[0] = 101;
   // Return address into JS code. It doesn't matter that this is not actually in
   // JSEntry, because we only check that for the top frame.
-  stack[1] = reinterpret_cast<uintptr_t>(code + 10);
+  StorePc(stack, 1, reinterpret_cast<uintptr_t>(code + 10));
   stack[2] = reinterpret_cast<uintptr_t>(stack + 5);  // saved FP (rbp).
-  stack[3] = 303;  // Return address into C++ code.
+  StorePc(stack, 3, 303);  // Return address into C++ code.
   stack[4] = 404;
   stack[5] = 505;
 
@@ -145,7 +151,7 @@
   uintptr_t stack[3];
   void* stack_base = stack + arraysize(stack);
   stack[0] = reinterpret_cast<uintptr_t>(stack + 2);  // saved FP (rbp).
-  stack[1] = 202;  // Return address into C++ code.
+  StorePc(stack, 1, 202);  // Return address into C++ code.
   stack[2] = 303;  // The SP points here in the caller's frame.
 
   register_state.sp = stack;
@@ -213,7 +219,7 @@
   stack[3] = 131;
   stack[4] = 141;
   stack[5] = 151;
-  stack[6] = 100;  // Return address into C++ code.
+  StorePc(stack, 6, 100);  // Return address into C++ code.
   stack[7] = 303;  // The SP points here in the caller's frame.
   stack[8] = 404;
   stack[9] = 505;
@@ -267,7 +273,7 @@
   stack[3] = 131;
   stack[4] = 141;
   stack[5] = reinterpret_cast<uintptr_t>(stack + 9);  // saved FP (rbp).
-  stack[6] = 100;  // Return address into C++ code.
+  StorePc(stack, 6, 100);  // Return address into C++ code.
   stack[7] = 303;  // The SP points here in the caller's frame.
   stack[8] = 404;
   stack[9] = 505;
@@ -311,10 +317,10 @@
   stack[1] = 111;
   stack[2] = reinterpret_cast<uintptr_t>(stack + 5);  // saved FP (rbp).
   // The fake return address is in the JS code range.
-  stack[3] = reinterpret_cast<uintptr_t>(code + 10);
+  StorePc(stack, 3, reinterpret_cast<uintptr_t>(code + 10));
   stack[4] = 141;
   stack[5] = reinterpret_cast<uintptr_t>(stack + 9);  // saved FP (rbp).
-  stack[6] = 100;  // Return address into C++ code.
+  StorePc(stack, 6, 100);  // Return address into C++ code.
   stack[7] = 303;  // The SP points here in the caller's frame.
   stack[8] = 404;
   stack[9] = 505;
@@ -371,7 +377,7 @@
 
   uintptr_t stack[3];
   stack[0] = reinterpret_cast<uintptr_t>(stack + 2);  // saved FP (rbp).
-  stack[1] = 202;  // Return address into C++ code.
+  StorePc(stack, 1, 202);  // Return address into C++ code.
   stack[2] = 303;  // The SP points here in the caller's frame.
 
   register_state.sp = stack;
@@ -414,12 +420,12 @@
   stack[3] = 131;
   stack[4] = 141;
   stack[5] = reinterpret_cast<uintptr_t>(stack + 9);  // saved FP (rbp).
-  stack[6] = reinterpret_cast<uintptr_t>(code + 20);  // JS code.
+  StorePc(stack, 6, reinterpret_cast<uintptr_t>(code + 20));  // JS code.
   stack[7] = 303;  // The SP points here in the caller's frame.
   stack[8] = 404;
   stack[9] = reinterpret_cast<uintptr_t>(stack) +
              (12 * sizeof(uintptr_t));                 // saved FP (OOB).
-  stack[10] = reinterpret_cast<uintptr_t>(code + 20);  // JS code.
+  StorePc(stack, 10, reinterpret_cast<uintptr_t>(code + 20));  // JS code.
 
   register_state.sp = stack;
   register_state.fp = stack + 5;
@@ -435,7 +441,7 @@
   // Change the return address so that it is not in range. We will not range
   // check the stack[9] FP value because we have finished unwinding and the
   // contents of rbp does not necessarily have to be the FP in this case.
-  stack[10] = 202;
+  StorePc(stack, 10, 202);
   unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
                                             stack_base);
   CHECK(unwound);
@@ -549,6 +555,76 @@
   CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
 }
 
+#ifdef USE_SIMULATOR
+// TODO(v8:10026): Make this also work without the simulator. The part that
+// needs modifications is getting the RegisterState.
+class UnwinderTestHelper {
+ public:
+  explicit UnwinderTestHelper(const std::string& test_function)
+      : isolate_(CcTest::isolate()) {
+    CHECK(!instance_);
+    instance_ = this;
+    v8::HandleScope scope(isolate_);
+    v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
+    global->Set(v8_str("TryUnwind"),
+                v8::FunctionTemplate::New(isolate_, TryUnwind));
+    LocalContext env(isolate_, nullptr, global);
+    CompileRun(v8_str(test_function.c_str()));
+  }
+
+  ~UnwinderTestHelper() { instance_ = nullptr; }
+
+ private:
+  static void TryUnwind(const v8::FunctionCallbackInfo<v8::Value>& args) {
+    instance_->DoTryUnwind();
+  }
+
+  void DoTryUnwind() {
+    // Set up RegisterState.
+    v8::RegisterState register_state;
+    SimulatorHelper simulator_helper;
+    if (!simulator_helper.Init(isolate_)) return;
+    simulator_helper.FillRegisters(&register_state);
+    // At this point, the PC will point to a Redirection object, which is not
+    // in V8 as far as the unwinder is concerned. To make this work, point to
+    // the return address, which is in V8, instead.
+    register_state.pc = register_state.lr;
+
+    UnwindState unwind_state = isolate_->GetUnwindState();
+    void* stack_base = reinterpret_cast<void*>(0xffffffffffffffffL);
+    bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state,
+                                                   &register_state, stack_base);
+    // Check that we have successfully unwound past js_entry_sp.
+    CHECK(unwound);
+    CHECK_GT(register_state.sp,
+             reinterpret_cast<void*>(CcTest::i_isolate()->js_entry_sp()));
+  }
+
+  v8::Isolate* isolate_;
+
+  static UnwinderTestHelper* instance_;
+};
+
+UnwinderTestHelper* UnwinderTestHelper::instance_;
+
+TEST(Unwind_TwoNestedFunctions) {
+  i::FLAG_allow_natives_syntax = true;
+  const char* test_script =
+      "function test_unwinder_api_inner() {"
+      "  TryUnwind();"
+      "  return 0;"
+      "}"
+      "function test_unwinder_api_outer() {"
+      "  return test_unwinder_api_inner();"
+      "}"
+      "%NeverOptimizeFunction(test_unwinder_api_inner);"
+      "%NeverOptimizeFunction(test_unwinder_api_outer);"
+      "test_unwinder_api_outer();";
+
+  UnwinderTestHelper helper(test_script);
+}
+#endif
+
 #if __clang__
 #pragma clang diagnostic pop
 #endif