diff options
author | Andreas Kling <kling@serenityos.org> | 2020-12-01 21:05:25 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-12-01 21:05:25 +0100 |
commit | 3565d3c60c43d29f35f309063de6b6a5dc70929a (patch) | |
tree | 85ca7e136026d6b0a885ca701bb87ad655b9b20b | |
parent | 93feb7a81fe7c1ca9603d94d24eeb5822c0c56f7 (diff) | |
download | serenity-3565d3c60c43d29f35f309063de6b6a5dc70929a.zip |
LibJS: Add six typed arrays (signed and unsigned 8/16/32-bit)
This patch adds six of the standard type arrays and tries to share as
much code as possible:
- Uint8Array
- Uint16Array
- Uint32Array
- Int8Array
- Int16Array
- Int32Array
-rw-r--r-- | Libraries/LibJS/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Libraries/LibJS/Forward.h | 61 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/CommonPropertyNames.h | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Error.cpp | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Error.h | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ErrorConstructor.cpp | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ErrorConstructor.h | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ErrorPrototype.cpp | 10 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ErrorPrototype.h | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/GlobalObject.cpp | 10 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/GlobalObject.h | 6 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Object.h | 1 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/TypedArray.cpp | 95 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/TypedArray.h | 165 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/builtins/TypedArray/typed-array-basic.js | 86 |
15 files changed, 403 insertions, 44 deletions
diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index 6517907288..778d2d71f9 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/Libraries/LibJS/CMakeLists.txt @@ -72,6 +72,7 @@ set(SOURCES Runtime/SymbolConstructor.cpp Runtime/SymbolObject.cpp Runtime/SymbolPrototype.cpp + Runtime/TypedArray.cpp Runtime/Uint8ClampedArray.cpp Runtime/VM.cpp Runtime/Value.cpp diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index a6f482f825..a351738cf6 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -45,28 +45,36 @@ void name([[maybe_unused]] JS::VM& vm, [[maybe_unused]] JS::GlobalObject& global_object, JS::Value value) // NOTE: Proxy is not included here as it doesn't have a prototype - m_proxy_constructor is initialized separately. -#define JS_ENUMERATE_NATIVE_OBJECTS \ - __JS_ENUMERATE(Array, array, ArrayPrototype, ArrayConstructor) \ - __JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor) \ - __JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor) \ - __JS_ENUMERATE(Date, date, DatePrototype, DateConstructor) \ - __JS_ENUMERATE(Error, error, ErrorPrototype, ErrorConstructor) \ - __JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor) \ - __JS_ENUMERATE(NumberObject, number, NumberPrototype, NumberConstructor) \ - __JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor) \ - __JS_ENUMERATE(RegExpObject, regexp, RegExpPrototype, RegExpConstructor) \ - __JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor) \ - __JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor) - -#define JS_ENUMERATE_ERROR_SUBCLASSES \ - __JS_ENUMERATE(EvalError, eval_error, EvalErrorPrototype, EvalErrorConstructor) \ - __JS_ENUMERATE(InternalError, internal_error, InternalErrorPrototype, InternalErrorConstructor) \ - __JS_ENUMERATE(InvalidCharacterError, invalid_character_error, InvalidCharacterErrorPrototype, InvalidCharacterErrorConstructor) \ - __JS_ENUMERATE(RangeError, range_error, RangeErrorPrototype, RangeErrorConstructor) \ - __JS_ENUMERATE(ReferenceError, reference_error, ReferenceErrorPrototype, ReferenceErrorConstructor) \ - __JS_ENUMERATE(SyntaxError, syntax_error, SyntaxErrorPrototype, SyntaxErrorConstructor) \ - __JS_ENUMERATE(TypeError, type_error, TypeErrorPrototype, TypeErrorConstructor) \ - __JS_ENUMERATE(URIError, uri_error, URIErrorPrototype, URIErrorConstructor) +#define JS_ENUMERATE_NATIVE_OBJECTS \ + __JS_ENUMERATE(Array, array, ArrayPrototype, ArrayConstructor, void) \ + __JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor, void) \ + __JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \ + __JS_ENUMERATE(Date, date, DatePrototype, DateConstructor, void) \ + __JS_ENUMERATE(Error, error, ErrorPrototype, ErrorConstructor, void) \ + __JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor, void) \ + __JS_ENUMERATE(NumberObject, number, NumberPrototype, NumberConstructor, void) \ + __JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor, void) \ + __JS_ENUMERATE(RegExpObject, regexp, RegExpPrototype, RegExpConstructor, void) \ + __JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor, void) \ + __JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor, void) + +#define JS_ENUMERATE_ERROR_SUBCLASSES \ + __JS_ENUMERATE(EvalError, eval_error, EvalErrorPrototype, EvalErrorConstructor, void) \ + __JS_ENUMERATE(InternalError, internal_error, InternalErrorPrototype, InternalErrorConstructor, void) \ + __JS_ENUMERATE(InvalidCharacterError, invalid_character_error, InvalidCharacterErrorPrototype, InvalidCharacterErrorConstructor, void) \ + __JS_ENUMERATE(RangeError, range_error, RangeErrorPrototype, RangeErrorConstructor, void) \ + __JS_ENUMERATE(ReferenceError, reference_error, ReferenceErrorPrototype, ReferenceErrorConstructor, void) \ + __JS_ENUMERATE(SyntaxError, syntax_error, SyntaxErrorPrototype, SyntaxErrorConstructor, void) \ + __JS_ENUMERATE(TypeError, type_error, TypeErrorPrototype, TypeErrorConstructor, void) \ + __JS_ENUMERATE(URIError, uri_error, URIErrorPrototype, URIErrorConstructor, void) + +#define JS_ENUMERATE_TYPED_ARRAYS \ + __JS_ENUMERATE(Uint8Array, uint8_array, Uint8ArrayPrototype, Uint8ArrayConstructor, u8) \ + __JS_ENUMERATE(Uint16Array, uint16_array, Uint16ArrayPrototype, Uint16ArrayConstructor, u16) \ + __JS_ENUMERATE(Uint32Array, uint32_array, Uint32ArrayPrototype, Uint32ArrayConstructor, u32) \ + __JS_ENUMERATE(Int8Array, int8_array, Int8ArrayPrototype, Int8ArrayConstructor, i8) \ + __JS_ENUMERATE(Int16Array, int16_array, Int16ArrayPrototype, Int16ArrayConstructor, i16) \ + __JS_ENUMERATE(Int32Array, int32_array, Int32ArrayPrototype, Int32ArrayConstructor, i32) #define JS_ENUMERATE_ITERATOR_PROTOTYPES \ __JS_ENUMERATE(Iterator, iterator) \ @@ -75,7 +83,8 @@ #define JS_ENUMERATE_BUILTIN_TYPES \ JS_ENUMERATE_NATIVE_OBJECTS \ - JS_ENUMERATE_ERROR_SUBCLASSES + JS_ENUMERATE_ERROR_SUBCLASSES \ + JS_ENUMERATE_TYPED_ARRAYS #define JS_ENUMERATE_WELL_KNOWN_SYMBOLS \ __JS_ENUMERATE(iterator, iterator) \ @@ -138,9 +147,9 @@ enum class DeclarationKind; class ProxyObject; class ProxyConstructor; -#define __JS_ENUMERATE(ClassName, snake_name, ConstructorName, PrototypeName) \ - class ClassName; \ - class ConstructorName; \ +#define __JS_ENUMERATE(ClassName, snake_name, ConstructorName, PrototypeName, ArrayType) \ + class ClassName; \ + class ConstructorName; \ class PrototypeName; JS_ENUMERATE_BUILTIN_TYPES #undef __JS_ENUMERATE diff --git a/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Libraries/LibJS/Runtime/CommonPropertyNames.h index f2803f3162..46d0eab30a 100644 --- a/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -223,7 +223,7 @@ struct CommonPropertyNames { #define __ENUMERATE(x) FlyString x { #x }; ENUMERATE_STANDARD_PROPERTY_NAMES(__ENUMERATE) #undef __ENUMERATE -#define __JS_ENUMERATE(x, a, b, c) FlyString x { #x }; +#define __JS_ENUMERATE(x, a, b, c, t) FlyString x { #x }; JS_ENUMERATE_BUILTIN_TYPES #undef __JS_ENUMERATE #define __JS_ENUMERATE(x, a) FlyString x { #x }; diff --git a/Libraries/LibJS/Runtime/Error.cpp b/Libraries/LibJS/Runtime/Error.cpp index f0d8c891b4..7f30ab872c 100644 --- a/Libraries/LibJS/Runtime/Error.cpp +++ b/Libraries/LibJS/Runtime/Error.cpp @@ -45,7 +45,7 @@ Error::~Error() { } -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ ClassName* ClassName::create(GlobalObject& global_object, const String& message) \ { \ return global_object.heap().allocate<ClassName>(global_object, message, *global_object.snake_name##_prototype()); \ diff --git a/Libraries/LibJS/Runtime/Error.h b/Libraries/LibJS/Runtime/Error.h index 7ec5c67ac2..e186b13e17 100644 --- a/Libraries/LibJS/Runtime/Error.h +++ b/Libraries/LibJS/Runtime/Error.h @@ -63,7 +63,7 @@ private: virtual ~ClassName() override; \ }; -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ DECLARE_ERROR_SUBCLASS(ClassName, snake_name, PrototypeName, ConstructorName) JS_ENUMERATE_ERROR_SUBCLASSES #undef __JS_ENUMERATE diff --git a/Libraries/LibJS/Runtime/ErrorConstructor.cpp b/Libraries/LibJS/Runtime/ErrorConstructor.cpp index 28b01bac33..549c74855c 100644 --- a/Libraries/LibJS/Runtime/ErrorConstructor.cpp +++ b/Libraries/LibJS/Runtime/ErrorConstructor.cpp @@ -64,7 +64,7 @@ Value ErrorConstructor::construct(Function&) return Error::create(global_object(), vm.names.Error, message); } -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ ConstructorName::ConstructorName(GlobalObject& global_object) \ : NativeFunction(*global_object.function_prototype()) \ { \ diff --git a/Libraries/LibJS/Runtime/ErrorConstructor.h b/Libraries/LibJS/Runtime/ErrorConstructor.h index 5f7d69830e..2626623aaa 100644 --- a/Libraries/LibJS/Runtime/ErrorConstructor.h +++ b/Libraries/LibJS/Runtime/ErrorConstructor.h @@ -61,7 +61,7 @@ private: virtual bool has_constructor() const override { return true; } \ }; -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ DECLARE_ERROR_SUBCLASS_CONSTRUCTOR(ClassName, snake_name, PrototypeName, ConstructorName) JS_ENUMERATE_ERROR_SUBCLASSES #undef __JS_ENUMERATE diff --git a/Libraries/LibJS/Runtime/ErrorPrototype.cpp b/Libraries/LibJS/Runtime/ErrorPrototype.cpp index 0a74ee8173..151924b76a 100644 --- a/Libraries/LibJS/Runtime/ErrorPrototype.cpp +++ b/Libraries/LibJS/Runtime/ErrorPrototype.cpp @@ -127,11 +127,11 @@ JS_DEFINE_NATIVE_FUNCTION(ErrorPrototype::to_string) return js_string(vm, String::formatted("{}: {}", name, message)); } -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ - PrototypeName::PrototypeName(GlobalObject& global_object) \ - : Object(*global_object.error_prototype()) \ - { \ - } \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + PrototypeName::PrototypeName(GlobalObject& global_object) \ + : Object(*global_object.error_prototype()) \ + { \ + } \ PrototypeName::~PrototypeName() { } JS_ENUMERATE_ERROR_SUBCLASSES diff --git a/Libraries/LibJS/Runtime/ErrorPrototype.h b/Libraries/LibJS/Runtime/ErrorPrototype.h index 0e3423b26d..3b461942ab 100644 --- a/Libraries/LibJS/Runtime/ErrorPrototype.h +++ b/Libraries/LibJS/Runtime/ErrorPrototype.h @@ -57,7 +57,7 @@ private: virtual ~PrototypeName() override; \ }; -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ DECLARE_ERROR_SUBCLASS_PROTOTYPE(ClassName, snake_name, PrototypeName, ConstructorName) JS_ENUMERATE_ERROR_SUBCLASSES #undef __JS_ENUMERATE diff --git a/Libraries/LibJS/Runtime/GlobalObject.cpp b/Libraries/LibJS/Runtime/GlobalObject.cpp index 7d8b0992df..7a0b939a02 100644 --- a/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -62,6 +62,7 @@ #include <LibJS/Runtime/StringPrototype.h> #include <LibJS/Runtime/SymbolConstructor.h> #include <LibJS/Runtime/SymbolPrototype.h> +#include <LibJS/Runtime/TypedArray.h> #include <LibJS/Runtime/Value.h> namespace JS { @@ -95,8 +96,8 @@ void GlobalObject::initialize() set_prototype(m_object_prototype); -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ - if (!m_##snake_name##_prototype) \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + if (!m_##snake_name##_prototype) \ m_##snake_name##_prototype = heap().allocate<PrototypeName>(*this, *this); JS_ENUMERATE_BUILTIN_TYPES #undef __JS_ENUMERATE @@ -136,9 +137,10 @@ void GlobalObject::initialize() add_constructor(vm.names.String, m_string_constructor, m_string_prototype); add_constructor(vm.names.Symbol, m_symbol_constructor, m_symbol_prototype); -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ add_constructor(vm.names.ClassName, m_##snake_name##_constructor, m_##snake_name##_prototype); JS_ENUMERATE_ERROR_SUBCLASSES + JS_ENUMERATE_TYPED_ARRAYS #undef __JS_ENUMERATE } @@ -154,7 +156,7 @@ void GlobalObject::visit_edges(Visitor& visitor) visitor.visit(m_new_object_shape); visitor.visit(m_new_script_function_prototype_object_shape); -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ visitor.visit(m_##snake_name##_constructor); JS_ENUMERATE_ERROR_SUBCLASSES #undef __JS_ENUMERATE diff --git a/Libraries/LibJS/Runtime/GlobalObject.h b/Libraries/LibJS/Runtime/GlobalObject.h index 6ac0eaaa47..154aa5b2aa 100644 --- a/Libraries/LibJS/Runtime/GlobalObject.h +++ b/Libraries/LibJS/Runtime/GlobalObject.h @@ -56,7 +56,7 @@ public: // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype ProxyConstructor* proxy_constructor() { return m_proxy_constructor; } -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ ConstructorName* snake_name##_constructor() { return m_##snake_name##_constructor; } \ Object* snake_name##_prototype() { return m_##snake_name##_prototype; } JS_ENUMERATE_BUILTIN_TYPES @@ -90,8 +90,8 @@ private: // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype ProxyConstructor* m_proxy_constructor { nullptr }; -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ - ConstructorName* m_##snake_name##_constructor { nullptr }; \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + ConstructorName* m_##snake_name##_constructor { nullptr }; \ Object* m_##snake_name##_prototype { nullptr }; JS_ENUMERATE_BUILTIN_TYPES #undef __JS_ENUMERATE diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index 5caa6780c4..54705576f6 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -129,6 +129,7 @@ public: virtual bool is_array_iterator_object() const { return false; } virtual bool is_lexical_environment() const { return false; } virtual bool is_global_object() const { return false; } + virtual bool is_typed_array() const { return false; } virtual const char* class_name() const override { return "Object"; } virtual void visit_edges(Cell::Visitor&) override; diff --git a/Libraries/LibJS/Runtime/TypedArray.cpp b/Libraries/LibJS/Runtime/TypedArray.cpp new file mode 100644 index 0000000000..99d0487ab3 --- /dev/null +++ b/Libraries/LibJS/Runtime/TypedArray.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. 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. + * + * 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 HOLDER 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 <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/TypedArray.h> + +namespace JS { + +#define JS_DEFINE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ + ClassName::~ClassName() { } \ + ClassName* ClassName::create(GlobalObject& global_object, u32 length) \ + { \ + return global_object.heap().allocate<ClassName>(global_object, length, *global_object.snake_name##_prototype()); \ + } \ + \ + ClassName::ClassName(u32 length, Object& prototype) \ + : TypedArray(length, prototype) \ + { \ + } \ + \ + PrototypeName::PrototypeName(GlobalObject& global_object) \ + : Object(*global_object.object_prototype()) \ + { \ + } \ + void PrototypeName::initialize(GlobalObject& global_object) \ + { \ + auto& vm = this->vm(); \ + Object::initialize(global_object); \ + define_property(vm.names.length, Value(0), Attribute::Configurable); \ + } \ + PrototypeName::~PrototypeName() { } \ + \ + ConstructorName::~ConstructorName() { } \ + Value ConstructorName::construct(Function&) { return call(); } \ + ConstructorName::ConstructorName(GlobalObject& global_object) \ + : NativeFunction(vm().names.ClassName, *global_object.function_prototype()) \ + { \ + } \ + void ConstructorName::initialize(GlobalObject& global_object) \ + { \ + auto& vm = this->vm(); \ + NativeFunction::initialize(global_object); \ + define_property(vm.names.prototype, global_object.snake_name##_prototype(), 0); \ + define_property(vm.names.length, Value(1), Attribute::Configurable); \ + } \ + Value ConstructorName::call() \ + { \ + if (vm().argument_count() <= 0) \ + return ClassName::create(global_object(), 0); \ + \ + if (vm().argument_count() == 1 && vm().argument(0).is_number()) { \ + auto array_length_value = vm().argument(0); \ + if (!array_length_value.is_integer() || array_length_value.as_i32() < 0) { \ + vm().throw_exception<TypeError>(global_object(), ErrorType::ArrayInvalidLength); \ + return {}; \ + } \ + auto* array = ClassName::create(global_object(), array_length_value.as_i32()); \ + return array; \ + } \ + auto* array = ClassName::create(global_object(), vm().argument_count()); \ + for (size_t i = 0; i < vm().argument_count(); ++i) \ + array->put_by_index(i, vm().argument(i)); \ + return array; \ + } + +#undef __JS_ENUMERATE +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + JS_DEFINE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType); +JS_ENUMERATE_TYPED_ARRAYS +#undef __JS_ENUMERATE + +} diff --git a/Libraries/LibJS/Runtime/TypedArray.h b/Libraries/LibJS/Runtime/TypedArray.h new file mode 100644 index 0000000000..8f8c928704 --- /dev/null +++ b/Libraries/LibJS/Runtime/TypedArray.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. 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. + * + * 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 HOLDER 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. + */ + +#pragma once + +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/NativeFunction.h> +#include <LibJS/Runtime/VM.h> + +namespace JS { + +template<typename T> +class TypedArray : public Object { + JS_OBJECT(TypedArray, Object); + +public: + virtual ~TypedArray() override + { + ASSERT(m_data); + free(m_data); + m_data = nullptr; + } + + i32 length() const { return m_length; } + + virtual bool put_by_index(u32 property_index, Value value) override + { + if (property_index >= m_length) + return Base::put_by_index(property_index, value); + + if constexpr (sizeof(T) < 4) { + auto number = value.to_i32(global_object()); + if (vm().exception()) + return {}; + m_data[property_index] = number; + } else if constexpr (sizeof(T) == 4) { + auto number = value.to_double(global_object()); + if (vm().exception()) + return {}; + m_data[property_index] = number; + } else { + static_assert(DependentFalse<T>, "TypedArray::put_by_index with unhandled type size"); + } + return true; + } + + virtual Value get_by_index(u32 property_index) const override + { + if (property_index >= m_length) + return Base::get_by_index(property_index); + + if constexpr (sizeof(T) < 4) { + return Value((i32)m_data[property_index]); + } else if constexpr (sizeof(T) == 4) { + auto value = m_data[property_index]; + if constexpr (NumericLimits<T>::is_signed()) { + if (value > NumericLimits<i32>::max() || value < NumericLimits<i32>::min()) + return Value((double)value); + } else { + if (value > NumericLimits<i32>::max()) + return Value((double)value); + } + return Value((i32)value); + } else { + static_assert(DependentFalse<T>, "TypedArray::get_by_index with unhandled type size"); + } + } + + T* data() { return m_data; } + const T* data() const { return m_data; } + +protected: + TypedArray(u32 length, Object& prototype) + : Object(prototype) + , m_length(length) + { + auto& vm = this->vm(); + define_native_property(vm.names.length, length_getter, nullptr); + m_data = (T*)calloc(m_length, sizeof(T)); + } + +private: + virtual bool is_typed_array() const final { return true; } + + JS_DECLARE_NATIVE_GETTER(length_getter); + + T* m_data { nullptr }; + u32 m_length { 0 }; +}; + +template<typename T> +inline JS_DEFINE_NATIVE_GETTER(TypedArray<T>::length_getter) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!this_object->is_typed_array()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "TypedArray"); + return {}; + } + return Value(static_cast<const TypedArray*>(this_object)->length()); +} + +#define JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ + class ClassName : public TypedArray<Type> { \ + JS_OBJECT(ClassName, TypedArray); \ + \ + public: \ + virtual ~ClassName(); \ + static ClassName* create(GlobalObject&, u32 length); \ + ClassName(u32 length, Object& prototype); \ + }; \ + class PrototypeName final : public Object { \ + JS_OBJECT(PrototypeName, Object); \ + \ + public: \ + PrototypeName(GlobalObject&); \ + virtual void initialize(GlobalObject&) override; \ + virtual ~PrototypeName() override; \ + }; \ + class ConstructorName final : public NativeFunction { \ + JS_OBJECT(ConstructorName, NativeFunction); \ + \ + public: \ + explicit ConstructorName(GlobalObject&); \ + virtual void initialize(GlobalObject&) override; \ + virtual ~ConstructorName() override; \ + \ + virtual Value call() override; \ + virtual Value construct(Function& new_target) override; \ + \ + private: \ + virtual bool has_constructor() const override { return true; } \ + }; + +#undef __JS_ENUMERATE +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType); +JS_ENUMERATE_TYPED_ARRAYS +#undef __JS_ENUMERATE + +} diff --git a/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-basic.js b/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-basic.js new file mode 100644 index 0000000000..aeefb5bb54 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/TypedArray/typed-array-basic.js @@ -0,0 +1,86 @@ +test("basic Uint8Array", () => { + var a = new Uint8Array(1); + expect(typeof a).toBe("object"); + expect(a instanceof Uint8Array).toBe(true); + expect(a.length).toBe(1); + a[0] = 1; + expect(a[0]).toBe(1); + a[0] -= 2; + expect(a[0]).toBe(0xff); + ++a[0]; + expect(a[0]).toBe(0); +}); + +test("basic Uint16Array", () => { + var a = new Uint16Array(1); + expect(typeof a).toBe("object"); + expect(a instanceof Uint16Array).toBe(true); + expect(a.length).toBe(1); + a[0] = 1; + expect(a[0]).toBe(1); + a[0] -= 2; + expect(a[0]).toBe(0xffff); + ++a[0]; + expect(a[0]).toBe(0); +}); + +test("basic Uint32Array", () => { + var a = new Uint32Array(1); + expect(typeof a).toBe("object"); + expect(a instanceof Uint32Array).toBe(true); + expect(a.length).toBe(1); + a[0] = 1; + expect(a[0]).toBe(1); + a[0] -= 2; + expect(a[0]).toBe(0xffffffff); + ++a[0]; + expect(a[0]).toBe(0); +}); + +test("basic Int8Array", () => { + var a = new Int8Array(1); + expect(typeof a).toBe("object"); + expect(a instanceof Int8Array).toBe(true); + expect(a.length).toBe(1); + a[0] = 1; + expect(a[0]).toBe(1); + a[0] -= 2; + expect(a[0]).toBe(-1); + ++a[0]; + expect(a[0]).toBe(0); + a[0] = 127; + a[0]++; + expect(a[0]).toBe(-128); +}); + +test("basic Int16Array", () => { + var a = new Int16Array(1); + expect(typeof a).toBe("object"); + expect(a instanceof Int16Array).toBe(true); + expect(a.length).toBe(1); + a[0] = 1; + expect(a[0]).toBe(1); + a[0] -= 2; + expect(a[0]).toBe(-1); + ++a[0]; + expect(a[0]).toBe(0); + a[0] = 32767; + a[0]++; + expect(a[0]).toBe(-32768); +}); + +test("basic Int32Array", () => { + var a = new Int32Array(1); + expect(typeof a).toBe("object"); + expect(a instanceof Int32Array).toBe(true); + expect(a.length).toBe(1); + a[0] = 1; + expect(a[0]).toBe(1); + a[0] -= 2; + expect(a[0]).toBe(-1); + ++a[0]; + expect(a[0]).toBe(0); + a[0] = 0x7fffffff; + a[0]++; + expect(a[0]).toBe(-0x80000000); +}); |