[sparkplug][cleanup] Add helper to en-/decode VLQ.

VLQ encoding was implemented in TranslationArray and Sparkplug PC <->
bytecode mapping.
This CL introduces new VLQ helper methods used in both.

Bug: v8:11429
Change-Id: I89d9777eab4ad28f08e5957421b63df07e37f9cc
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2704674
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Commit-Queue: Patrick Thier <pthier@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73054}
This commit is contained in:
pthier 2021-02-25 13:52:38 +00:00 committed by Commit Bot
parent 17feb9b2f5
commit 12eaa0fe61
7 changed files with 233 additions and 61 deletions

View File

@ -4396,6 +4396,7 @@ v8_component("v8_libbase") {
"src/base/utils/random-number-generator.h",
"src/base/vlq-base64.cc",
"src/base/vlq-base64.h",
"src/base/vlq.h",
]
configs = [ ":internal_config_base" ]

85
src/base/vlq.h Normal file
View File

@ -0,0 +1,85 @@
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_BASE_VLQ_H_
#define V8_BASE_VLQ_H_
#include <limits>
#include <vector>
#include "src/base/memory.h"
namespace v8 {
namespace base {
static constexpr uint32_t kContinueShift = 7;
static constexpr uint32_t kContinueMask = 1 << kContinueShift;
static constexpr uint32_t kDataMask = kContinueMask - 1;
// Encodes an unsigned value using variable-length encoding and stores it using
// the passed process_byte function.
inline void VLQEncodeUnsigned(const std::function<void(byte)>& process_byte,
uint32_t value) {
bool has_next;
do {
byte cur_byte = value & kDataMask;
value >>= kContinueShift;
has_next = value != 0;
// The most significant bit is set when we are not done with the value yet.
cur_byte |= static_cast<uint32_t>(has_next) << kContinueShift;
process_byte(cur_byte);
} while (has_next);
}
// Encodes value using variable-length encoding and stores it using the passed
// process_byte function.
inline void VLQEncode(const std::function<void(byte)>& process_byte,
int32_t value) {
// This wouldn't handle kMinInt correctly if it ever encountered it.
DCHECK_NE(value, std::numeric_limits<int32_t>::min());
bool is_negative = value < 0;
// Encode sign in least significant bit.
uint32_t bits = static_cast<uint32_t>((is_negative ? -value : value) << 1) |
static_cast<uint32_t>(is_negative);
VLQEncodeUnsigned(process_byte, bits);
}
// Wrapper of VLQEncode for std::vector backed storage containers.
template <typename A>
inline void VLQEncode(std::vector<byte, A>* data, int32_t value) {
VLQEncode([data](byte value) { data->push_back(value); }, value);
}
// Wrapper of VLQEncodeUnsigned for std::vector backed storage containers.
template <typename A>
inline void VLQEncodeUnsigned(std::vector<byte, A>* data, uint32_t value) {
VLQEncodeUnsigned([data](byte value) { data->push_back(value); }, value);
}
// Decodes a variable-length encoded unsigned value stored in contiguous memory
// starting at data_start + index, updating index to where the next encoded
// value starts.
inline uint32_t VLQDecodeUnsigned(byte* data_start, int* index) {
uint32_t bits = 0;
for (int shift = 0; true; shift += kContinueShift) {
byte cur_byte = data_start[(*index)++];
bits += (cur_byte & kDataMask) << shift;
if ((cur_byte & kContinueMask) == 0) break;
}
return bits;
}
// Decodes a variable-length encoded value stored in contiguous memory starting
// at data_start + index, updating index to where the next encoded value starts.
inline int32_t VLQDecode(byte* data_start, int* index) {
uint32_t bits = VLQDecodeUnsigned(data_start, index);
bool is_negative = (bits & 1) == 1;
int32_t result = bits >> 1;
return is_negative ? -result : result;
}
} // namespace base
} // namespace v8
#endif // V8_BASE_VLQ_H_

