[vm] Support assembly output for Windows.

The resulting DLL lacks debugging information / PDB.

TEST=ci
Bug: https://github.com/dart-lang/sdk/issues/60812
Bug: https://github.com/dart-lang/sdk/issues/60813
Cq-Include-Trybots: luci.dart.try:vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64-try,vm-aot-win-release-arm64-try,vm-aot-win-release-x64-try
Change-Id: I305bad0081ec24f27249ad9b75ff8d32fa9c4893
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428200
Reviewed-by: Tess Strickland <sstrickl@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index ac47963..3e6c707 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -956,14 +956,16 @@
     }
 
     var format = _configuration.genSnapshotFormat!;
-    var basename = (format == GenSnapshotFormat.assembly) ? 'out.S' : 'out.aotsnapshot';
+    var output = (format == GenSnapshotFormat.assembly)
+        ? tempAssemblyFile(tempDir)
+        : tempAOTFile(tempDir);
     // Whether or not loading units are used. Mach-O doesn't currently support
     // this, and this isn't done for assembly output to avoid having to handle
     // the assembly of multiple assembly output files.
     var split = format == GenSnapshotFormat.elf;
     var args = [
       "--snapshot-kind=${format.snapshotType}",
-      "--${format.fileOption}=$tempDir/$basename",
+      "--${format.fileOption}=$output",
       if (split) "--loading-unit-manifest=$tempDir/ignored.json",
       if (_isAndroid && (_isArm || _isArmX64)) ...[
         '--no-sim-use-hardfp',
@@ -1074,6 +1076,8 @@
         case Architecture.arm_x64:
           target = ['-arch', 'armv7'];
           break;
+        case Architecture.arm64:
+        case Architecture.arm64c:
         case Architecture.simarm64:
         case Architecture.simarm64c:
           target = ['-arch', 'arm64'];
@@ -1087,6 +1091,25 @@
           target = ['-arch', 'riscv64'];
           break;
       }
+    } else if (Platform.isWindows) {
+      cc = 'buildtools\\win-x64\\clang\\bin\\clang.exe';
+      shared = '-shared';
+      switch (_configuration.architecture) {
+        case Architecture.x64:
+        case Architecture.x64c:
+        case Architecture.simx64:
+        case Architecture.simx64c:
+          target = ['--target=x86_64-windows'];
+          break;
+        case Architecture.arm64:
+        case Architecture.arm64c:
+        case Architecture.simarm64:
+        case Architecture.simarm64c:
+          target = ['--target=arm64-windows'];
+          break;
+      }
+      ldFlags.add('-nostdlib');
+      ldFlags.add('-Wl,/NOENTRY');
     } else {
       throw "Platform not supported: ${Platform.operatingSystem}";
     }
@@ -1096,8 +1119,8 @@
       ...ldFlags,
       shared,
       '-o',
