V8 x64 backend doesn't emit ABI compliant stack frames

On 64 bit Windows, the OS stack walking does not work because the V8 x64
backend doesn't emit unwinding info and also because it doesn't emit ABI
compliant stack frames. See
https://docs.google.com/document/d/1-wf50jFlii0c_Pr52lm2ZU-49m220nhYMrHDi3vXnh0/edit
for more details.

This problem can be fixed by observing that V8 frames usually all have the same
prolog and epilog:

push rbp,
mov rbp, rsp
...
pop rbp
ret N

and that it is possible to define XDATA (UNWIND_CODEs) that specify how Windows
should walk through V8 frames. Furthermore, since V8 Code objects are all
allocated in the same code-range for an Isolate, it is possible to register a
single PDATA/XDATA entry to cover stack walking for all the code generated
inside that code-range.

This PR contains changes required to enable stack walking on Win64:

EmbeddedFileWriter now adds assembler directives to the builtins
snapshot source file (embedded.cc) to emit additional entries in the .pdata and
in the .xdata section of the V8 executable. This takes care of stack walking
for embedded builtins. (The case of non-embedded builtins is not supported).
The x64 Assembler has been modified to collect the information required to emit
this unwind info for builtins.

Stack walking for jitted code is handled is Isolate.cpp, by registering
dynamically PDATA/XDATA for the whole code-range address space every time a new
Isolate is initialized, and by unregistering them when the Isolate is
destroyed.

Stack walking for WASM jitted code is handled is the same way in
wasm::NativeModule (wasm/wasm-code-manager.cpp).

It is important to note that Crashpad and Breakpad are already registering
PDATA/XDATA to manage and report unhandled exceptions (but not for embedded
builtins). Since it is not possible to register multiple PDATA entries for the
same address range, a new function is added to the V8 API:
SetUnhandledExceptionCallback() can be used by an embedder to register its own
unhandled exception handler for exceptions that arise in v8-generated code.
V8 embedders should be modified accordingly (code for this is in a separate PR
in the Chromium repository:
https://chromium-review.googlesource.com/c/chromium/src/+/1474703).

All these changes are experimental, behind:

the 'v8_win64_unwinding_info' build flag, and
the '--win64-unwinding-info' runtime flag.

Bug: v8:3598
Change-Id: Iea455ab6d0e2bf1c556aa1cf870841d44ab6e4b1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1469329
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#60330}
diff --git a/BUILD.gn b/BUILD.gn
index b843e32..821b31c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -88,6 +88,9 @@
   # Enable embedded builtins.
   v8_enable_embedded_builtins = true
 
+  # Enable the registration of unwinding info for Windows/x64.
+  v8_win64_unwinding_info = false
+
   # Enable code comments for builtins in the snapshot (impacts performance).
   v8_enable_snapshot_code_comments = false
 
@@ -413,6 +416,9 @@
   if (v8_enable_shared_ro_heap) {
     defines += [ "V8_SHARED_RO_HEAP" ]
   }
+  if (v8_win64_unwinding_info) {
+    defines += [ "V8_WIN64_UNWINDING_INFO" ]
+  }
 }
 
 config("toolchain") {
@@ -1121,6 +1127,10 @@
 
     args += invoker.args
 
+    if (v8_win64_unwinding_info) {
+      args += [ "--win64-unwinding-info" ]
+    }
+
     if (v8_enable_embedded_builtins) {
       outputs += [ "$target_gen_dir/embedded${suffix}.S" ]
       args += [
@@ -2876,6 +2886,8 @@
         "src/trap-handler/handler-inside-win.cc",
         "src/trap-handler/handler-inside-win.h",
         "src/trap-handler/handler-outside-win.cc",
+        "src/unwinding-info-win64.cc",
+        "src/unwinding-info-win64.h",
       ]
     }
   } else if (v8_current_cpu == "arm") {
diff --git a/include/v8.h b/include/v8.h
index 6dee8ca..9e65639 100644
--- a/include/v8.h
+++ b/include/v8.h
@@ -7166,6 +7166,13 @@
  */
 typedef void (*JitCodeEventHandler)(const JitCodeEvent* event);
 
+/**
+ * Callback function passed to SetUnhandledExceptionCallback.
+ */
+#if defined(V8_OS_WIN_X64)
+typedef int (*UnhandledExceptionCallback)(
+    _EXCEPTION_POINTERS* exception_pointers);
+#endif
 
 /**
  * Interface for iterating through all external resources in the heap.
@@ -8380,13 +8387,13 @@
   /**
    * Returns a memory range that can potentially contain jitted code. Code for
    * V8's 'builtins' will not be in this range if embedded builtins is enabled.
-   * Instead, see GetEmbeddedCodeRange.
    *
    * On Win64, embedders are advised to install function table callbacks for
    * these ranges, as default SEH won't be able to unwind through jitted code.
-   *
    * The first page of the code range is reserved for the embedder and is
-   * committed, writable, and executable.
+   * committed, writable, and executable, to be used to store unwind data, as
+   * documented in
+   * https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64.
    *
    * Might be empty on other platforms.
    *
@@ -8786,6 +8793,20 @@
    */
   static bool EnableWebAssemblyTrapHandler(bool use_v8_signal_handler);
 
+#if defined(V8_OS_WIN_X64)
+  /**
+   * On Win64, by default V8 does not emit unwinding data for jitted code,
+   * which means the OS cannot walk the stack frames and the system Structured
+   * Exception Handling (SEH) cannot unwind through V8-generated code:
+   * https://code.google.com/p/v8/issues/detail?id=3598.
+   *
+   * This function allows embedders to register a custom exception handler for
+   * exceptions in V8-generated code.
+   */
+  static void SetUnhandledExceptionCallback(
+      UnhandledExceptionCallback unhandled_exception_callback);
+#endif
+
  private:
   V8();
 
diff --git a/include/v8config.h b/include/v8config.h
index e30a582..9a1ee6f 100644
--- a/include/v8config.h
+++ b/include/v8config.h
@@ -364,6 +364,10 @@
 # define V8_EXPORT
 #endif  // BUILDING_V8_SHARED
 
+#if defined(_M_X64) || defined(__x86_64__)
+#  define V8_OS_WIN_X64 true
+#endif
+
 #else  // V8_OS_WIN
 
 // Setup for Linux shared library export.
diff --git a/src/api.cc b/src/api.cc
index a5bb14f..3069629 100644
--- a/src/api.cc
+++ b/src/api.cc
@@ -116,10 +116,14 @@
 #endif
 
 #if V8_OS_WIN
+#include <versionhelpers.h>
 #include <windows.h>
 #include "include/v8-wasm-trap-handler-win.h"
 #include "src/trap-handler/handler-inside-win.h"
-#endif
+#if V8_TARGET_ARCH_X64
+#include "src/unwinding-info-win64.h"
+#endif  // V8_TARGET_ARCH_X64
+#endif  // V8_OS_WIN
 
 namespace v8 {
 
@@ -5787,6 +5791,14 @@
   return v8::internal::trap_handler::EnableTrapHandler(use_v8_signal_handler);
 }
 
+#if defined(V8_OS_WIN_X64)
+void V8::SetUnhandledExceptionCallback(
+    UnhandledExceptionCallback unhandled_exception_callback) {
+  v8::internal::win64_unwindinfo::SetUnhandledExceptionCallback(
+      unhandled_exception_callback);
+}
+#endif
+
 void v8::V8::SetEntropySource(EntropySource entropy_source) {
   base::RandomNumberGenerator::SetEntropySource(entropy_source);
 }
diff --git a/src/assembler.h b/src/assembler.h
index 7efaf6a..d39d8f2 100644
--- a/src/assembler.h
+++ b/src/assembler.h
@@ -166,6 +166,10 @@
   // this flag, the code range must be small enough to fit all offsets into
   // the instruction immediates.
   bool use_pc_relative_calls_and_jumps = false;
+  // Enables the collection of information useful for the generation of unwind
+  // info. This is useful in some platform (Win64) where the unwind info depends
+  // on a function prologue/epilogue.
+  bool collect_win64_unwind_info = false;
 
   // Constructs V8-agnostic set of options from current state.
   AssemblerOptions EnableV8AgnosticCode() const;
diff --git a/src/base/win32-headers.h b/src/base/win32-headers.h
index b61ce71..8255546 100644
--- a/src/base/win32-headers.h
+++ b/src/base/win32-headers.h
@@ -45,8 +45,9 @@
 #define _WIN32_WINNT 0x501
 #endif  // __MINGW32__
 #if !defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR)
-#include <dbghelp.h>  // For SymLoadModule64 and al.
-#include <errno.h>  // For STRUNCATE
+#include <dbghelp.h>         // For SymLoadModule64 and al.
+#include <errno.h>           // For STRUNCATE
+#include <versionhelpers.h>  // For IsWindows8OrGreater().
 #endif  // !defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR)
 #include <limits.h>  // For INT_MAX and al.
 #include <tlhelp32.h>  // For Module32First and al.
diff --git a/src/builtins/setup-builtins-internal.cc b/src/builtins/setup-builtins-internal.cc
index 23e03a7..44f27f2 100644
--- a/src/builtins/setup-builtins-internal.cc
+++ b/src/builtins/setup-builtins-internal.cc
@@ -41,6 +41,7 @@
   AssemblerOptions options = AssemblerOptions::Default(isolate);
   CHECK(!options.isolate_independent_code);
   CHECK(!options.use_pc_relative_calls_and_jumps);
+  CHECK(!options.collect_win64_unwind_info);
 
   if (!isolate->IsGeneratingEmbeddedBuiltins() ||
       !Builtins::IsIsolateIndependent(builtin_index)) {
@@ -56,6 +57,7 @@
 
   options.isolate_independent_code = true;
   options.use_pc_relative_calls_and_jumps = pc_relative_calls_fit_in_code_range;
+  options.collect_win64_unwind_info = true;
 
   return options;
 }
@@ -126,6 +128,9 @@
       desc, Code::BUILTIN, masm.CodeObject(), builtin_index,
       MaybeHandle<ByteArray>(), DeoptimizationData::Empty(isolate), kMovable,
       kIsNotTurbofanned, kStackSlots);
