blob: 117089d566b377f8b11350469e2d1da7c5ac853c [file] [log] [blame]
// Copyright 2019 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 "test/cctest/test-api.h"
#include "src/api/api-inl.h"
using ::v8::Array;
using ::v8::Context;
using ::v8::Local;
using ::v8::Value;
namespace {
void CheckElementValue(i::Isolate* isolate, int expected,
i::Handle<i::Object> obj, int offset) {
i::Object element =
*i::Object::GetElement(isolate, obj, offset).ToHandleChecked();
CHECK_EQ(expected, i::Smi::ToInt(element));
}
template <class ElementType>
void ObjectWithExternalArrayTestHelper(Local<Context> context,
v8::Local<v8::TypedArray> obj,
int element_count,
i::ExternalArrayType array_type,
int64_t low, int64_t high) {
i::Handle<i::JSTypedArray> jsobj = v8::Utils::OpenHandle(*obj);
v8::Isolate* v8_isolate = context->GetIsolate();
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
obj->Set(context, v8_str("field"), v8::Int32::New(v8_isolate, 1503))
.FromJust();
CHECK(context->Global()->Set(context, v8_str("ext_array"), obj).FromJust());
v8::Local<v8::Value> result = CompileRun("ext_array.field");
CHECK_EQ(1503, result->Int32Value(context).FromJust());
result = CompileRun("ext_array[1]");
CHECK_EQ(1, result->Int32Value(context).FromJust());
// Check assigned smis
result = CompileRun(
"for (var i = 0; i < 8; i++) {"
" ext_array[i] = i;"
"}"
"var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += ext_array[i];"
"}"
"sum;");
CHECK_EQ(28, result->Int32Value(context).FromJust());
// Check pass through of assigned smis
result = CompileRun(
"var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += ext_array[i] = ext_array[i] = -i;"
"}"
"sum;");
CHECK_EQ(-28, result->Int32Value(context).FromJust());
// Check assigned smis in reverse order
result = CompileRun(
"for (var i = 8; --i >= 0; ) {"
" ext_array[i] = i;"
"}"
"var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += ext_array[i];"
"}"
"sum;");
CHECK_EQ(28, result->Int32Value(context).FromJust());
// Check pass through of assigned HeapNumbers
result = CompileRun(
"var sum = 0;"
"for (var i = 0; i < 16; i+=2) {"
" sum += ext_array[i] = ext_array[i] = (-i * 0.5);"
"}"
"sum;");
CHECK_EQ(-28, result->Int32Value(context).FromJust());
// Check assigned HeapNumbers
result = CompileRun(
"for (var i = 0; i < 16; i+=2) {"
" ext_array[i] = (i * 0.5);"
"}"
"var sum = 0;"
"for (var i = 0; i < 16; i+=2) {"
" sum += ext_array[i];"
"}"
"sum;");
CHECK_EQ(28, result->Int32Value(context).FromJust());
// Check assigned HeapNumbers in reverse order
result = CompileRun(
"for (var i = 14; i >= 0; i-=2) {"
" ext_array[i] = (i * 0.5);"
"}"
"var sum = 0;"
"for (var i = 0; i < 16; i+=2) {"
" sum += ext_array[i];"
"}"
"sum;");
CHECK_EQ(28, result->Int32Value(context).FromJust());
i::ScopedVector<char> test_buf(1024);
// Check legal boundary conditions.
// The repeated loads and stores ensure the ICs are exercised.
const char* boundary_program =
"var res = 0;"
"for (var i = 0; i < 16; i++) {"
" ext_array[i] = %lld;"
" if (i > 8) {"
" res = ext_array[i];"
" }"
"}"
"res;";
i::SNPrintF(test_buf, boundary_program, low);
result = CompileRun(test_buf.begin());
CHECK_EQ(low, result->IntegerValue(context).FromJust());
i::SNPrintF(test_buf, boundary_program, high);
result = CompileRun(test_buf.begin());
CHECK_EQ(high, result->IntegerValue(context).FromJust());
// Check misprediction of type in IC.
result = CompileRun(
"var tmp_array = ext_array;"
"var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" tmp_array[i] = i;"
" sum += tmp_array[i];"
" if (i == 4) {"
" tmp_array = {};"
" }"
"}"
"sum;");
// Force GC to trigger verification.
CcTest::CollectAllGarbage();
CHECK_EQ(28, result->Int32Value(context).FromJust());
// Make sure out-of-range loads do not throw.
i::SNPrintF(test_buf,
"var caught_exception = false;"
"try {"
" ext_array[%d];"
"} catch (e) {"
" caught_exception = true;"
"}"
"caught_exception;",
element_count);
result = CompileRun(test_buf.begin());
CHECK(!result->BooleanValue(v8_isolate));
// Make sure out-of-range stores do not throw.
i::SNPrintF(test_buf,
"var caught_exception = false;"
"try {"
" ext_array[%d] = 1;"
"} catch (e) {"
" caught_exception = true;"
"}"
"caught_exception;",
element_count);
result = CompileRun(test_buf.begin());
CHECK(!result->BooleanValue(v8_isolate));
// Check other boundary conditions, values and operations.
result = CompileRun(
"for (var i = 0; i < 8; i++) {"
" ext_array[7] = undefined;"
"}"
"ext_array[7];");
CHECK_EQ(0, result->Int32Value(context).FromJust());
if (array_type == i::kExternalFloat64Array ||
array_type == i::kExternalFloat32Array) {
CHECK(std::isnan(
i::Object::GetElement(isolate, jsobj, 7).ToHandleChecked()->Number()));
} else {
CheckElementValue(isolate, 0, jsobj, 7);
}
result = CompileRun(
"for (var i = 0; i < 8; i++) {"
" ext_array[6] = '2.3';"
"}"
"ext_array[6];");
CHECK_EQ(2, result->Int32Value(context).FromJust());
CHECK_EQ(2, static_cast<int>(i::Object::GetElement(isolate, jsobj, 6)
.ToHandleChecked()
->Number()));
if (array_type != i::kExternalFloat32Array &&
array_type != i::kExternalFloat64Array) {
// Though the specification doesn't state it, be explicit about
// converting NaNs and +/-Infinity to zero.
result = CompileRun(
"for (var i = 0; i < 8; i++) {"
" ext_array[i] = 5;"
"}"
"for (var i = 0; i < 8; i++) {"
" ext_array[i] = NaN;"
"}"
"ext_array[5];");
CHECK_EQ(0, result->Int32Value(context).FromJust());
CheckElementValue(isolate, 0, jsobj, 5);
result = CompileRun(
"for (var i = 0; i < 8; i++) {"
" ext_array[i] = 5;"
"}"
"for (var i = 0; i < 8; i++) {"
" ext_array[i] = Infinity;"
"}"
"ext_array[5];");
int expected_value =
(array_type == i::kExternalUint8ClampedArray) ? 255 : 0;
CHECK_EQ(expected_value, result->Int32Value(context).FromJust());
CheckElementValue(isolate, expected_value, jsobj, 5);
result = CompileRun(
"for (var i = 0; i < 8; i++) {"
" ext_array[i] = 5;"
"}"
"for (var i = 0; i < 8; i++) {"
" ext_array[i] = -Infinity;"
"}"
"ext_array[5];");
CHECK_EQ(0, result->Int32Value(context).FromJust());
CheckElementValue(isolate, 0, jsobj, 5);
// Check truncation behavior of integral arrays.
const char* unsigned_data =
"var source_data = [0.6, 10.6];"
"var expected_results = [0, 10];";
const char* signed_data =
"var source_data = [0.6, 10.6, -0.6, -10.6];"
"var expected_results = [0, 10, 0, -10];";
const char* pixel_data =
"var source_data = [0.6, 10.6];"
"var expected_results = [1, 11];";
bool is_unsigned = (array_type == i::kExternalUint8Array ||
array_type == i::kExternalUint16Array ||
array_type == i::kExternalUint32Array);
bool is_pixel_data = array_type == i::kExternalUint8ClampedArray;
i::SNPrintF(test_buf,
"%s"
"var all_passed = true;"
"for (var i = 0; i < source_data.length; i++) {"
" for (var j = 0; j < 8; j++) {"
" ext_array[j] = source_data[i];"
" }"
" all_passed = all_passed &&"
" (ext_array[5] == expected_results[i]);"
"}"
"all_passed;",
(is_unsigned ? unsigned_data
: (is_pixel_data ? pixel_data : signed_data)));
result = CompileRun(test_buf.begin());
CHECK(result->BooleanValue(v8_isolate));
}
{
ElementType* data_ptr = static_cast<ElementType*>(jsobj->DataPtr());
for (int i = 0; i < element_count; i++) {
data_ptr[i] = static_cast<ElementType>(i);
}
}
bool old_natives_flag_sentry = i::FLAG_allow_natives_syntax;
i::FLAG_allow_natives_syntax = true;
// Test complex assignments
result = CompileRun(
"function ee_op_test_complex_func(sum) {"
" for (var i = 0; i < 40; ++i) {"
" sum += (ext_array[i] += 1);"
" sum += (ext_array[i] -= 1);"
" } "
" return sum;"
"};"
"%PrepareFunctionForOptimization(ee_op_test_complex_func);"
"sum=0;"
"sum=ee_op_test_complex_func(sum);"
"sum=ee_op_test_complex_func(sum);"
"%OptimizeFunctionOnNextCall(ee_op_test_complex_func);"
"sum=ee_op_test_complex_func(sum);"
"sum;");
CHECK_EQ(4800, result->Int32Value(context).FromJust());
// Test count operations
result = CompileRun(
"function ee_op_test_count_func(sum) {"
" for (var i = 0; i < 40; ++i) {"
" sum += (++ext_array[i]);"
" sum += (--ext_array[i]);"
" } "
" return sum;"
"};"
"%PrepareFunctionForOptimization(ee_op_test_count_func);"
"sum=0;"
"sum=ee_op_test_count_func(sum);"
"sum=ee_op_test_count_func(sum);"
"%OptimizeFunctionOnNextCall(ee_op_test_count_func);"
"sum=ee_op_test_count_func(sum);"
"sum;");
CHECK_EQ(4800, result->Int32Value(context).FromJust());
i::FLAG_allow_natives_syntax = old_natives_flag_sentry;
result = CompileRun(
"ext_array[3] = 33;"
"delete ext_array[3];"
"ext_array[3];");
CHECK_EQ(33, result->Int32Value(context).FromJust());
result = CompileRun(
"ext_array[0] = 10; ext_array[1] = 11;"
"ext_array[2] = 12; ext_array[3] = 13;"
"try { ext_array.__defineGetter__('2', function() { return 120; }); }"
"catch (e) { }"
"ext_array[2];");
CHECK_EQ(12, result->Int32Value(context).FromJust());
result = CompileRun(
"var js_array = new Array(40);"
"js_array[0] = 77;"
"js_array;");
CHECK_EQ(77, v8::Object::Cast(*result)
->Get(context, v8_str("0"))
.ToLocalChecked()
->Int32Value(context)
.FromJust());
result = CompileRun(
"ext_array[1] = 23;"
"ext_array.__proto__ = [];"
"js_array.__proto__ = ext_array;"
"js_array.concat(ext_array);");
CHECK_EQ(77, v8::Object::Cast(*result)
->Get(context, v8_str("0"))
.ToLocalChecked()
->Int32Value(context)
.FromJust());
CHECK_EQ(23, v8::Object::Cast(*result)
->Get(context, v8_str("1"))
.ToLocalChecked()
->Int32Value(context)
.FromJust());
result = CompileRun("ext_array[1] = 23;");
CHECK_EQ(23, result->Int32Value(context).FromJust());
}
template <typename ElementType, typename TypedArray, class ArrayBufferType>
void TypedArrayTestHelper(i::ExternalArrayType array_type, int64_t low,
int64_t high) {
const int kElementCount = 50;
i::ScopedVector<ElementType> backing_store(kElementCount + 2);
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
Local<ArrayBufferType> ab =
ArrayBufferType::New(isolate, backing_store.begin(),
(kElementCount + 2) * sizeof(ElementType));
Local<TypedArray> ta =
TypedArray::New(ab, 2 * sizeof(ElementType), kElementCount);
CheckInternalFieldsAreZero<v8::ArrayBufferView>(ta);
CHECK_EQ(kElementCount, static_cast<int>(ta->Length()));
CHECK_EQ(2 * sizeof(ElementType), ta->ByteOffset());
CHECK_EQ(kElementCount * sizeof(ElementType), ta->ByteLength());
CHECK(ab->Equals(env.local(), ta->Buffer()).FromJust());
ElementType* data = backing_store.begin() + 2;
for (int i = 0; i < kElementCount; i++) {
data[i] = static_cast<ElementType>(i);
}
ObjectWithExternalArrayTestHelper<ElementType>(env.local(), ta, kElementCount,
array_type, low, high);
}
} // namespace
THREADED_TEST(Uint8Array) {
TypedArrayTestHelper<uint8_t, v8::Uint8Array, v8::ArrayBuffer>(
i::kExternalUint8Array, 0, 0xFF);
}
THREADED_TEST(Int8Array) {
TypedArrayTestHelper<int8_t, v8::Int8Array, v8::ArrayBuffer>(
i::kExternalInt8Array, -0x80, 0x7F);
}
THREADED_TEST(Uint16Array) {
TypedArrayTestHelper<uint16_t, v8::Uint16Array, v8::ArrayBuffer>(
i::kExternalUint16Array, 0, 0xFFFF);
}
THREADED_TEST(Int16Array) {
TypedArrayTestHelper<int16_t, v8::Int16Array, v8::ArrayBuffer>(
i::kExternalInt16Array, -0x8000, 0x7FFF);
}
THREADED_TEST(Uint32Array) {
TypedArrayTestHelper<uint32_t, v8::Uint32Array, v8::ArrayBuffer>(
i::kExternalUint32Array, 0, UINT_MAX);
}
THREADED_TEST(Int32Array) {
TypedArrayTestHelper<int32_t, v8::Int32Array, v8::ArrayBuffer>(
i::kExternalInt32Array, INT_MIN, INT_MAX);
}
THREADED_TEST(Float32Array) {
TypedArrayTestHelper<float, v8::Float32Array, v8::ArrayBuffer>(
i::kExternalFloat32Array, -500, 500);
}
THREADED_TEST(Float64Array) {
TypedArrayTestHelper<double, v8::Float64Array, v8::ArrayBuffer>(
i::kExternalFloat64Array, -500, 500);
}
THREADED_TEST(Uint8ClampedArray) {
TypedArrayTestHelper<uint8_t, v8::Uint8ClampedArray, v8::ArrayBuffer>(
i::kExternalUint8ClampedArray, 0, 0xFF);
}
THREADED_TEST(DataView) {
const int kSize = 50;
i::ScopedVector<uint8_t> backing_store(kSize + 2);
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab =
v8::ArrayBuffer::New(isolate, backing_store.begin(), 2 + kSize);
Local<v8::DataView> dv = v8::DataView::New(ab, 2, kSize);
CheckInternalFieldsAreZero<v8::ArrayBufferView>(dv);
CHECK_EQ(2u, dv->ByteOffset());
CHECK_EQ(kSize, static_cast<int>(dv->ByteLength()));
CHECK(ab->Equals(env.local(), dv->Buffer()).FromJust());
}
THREADED_TEST(SharedUint8Array) {
i::FLAG_harmony_sharedarraybuffer = true;
TypedArrayTestHelper<uint8_t, v8::Uint8Array, v8::SharedArrayBuffer>(
i::kExternalUint8Array, 0, 0xFF);
}
THREADED_TEST(SharedInt8Array) {
i::FLAG_harmony_sharedarraybuffer = true;
TypedArrayTestHelper<int8_t, v8::Int8Array, v8::SharedArrayBuffer>(
i::kExternalInt8Array, -0x80, 0x7F);
}
THREADED_TEST(SharedUint16Array) {
i::FLAG_harmony_sharedarraybuffer = true;
TypedArrayTestHelper<uint16_t, v8::Uint16Array, v8::SharedArrayBuffer>(
i::kExternalUint16Array, 0, 0xFFFF);
}
THREADED_TEST(SharedInt16Array) {
i::FLAG_harmony_sharedarraybuffer = true;
TypedArrayTestHelper<int16_t, v8::Int16Array, v8::SharedArrayBuffer>(
i::kExternalInt16Array, -0x8000, 0x7FFF);
}
THREADED_TEST(SharedUint32Array) {
i::FLAG_harmony_sharedarraybuffer = true;
TypedArrayTestHelper<uint32_t, v8::Uint32Array, v8::SharedArrayBuffer>(
i::kExternalUint32Array, 0, UINT_MAX);
}
THREADED_TEST(SharedInt32Array) {
i::FLAG_harmony_sharedarraybuffer = true;
TypedArrayTestHelper<int32_t, v8::Int32Array, v8::SharedArrayBuffer>(
i::kExternalInt32Array, INT_MIN, INT_MAX);
}
THREADED_TEST(SharedFloat32Array) {
i::FLAG_harmony_sharedarraybuffer = true;
TypedArrayTestHelper<float, v8::Float32Array, v8::SharedArrayBuffer>(
i::kExternalFloat32Array, -500, 500);
}
THREADED_TEST(SharedFloat64Array) {
i::FLAG_harmony_sharedarraybuffer = true;
TypedArrayTestHelper<double, v8::Float64Array, v8::SharedArrayBuffer>(
i::kExternalFloat64Array, -500, 500);
}
THREADED_TEST(SharedUint8ClampedArray) {
i::FLAG_harmony_sharedarraybuffer = true;
TypedArrayTestHelper<uint8_t, v8::Uint8ClampedArray, v8::SharedArrayBuffer>(
i::kExternalUint8ClampedArray, 0, 0xFF);
}
THREADED_TEST(SharedDataView) {
i::FLAG_harmony_sharedarraybuffer = true;
const int kSize = 50;
i::ScopedVector<uint8_t> backing_store(kSize + 2);
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
Local<v8::SharedArrayBuffer> ab =
v8::SharedArrayBuffer::New(isolate, backing_store.begin(), 2 + kSize);
Local<v8::DataView> dv = v8::DataView::New(ab, 2, kSize);
CheckInternalFieldsAreZero<v8::ArrayBufferView>(dv);
CHECK_EQ(2u, dv->ByteOffset());
CHECK_EQ(kSize, static_cast<int>(dv->ByteLength()));
CHECK(ab->Equals(env.local(), dv->Buffer()).FromJust());
}
#define IS_ARRAY_BUFFER_VIEW_TEST(View) \
THREADED_TEST(Is##View) { \
LocalContext env; \
v8::Isolate* isolate = env->GetIsolate(); \
v8::HandleScope handle_scope(isolate); \
\
Local<Value> result = CompileRun( \
"var ab = new ArrayBuffer(128);" \
"new " #View "(ab)"); \
CHECK(result->IsArrayBufferView()); \
CHECK(result->Is##View()); \
CheckInternalFieldsAreZero<v8::ArrayBufferView>(result.As<v8::View>()); \
}
IS_ARRAY_BUFFER_VIEW_TEST(Uint8Array)
IS_ARRAY_BUFFER_VIEW_TEST(Int8Array)
IS_ARRAY_BUFFER_VIEW_TEST(Uint16Array)
IS_ARRAY_BUFFER_VIEW_TEST(Int16Array)
IS_ARRAY_BUFFER_VIEW_TEST(Uint32Array)
IS_ARRAY_BUFFER_VIEW_TEST(Int32Array)
IS_ARRAY_BUFFER_VIEW_TEST(Float32Array)
IS_ARRAY_BUFFER_VIEW_TEST(Float64Array)
IS_ARRAY_BUFFER_VIEW_TEST(Uint8ClampedArray)
IS_ARRAY_BUFFER_VIEW_TEST(DataView)
#undef IS_ARRAY_BUFFER_VIEW_TEST
TEST(InternalFieldsOnTypedArray) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = env.local();
Context::Scope context_scope(context);
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, 1);
v8::Local<v8::Uint8Array> array = v8::Uint8Array::New(buffer, 0, 1);
for (int i = 0; i < v8::ArrayBufferView::kInternalFieldCount; i++) {
CHECK_EQ(static_cast<void*>(nullptr),
array->GetAlignedPointerFromInternalField(i));
}
}
TEST(InternalFieldsOnDataView) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = env.local();
Context::Scope context_scope(context);
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, 1);
v8::Local<v8::DataView> array = v8::DataView::New(buffer, 0, 1);
for (int i = 0; i < v8::ArrayBufferView::kInternalFieldCount; i++) {
CHECK_EQ(static_cast<void*>(nullptr),
array->GetAlignedPointerFromInternalField(i));
}
}
namespace {
void TestOnHeapHasBuffer(const char* array_name, size_t elem_size) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
i::ScopedVector<char> source(128);
// Test on-heap sizes.
for (size_t size = 0; size <= i::JSTypedArray::kMaxSizeInHeap;
size += elem_size) {
size_t length = size / elem_size;
i::SNPrintF(source, "new %sArray(%zu)", array_name, length);
auto typed_array =
v8::Local<v8::TypedArray>::Cast(CompileRun(source.begin()));
CHECK_EQ(length, typed_array->Length());
// Should not (yet) have a buffer.
CHECK(!typed_array->HasBuffer());
// Get the buffer and check its length.
i::Handle<i::JSTypedArray> i_typed_array =
v8::Utils::OpenHandle(*typed_array);
auto i_array_buffer1 = i_typed_array->GetBuffer();
CHECK_EQ(size, i_array_buffer1->byte_length());
CHECK(typed_array->HasBuffer());
// Should have the same buffer each time.
auto i_array_buffer2 = i_typed_array->GetBuffer();
CHECK(i_array_buffer1.is_identical_to(i_array_buffer2));
}
}
void TestOffHeapHasBuffer(const char* array_name, size_t elem_size) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
i::ScopedVector<char> source(128);
// Test off-heap sizes.
size_t size = i::JSTypedArray::kMaxSizeInHeap;
for (int i = 0; i < 3; i++) {
size_t length = 1 + (size / elem_size);
i::SNPrintF(source, "new %sArray(%zu)", array_name, length);
auto typed_array =
v8::Local<v8::TypedArray>::Cast(CompileRun(source.begin()));
CHECK_EQ(length, typed_array->Length());
// Should already have a buffer.
CHECK(typed_array->HasBuffer());
// Get the buffer and check its length.
i::Handle<i::JSTypedArray> i_typed_array =
v8::Utils::OpenHandle(*typed_array);
auto i_array_buffer1 = i_typed_array->GetBuffer();
CHECK_EQ(length * elem_size, i_array_buffer1->byte_length());
size *= 2;
}
}
} // namespace
#define TEST_HAS_BUFFER(array_name, elem_size) \
TEST(OnHeap_##array_name##Array_HasBuffer) { \
TestOnHeapHasBuffer(#array_name, elem_size); \
} \
TEST(OffHeap_##array_name##_HasBuffer) { \
TestOffHeapHasBuffer(#array_name, elem_size); \
}
TEST_HAS_BUFFER(Uint8, 1)
TEST_HAS_BUFFER(Int8, 1)
TEST_HAS_BUFFER(Uint16, 2)
TEST_HAS_BUFFER(Int16, 2)
TEST_HAS_BUFFER(Uint32, 4)
TEST_HAS_BUFFER(Int32, 4)
TEST_HAS_BUFFER(Float32, 4)
TEST_HAS_BUFFER(Float64, 8)
#undef TEST_HAS_BUFFER