Version 2.17.0-235.0.dev

Merge commit 'da691ea9c1383dcbccfa75440112f3ae9e875ed0' into 'dev'
diff --git a/pkg/dart2native/lib/dart2native.dart b/pkg/dart2native/lib/dart2native.dart
index 8b6b46e..d89c33d 100644
--- a/pkg/dart2native/lib/dart2native.dart
+++ b/pkg/dart2native/lib/dart2native.dart
@@ -5,8 +5,8 @@
 import 'dart:io';
 import 'dart:typed_data';
 
-import 'package:dart2native/dart2native_macho.dart'
-    show writeAppendedMachOExecutable;
+import 'dart2native_macho.dart' show writeAppendedMachOExecutable;
+import 'dart2native_pe.dart' show writeAppendedPortableExecutable;
 
 // Maximum page size across all supported architectures (arm64 macOS has 16K
 // pages, some arm64 Linux distributions have 64K pages).
@@ -20,6 +20,9 @@
   if (Platform.isMacOS) {
     return await writeAppendedMachOExecutable(
         dartaotruntimePath, payloadPath, outputPath);
+  } else if (Platform.isWindows) {
+    return await writeAppendedPortableExecutable(
+        dartaotruntimePath, payloadPath, outputPath);
   }
 
   final dartaotruntime = File(dartaotruntimePath);
diff --git a/pkg/dart2native/lib/dart2native_pe.dart b/pkg/dart2native/lib/dart2native_pe.dart
new file mode 100644
index 0000000..144112f
--- /dev/null
+++ b/pkg/dart2native/lib/dart2native_pe.dart
@@ -0,0 +1,334 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:io';
+import 'dart:math';
+import 'dart:typed_data';
+
+int align(int size, int base) {
+  final int over = size % base;
+  if (over != 0) {
+    return size + (base - over);
+  }
+  return size;
+}
+
+class BytesBacked {
+  ByteData data;
+
+  BytesBacked(this.data);
+
+  int get size => data.lengthInBytes;
+
+  Future<void> write(RandomAccessFile output) async {
+    await output.writeFrom(Uint8List.sublistView(data));
+  }
+}
+
+class CoffFileHeader extends BytesBacked {
+  CoffFileHeader._(ByteData data) : super(data);
+
+  static const _fileHeaderSize = 20;
+  static const _sectionCountOffset = 2;
+  static const _optionalHeaderSizeOffset = 16;
+
+  static CoffFileHeader fromTypedData(TypedData source, int offset) {
+    if (source.lengthInBytes < offset + _fileHeaderSize) {
+      throw 'File is truncated within the COFF file header';
+    }
+    final buffer = Uint8List(_fileHeaderSize);
+    buffer.setAll(
+        0, Uint8List.sublistView(source, offset, offset + _fileHeaderSize));
+    return CoffFileHeader._(ByteData.sublistView(buffer));
+  }
+
+  int get sectionCount => data.getUint16(_sectionCountOffset, Endian.little);
+  set sectionCount(int value) =>
+      data.setUint16(_sectionCountOffset, value, Endian.little);
+
+  int get optionalHeaderSize =>
+      data.getUint16(_optionalHeaderSizeOffset, Endian.little);
+}
+
+class CoffOptionalHeader extends BytesBacked {
+  CoffOptionalHeader._(ByteData data) : super(data);
+
+  static const _pe32Magic = 0x10b;
+  static const _pe32PlusMagic = 0x20b;
+
+  static const _magicOffset = 0;
+  static const _sectionAlignmentOffset = 32;
+  static const _fileAlignmentOffset = 36;
+  static const _imageSizeOffset = 56;
+  static const _headersSizeOffset = 60;
+
+  static CoffOptionalHeader fromTypedData(
+      TypedData source, int offset, int size) {
+    if (source.lengthInBytes < offset + size) {
+      throw 'File is truncated within the COFF optional header';
+    }
+    final buffer = Uint8List(size);
+    buffer.setAll(0, Uint8List.sublistView(source, offset, offset + size));
+    final data = ByteData.sublistView(buffer);
+    final magic = data.getUint16(_magicOffset, Endian.little);
+    if (magic != _pe32Magic && magic != _pe32PlusMagic) {
+      throw 'Not a PE32 or PE32+ image file';
+    }
+    return CoffOptionalHeader._(data);
+  }
+
+  // The alignment used for virtual addresses of sections, _not_ file offsets.
+  int get sectionAlignment =>
+      data.getUint32(_sectionAlignmentOffset, Endian.little);
+
+  // The alignment used for file offsets of section data and other contents.
+  int get fileAlignment => data.getUint32(_fileAlignmentOffset, Endian.little);
+
+  int get headersSize => data.getUint32(_headersSizeOffset, Endian.little);
+  set headersSize(int value) =>
+      data.setUint32(_headersSizeOffset, value, Endian.little);
+
+  int get imageSize => data.getUint32(_imageSizeOffset, Endian.little);
+  set imageSize(int value) =>
+      data.setUint32(_imageSizeOffset, value, Endian.little);
+}
+
+class CoffSectionHeader extends BytesBacked {
+  CoffSectionHeader._(ByteData data) : super(data);
+
+  static const _virtualSizeOffset = 8;
+  static const _virtualAddressOffset = 12;
+  static const _fileSizeOffset = 16;
+  static const _fileOffsetOffset = 20;
+  static const _characteristicsOffset = 36;
+
+  static const _discardableFlag = 0x02000000;
+
+  String get name => String.fromCharCodes(Uint8List.sublistView(data, 0, 8));
+  set name(String name) {
+    // Each section header has only eight bytes for the section name.
+    // First reset it to zeroes, then copy over the UTF-8 encoded version.
+    final buffer = Uint8List.sublistView(data, 0, 8);
+    buffer.fillRange(0, 8, 0);
+    buffer.setAll(0, utf8.encode(name));
+  }
+
+  int get virtualAddress =>
+      data.getUint32(_virtualAddressOffset, Endian.little);
+  set virtualAddress(int offset) =>
+      data.setUint32(_virtualAddressOffset, offset, Endian.little);
+
+  int get virtualSize => data.getUint32(_virtualSizeOffset, Endian.little);
+  set virtualSize(int offset) =>
+      data.setUint32(_virtualSizeOffset, offset, Endian.little);
+
+  int get fileOffset => data.getUint32(_fileOffsetOffset, Endian.little);
+  set fileOffset(int offset) =>
+      data.setUint32(_fileOffsetOffset, offset, Endian.little);
+
+  int get fileSize => data.getUint32(_fileSizeOffset, Endian.little);
+  set fileSize(int offset) =>
+      data.setUint32(_fileSizeOffset, offset, Endian.little);
+
+  int get characteristics =>
+      data.getUint32(_characteristicsOffset, Endian.little);
+  set characteristics(int value) =>
+      data.setUint32(_characteristicsOffset, value, Endian.little);
+
+  bool get isDiscardable => characteristics & _discardableFlag != 0;
+  set isDiscardable(bool value) {
+    if (value) {
+      characteristics |= _discardableFlag;
+    } else {
+      characteristics &= ~_discardableFlag;
+    }
+  }
+}
+
+class CoffSectionTable extends BytesBacked {
+  CoffSectionTable._(ByteData data) : super(data);
+
+  static const _entrySize = 40;
+
+  static CoffSectionTable fromTypedData(
+      TypedData source, int offset, int sections) {
+    final size = sections * _entrySize;
+    if (source.lengthInBytes < offset + size) {
+      throw 'File is truncated within the COFF section table';
+    }
+    final buffer = Uint8List(size);
+    buffer.setAll(0, Uint8List.sublistView(source, offset, offset + size));
+    return CoffSectionTable._(ByteData.sublistView(buffer));
+  }
+
+  Iterable<CoffSectionHeader> get entries sync* {
+    for (int i = 0; i < size; i += _entrySize) {
+      yield CoffSectionHeader._(ByteData.sublistView(data, i, i + _entrySize));
+    }
+  }
+
+  int get addressEnd => entries.fold(
+      0, (i, entry) => max(i, entry.virtualAddress + entry.virtualSize));
+  int get offsetEnd =>
+      entries.fold(0, (i, entry) => max(i, entry.fileOffset + entry.fileSize));
+
+  CoffSectionHeader allocateNewSectionHeader() {
+    final newBuffer = Uint8List(size + _entrySize);
+    newBuffer.setAll(0, Uint8List.sublistView(data));
+    data = ByteData.sublistView(newBuffer);
+    return CoffSectionHeader._(
+        ByteData.sublistView(data, size - _entrySize, size));
+  }
+}
+
+class CoffHeaders {
+  final int _coffOffset;
+  final CoffFileHeader fileHeader;
+  final CoffOptionalHeader optionalHeader;
+  final CoffSectionTable sectionTable;
+
+  CoffHeaders._(this._coffOffset, this.fileHeader, this.optionalHeader,
+      this.sectionTable);
+
+  static CoffHeaders fromTypedData(TypedData source, int offset) {
+    final fileHeader = CoffFileHeader.fromTypedData(source, offset);
+    final optionalHeader = CoffOptionalHeader.fromTypedData(
+        source, offset + fileHeader.size, fileHeader.optionalHeaderSize);
+    final sectionTable = CoffSectionTable.fromTypedData(
+        source,
+        offset + fileHeader.size + optionalHeader.size,
+        fileHeader.sectionCount);
+    return CoffHeaders._(offset, fileHeader, optionalHeader, sectionTable);
+  }
+
+  // Keep in sync with kSnapshotSectionName in snapshot_utils.cc.
+  static const _snapshotSectionName = "snapshot";
+
+  int get size => optionalHeader.headersSize;
+
+  void addSnapshotSectionHeader(int length) {
+    final oldHeadersSize = optionalHeader.headersSize;
+    final address =
+        align(sectionTable.addressEnd, optionalHeader.sectionAlignment);
+    final offset = align(sectionTable.offsetEnd, optionalHeader.fileAlignment);
+
+    // Create and fill the new section header entry.
+    final newHeader = sectionTable.allocateNewSectionHeader();
+    newHeader.name = _snapshotSectionName;
+    newHeader.virtualAddress = address;
+    newHeader.virtualSize = length;
+    newHeader.fileOffset = offset;
+    newHeader.fileSize = align(length, optionalHeader.fileAlignment);
+    newHeader.isDiscardable = true;
+    // Leave the rest of the header fields with zero values.
+
+    // Increment the number of sections in the file header.
+    fileHeader.sectionCount += 1;
+
+    // Adjust the header size stored in the optional header, which must be
+    // a multiple of fileAlignment.
+    optionalHeader.headersSize = align(
+        _coffOffset + fileHeader.size + optionalHeader.size + sectionTable.size,
+        optionalHeader.fileAlignment);
+
+    // If the size of the headers changed, we'll need to adjust the section
+    // offsets.
+    final headersSizeDiff = optionalHeader.headersSize - oldHeadersSize;
+    if (headersSizeDiff > 0) {
+      // Safety check that section virtual addresses need not be adjusted, as
+      // that requires rewriting much more of the fields and section contents.
+      // (Generally, the size of the headers is much smaller than the section
+      // alignment and so this is not expected to happen.)
+      if (size ~/ optionalHeader.sectionAlignment !=
+          oldHeadersSize ~/ optionalHeader.sectionAlignment) {
+        throw 'Adding the snapshot would require adjusting virtual addresses';
+      }
+      assert(headersSizeDiff % optionalHeader.fileAlignment == 0);
+      for (final entry in sectionTable.entries) {
+        entry.fileOffset += headersSizeDiff;
+      }
+    }
+
+    // Adjust the image size stored in the optional header, which must be a
+    // multiple of section alignment (as it is the size in memory, not on disk).
+    optionalHeader.imageSize = align(
+        newHeader.virtualAddress + newHeader.virtualSize,
+        optionalHeader.sectionAlignment);
+  }
+
+  Future<void> write(RandomAccessFile output) async {
+    await fileHeader.write(output);
+    await optionalHeader.write(output);
+    await sectionTable.write(output);
+    // Pad to the recorded headers size, which includes the MS-DOS stub.
+    final written = await output.position();
+    await output.writeFrom(Uint8List(size - written));
+  }
+}
+
+class PortableExecutable {
+  final Uint8List source;
+  final CoffHeaders headers;
+  final int sourceFileHeaderOffset;
+  final int sourceSectionContentsOffset;
+
+  PortableExecutable._(this.source, this.headers, this.sourceFileHeaderOffset,
+      this.sourceSectionContentsOffset);
+
+  static const _expectedPESignature = <int>[80, 69, 0, 0];
+  static const _offsetForPEOffset = 0x3c;
+
+  static Future<PortableExecutable> fromFile(File file) async {
+    final source = await file.readAsBytes();
+    final byteData = ByteData.sublistView(source);
+    final peOffset = byteData.getUint32(_offsetForPEOffset, Endian.little);
+    for (int i = 0; i < _expectedPESignature.length; i++) {
+      if (byteData.getUint8(peOffset + i) != _expectedPESignature[i]) {
+        throw 'Not a Portable Executable file';
+      }
+    }
+    final fileHeaderOffset = peOffset + _expectedPESignature.length;
+    final headers = CoffHeaders.fromTypedData(source, fileHeaderOffset);
+    final sectionContentsOffset = headers.size;
+    return PortableExecutable._(
+        source, headers, fileHeaderOffset, sectionContentsOffset);
+  }
+
+  Future<void> _fileAlignSectionEnd(RandomAccessFile output) async {
+    final current = await output.position();
+    final padding =
+        align(current, headers.optionalHeader.fileAlignment) - current;
+    await output.writeFrom(Uint8List(padding));
+  }
+
+  Future<void> appendSnapshotAndWrite(File output, File snapshot) async {
+    final stream = await output.open(mode: FileMode.write);
+    // Write MS-DOS stub.
+    await stream.writeFrom(source, 0, sourceFileHeaderOffset);
+    // Write headers with additional snapshot section.
+    final snapshotBytes = await snapshot.readAsBytes();
+    headers.addSnapshotSectionHeader(snapshotBytes.length);
+    await headers.write(stream);
+    // Write original section contents with alignment padding.
+    await stream.writeFrom(source, sourceSectionContentsOffset);
+    await _fileAlignSectionEnd(stream);
+    // Write snapshot with alignment padding.
+    await stream.writeFrom(snapshotBytes);
+    await _fileAlignSectionEnd(stream);
+    await stream.close();
+  }
+}
+
+// Writes an "appended" dart runtime + script snapshot file in a format
+// compatible with Portable Executable files.
+Future writeAppendedPortableExecutable(
+    String dartaotruntimePath, String payloadPath, String outputPath) async {
+  File originalExecutableFile = File(dartaotruntimePath);
+  File newSegmentFile = File(payloadPath);
+  File outputFile = File(outputPath);
+
+  final pe = await PortableExecutable.fromFile(originalExecutableFile);
+  await pe.appendSnapshotAndWrite(outputFile, newSegmentFile);
+}
diff --git a/pkg/dart2wasm/dart2wasm.md b/pkg/dart2wasm/dart2wasm.md
index 8dc3203..bfc2d47 100644
--- a/pkg/dart2wasm/dart2wasm.md
+++ b/pkg/dart2wasm/dart2wasm.md
@@ -16,12 +16,12 @@
 | `--`[`no-`]`lazy-constants`             | no      | Instantiate constants lazily.
 | `--`[`no-`]`local-nullability`          | no      | Use non-nullable types for non-nullable locals and temporaries.
 | `--`[`no-`]`name-section`               | yes     | Emit Name Section with function names.