+#if defined(V8_OS_WIN_X64)
+  isolate->SetBuiltinUnwindData(builtin_index, masm.GetUnwindInfo());
+#endif
   PostBuildProfileAndTracing(isolate, *code, s_name);
   return *code;
 }
diff --git a/src/compiler/backend/code-generator.cc b/src/compiler/backend/code-generator.cc
index 6644dcf..cba7637 100644
--- a/src/compiler/backend/code-generator.cc
+++ b/src/compiler/backend/code-generator.cc
@@ -396,6 +396,14 @@
   // Allocate and install the code.
   CodeDesc desc;
   tasm()->GetCode(isolate(), &desc, safepoints(), handler_table_offset_);
+
+#if defined(V8_OS_WIN_X64)
+  if (Builtins::IsBuiltinId(info_->builtin_index())) {
+    isolate_->SetBuiltinUnwindData(info_->builtin_index(),
+                                   tasm()->GetUnwindInfo());
+  }
+#endif
+
   if (unwinding_info_writer_.eh_frame_writer()) {
     unwinding_info_writer_.eh_frame_writer()->GetEhFrame(&desc);
   }
diff --git a/src/flag-definitions.h b/src/flag-definitions.h
index 75d8167..389b7fd 100644
--- a/src/flag-definitions.h
+++ b/src/flag-definitions.h
@@ -1395,6 +1395,9 @@
 DEFINE_BOOL(print_opt_source, false,
             "print source code of optimized and inlined functions")
 
+DEFINE_BOOL(win64_unwinding_info, false,
+            "Enable unwinding info for Windows/x64 (experimental).")
+
 #ifdef V8_TARGET_ARCH_ARM
 // Unsupported on arm. See https://crbug.com/v8/8713.
 DEFINE_BOOL_READONLY(
diff --git a/src/heap/heap.cc b/src/heap/heap.cc
index 18e3577..fb923cd 100644
--- a/src/heap/heap.cc
+++ b/src/heap/heap.cc
@@ -2499,6 +2499,10 @@
   return 0;
 }
 
+size_t Heap::GetCodeRangeReservedAreaSize() {
+  return kReservedCodeRangePages * MemoryAllocator::GetCommitPageSize();
+}
+
 HeapObject Heap::PrecedeWithFiller(HeapObject object, int filler_size) {
   CreateFillerObjectAt(object->address(), filler_size, ClearRecordedSlots::kNo);
   return HeapObject::FromAddress(object->address() + filler_size);
diff --git a/src/heap/heap.h b/src/heap/heap.h
index cb2c4ca..4076b23 100644
--- a/src/heap/heap.h
+++ b/src/heap/heap.h
@@ -276,6 +276,10 @@
   // given alignment.
   static int GetFillToAlign(Address address, AllocationAlignment alignment);
 
+  // Returns the size of the initial area of a code-range, which is marked
+  // writable and reserved to contain unwind information.
+  static size_t GetCodeRangeReservedAreaSize();
+
   void FatalProcessOutOfMemory(const char* location);
 
   // Checks whether the space is valid.
diff --git a/src/isolate.cc b/src/isolate.cc
index 1e3f800..8dd7c03 100644
--- a/src/isolate.cc
+++ b/src/isolate.cc
@@ -84,6 +84,10 @@
 #include "unicode/uobject.h"
 #endif  // V8_INTL_SUPPORT
 
+#if defined(V8_OS_WIN_X64)
+#include "src/unwinding-info-win64.h"
+#endif
+
 extern "C" const uint8_t* v8_Default_embedded_blob_;
 extern "C" uint32_t v8_Default_embedded_blob_size_;
 
@@ -2925,6 +2929,16 @@
     heap_profiler()->StopSamplingHeapProfiler();
   }
 
+#if defined(V8_OS_WIN_X64)
+  if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange() &&
+      heap()->memory_allocator()) {
+    const base::AddressRegion& code_range =
+        heap()->memory_allocator()->code_range();
+    void* start = reinterpret_cast<void*>(code_range.begin());
+    win64_unwindinfo::UnregisterNonABICompliantCodeRange(start);
+  }
+#endif
+
   debug()->Unload();
 
   wasm_engine()->DeleteCompileJobsOnIsolate(this);
@@ -3495,6 +3509,16 @@
                                                sampling_flags);
   }
 
+#if defined(V8_OS_WIN_X64)
+  if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange()) {
+    const base::AddressRegion& code_range =
+        heap()->memory_allocator()->code_range();
+    void* start = reinterpret_cast<void*>(code_range.begin());
+    size_t size_in_bytes = code_range.size();
+    win64_unwindinfo::RegisterNonABICompliantCodeRange(start, size_in_bytes);
+  }
+#endif
+
   if (create_heap_objects && FLAG_profile_deserialization) {
     double ms = timer.Elapsed().InMillisecondsF();
     PrintF("[Initializing isolate from scratch took %0.3f ms]\n", ms);
@@ -4303,6 +4327,16 @@
   }
 }
 