View File

@ -13,6 +13,7 @@
#include "src/base/logging.h"
#include "src/base/threaded-list.h"
#include "src/base/vlq.h"
#include "src/baseline/baseline-assembler.h"
#include "src/handles/handles.h"
#include "src/interpreter/bytecode-array-iterator.h"
@ -33,8 +34,14 @@ namespace baseline {
class BytecodeOffsetTableBuilder {
public:
void AddPosition(size_t pc_offset, size_t bytecode_offset) {
WriteUint(pc_offset - previous_pc_);
WriteUint(bytecode_offset - previous_bytecode_);
size_t pc_diff = pc_offset - previous_pc_;
size_t bytecode_diff = bytecode_offset - previous_bytecode_;
DCHECK_GE(pc_diff, 0);
DCHECK_LE(pc_diff, std::numeric_limits<uint32_t>::max());
DCHECK_GE(bytecode_diff, 0);
DCHECK_LE(bytecode_diff, std::numeric_limits<uint32_t>::max());
base::VLQEncodeUnsigned(&bytes_, static_cast<uint32_t>(pc_diff));
base::VLQEncodeUnsigned(&bytes_, static_cast<uint32_t>(bytecode_diff));
previous_pc_ = pc_offset;
previous_bytecode_ = bytecode_offset;
}
@ -43,17 +50,6 @@ class BytecodeOffsetTableBuilder {
Handle<ByteArray> ToBytecodeOffsetTable(LocalIsolate* isolate);
private:
void WriteUint(size_t value) {
bool has_next;
do {
uint8_t byte = value & ((1 << 7) - 1);
value >>= 7;
has_next = value != 0;
byte |= (has_next << 7);
bytes_.push_back(byte);
} while (has_next);
}
size_t previous_pc_ = 0;
size_t previous_bytecode_ = 0;
std::vector<byte> bytes_;

View File

@ -4,6 +4,7 @@
#include "src/deoptimizer/translation-array.h"
#include "src/base/vlq.h"
#include "src/deoptimizer/translated-state.h"
#include "src/objects/fixed-array-inl.h"
#include "third_party/zlib/google/compression_utils_portable.h"
@ -56,19 +57,9 @@ int32_t TranslationArrayIterator::Next() {
if (V8_UNLIKELY(FLAG_turbo_compress_translation_arrays)) {
return uncompressed_contents_[index_++];
} else {
// Run through the bytes until we reach one with a least significant
// bit of zero (marks the end).
uint32_t bits = 0;
for (int i = 0; true; i += 7) {
DCHECK(HasNext());
uint8_t next = buffer_.get(index_++);
bits |= (next >> 1) << i;
if ((next & 1) == 0) break;
}
// The bits encode the sign in the least significant bit.
bool is_negative = (bits & 1) == 1;
int32_t result = bits >> 1;
return is_negative ? -result : result;
int32_t value = base::VLQDecode(buffer_.GetDataStartAddress(), &index_);
DCHECK_LE(index_, buffer_.length());
return value;
}
}
@ -84,19 +75,7 @@ void TranslationArrayBuilder::Add(int32_t value) {
if (V8_UNLIKELY(FLAG_turbo_compress_translation_arrays)) {
contents_for_compression_.push_back(value);
} else {
// This wouldn't handle kMinInt correctly if it ever encountered it.
DCHECK_NE(value, kMinInt);
// Encode the sign bit in the least significant bit.
bool is_negative = (value < 0);
uint32_t bits = (static_cast<uint32_t>(is_negative ? -value : value) << 1) |
static_cast<uint32_t>(is_negative);
// Encode the individual bytes using the least significant bit of
// each byte to indicate whether or not more bytes follow.
do {
uint32_t next = bits >> 7;
contents_.push_back(((bits << 1) & 0xFF) | (next != 0));
bits = next;
} while (bits != 0);
base::VLQEncode(&contents_, value);
}
}

View File

@ -6,6 +6,7 @@
#define V8_OBJECTS_CODE_INL_H_
#include "src/base/memory.h"
#include "src/base/vlq.h"
#include "src/codegen/code-desc.h"
#include "src/common/assert-scope.h"
#include "src/execution/isolate.h"
@ -329,24 +330,6 @@ CodeKind Code::kind() const {
return KindField::decode(ReadField<uint32_t>(kFlagsOffset));
}
namespace detail {
// TODO(v8:11429): Extract out of header, to generic helper, and merge with
// TranslationArray de/encoding.
inline int ReadUint(ByteArray array, int* index) {
int byte = 0;
int value = 0;
int shift = 0;
do {
byte = array.get((*index)++);
value += (byte & ((1 << 7) - 1)) << shift;
shift += 7;
} while (byte & (1 << 7));
return value;
}
} // namespace detail
int Code::GetBytecodeOffsetForBaselinePC(Address baseline_pc) {
DisallowGarbageCollection no_gc;
CHECK(!is_baseline_prologue_builtin());
@ -357,10 +340,12 @@ int Code::GetBytecodeOffsetForBaselinePC(Address baseline_pc) {
Address pc = baseline_pc - InstructionStart();
int index = 0;
int offset = 0;
byte* data_start = data.GetDataStartAddress();
while (pc > lookup_pc) {
lookup_pc += detail::ReadUint(data, &index);
offset += detail::ReadUint(data, &index);
lookup_pc += base::VLQDecodeUnsigned(data_start, &index);
offset += base::VLQDecodeUnsigned(data_start, &index);
}
DCHECK_LE(index, data.Size());
CHECK_EQ(pc, lookup_pc);
return offset;
}
@ -375,13 +360,15 @@ uintptr_t Code::GetBaselinePCForBytecodeOffset(int bytecode_offset,
int offset = 0;
// TODO(v8:11429,cbruni): clean up
// Return the offset for the last bytecode that matches
byte* data_start = data.GetDataStartAddress();
while (offset < bytecode_offset && index < data.length()) {
int delta_pc = detail::ReadUint(data, &index);
int delta_offset = detail::ReadUint(data, &index);
int delta_pc = base::VLQDecodeUnsigned(data_start, &index);
int delta_offset = base::VLQDecodeUnsigned(data_start, &index);
if (!precise && (bytecode_offset < offset + delta_offset)) break;
pc += delta_pc;
offset += delta_offset;
}
DCHECK_LE(index, data.length());
if (precise) {
CHECK_EQ(offset, bytecode_offset);
} else {

View File

@ -226,6 +226,7 @@ v8_source_set("unittests_sources") {
"base/threaded-list-unittest.cc",
"base/utils/random-number-generator-unittest.cc",
"base/vlq-base64-unittest.cc",
"base/vlq-unittest.cc",
"codegen/code-stub-assembler-unittest.cc",
"codegen/code-stub-assembler-unittest.h",
"codegen/register-configuration-unittest.cc",

View File

@ -0,0 +1,123 @@
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/base/vlq.h"
#include <cmath>
#include <limits>
#include "src/base/memory.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest-support.h"
namespace v8 {
namespace base {
int ExpectedBytesUsed(int64_t value, bool is_signed) {
uint64_t bits = value;
if (is_signed) {
bits = (value < 0 ? -value : value) << 1;
}
int num_bits = 0;
while (bits != 0) {
num_bits++;
bits >>= 1;
}
return std::max(1, static_cast<int>(ceil(static_cast<float>(num_bits) / 7)));
}
void TestVLQUnsignedEquals(uint32_t value) {
std::vector<byte> buffer;
VLQEncodeUnsigned(&buffer, value);
byte* data_start = buffer.data();
int index = 0;
int expected_bytes_used = ExpectedBytesUsed(value, false);
EXPECT_EQ(buffer.size(), static_cast<size_t>(expected_bytes_used));
EXPECT_EQ(value, VLQDecodeUnsigned(data_start, &index));
EXPECT_EQ(index, expected_bytes_used);
}
void TestVLQEquals(int32_t value) {
std::vector<byte> buffer;
VLQEncode(&buffer, value);
byte* data_start = buffer.data();
int index = 0;
int expected_bytes_used = ExpectedBytesUsed(value, true);
EXPECT_EQ(buffer.size(), static_cast<size_t>(expected_bytes_used));
EXPECT_EQ(value, VLQDecode(data_start, &index));
EXPECT_EQ(index, expected_bytes_used);
}
TEST(VLQ, Unsigned) {
TestVLQUnsignedEquals(0);
TestVLQUnsignedEquals(1);
TestVLQUnsignedEquals(63);
TestVLQUnsignedEquals(64);
TestVLQUnsignedEquals(127);
TestVLQUnsignedEquals(255);
TestVLQUnsignedEquals(256);
}
TEST(VLQ, Positive) {
TestVLQEquals(0);
TestVLQEquals(1);
TestVLQEquals(63);
TestVLQEquals(64);
TestVLQEquals(127);
TestVLQEquals(255);
TestVLQEquals(256);
}
TEST(VLQ, Negative) {
TestVLQEquals(-1);
TestVLQEquals(-63);
TestVLQEquals(-64);
TestVLQEquals(-127);
TestVLQEquals(-255);
TestVLQEquals(-256);
}
TEST(VLQ, LimitsUnsigned) {
TestVLQEquals(std::numeric_limits<uint8_t>::max());
TestVLQEquals(std::numeric_limits<uint8_t>::max() - 1);
TestVLQEquals(std::numeric_limits<uint8_t>::max() + 1);
TestVLQEquals(std::numeric_limits<uint16_t>::max());
TestVLQEquals(std::numeric_limits<uint16_t>::max() - 1);
TestVLQEquals(std::numeric_limits<uint16_t>::max() + 1);
TestVLQEquals(std::numeric_limits<uint32_t>::max());
TestVLQEquals(std::numeric_limits<uint32_t>::max() - 1);
}
TEST(VLQ, LimitsSigned) {
TestVLQEquals(std::numeric_limits<int8_t>::max());
TestVLQEquals(std::numeric_limits<int8_t>::max() - 1);
TestVLQEquals(std::numeric_limits<int8_t>::max() + 1);
TestVLQEquals(std::numeric_limits<int16_t>::max());
TestVLQEquals(std::numeric_limits<int16_t>::max() - 1);
TestVLQEquals(std::numeric_limits<int16_t>::max() + 1);
TestVLQEquals(std::numeric_limits<int32_t>::max());
TestVLQEquals(std::numeric_limits<int32_t>::max() - 1);
TestVLQEquals(std::numeric_limits<int8_t>::min());
TestVLQEquals(std::numeric_limits<int8_t>::min() - 1);
TestVLQEquals(std::numeric_limits<int8_t>::min() + 1);
TestVLQEquals(std::numeric_limits<int16_t>::min());
TestVLQEquals(std::numeric_limits<int16_t>::min() - 1);
TestVLQEquals(std::numeric_limits<int16_t>::min() + 1);
// int32_t::min() is not supported.
TestVLQEquals(std::numeric_limits<int32_t>::min() + 1);
}
TEST(VLQ, Random) {
static constexpr int RANDOM_RUNS = 50;
base::RandomNumberGenerator rng(::testing::FLAGS_gtest_random_seed);
for (int i = 0; i < RANDOM_RUNS; ++i) {
TestVLQUnsignedEquals(rng.NextInt(std::numeric_limits<int32_t>::max()));
}
for (int i = 0; i < RANDOM_RUNS; ++i) {
TestVLQEquals(rng.NextInt());
}
}
} // namespace base
} // namespace v8