diff a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp
--- a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp
+++ b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp
@@ -63,10 +63,13 @@
 #include "c1/c1_Runtime1.hpp"
 #endif
 #ifdef COMPILER2
 #include "opto/runtime.hpp"
 #endif
+#if INCLUDE_SHENANDOAHGC
+#include "gc/shenandoah/shenandoahRuntime.hpp"
+#endif
 #if INCLUDE_JVMCI
 #include "jvmci/jvmciJavaClasses.hpp"
 #endif
 
 #define __ masm->
@@ -157,10 +160,12 @@
     rbp_off, rbpH_off,        // copy of rbp we will restore
     return_off, returnH_off,  // slot for return address
     reg_save_size             // size in compiler stack slots
   };
 
+  static void adjust_wide_vectors_support(bool& wide_vectors);
+
  public:
   static OopMap* save_live_registers(MacroAssembler* masm, int additional_frame_words, int* total_frame_words, bool save_wide_vectors);
   static void restore_live_registers(MacroAssembler* masm, bool restore_wide_vectors = false);
 
   // Offsets into the register save area
@@ -177,21 +182,27 @@
   // During deoptimization only the result registers need to be restored,
   // all the other values have already been extracted.
   static void restore_result_registers(MacroAssembler* masm);
 };
 
-OopMap* RegisterSaver::save_live_registers(MacroAssembler* masm, int additional_frame_words, int* total_frame_words, bool save_wide_vectors) {
-  int off = 0;
-  int num_xmm_regs = XMMRegister::available_xmm_registers();
+// TODO: Should be upstreamed separately.
+void RegisterSaver::adjust_wide_vectors_support(bool& wide_vectors) {
 #if COMPILER2_OR_JVMCI
-  if (save_wide_vectors && UseAVX == 0) {
-    save_wide_vectors = false; // vectors larger than 16 byte long are supported only with AVX
+  if (wide_vectors && UseAVX == 0) {
+    wide_vectors = false; // vectors larger than 16 byte long are supported only with AVX
   }
-  assert(!save_wide_vectors || MaxVectorSize <= 64, "Only up to 64 byte long vectors are supported");
+  assert(!wide_vectors || MaxVectorSize <= 64, "Only up to 64 byte long vectors are supported");
 #else
-  save_wide_vectors = false; // vectors are generated only by C2 and JVMCI
+  wide_vectors = false; // vectors are generated only by C2 and JVMCI
 #endif
+}
+
+OopMap* RegisterSaver::save_live_registers(MacroAssembler* masm, int additional_frame_words, int* total_frame_words, bool save_wide_vectors) {
+  int off = 0;
+  int num_xmm_regs = XMMRegister::available_xmm_registers();
+
+  adjust_wide_vectors_support(save_wide_vectors);
 
   // Always make the frame size 16-byte aligned, both vector and non vector stacks are always allocated
   int frame_size_in_bytes = align_up(reg_save_size*BytesPerInt, num_xmm_regs);
   // OopMap frame size is in compiler stack slots (jint's) not bytes or words
   int frame_size_in_slots = frame_size_in_bytes / BytesPerInt;
@@ -429,18 +440,11 @@
   if (frame::arg_reg_save_area_bytes != 0) {
     // Pop arg register save area
     __ addptr(rsp, frame::arg_reg_save_area_bytes);
   }
 
-#if COMPILER2_OR_JVMCI
-  if (restore_wide_vectors) {
-    assert(UseAVX > 0, "Vectors larger than 16 byte long are supported only with AVX");
-    assert(MaxVectorSize <= 64, "Only up to 64 byte long vectors are supported");
-  }
-#else
-  assert(!restore_wide_vectors, "vectors are generated only by C2");
-#endif
+  adjust_wide_vectors_support(restore_wide_vectors);
 
   __ vzeroupper();
 
   // On EVEX enabled targets everything is handled in pop fpu state
   if (restore_wide_vectors) {
@@ -3599,5 +3603,103 @@
                                   false);
   return stub;
 }
 
 #endif // INCLUDE_JFR
+
+RuntimeStub* SharedRuntime::generate_shenandoah_stub(StubId stub_id) {
+  assert(UseShenandoahGC, "Only generate when Shenandoah is enabled");
+
+  const char* name = SharedRuntime::stub_name(stub_id);
+  address stub_addr = nullptr;
+  bool returns_obj = true;
+
+  switch (stub_id) {
+    case StubId::shared_shenandoah_keepalive_id: {
+      stub_addr = CAST_FROM_FN_PTR(address, ShenandoahRuntime::write_barrier_pre);
+      returns_obj = false;
+      break;
+    }
+    case StubId::shared_shenandoah_lrb_strong_id: {
+      stub_addr = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong);
+      break;
+    }
+    case StubId::shared_shenandoah_lrb_weak_id: {
+      stub_addr = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak);
+      break;
+    }
+    case StubId::shared_shenandoah_lrb_phantom_id: {
+      stub_addr = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom);
+      break;
+    }
+    case StubId::shared_shenandoah_lrb_strong_narrow_id: {
+      stub_addr = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong_narrow);
+      break;
+    }
+    case StubId::shared_shenandoah_lrb_weak_narrow_id: {
+      stub_addr = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak_narrow);
+      break;
+    }
+    case StubId::shared_shenandoah_lrb_phantom_narrow_id: {
+      stub_addr = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom_narrow);
+      break;
+    }
+    default:
+      ShouldNotReachHere();
+  }
+
+  CodeBuffer code(name, 2048, 64);
+  MacroAssembler* masm = new MacroAssembler(&code);
+  address start = __ pc();
+
+  int frame_size_in_words;
+  OopMap* map = RegisterSaver::save_live_registers(masm, 0, &frame_size_in_words, true);
+  address frame_complete_pc = __ pc();
+
+  address post_call_pc;
+
+  // Call the runtime. This is what MacroAssember::call_VM_leaf does,
+  // but we also want to have exact post-call PC for oop map location.
+  {
+    Label L_stack_aligned, L_end;
+
+    #ifdef _WIN64
+      // Windows always allocates space for it's register args
+      __ subptr(rsp, frame::arg_reg_save_area_bytes);
+    #endif
+
+    __ testptr(rsp, 15);
+    __ jccb(Assembler::zero, L_stack_aligned);
+      __ subptr(rsp, 8);
+      __ call(RuntimeAddress(stub_addr));
+      post_call_pc = __ pc();
+      __ addptr(rsp, 8);
+      __ jmpb(L_end);
+    __ bind(L_stack_aligned);
+      __ call(RuntimeAddress(stub_addr));
+      post_call_pc = __ pc();
+    __ bind(L_end);
+
+    #ifdef _WIN64
+      __ addptr(rsp, frame::arg_reg_save_area_bytes);
+    #endif
+  }
+
+  if (returns_obj) {
+    // RegisterSaver would clobber the call result when restoring.
+    // Carry the result out of this stub by overwriting saved register.
+    __ movptr(Address(rsp, RegisterSaver::rax_offset_in_bytes()), rax);
+  }
+
+  OopMapSet* oop_maps = new OopMapSet();
+  oop_maps->add_gc_map(post_call_pc - start, map);
+
+  RegisterSaver::restore_live_registers(masm, true);
+  __ ret(0);
+
+  return RuntimeStub::new_runtime_stub(name,
+                                       &code,
+                                       frame_complete_pc - start,
+                                       frame_size_in_words,
+                                       oop_maps,
+                                       true);
+}