+#if defined(V8_OS_WIN_X64)
+void Isolate::SetBuiltinUnwindData(
+    int builtin_index,
+    const win64_unwindinfo::BuiltinUnwindInfo& unwinding_info) {
+  if (embedded_file_writer_ != nullptr) {
+    embedded_file_writer_->SetBuiltinUnwindData(builtin_index, unwinding_info);
+  }
+}
+#endif
+
 void Isolate::SetPrepareStackTraceCallback(PrepareStackTraceCallback callback) {
   prepare_stack_trace_callback_ = callback;
 }
diff --git a/src/isolate.h b/src/isolate.h
index 27bf1ee..1823764 100644
--- a/src/isolate.h
+++ b/src/isolate.h
@@ -114,6 +114,10 @@
 class WasmEngine;
 }
 
+namespace win64_unwindinfo {
+class BuiltinUnwindInfo;
+}
+
 #define RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate) \
   do {                                                 \
     Isolate* __isolate__ = (isolate);                  \
@@ -1442,6 +1446,12 @@
   // annotate the builtin blob with debugging information.
   void PrepareBuiltinSourcePositionMap();
 
+#if defined(V8_OS_WIN_X64)
+  void SetBuiltinUnwindData(
+      int builtin_index,
+      const win64_unwindinfo::BuiltinUnwindInfo& unwinding_info);
+#endif
+
   void SetPrepareStackTraceCallback(PrepareStackTraceCallback callback);
   MaybeHandle<Object> RunPrepareStackTraceCallback(Handle<Context>,
                                                    Handle<JSObject> Error,
diff --git a/src/snapshot/embedded-file-writer.cc b/src/snapshot/embedded-file-writer.cc
index cfeb6a4..ad5c06a 100644
--- a/src/snapshot/embedded-file-writer.cc
+++ b/src/snapshot/embedded-file-writer.cc
@@ -127,6 +127,119 @@
   }
 }
 
+#if defined(V8_OS_WIN_X64)
+std::string EmbeddedFileWriter::BuiltinsUnwindInfoLabel() const {
+  char embedded_blob_data_symbol[kTemporaryStringLength];
+  i::SNPrintF(i::Vector<char>(embedded_blob_data_symbol),
+              "%s_Builtins_UnwindInfo", embedded_variant_);
+  return embedded_blob_data_symbol;
+}
+
+void EmbeddedFileWriter::SetBuiltinUnwindData(
+    int builtin_index, const win64_unwindinfo::BuiltinUnwindInfo& unwind_info) {
+  DCHECK_LT(builtin_index, Builtins::builtin_count);
+  unwind_infos_[builtin_index] = unwind_info;
+}
+
+void EmbeddedFileWriter::WriteUnwindInfoEntry(
+    PlatformDependentEmbeddedFileWriter* w, uint64_t rva_start,
+    uint64_t rva_end) const {
+  w->DeclareRvaToSymbol(EmbeddedBlobDataSymbol().c_str(), rva_start);
+  w->DeclareRvaToSymbol(EmbeddedBlobDataSymbol().c_str(), rva_end);
+  w->DeclareRvaToSymbol(BuiltinsUnwindInfoLabel().c_str());
+}
+
+void EmbeddedFileWriter::WriteUnwindInfo(PlatformDependentEmbeddedFileWriter* w,
+                                         const i::EmbeddedData* blob) const {
+  // Emit an UNWIND_INFO (XDATA) struct, which contains the unwinding
+  // information that is used for all builtin functions.
+  DCHECK(win64_unwindinfo::CanEmitUnwindInfoForBuiltins());
+  w->Comment("xdata for all the code in the embedded blob.");
+  w->DeclareExternalFunction(CRASH_HANDLER_FUNCTION_NAME_STRING);
+
+  w->StartXdataSection();
+  {
+    w->DeclareLabel(BuiltinsUnwindInfoLabel().c_str());
+    std::vector<uint8_t> xdata =
+        win64_unwindinfo::GetUnwindInfoForBuiltinFunctions();
+    WriteBinaryContentsAsInlineAssembly(w, xdata.data(),
+                                        static_cast<uint32_t>(xdata.size()));
+    w->Comment("    ExceptionHandler");
+    w->DeclareRvaToSymbol(CRASH_HANDLER_FUNCTION_NAME_STRING);
+  }
+  w->EndXdataSection();
+  w->Newline();
+
+  // Emit a RUNTIME_FUNCTION (PDATA) entry for each builtin function, as
+  // documented here:
+  // https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64.
+  w->Comment(
+      "pdata for all the code in the embedded blob (structs of type "
+      "RUNTIME_FUNCTION).");
+  w->Comment("    BeginAddress");
+  w->Comment("    EndAddress");
+  w->Comment("    UnwindInfoAddress");
+  w->StartPdataSection();
+  {
+    Address prev_builtin_end_offset = 0;
+    for (int i = 0; i < Builtins::builtin_count; i++) {
+      // Some builtins are leaf functions from the point of view of Win64 stack
+      // walking: they do not move the stack pointer and do not require a PDATA
+      // entry because the return address can be retrieved from [rsp].
+      if (!blob->ContainsBuiltin(i)) continue;
+      if (unwind_infos_[i].is_leaf_function()) continue;
+
+      uint64_t builtin_start_offset = blob->InstructionStartOfBuiltin(i) -
+                                      reinterpret_cast<Address>(blob->data());
+      uint32_t builtin_size = blob->InstructionSizeOfBuiltin(i);
+
+      const std::vector<int>& xdata_desc = unwind_infos_[i].fp_offsets();
+      if (xdata_desc.empty()) {
+        // Some builtins do not have any "push rbp - mov rbp, rsp" instructions
+        // to start a stack frame. We still emit a PDATA entry as if they had,
+        // relying on the fact that we can find the previous frame address from
+        // rbp in most cases. Note that since the function does not really start
+        // with a 'push rbp' we need to specify the start RVA in the PDATA entry
+        // a few bytes before the beginning of the function, if it does not
+        // overlap the end of the previous builtin.
+        WriteUnwindInfoEntry(
+            w,
+            std::max(prev_builtin_end_offset,
+                     builtin_start_offset - win64_unwindinfo::kRbpPrefixLength),
+            builtin_start_offset + builtin_size);
+      } else {
+        // Some builtins have one or more "push rbp - mov rbp, rsp" sequences,
+        // but not necessarily at the beginning of the function. In this case
+        // we want to yield a PDATA entry for each block of instructions that
+        // emit an rbp frame. If the function does not start with 'push rbp'
+        // we also emit a PDATA entry for the initial block of code up to the
+        // first 'push rbp', like in the case above.
+        if (xdata_desc[0] > 0) {
+          WriteUnwindInfoEntry(w,
+                               std::max(prev_builtin_end_offset,
+                                        builtin_start_offset -
+                                            win64_unwindinfo::kRbpPrefixLength),
+                               builtin_start_offset + xdata_desc[0]);
+        }
+
+        for (size_t j = 0; j < xdata_desc.size(); j++) {
+          int chunk_start = xdata_desc[j];
+          int chunk_end =
+              (j < xdata_desc.size() - 1) ? xdata_desc[j + 1] : builtin_size;
+          WriteUnwindInfoEntry(w, builtin_start_offset + chunk_start,
+                               builtin_start_offset + chunk_end);
+        }
+      }
+
+      prev_builtin_end_offset = builtin_start_offset + builtin_size;
+      w->Newline();
+    }
+  }
+  w->EndPdataSection();
+  w->Newline();
+}
+#endif
+
 // V8_OS_MACOSX
 // Fuchsia target is explicitly excluded here for Mac hosts. This is to avoid
 // generating uncompilable assembly files for the Fuchsia target.
