clean up memory allocation in bitfield (#910)

clean up memory allocation in bitfield
This commit is contained in:
Arvid Norberg 2016-07-16 14:31:25 -07:00 committed by GitHub
parent e72f95e0d9
commit 31fbbab89c
4 changed files with 157 additions and 100 deletions

View File

@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/assert.hpp" #include "libtorrent/assert.hpp"
#include "libtorrent/config.hpp" #include "libtorrent/config.hpp"
#include "libtorrent/buffer.hpp"
#include "libtorrent/aux_/byteswap.hpp" #include "libtorrent/aux_/byteswap.hpp"
#include <cstring> // for memset and memcpy #include <cstring> // for memset and memcpy
#include <cstdlib> // for malloc, free and realloc
#include <cstdint> // uint32_t #include <cstdint> // uint32_t
#include <algorithm> // for min() #include <memory> // for unique_ptr
namespace libtorrent namespace libtorrent
{ {
@ -56,22 +56,12 @@ namespace libtorrent
// The constructor taking a pointer ``b`` and ``bits`` copies a bitfield // The constructor taking a pointer ``b`` and ``bits`` copies a bitfield
// from the specified buffer, and ``bits`` number of bits (rounded up to // from the specified buffer, and ``bits`` number of bits (rounded up to
// the nearest byte boundary). // the nearest byte boundary).
bitfield(): m_buf(nullptr) {} bitfield() = default;
bitfield(int bits): m_buf(nullptr) bitfield(int bits) { resize(bits); }
{ resize(bits); } bitfield(int bits, bool val) { resize(bits, val); }
bitfield(int bits, bool val): m_buf(nullptr) bitfield(char const* b, int bits) { assign(b, bits); }
{ resize(bits, val); } bitfield(bitfield const& rhs) { assign(rhs.data(), rhs.size()); }
bitfield(char const* b, int bits): m_buf(nullptr) bitfield(bitfield&& rhs) = default;
{ assign(b, bits); }
bitfield(bitfield const& rhs): m_buf(nullptr)
{ assign(rhs.data(), rhs.size()); }
#if __cplusplus > 199711L
bitfield(bitfield&& rhs): m_buf(rhs.m_buf)
{ rhs.m_buf = nullptr; }
#endif
// hidden
~bitfield() { dealloc(); }
// copy bitfield from buffer ``b`` of ``bits`` number of bits, rounded up to // copy bitfield from buffer ``b`` of ``bits`` number of bits, rounded up to
// the nearest byte boundary. // the nearest byte boundary.
@ -80,7 +70,7 @@ namespace libtorrent
resize(bits); resize(bits);
if (bits > 0) if (bits > 0)
{ {
std::memcpy(m_buf, b, size_t((bits + 7) / 8)); std::memcpy(buf(), b, size_t((bits + 7) / 8));
clear_trailing_bits(); clear_trailing_bits();
} }
} }
@ -93,7 +83,7 @@ namespace libtorrent
{ {
TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index >= 0);
TORRENT_ASSERT(index < size()); TORRENT_ASSERT(index < size());
return (m_buf[index / 32] & aux::host_to_network((0x80000000 >> (index & 31)))) != 0; return (buf()[index / 32] & aux::host_to_network((0x80000000 >> (index & 31)))) != 0;
} }
// set bit at ``index`` to 0 (clear_bit) or 1 (set_bit). // set bit at ``index`` to 0 (clear_bit) or 1 (set_bit).
@ -101,13 +91,13 @@ namespace libtorrent
{ {
TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index >= 0);
TORRENT_ASSERT(index < size()); TORRENT_ASSERT(index < size());
m_buf[index / 32] &= aux::host_to_network(~(0x80000000 >> (index & 31))); buf()[index / 32] &= aux::host_to_network(~(0x80000000 >> (index & 31)));
} }
void set_bit(int index) void set_bit(int index)
{ {
TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index >= 0);
TORRENT_ASSERT(index < size()); TORRENT_ASSERT(index < size());
m_buf[index / 32] |= aux::host_to_network((0x80000000 >> (index & 31))); buf()[index / 32] |= aux::host_to_network((0x80000000 >> (index & 31)));
} }
// returns true if all bits in the bitfield are set // returns true if all bits in the bitfield are set
@ -116,9 +106,10 @@ namespace libtorrent
bool none_set() const bool none_set() const
{ {
const int words = num_words(); const int words = num_words();
std::uint32_t const* b = buf();
for (int i = 0; i < words; ++i) for (int i = 0; i < words; ++i)
{ {
if (m_buf[i] != 0) return false; if (b[i] != 0) return false;
} }
return true; return true;
} }
@ -126,7 +117,7 @@ namespace libtorrent
// returns the size of the bitfield in bits. // returns the size of the bitfield in bits.
int size() const int size() const
{ {
return m_buf == nullptr ? 0 : int(m_buf[-1]); return m_buf == nullptr ? 0 : int(m_buf[0]);
} }
int num_words() const int num_words() const
@ -135,28 +126,29 @@ namespace libtorrent
} }
// returns true if the bitfield has zero size. // returns true if the bitfield has zero size.
bool empty() const { return m_buf == nullptr ? true : m_buf[-1] == 0; } bool empty() const { return m_buf == nullptr ? true : m_buf[0] == 0; }
// returns a pointer to the internal buffer of the bitfield. // returns a pointer to the internal buffer of the bitfield.
char const* data() const { return reinterpret_cast<char const*>(m_buf); } char const* data() const { return m_buf ? reinterpret_cast<char const*>(&m_buf[1]) : nullptr; }
char* data() { return m_buf ? reinterpret_cast<char*>(&m_buf[1]) : nullptr; }
#ifndef TORRENT_NO_DEPRECATE #ifndef TORRENT_NO_DEPRECATE
TORRENT_DEPRECATED TORRENT_DEPRECATED
char const* bytes() const { return data(); } char const* bytes() const { return data(); }
#endif #endif
// copy operator // assignment operator
bitfield& operator=(bitfield const& rhs) bitfield& operator=(bitfield const& rhs)
{ {
assign(rhs.data(), rhs.size()); assign(rhs.data(), rhs.size());
return *this; return *this;
} }
bitfield& operator=(bitfield&& rhs) = default;
void swap(bitfield& rhs) void swap(bitfield& rhs)
{ {
std::uint32_t* tmp = m_buf; std::swap(m_buf, rhs.m_buf);
m_buf = rhs.m_buf;
rhs.m_buf = tmp;
} }
// count the number of bits in the bitfield that are set to 1. // count the number of bits in the bitfield that are set to 1.
@ -220,49 +212,44 @@ namespace libtorrent
std::uint32_t bit; std::uint32_t bit;
}; };
const_iterator begin() const { return const_iterator(m_buf, 0); } const_iterator begin() const { return const_iterator(m_buf ? buf() : nullptr, 0); }
const_iterator end() const { return const_iterator( const_iterator end() const { return const_iterator(
m_buf + num_words() - (((size() & 31) == 0) ? 0 : 1), size() & 31); } m_buf ? buf() + num_words() - (((size() & 31) == 0) ? 0 : 1) : nullptr, size() & 31); }
// set the size of the bitfield to ``bits`` length. If the bitfield is extended, // set the size of the bitfield to ``bits`` length. If the bitfield is extended,
// the new bits are initialized to ``val``. // the new bits are initialized to ``val``.
void resize(int bits, bool val); void resize(int bits, bool val);
void resize(int bits); void resize(int bits);
// set all bits in the bitfield to 1 (set_all) or 0 (clear_all). // set all bits in the bitfield to 1 (set_all) or 0 (clear_all).
void set_all() void set_all()
{ {
std::memset(m_buf, 0xff, size_t(num_words() * 4)); if (m_buf == nullptr) return;
std::memset(buf(), 0xff, size_t(num_words() * 4));
clear_trailing_bits(); clear_trailing_bits();
} }
void clear_all() void clear_all()
{ {
std::memset(m_buf, 0x00, size_t(num_words() * 4)); if (m_buf == nullptr) return;
std::memset(buf(), 0x00, size_t(num_words() * 4));
} }
// make the bitfield empty, of zero size. // make the bitfield empty, of zero size.
void clear() { dealloc(); } void clear() { m_buf.reset(); }
private: private:
std::uint32_t const* buf() const { TORRENT_ASSERT(m_buf); return &m_buf[1]; }
std::uint32_t* buf() { TORRENT_ASSERT(m_buf); return &m_buf[1]; }
void clear_trailing_bits() void clear_trailing_bits()
{ {
// clear the tail bits in the last byte // clear the tail bits in the last byte
if (size() & 31) m_buf[num_words() - 1] &= aux::host_to_network(0xffffffff << (32 - (size() & 31))); if (size() & 31) buf()[num_words() - 1] &= aux::host_to_network(0xffffffff << (32 - (size() & 31)));
}
void dealloc()
{
if (m_buf) std::free(m_buf-1);
m_buf = nullptr;
} }
// the first element is not part of the bitfield, it's the // the first element is not part of the bitfield, it's the
// number of bits. For this purpose, the m_buf actually points // number of bits.
// the element 1, not 0. To access the size (in bits), access std::unique_ptr<std::uint32_t[]> m_buf;
// m_buf[-1]
std::uint32_t* m_buf;
}; };
} }

