Add support for sub-byte aligned writes to lib/Support/Endian.h
authorTeresa Johnson <tejohnson@google.com>
Wed, 30 Sep 2015 13:20:37 +0000 (13:20 +0000)
committerTeresa Johnson <tejohnson@google.com>
Wed, 30 Sep 2015 13:20:37 +0000 (13:20 +0000)
Summary:
As per Duncan's review for D12536, I extracted the sub-byte bit aligned
reading and writing code into lib/Support, and generalized it. Added calls from
BackpatchWord. Also added unittests.

Reviewers: dexonsmith

Subscribers: llvm-commits

Differential Revision: http://reviews.llvm.org/D13189

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

include/llvm/Bitcode/BitstreamWriter.h
include/llvm/Support/Endian.h
unittests/Support/EndianTest.cpp

index df07204..f4c8366 100644 (file)
@@ -102,18 +102,13 @@ public:
   /// Backpatch a 32-bit word in the output at the given bit offset
   /// with the specified value.
   void BackpatchWord(uint64_t BitNo, unsigned NewWord) {
+    using namespace llvm::support;
     unsigned ByteNo = BitNo / 8;
-    if ((BitNo & 7) == 0) {
-      // Already 8-bit aligned
-      support::endian::write32le(&Out[ByteNo], NewWord);
-    } else {
-      uint64_t CurDWord = support::endian::read64le(&Out[ByteNo]);
-      unsigned StartBit = BitNo & 7;
-      // Currently expect to backpatch 0-value placeholders.
-      assert(((CurDWord >> StartBit) & 0xffffffff) == 0);
-      CurDWord |= NewWord << StartBit;
-      support::endian::write64le(&Out[ByteNo], CurDWord);
-    }
+    assert((!endian::readAtBitAlignment<uint32_t, little, unaligned>(
+               &Out[ByteNo], BitNo & 7)) &&
+           "Expected to be patching over 0-value placeholders");
+    endian::writeAtBitAlignment<uint32_t, little, unaligned>(
+        &Out[ByteNo], NewWord, BitNo & 7);
   }
 
   void Emit(uint32_t Val, unsigned NumBits) {
index fd59009..b72200e 100644 (file)
@@ -77,6 +77,81 @@ inline void write(void *memory, value_type value) {
          &value,
          sizeof(value_type));
 }
+
+/// Read a value of a particular endianness from memory, for a location
+/// that starts at the given bit offset within the first byte.
+template <typename value_type, endianness endian, std::size_t alignment>
+inline value_type readAtBitAlignment(const void *memory, uint64_t startBit) {
+  assert(startBit < 8);
+  if (startBit == 0)
+    return read<value_type, endian, alignment>(memory);
+  else {
+    // Read two values and compose the result from them.
+    value_type val[2];
+    memcpy(&val[0],
+           LLVM_ASSUME_ALIGNED(
+               memory, (detail::PickAlignment<value_type, alignment>::value)),
+           sizeof(value_type) * 2);
+    val[0] = byte_swap<value_type, endian>(val[0]);
+    val[1] = byte_swap<value_type, endian>(val[1]);
+
+    // Shift bits from the lower value into place.
+    unsigned lowerVal = val[0] >> startBit;
+    // Mask off upper bits after right shift in case of signed type.
+    unsigned numBitsFirstVal = (sizeof(value_type) * 8) - startBit;
+    lowerVal &= (1 << numBitsFirstVal) - 1;
+
+    // Get the bits from the upper value.
+    unsigned upperVal = val[1] & ((1 << startBit) - 1);
+    // Shift them in to place.
+    upperVal <<= numBitsFirstVal;
+
+    return lowerVal | upperVal;
+  }
+}
+
+/// Write a value to memory with a particular endianness, for a location
+/// that starts at the given bit offset within the first byte.
+template <typename value_type, endianness endian, std::size_t alignment>
+inline void writeAtBitAlignment(void *memory, value_type value,
+                                uint64_t startBit) {
+  assert(startBit < 8);
+  if (startBit == 0)
+    write<value_type, endian, alignment>(memory, value);
+  else {
+    // Read two values and shift the result into them.
+    value_type val[2];
+    memcpy(&val[0],
+           LLVM_ASSUME_ALIGNED(
+               memory, (detail::PickAlignment<value_type, alignment>::value)),
+           sizeof(value_type) * 2);
+    val[0] = byte_swap<value_type, endian>(val[0]);
+    val[1] = byte_swap<value_type, endian>(val[1]);
+
+    // Mask off any existing bits in the upper part of the lower value that
+    // we want to replace.
+    val[0] &= (1 << startBit) - 1;
+    // Now shift in the new bits
+    val[0] |= value << startBit;
+
+    // Mask off any existing bits in the lower part of the upper value that
+    // we want to replace.
+    val[1] &= ~((1 << startBit) - 1);
+    // Next shift the bits that go into the upper value into position.
+    unsigned numBitsFirstVal = (sizeof(value_type) * 8) - startBit;
+    unsigned upperVal = value >> numBitsFirstVal;
+    // Mask off upper bits after right shift in case of signed type.
+    upperVal &= (1 << startBit) - 1;
+    val[1] |= upperVal;
+
+    // Finally, rewrite values.
+    val[0] = byte_swap<value_type, endian>(val[0]);
+    val[1] = byte_swap<value_type, endian>(val[1]);
+    memcpy(LLVM_ASSUME_ALIGNED(
+               memory, (detail::PickAlignment<value_type, alignment>::value)),
+           &val[0], sizeof(value_type) * 2);
+  }
+}
 } // end namespace endian
 
 namespace detail {
index 8f93553..ee236d0 100644 (file)
@@ -32,6 +32,54 @@ TEST(Endian, Read) {
             (endian::read<int32_t, little, unaligned>(littleval + 1)));
 }
 
