]> granicus.if.org Git - llvm/commitdiff
[pdb/lld] Write a valid FPM.
authorZachary Turner <zturner@google.com>
Wed, 2 Aug 2017 22:31:39 +0000 (22:31 +0000)
committerZachary Turner <zturner@google.com>
Wed, 2 Aug 2017 22:31:39 +0000 (22:31 +0000)
The PDB reserves certain blocks for the FPM that describe which
blocks in the file are allocated and which are free.  We weren't
filling that out at all, and in some cases we were even stomping
it with incorrect data.  This patch writes a correct FPM.

Differential Revision: https://reviews.llvm.org/D36235

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@309896 91177308-0d34-0410-b5e6-96231b3b80d8

14 files changed:
include/llvm/DebugInfo/MSF/MSFCommon.h
include/llvm/DebugInfo/MSF/MappedBlockStream.h
include/llvm/DebugInfo/PDB/Native/PDBFileBuilder.h
include/llvm/Support/MathExtras.h
lib/DebugInfo/MSF/MSFBuilder.cpp
lib/DebugInfo/MSF/MSFCommon.cpp
lib/DebugInfo/MSF/MappedBlockStream.cpp
lib/DebugInfo/PDB/Native/PDBFile.cpp
lib/DebugInfo/PDB/Native/PDBFileBuilder.cpp
test/DebugInfo/PDB/write-fpm.test [new file with mode: 0644]
unittests/DebugInfo/MSF/CMakeLists.txt
unittests/DebugInfo/MSF/MSFBuilderTest.cpp
unittests/DebugInfo/MSF/MSFCommonTest.cpp [new file with mode: 0644]
unittests/DebugInfo/MSF/MappedBlockStreamTest.cpp

index 458de415ee1f0328eba6798fd4a13fecee014dfd..f28415d4e60349def1728a03cb514ccd2022bc6e 100644 (file)
@@ -74,7 +74,9 @@ public:
 /// \brief Determine the layout of the FPM stream, given the MSF layout.  An FPM
 /// stream spans 1 or more blocks, each at equally spaced intervals throughout
 /// the file.