View File

@ -135,7 +135,7 @@ public:
// allocate an uninitialized buffer of the specified size // allocate an uninitialized buffer of the specified size
buffer(std::size_t size = 0) buffer(std::size_t size = 0)
{ {
TORRENT_ASSERT(size < std::size_t(std::numeric_limits<std::int32_t>::max())); TORRENT_ASSERT(size < std::size_t((std::numeric_limits<std::int32_t>::max)()));
if (size == 0) return; if (size == 0) return;
@ -172,7 +172,7 @@ public:
TORRENT_ASSERT(initialize.size() <= size); TORRENT_ASSERT(initialize.size() <= size);
if (initialize.size() > 0) if (initialize.size() > 0)
{ {
memcpy(m_begin, initialize.data(), std::min(initialize.size(), size)); memcpy(m_begin, initialize.data(), (std::min)(initialize.size(), size));
} }
} }

View File

@ -45,16 +45,16 @@ namespace libtorrent
{ {
bool bitfield::all_set() const bool bitfield::all_set() const
{ {
const int words = size() / 32; int const words = size() / 32;
for (int i = 0; i < words; ++i) for (int i = 1; i < words + 1; ++i)
{ {
if (m_buf[i] != 0xffffffff) return false; if (m_buf[i] != 0xffffffff) return false;
} }
int rest = size() & 31; int const rest = size() & 31;
if (rest > 0) if (rest > 0)
{ {
std::uint32_t mask = aux::host_to_network(0xffffffff << (32-rest)); std::uint32_t const mask = aux::host_to_network(0xffffffff << (32-rest));
if ((m_buf[words] & mask) != mask) return false; if ((m_buf[words+1] & mask) != mask) return false;
} }
return true; return true;
} }
@ -62,11 +62,11 @@ namespace libtorrent
int bitfield::count() const int bitfield::count() const
{ {
int ret = 0; int ret = 0;
const int words = num_words(); int const words = num_words();
#if TORRENT_HAS_SSE #if TORRENT_HAS_SSE
if (aux::mmx_support) if (aux::mmx_support)
{ {
for (int i = 0; i < words; ++i) for (int i = 1; i < words+1; ++i)
{ {
#ifdef __GNUC__ #ifdef __GNUC__
std::uint32_t cnt = 0; std::uint32_t cnt = 0;
@ -79,6 +79,8 @@ namespace libtorrent
#endif #endif
} }
TORRENT_ASSERT(ret <= size());
TORRENT_ASSERT(ret >= 0);
return ret; return ret;
} }
#endif // TORRENT_HAS_SSE #endif // TORRENT_HAS_SSE
@ -86,7 +88,7 @@ namespace libtorrent
#if TORRENT_HAS_ARM_NEON #if TORRENT_HAS_ARM_NEON
if (aux::arm_neon_support) if (aux::arm_neon_support)
{ {
for (int i = 0; i < words; ++i) for (int i = 1; i < words + 1; ++i)
{ {
uint8x8_t const in_val = vld1_u8((unsigned char *) &m_buf[i]); uint8x8_t const in_val = vld1_u8((unsigned char *) &m_buf[i]);
uint8x8_t const cnt8x8_val = vcnt_u8(in_val); uint8x8_t const cnt8x8_val = vcnt_u8(in_val);
@ -98,13 +100,15 @@ namespace libtorrent
ret += cnt; ret += cnt;
} }
TORRENT_ASSERT(ret <= size());
TORRENT_ASSERT(ret >= 0);
return ret; return ret;
} }
#endif // TORRENT_HAS_ARM_NEON #endif // TORRENT_HAS_ARM_NEON
for (int i = 0; i < words; ++i) for (int i = 1; i < words + 1; ++i)
{ {
std::uint32_t v = m_buf[i]; std::uint32_t const v = m_buf[i];
// from: // from:
// http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
static const int S[] = {1, 2, 4, 8, 16}; // Magic Binary Numbers static const int S[] = {1, 2, 4, 8, 16}; // Magic Binary Numbers
@ -116,6 +120,7 @@ namespace libtorrent
c = ((c >> S[3]) + c) & B[3]; c = ((c >> S[3]) + c) & B[3];
c = ((c >> S[4]) + c) & B[4]; c = ((c >> S[4]) + c) & B[4];
ret += c; ret += c;
TORRENT_ASSERT(ret <= size());
} }
TORRENT_ASSERT(ret <= size()); TORRENT_ASSERT(ret <= size());
@ -127,24 +132,24 @@ namespace libtorrent
{ {
if (bits == size()) return; if (bits == size()) return;
int s = size(); int const s = size();
int b = size() & 31; int const b = size() & 31;
resize(bits); resize(bits);
if (s >= size()) return; if (s >= size()) return;
int old_size_words = (s + 31) / 32; int const old_size_words = (s + 31) / 32;
int new_size_words = num_words(); int const new_size_words = num_words();
if (val) if (val)
{ {
if (old_size_words && b) m_buf[old_size_words - 1] |= aux::host_to_network((0xffffffff >> b)); if (old_size_words && b) buf()[old_size_words - 1] |= aux::host_to_network((0xffffffff >> b));
if (old_size_words < new_size_words) if (old_size_words < new_size_words)
std::memset(m_buf + old_size_words, 0xff std::memset(buf() + old_size_words, 0xff
, size_t((new_size_words - old_size_words) * 4)); , size_t((new_size_words - old_size_words) * 4));
clear_trailing_bits(); clear_trailing_bits();
} }
else else
{ {
if (old_size_words < new_size_words) if (old_size_words < new_size_words)
std::memset(m_buf + old_size_words, 0x00 std::memset(buf() + old_size_words, 0x00
, size_t((new_size_words - old_size_words) * 4)); , size_t((new_size_words - old_size_words) * 4));
} }
TORRENT_ASSERT(size() == bits); TORRENT_ASSERT(size() == bits);
@ -155,37 +160,35 @@ namespace libtorrent
if (bits == size()) return; if (bits == size()) return;
TORRENT_ASSERT(bits >= 0); TORRENT_ASSERT(bits >= 0);
const int b = (bits + 31) / 32;
if (bits == 0) if (bits == 0)
{ {
if (m_buf != nullptr) m_buf.reset();
{
std::free(m_buf-1);
m_buf = nullptr;
}
return; return;
} }
int const new_size_words = (bits + 31) / 32;
if (m_buf) int const cur_size_words = num_words();
if (cur_size_words != new_size_words)
{ {
std::uint32_t* tmp = static_cast<std::uint32_t*>(std::realloc(m_buf-1, (b+1) * 4)); std::unique_ptr<std::uint32_t[]> b(new std::uint32_t[new_size_words + 1]);
#ifndef BOOST_NO_EXCEPTIONS #ifdef BOOST_NO_EXCEPTIONS
if (tmp == nullptr) throw std::bad_alloc(); if (b == nullptr) std::terminate();
#endif #endif
m_buf = tmp + 1; b[0] = bits;
m_buf[-1] = bits; if (m_buf) memcpy(&b[1], buf(), (std::min)(new_size_words, cur_size_words) * 4);
if (new_size_words > cur_size_words)
{
memset(&b[1 + cur_size_words], 0
, (new_size_words - cur_size_words) * 4);
}
m_buf = std::move(b);
} }
else else
{ {
// +1 because the first word is the size (in bits) m_buf[0] = bits;
std::uint32_t* tmp = static_cast<std::uint32_t*>(std::malloc((b+1) * 4));
#ifndef BOOST_NO_EXCEPTIONS
if (tmp == nullptr) throw std::bad_alloc();
#endif
m_buf = tmp + 1;
m_buf[-1] = bits;
} }
clear_trailing_bits(); clear_trailing_bits();
TORRENT_ASSERT(size() == bits); TORRENT_ASSERT(size() == bits);
} }
} }

View File

@ -40,10 +40,9 @@ using namespace libtorrent;
void print_bitfield(bitfield const& b) void print_bitfield(bitfield const& b)
{ {
std::string out; std::string out;
for (int i = 0; i < b.size(); ++i) out.reserve(b.size());
{ for (bool bit : b)
out += b.get_bit(i) ? "1" : "0"; out += bit ? '1' : '0';
}
std::printf("%s\n", out.c_str()); std::printf("%s\n", out.c_str());
} }
@ -116,16 +115,16 @@ TORRENT_TEST(bitfield)
test1.resize(100, true); test1.resize(100, true);
TEST_CHECK(test1.all_set() == true); TEST_CHECK(test1.all_set() == true);
TEST_CHECK(test1.count() == 100); TEST_EQUAL(test1.count(), 100);
test1.resize(200, false); test1.resize(200, false);
TEST_CHECK(test1.all_set() == false); TEST_CHECK(test1.all_set() == false);
TEST_CHECK(test1.count() == 100); TEST_EQUAL(test1.count(), 100);
test1.resize(50, false); test1.resize(50, false);
TEST_CHECK(test1.all_set() == true); TEST_CHECK(test1.all_set() == true);
TEST_CHECK(test1.count() == 50); TEST_EQUAL(test1.count(), 50);
test1.resize(101, true); test1.resize(101, true);
TEST_CHECK(test1.all_set() == true); TEST_CHECK(test1.all_set() == true);
TEST_CHECK(test1.count() == 101); TEST_EQUAL(test1.count(), 101);
std::uint8_t b1[] = { 0x08, 0x10 }; std::uint8_t b1[] = { 0x08, 0x10 };
test1.assign((char*)b1, 14); test1.assign((char*)b1, 14);
@ -149,7 +148,11 @@ TORRENT_TEST(bitfield)
TEST_EQUAL(test1.get_bit(0), true); TEST_EQUAL(test1.get_bit(0), true);
TEST_EQUAL(test1.get_bit(1), false); TEST_EQUAL(test1.get_bit(1), false);
TEST_EQUAL(test1.get_bit(2), true); TEST_EQUAL(test1.get_bit(2), true);
}
TORRENT_TEST(test_assign3)
{
bitfield test1;
std::uint8_t b2[] = { 0x08, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf, 0xc, 0x7f }; std::uint8_t b2[] = { 0x08, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf, 0xc, 0x7f };
test1.assign((char*)b2, 72); test1.assign((char*)b2, 72);
print_bitfield(test1); print_bitfield(test1);
@ -159,31 +162,42 @@ TORRENT_TEST(bitfield)
test1.assign((char*)b3, 64); test1.assign((char*)b3, 64);
print_bitfield(test1); print_bitfield(test1);
TEST_EQUAL(test1.count(), 40); TEST_EQUAL(test1.count(), 40);
}
TORRENT_TEST(test_iterators)
{
bitfield test1;
for (int i = 0; i < 100; ++i) for (int i = 0; i < 100; ++i)
{ {
test1.resize(i, false); test1.resize(i, false);
test_iterators(test1); test_iterators(test1);
} }
}
// test alignment TORRENT_TEST(test_assign)
std::uint32_t buffer[4]; {
char* b = (char*)buffer; std::array<char, 16> b;
bitfield test1;
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
{ {
b[i] = char(0xc0); b[i] = char(0xc0);
test1.assign(b + i, 2); test1.assign(&b[i], 2);
print_bitfield(test1); print_bitfield(test1);
TEST_EQUAL(test1.count(), 2); TEST_EQUAL(test1.count(), 2);
TEST_EQUAL(test1.all_set(), true); TEST_EQUAL(test1.all_set(), true);
} }
}
TORRENT_TEST(test_assign2)
{
std::array<char, 16> b;
bitfield test1;
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
{ {
memset(b + i, 0xff, 5); memset(&b[i], 0xff, 5);
b[i + 5] = char(0xc0); b[i + 5] = char(0xc0);
test1.assign(b + i, 32 + 8 + 2); test1.assign(&b[i], 32 + 8 + 2);
print_bitfield(test1); print_bitfield(test1);
TEST_EQUAL(test1.count(), 32 + 8 + 2); TEST_EQUAL(test1.count(), 32 + 8 + 2);
TEST_EQUAL(test1.all_set(), true); TEST_EQUAL(test1.all_set(), true);
@ -195,3 +209,56 @@ TORRENT_TEST(bitfield)
TORRENT_ASSERT(!aux::arm_neon_support); TORRENT_ASSERT(!aux::arm_neon_support);
#endif #endif
} }
TORRENT_TEST(test_resize_val)
{
std::array<char, 8> b;
b.fill(0xcc);
bitfield test1(b.data(), 8 * 8);
print_bitfield(test1);
TEST_EQUAL(test1.size(), 8 * 8);
TEST_EQUAL(test1.count(), 4 * 8);
for (int i = 1; i < 4 * 8; ++i)
{
test1.resize(8 * 8 + i, true);
print_bitfield(test1);
TEST_EQUAL(test1.count(), 4 * 8 + i);
}
}
TORRENT_TEST(test_resize_up)
{
std::array<char, 8> b;
b.fill(0xcc);
bitfield test1(b.data(), 8 * 8);
print_bitfield(test1);
TEST_EQUAL(test1.size(), 8 * 8);
TEST_EQUAL(test1.count(), 4 * 8);
for (int i = 1; i < 5 * 8; ++i)
{
test1.resize(8 * 8 + i);
print_bitfield(test1);
TEST_EQUAL(test1.size(), 8 * 8 + i);
TEST_EQUAL(test1.count(), 4 * 8);
}
}
TORRENT_TEST(test_resize_down)
{
std::array<char, 8> b;
b.fill(0x55);
bitfield test1(b.data(), 8 * 8);
for (int i = 8 * 8; i > -1; --i)
{
test1.resize(i);
print_bitfield(test1);
TEST_EQUAL(test1.size(), i);
TEST_EQUAL(test1.count(), i / 2);
}
}