+TEST(Endian, ReadBitAligned) {
+  // Simple test to make sure we properly pull out the 0x0 word.
+  unsigned char littleval[] = {0x3f, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff};
+  unsigned char bigval[] = {0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0};
+  EXPECT_EQ(
+      (endian::readAtBitAlignment<int, little, unaligned>(&littleval[0], 6)),
+      0x0);
+  EXPECT_EQ((endian::readAtBitAlignment<int, big, unaligned>(&bigval[0], 6)),
+            0x0);
+  // Test to make sure that signed right shift of 0xf0000000 is masked
+  // properly.
+  unsigned char littleval2[] = {0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00};
+  unsigned char bigval2[] = {0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+  EXPECT_EQ(
+      (endian::readAtBitAlignment<int, little, unaligned>(&littleval2[0], 4)),
+      0x0f000000);
+  EXPECT_EQ((endian::readAtBitAlignment<int, big, unaligned>(&bigval2[0], 4)),
+            0x0f000000);
+}
+
+TEST(Endian, WriteBitAligned) {
+  // This test ensures that signed right shift of 0xffffaa is masked
+  // properly.
+  unsigned char bigval[8] = {0x00};
+  endian::writeAtBitAlignment<int32_t, big, unaligned>(bigval, (int)0xffffaaaa,
+                                                       4);
+  EXPECT_EQ(bigval[0], 0xff);
+  EXPECT_EQ(bigval[1], 0xfa);
+  EXPECT_EQ(bigval[2], 0xaa);
+  EXPECT_EQ(bigval[3], 0xa0);
+  EXPECT_EQ(bigval[4], 0x00);
+  EXPECT_EQ(bigval[5], 0x00);
+  EXPECT_EQ(bigval[6], 0x00);
+  EXPECT_EQ(bigval[7], 0x0f);
+
+  unsigned char littleval[8] = {0x00};
+  endian::writeAtBitAlignment<int32_t, little, unaligned>(littleval,
+                                                          (int)0xffffaaaa, 4);
+  EXPECT_EQ(littleval[0], 0xa0);
+  EXPECT_EQ(littleval[1], 0xaa);
+  EXPECT_EQ(littleval[2], 0xfa);
+  EXPECT_EQ(littleval[3], 0xff);
+  EXPECT_EQ(littleval[4], 0x0f);
+  EXPECT_EQ(littleval[5], 0x00);
+  EXPECT_EQ(littleval[6], 0x00);
+  EXPECT_EQ(littleval[7], 0x00);
+}
+
 TEST(Endian, Write) {
   unsigned char data[5];
   endian::write<int32_t, big, unaligned>(data, -1362446643);