-      '$tempDir/out.aotsnapshot',
-      '$tempDir/out.S'
+      tempAOTFile(tempDir),
+      tempAssemblyFile(tempDir),
     ];
 
     return CompilationCommand('assemble', tempDir, bootstrapDependencies(), cc,
@@ -1109,7 +1132,7 @@
       String tempDir, Map<String, String> environmentOverrides) {
     var stripTool = "$ndkPath/toolchains/llvm/prebuilt/"
         "$host-x86_64/bin/llvm-strip";
-    var args = ['--strip-unneeded', "$tempDir/out.aotsnapshot"];
+    var args = ['--strip-unneeded', tempAOTFile(tempDir)];
     return CompilationCommand('strip', tempDir, bootstrapDependencies(),
         stripTool, args, environmentOverrides,
         alwaysCompile: !_useSdk);
@@ -1124,8 +1147,19 @@
   /// almost identical configurations are tested simultaneously.
   Command computeRemoveAssemblyCommand(String tempDir, List arguments,
       Map<String, String> environmentOverrides) {
-    return CompilationCommand('remove_assembly', tempDir,
-        bootstrapDependencies(), 'rm', ['$tempDir/out.S'], environmentOverrides,
+    String exec;
+    List<String> args;
+
+    if (Platform.isWindows) {
+      exec = "cmd.exe";
+      args = ["/c", "del", tempAssemblyFile(tempDir)];
+    } else {
+      exec = "rm";
+      args = [tempAssemblyFile(tempDir)];
+    }
+
+    return CompilationCommand("remove_assembly", tempDir,
+        bootstrapDependencies(), exec, args, environmentOverrides,
         alwaysCompile: !_useSdk);
   }
 
@@ -1169,8 +1203,7 @@
       // directory on the device, use that one instead.
       dir = DartPrecompiledAdbRuntimeConfiguration.deviceTestDir;
     }
-    originalArguments =
-        _replaceDartFiles(originalArguments, "$dir/out.aotsnapshot");
+    originalArguments = _replaceDartFiles(originalArguments, tempAOTFile(dir));
 
     return [
       if (_enableAsserts) '--enable_asserts',
@@ -1373,6 +1406,21 @@
 
   String tempKernelFile(String tempDir) =>
       Path('$tempDir/out.dill').toNativePath();
+  String tempAssemblyFile(String tempDir) =>
+      Path('$tempDir/out.S').toNativePath();
+  String tempAOTFile(String tempDir) {
+    switch (_configuration.system) {
+      case System.android:
+      case System.fuchsia:
+      case System.linux:
+        return Path('$tempDir/libout.so').toNativePath();
+      case System.mac:
+        return Path('$tempDir/libout.dylib').toNativePath();
+      case System.win:
+        return Path('$tempDir/out.dll').toNativePath();
+    }
+    return Path('$tempDir/out.aotsnapshot').toNativePath();
+  }
 
   Command computeCompileToKernelCommand(String tempDir, List<String> arguments,
       Map<String, String> environmentOverrides) {
diff --git a/pkg/vm_service/test/common/test_helper.dart b/pkg/vm_service/test/common/test_helper.dart
index 7c8dff7..a2a1fad 100644
--- a/pkg/vm_service/test/common/test_helper.dart
+++ b/pkg/vm_service/test/common/test_helper.dart
@@ -35,7 +35,7 @@
     // If running from pub we can assume that we're in the root of the package
     // directory.
     return Uri.parse('test/$script');
-  } else if (io.Platform.script.toFilePath().endsWith('out.aotsnapshot')) {
+  } else if (!io.Platform.script.toFilePath().endsWith('.dart')) {
     // We're running an AOT test. In this case, we need to use the exact URI we
     // launched with.
     return io.Platform.script;
diff --git a/runtime/bin/dartutils.cc b/runtime/bin/dartutils.cc
index 0c5251c..f32af14 100644
--- a/runtime/bin/dartutils.cc
+++ b/runtime/bin/dartutils.cc
@@ -35,6 +35,7 @@
 
 MagicNumberData appjit_magic_number = {8, {0xdc, 0xdc, 0xf6, 0xf6, 0, 0, 0, 0}};
 MagicNumberData aotelf_magic_number = {4, {0x7F, 0x45, 0x4C, 0x46, 0x0}};
+MagicNumberData aotpe_magic_number = {2, {0x4d, 0x5a}};
 MagicNumberData aotcoff_arm32_magic_number = {2, {0x01, 0xC0}};
 MagicNumberData aotcoff_arm64_magic_number = {2, {0xAA, 0x64}};
 MagicNumberData aotcoff_riscv32_magic_number = {2, {0x50, 0x32}};
@@ -404,6 +405,7 @@
   ASSERT(aotelf_magic_number.length <= appjit_magic_number.length);
   ASSERT(static_cast<intptr_t>(sizeof(mach_o::mach_header::magic)) <=
          appjit_magic_number.length);
+  ASSERT(aotpe_magic_number.length <= appjit_magic_number.length);
   ASSERT(aotcoff_arm32_magic_number.length <= appjit_magic_number.length);
   ASSERT(aotcoff_arm64_magic_number.length <= appjit_magic_number.length);
   ASSERT(aotcoff_riscv32_magic_number.length <= appjit_magic_number.length);
@@ -465,6 +467,10 @@
     }
   }
 
+  if (CheckMagicNumber(buffer, buffer_length, aotpe_magic_number)) {
+    return kAotPEMagicNumber;
+  }
+
   if (CheckMagicNumber(buffer, buffer_length, aotcoff_arm32_magic_number)) {
     return kAotCoffARM32MagicNumber;
   }
diff --git a/runtime/bin/dartutils.h b/runtime/bin/dartutils.h
index 2d0aa81..0562ae5 100644
--- a/runtime/bin/dartutils.h
+++ b/runtime/bin/dartutils.h
@@ -264,6 +264,7 @@
     // ("cigam") ones, as we can't load a reverse-endian snapshot anyway.
     kAotMachO32MagicNumber,
     kAotMachO64MagicNumber,
+    kAotPEMagicNumber,
     kAotCoffARM32MagicNumber,
     kAotCoffARM64MagicNumber,
     kAotCoffRISCV32MagicNumber,
diff --git a/runtime/bin/gen_snapshot.cc b/runtime/bin/gen_snapshot.cc
index dcdd49f..c2b5f9b 100644
--- a/runtime/bin/gen_snapshot.cc
+++ b/runtime/bin/gen_snapshot.cc
@@ -297,6 +297,14 @@
             "an output file for --assembly.\n\n");
         return -1;
       }