-| `--`[`no-`]`nominal-types`              | no      | Emit experimental nominal types.
+| `--`[`no-`]`nominal-types`              | yes     | Emit nominal types.
 | `--`[`no-`]`parameter-nullability`      | yes     | Use non-nullable types for non-nullable parameters and return values.
 | `--`[`no-`]`polymorphic-specialization` | no      | Do virtual calls by switching on the class ID instead of using `call_indirect`.
 | `--`[`no-`]`print-kernel`               | no      | Print IR for each function before compiling it.
 | `--`[`no-`]`print-wasm`                 | no      | Print Wasm instructions of each compiled function.
-| `--`[`no-`]`runtime-types`              | yes     | Use RTTs for allocations and casts.
+| `--`[`no-`]`runtime-types`              | no      | Use RTTs for allocations and casts.
 | `--`[`no-`]`string-data-segments`       | no      | Use experimental array init from data segment for string constants.
 | `--watch` *offset*                      |         | Print stack trace leading to the byte at offset *offset* in the `.wasm` output file. Can be specified multiple times.
 
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 8fdc1b8..3bf91b4 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -32,12 +32,12 @@
   bool lazyConstants = false;
   bool localNullability = false;
   bool nameSection = true;
-  bool nominalTypes = false;
+  bool nominalTypes = true;
   bool parameterNullability = true;
   bool polymorphicSpecialization = false;
   bool printKernel = false;
   bool printWasm = false;