@@ -347,6 +460,42 @@
           DirectiveAsString(PointerSizeDirective()), SYMBOL_PREFIX, target);
 }
 
+#if defined(V8_OS_WIN_X64)
+
+void PlatformDependentEmbeddedFileWriter::StartPdataSection() {
+  fprintf(fp_, "OPTION DOTNAME\n");
+  fprintf(fp_, ".pdata SEGMENT DWORD READ ''\n");
+}
+
+void PlatformDependentEmbeddedFileWriter::EndPdataSection() {
+  fprintf(fp_, ".pdata ENDS\n");
+}
+
+void PlatformDependentEmbeddedFileWriter::StartXdataSection() {
+  fprintf(fp_, "OPTION DOTNAME\n");
+  fprintf(fp_, ".xdata SEGMENT DWORD READ ''\n");
+}
+
+void PlatformDependentEmbeddedFileWriter::EndXdataSection() {
+  fprintf(fp_, ".xdata ENDS\n");
+}
+
+void PlatformDependentEmbeddedFileWriter::DeclareExternalFunction(
+    const char* name) {
+  fprintf(fp_, "EXTERN %s : PROC\n", name);
+}
+
+void PlatformDependentEmbeddedFileWriter::DeclareRvaToSymbol(const char* name,
+                                                             uint64_t offset) {
+  if (offset > 0) {
+    fprintf(fp_, "DD IMAGEREL %s+%llu\n", name, offset);
+  } else {
+    fprintf(fp_, "DD IMAGEREL %s\n", name);
+  }
+}
+
+#endif  // defined(V8_OS_WIN_X64)
+
 void PlatformDependentEmbeddedFileWriter::DeclareSymbolGlobal(
     const char* name) {
   fprintf(fp_, "PUBLIC %s%s\n", SYMBOL_PREFIX, name);
@@ -557,6 +706,34 @@
           SYMBOL_PREFIX, target);
 }
 
