Commit 39bbb6f2 authored by jbroman's avatar jbroman Committed by Commit bot

Blink-compatible serialization of numbers.

This includes unsigned integers (encoded as base-128 varints), signed integers
(ZigZag-encoded, then varint-encoded) and doubles (written in host byte order).

BUG=chromium:148757

Review-Url: https://codereview.chromium.org/2232323004
Cr-Commit-Position: refs/heads/master@{#38630}
parent d1dcebd1
......@@ -19,13 +19,26 @@ namespace internal {
static const uint32_t kLatestVersion = 9;
enum class SerializationTag : uint8_t {
// version:uint32_t (if at beginning of data, sets version > 0)
kVersion = 0xFF,
// ignore
kPadding = '\0',
// refTableSize:uint32_t (previously used for sanity checks; safe to ignore)
kVerifyObjectCount = '?',
// Oddballs (no data).
kUndefined = '_',
kNull = '0',
kTrue = 'T',
kFalse = 'F',
// Number represented as 32-bit integer, ZigZag-encoded
// (like sint32 in protobuf)
kInt32 = 'I',
// Number represented as 32-bit unsigned integer, varint-encoded
// (like uint32 in protobuf)
kUint32 = 'U',
// Number represented as a 64-bit double.
// Host byte order is used (N.B. this makes the format non-portable).
kDouble = 'N',
};
ValueSerializer::ValueSerializer() {}
......@@ -60,14 +73,40 @@ void ValueSerializer::WriteVarint(T value) {
buffer_.insert(buffer_.end(), stack_buffer, next_byte);
}
template <typename T>
void ValueSerializer::WriteZigZag(T value) {
// Writes a signed integer as a varint using ZigZag encoding (i.e. 0 is
// encoded as 0, -1 as 1, 1 as 2, -2 as 3, and so on).
// See also https://developers.google.com/protocol-buffers/docs/encoding
// Note that this implementation relies on the right shift being arithmetic.
static_assert(std::is_integral<T>::value && std::is_signed<T>::value,
"Only signed integer types can be written as zigzag.");
using UnsignedT = typename std::make_unsigned<T>::type;
WriteVarint((static_cast<UnsignedT>(value) << 1) ^
(value >> (8 * sizeof(T) - 1)));
}
void ValueSerializer::WriteDouble(double value) {
// Warning: this uses host endianness.
buffer_.insert(buffer_.end(), reinterpret_cast<const uint8_t*>(&value),
reinterpret_cast<const uint8_t*>(&value + 1));
}
Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
if (object->IsSmi()) UNIMPLEMENTED();
if (object->IsSmi()) {
WriteSmi(Smi::cast(*object));
return Just(true);
}
DCHECK(object->IsHeapObject());
switch (HeapObject::cast(*object)->map()->instance_type()) {
case ODDBALL_TYPE:
WriteOddball(Oddball::cast(*object));
return Just(true);
case HEAP_NUMBER_TYPE:
case MUTABLE_HEAP_NUMBER_TYPE:
WriteHeapNumber(HeapNumber::cast(*object));
return Just(true);
default:
UNIMPLEMENTED();
return Nothing<bool>();
......@@ -96,6 +135,17 @@ void ValueSerializer::WriteOddball(Oddball* oddball) {
WriteTag(tag);
}
void ValueSerializer::WriteSmi(Smi* smi) {
static_assert(kSmiValueSize <= 32, "Expected SMI <= 32 bits.");
WriteTag(SerializationTag::kInt32);
WriteZigZag<int32_t>(smi->value());
}
void ValueSerializer::WriteHeapNumber(HeapNumber* number) {
WriteTag(SerializationTag::kDouble);
WriteDouble(number->value());
}
ValueDeserializer::ValueDeserializer(Isolate* isolate,
Vector<const uint8_t> data)
: isolate_(isolate),
......@@ -149,6 +199,30 @@ Maybe<T> ValueDeserializer::ReadVarint() {
return Just(value);
}
template <typename T>
Maybe<T> ValueDeserializer::ReadZigZag() {
// Writes a signed integer as a varint using ZigZag encoding (i.e. 0 is
// encoded as 0, -1 as 1, 1 as 2, -2 as 3, and so on).
// See also https://developers.google.com/protocol-buffers/docs/encoding
static_assert(std::is_integral<T>::value && std::is_signed<T>::value,
"Only signed integer types can be read as zigzag.");
using UnsignedT = typename std::make_unsigned<T>::type;
UnsignedT unsigned_value;
if (!ReadVarint<UnsignedT>().To(&unsigned_value)) return Nothing<T>();
return Just(static_cast<T>((unsigned_value >> 1) ^
-static_cast<T>(unsigned_value & 1)));
}
Maybe<double> ValueDeserializer::ReadDouble() {
// Warning: this uses host endianness.
if (position_ > end_ - sizeof(double)) return Nothing<double>();
double value;
memcpy(&value, position_, sizeof(double));
position_ += sizeof(double);
if (std::isnan(value)) value = std::numeric_limits<double>::quiet_NaN();
return Just(value);
}
MaybeHandle<Object> ValueDeserializer::ReadObject() {
SerializationTag tag;
if (!ReadTag().To(&tag)) return MaybeHandle<Object>();
......@@ -165,6 +239,21 @@ MaybeHandle<Object> ValueDeserializer::ReadObject() {
return isolate_->factory()->true_value();
case SerializationTag::kFalse:
return isolate_->factory()->false_value();
case SerializationTag::kInt32: {
Maybe<int32_t> number = ReadZigZag<int32_t>();
if (number.IsNothing()) return MaybeHandle<Object>();
return isolate_->factory()->NewNumberFromInt(number.FromJust());
}
case SerializationTag::kUint32: {
Maybe<uint32_t> number = ReadVarint<uint32_t>();
if (number.IsNothing()) return MaybeHandle<Object>();
return isolate_->factory()->NewNumberFromUint(number.FromJust());
}
case SerializationTag::kDouble: {
Maybe<double> number = ReadDouble();
if (number.IsNothing()) return MaybeHandle<Object>();
return isolate_->factory()->NewNumber(number.FromJust());
}
default:
return MaybeHandle<Object>();
}
......
......@@ -16,9 +16,11 @@
namespace v8 {
namespace internal {
class HeapNumber;
class Isolate;
class Object;
class Oddball;
class Smi;
enum class SerializationTag : uint8_t;
......@@ -54,9 +56,14 @@ class ValueSerializer {
void WriteTag(SerializationTag tag);
template <typename T>
void WriteVarint(T value);
template <typename T>
void WriteZigZag(T value);
void WriteDouble(double value);
// Writing V8 objects of various kinds.
void WriteOddball(Oddball* oddball);
void WriteSmi(Smi* smi);
void WriteHeapNumber(HeapNumber* number);
std::vector<uint8_t> buffer_;
......@@ -86,6 +93,9 @@ class ValueDeserializer {
Maybe<SerializationTag> ReadTag() WARN_UNUSED_RESULT;
template <typename T>
Maybe<T> ReadVarint() WARN_UNUSED_RESULT;
template <typename T>
Maybe<T> ReadZigZag() WARN_UNUSED_RESULT;
Maybe<double> ReadDouble() WARN_UNUSED_RESULT;
Isolate* const isolate_;
const uint8_t* position_;
......
......@@ -3,8 +3,10 @@
// found in the LICENSE file.
#include "src/value-serializer.h"
#include "include/v8.h"
#include "src/api.h"
#include "src/base/build_config.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -163,5 +165,88 @@ TEST_F(ValueSerializerTest, DecodeOddball) {
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
}
TEST_F(ValueSerializerTest, RoundTripNumber) {
RoundTripTest([this]() { return Integer::New(isolate(), 42); },
[](Local<Value> value) {
ASSERT_TRUE(value->IsInt32());
EXPECT_EQ(42, Int32::Cast(*value)->Value());
});
RoundTripTest([this]() { return Integer::New(isolate(), -31337); },
[](Local<Value> value) {
ASSERT_TRUE(value->IsInt32());
EXPECT_EQ(-31337, Int32::Cast(*value)->Value());
});
RoundTripTest(
[this]() {
return Integer::New(isolate(), std::numeric_limits<int32_t>::min());
},
[](Local<Value> value) {
ASSERT_TRUE(value->IsInt32());
EXPECT_EQ(std::numeric_limits<int32_t>::min(),
Int32::Cast(*value)->Value());
});
RoundTripTest([this]() { return Number::New(isolate(), -0.25); },
[](Local<Value> value) {
ASSERT_TRUE(value->IsNumber());
EXPECT_EQ(-0.25, Number::Cast(*value)->Value());
});
RoundTripTest(
[this]() {
return Number::New(isolate(), std::numeric_limits<double>::quiet_NaN());
},
[](Local<Value> value) {
ASSERT_TRUE(value->IsNumber());
EXPECT_TRUE(std::isnan(Number::Cast(*value)->Value()));
});
}
TEST_F(ValueSerializerTest, DecodeNumber) {
// 42 zig-zag encoded (signed)
DecodeTest({0xff, 0x09, 0x49, 0x54},
[](Local<Value> value) {
ASSERT_TRUE(value->IsInt32());
EXPECT_EQ(42, Int32::Cast(*value)->Value());
});
// 42 varint encoded (unsigned)
DecodeTest({0xff, 0x09, 0x55, 0x2a},
[](Local<Value> value) {
ASSERT_TRUE(value->IsInt32());
EXPECT_EQ(42, Int32::Cast(*value)->Value());
});
// 160 zig-zag encoded (signed)
DecodeTest({0xff, 0x09, 0x49, 0xc0, 0x02},
[](Local<Value> value) {
ASSERT_TRUE(value->IsInt32());
ASSERT_EQ(160, Int32::Cast(*value)->Value());
});
// 160 varint encoded (unsigned)
DecodeTest({0xff, 0x09, 0x55, 0xa0, 0x01},
[](Local<Value> value) {
ASSERT_TRUE(value->IsInt32());
ASSERT_EQ(160, Int32::Cast(*value)->Value());
});
#if defined(V8_TARGET_LITTLE_ENDIAN)
// IEEE 754 doubles, little-endian byte order
DecodeTest({0xff, 0x09, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xbf},
[](Local<Value> value) {
ASSERT_TRUE(value->IsNumber());
EXPECT_EQ(-0.25, Number::Cast(*value)->Value());
});
// quiet NaN
DecodeTest({0xff, 0x09, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x7f},
[](Local<Value> value) {
ASSERT_TRUE(value->IsNumber());
EXPECT_TRUE(std::isnan(Number::Cast(*value)->Value()));
});
// signaling NaN
DecodeTest({0xff, 0x09, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x7f},
[](Local<Value> value) {
ASSERT_TRUE(value->IsNumber());
EXPECT_TRUE(std::isnan(Number::Cast(*value)->Value()));
});
#endif
// TODO(jbroman): Equivalent test for big-endian machines.
}
} // namespace
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment