diff --git a/gjstest/internal/cpp/run_tests.cc b/gjstest/internal/cpp/run_tests.cc index a4d6ae5..8b31501 100644 --- a/gjstest/internal/cpp/run_tests.cc +++ b/gjstest/internal/cpp/run_tests.cc @@ -29,6 +29,7 @@ #include "base/stringprintf.h" #include "base/timer.h" #include "gjstest/internal/cpp/test_case.h" +#include "gjstest/internal/cpp/typed_arrays.h" #include "gjstest/internal/cpp/v8_utils.h" #include "gjstest/internal/proto/named_scripts.pb.h" #include "strings/strutil.h" @@ -193,7 +194,10 @@ bool RunTests( // Create a context in which to run scripts and ensure that it's used whenever // a context is needed below. - Persistent context = Context::New(); + Handle global_template = ObjectTemplate::New(); + ExportTypedArrays(global_template); + + Persistent context = Context::New(NULL, global_template); Context::Scope context_scope(context); // Run all of the scripts. diff --git a/gjstest/internal/cpp/targets.mk b/gjstest/internal/cpp/targets.mk index 6ea0fdb..50f125c 100644 --- a/gjstest/internal/cpp/targets.mk +++ b/gjstest/internal/cpp/targets.mk @@ -23,6 +23,7 @@ $(eval $(call cc_library, \ base/stringprintf \ base/timer \ gjstest/internal/cpp/test_case \ + gjstest/internal/cpp/typed_arrays \ gjstest/internal/cpp/v8_utils \ gjstest/internal/proto/named_scripts.pb \ strings/strutil \ @@ -43,6 +44,11 @@ $(eval $(call cc_library, \ gjstest/internal/cpp/v8_utils \ )) +$(eval $(call cc_library, \ + gjstest/internal/cpp/typed_arrays, \ + base/logging \ +)) + $(eval $(call cc_library, \ gjstest/internal/cpp/v8_utils, \ base/callback-types \ diff --git a/gjstest/internal/cpp/typed_arrays.cc b/gjstest/internal/cpp/typed_arrays.cc new file mode 100644 index 0000000..8a2caa0 --- /dev/null +++ b/gjstest/internal/cpp/typed_arrays.cc @@ -0,0 +1,324 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// Author: jacobsa@google.com (Aaron Jacobs) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// This file contains mostly code imported from the v8 project, at +// trunk/src/d8.cc. The original copyright notice is as follows. + +// Copyright 2012 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gjstest/internal/cpp/typed_arrays.h" + +#include "base/logging.h" + +using v8::Arguments; +using v8::ExternalArrayType; +using v8::FunctionTemplate; +using v8::Handle; +using v8::HandleScope; +using v8::Int32; +using v8::Local; +using v8::Object; +using v8::ObjectTemplate; +using v8::Persistent; +using v8::ReadOnly; +using v8::String; +using v8::True; +using v8::TryCatch; +using v8::Value; +using v8::kExternalByteArray; +using v8::kExternalDoubleArray; +using v8::kExternalFloatArray; +using v8::kExternalIntArray; +using v8::kExternalShortArray; +using v8::kExternalUnsignedByteArray; +using v8::kExternalUnsignedIntArray; +using v8::kExternalUnsignedShortArray; + +namespace gjstest { + +static const char kArrayBufferReferencePropName[] = "_is_array_buffer_"; +static const char kArrayBufferMarkerPropName[] = "_array_buffer_ref_"; + +static size_t convertToUint(Local value_in, TryCatch* try_catch) { + if (value_in->IsUint32()) { + return value_in->Uint32Value(); + } + + Local number = value_in->ToNumber(); + if (try_catch->HasCaught()) return 0; + + CHECK(number->IsNumber()); + Local int32 = number->ToInt32(); + if (try_catch->HasCaught() || int32.IsEmpty()) return 0; + + int32_t raw_value = int32->Int32Value(); + if (try_catch->HasCaught()) return 0; + + if (raw_value < 0) { + ThrowException(String::New("Array length must not be negative.")); + return 0; + } + + static const int kMaxLength = 0x3fffffff; + if (raw_value > static_cast(kMaxLength)) { + ThrowException( + String::New("Array length exceeds maximum length.")); + } + return static_cast(raw_value); +} + +static void ExternalArrayWeakCallback(Persistent object, void* data) { + HandleScope scope; + Handle prop_name = String::New(kArrayBufferReferencePropName); + Handle converted_object = object->ToObject(); + Local prop_value = converted_object->Get(prop_name); + if (data != NULL && !prop_value->IsObject()) { + free(data); + } + object.Dispose(); +} + +static Handle CreateExternalArray( + const Arguments& args, + ExternalArrayType type, + size_t element_size) { + TryCatch try_catch; + bool is_array_buffer_construct = element_size == 0; + if (is_array_buffer_construct) { + type = v8::kExternalByteArray; + element_size = 1; + } + CHECK(element_size == 1 || element_size == 2 || element_size == 4 || + element_size == 8); + if (args.Length() == 0) { + return ThrowException( + String::New("Array constructor must have at least one " + "parameter.")); + } + bool first_arg_is_array_buffer = + args[0]->IsObject() && + args[0]->ToObject()->Get( + String::New(kArrayBufferMarkerPropName))->IsTrue(); + // Currently, only the following constructors are supported: + // TypedArray(unsigned long length) + // TypedArray(ArrayBuffer buffer, + // optional unsigned long byteOffset, + // optional unsigned long length) + if (args.Length() > 3) { + return ThrowException( + String::New("Array constructor from ArrayBuffer must " + "have 1-3 parameters.")); + } + + Local length_value = (args.Length() < 3) + ? (first_arg_is_array_buffer + ? args[0]->ToObject()->Get(String::New("length")) + : args[0]) + : args[2]; + size_t length = convertToUint(length_value, &try_catch); + if (try_catch.HasCaught()) return try_catch.Exception(); + + void* data = NULL; + size_t offset = 0; + + Handle array = Object::New(); + if (first_arg_is_array_buffer) { + Handle derived_from = args[0]->ToObject(); + data = derived_from->GetIndexedPropertiesExternalArrayData(); + + size_t array_buffer_length = convertToUint( + derived_from->Get(String::New("length")), + &try_catch); + if (try_catch.HasCaught()) return try_catch.Exception(); + + if (data == NULL && array_buffer_length != 0) { + return ThrowException( + String::New("ArrayBuffer doesn't have data")); + } + + if (args.Length() > 1) { + offset = convertToUint(args[1], &try_catch); + if (try_catch.HasCaught()) return try_catch.Exception(); + + // The given byteOffset must be a multiple of the element size of the + // specific type, otherwise an exception is raised. + if (offset % element_size != 0) { + return ThrowException( + String::New("offset must be multiple of element_size")); + } + } + + if (offset > array_buffer_length) { + return ThrowException( + String::New("byteOffset must be less than ArrayBuffer length.")); + } + + if (args.Length() == 2) { + // If length is not explicitly specified, the length of the ArrayBuffer + // minus the byteOffset must be a multiple of the element size of the + // specific type, or an exception is raised. + length = array_buffer_length - offset; + } + + if (args.Length() != 3) { + if (length % element_size != 0) { + return ThrowException( + String::New("ArrayBuffer length minus the byteOffset must be a " + "multiple of the element size")); + } + length /= element_size; + } + + // If a given byteOffset and length references an area beyond the end of + // the ArrayBuffer an exception is raised. + if (offset + (length * element_size) > array_buffer_length) { + return ThrowException( + String::New("length references an area beyond the end of the " + "ArrayBuffer")); + } + + // Hold a reference to the ArrayBuffer so its buffer doesn't get collected. + array->Set(String::New(kArrayBufferReferencePropName), args[0], ReadOnly); + } + + if (is_array_buffer_construct) { + array->Set(String::New(kArrayBufferMarkerPropName), True(), ReadOnly); + } + + Persistent persistent_array = Persistent::New(array); + persistent_array.MakeWeak(data, ExternalArrayWeakCallback); + persistent_array.MarkIndependent(); + if (data == NULL && length != 0) { + data = calloc(length, element_size); + if (data == NULL) { + return ThrowException(String::New("Memory allocation failed.")); + } + } + + array->SetIndexedPropertiesToExternalArrayData( + reinterpret_cast(data) + offset, type, + static_cast(length)); + array->Set(String::New("length"), + Int32::New(static_cast(length)), ReadOnly); + array->Set(String::New("BYTES_PER_ELEMENT"), + Int32::New(static_cast(element_size))); + return array; +} + +Handle ArrayBuffer(const Arguments& args) { + return CreateExternalArray(args, kExternalByteArray, 0); +} + +Handle Int8Array(const Arguments& args) { + return CreateExternalArray(args, kExternalByteArray, sizeof(int8_t)); +} + +Handle Int16Array(const Arguments& args) { + return CreateExternalArray(args, kExternalShortArray, sizeof(int16_t)); +} + +Handle Int32Array(const Arguments& args) { + return CreateExternalArray(args, kExternalIntArray, sizeof(int32_t)); +} + +Handle Uint8Array(const Arguments& args) { + return CreateExternalArray(args, kExternalUnsignedByteArray, sizeof(uint8_t)); +} + +Handle Uint16Array(const Arguments& args) { + return CreateExternalArray(args, kExternalUnsignedShortArray, sizeof(uint16_t)); +} + +Handle Uint32Array(const Arguments& args) { + return CreateExternalArray(args, kExternalUnsignedIntArray, sizeof(uint32_t)); +} + +Handle Float32Array(const Arguments& args) { + return CreateExternalArray(args, kExternalFloatArray, sizeof(float)); +} + +Handle Float64Array(const Arguments& args) { + return CreateExternalArray(args, kExternalDoubleArray, sizeof(double)); +} + +void ExportTypedArrays( + const Handle& global_template) { + global_template->Set( + String::New("ArrayBuffer"), + FunctionTemplate::New(ArrayBuffer)); + + // Signed integers. + global_template->Set( + String::New("Int8Array"), + FunctionTemplate::New(Int8Array)); + + global_template->Set( + String::New("Int16Array"), + FunctionTemplate::New(Int16Array)); + + global_template->Set( + String::New("Int32Array"), + FunctionTemplate::New(Int32Array)); + + // Unigned integers. + global_template->Set( + String::New("Uint8Array"), + FunctionTemplate::New(Uint8Array)); + + global_template->Set( + String::New("Uint16Array"), + FunctionTemplate::New(Uint16Array)); + + global_template->Set( + String::New("Uint32Array"), + FunctionTemplate::New(Uint32Array)); + + // Floats. + global_template->Set( + String::New("Float32Array"), + FunctionTemplate::New(Float32Array)); + + global_template->Set( + String::New("Float64Array"), + FunctionTemplate::New(Float64Array)); +} + +} // namespace gjstest diff --git a/gjstest/internal/cpp/typed_arrays.h b/gjstest/internal/cpp/typed_arrays.h new file mode 100644 index 0000000..1be5acc --- /dev/null +++ b/gjstest/internal/cpp/typed_arrays.h @@ -0,0 +1,34 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// Author: jacobsa@google.com (Aaron Jacobs) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Utility functions useful in implementing typed array support, as defined in +// this specification: +// +// http://www.khronos.org/registry/typedarray/specs/latest/ +// + +#ifndef GJSTEST_INTERNAL_CPP_TYPED_ARRAYS_H_ +#define GJSTEST_INTERNAL_CPP_TYPED_ARRAYS_H_ + +#include + +namespace gjstest { + +// Export the global functions necessary for typed array support. +void ExportTypedArrays(const v8::Handle& global_template); + +} // namespace gjstest + +#endif // GJSTEST_INTERNAL_CPP_TYPED_ARRAYS_H_ diff --git a/gjstest/internal/integration_tests/integration_test.cc b/gjstest/internal/integration_tests/integration_test.cc index 355cf00..5af1bf8 100644 --- a/gjstest/internal/integration_tests/integration_test.cc +++ b/gjstest/internal/integration_tests/integration_test.cc @@ -218,6 +218,12 @@ TEST_F(IntegrationTest, ExceptionDuringTest) { EXPECT_TRUE(CheckGoldenFile("exception.golden.xml", xml_)); } +TEST_F(IntegrationTest, TypedArrays) { + EXPECT_TRUE(RunBundleNamed("typed_arrays")) << txt_; + EXPECT_TRUE(CheckGoldenFile("typed_arrays.golden.txt", txt_)); + EXPECT_TRUE(CheckGoldenFile("typed_arrays.golden.xml", xml_)); +} + TEST_F(IntegrationTest, TestCaseCalledConstructor) { EXPECT_FALSE(RunBundleNamed("constructor")) << txt_; EXPECT_TRUE(CheckGoldenFile("constructor.golden.txt", txt_)); diff --git a/gjstest/internal/integration_tests/typed_arrays.golden.txt b/gjstest/internal/integration_tests/typed_arrays.golden.txt new file mode 100644 index 0000000..be04abe --- /dev/null +++ b/gjstest/internal/integration_tests/typed_arrays.golden.txt @@ -0,0 +1,22 @@ +[----------] +[ RUN ] TypedArraysTest.ArrayBuffer +[ OK ] TypedArraysTest.ArrayBuffer (1 ms) +[ RUN ] TypedArraysTest.Int8Array +[ OK ] TypedArraysTest.Int8Array (1 ms) +[ RUN ] TypedArraysTest.Int16Array +[ OK ] TypedArraysTest.Int16Array (1 ms) +[ RUN ] TypedArraysTest.Int32Array +[ OK ] TypedArraysTest.Int32Array (1 ms) +[ RUN ] TypedArraysTest.Uint8Array +[ OK ] TypedArraysTest.Uint8Array (1 ms) +[ RUN ] TypedArraysTest.Uint16Array +[ OK ] TypedArraysTest.Uint16Array (1 ms) +[ RUN ] TypedArraysTest.Uint32Array +[ OK ] TypedArraysTest.Uint32Array (1 ms) +[ RUN ] TypedArraysTest.Float32Array +[ OK ] TypedArraysTest.Float32Array (1 ms) +[ RUN ] TypedArraysTest.Float64Array +[ OK ] TypedArraysTest.Float64Array (1 ms) +[----------] + +[ PASSED ] diff --git a/gjstest/internal/integration_tests/typed_arrays.golden.xml b/gjstest/internal/integration_tests/typed_arrays.golden.xml new file mode 100644 index 0000000..871bc82 --- /dev/null +++ b/gjstest/internal/integration_tests/typed_arrays.golden.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/gjstest/internal/integration_tests/typed_arrays_test.js b/gjstest/internal/integration_tests/typed_arrays_test.js new file mode 100644 index 0000000..1ba1057 --- /dev/null +++ b/gjstest/internal/integration_tests/typed_arrays_test.js @@ -0,0 +1,234 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// Author: jacobsa@google.com (Aaron Jacobs) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A test file with test cases that use typed array types, for use by +// integration_test.cc. + +function TypedArraysTest() {} +registerTestSuite(TypedArraysTest); + +TypedArraysTest.prototype.ArrayBuffer = function() { + var buffer = new ArrayBuffer(4); + var bytes = new Uint8Array(buffer); + var last16 = new Uint16Array(buffer, 2, 1); + + expectEq(4, bytes.length); + expectEq(1, last16.length); + + bytes[0] = 0x12; + bytes[1] = 0x34; + bytes[2] = 0x56; + bytes[3] = 0x78; + + expectEq(0x7856, last16[0]); +} + +TypedArraysTest.prototype.Int8Array = function() { + var kType = Int8Array; + var kBitWidth = 8; + + var a = new kType(2); + expectEq(kBitWidth / 8, a.BYTES_PER_ELEMENT); + expectEq(2, a.length); + + var kMax = Math.pow(2, kBitWidth - 1) - 1; + var kMin = -1 * kMax - 1; + + // Non-overflowing + a[0] = kMin; + a[1] = kMax; + + expectEq(kMin, a[0]); + expectEq(kMax, a[1]); + expectThat(a, elementsAre([kMin, kMax])); + + // Overflowing + a[0] = kMin - 2; + a[1] = kMax + 3; + + expectEq(kMax - 1, a[0]); + expectEq(kMin + 2, a[1]); + expectThat(a, elementsAre([kMax - 1, kMin + 2])); +}; + +TypedArraysTest.prototype.Int16Array = function() { + var kType = Int16Array; + var kBitWidth = 16; + + var a = new kType(2); + expectEq(kBitWidth / 8, a.BYTES_PER_ELEMENT); + expectEq(2, a.length); + + var kMax = Math.pow(2, kBitWidth - 1) - 1; + var kMin = -1 * kMax - 1; + + // Non-overflowing + a[0] = kMin; + a[1] = kMax; + + expectEq(kMin, a[0]); + expectEq(kMax, a[1]); + expectThat(a, elementsAre([kMin, kMax])); + + // Overflowing + a[0] = kMin - 2; + a[1] = kMax + 3; + + expectEq(kMax - 1, a[0]); + expectEq(kMin + 2, a[1]); + expectThat(a, elementsAre([kMax - 1, kMin + 2])); +}; + +TypedArraysTest.prototype.Int32Array = function() { + var kType = Int32Array; + var kBitWidth = 32; + + var a = new kType(2); + expectEq(kBitWidth / 8, a.BYTES_PER_ELEMENT); + expectEq(2, a.length); + + var kMax = Math.pow(2, kBitWidth - 1) - 1; + var kMin = -1 * kMax - 1; + + // Non-overflowing + a[0] = kMin; + a[1] = kMax; + + expectEq(kMin, a[0]); + expectEq(kMax, a[1]); + expectThat(a, elementsAre([kMin, kMax])); + + // Overflowing + a[0] = kMin - 2; + a[1] = kMax + 3; + + expectEq(kMax - 1, a[0]); + expectEq(kMin + 2, a[1]); + expectThat(a, elementsAre([kMax - 1, kMin + 2])); +}; + +TypedArraysTest.prototype.Uint8Array = function() { + var kType = Uint8Array; + var kBitWidth = 8; + + var a = new kType(2); + expectEq(kBitWidth / 8, a.BYTES_PER_ELEMENT); + expectEq(2, a.length); + + var kMin = 0; + var kMax = Math.pow(2, kBitWidth) - 1; + + // Non-overflowing + a[0] = kMin; + a[1] = kMax; + + expectEq(kMin, a[0]); + expectEq(kMax, a[1]); + expectThat(a, elementsAre([kMin, kMax])); + + // Overflowing + a[0] = kMin - 2; + a[1] = kMax + 3; + + expectEq(kMax - 1, a[0]); + expectEq(kMin + 2, a[1]); + expectThat(a, elementsAre([kMax - 1, kMin + 2])); +}; + +TypedArraysTest.prototype.Uint16Array = function() { + var kType = Uint16Array; + var kBitWidth = 16; + + var a = new kType(2); + expectEq(kBitWidth / 8, a.BYTES_PER_ELEMENT); + expectEq(2, a.length); + + var kMin = 0; + var kMax = Math.pow(2, kBitWidth) - 1; + + // Non-overflowing + a[0] = kMin; + a[1] = kMax; + + expectEq(kMin, a[0]); + expectEq(kMax, a[1]); + expectThat(a, elementsAre([kMin, kMax])); + + // Overflowing + a[0] = kMin - 2; + a[1] = kMax + 3; + + expectEq(kMax - 1, a[0]); + expectEq(kMin + 2, a[1]); + expectThat(a, elementsAre([kMax - 1, kMin + 2])); +}; + +TypedArraysTest.prototype.Uint32Array = function() { + var kType = Uint32Array; + var kBitWidth = 32; + + var a = new kType(2); + expectEq(kBitWidth / 8, a.BYTES_PER_ELEMENT); + expectEq(2, a.length); + + var kMin = 0; + var kMax = Math.pow(2, kBitWidth) - 1; + + // Non-overflowing + a[0] = kMin; + a[1] = kMax; + + expectEq(kMin, a[0]); + expectEq(kMax, a[1]); + expectThat(a, elementsAre([kMin, kMax])); + + // Overflowing + a[0] = kMin - 2; + a[1] = kMax + 3; + + expectEq(kMax - 1, a[0]); + expectEq(kMin + 2, a[1]); + expectThat(a, elementsAre([kMax - 1, kMin + 2])); +}; + +TypedArraysTest.prototype.Float32Array = function() { + var a = new Float32Array(2); + + expectEq(4, a.BYTES_PER_ELEMENT); + expectEq(2, a.length); + + a[0] = 17.5; + a[1] = -17.5 + + + expectEq(17.5, a[0]); + expectEq(-17.5, a[1]); + expectThat(a, elementsAre([17.5, -17.5])); +}; + +TypedArraysTest.prototype.Float64Array = function() { + var a = new Float64Array(2); + + expectEq(8, a.BYTES_PER_ELEMENT); + expectEq(2, a.length); + + a[0] = 17.5; + a[1] = -17.5 + + + expectEq(17.5, a[0]); + expectEq(-17.5, a[1]); + expectThat(a, elementsAre([17.5, -17.5])); +};