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 { ...@@ -19,13 +19,26 @@ namespace internal {
static const uint32_t kLatestVersion = 9; static const uint32_t kLatestVersion = 9;
enum class SerializationTag : uint8_t { enum class SerializationTag : uint8_t {
// version:uint32_t (if at beginning of data, sets version > 0)
kVersion = 0xFF, kVersion = 0xFF,
// ignore
kPadding = '\0', kPadding = '\0',
// refTableSize:uint32_t (previously used for sanity checks; safe to ignore)
kVerifyObjectCount = '?', kVerifyObjectCount = '?',
// Oddballs (no data).
kUndefined = '_', kUndefined = '_',
kNull = '0', kNull = '0',
kTrue = 'T', kTrue = 'T',
kFalse = 'F', 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() {} ValueSerializer::ValueSerializer() {}
...@@ -60,14 +73,40 @@ void ValueSerializer::WriteVarint(T value) { ...@@ -60,14 +73,40 @@ void ValueSerializer::WriteVarint(T value) {
buffer_.insert(buffer_.end(), stack_buffer, next_byte); 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) { Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
if (object->IsSmi()) UNIMPLEMENTED(); if (object->IsSmi()) {
WriteSmi(Smi::cast(*object));
return Just(true);
}
DCHECK(object->IsHeapObject()); DCHECK(object->IsHeapObject());
switch (HeapObject::cast(*object)->map()->instance_type()) { switch (HeapObject::cast(*object)->map()->instance_type()) {
case ODDBALL_TYPE: case ODDBALL_TYPE:
WriteOddball(Oddball::cast(*object)); WriteOddball(Oddball::cast(*object));
return Just(true); return Just(true);
case HEAP_NUMBER_TYPE:
case MUTABLE_HEAP_NUMBER_TYPE:
WriteHeapNumber(HeapNumber::cast(*object));
return Just(true);
default: default:
UNIMPLEMENTED(); UNIMPLEMENTED();
return Nothing<bool>(); return Nothing<bool>();
...@@ -96,6 +135,17 @@ void ValueSerializer::WriteOddball(Oddball* oddball) { ...@@ -96,6 +135,17 @@ void ValueSerializer::WriteOddball(Oddball* oddball) {
WriteTag(tag); 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, ValueDeserializer::ValueDeserializer(Isolate* isolate,
Vector<const uint8_t> data) Vector<const uint8_t> data)
: isolate_(isolate), : isolate_(isolate),
...@@ -149,6 +199,30 @@ Maybe<T> ValueDeserializer::ReadVarint() { ...@@ -149,6 +199,30 @@ Maybe<T> ValueDeserializer::ReadVarint() {
return Just(value); 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() { MaybeHandle<Object> ValueDeserializer::ReadObject() {
SerializationTag tag; SerializationTag tag;
if (!ReadTag().To(&tag)) return MaybeHandle<Object>(); if (!ReadTag().To(&tag)) return MaybeHandle<Object>();
...@@ -165,6 +239,21 @@ MaybeHandle<Object> ValueDeserializer::ReadObject() { ...@@ -165,6 +239,21 @@ MaybeHandle<Object> ValueDeserializer::ReadObject() {
return isolate_->factory()->true_value(); return isolate_->factory()->true_value();
case SerializationTag::kFalse: case SerializationTag::kFalse:
return isolate_->factory()->false_value(); 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: default:
return MaybeHandle<Object>(); return MaybeHandle<Object>();
} }
......
...@@ -16,9 +16,11 @@ ...@@ -16,9 +16,11 @@
namespace v8 { namespace v8 {
namespace internal { namespace internal {
class HeapNumber;
class Isolate; class Isolate;
class Object; class Object;
class Oddball; class Oddball;
class Smi;
enum class SerializationTag : uint8_t; enum class SerializationTag : uint8_t;
...@@ -54,9 +56,14 @@ class ValueSerializer { ...@@ -54,9 +56,14 @@ class ValueSerializer {
void WriteTag(SerializationTag tag); void WriteTag(SerializationTag tag);
template <typename T> template <typename T>
void WriteVarint(T value); void WriteVarint(T value);
template <typename T>
void WriteZigZag(T value);
void WriteDouble(double value);
// Writing V8 objects of various kinds. // Writing V8 objects of various kinds.
void WriteOddball(Oddball* oddball); void WriteOddball(Oddball* oddball);
void WriteSmi(Smi* smi);
void WriteHeapNumber(HeapNumber* number);
std::vector<uint8_t> buffer_; std::vector<uint8_t> buffer_;
...@@ -86,6 +93,9 @@ class ValueDeserializer { ...@@ -86,6 +93,9 @@ class ValueDeserializer {
Maybe<SerializationTag> ReadTag() WARN_UNUSED_RESULT; Maybe<SerializationTag> ReadTag() WARN_UNUSED_RESULT;
template <typename T> template <typename T>
Maybe<T> ReadVarint() WARN_UNUSED_RESULT; Maybe<T> ReadVarint() WARN_UNUSED_RESULT;
template <typename T>
Maybe<T> ReadZigZag() WARN_UNUSED_RESULT;
Maybe<double> ReadDouble() WARN_UNUSED_RESULT;
Isolate* const isolate_; Isolate* const isolate_;
const uint8_t* position_; const uint8_t* position_;
......
...@@ -3,8 +3,10 @@ ...@@ -3,8 +3,10 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "src/value-serializer.h" #include "src/value-serializer.h"
#include "include/v8.h" #include "include/v8.h"
#include "src/api.h" #include "src/api.h"
#include "src/base/build_config.h"
#include "test/unittests/test-utils.h" #include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -163,5 +165,88 @@ TEST_F(ValueSerializerTest, DecodeOddball) { ...@@ -163,5 +165,88 @@ TEST_F(ValueSerializerTest, DecodeOddball) {
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); }); [](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
} // namespace v8 } // 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