This is an archive of the discontinued LLVM Phabricator instance.

Use a byte array as an internal buffer of BitVector.
Needs ReviewPublic

Authored by ruiu on Aug 10 2016, 4:42 PM.

Details

Reviewers
zturner
Summary

Previously, BitVector was using an array of unsigned long as an
internal buffer. That design choice was not desirable if we want to
allow exporting bitmaps or importing existing bitmaps, because the
internal buffer's contents are different on machines with different
endianness.

In PDB file processing, we create bitmaps and then write it to a file.
We also read a bitmap from disk and convert it to BitVector.
All these operations involve for-loop for each bit and data copying
because we cannot map existing bitmaps to BitVector.

This patch solves the problem. Now the internal buffer is represented
as a byte array. So, two BitVectors having the identical bits are now
represented in the same way both on the LE and BE machines. The
endianness problem is gone.

In this patch, we are still doing word-size operations instead of
byte-size operations where possible. Changing the internal format
shouldn't degrade the performance.

Diff Detail

Event Timeline

ruiu updated this revision to Diff 67631.Aug 10 2016, 4:42 PM
ruiu retitled this revision from to Use a byte array as an internal buffer of BitVector..
ruiu updated this object.
ruiu added a reviewer: zturner.
ruiu added a subscriber: llvm-commits.
bryant added a subscriber: bryant.Aug 10 2016, 5:16 PM
bryant added inline comments.
include/llvm/ADT/BitVector.h
480

Would this violate strict aliasing rules?

ruiu updated this revision to Diff 67637.Aug 10 2016, 5:19 PM
  • Upload the correct patch. I split the patch locally, so the previous patch didn't include all changes.
ruiu added inline comments.Aug 10 2016, 5:23 PM
include/llvm/ADT/BitVector.h
480

I think it is, but we are doing this at so many places to read and convert values in different endianness.

zturner added inline comments.Aug 10 2016, 5:32 PM
include/llvm/ADT/BitVector.h
44

Since words are always little endian, why not make the type of Word be support::ulittle32_t

480

If the type of Word is support::ulittle32_t, then you don't have to worry about this, as you can just delete this entire function.

ruiu added inline comments.Aug 10 2016, 5:35 PM
include/llvm/ADT/BitVector.h
44

But in many cases you don't actually care about endianness. For example, for count(), only the number of 1s matters. You don't need to swap bytes. So I think using ulittle32_t is inefficient.

bryant added inline comments.Aug 10 2016, 5:37 PM
include/llvm/ADT/BitVector.h
480

not sure if ub is good, regardless. maybe return WordLE(WW); and let packed_endian_specific_integral::operator value_type do the potential conversion for you through void *, which is one of two exceptions to strict aliasing.

zturner added inline comments.Aug 10 2016, 5:42 PM
include/llvm/ADT/BitVector.h
44

But if you don't care about endianness, then it doesn't matter what you pass to popcnt either, so long as it contains the same number of 1s. So you could write it like this:

size_type count() const {
  unsigned NumBits = 0;
  for (unsigned i = 0; i < Bytes.size(); i += WORD_SIZE)
    NumBits += countPopulation(*reinterpret_cast<uint32_t*>(&Bytes[i]));
  return NumBits;
}

Maybe it's better to delete the Words array entirely and just do word-sized operations by casting the underlying byte vector.

ruiu added inline comments.Aug 10 2016, 5:53 PM
include/llvm/ADT/BitVector.h
44

I tried to remove Words, but the result did not look great. Casting to Word everywhere makes code longer and error-prone. As an example, current count() is this, which is much easier to understand than yours.

size_type count() const {
  unsigned N = 0;
  for (Word W : Words)
    N += countPopulation(W);
  return N;
}
ruiu updated this revision to Diff 67643.Aug 10 2016, 5:54 PM
  • Use getSwappedBytes instead of packed_endian_specific_integral.
ruiu added inline comments.Aug 10 2016, 5:55 PM
include/llvm/ADT/BitVector.h
480

I used getSwappedBytes instead.

zturner added inline comments.Aug 10 2016, 5:57 PM
include/llvm/ADT/BitVector.h
478

I don't think it's a good idea to check system endianness. This would be the only place in the entire codebase we checked it aside from something in libfuzzer. What about the alternative that bryant proposed earlier?

ruiu added inline comments.Aug 10 2016, 6:03 PM
include/llvm/ADT/BitVector.h
480

How does it work? What is WW?

bryant added inline comments.Aug 10 2016, 6:18 PM
include/llvm/ADT/BitVector.h
480

typo. i meant W. as for how it works:

explicit packed_endian_specific_integral(value_type val) { *this = val; }

operator value_type() const {
  return endian::read<value_type, endian, alignment>(
    (const void*)Value.buffer);
}
ruiu added inline comments.Aug 10 2016, 6:23 PM
include/llvm/ADT/BitVector.h
480

Is it different from

return *(WordLE *)((void *)&W)

?

bryant added inline comments.Aug 10 2016, 6:30 PM
include/llvm/ADT/BitVector.h
480

unfortunately, that would also be ub.

Have you actually measured this to be a major improvement? Are there so many bits that the old way is a major bottleneck?
The BitVector is used for the CodeGen and optimizer, I'm not entirely convinced it makes sense to modify this generic datastructure so drastically for PDBs...