-MSFStreamLayout getFpmStreamLayout(const MSFLayout &Msf);
+MSFStreamLayout getFpmStreamLayout(const MSFLayout &Msf,
+                                   bool IncludeUnusedFpmData = false,
+                                   bool AltFpm = false);
 
 inline bool isValidBlockSize(uint32_t Size) {
   switch (Size) {
@@ -95,7 +97,7 @@ inline uint32_t getMinimumBlockCount() { return 4; }
 inline uint32_t getFirstUnreservedBlock() { return 3; }
 
 inline uint64_t bytesToBlocks(uint64_t NumBytes, uint64_t BlockSize) {
-  return alignTo(NumBytes, BlockSize) / BlockSize;
+  return divideCeil(NumBytes, BlockSize);
 }
 
 inline uint64_t blockToOffset(uint64_t BlockNumber, uint64_t BlockSize) {
@@ -106,13 +108,14 @@ inline uint32_t getFpmIntervalLength(const MSFLayout &L) {
   return L.SB->BlockSize;
 }
 
-inline uint32_t getNumFpmIntervals(const MSFLayout &L) {
-  uint32_t Length = getFpmIntervalLength(L);
-  return alignTo(L.SB->NumBlocks, Length) / Length;
-}
+inline uint32_t getNumFpmIntervals(const MSFLayout &L,
+                                   bool IncludeUnusedFpmData = false) {
+  if (IncludeUnusedFpmData)
+    return divideCeil(L.SB->NumBlocks, L.SB->BlockSize);
 
-inline uint32_t getFullFpmByteSize(const MSFLayout &L) {
-  return alignTo(L.SB->NumBlocks, 8) / 8;
+  // We want the minimum number of intervals required, where each interval can
+  // represent BlockSize * 8 blocks.
+  return divideCeil(L.SB->NumBlocks, 8 * L.SB->BlockSize);
 }
 
 Error validateSuperBlock(const SuperBlock &SB);
index 6cb07d26836f88446e009630fac8961d0f7f5bf5..f65e52922da775b9e330f904bd53aa593ca30d2e 100644 (file)
@@ -122,7 +122,7 @@ public:
 
   static std::unique_ptr<WritableMappedBlockStream>
   createFpmStream(const MSFLayout &Layout, WritableBinaryStreamRef MsfData,
-                  BumpPtrAllocator &Allocator);
+                  BumpPtrAllocator &Allocator, bool AltFpm = false);
 
   support::endianness getEndian() const override {
     return support::little;
index 68d9d306de65b124f60bafe8faf404c1cb5ab82d..684fd0eff09e106a015b3bd26ea790fb46cad7d4 100644 (file)
@@ -61,6 +61,8 @@ public:
 private:
   Expected<msf::MSFLayout> finalizeMsfLayout();
 
+  void commitFpm(WritableBinaryStream &MsfBuffer, const msf::MSFLayout &Layout);
+
   BumpPtrAllocator &Allocator;
 
   std::unique_ptr<msf::MSFBuilder> Msf;
index db01e99334ae524144413543e8132eec149edafd..a37a16784e2acbfd0f6aecc6a7981227293630f2 100644 (file)
@@ -687,6 +687,11 @@ template <uint64_t Align> constexpr inline uint64_t alignTo(uint64_t Value) {
   return (Value + Align - 1) / Align * Align;
 }
 
+/// Returns the integer ceil(Numerator / Denominator).
+inline uint64_t divideCeil(uint64_t Numerator, uint64_t Denominator) {
+  return alignTo(Numerator, Denominator) / Denominator;
+}
+
 /// \c alignTo for contexts where a constant expression is required.
 /// \sa alignTo
 ///
index 0f4f785abf55ada8ebb95a6a975819ce086fae89..de5704ff665a9cf41ebaaa8be020d5edaa3abdd0 100644 (file)
@@ -107,7 +107,23 @@ Error MSFBuilder::allocateBlocks(uint32_t NumBlocks,
       return make_error<MSFError>(msf_error_code::insufficient_buffer,
                                   "There are no free Blocks in the file");
     uint32_t AllocBlocks = NumBlocks - NumFreeBlocks;
-    FreeBlocks.resize(AllocBlocks + FreeBlocks.size(), true);
+    uint32_t OldBlockCount = FreeBlocks.size();
+    uint32_t NewBlockCount = AllocBlocks + OldBlockCount;
+    uint32_t NextFpmBlock = alignTo(OldBlockCount, BlockSize) + 1;
+    FreeBlocks.resize(NewBlockCount, true);
+    // If we crossed over an fpm page, we actually need to allocate 2 extra
+    // blocks for each FPM group crossed and mark both blocks from the group as
+    // used.  We may not actually use them since there are many more FPM blocks
+    // present than are required to represent all blocks in a given PDB, but we
+    // need to make sure they aren't allocated to a stream or something else.
+    // At the end when committing the PDB, we'll go through and mark the
+    // extraneous ones unused.
+    while (NextFpmBlock < NewBlockCount) {
+      NewBlockCount += 2;
+      FreeBlocks.resize(NewBlockCount, true);
+      FreeBlocks.reset(NextFpmBlock, NextFpmBlock + 2);
+      NextFpmBlock += BlockSize;
+    }
   }
 
   int I = 0;
@@ -229,6 +245,19 @@ uint32_t MSFBuilder::computeDirectoryByteSize() const {
   return Size;
 }
 
+static void finalizeFpmBlockStatus(uint32_t B, ArrayRef<ulittle32_t> &FpmBlocks,
+                                   BitVector &Fpm) {
+  if (FpmBlocks.empty() || FpmBlocks.front() != B) {
+    Fpm.set(B);
+    return;
+  }
+
+  // If the next block in the actual layout is this block, it should *not* be
+  // free.
+  assert(!Fpm.test(B));
+  FpmBlocks = FpmBlocks.drop_front();
+}
+
 Expected<MSFLayout> MSFBuilder::build() {
   SuperBlock *SB = Allocator.Allocate<SuperBlock>();
   MSFLayout L;
@@ -287,5 +316,20 @@ Expected<MSFLayout> MSFBuilder::build() {
     }
   }
 
+  // FPM blocks occur in pairs at every `BlockLength` interval.  While blocks of
+  // this form are reserved for FPM blocks, not all blocks of this form will
+  // actually be needed for FPM data because there are more blocks of this form
+  // than are required to represent a PDB file with a given number of blocks.
+  // So we need to find out which blocks are *actually* going to be real FPM
+  // blocks, then mark the reset of the reserved blocks as unallocated.
+  MSFStreamLayout FpmLayout = msf::getFpmStreamLayout(L, true);
+  auto FpmBlocks = makeArrayRef(FpmLayout.Blocks);
+  for (uint32_t B = kFreePageMap0Block; B < SB->NumBlocks;
+       B += msf::getFpmIntervalLength(L)) {
+    finalizeFpmBlockStatus(B, FpmBlocks, FreeBlocks);
+    finalizeFpmBlockStatus(B + 1, FpmBlocks, FreeBlocks);
+  }
+  L.FreePageMap = FreeBlocks;
+
   return L;
 }
index 484e1845ce2585babf4f77ce235a38fae101dcfe..d7e1dcf31a3a4179c1640145a2cc3e8485e374d7 100644 (file)
@@ -60,17 +60,26 @@ Error llvm::msf::validateSuperBlock(const SuperBlock &SB) {
   return Error::success();
 }
 
-MSFStreamLayout llvm::msf::getFpmStreamLayout(const MSFLayout &Msf) {
+MSFStreamLayout llvm::msf::getFpmStreamLayout(const MSFLayout &Msf,
+                                              bool IncludeUnusedFpmData,
+                                              bool AltFpm) {
   MSFStreamLayout FL;
-  uint32_t NumFpmIntervals = getNumFpmIntervals(Msf);
+  uint32_t NumFpmIntervals = getNumFpmIntervals(Msf, IncludeUnusedFpmData);
   support::ulittle32_t FpmBlock = Msf.SB->FreeBlockMapBlock;
   assert(FpmBlock == 1 || FpmBlock == 2);
-  while (NumFpmIntervals > 0) {
+  if (AltFpm) {
+    // If they requested the alternate FPM, then 2 becomes 1 and 1 becomes 2.
+    FpmBlock = 3U - FpmBlock;
+  }
+  for (uint32_t I = 0; I < NumFpmIntervals; ++I) {
     FL.Blocks.push_back(FpmBlock);
     FpmBlock += msf::getFpmIntervalLength(Msf);
-    --NumFpmIntervals;
   }
-  FL.Length = getFullFpmByteSize(Msf);
+
+  if (IncludeUnusedFpmData)
+    FL.Length = NumFpmIntervals * Msf.SB->BlockSize;
+  else
+    FL.Length = divideCeil(Msf.SB->NumBlocks, 8);
 
   return FL;
 }
index 2a200edeaf7c4c4c4493a6a5f068141d4b691361..f28f944131b8821b71bc3a8841e341af17a429d0 100644 (file)
@@ -11,6 +11,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/DebugInfo/MSF/MSFCommon.h"
+#include "llvm/Support/BinaryStreamWriter.h"
 #include "llvm/Support/Endian.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/MathExtras.h"
@@ -347,9 +348,27 @@ WritableMappedBlockStream::createDirectoryStream(
 std::unique_ptr<WritableMappedBlockStream>
 WritableMappedBlockStream::createFpmStream(const MSFLayout &Layout,
                                            WritableBinaryStreamRef MsfData,
-                                           BumpPtrAllocator &Allocator) {
-  MSFStreamLayout SL(getFpmStreamLayout(Layout));
-  return createStream(Layout.SB->BlockSize, SL, MsfData, Allocator);
+                                           BumpPtrAllocator &Allocator,
+                                           bool AltFpm) {
+  // We only want to give the user a stream containing the bytes of the FPM that
+  // are actually valid, but we want to initialize all of the bytes, even those
+  // that come from reserved FPM blocks where the entire block is unused.  To do
+  // this, we first create the full layout, which gives us a stream with all
+  // bytes and all blocks, and initialize everything to 0xFF (all blocks in the
+  // file are unused).  Then we create the minimal layout (which contains only a
+  // subset of the bytes previously initialized), and return that to the user.
+  MSFStreamLayout MinLayout(getFpmStreamLayout(Layout, false, AltFpm));
+
+  MSFStreamLayout FullLayout(getFpmStreamLayout(Layout, true, AltFpm));
+  auto Result =
+      createStream(Layout.SB->BlockSize, FullLayout, MsfData, Allocator);
+  if (!Result)
+    return Result;
+  std::vector<uint8_t> InitData(Layout.SB->BlockSize, 0xFF);
+  BinaryStreamWriter Initializer(*Result);
+  while (Initializer.bytesRemaining() > 0)
+    cantFail(Initializer.writeBytes(InitData));
+  return createStream(Layout.SB->BlockSize, MinLayout, MsfData, Allocator);
 }
 
 Error WritableMappedBlockStream::readBytes(uint32_t Offset, uint32_t Size,
index e0bb460d21cd6d331b445c72292a5ba28d943740..de460d03e6b6c300d745781311a6659eed025051 100644 (file)
@@ -150,8 +150,7 @@ Error PDBFile::parseFileHeaders() {
       MappedBlockStream::createFpmStream(ContainerLayout, *Buffer, Allocator);
   BinaryStreamReader FpmReader(*FpmStream);
   ArrayRef<uint8_t> FpmBytes;
-  if (auto EC = FpmReader.readBytes(FpmBytes,
-                                    msf::getFullFpmByteSize(ContainerLayout)))
+  if (auto EC = FpmReader.readBytes(FpmBytes, FpmReader.bytesRemaining()))
     return EC;
   uint32_t BlocksRemaining = getBlockCount();
   uint32_t BI = 0;
index a8b80ac6d78846f508338a60b1d5369924085fb3..09e86bf0c13b1d02145aaf83c7297b7ab381d9a6 100644 (file)
@@ -155,6 +155,31 @@ Expected<uint32_t> PDBFileBuilder::getNamedStreamIndex(StringRef Name) const {
   return SN;
 }
 
+void PDBFileBuilder::commitFpm(WritableBinaryStream &MsfBuffer,
+                               const MSFLayout &Layout) {
+  auto FpmStream =
+      WritableMappedBlockStream::createFpmStream(Layout, MsfBuffer, Allocator);
+
+  // We only need to create the alt fpm stream so that it gets initialized.
+  WritableMappedBlockStream::createFpmStream(Layout, MsfBuffer, Allocator,
+                                             true);
+
+  uint32_t BI = 0;
+  BinaryStreamWriter FpmWriter(*FpmStream);
+  while (BI < Layout.SB->NumBlocks) {
+    uint8_t ThisByte = 0;
+    for (uint32_t I = 0; I < 8; ++I) {
+      bool IsFree =
+          (BI < Layout.SB->NumBlocks) ? Layout.FreePageMap.test(BI) : true;
+      uint8_t Mask = uint8_t(IsFree) << I;
+      ThisByte |= Mask;
+      ++BI;
+    }
+    cantFail(FpmWriter.writeObject(ThisByte));
+  }
+  assert(FpmWriter.bytesRemaining() == 0);
+}
+
 Error PDBFileBuilder::commit(StringRef Filename) {
   assert(!Filename.empty());
   auto ExpectedLayout = finalizeMsfLayout();
@@ -173,6 +198,9 @@ Error PDBFileBuilder::commit(StringRef Filename) {
 
   if (auto EC = Writer.writeObject(*Layout.SB))
     return EC;
+
+  commitFpm(Buffer, Layout);
+
   uint32_t BlockMapOffset =
       msf::blockToOffset(Layout.SB->BlockMapAddr, Layout.SB->BlockSize);
   Writer.setOffset(BlockMapOffset);
diff --git a/test/DebugInfo/PDB/write-fpm.test b/test/DebugInfo/PDB/write-fpm.test
new file mode 100644 (file)
index 0000000..6e2c67d
--- /dev/null
@@ -0,0 +1,11 @@
+; RUN: llvm-pdbutil yaml2pdb -pdb=%t.pdb %p/Inputs/one-symbol.yaml
+; RUN: llvm-pdbutil bytes -fpm %t.pdb | FileCheck %s
+
+
+CHECK:                             Free Page Map
+CHECK-NEXT: ============================================================
+CHECK-NEXT: Block 1 (
+CHECK-NEXT:   1000: 04F8FFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF  |................................|
+CHECK-NEXT:   1020: FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF  |................................|
+CHECK:        1FE0: FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF  |................................|
+CHECK:      )
index 3f5acc2d2ff2d964bfdbdd8ecebc0c1bf4a22e31..25e011178cddb198af5215a36bd33c165dbdd376 100644 (file)
@@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS
 set(DebugInfoMSFSources
   MappedBlockStreamTest.cpp
   MSFBuilderTest.cpp
+  MSFCommonTest.cpp
   )
 
 add_llvm_unittest(DebugInfoMSFTests
index 4791c982fd86d174600c75a669a6409a1e501074..a91ac8d443fe4d71d2f88792cb5376395b9ddc15 100644 (file)
 #include "llvm/DebugInfo/MSF/MSFCommon.h"
 #include "llvm/Testing/Support/Error.h"
 
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 using namespace llvm;
 using namespace llvm::msf;
+using namespace testing;
 
 namespace {
 class MSFBuilderTest : public testing::Test {
@@ -359,3 +362,36 @@ TEST_F(MSFBuilderTest, DirectoryBlockHintOverestimated) {
   EXPECT_EQ(1U, L.DirectoryBlocks.size());
   EXPECT_EQ(B + 1, L.DirectoryBlocks[0]);
 }
+
+TEST_F(MSFBuilderTest, StreamDoesntUseFpmBlocks) {
+  Expected<MSFBuilder> ExpectedMsf = MSFBuilder::create(Allocator, 4096);
+  ASSERT_THAT_EXPECTED(ExpectedMsf, Succeeded());
+  auto &Msf = *ExpectedMsf;
+
+  // A block is 4096 bytes, and every 4096 blocks we have 2 reserved FPM blocks.
+  // By creating add a stream that spans 4096*4096*3 bytes, we ensure that we
+  // cross over a couple of reserved FPM blocks, and that none of them are
+  // allocated to the stream.
+  constexpr uint32_t StreamSize = 4096 * 4096 * 3;
+  Expected<uint32_t> SN = Msf.addStream(StreamSize);
+  ASSERT_THAT_EXPECTED(SN, Succeeded());
+
+  auto ExpectedLayout = Msf.build();
+  ASSERT_THAT_EXPECTED(ExpectedLayout, Succeeded());
+  MSFLayout &L = *ExpectedLayout;
+  auto BlocksRef = L.StreamMap[*SN];
+  std::vector<uint32_t> Blocks(BlocksRef.begin(), BlocksRef.end());
+  EXPECT_EQ(StreamSize, L.StreamSizes[*SN]);
+
+  for (uint32_t I = 0; I <= 3; ++I) {
+    // Pages from the regular FPM are allocated, while pages from the alt fpm
+    // are free.
+    EXPECT_FALSE(L.FreePageMap.test(1 + I * 4096));
+    EXPECT_TRUE(L.FreePageMap.test(2 + I * 4096));
+  }
+
+  for (uint32_t I = 1; I <= 3; ++I) {
+    EXPECT_THAT(Blocks, Not(Contains(1 + I * 4096)));
+    EXPECT_THAT(Blocks, Not(Contains(2 + I * 4096)));
+  }
+}
diff --git a/unittests/DebugInfo/MSF/MSFCommonTest.cpp b/unittests/DebugInfo/MSF/MSFCommonTest.cpp
new file mode 100644 (file)
index 0000000..144f5b1
--- /dev/null
@@ -0,0 +1,104 @@
+//===- MSFBuilderTest.cpp  Tests manipulation of MSF stream metadata ------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/DebugInfo/MSF/MSFCommon.h"
+#include "llvm/Testing/Support/Error.h"
+
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::msf;
+
+TEST(MSFCommonTest, BytesToBlocks) {
+  EXPECT_EQ(0ULL, bytesToBlocks(0, 4096));
+  EXPECT_EQ(1ULL, bytesToBlocks(12, 4096));
+  EXPECT_EQ(1ULL, bytesToBlocks(4096, 4096));
+  EXPECT_EQ(2ULL, bytesToBlocks(4097, 4096));
+  EXPECT_EQ(2ULL, bytesToBlocks(600, 512));
+}
+
+TEST(MSFCommonTest, FpmIntervals) {
+  SuperBlock SB;
+  SB.FreeBlockMapBlock = 1;
+  SB.BlockSize = 4096;
+
+  MSFLayout L;
+  L.SB = &SB;
+
+  SB.NumBlocks = 12;
+  EXPECT_EQ(1u, getNumFpmIntervals(L, false));
+  SB.NumBlocks = SB.BlockSize;
+  EXPECT_EQ(1u, getNumFpmIntervals(L, false));
+  SB.NumBlocks = SB.BlockSize + 1;
+  EXPECT_EQ(1u, getNumFpmIntervals(L, false));
+  SB.NumBlocks = SB.BlockSize * 8;
+  EXPECT_EQ(1u, getNumFpmIntervals(L, false));
+  SB.NumBlocks = SB.BlockSize * 8 + 1;
+  EXPECT_EQ(2u, getNumFpmIntervals(L, false));
+
+  SB.NumBlocks = 12;
+  EXPECT_EQ(1u, getNumFpmIntervals(L, true));
+  SB.NumBlocks = SB.BlockSize;
+  EXPECT_EQ(1u, getNumFpmIntervals(L, true));
+  SB.NumBlocks = SB.BlockSize + 1;
+  EXPECT_EQ(2u, getNumFpmIntervals(L, true));
+  SB.NumBlocks = SB.BlockSize * 8;
+  EXPECT_EQ(8u, getNumFpmIntervals(L, true));
+  SB.NumBlocks = SB.BlockSize * 8 + 1;
+  EXPECT_EQ(9u, getNumFpmIntervals(L, true));
+}
+
+TEST(MSFCommonTest, FpmStreamLayout) {
+  SuperBlock SB;
+  MSFLayout L;
+  L.SB = &SB;
+  SB.FreeBlockMapBlock = 1;
+
+  // Each FPM block has 4096 bytes for a maximum of 4096*8 allocation states.
+  SB.BlockSize = 4096;
+
+  // 1. When we're not including unused FPM data, the length of the FPM stream
+  //    should be only long enough to contain 1 bit for each block.
+
+  // 1a. When the PDB has <= 4096*8 blocks, there should only be one FPM block.
+  SB.NumBlocks = 8000;
+  MSFStreamLayout SL = getFpmStreamLayout(L, false, false);
+  EXPECT_EQ(1000u, SL.Length);
+  EXPECT_EQ(1u, SL.Blocks.size());
+  EXPECT_EQ(SB.FreeBlockMapBlock, SL.Blocks.front());
+
+  SL = getFpmStreamLayout(L, false, true);
+  EXPECT_EQ(1000u, SL.Length);
+  EXPECT_EQ(1u, SL.Blocks.size());
+  EXPECT_EQ(3u - SB.FreeBlockMapBlock, SL.Blocks.front());
+
+  // 1b. When the PDB has > 4096*8 blocks, there should be multiple FPM blocks.
+  SB.NumBlocks = SB.BlockSize * 8 + 1;
+  SL = getFpmStreamLayout(L, false, false);
+  EXPECT_EQ(SB.BlockSize + 1, SL.Length);
+  EXPECT_EQ(2u, SL.Blocks.size());
+  EXPECT_EQ(SB.FreeBlockMapBlock, SL.Blocks[0]);
+  EXPECT_EQ(SB.FreeBlockMapBlock + SB.BlockSize, SL.Blocks[1]);
+
+  SL = getFpmStreamLayout(L, false, true);
+  EXPECT_EQ(SB.BlockSize + 1, SL.Length);
+  EXPECT_EQ(2u, SL.Blocks.size());
+  EXPECT_EQ(3u - SB.FreeBlockMapBlock, SL.Blocks[0]);
+  EXPECT_EQ(3u - SB.FreeBlockMapBlock + SB.BlockSize, SL.Blocks[1]);
+
+  // 2. When we are including unused FPM data, there should be one FPM block
+  //    at every BlockSize interval in the file, even if entire FPM blocks are
+  //    unused.
+  SB.NumBlocks = SB.BlockSize * 8 + 1;
+  SL = getFpmStreamLayout(L, true, false);
+  EXPECT_EQ(SB.BlockSize * 9, SL.Length);
+  EXPECT_EQ(9u, SL.Blocks.size());
+  for (int I = 0; I < 9; ++I)
+    EXPECT_EQ(I * SB.BlockSize + SB.FreeBlockMapBlock, SL.Blocks[I]);
+}
index 94cd347d09a62c61cca4b98970264c05cefe9661..94c4898551d4cdd3dd99ef177165774defca8727 100644 (file)
@@ -16,6 +16,7 @@
 #include "llvm/Support/BinaryStreamWriter.h"
 #include "llvm/Testing/Support/Error.h"
 
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 #include <unordered_map>
@@ -494,5 +495,59 @@ TEST(MappedBlockStreamTest, DataLivesAfterStreamDestruction) {
 
   EXPECT_EQ(Str[0], Str[1]);
 }
+} // namespace
+
+MATCHER_P3(BlockIsFilledWith, Layout, BlockIndex, Byte, "succeeded") {
+  uint64_t Offset = msf::blockToOffset(BlockIndex, Layout.SB->BlockSize);
+  ArrayRef<uint8_t> BufferRef = makeArrayRef(arg);
+  BufferRef = BufferRef.slice(Offset, Layout.SB->BlockSize);
+  return llvm::all_of(BufferRef, [this](uint8_t B) { return B == Byte; });
+}
+
+namespace {
+TEST(MappedBlockStreamTest, CreateFpmStream) {
+  BumpPtrAllocator Allocator;
+  SuperBlock SB;
+  MSFLayout L;
+  L.SB = &SB;
+
+  SB.FreeBlockMapBlock = 1;
+  SB.BlockSize = 4096;
+
+  constexpr uint32_t NumFileBlocks = 4096 * 4;
+
+  std::vector<uint8_t> MsfBuffer(NumFileBlocks * SB.BlockSize);
+  MutableBinaryByteStream MsfStream(MsfBuffer, llvm::support::little);
+
+  SB.NumBlocks = NumFileBlocks;
+  auto FpmStream =
+      WritableMappedBlockStream::createFpmStream(L, MsfStream, Allocator);
+  // 4096 * 4 / 8 = 2048 bytes of FPM data is needed to describe 4096 * 4
+  // blocks.  This translates to 1 FPM block.
+  EXPECT_EQ(2048u, FpmStream->getLength());
+  EXPECT_EQ(1u, FpmStream->getStreamLayout().Blocks.size());
+  EXPECT_EQ(1u, FpmStream->getStreamLayout().Blocks[0]);
+  // All blocks from FPM1 should be 1 initialized, and all blocks from FPM2
+  // should be 0 initialized (since we requested the main FPM, not the alt FPM)
+  for (int I = 0; I < 4; ++I) {
+    EXPECT_THAT(MsfBuffer, BlockIsFilledWith(L, 1 + I * SB.BlockSize, 0xFF));
+    EXPECT_THAT(MsfBuffer, BlockIsFilledWith(L, 2 + I * SB.BlockSize, 0));
+  }
+
+  ::memset(MsfBuffer.data(), 0, MsfBuffer.size());
+  FpmStream =
+      WritableMappedBlockStream::createFpmStream(L, MsfStream, Allocator, true);
+  // 4096 * 4 / 8 = 2048 bytes of FPM data is needed to describe 4096 * 4
+  // blocks.  This translates to 1 FPM block.
+  EXPECT_EQ(2048u, FpmStream->getLength());
+  EXPECT_EQ(1u, FpmStream->getStreamLayout().Blocks.size());
+  EXPECT_EQ(2u, FpmStream->getStreamLayout().Blocks[0]);
+  // All blocks from FPM2 should be 1 initialized, and all blocks from FPM1
+  // should be 0 initialized (since we requested the alt FPM, not the main FPM)
+  for (int I = 0; I < 4; ++I) {
+    EXPECT_THAT(MsfBuffer, BlockIsFilledWith(L, 1 + I * SB.BlockSize, 0));
+    EXPECT_THAT(MsfBuffer, BlockIsFilledWith(L, 2 + I * SB.BlockSize, 0xFF));
+  }
+}
 
 } // end anonymous namespace