-  bool runtimeTypes = true;
+  bool runtimeTypes = false;
   bool stringDataSegments = false;
   List<int>? watchPoints = null;
 
diff --git a/runtime/bin/snapshot_utils.cc b/runtime/bin/snapshot_utils.cc
index 243fac3..fd0d4dc 100644
--- a/runtime/bin/snapshot_utils.cc
+++ b/runtime/bin/snapshot_utils.cc
@@ -16,6 +16,9 @@
 #if defined(DART_TARGET_OS_MACOS)
 #include <platform/mach_o.h>
 #endif
+#if defined(DART_TARGET_OS_WINDOWS)
+#include <platform/pe.h>
+#endif
 #include "platform/utils.h"
 
 #define LOG_SECTION_BOUNDARIES false
@@ -260,7 +263,7 @@
   // Parse the first 4bytes and extract the magic number.
   uint32_t magic;
   file->SetPosition(0);
-  file->Read(&magic, sizeof(uint32_t));
+  file->ReadFully(&magic, sizeof(uint32_t));
 
   const bool is64Bit =
       magic == mach_o::MH_MAGIC_64 || magic == mach_o::MH_CIGAM_64;
@@ -289,11 +292,11 @@
     return nullptr;
   } else {
     mach_o::mach_header_64 header;
-    file->Read(&header, sizeof(header));
+    file->ReadFully(&header, sizeof(header));
 
     for (uint32_t i = 0; i < header.ncmds; ++i) {
       mach_o::load_command command;
-      file->Read(&command, sizeof(mach_o::load_command));
+      file->ReadFully(&command, sizeof(mach_o::load_command));
 
       file->SetPosition(file->Position() - sizeof(command));
       if (command.cmd != mach_o::LC_SEGMENT &&
@@ -303,11 +306,11 @@
       }
 
       mach_o::segment_command_64 segment;
-      file->Read(&segment, sizeof(segment));
+      file->ReadFully(&segment, sizeof(segment));
 
       for (uint32_t j = 0; j < segment.nsects; ++j) {
         mach_o::section_64 section;
-        file->Read(&section, sizeof(section));
+        file->ReadFully(&section, sizeof(section));
 
         if (segment.cmd == mach_o::LC_SEGMENT_64 &&
             strcmp(section.segname, kMachOAppSnapshotSegmentName) == 0 &&
@@ -323,7 +326,7 @@
 
           std::unique_ptr<uint8_t[]> snapshot(new uint8_t[section.size]);
           file->SetPosition(section.offset);
-          file->Read(snapshot.get(), sizeof(uint8_t) * section.size);
+          file->ReadFully(snapshot.get(), sizeof(uint8_t) * section.size);
 
           Dart_LoadedElf* handle = Dart_LoadELF_Memory(
               snapshot.get(), section.size, &error, &vm_data_buffer,
@@ -347,12 +350,101 @@
 }
 #endif  // defined(DART_TARGET_OS_MACOS)
 
+#if defined(DART_TARGET_OS_WINDOWS)
+// Keep in sync with CoffSectionTable._snapshotSectionName from
+// pkg/dart2native/lib/dart2native_pe.dart.
+static const char kSnapshotSectionName[] = "snapshot";
+// Ignore the null terminator, as it won't be present if the string length is
+// exactly pe::kCoffSectionNameSize.
+static_assert(sizeof(kSnapshotSectionName) - 1 <= pe::kCoffSectionNameSize,
+              "Section name of snapshot too large");
+
+AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromPE(
+    const char* container_path) {
+  File* const file = File::Open(NULL, container_path, File::kRead);
+  if (file == nullptr) {
+    return nullptr;
+  }
+  RefCntReleaseScope<File> rs(file);
+
+  // Ensure file is actually PE-formatted.
+  if (!IsPEFormattedBinary(container_path)) {
+    Syslog::PrintErr(
+        "Attempted load target was not formatted as expected: "
+        "expected PE32 or PE32+ image file.\n");
+    return nullptr;
+  }
+
+  // Parse the offset into the PE contents (i.e., skipping the MS-DOS stub).
+  uint32_t pe_offset;
+  file->SetPosition(pe::kPEOffsetOffset);
+  file->ReadFully(&pe_offset, sizeof(pe_offset));
+
+  // Skip past the magic bytes to the COFF file header and COFF optional header.
+  const intptr_t coff_offset = pe_offset + sizeof(pe::kPEMagic);
+  file->SetPosition(coff_offset);
+  pe::coff_file_header file_header;
+  file->ReadFully(&file_header, sizeof(file_header));
+  // The optional header follows directly after the file header.
+  pe::coff_optional_header opt_header;
+  file->ReadFully(&opt_header, sizeof(opt_header));
+
+  // Skip to the section table.
+  const intptr_t coff_symbol_table_offset =
+      coff_offset + sizeof(file_header) + file_header.optional_header_size;
+  file->SetPosition(coff_symbol_table_offset);
+  for (intptr_t i = 0; i < file_header.num_sections; i++) {
+    pe::coff_section_header section_header;
+    file->ReadFully(&section_header, sizeof(section_header));
+    if (strncmp(section_header.name, kSnapshotSectionName,
+                pe::kCoffSectionNameSize) == 0) {
+      // We have to do the loading manually even though currently the snapshot
+      // data is at the end of the file because the file alignment for
+      // PE sections can be less than the page size, and TryReadAppSnapshotElf
+      // won't work if the file offset isn't page-aligned.
+      const char* error = nullptr;
+      const uint8_t* vm_data_buffer = nullptr;
+      const uint8_t* vm_instructions_buffer = nullptr;
+      const uint8_t* isolate_data_buffer = nullptr;
+      const uint8_t* isolate_instructions_buffer = nullptr;
+
+      const intptr_t offset = section_header.file_offset;
+      const intptr_t size = section_header.file_size;
+
+      std::unique_ptr<uint8_t[]> snapshot(new uint8_t[size]);
+      file->SetPosition(offset);
+      file->ReadFully(snapshot.get(), sizeof(uint8_t) * size);
+
+      Dart_LoadedElf* const handle =
+          Dart_LoadELF_Memory(snapshot.get(), size, &error, &vm_data_buffer,
+                              &vm_instructions_buffer, &isolate_data_buffer,
+                              &isolate_instructions_buffer);
+
+      if (handle == nullptr) {
+        Syslog::PrintErr("Loading failed: %s\n", error);
+        return nullptr;
+      }
+
+      return new ElfAppSnapshot(handle, vm_data_buffer, vm_instructions_buffer,
+                                isolate_data_buffer,
+                                isolate_instructions_buffer);
+    }
+  }
+
+  return nullptr;
+}
+#endif  // defined(DART_TARGET_OS_WINDOWS)
+
 AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElf(
     const char* container_path) {
 #if defined(DART_TARGET_OS_MACOS)
   if (IsMachOFormattedBinary(container_path)) {
     return TryReadAppendedAppSnapshotElfFromMachO(container_path);
   }
+#elif defined(DART_TARGET_OS_WINDOWS)
+  if (IsPEFormattedBinary(container_path)) {
+    return TryReadAppendedAppSnapshotElfFromPE(container_path);
+  }
 #endif
 
   File* file = File::Open(NULL, container_path, File::kRead);
@@ -472,6 +564,57 @@
 }
 #endif  // defined(DART_TARGET_OS_MACOS)
 
+#if defined(DART_TARGET_OS_WINDOWS)
+bool Snapshot::IsPEFormattedBinary(const char* filename) {
+  File* file = File::Open(NULL, filename, File::kRead);
+  if (file == nullptr) {
+    return false;
+  }
+  RefCntReleaseScope<File> rs(file);
+
+  // Parse the PE offset.
+  uint32_t pe_offset;
+  // Ensure the file is long enough to contain the PE offset.
+  if (file->Length() <
+      static_cast<intptr_t>(pe::kPEOffsetOffset + sizeof(pe_offset))) {
+    return false;
+  }
+  file->SetPosition(pe::kPEOffsetOffset);
+  file->Read(&pe_offset, sizeof(pe_offset));
+
+  // Ensure the file is long enough to contain the PE magic bytes.
+  if (file->Length() <
+      static_cast<intptr_t>(pe_offset + sizeof(pe::kPEMagic))) {
+    return false;
+  }
+  // Check the magic bytes.
+  file->SetPosition(pe_offset);
+  for (size_t i = 0; i < sizeof(pe::kPEMagic); i++) {
+    char c;
+    file->Read(&c, sizeof(c));
+    if (c != pe::kPEMagic[i]) {
+      return false;
+    }
+  }
+
+  // Check that there is a coff optional header.
+  pe::coff_file_header file_header;
+  pe::coff_optional_header opt_header;
+  file->Read(&file_header, sizeof(file_header));
+  if (file_header.optional_header_size < sizeof(opt_header)) {
+    return false;
+  }
+  file->Read(&opt_header, sizeof(opt_header));
+  // Check the magic bytes in the coff optional header.
+  if (opt_header.magic != pe::kPE32Magic &&
+      opt_header.magic != pe::kPE32PlusMagic) {
+    return false;
+  }
+
+  return true;
+}
+#endif  // defined(DART_TARGET_OS_WINDOWS)
+
 AppSnapshot* Snapshot::TryReadAppSnapshot(const char* script_uri,
                                           bool force_load_elf_from_memory,
                                           bool decode_uri) {
diff --git a/runtime/bin/snapshot_utils.h b/runtime/bin/snapshot_utils.h
index f26f7b2..4359d66 100644
--- a/runtime/bin/snapshot_utils.h
+++ b/runtime/bin/snapshot_utils.h
@@ -41,6 +41,9 @@
 #if defined(DART_TARGET_OS_MACOS)
   static bool IsMachOFormattedBinary(const char* container_path);
 #endif
+#if defined(DART_TARGET_OS_WINDOWS)
+  static bool IsPEFormattedBinary(const char* container_path);
+#endif
 
   static AppSnapshot* TryReadAppendedAppSnapshotElf(const char* container_path);
   static AppSnapshot* TryReadAppSnapshot(
@@ -62,6 +65,10 @@
   static AppSnapshot* TryReadAppendedAppSnapshotElfFromMachO(
       const char* container_path);
 #endif
+#if defined(DART_TARGET_OS_WINDOWS)
+  static AppSnapshot* TryReadAppendedAppSnapshotElfFromPE(
+      const char* container_path);
+#endif
 
   DISALLOW_ALLOCATION();
   DISALLOW_IMPLICIT_CONSTRUCTORS(Snapshot);
diff --git a/runtime/platform/pe.h b/runtime/platform/pe.h
new file mode 100644
index 0000000..952e856
--- /dev/null
+++ b/runtime/platform/pe.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef RUNTIME_PLATFORM_PE_H_
+#define RUNTIME_PLATFORM_PE_H_
+
+#include <platform/globals.h>
+
+namespace dart {
+
+namespace pe {
+
+#pragma pack(push, 1)
+
+static const intptr_t kPEOffsetOffset = 0x3c;
+static const char kPEMagic[] = {'P', 'E', '\0', '\0'};
+
+struct coff_file_header {
+  uint16_t machine;
+  uint16_t num_sections;
+  uint32_t timestamp;
+  uint32_t symbol_table_offset;
+  uint32_t num_symbols;
+  uint16_t optional_header_size;
+  uint16_t characteristics;
+};
+
+// Does not include the BaseOfData field for PE32 (not PE32+) files, but we
+// don't need that to load a snapshot out of a PE file.
+struct coff_optional_header {
+  uint16_t magic;
+  uint8_t linker_major_version;
+  uint8_t linker_minor_version;
+  uint32_t code_size;
+  uint32_t initialized_data_size;
+  uint32_t uninitialized_data_size;
+  uint32_t entry_point_address;
+  uint32_t code_base_address;
+};
+
+static const uint16_t kPE32Magic = 0x10b;
+static const uint16_t kPE32PlusMagic = 0x20b;
+
+static const intptr_t kCoffSectionNameSize = 8;
+
+struct coff_section_header {
+  char name[kCoffSectionNameSize];
+  uint32_t virtual_size;
+  uint32_t virtual_address;
+  uint32_t file_size;
+  uint32_t file_offset;
+  uint32_t relocations_offset;
+  uint32_t line_numbers_offset;
+  uint16_t num_relocations;
+  uint16_t num_line_numbers;
+  uint32_t characteristics;
+};
+
+#pragma pack(pop)
+
+}  // namespace pe
+
+}  // namespace dart
+
+#endif  // RUNTIME_PLATFORM_PE_H_
diff --git a/tools/VERSION b/tools/VERSION
index 5843869..006ee29 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 17
 PATCH 0
-PRERELEASE 234
+PRERELEASE 235
 PRERELEASE_PATCH 0
\ No newline at end of file