1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "vm/bootstrap_natives.h" |
6 | |
7 | #include "include/dart_api.h" |
8 | |
9 | #include "vm/exceptions.h" |
10 | #include "vm/native_entry.h" |
11 | #include "vm/object.h" |
12 | #include "vm/object_store.h" |
13 | |
14 | namespace dart { |
15 | |
16 | // TypedData. |
17 | |
18 | // Checks to see if offsetInBytes + num_bytes is in the range. |
19 | static void RangeCheck(intptr_t offset_in_bytes, |
20 | intptr_t access_size, |
21 | intptr_t length_in_bytes, |
22 | intptr_t element_size_in_bytes) { |
23 | if (!Utils::RangeCheck(offset_in_bytes, access_size, length_in_bytes)) { |
24 | const intptr_t index = |
25 | (offset_in_bytes + access_size) / element_size_in_bytes; |
26 | const intptr_t length = length_in_bytes / element_size_in_bytes; |
27 | Exceptions::ThrowRangeError("index" , Integer::Handle(Integer::New(index)), |
28 | 0, length); |
29 | } |
30 | } |
31 | |
32 | static void AlignmentCheck(intptr_t offset_in_bytes, intptr_t element_size) { |
33 | if ((offset_in_bytes % element_size) != 0) { |
34 | const auto& error = String::Handle(String::NewFormatted( |
35 | "Offset in bytes (%" Pd ") must be a multiple of %" Pd "" , |
36 | offset_in_bytes, element_size)); |
37 | Exceptions::ThrowArgumentError(error); |
38 | } |
39 | } |
40 | |
41 | // Checks to see if a length will not result in an OOM error. |
42 | static void LengthCheck(intptr_t len, intptr_t max) { |
43 | if (len < 0 || len > max) { |
44 | const String& error = String::Handle(String::NewFormatted( |
45 | "Length (%" Pd ") of object must be in range [0..%" Pd "]" , len, max)); |
46 | Exceptions::ThrowArgumentError(error); |
47 | } |
48 | } |
49 | |
50 | DEFINE_NATIVE_ENTRY(TypedData_length, 0, 1) { |
51 | GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); |
52 | if (instance.IsTypedData()) { |
53 | const TypedData& array = TypedData::Cast(instance); |
54 | return Smi::New(array.Length()); |
55 | } |
56 | if (instance.IsExternalTypedData()) { |
57 | const ExternalTypedData& array = ExternalTypedData::Cast(instance); |
58 | return Smi::New(array.Length()); |
59 | } |
60 | const String& error = String::Handle(String::NewFormatted( |
61 | "Expected a TypedData object but found %s" , instance.ToCString())); |
62 | Exceptions::ThrowArgumentError(error); |
63 | return Integer::null(); |
64 | } |
65 | |
66 | DEFINE_NATIVE_ENTRY(TypedDataView_offsetInBytes, 0, 1) { |
67 | // "this" is either a _*ArrayView class or _ByteDataView. |
68 | GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); |
69 | ASSERT(instance.IsTypedDataView()); |
70 | return TypedDataView::Cast(instance).offset_in_bytes(); |
71 | } |
72 | |
73 | DEFINE_NATIVE_ENTRY(TypedDataView_length, 0, 1) { |
74 | // "this" is either a _*ArrayView class or _ByteDataView. |
75 | GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); |
76 | ASSERT(instance.IsTypedDataView()); |
77 | return TypedDataView::Cast(instance).length(); |
78 | } |
79 | |
80 | DEFINE_NATIVE_ENTRY(TypedDataView_typedData, 0, 1) { |
81 | // "this" is either a _*ArrayView class or _ByteDataView. |
82 | GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); |
83 | ASSERT(instance.IsTypedDataView()); |
84 | return TypedDataView::Cast(instance).typed_data(); |
85 | } |
86 | |
87 | template <typename DstType, typename SrcType> |
88 | static BoolPtr CopyData(const Instance& dst, |
89 | const Instance& src, |
90 | const Smi& dst_start, |
91 | const Smi& src_start, |
92 | const Smi& length, |
93 | bool clamped) { |
94 | const DstType& dst_array = DstType::Cast(dst); |
95 | const SrcType& src_array = SrcType::Cast(src); |
96 | const intptr_t dst_offset_in_bytes = dst_start.Value(); |
97 | const intptr_t src_offset_in_bytes = src_start.Value(); |
98 | const intptr_t length_in_bytes = length.Value(); |
99 | ASSERT(Utils::RangeCheck(src_offset_in_bytes, length_in_bytes, |
100 | src_array.LengthInBytes())); |
101 | ASSERT(Utils::RangeCheck(dst_offset_in_bytes, length_in_bytes, |
102 | dst_array.LengthInBytes())); |
103 | if (clamped) { |
104 | TypedData::ClampedCopy<DstType, SrcType>(dst_array, dst_offset_in_bytes, |
105 | src_array, src_offset_in_bytes, |
106 | length_in_bytes); |
107 | } else { |
108 | TypedData::Copy<DstType, SrcType>(dst_array, dst_offset_in_bytes, src_array, |
109 | src_offset_in_bytes, length_in_bytes); |
110 | } |
111 | return Bool::True().raw(); |
112 | } |
113 | |
114 | static bool IsClamped(intptr_t cid) { |
115 | switch (cid) { |
116 | case kTypedDataUint8ClampedArrayCid: |
117 | case kExternalTypedDataUint8ClampedArrayCid: |
118 | case kTypedDataUint8ClampedArrayViewCid: |
119 | return true; |
120 | default: |
121 | return false; |
122 | } |
123 | } |
124 | |
125 | static bool IsUint8(intptr_t cid) { |
126 | switch (cid) { |
127 | case kTypedDataUint8ClampedArrayCid: |
128 | case kExternalTypedDataUint8ClampedArrayCid: |
129 | case kTypedDataUint8ClampedArrayViewCid: |
130 | case kTypedDataUint8ArrayCid: |
131 | case kExternalTypedDataUint8ArrayCid: |
132 | case kTypedDataUint8ArrayViewCid: |
133 | return true; |
134 | default: |
135 | return false; |
136 | } |
137 | } |
138 | |
139 | DEFINE_NATIVE_ENTRY(TypedData_setRange, 0, 7) { |
140 | const Instance& dst = |
141 | Instance::CheckedHandle(zone, arguments->NativeArgAt(0)); |
142 | const Smi& dst_start = Smi::CheckedHandle(zone, arguments->NativeArgAt(1)); |
143 | const Smi& length = Smi::CheckedHandle(zone, arguments->NativeArgAt(2)); |
144 | const Instance& src = |
145 | Instance::CheckedHandle(zone, arguments->NativeArgAt(3)); |
146 | const Smi& src_start = Smi::CheckedHandle(zone, arguments->NativeArgAt(4)); |
147 | const Smi& to_cid_smi = Smi::CheckedHandle(zone, arguments->NativeArgAt(5)); |
148 | const Smi& from_cid_smi = Smi::CheckedHandle(zone, arguments->NativeArgAt(6)); |
149 | |
150 | if (length.Value() < 0) { |
151 | const String& error = String::Handle(String::NewFormatted( |
152 | "length (%" Pd ") must be non-negative" , length.Value())); |
153 | Exceptions::ThrowArgumentError(error); |
154 | } |
155 | const intptr_t to_cid = to_cid_smi.Value(); |
156 | const intptr_t from_cid = from_cid_smi.Value(); |
157 | |
158 | const bool needs_clamping = IsClamped(to_cid) && !IsUint8(from_cid); |
159 | if (dst.IsTypedData()) { |
160 | if (src.IsTypedData()) { |
161 | return CopyData<TypedData, TypedData>(dst, src, dst_start, src_start, |
162 | length, needs_clamping); |
163 | } else if (src.IsExternalTypedData()) { |
164 | return CopyData<TypedData, ExternalTypedData>( |
165 | dst, src, dst_start, src_start, length, needs_clamping); |
166 | } |
167 | } else if (dst.IsExternalTypedData()) { |
168 | if (src.IsTypedData()) { |
169 | return CopyData<ExternalTypedData, TypedData>( |
170 | dst, src, dst_start, src_start, length, needs_clamping); |
171 | } else if (src.IsExternalTypedData()) { |
172 | return CopyData<ExternalTypedData, ExternalTypedData>( |
173 | dst, src, dst_start, src_start, length, needs_clamping); |
174 | } |
175 | } |
176 | UNREACHABLE(); |
177 | return Bool::False().raw(); |
178 | } |
179 | |
180 | // We check the length parameter against a possible maximum length for the |
181 | // array based on available physical addressable memory on the system. |
182 | // |
183 | // More specifically |
184 | // |
185 | // TypedData::MaxElements(cid) is equal to (kSmiMax / ElementSizeInBytes(cid)) |
186 | // |
187 | // which ensures that the number of bytes the array holds is guaranteed to fit |
188 | // into a _Smi. |
189 | // |
190 | // Argument 0 is type arguments and is ignored. |
191 | #define TYPED_DATA_NEW(name) \ |
192 | DEFINE_NATIVE_ENTRY(TypedData_##name##_new, 0, 2) { \ |
193 | GET_NON_NULL_NATIVE_ARGUMENT(Integer, length, arguments->NativeArgAt(1)); \ |
194 | const intptr_t cid = kTypedData##name##Cid; \ |
195 | const intptr_t max = TypedData::MaxElements(cid); \ |
196 | const int64_t len = length.AsInt64Value(); \ |
197 | if (len < 0) { \ |
198 | Exceptions::ThrowRangeError("length", length, 0, max); \ |
199 | } else if (len > max) { \ |
200 | const Instance& exception = Instance::Handle( \ |
201 | zone, thread->isolate()->object_store()->out_of_memory()); \ |
202 | Exceptions::Throw(thread, exception); \ |
203 | } \ |
204 | return TypedData::New(cid, static_cast<intptr_t>(len)); \ |
205 | } |
206 | |
207 | #define TYPED_DATA_NEW_NATIVE(name) TYPED_DATA_NEW(name) |
208 | |
209 | CLASS_LIST_TYPED_DATA(TYPED_DATA_NEW_NATIVE) |
210 | #undef TYPED_DATA_NEW_NATIVE |
211 | #undef TYPED_DATA_NEW |
212 | |
213 | #define TYPED_DATA_VIEW_NEW(native_name, cid) \ |
214 | DEFINE_NATIVE_ENTRY(native_name, 0, 4) { \ |
215 | GET_NON_NULL_NATIVE_ARGUMENT(TypedDataBase, typed_data, \ |
216 | arguments->NativeArgAt(1)); \ |
217 | GET_NON_NULL_NATIVE_ARGUMENT(Smi, offset, arguments->NativeArgAt(2)); \ |
218 | GET_NON_NULL_NATIVE_ARGUMENT(Smi, len, arguments->NativeArgAt(3)); \ |
219 | const intptr_t backing_length = typed_data.LengthInBytes(); \ |
220 | const intptr_t offset_in_bytes = offset.Value(); \ |
221 | const intptr_t length = len.Value(); \ |
222 | const intptr_t element_size = TypedDataBase::ElementSizeInBytes(cid); \ |
223 | AlignmentCheck(offset_in_bytes, element_size); \ |
224 | LengthCheck(offset_in_bytes + length * element_size, backing_length); \ |
225 | return TypedDataView::New(cid, typed_data, offset_in_bytes, length); \ |
226 | } |
227 | |
228 | #define TYPED_DATA_NEW_NATIVE(name) \ |
229 | TYPED_DATA_VIEW_NEW(TypedDataView_##name##View_new, kTypedData##name##ViewCid) |
230 | |
231 | CLASS_LIST_TYPED_DATA(TYPED_DATA_NEW_NATIVE) |
232 | TYPED_DATA_VIEW_NEW(TypedDataView_ByteDataView_new, kByteDataViewCid) |
233 | #undef TYPED_DATA_NEW_NATIVE |
234 | #undef TYPED_DATA_VIEW_NEW |
235 | |
236 | #define TYPED_DATA_GETTER(getter, object, ctor, access_size) \ |
237 | DEFINE_NATIVE_ENTRY(TypedData_##getter, 0, 2) { \ |
238 | GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, \ |
239 | arguments->NativeArgAt(0)); \ |
240 | GET_NON_NULL_NATIVE_ARGUMENT(Smi, offsetInBytes, \ |
241 | arguments->NativeArgAt(1)); \ |
242 | if (instance.IsTypedData()) { \ |
243 | const TypedData& array = TypedData::Cast(instance); \ |
244 | RangeCheck(offsetInBytes.Value(), access_size, array.LengthInBytes(), \ |
245 | access_size); \ |
246 | return object::ctor(array.getter(offsetInBytes.Value())); \ |
247 | } \ |
248 | if (instance.IsExternalTypedData()) { \ |
249 | const ExternalTypedData& array = ExternalTypedData::Cast(instance); \ |
250 | RangeCheck(offsetInBytes.Value(), access_size, array.LengthInBytes(), \ |
251 | access_size); \ |
252 | return object::ctor(array.getter(offsetInBytes.Value())); \ |
253 | } \ |
254 | const String& error = String::Handle(String::NewFormatted( \ |
255 | "Expected a TypedData object but found %s", instance.ToCString())); \ |
256 | Exceptions::ThrowArgumentError(error); \ |
257 | return object::null(); \ |
258 | } |
259 | |
260 | #define TYPED_DATA_SETTER(setter, object, get_object_value, access_size, \ |
261 | access_type) \ |
262 | DEFINE_NATIVE_ENTRY(TypedData_##setter, 0, 3) { \ |
263 | GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, \ |
264 | arguments->NativeArgAt(0)); \ |
265 | GET_NON_NULL_NATIVE_ARGUMENT(Smi, offsetInBytes, \ |
266 | arguments->NativeArgAt(1)); \ |
267 | GET_NON_NULL_NATIVE_ARGUMENT(object, value, arguments->NativeArgAt(2)); \ |
268 | if (instance.IsTypedData()) { \ |
269 | const TypedData& array = TypedData::Cast(instance); \ |
270 | RangeCheck(offsetInBytes.Value(), access_size, array.LengthInBytes(), \ |
271 | access_size); \ |
272 | array.setter(offsetInBytes.Value(), \ |
273 | static_cast<access_type>(value.get_object_value())); \ |
274 | } else if (instance.IsExternalTypedData()) { \ |
275 | const ExternalTypedData& array = ExternalTypedData::Cast(instance); \ |
276 | RangeCheck(offsetInBytes.Value(), access_size, array.LengthInBytes(), \ |
277 | access_size); \ |
278 | array.setter(offsetInBytes.Value(), \ |
279 | static_cast<access_type>(value.get_object_value())); \ |
280 | } else { \ |
281 | const String& error = String::Handle(String::NewFormatted( \ |
282 | "Expected a TypedData object but found %s", instance.ToCString())); \ |
283 | Exceptions::ThrowArgumentError(error); \ |
284 | } \ |
285 | return Object::null(); \ |
286 | } |
287 | |
288 | #define TYPED_DATA_NATIVES(type_name, object, ctor, get_object_value, \ |
289 | access_size, access_type) \ |
290 | TYPED_DATA_GETTER(Get##type_name, object, ctor, access_size) \ |
291 | TYPED_DATA_SETTER(Set##type_name, object, get_object_value, access_size, \ |
292 | access_type) |
293 | |
294 | TYPED_DATA_NATIVES(Int8, Integer, New, AsTruncatedUint32Value, 1, int8_t) |
295 | TYPED_DATA_NATIVES(Uint8, Integer, New, AsTruncatedUint32Value, 1, uint8_t) |
296 | TYPED_DATA_NATIVES(Int16, Integer, New, AsTruncatedUint32Value, 2, int16_t) |
297 | TYPED_DATA_NATIVES(Uint16, Integer, New, AsTruncatedUint32Value, 2, uint16_t) |
298 | TYPED_DATA_NATIVES(Int32, Integer, New, AsTruncatedUint32Value, 4, int32_t) |
299 | TYPED_DATA_NATIVES(Uint32, Integer, New, AsTruncatedUint32Value, 4, uint32_t) |
300 | TYPED_DATA_NATIVES(Int64, Integer, New, AsTruncatedInt64Value, 8, int64_t) |
301 | TYPED_DATA_NATIVES(Uint64, |
302 | Integer, |
303 | NewFromUint64, |
304 | AsTruncatedInt64Value, |
305 | 8, |
306 | uint64_t) |
307 | TYPED_DATA_NATIVES(Float32, Double, New, value, 4, float) |
308 | TYPED_DATA_NATIVES(Float64, Double, New, value, 8, double) |
309 | TYPED_DATA_NATIVES(Float32x4, Float32x4, New, value, 16, simd128_value_t) |
310 | TYPED_DATA_NATIVES(Int32x4, Int32x4, New, value, 16, simd128_value_t) |
311 | TYPED_DATA_NATIVES(Float64x2, Float64x2, New, value, 16, simd128_value_t) |
312 | |
313 | } // namespace dart |
314 | |