+#if defined(DART_TARGET_OS_WINDOWS)
+      if (debugging_info_filename != nullptr) {
+        // TODO(https://github.com/dart-lang/sdk/issues/60812): Support PDB.
+        Syslog::PrintErr(
+            "warning: ignoring --save-debugging-info when "
+            "generating assembly for Windows.\n\n");
+      }
+#endif
       break;
     }
   }
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index 675c98b..4af076b 100644
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -3998,7 +3998,8 @@
  *  debugging sections.
  *
  *  If debug_callback_data is provided, debug_callback_data will be used with
- *  the callback to provide separate debugging information.
+ *  the callback to provide separate debugging information. Ignored when
+ *  targeting Windows.
  *
  *  \return A valid handle if no error occurs during the operation.
  */
diff --git a/runtime/tests/vm/dart/awaiter_stacks/harness.dart b/runtime/tests/vm/dart/awaiter_stacks/harness.dart
index 348e0b8..7287fe4 100644
--- a/runtime/tests/vm/dart/awaiter_stacks/harness.dart
+++ b/runtime/tests/vm/dart/awaiter_stacks/harness.dart
@@ -249,8 +249,10 @@
   final stack = StackTrace.current.toString();
   final isObfuscateMode = !stack.contains('shouldSkip');
   final isDwarfStackTracesMode = stack.contains('*** ***');
+  final isDLL = Platform.script.toString().endsWith(".dll");
 
   // We should skip the test if we are running without DWARF stack
-  // traces enabled but with obfuscation.
-  return !isDwarfStackTracesMode && isObfuscateMode;
+  // traces enabled but with obfuscation. Or if running from a DLL,
+  // which lacks DWARF.
+  return isDLL || (!isDwarfStackTracesMode && isObfuscateMode);
 }
diff --git a/runtime/tests/vm/dart/build_id_test.dart b/runtime/tests/vm/dart/build_id_test.dart
index 24f71a8..e2bf205 100644
--- a/runtime/tests/vm/dart/build_id_test.dart
+++ b/runtime/tests/vm/dart/build_id_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:developer';
+import 'dart:io';
 
 import 'package:expect/expect.dart';
 