+#if defined(V8_OS_WIN_X64)
+
+void PlatformDependentEmbeddedFileWriter::StartPdataSection() {
+  fprintf(fp_, ".section .pdata\n");
+}
+
+void PlatformDependentEmbeddedFileWriter::EndPdataSection() {}
+
+void PlatformDependentEmbeddedFileWriter::StartXdataSection() {
+  fprintf(fp_, ".section .xdata\n");
+}
+
+void PlatformDependentEmbeddedFileWriter::EndXdataSection() {}
+
+void PlatformDependentEmbeddedFileWriter::DeclareExternalFunction(
+    const char* name) {}
+
+void PlatformDependentEmbeddedFileWriter::DeclareRvaToSymbol(const char* name,
+                                                             uint64_t offset) {
+  if (offset > 0) {
+    fprintf(fp_, ".rva %s + %llu\n", name, offset);
+  } else {
+    fprintf(fp_, ".rva %s\n", name);
+  }
+}
+
+#endif  // defined(V8_OS_WIN_X64)
+
 void PlatformDependentEmbeddedFileWriter::DeclareSymbolGlobal(
     const char* name) {
   fprintf(fp_, ".global %s%s\n", SYMBOL_PREFIX, name);
diff --git a/src/snapshot/embedded-file-writer.h b/src/snapshot/embedded-file-writer.h
index 48c1670..fa62016 100644
--- a/src/snapshot/embedded-file-writer.h
+++ b/src/snapshot/embedded-file-writer.h
@@ -12,6 +12,10 @@
 #include "src/snapshot/snapshot.h"
 #include "src/source-position-table.h"
 
+#if defined(V8_OS_WIN_X64)
+#include "src/unwinding-info-win64.h"
+#endif
+
 namespace v8 {
 namespace internal {
 
@@ -41,6 +45,18 @@
   void DeclareUint32(const char* name, uint32_t value);
   void DeclarePointerToSymbol(const char* name, const char* target);
 
+#if defined(V8_OS_WIN_X64)
+  void StartPdataSection();
+  void EndPdataSection();
+  void StartXdataSection();
+  void EndXdataSection();
+  void DeclareExternalFunction(const char* name);
+
+  // Emits an RVA (address relative to the module load address) specified as an
+  // offset from a given symbol.
+  void DeclareRvaToSymbol(const char* name, uint64_t offset = 0);
+#endif
+
   void DeclareLabel(const char* name);
 
   void SourceInfo(int fileid, int line);
@@ -81,6 +97,12 @@
   // The isolate will call the method below just prior to replacing the
   // compiled builtin Code objects with trampolines.
   virtual void PrepareBuiltinSourcePositionMap(Builtins* builtins) = 0;
+
+#if defined(V8_OS_WIN_X64)
+  virtual void SetBuiltinUnwindData(
+      int builtin_index,
+      const win64_unwindinfo::BuiltinUnwindInfo& unwinding_info) = 0;
+#endif
 };
 
 // Generates the embedded.S file which is later compiled into the final v8
@@ -121,6 +143,12 @@
 
   void PrepareBuiltinSourcePositionMap(Builtins* builtins) override;
 
+#if defined(V8_OS_WIN_X64)
+  void SetBuiltinUnwindData(
+      int builtin_index,
+      const win64_unwindinfo::BuiltinUnwindInfo& unwinding_info) override;
+#endif
+
   void SetEmbeddedFile(const char* embedded_src_path) {
     embedded_src_path_ = embedded_src_path;
   }
@@ -183,17 +211,20 @@
   // Fairly arbitrary but should fit all symbol names.
   static constexpr int kTemporaryStringLength = 256;
 
-  void WriteMetadataSection(PlatformDependentEmbeddedFileWriter* w,
-                            const i::EmbeddedData* blob) const {
+  std::string EmbeddedBlobDataSymbol() const {
     char embedded_blob_data_symbol[kTemporaryStringLength];
     i::SNPrintF(i::Vector<char>(embedded_blob_data_symbol),
                 "v8_%s_embedded_blob_data_", embedded_variant_);
+    return embedded_blob_data_symbol;
+  }
 
+  void WriteMetadataSection(PlatformDependentEmbeddedFileWriter* w,
+                            const i::EmbeddedData* blob) const {
     w->Comment("The embedded blob starts here. Metadata comes first, followed");
     w->Comment("by builtin instruction streams.");
     w->SectionText();
     w->AlignToCodeAlignment();
-    w->DeclareLabel(embedded_blob_data_symbol);
+    w->DeclareLabel(EmbeddedBlobDataSymbol().c_str());
 
     WriteBinaryContentsAsInlineAssembly(w, blob->data(),
                                         i::EmbeddedData::RawDataOffset());
@@ -263,10 +294,6 @@
   void WriteFileEpilogue(PlatformDependentEmbeddedFileWriter* w,
                          const i::EmbeddedData* blob) const {
     {
-      char embedded_blob_data_symbol[kTemporaryStringLength];
-      i::SNPrintF(i::Vector<char>(embedded_blob_data_symbol),
-                  "v8_%s_embedded_blob_data_", embedded_variant_);
-
       char embedded_blob_symbol[kTemporaryStringLength];
       i::SNPrintF(i::Vector<char>(embedded_blob_symbol), "v8_%s_embedded_blob_",
                   embedded_variant_);
@@ -275,7 +302,7 @@
       w->SectionData();
       w->AlignToDataAlignment();
       w->DeclarePointerToSymbol(embedded_blob_symbol,
-                                embedded_blob_data_symbol);
+                                EmbeddedBlobDataSymbol().c_str());
       w->Newline();
     }
 
@@ -291,9 +318,23 @@
       w->Newline();
     }
 
+#if defined(V8_OS_WIN_X64)
+    if (win64_unwindinfo::CanEmitUnwindInfoForBuiltins()) {
+      WriteUnwindInfo(w, blob);
+    }
+#endif
+
     w->FileEpilogue();
   }
 
+#if defined(V8_OS_WIN_X64)
+  std::string BuiltinsUnwindInfoLabel() const;
+  void WriteUnwindInfo(PlatformDependentEmbeddedFileWriter* w,
+                       const i::EmbeddedData* blob) const;
+  void WriteUnwindInfoEntry(PlatformDependentEmbeddedFileWriter* w,
+                            uint64_t rva_start, uint64_t rva_end) const;
+#endif
+
 #if defined(_MSC_VER) && !defined(__clang__)
 #define V8_COMPILER_IS_MSVC
 #endif
@@ -411,6 +452,10 @@
 
   std::vector<byte> source_positions_[Builtins::builtin_count];
 
+#if defined(V8_OS_WIN_X64)
+  win64_unwindinfo::BuiltinUnwindInfo unwind_infos_[Builtins::builtin_count];
+#endif
+
   // In assembly directives, filename ids need to begin with 1.
   static const int kFirstExternalFilenameId = 1;
   std::map<const char*, int> external_filenames_;
diff --git a/src/unwinding-info-win64.cc b/src/unwinding-info-win64.cc
new file mode 100644
index 0000000..c96e013
--- /dev/null
+++ b/src/unwinding-info-win64.cc
@@ -0,0 +1,289 @@
+// 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.
+
+#include "src/unwinding-info-win64.h"
+
+#if defined(V8_OS_WIN_X64)
+
+#include "src/allocation.h"
+#include "src/macro-assembler.h"
+#include "src/x64/assembler-x64.h"
+
+namespace v8 {
+namespace internal {
+namespace win64_unwindinfo {
+
+bool CanEmitUnwindInfoForBuiltins() { return FLAG_win64_unwinding_info; }
+
+bool CanRegisterUnwindInfoForNonABICompliantCodeRange() {
+  return !FLAG_jitless;
+}
+
+bool RegisterUnwindInfoForExceptionHandlingOnly() {
+  return !CanEmitUnwindInfoForBuiltins() || !IsWindows8OrGreater();
+}
+
+#pragma pack(push, 1)
+
+/*
+ * From Windows SDK ehdata.h, which does not compile with Clang.
+ * See https://msdn.microsoft.com/en-us/library/ddssxxy8.aspx.
+ */
+typedef union _UNWIND_CODE {
+  struct {
+    unsigned char CodeOffset;
+    unsigned char UnwindOp : 4;
+    unsigned char OpInfo : 4;
+  };
+  uint16_t FrameOffset;
+} UNWIND_CODE, *PUNWIND_CODE;
+
+typedef struct _UNWIND_INFO {
+  unsigned char Version : 3;
+  unsigned char Flags : 5;
+  unsigned char SizeOfProlog;
+  unsigned char CountOfCodes;
+  unsigned char FrameRegister : 4;
+  unsigned char FrameOffset : 4;
+} UNWIND_INFO, *PUNWIND_INFO;
+
+struct V8UnwindData {
+  UNWIND_INFO unwind_info;
+  UNWIND_CODE unwind_codes[2];
+
+  V8UnwindData() {
+    static constexpr int kOpPushNonvol = 0;
+    static constexpr int kOpSetFPReg = 3;
+
+    unwind_info.Version = 1;
+    unwind_info.Flags = UNW_FLAG_EHANDLER;
+    unwind_info.SizeOfProlog = kRbpPrefixLength;
+    unwind_info.CountOfCodes = kRbpPrefixCodes;
+    unwind_info.FrameRegister = rbp.code();
+    unwind_info.FrameOffset = 0;
+
+    unwind_codes[0].CodeOffset = kRbpPrefixLength;  // movq rbp, rsp
+    unwind_codes[0].UnwindOp = kOpSetFPReg;
+    unwind_codes[0].OpInfo = 0;
+
+    unwind_codes[1].CodeOffset = kPushRbpInstructionLength;  // push rbp
+    unwind_codes[1].UnwindOp = kOpPushNonvol;
+    unwind_codes[1].OpInfo = rbp.code();
+  }
+};
+
+struct ExceptionHandlerUnwindData {
+  UNWIND_INFO unwind_info;
+
+  ExceptionHandlerUnwindData() {
+    unwind_info.Version = 1;
+    unwind_info.Flags = UNW_FLAG_EHANDLER;
+    unwind_info.SizeOfProlog = 0;
+    unwind_info.CountOfCodes = 0;
+    unwind_info.FrameRegister = 0;
+    unwind_info.FrameOffset = 0;
+  }
+};
+
+#pragma pack(pop)
+
+v8::UnhandledExceptionCallback unhandled_exception_callback_g = nullptr;
+
+void SetUnhandledExceptionCallback(
+    v8::UnhandledExceptionCallback unhandled_exception_callback) {
+  unhandled_exception_callback_g = unhandled_exception_callback;
+}
+
+// This function is registered as exception handler for V8-generated code as
+// part of the registration of unwinding info. It is referenced by
+// RegisterNonABICompliantCodeRange(), below, and by the unwinding info for
+// builtins declared in the embedded blob.
+extern "C" int CRASH_HANDLER_FUNCTION_NAME(
+    PEXCEPTION_RECORD ExceptionRecord, ULONG64 EstablisherFrame,
+    PCONTEXT ContextRecord, PDISPATCHER_CONTEXT DispatcherContext) {
+  if (unhandled_exception_callback_g != nullptr) {
+    EXCEPTION_POINTERS info = {ExceptionRecord, ContextRecord};
+    return unhandled_exception_callback_g(&info);
+  }
+  return EXCEPTION_CONTINUE_SEARCH;
+}
+
+static constexpr int kMaxExceptionThunkSize = 12;
+
+struct CodeRangeUnwindingRecord {
+  RUNTIME_FUNCTION runtime_function;
+  V8UnwindData unwind_info;
+  uint32_t exception_handler;
+  uint8_t exception_thunk[kMaxExceptionThunkSize];
+  void* dynamic_table;
+};
+
+struct ExceptionHandlerRecord {
+  RUNTIME_FUNCTION runtime_function;
+  ExceptionHandlerUnwindData unwind_info;
+  uint32_t exception_handler;
+  uint8_t exception_thunk[kMaxExceptionThunkSize];
+};
+
+static decltype(
+    &::RtlAddGrowableFunctionTable) add_growable_function_table_func = nullptr;
+static decltype(
+    &::RtlDeleteGrowableFunctionTable) delete_growable_function_table_func =
+    nullptr;
+
+namespace {
+
+void LoadNtdllUnwindingFunctions() {
+  static bool loaded = false;
+  if (loaded) {
+    return;
+  }
+  loaded = true;
+
+  // Load functions from the ntdll.dll module.
+  HMODULE ntdll_module =
+      LoadLibraryEx(L"ntdll.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
+  DCHECK_NOT_NULL(ntdll_module);
+
+  // This fails on Windows 7.
+  add_growable_function_table_func =
+      reinterpret_cast<decltype(&::RtlAddGrowableFunctionTable)>(
+          ::GetProcAddress(ntdll_module, "RtlAddGrowableFunctionTable"));
+  DCHECK_IMPLIES(IsWindows8OrGreater(), add_growable_function_table_func);
+
+  delete_growable_function_table_func =
+      reinterpret_cast<decltype(&::RtlDeleteGrowableFunctionTable)>(
+          ::GetProcAddress(ntdll_module, "RtlDeleteGrowableFunctionTable"));
+  DCHECK_IMPLIES(IsWindows8OrGreater(), delete_growable_function_table_func);
+}
+
+bool AddGrowableFunctionTable(PVOID* DynamicTable,
+                              PRUNTIME_FUNCTION FunctionTable, DWORD EntryCount,
+                              DWORD MaximumEntryCount, ULONG_PTR RangeBase,
+                              ULONG_PTR RangeEnd) {
+  DCHECK(::IsWindows8OrGreater());
+
+  LoadNtdllUnwindingFunctions();
+  DCHECK_NOT_NULL(add_growable_function_table_func);
+
+  *DynamicTable = nullptr;
+  DWORD status =
+      add_growable_function_table_func(DynamicTable, FunctionTable, EntryCount,
+                                       MaximumEntryCount, RangeBase, RangeEnd);
+  DCHECK((status == 0 && *DynamicTable != nullptr) ||
+         status == 0xC000009A);  // STATUS_INSUFFICIENT_RESOURCES
+  return (status == 0);
+}
+
+void DeleteGrowableFunctionTable(PVOID dynamic_table) {
+  DCHECK(::IsWindows8OrGreater());
+
+  LoadNtdllUnwindingFunctions();
+  DCHECK_NOT_NULL(delete_growable_function_table_func);
+
+  delete_growable_function_table_func(dynamic_table);
+}
+
+}  // namespace
+
+std::vector<uint8_t> GetUnwindInfoForBuiltinFunctions() {
+  V8UnwindData xdata;
+  return std::vector<uint8_t>(
+      reinterpret_cast<uint8_t*>(&xdata),
+      reinterpret_cast<uint8_t*>(&xdata) + sizeof(xdata));
+}
+
+template <typename Record>
+void InitUnwindingRecord(Record* record, size_t code_size_in_bytes) {
+  // We assume that the first page of the code range is executable and
+  // committed and reserved to contain PDATA/XDATA.
+
+  // All addresses are 32bit relative offsets to start.
+  record->runtime_function.BeginAddress = 0;
+  record->runtime_function.EndAddress = static_cast<DWORD>(code_size_in_bytes);
+  record->runtime_function.UnwindData = offsetof(Record, unwind_info);
+
+  record->exception_handler = offsetof(Record, exception_thunk);
+
+  // Hardcoded thunk.
+  MacroAssembler masm(AssemblerOptions{}, NewAssemblerBuffer(64));
+  masm.movq(rax, reinterpret_cast<uint64_t>(&CRASH_HANDLER_FUNCTION_NAME));
+  masm.jmp(rax);
+  DCHECK_GE(masm.buffer_size(), sizeof(record->exception_thunk));
+  memcpy(&record->exception_thunk[0], masm.buffer_start(), masm.buffer_size());
+}
+
+void RegisterNonABICompliantCodeRange(void* start, size_t size_in_bytes) {
+  DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());
+
+  // When the --win64-unwinding-info flag is set, we call
+  // RtlAddGrowableFunctionTable to register unwinding info for the whole code
+  // range of an isolate or WASM module. This enables the Windows OS stack
+  // unwinder to work correctly with V8-generated code, enabling stack walking
+  // in Windows debuggers and performance tools. However, the
+  // RtlAddGrowableFunctionTable API is only supported on Windows 8 and above.
+  //
+  // On Windows 7, or when --win64-unwinding-info is not set, we may still need
+  // to call RtlAddFunctionTable to register a custom exception handler passed
+  // by the embedder (like Crashpad).
+
+  if (RegisterUnwindInfoForExceptionHandlingOnly()) {
+    if (unhandled_exception_callback_g) {
+      ExceptionHandlerRecord* record = new (start) ExceptionHandlerRecord();
+      InitUnwindingRecord(record, size_in_bytes);
+
+      CHECK(::RtlAddFunctionTable(&record->runtime_function, 1,
+                                  reinterpret_cast<DWORD64>(start)));
+    }
+  } else {
+    CodeRangeUnwindingRecord* record = new (start) CodeRangeUnwindingRecord();
+    InitUnwindingRecord(record, size_in_bytes);
+
+    CHECK(AddGrowableFunctionTable(
+        &record->dynamic_table, &record->runtime_function, 1, 1,
+        reinterpret_cast<DWORD64>(start),
+        reinterpret_cast<DWORD64>(reinterpret_cast<uint8_t*>(start) +
+                                  size_in_bytes)));
+  }
+
+  // Protect reserved page against modifications.
+  DWORD old_protect;
+  CHECK(VirtualProtect(start, sizeof(CodeRangeUnwindingRecord),
+                       PAGE_EXECUTE_READ, &old_protect));
+}
+
+void UnregisterNonABICompliantCodeRange(void* start) {
+  DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());
+
+  if (RegisterUnwindInfoForExceptionHandlingOnly()) {
+    if (unhandled_exception_callback_g) {
+      ExceptionHandlerRecord* record =
+          reinterpret_cast<ExceptionHandlerRecord*>(start);
+      CHECK(::RtlDeleteFunctionTable(&record->runtime_function));
+    }
+  } else {
+    CodeRangeUnwindingRecord* record =
+        reinterpret_cast<CodeRangeUnwindingRecord*>(start);
+    if (record->dynamic_table) {
+      DeleteGrowableFunctionTable(record->dynamic_table);
+    }
+  }
+}
+
+void XdataEncoder::onPushRbp() {
+  current_push_rbp_offset_ = assembler_.pc_offset() - kPushRbpInstructionLength;
+}
+
+void XdataEncoder::onMovRbpRsp() {
+  if (current_push_rbp_offset_ >= 0 &&
+      current_push_rbp_offset_ == assembler_.pc_offset() - kRbpPrefixLength) {
+    fp_offsets_.push_back(current_push_rbp_offset_);
+  }
+}
+
+}  // namespace win64_unwindinfo
+}  // namespace internal
+}  // namespace v8
+
+#endif  // defined(V8_OS_WIN_X64)
diff --git a/src/unwinding-info-win64.h b/src/unwinding-info-win64.h
new file mode 100644
index 0000000..7d1a31c
--- /dev/null
+++ b/src/unwinding-info-win64.h
@@ -0,0 +1,101 @@
+// 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_UNWINDING_INFO_WIN64_H_
+#define V8_UNWINDING_INFO_WIN64_H_
+
+#include "include/v8config.h"
+
+#if defined(V8_OS_WIN_X64)
+#include "include/v8.h"
+#include "src/base/win32-headers.h"
+#include "src/globals.h"
+
+namespace v8 {
+namespace internal {
+
+namespace win64_unwindinfo {
+
+#define CRASH_HANDLER_FUNCTION_NAME CrashForExceptionInNonABICompliantCodeRange
+#define CRASH_HANDLER_FUNCTION_NAME_STRING \
+  "CrashForExceptionInNonABICompliantCodeRange"
+
+static const int kPushRbpInstructionLength = 1;
+static const int kMovRbpRspInstructionLength = 3;
+static const int kRbpPrefixCodes = 2;
+static const int kRbpPrefixLength =
+    kPushRbpInstructionLength + kMovRbpRspInstructionLength;
+
+/**
+ * Returns true if V8 is configured to emit unwinding data for embedded in the
+ * pdata/xdata sections of the executable. Currently, this happens when V8 is
+ * built with "v8_win64_unwinding_info = true".
+ */
+bool CanEmitUnwindInfoForBuiltins();
+
+/**
+ * Returns true if V8 if we can register unwinding data for the whole code range
+ * of an isolate or WASM module. The first page of the code range is reserved
+ * and writable, to be used to store unwind data, as documented in:
+ * https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64.
+ * In jitless mode V8 does not allocate any executable memory itself so the only
+ * non-abi-compliant code range is in the embedded blob.
+ */
+bool CanRegisterUnwindInfoForNonABICompliantCodeRange();
+
+/**
+ * Registers a custom exception handler for exceptions in V8-generated code.
+ */
+void SetUnhandledExceptionCallback(
+    v8::UnhandledExceptionCallback unhandled_exception_callback);
+
+/**
+ * Returns a vector of bytes that contains the Win64 unwind data used for all
+ * V8 builtin functions.
+ */
+std::vector<uint8_t> GetUnwindInfoForBuiltinFunctions();
+
+void RegisterNonABICompliantCodeRange(void* start, size_t size_in_bytes);
+void UnregisterNonABICompliantCodeRange(void* start);
+
+class BuiltinUnwindInfo {
+ public:
+  BuiltinUnwindInfo() : is_leaf_function_(true) {}
+  explicit BuiltinUnwindInfo(const std::vector<int>& fp_offsets)
+      : is_leaf_function_(false), fp_offsets_(fp_offsets) {}
+
+  bool is_leaf_function() const { return is_leaf_function_; }
+  const std::vector<int>& fp_offsets() const { return fp_offsets_; }
+
+ private:
+  bool is_leaf_function_;
+  std::vector<int> fp_offsets_;
+};
+
+class XdataEncoder {
+ public:
+  explicit XdataEncoder(const Assembler& assembler)
+      : assembler_(assembler), current_push_rbp_offset_(-1) {}
+
+  void onPushRbp();
+  void onMovRbpRsp();
+
+  BuiltinUnwindInfo unwinding_info() const {
+    return BuiltinUnwindInfo(fp_offsets_);
+  }
+
+ private:
+  const Assembler& assembler_;
+  std::vector<int> fp_offsets_;
+  int current_push_rbp_offset_;
+};
+
+}  // namespace win64_unwindinfo
+
+}  // namespace internal
+}  // namespace v8
+
+#endif  // defined(V8_OS_WIN_X64)
+
+#endif  // V8_UNWINDING_INFO_WIN64_H_
diff --git a/src/wasm/wasm-code-manager.cc b/src/wasm/wasm-code-manager.cc
index cd83080..8fa67e2 100644
--- a/src/wasm/wasm-code-manager.cc
+++ b/src/wasm/wasm-code-manager.cc
@@ -27,6 +27,10 @@
 #include "src/wasm/wasm-objects-inl.h"
 #include "src/wasm/wasm-objects.h"
 
+#if defined(V8_OS_WIN_X64)
+#include "src/unwinding-info-win64.h"
+#endif
+
 #define TRACE_HEAP(...)                                   \
   do {                                                    \
     if (FLAG_trace_wasm_native_heap) PrintF(__VA_ARGS__); \
@@ -390,6 +394,18 @@
   owned_code_space_.emplace_back(std::move(code_space));
   owned_code_.reserve(num_functions());
 
+#if defined(V8_OS_WIN_X64)
+  // On some platforms, specifically Win64, we need to reserve some pages at
+  // the beginning of an executable space.
+  // See src/heap/spaces.cc, MemoryAllocator::InitializeCodePageAllocator() and
+  // https://cs.chromium.org/chromium/src/components/crash/content/app/crashpad_win.cc?rcl=fd680447881449fba2edcf0589320e7253719212&l=204
+  // for details.
+  if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange() &&
+      FLAG_win64_unwinding_info) {
+    AllocateForCode(Heap::GetCodeRangeReservedAreaSize());
+  }
+#endif
+
   uint32_t num_wasm_functions = module_->num_declared_functions;
   if (num_wasm_functions > 0) {
     code_table_.reset(new WasmCode*[num_wasm_functions]);
@@ -1124,6 +1140,15 @@
   DCHECK_NOT_NULL(ret);
   TRACE_HEAP("New NativeModule %p: Mem: %" PRIuPTR ",+%zu\n", ret.get(), start,
              size);
+
+#if defined(V8_OS_WIN_X64)
+  if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange() &&
+      FLAG_win64_unwinding_info) {
+    win64_unwindinfo::RegisterNonABICompliantCodeRange(
+        reinterpret_cast<void*>(start), size);
+  }
+#endif
+
   base::MutexGuard lock(&native_modules_mutex_);
   lookup_map_.insert(std::make_pair(start, std::make_pair(end, ret.get())));
   return ret;
@@ -1244,6 +1269,15 @@
     DCHECK(code_space.IsReserved());
     TRACE_HEAP("VMem Release: %" PRIxPTR ":%" PRIxPTR " (%zu)\n",
                code_space.address(), code_space.end(), code_space.size());
+
+#if defined(V8_OS_WIN_X64)
+    if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange() &&
+        FLAG_win64_unwinding_info) {
+      win64_unwindinfo::UnregisterNonABICompliantCodeRange(
+          reinterpret_cast<void*>(code_space.address()));
+    }
+#endif
+
     lookup_map_.erase(code_space.address());
     memory_tracker_->ReleaseReservation(code_space.size());
     code_space.Free();
