diff --git a/libcxx/include/exception b/libcxx/include/exception
--- a/libcxx/include/exception
+++ b/libcxx/include/exception
@@ -85,8 +85,10 @@
 #include <type_traits>
 #include <version>
 
-#if defined(_LIBCPP_ABI_VCRUNTIME)
-#include <vcruntime_exception.h>
+// <vcruntime_exception.h> defines its own std::exception and std::bad_exception types,
+// which we use in order to be ABI-compatible with other STLs on Windows.
+#if defined(_LIBCPP_ABI_VCRUNTIME) && (!defined(_HAS_EXCEPTIONS) || _HAS_EXCEPTIONS != 0)
+#  include <vcruntime_exception.h>
 #endif
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -96,7 +98,55 @@
 namespace std  // purposefully not using versioning namespace
 {
 
-#if !defined(_LIBCPP_ABI_VCRUNTIME)
+#if defined(_LIBCPP_ABI_VCRUNTIME) && (!defined(_HAS_EXCEPTIONS) || _HAS_EXCEPTIONS != 0)
+// The std::exception class was already included above, but we're explicit about this condition here for clarity
+
+#elif defined(_LIBCPP_ABI_VCRUNTIME) && _HAS_EXCEPTIONS == 0
+// However, <vcruntime_exception.h> does not define std::exception and std::bad_exception
+// when _HAS_EXCEPTIONS == 0.
+//
+// Since libc++ still wants to provide the std::exception hierarchy even when _HAS_EXCEPTIONS == 0
+// (after all those are simply types like any other), we define an ABI-compatible version
+// of the VCRuntime std::exception and std::bad_exception types in that mode.
+
+// Note: details for an ABI compatible implementation were taken from:
+// https://github.com/microsoft/STL/blob/afe0800cd6bf0d163bf678ad4e1feb3c130d1ec5/stl/inc/exception#L80
+
+class _LIBCPP_EXCEPTION_ABI exception { // base of all library exceptions
+public:
+  // This constructor is necessary to compile header new for the _HAS_EXCEPTIONS==0 scenario
+  explicit exception(const char* _Message = "unknown", int = 1) _NOEXCEPT {}
+
+  exception(const exception&) _NOEXCEPT {}
+
+  exception& operator=(const exception&) _NOEXCEPT { return *this; }
+
+  virtual ~exception() _NOEXCEPT {}
+
+  _LIBCPP_NODISCARD virtual const char* what() const _NOEXCEPT { return "unknown exception"; }
+
+  _LIBCPP_NORETURN void _Raise() const {}
+
+protected:
+  virtual void _Doraise() const {}
+
+  const char* _Ptr;
+};
+
+class _LIBCPP_EXCEPTION_ABI bad_exception : public exception {
+public:
+  bad_exception(const char* _Message = "bad exception") _NOEXCEPT : exception(_Message) {}
+
+  ~bad_exception() _NOEXCEPT {}
+
+protected:
+  void _Doraise() const {}
+};
+
+#else // !defined(_LIBCPP_ABI_VCRUNTIME)
+// On all other platforms, we define our own std::exception and std::bad_exception types
+// regardless of whether exceptions are turned on as a language feature.
+
 class _LIBCPP_EXCEPTION_ABI exception
 {
 public:
diff --git a/libcxx/test/configs/llvm-libc++-shared-no-exception-clangcl.cfg.in b/libcxx/test/configs/llvm-libc++-shared-no-exception-clangcl.cfg.in
new file mode 100644
--- /dev/null
+++ b/libcxx/test/configs/llvm-libc++-shared-no-exception-clangcl.cfg.in
@@ -0,0 +1,32 @@
+# This testing configuration handles running the test suite against LLVM's libc++
+# using a DLL, with Clang-cl on Windows.
+
+lit_config.load_config(config, '@CMAKE_CURRENT_BINARY_DIR@/cmake-bridge.cfg')
+
+config.substitutions.append(('%{flags}', '--driver-mode=g++'))
+config.substitutions.append(('%{compile_flags}',
+    '-nostdinc++ -I %{include} -I %{target-include} -I %{libcxx}/test/support -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_STDIO_ISO_WIDE_SPECIFIERS -DNOMINMAX -D_HAS_EXCEPTIONS=0'
+))
+config.substitutions.append(('%{link_flags}',
+    '-nostdlib -L %{lib} -lc++ -lmsvcrt -lmsvcprt -loldnames'
+))
+config.substitutions.append(('%{exec}',
+    '%{executor} --execdir %T --env PATH=%{lib} -- '
+))
+
+# LIBCXX-WINDOWS-FIXME is the feature name used to XFAIL the
+# initial Windows failures until they can be properly diagnosed
+# and fixed. This allows easier detection of new test failures
+# and regressions. Note: New failures should not be suppressed
+# using this feature. (Also see llvm.org/PR32730)
+config.available_features.add('LIBCXX-WINDOWS-FIXME')
+
+import os, site
+site.addsitedir(os.path.join('@LIBCXX_SOURCE_DIR@', 'utils'))
+import libcxx.test.params, libcxx.test.newconfig
+libcxx.test.newconfig.configure(
+    libcxx.test.params.DEFAULT_PARAMETERS,
+    libcxx.test.features.DEFAULT_FEATURES,
+    config,
+    lit_config
+)
diff --git a/libcxx/test/support/test_macros.h b/libcxx/test/support/test_macros.h
--- a/libcxx/test/support/test_macros.h
+++ b/libcxx/test/support/test_macros.h
@@ -179,9 +179,9 @@
 # define RTTI_ASSERT(X)
 #endif
 
-#if !TEST_HAS_FEATURE(cxx_exceptions) && !defined(__cpp_exceptions) \
-     && !defined(__EXCEPTIONS)
-#define TEST_HAS_NO_EXCEPTIONS
+#if (!TEST_HAS_FEATURE(cxx_exceptions) && !defined(__cpp_exceptions) && !defined(__EXCEPTIONS)) ||                     \
+    (defined(_HAS_EXCEPTIONS) && _HAS_EXCEPTIONS == 0)
+#  define TEST_HAS_NO_EXCEPTIONS
 #endif
 
 #if TEST_HAS_FEATURE(address_sanitizer) || TEST_HAS_FEATURE(memory_sanitizer) || \
diff --git a/libcxx/utils/ci/buildkite-pipeline.yml b/libcxx/utils/ci/buildkite-pipeline.yml
--- a/libcxx/utils/ci/buildkite-pipeline.yml
+++ b/libcxx/utils/ci/buildkite-pipeline.yml
@@ -579,6 +579,18 @@
             limit: 2
       timeout_in_minutes: 120
 
+    - label: "Clang-cl (no-exceptions)"
+      command: "bash libcxx/utils/ci/run-buildbot clang-cl-noexceptions"
+      artifact_paths:
+        - "**/test-results.xml"
+        - "**/*.abilist"
+      agents:
+        queue: "windows"
+      retry:
+        automatic:
+          - exit_status: -1  # Agent was lost
+            limit: 2
+
     - label: "MinGW (DLL)"
       command: "bash libcxx/utils/ci/run-buildbot mingw-dll"
       artifact_paths:
diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot
--- a/libcxx/utils/ci/run-buildbot
+++ b/libcxx/utils/ci/run-buildbot
@@ -548,6 +548,17 @@
     echo "+++ Running the libc++ tests"
     ${NINJA} -vC "${BUILD_DIR}" check-cxx
 ;;
+clang-cl-noexceptions)
+    clean
+    # Building libc++ in the same way as in clang-cl-dll above, but running
+    # tests with -D_HAS_EXCEPTIONS=0, which users might set in certain
+    # translation units while using libc++, even if libc++ is built with
+    # exceptions enabled.
+    generate-cmake-libcxx-win  -DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=OFF \
+                               -DLIBCXX_TEST_CONFIG="llvm-libc++-shared-no-exception-clangcl.cfg.in"
+    echo "+++ Running the libc++ tests"
+    ${NINJA} -vC "${BUILD_DIR}" check-cxx
+;;
 mingw-dll)
     clean
     # Explicitly specify the compiler with a triple prefix. The CI
diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py
--- a/libcxx/utils/libcxx/test/features.py
+++ b/libcxx/utils/libcxx/test/features.py
@@ -39,6 +39,13 @@
           when=lambda cfg: hasCompileFlag(cfg, '-Wuser-defined-warnings'),
           actions=[AddCompileFlag('-Wuser-defined-warnings')]),
 
+  # Normally, the feature 'no-exceptions' is set in params.py if
+  # running tests with enable_exceptions=false, but if we didn't set
+  # that and only defined _HAS_EXCEPTIONS=0, pick up on that and set the
+  # no-exceptions feature so certain tests are omitted.
+  # It would probably be fine to just run that test configuration with
+  # enable_exceptions=false too.
+  Feature(name='no-exceptions',                 when=lambda cfg: '_HAS_EXCEPTIONS' in compilerMacros(cfg) and compilerMacros(cfg)['_HAS_EXCEPTIONS'] == '0'),
   Feature(name='has-fblocks',                   when=lambda cfg: hasCompileFlag(cfg, '-fblocks')),
   Feature(name='-fsized-deallocation',          when=lambda cfg: hasCompileFlag(cfg, '-fsized-deallocation')),
   Feature(name='-faligned-allocation',          when=lambda cfg: hasCompileFlag(cfg, '-faligned-allocation')),