@@ -10,7 +11,10 @@
 
 void main() {
   final buildId = NativeRuntime.buildId;
-  if (isAOTRuntime) {
+
+  if (Platform.script.toString().endsWith(".dll")) {
+    Expect.isNull(buildId); // No build id in DLLs.
+  } else if (isAOTRuntime) {
     Expect.isNotNull(buildId);
     Expect.isTrue(buildId!.isNotEmpty, 'Build ID is an empty string');
   } else {
diff --git a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_deferred_test.dart b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_deferred_test.dart
index 8804ba0..6fbf870 100644
--- a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_deferred_test.dart
+++ b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_deferred_test.dart
@@ -35,7 +35,11 @@
       'use_dwarf_stack_traces_flag_deferred_program.dart',
     ),
     runNonDwarf,
-    [runElf, runAssembly],
+    [
+      runElf,
+      // Don't run assembly on Windows since DLLs don't contain DWARF.
+      if (!Platform.isWindows) runAssembly,
+    ],
   );
 }
 
diff --git a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_helper.dart b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_helper.dart
index 9c94865..35f5ea1 100644
--- a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_helper.dart
+++ b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_helper.dart
@@ -23,7 +23,7 @@
   if (isSimulator) {
     return "running on a simulated architecture";
   }
-  return (Platform.isLinux || Platform.isMacOS)
+  return (Platform.isLinux || Platform.isMacOS || Platform.isWindows)
       ? false
       : "no process for assembling snapshots on this platform";
 }
diff --git a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart
index dccb920..325aa3c 100644
--- a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart
+++ b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart
@@ -34,7 +34,8 @@
       // Only generate Mach-O on MacOS, since there is no MachOLoader
       // to run the binary on platforms where that isn't the native format.
       if (Platform.isMacOS) runMachODylib,
-      runAssembly,
+      // Don't run assembly on Windows since DLLs don't contain DWARF.
+      if (!Platform.isWindows) runAssembly,
     ],
   );
 }
diff --git a/runtime/tests/vm/dart/use_flag_test_helper.dart b/runtime/tests/vm/dart/use_flag_test_helper.dart
index d40a0d6d..fa4f6ba 100644
--- a/runtime/tests/vm/dart/use_flag_test_helper.dart
+++ b/runtime/tests/vm/dart/use_flag_test_helper.dart
@@ -105,6 +105,7 @@
       return null;
   }
   var clangDir = path.join(sdkDir, 'buildtools', archDir, 'clang', 'bin');
+  print(clangDir);
   return Directory(clangDir).existsSync() ? clangDir : null;
 }
 