diff --git a/src/x64/assembler-x64.cc b/src/x64/assembler-x64.cc
index a9c78a8..34161a3 100644
--- a/src/x64/assembler-x64.cc
+++ b/src/x64/assembler-x64.cc
@@ -433,6 +433,12 @@
   if (CpuFeatures::IsSupported(SSE4_1)) {
     EnableCpuFeature(SSSE3);
   }
+
+#if defined(V8_OS_WIN_X64)
+  if (options.collect_win64_unwind_info) {
+    xdata_encoder_ = std::make_unique<win64_unwindinfo::XdataEncoder>(*this);
+  }
+#endif
 }
 
 void Assembler::GetCode(Isolate* isolate, CodeDesc* desc,
@@ -496,6 +502,14 @@
   }
 }
 
+#if defined(V8_OS_WIN_X64)
+win64_unwindinfo::BuiltinUnwindInfo Assembler::GetUnwindInfo() const {
+  DCHECK(options().collect_win64_unwind_info);
+  DCHECK_NOT_NULL(xdata_encoder_);
+  return xdata_encoder_->unwinding_info();
+}
+#endif
+
 void Assembler::Align(int m) {
   DCHECK(base::bits::IsPowerOfTwo(m));
   int delta = (m - (pc_offset() & (m - 1))) & (m - 1);
@@ -1750,6 +1764,12 @@
     emit(0x8B);
     emit_modrm(dst, src);
   }
