Add AlignTo method to make a Blob's contents aligned

After loading a Blob from the cache, Dawn may need it to
match a particular alignment. For example, SPIRV must be 4-byte
aligned, or Dawn may need to cast a Blob to a struct layout.

Bug: dawn:549
Change-Id: Iad4857d1ad2d9b41e61e9f177aa7083b1f078be5
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/94532
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Austin Eng <enga@chromium.org>
This commit is contained in:
Austin Eng 2022-06-24 07:44:49 +00:00 committed by Dawn LUCI CQ
parent d69c5d2d1c
commit 245e91d42e
3 changed files with 84 additions and 4 deletions

View File

@ -15,14 +15,24 @@
#include <utility>
#include "dawn/common/Assert.h"
#include "dawn/common/Math.h"
#include "dawn/native/Blob.h"
namespace dawn::native {
Blob CreateBlob(size_t size) {
Blob CreateBlob(size_t size, size_t alignment) {
ASSERT(IsPowerOfTwo(alignment));
ASSERT(alignment != 0);
if (size > 0) {
uint8_t* data = new uint8_t[size];
return Blob::UnsafeCreateWithDeleter(data, size, [=]() { delete[] data; });
// Allocate extra space so that there will be sufficient space for |size| even after
// the |data| pointer is aligned.
// TODO(crbug.com/dawn/824): Use aligned_alloc when possible. It should be available
// with C++17 but on macOS it also requires macOS 10.15 to work.
size_t allocatedSize = size + alignment - 1;
uint8_t* data = new uint8_t[allocatedSize];
uint8_t* ptr = AlignPtr(data, alignment);
ASSERT(ptr + size <= data + allocatedSize);
return Blob::UnsafeCreateWithDeleter(ptr, size, [=]() { delete[] data; });
} else {
return Blob();
}
@ -77,4 +87,14 @@ size_t Blob::Size() const {
return mSize;
}
void Blob::AlignTo(size_t alignment) {
if (IsPtrAligned(mData, alignment)) {
return;
}
Blob blob = CreateBlob(mSize, alignment);
memcpy(blob.Data(), mData, mSize);
*this = std::move(blob);
}
} // namespace dawn::native

View File

@ -43,6 +43,10 @@ class Blob {
uint8_t* Data();
size_t Size() const;
// If the blob data is not aligned to |alignment|, copy it into a new backing store which
// is aligned.
void AlignTo(size_t alignment);
private:
// The constructor should be responsible to take ownership of |data| and releases ownership by
// calling |deleter|. The deleter function is called at ~Blob() and during std::move.
@ -53,7 +57,7 @@ class Blob {
std::function<void()> mDeleter;
};
Blob CreateBlob(size_t size);
Blob CreateBlob(size_t size, size_t alignment = 1);
} // namespace dawn::native

View File

@ -14,6 +14,7 @@
#include <utility>
#include "dawn/common/Math.h"
#include "dawn/native/Blob.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@ -163,6 +164,61 @@ TEST(BlobTests, MoveAssignOver) {
EXPECT_EQ(memcmp(b2.Data(), data, sizeof(data)), 0);
}
// Test that an empty blob can be requested to have a particular alignment.
TEST(BlobTests, EmptyAlignTo) {
for (size_t alignment : {1, 2, 4, 8, 16, 32}) {
Blob b;
EXPECT_TRUE(b.Empty());
EXPECT_EQ(b.Size(), 0u);
EXPECT_EQ(b.Data(), nullptr);
b.AlignTo(alignment);
// After aligning, it is still empty.
EXPECT_TRUE(b.Empty());
EXPECT_EQ(b.Size(), 0u);
EXPECT_EQ(b.Data(), nullptr);
}
}
// Test that AlignTo makes a blob have a particular alignment.
TEST(BlobTests, AlignTo) {
uint8_t data[64];
for (uint8_t i = 0; i < sizeof(data); ++i) {
data[i] = i;
}
// Test multiple alignments.
for (size_t alignment : {1, 2, 4, 8, 16, 32}) {
for (size_t offset = 0; offset < alignment; ++offset) {
// Make a blob pointing to |data| starting at |offset|.
size_t size = sizeof(data) - offset;
testing::StrictMock<testing::MockFunction<void()>> mockDeleter;
Blob b =
Blob::UnsafeCreateWithDeleter(&data[offset], size, [&]() { mockDeleter.Call(); });
bool alreadyAligned = IsPtrAligned(&data[offset], alignment);
// The backing store should be deleted at the end of the scope, or because it was
// replaced.
EXPECT_CALL(mockDeleter, Call());
b.AlignTo(alignment);
if (!alreadyAligned) {
// If the Blob is not aligned, its data will be deleted and replaced by AlignTo.
testing::Mock::VerifyAndClearExpectations(&mockDeleter);
}
// The blob should not have changed in size.
EXPECT_EQ(b.Size(), size) << "alignment = " << alignment << " offset = " << offset;
// The data should be aligned.
EXPECT_TRUE(IsPtrAligned(b.Data(), alignment))
<< "alignment = " << alignment << " offset = " << offset;
// The contents should be the same.
EXPECT_EQ(memcmp(b.Data(), &data[offset], size), 0)
<< "alignment = " << alignment << " offset = " << offset;
// If the data was already aligned, the blob should point to the same memory.
EXPECT_EQ(alreadyAligned, b.Data() == &data[offset])
<< "alignment = " << alignment << " offset = " << offset;
}
}
}
} // namespace
} // namespace dawn::native