@@ -113,7 +114,7 @@
   String snapshotPath, {
   bool debug = false,
 }) async {
-  if (!Platform.isLinux && !Platform.isMacOS) {
+  if (!Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) {
     throw "Unsupported platform ${Platform.operatingSystem} for assembling";
   }
 
@@ -171,20 +172,21 @@
   String strippedPath, {
   bool forceElf = false,
 }) async {
-  if (!Platform.isLinux && !Platform.isMacOS) {
+  if (!Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) {
     throw "Unsupported platform ${Platform.operatingSystem} for stripping";
   }
 
   var strip = 'strip';
 
-  if (isSimulator || (Platform.isMacOS && forceElf)) {
-    final clangBuildTools = clangBuildToolsDir;
-    if (clangBuildTools != null) {
-      strip = path.join(clangBuildTools, 'llvm-strip');
-    } else {
-      throw 'Cannot strip ELF files for ${path.basename(buildDir)} '
-          'without //buildtools on ${Platform.operatingSystem}';
-    }
+  final clangBuildTools = clangBuildToolsDir;
+  if (clangBuildTools != null) {
+    strip = path.join(
+      clangBuildTools,
+      Platform.isWindows ? 'llvm-strip.exe' : 'llvm-strip',
+    );
+  } else {
+    throw 'Cannot strip ELF files for ${path.basename(buildDir)} '
+        'without //buildtools on ${Platform.operatingSystem}';
   }
 
   await run(strip, <String>['-o', strippedPath, snapshotPath]);
diff --git a/runtime/tests/vm/dart/use_resolve_dwarf_paths_flag_test.dart b/runtime/tests/vm/dart/use_resolve_dwarf_paths_flag_test.dart
index 4755d53..883f01b 100644
--- a/runtime/tests/vm/dart/use_resolve_dwarf_paths_flag_test.dart
+++ b/runtime/tests/vm/dart/use_resolve_dwarf_paths_flag_test.dart
@@ -19,6 +19,9 @@
   if (Platform.isAndroid) {
     return;
   }
+  if (Platform.script.toString().endsWith(".dll")) {
+    return;
+  }
 
   final isDwarfStackTraces = StackTrace.current.toString().contains('*** ***');
   if (!isDwarfStackTraces) {
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 48608cc..da748c1 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -6451,7 +6451,13 @@
                                         FullSnapshotWriter::kInitialSize);
   ZoneWriteStream isolate_snapshot_instructions(T->zone(), kInitialSize);
 
-  const bool generate_debug = debug_callback_data != nullptr;
+  bool generate_debug = debug_callback_data != nullptr;
+#if defined(DART_TARGET_OS_WINDOWS)
+  if (format == Dart_AotBinaryFormat_Assembly) {
+    // TODO(https://github.com/dart-lang/sdk/issues/60812): Support PDB.
+    generate_debug = false;  // PDB unimplemented, no DWARF in PE.
+  }
+#endif
 
   auto* const deobfuscation_trie =
       (strip && !generate_debug) ? nullptr
@@ -6574,8 +6580,6 @@
                                     void* debug_callback_data) {
 #if defined(TARGET_ARCH_IA32)
   return Api::NewError("AOT compilation is not supported on IA32.");
-#elif defined(DART_TARGET_OS_WINDOWS)
-  return Api::NewError("Assembly generation is not implemented for Windows.");
 #elif !defined(DART_PRECOMPILER)
   return Api::NewError(
       "This VM was built without support for AOT compilation.");
@@ -6603,8 +6607,6 @@
     Dart_StreamingCloseCallback close_callback) {
 #if defined(TARGET_ARCH_IA32)
   return Api::NewError("AOT compilation is not supported on IA32.");
-#elif defined(DART_TARGET_OS_WINDOWS)
-  return Api::NewError("Assembly generation is not implemented for Windows.");
 #elif !defined(DART_PRECOMPILER)
   return Api::NewError(
       "This VM was built without support for AOT compilation.");
@@ -6627,8 +6629,6 @@
                                    void* callback_data) {
 #if defined(TARGET_ARCH_IA32)
   return Api::NewError("AOT compilation is not supported on IA32.");
-#elif defined(DART_TARGET_OS_WINDOWS)
-  return Api::NewError("Assembly generation is not implemented for Windows.");
 #elif !defined(DART_PRECOMPILER)
   return Api::NewError(
       "This VM was built without support for AOT compilation.");
diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc
index 66d9c3b..7cba27b 100644
--- a/runtime/vm/image_snapshot.cc
+++ b/runtime/vm/image_snapshot.cc
@@ -1191,6 +1191,10 @@
     bool strip,
     SharedObjectWriter* writer,
     const Trie<const char>* deobfuscation_trie) {
+#if defined(DART_TARGET_OS_WINDOWS)
+  // PE uses PDB instead of DWARF.
+  return nullptr;
+#else
   if (!strip) {
     if (writer != nullptr) {
       // Reuse the existing DWARF object.
@@ -1200,6 +1204,7 @@
     return new (zone) Dwarf(zone, deobfuscation_trie);
   }
   return nullptr;
+#endif
 }
 
 AssemblyImageWriter::AssemblyImageWriter(
@@ -1258,6 +1263,23 @@
   assembly_stream_->WriteString(".section .note.GNU-stack,\"\",@progbits\n");
 #endif
 #endif
+
+#if defined(DART_TARGET_OS_WINDOWS)
+  // __declspec(dllexport)
+  const char* const exported_symbols[] = {
+      kVmSnapshotDataCSymbol,
+      kVmSnapshotInstructionsCSymbol,
+      kIsolateSnapshotDataCSymbol,
+      kIsolateSnapshotInstructionsCSymbol,
+  };
+  assembly_stream_->WriteString(".section .drectve,\"yni\"\n");
+  assembly_stream_->WriteString(".ascii \"");
+  for (const char* exported_symbol : exported_symbols) {
+    assembly_stream_->WriteString(" /EXPORT:");
+    assembly_stream_->WriteString(exported_symbol);
+  }
+  assembly_stream_->WriteString("\"\n");
+#endif
 }
 
 void ImageWriter::SnapshotTextObjectNamer::AddNonUniqueNameFor(
@@ -1492,6 +1514,8 @@
 #elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
     // MachO symbol tables don't include the size of the symbol, so don't bother
     // printing it to the assembly output.
+#elif defined(DART_TARGET_OS_WINDOWS)
+    // Windows also doesn't have a .size directive.
 #else
     UNIMPLEMENTED();
 #endif
@@ -1527,7 +1551,7 @@
       current_symbols_ =
           new (zone_) SharedObjectWriter::SymbolDataArray(zone_, 0);
 #if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) ||        \
-    defined(DART_TARGET_OS_FUCHSIA)
+    defined(DART_TARGET_OS_FUCHSIA) || defined(DART_TARGET_OS_WINDOWS)
       assembly_stream_->WriteString(".section .rodata\n");
 #elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
       assembly_stream_->WriteString(".const\n");
@@ -1591,9 +1615,12 @@
 #elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
   // MachO symbol tables don't include the size of the symbol, so don't bother
   // printing it to the assembly output.
+#elif defined(DART_TARGET_OS_WINDOWS)
+  // Windows also doesn't have a .size directive.
 #else
   UNIMPLEMENTED();
 #endif
+
   // We need to generate a text segment of the appropriate size in the shared
   // object writer for two reasons:
   //
@@ -1687,6 +1714,8 @@
 #elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
   // MachO symbol tables don't include the size of the symbol, so don't bother
   // printing it to the assembly output.
+#elif defined(DART_TARGET_OS_WINDOWS)
+  // Windows also doesn't have a .size directive.
 #else
   UNIMPLEMENTED();
 #endif
diff --git a/runtime/vm/os_win.cc b/runtime/vm/os_win.cc
index 12d056f..b0472b8 100644
--- a/runtime/vm/os_win.cc
+++ b/runtime/vm/os_win.cc
@@ -403,12 +403,12 @@
 }
 
 OS::BuildId OS::GetAppBuildId(const uint8_t* snapshot_instructions) {
-  // Since we only use direct-to-ELF snapshots on Windows, the build ID
-  // information must be available from the instructions image.
+  // Return the build ID information from the instructions image if available.
   const Image instructions_image(snapshot_instructions);
-  auto* const image_build_id = instructions_image.build_id();
-  ASSERT(image_build_id != nullptr);
-  return {instructions_image.build_id_length(), image_build_id};
+  if (auto* const image_build_id = instructions_image.build_id()) {
+    return {instructions_image.build_id_length(), image_build_id};
+  }
+  return {0, nullptr};
 }
 
 }  // namespace dart
diff --git a/tests/ffi/ffi_induce_a_crash_test.dart b/tests/ffi/ffi_induce_a_crash_test.dart
index 9dbe1d5..da37773 100644
--- a/tests/ffi/ffi_induce_a_crash_test.dart
+++ b/tests/ffi/ffi_induce_a_crash_test.dart
@@ -19,6 +19,8 @@
 void main(List<String> args) async {
   // Test exercises JIT, Windows-only functionality.
   if (!Platform.isWindows) return;
+  if (Platform.script.toString().endsWith(".dll")) return;
+
   if (args.length == 0) {
     asyncStart();
     final results = await Process.run(
diff --git a/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart b/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart
index 6a52db3..7d67abf 100644
--- a/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart
+++ b/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart
@@ -67,6 +67,10 @@
     return; // Generated dwarf.so not available on the test device.
   }
 
+  if (Platform.script.toString().endsWith(".dll")) {
+    return; // DWARF not available in DLLs.
+  }
+
   final dwarf = Dwarf.fromFile(
     path.join(
       Platform.environment["TEST_COMPILATION_DIR"]!,
diff --git a/tests/standalone/dwarf_stack_trace_obfuscate_test.dart b/tests/standalone/dwarf_stack_trace_obfuscate_test.dart
index 0429a82..20e990e 100644
--- a/tests/standalone/dwarf_stack_trace_obfuscate_test.dart
+++ b/tests/standalone/dwarf_stack_trace_obfuscate_test.dart
@@ -40,6 +40,10 @@
     return; // Generated dwarf.so not available on the test device.
   }
 
+  if (Platform.script.toString().endsWith(".dll")) {
+    return; // DWARF not available in DLLs.
+  }
+
   final dwarf = Dwarf.fromFile(
     path.join(
       Platform.environment['TEST_COMPILATION_DIR']!,
diff --git a/tests/standalone/dwarf_stack_trace_test.dart b/tests/standalone/dwarf_stack_trace_test.dart
index 196b111..4d0e66b 100644
--- a/tests/standalone/dwarf_stack_trace_test.dart
+++ b/tests/standalone/dwarf_stack_trace_test.dart
@@ -40,6 +40,10 @@
     return; // Generated dwarf.so not available on the test device.
   }
 
+  if (Platform.script.toString().endsWith(".dll")) {
+    return; // DWARF not available in DLLs.
+  }
+
   final dwarf = Dwarf.fromFile(
     path.join(Platform.environment["TEST_COMPILATION_DIR"]!, "dwarf.so"),
   )!;
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index ed83d22..edf6613 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -138,7 +138,11 @@
       "buildtools/mac-arm64/clang/lib/clang/21/lib/darwin/",
       "buildtools/mac-x64/clang/bin/",
       "buildtools/mac-x64/clang/lib/clang/21/lib/darwin/",
+      "buildtools/win-x64/clang/bin/clang.exe",
+      "buildtools/win-x64/clang/bin/lld-link.exe",
+      "buildtools/win-x64/clang/bin/llvm-strip.exe",
       "buildtools/win-x64/clang/bin/llvm-symbolizer.exe",
+      "buildtools/win-x64/clang/lib/clang/",
       "third_party/android_tools/sdk/platform-tools/adb",
       "third_party/android_tools/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/../lib/x86_64-unknown-linux-gnu/libc++.so",
       "third_party/android_tools/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objcopy",
@@ -301,7 +305,7 @@
         "enable-asserts": true
       }
     },
-    "vm-aot-(linux|fuchsia)-(debug|product|release)-(x64|x64c|arm64|arm64c)": {
+    "vm-aot-(linux|fuchsia|win)-(debug|product|release)-(x64|x64c|arm64|arm64c)": {
       "options": {}
     },
     "vm-aot-mac-(debug|product|release)-(x64|x64c|arm64|arm64c)": {
@@ -309,11 +313,6 @@
         "gen-snapshot-format" : "macho-dylib"
       }
     },
-    "vm-aot-win-(debug|product|release)-(x64|x64c|arm64|arm64c)": {
-      "options": {
-        "gen-snapshot-format" : "elf"
-      }
-    },
     "vm-aot-(linux|mac|win)-(debug|product|release)-(simarm|simarm_x64|simarm64|simriscv32|simriscv64)": {
       "options": {
         "gen-snapshot-format" : "elf"