+
+#if defined(V8_OS_WIN_X64)
+  if (xdata_encoder_ && dst == rbp && src == rsp) {
+    xdata_encoder_->onMovRbpRsp();
+  }
+#endif
 }
 
 void Assembler::emit_mov(Operand dst, Register src, int size) {
@@ -2154,6 +2174,12 @@
   EnsureSpace ensure_space(this);
   emit_optional_rex_32(src);
   emit(0x50 | src.low_bits());
+
+#if defined(V8_OS_WIN_X64)
+  if (xdata_encoder_ && src == rbp) {
+    xdata_encoder_->onPushRbp();
+  }
+#endif
 }
 
 void Assembler::pushq(Operand src) {
diff --git a/src/x64/assembler-x64.h b/src/x64/assembler-x64.h
index 5e1d93b..4f8a4f6 100644
--- a/src/x64/assembler-x64.h
+++ b/src/x64/assembler-x64.h
@@ -47,6 +47,9 @@
 #include "src/x64/constants-x64.h"
 #include "src/x64/register-x64.h"
 #include "src/x64/sse-instr.h"
+#if defined(V8_OS_WIN_X64)
+#include "src/unwinding-info-win64.h"
+#endif
 
 namespace v8 {
 namespace internal {
@@ -1776,6 +1779,10 @@
   byte byte_at(int pos) { return buffer_start_[pos]; }
   void set_byte_at(int pos, byte value) { buffer_start_[pos] = value; }
 
+#if defined(V8_OS_WIN_X64)
+  win64_unwindinfo::BuiltinUnwindInfo GetUnwindInfo() const;
+#endif
+
  protected:
   // Call near indirect
   void call(Operand operand);
@@ -2246,6 +2253,10 @@
   ConstPool constpool_;
 
   friend class ConstPool;
+
+#if defined(V8_OS_WIN_X64)
+  std::unique_ptr<win64_unwindinfo::XdataEncoder> xdata_encoder_;
+#endif
 };
 
 
diff --git a/test/cctest/BUILD.gn b/test/cctest/BUILD.gn
index a58e9e5..6d72e04 100644
--- a/test/cctest/BUILD.gn
+++ b/test/cctest/BUILD.gn
@@ -350,6 +350,9 @@
       "test-log-stack-tracer.cc",
       "test-macro-assembler-x64.cc",
     ]
+    if (is_win) {
+      sources += [ "test-stack-unwinding-x64.cc" ]
+    }
   } else if (v8_current_cpu == "ppc" || v8_current_cpu == "ppc64") {
     sources += [  ### gcmole(arch:ppc) ###
       "test-assembler-ppc.cc",
diff --git a/test/cctest/cctest.status b/test/cctest/cctest.status
index 3bef9cf..fe4569c 100644
--- a/test/cctest/cctest.status
+++ b/test/cctest/cctest.status
@@ -450,6 +450,12 @@
 }],
 
 ##############################################################################
