[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:
parent
17feb9b2f5
commit
12eaa0fe61
1
BUILD.gn
1
BUILD.gn
@ -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
85
src/base/vlq.h
Normal 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_
|
@ -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_;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
123
test/unittests/base/vlq-unittest.cc
Normal file
123
test/unittests/base/vlq-unittest.cc
Normal 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
|
Loading…
Reference in New Issue
Block a user