+# Windows stack unwinding is only supported on x64.
+['arch != x64 or system != windows', {
+  'test-stack-unwinding-x64/*': [SKIP]
+}],
+
+##############################################################################
 ['lite_mode or variant == jitless', {
 
   # TODO(8394): First execution events don't work in lite_mode. Enable this after
diff --git a/test/cctest/test-stack-unwinding-x64.cc b/test/cctest/test-stack-unwinding-x64.cc
new file mode 100644
index 0000000..1802c10
--- /dev/null
+++ b/test/cctest/test-stack-unwinding-x64.cc
@@ -0,0 +1,107 @@
+// 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.
+
+#include "src/base/win32-headers.h"
+#include "src/v8.h"
+#include "test/cctest/cctest.h"
+
+class UnwindingWinX64Callbacks {
+ public:
+  UnwindingWinX64Callbacks() = default;
+
+  static void Getter(v8::Local<v8::String> name,
+                     const v8::PropertyCallbackInfo<v8::Value>& info) {
+    // Expects to find at least 15 stack frames in the call stack.
+    // The stack walking should fail on stack frames for builtin functions if
+    // stack unwinding data has not been correctly registered.
+    int stack_frames = CountCallStackFrames(15);
+    CHECK_GE(stack_frames, 15);
+  }
+  static void Setter(v8::Local<v8::String> name, v8::Local<v8::Value> value,
+                     const v8::PropertyCallbackInfo<void>& info) {}
+
+ private:
+  // Windows-specific code to walk the stack starting from the current
+  // instruction pointer.
+  static int CountCallStackFrames(int max_frames) {
+    CONTEXT context_record;
+    ::RtlCaptureContext(&context_record);
+
+    int iframe = 0;
+    while (++iframe < max_frames) {
+      uint64_t image_base;
+      PRUNTIME_FUNCTION function_entry =
+          ::RtlLookupFunctionEntry(context_record.Rip, &image_base, nullptr);
+      if (!function_entry) break;
+
+      void* handler_data;
+      uint64_t establisher_frame;
+      ::RtlVirtualUnwind(UNW_FLAG_NHANDLER, image_base, context_record.Rip,
+                         function_entry, &context_record, &handler_data,
+                         &establisher_frame, NULL);
+    }
+    return iframe;
+  }
+};
+
+// Verifies that stack unwinding data has been correctly registered on Win/x64.
+UNINITIALIZED_TEST(StackUnwindingWinX64) {
+#ifdef V8_WIN64_UNWINDING_INFO
+
+  static const char* unwinding_win_x64_test_source =
+      "function start(count) {\n"
+      "  for (var i = 0; i < count; i++) {\n"
+      "    var o = instance.foo;\n"
+      "    instance.foo = o + 1;\n"
+      "  }\n"
+      "}\n";
+
+  // This test may fail on Windows 7
+  if (!::IsWindows8OrGreater()) {
+    return;
+  }
+
+  i::FLAG_allow_natives_syntax = true;
+  i::FLAG_win64_unwinding_info = true;
+
+  v8::Isolate::CreateParams create_params;
+  create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
+  v8::Isolate* isolate = v8::Isolate::New(create_params);
+  isolate->Enter();
+  {
+    v8::HandleScope scope(isolate);
+    LocalContext env(isolate);
+
+    v8::Local<v8::FunctionTemplate> func_template =
+        v8::FunctionTemplate::New(isolate);
+    v8::Local<v8::ObjectTemplate> instance_template =
+        func_template->InstanceTemplate();
+
+    UnwindingWinX64Callbacks accessors;
+    v8::Local<v8::External> data = v8::External::New(isolate, &accessors);
+    instance_template->SetAccessor(v8_str("foo"),
+                                   &UnwindingWinX64Callbacks::Getter,
+                                   &UnwindingWinX64Callbacks::Setter, data);
+    v8::Local<v8::Function> func =
+        func_template->GetFunction(env.local()).ToLocalChecked();
+    v8::Local<v8::Object> instance =
+        func->NewInstance(env.local()).ToLocalChecked();
+    env->Global()->Set(env.local(), v8_str("instance"), instance).FromJust();
+
+    CompileRun(unwinding_win_x64_test_source);
+    v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(
+        env->Global()->Get(env.local(), v8_str("start")).ToLocalChecked());
+
+    CompileRun("%OptimizeFunctionOnNextCall(start);");
+
+    int32_t repeat_count = 100;
+    v8::Local<v8::Value> args[] = {v8::Integer::New(isolate, repeat_count)};
+    function->Call(env.local(), env.local()->Global(), arraysize(args), args)
+        .ToLocalChecked();
+  }
+  isolate->Exit();
+  isolate->Dispose();
+
+#endif  // V8_WIN64_UNWINDING_INFO
+}