OLD | NEW |
| (Empty) |
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/bigint_operations.h" | |
10 #include "vm/exceptions.h" | |
11 #include "vm/native_entry.h" | |
12 #include "vm/object.h" | |
13 | |
14 namespace dart { | |
15 | |
16 // TypedData. | |
17 | |
18 // Checks to see if offset_in_bytes is in the range. | |
19 static bool RangeCheck(intptr_t offset_in_bytes, intptr_t length_in_bytes) { | |
20 return ((offset_in_bytes >= 0) && | |
21 (length_in_bytes > 0) && | |
22 (offset_in_bytes < length_in_bytes)); | |
23 } | |
24 | |
25 | |
26 // Checks to see if offsetInBytes + num_bytes is in the range. | |
27 static void SetRangeCheck(intptr_t offset_in_bytes, | |
28 intptr_t num_bytes, | |
29 intptr_t length_in_bytes, | |
30 intptr_t element_size_in_bytes) { | |
31 if (!Utils::RangeCheck(offset_in_bytes, num_bytes, length_in_bytes)) { | |
32 const String& error = String::Handle(String::NewFormatted( | |
33 "index (%"Pd") must be in the range [0..%"Pd")", | |
34 (offset_in_bytes / element_size_in_bytes), | |
35 (length_in_bytes / element_size_in_bytes))); | |
36 const Array& args = Array::Handle(Array::New(1)); | |
37 args.SetAt(0, error); | |
38 Exceptions::ThrowByType(Exceptions::kRange, args); | |
39 } | |
40 } | |
41 | |
42 | |
43 // Checks to see if a length will not result in an OOM error. | |
44 static void LengthCheck(intptr_t len, intptr_t max) { | |
45 ASSERT(len >= 0); | |
46 if (len > max) { | |
47 const String& error = String::Handle(String::NewFormatted( | |
48 "insufficient memory to allocate a TypedData object of length (%"Pd")", | |
49 len)); | |
50 const Array& args = Array::Handle(Array::New(1)); | |
51 args.SetAt(0, error); | |
52 Exceptions::ThrowByType(Exceptions::kOutOfMemory, args); | |
53 } | |
54 } | |
55 | |
56 | |
57 static void PeerFinalizer(Dart_Handle handle, void* peer) { | |
58 Dart_DeletePersistentHandle(handle); | |
59 OS::AlignedFree(peer); | |
60 } | |
61 | |
62 | |
63 DEFINE_NATIVE_ENTRY(TypedData_length, 1) { | |
64 GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); | |
65 if (instance.IsTypedData()) { | |
66 const TypedData& array = TypedData::Cast(instance); | |
67 return Smi::New(array.Length()); | |
68 } | |
69 if (instance.IsExternalTypedData()) { | |
70 const ExternalTypedData& array = ExternalTypedData::Cast(instance); | |
71 return Smi::New(array.Length()); | |
72 } | |
73 const String& error = String::Handle(String::NewFormatted( | |
74 "Expected a TypedData object but found %s", instance.ToCString())); | |
75 const Array& args = Array::Handle(Array::New(1)); | |
76 args.SetAt(0, error); | |
77 Exceptions::ThrowByType(Exceptions::kArgument, args); | |
78 return Integer::null(); | |
79 } | |
80 | |
81 template <typename DstType, typename SrcType> | |
82 static RawBool* CopyData(const Instance& dst, const Instance& src, | |
83 const Smi& dst_start, const Smi& src_start, | |
84 const Smi& length) { | |
85 const DstType& dst_array = DstType::Cast(dst); | |
86 const SrcType& src_array = SrcType::Cast(src); | |
87 intptr_t element_size_in_bytes = dst_array.ElementSizeInBytes(); | |
88 intptr_t dst_offset_in_bytes = dst_start.Value() * element_size_in_bytes; | |
89 intptr_t src_offset_in_bytes = src_start.Value() * element_size_in_bytes; | |
90 intptr_t length_in_bytes = length.Value() * element_size_in_bytes; | |
91 if (dst_array.ElementType() != src_array.ElementType()) { | |
92 return Bool::False().raw(); | |
93 } | |
94 SetRangeCheck(src_offset_in_bytes, | |
95 length_in_bytes, | |
96 src_array.LengthInBytes(), | |
97 element_size_in_bytes); | |
98 SetRangeCheck(dst_offset_in_bytes, | |
99 length_in_bytes, | |
100 dst_array.LengthInBytes(), | |
101 element_size_in_bytes); | |
102 TypedData::Copy<DstType, SrcType>(dst_array, dst_offset_in_bytes, | |
103 src_array, src_offset_in_bytes, | |
104 length_in_bytes); | |
105 return Bool::True().raw(); | |
106 } | |
107 | |
108 DEFINE_NATIVE_ENTRY(TypedData_setRange, 5) { | |
109 GET_NON_NULL_NATIVE_ARGUMENT(Instance, dst, arguments->NativeArgAt(0)); | |
110 GET_NON_NULL_NATIVE_ARGUMENT(Smi, dst_start, arguments->NativeArgAt(1)); | |
111 GET_NON_NULL_NATIVE_ARGUMENT(Smi, length, arguments->NativeArgAt(2)); | |
112 GET_NON_NULL_NATIVE_ARGUMENT(Instance, src, arguments->NativeArgAt(3)); | |
113 GET_NON_NULL_NATIVE_ARGUMENT(Smi, src_start, arguments->NativeArgAt(4)); | |
114 | |
115 if (length.Value() < 0) { | |
116 const String& error = String::Handle(String::NewFormatted( | |
117 "length (%"Pd") must be non-negative", length.Value())); | |
118 const Array& args = Array::Handle(Array::New(1)); | |
119 args.SetAt(0, error); | |
120 Exceptions::ThrowByType(Exceptions::kArgument, args); | |
121 } | |
122 if (dst.IsTypedData()) { | |
123 if (src.IsTypedData()) { | |
124 return CopyData<TypedData, TypedData>( | |
125 dst, src, dst_start, src_start, length); | |
126 } else if (src.IsExternalTypedData()) { | |
127 return CopyData<TypedData, ExternalTypedData>( | |
128 dst, src, dst_start, src_start, length); | |
129 } | |
130 } else if (dst.IsExternalTypedData()) { | |
131 if (src.IsTypedData()) { | |
132 return CopyData<ExternalTypedData, TypedData>( | |
133 dst, src, dst_start, src_start, length); | |
134 } else if (src.IsExternalTypedData()) { | |
135 return CopyData<ExternalTypedData, ExternalTypedData>( | |
136 dst, src, dst_start, src_start, length); | |
137 } | |
138 } | |
139 return Bool::False().raw(); | |
140 } | |
141 | |
142 | |
143 // We check the length parameter against a possible maximum length for the | |
144 // array based on available physical addressable memory on the system. The | |
145 // maximum possible length is a scaled value of kSmiMax which is set up based | |
146 // on whether the underlying architecture is 32-bit or 64-bit. | |
147 #define TYPED_DATA_NEW(name) \ | |
148 DEFINE_NATIVE_ENTRY(TypedData_##name##_new, 1) { \ | |
149 GET_NON_NULL_NATIVE_ARGUMENT(Smi, length, arguments->NativeArgAt(0)); \ | |
150 intptr_t cid = kTypedData##name##Cid; \ | |
151 intptr_t len = length.Value(); \ | |
152 intptr_t max = TypedData::MaxElements(cid); \ | |
153 LengthCheck(len, max); \ | |
154 return TypedData::New(cid, len); \ | |
155 } \ | |
156 | |
157 | |
158 // We check the length parameter against a possible maximum length for the | |
159 // array based on available physical addressable memory on the system. The | |
160 // maximum possible length is a scaled value of kSmiMax which is set up based | |
161 // on whether the underlying architecture is 32-bit or 64-bit. | |
162 #define EXT_TYPED_DATA_NEW(name) \ | |
163 DEFINE_NATIVE_ENTRY(ExternalTypedData_##name##_new, 1) { \ | |
164 const int kAlignment = 16; \ | |
165 GET_NON_NULL_NATIVE_ARGUMENT(Smi, length, arguments->NativeArgAt(0)); \ | |
166 intptr_t cid = kExternalTypedData##name##Cid; \ | |
167 intptr_t len = length.Value(); \ | |
168 intptr_t max = ExternalTypedData::MaxElements(cid); \ | |
169 LengthCheck(len, max); \ | |
170 intptr_t len_bytes = len * ExternalTypedData::ElementSizeInBytes(cid); \ | |
171 uint8_t* data = OS::AllocateAlignedArray<uint8_t>(len_bytes, kAlignment); \ | |
172 const ExternalTypedData& obj = \ | |
173 ExternalTypedData::Handle(ExternalTypedData::New(cid, data, len)); \ | |
174 obj.AddFinalizer(data, PeerFinalizer); \ | |
175 return obj.raw(); \ | |
176 } \ | |
177 | |
178 | |
179 #define TYPED_DATA_NEW_NATIVE(name) \ | |
180 TYPED_DATA_NEW(name) \ | |
181 EXT_TYPED_DATA_NEW(name) \ | |
182 | |
183 | |
184 CLASS_LIST_TYPED_DATA(TYPED_DATA_NEW_NATIVE) | |
185 | |
186 #define TYPED_DATA_GETTER(getter, object) \ | |
187 DEFINE_NATIVE_ENTRY(TypedData_##getter, 2) { \ | |
188 GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); \ | |
189 GET_NON_NULL_NATIVE_ARGUMENT(Smi, offsetInBytes, arguments->NativeArgAt(1)); \ | |
190 if (instance.IsTypedData()) { \ | |
191 const TypedData& array = TypedData::Cast(instance); \ | |
192 ASSERT(RangeCheck(offsetInBytes.Value(), array.LengthInBytes())); \ | |
193 return object::New(array.getter(offsetInBytes.Value())); \ | |
194 } \ | |
195 if (instance.IsExternalTypedData()) { \ | |
196 const ExternalTypedData& array = ExternalTypedData::Cast(instance); \ | |
197 ASSERT(RangeCheck(offsetInBytes.Value(), array.LengthInBytes())); \ | |
198 return object::New(array.getter(offsetInBytes.Value())); \ | |
199 } \ | |
200 const String& error = String::Handle(String::NewFormatted( \ | |
201 "Expected a TypedData object but found %s", instance.ToCString())); \ | |
202 const Array& args = Array::Handle(Array::New(1)); \ | |
203 args.SetAt(0, error); \ | |
204 Exceptions::ThrowByType(Exceptions::kArgument, args); \ | |
205 return object::null(); \ | |
206 } \ | |
207 | |
208 | |
209 #define TYPED_DATA_SETTER(setter, object, get_object_value) \ | |
210 DEFINE_NATIVE_ENTRY(TypedData_##setter, 3) { \ | |
211 GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); \ | |
212 GET_NON_NULL_NATIVE_ARGUMENT(Smi, offsetInBytes, arguments->NativeArgAt(1)); \ | |
213 GET_NON_NULL_NATIVE_ARGUMENT(object, value, arguments->NativeArgAt(2)); \ | |
214 if (instance.IsTypedData()) { \ | |
215 const TypedData& array = TypedData::Cast(instance); \ | |
216 ASSERT(RangeCheck(offsetInBytes.Value(), array.LengthInBytes())); \ | |
217 array.setter(offsetInBytes.Value(), value.get_object_value()); \ | |
218 } else if (instance.IsExternalTypedData()) { \ | |
219 const ExternalTypedData& array = ExternalTypedData::Cast(instance); \ | |
220 ASSERT(RangeCheck(offsetInBytes.Value(), array.LengthInBytes())); \ | |
221 array.setter(offsetInBytes.Value(), value.get_object_value()); \ | |
222 } else { \ | |
223 const String& error = String::Handle(String::NewFormatted( \ | |
224 "Expected a TypedData object but found %s", instance.ToCString())); \ | |
225 const Array& args = Array::Handle(Array::New(1)); \ | |
226 args.SetAt(0, error); \ | |
227 Exceptions::ThrowByType(Exceptions::kArgument, args); \ | |
228 } \ | |
229 return Object::null(); \ | |
230 } | |
231 | |
232 | |
233 #define TYPED_DATA_UINT64_GETTER(getter, object) \ | |
234 DEFINE_NATIVE_ENTRY(TypedData_##getter, 2) { \ | |
235 GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); \ | |
236 GET_NON_NULL_NATIVE_ARGUMENT(Smi, offsetInBytes, arguments->NativeArgAt(1)); \ | |
237 uint64_t value = 0; \ | |
238 if (instance.IsTypedData()) { \ | |
239 const TypedData& array = TypedData::Cast(instance); \ | |
240 ASSERT(RangeCheck(offsetInBytes.Value(), array.LengthInBytes())); \ | |
241 value = array.getter(offsetInBytes.Value()); \ | |
242 } else if (instance.IsExternalTypedData()) { \ | |
243 const ExternalTypedData& array = ExternalTypedData::Cast(instance); \ | |
244 ASSERT(RangeCheck(offsetInBytes.Value(), array.LengthInBytes())); \ | |
245 value = array.getter(offsetInBytes.Value()); \ | |
246 } else { \ | |
247 const String& error = String::Handle(String::NewFormatted( \ | |
248 "Expected a TypedData object but found %s", instance.ToCString())); \ | |
249 const Array& args = Array::Handle(Array::New(1)); \ | |
250 args.SetAt(0, error); \ | |
251 Exceptions::ThrowByType(Exceptions::kArgument, args); \ | |
252 } \ | |
253 Integer& result = Integer::Handle(); \ | |
254 if (value > static_cast<uint64_t>(Mint::kMaxValue)) { \ | |
255 result = BigintOperations::NewFromUint64(value); \ | |
256 } else if (value > static_cast<uint64_t>(Smi::kMaxValue)) { \ | |
257 result = Mint::New(value); \ | |
258 } else { \ | |
259 result = Smi::New(value); \ | |
260 } \ | |
261 return result.raw(); \ | |
262 } \ | |
263 | |
264 | |
265 // TODO(asiva): Consider truncating the bigint value if it does not fit into | |
266 // a uint64_t value (see ASSERT(BigintOperations::FitsIntoUint64(bigint))). | |
267 #define TYPED_DATA_UINT64_SETTER(setter, object) \ | |
268 DEFINE_NATIVE_ENTRY(TypedData_##setter, 3) { \ | |
269 GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); \ | |
270 GET_NON_NULL_NATIVE_ARGUMENT(Smi, offsetInBytes, arguments->NativeArgAt(1)); \ | |
271 GET_NON_NULL_NATIVE_ARGUMENT(object, value, arguments->NativeArgAt(2)); \ | |
272 uint64_t object_value; \ | |
273 if (value.IsBigint()) { \ | |
274 const Bigint& bigint = Bigint::Cast(value); \ | |
275 ASSERT(BigintOperations::FitsIntoUint64(bigint)); \ | |
276 object_value = BigintOperations::AbsToUint64(bigint); \ | |
277 } else { \ | |
278 ASSERT(value.IsMint() || value.IsSmi()); \ | |
279 object_value = value.AsInt64Value(); \ | |
280 } \ | |
281 if (instance.IsTypedData()) { \ | |
282 const TypedData& array = TypedData::Cast(instance); \ | |
283 ASSERT(RangeCheck(offsetInBytes.Value(), array.LengthInBytes())); \ | |
284 array.setter(offsetInBytes.Value(), object_value); \ | |
285 } else if (instance.IsExternalTypedData()) { \ | |
286 const ExternalTypedData& array = ExternalTypedData::Cast(instance); \ | |
287 ASSERT(RangeCheck(offsetInBytes.Value(), array.LengthInBytes())); \ | |
288 array.setter(offsetInBytes.Value(), object_value); \ | |
289 } else { \ | |
290 const String& error = String::Handle(String::NewFormatted( \ | |
291 "Expected a TypedData object but found %s", instance.ToCString())); \ | |
292 const Array& args = Array::Handle(Array::New(1)); \ | |
293 args.SetAt(0, error); \ | |
294 Exceptions::ThrowByType(Exceptions::kArgument, args); \ | |
295 } \ | |
296 return Object::null(); \ | |
297 } | |
298 | |
299 | |
300 #define TYPED_DATA_NATIVES(getter, setter, object, get_object_value) \ | |
301 TYPED_DATA_GETTER(getter, object) \ | |
302 TYPED_DATA_SETTER(setter, object, get_object_value) \ | |
303 | |
304 | |
305 #define TYPED_DATA_UINT64_NATIVES(getter, setter, object) \ | |
306 TYPED_DATA_UINT64_GETTER(getter, object) \ | |
307 TYPED_DATA_UINT64_SETTER(setter, object) \ | |
308 | |
309 | |
310 TYPED_DATA_NATIVES(GetInt8, SetInt8, Smi, Value) | |
311 TYPED_DATA_NATIVES(GetUint8, SetUint8, Smi, Value) | |
312 TYPED_DATA_NATIVES(GetInt16, SetInt16, Smi, Value) | |
313 TYPED_DATA_NATIVES(GetUint16, SetUint16, Smi, Value) | |
314 TYPED_DATA_NATIVES(GetInt32, SetInt32, Integer, AsInt64Value) | |
315 TYPED_DATA_NATIVES(GetUint32, SetUint32, Integer, AsInt64Value) | |
316 TYPED_DATA_NATIVES(GetInt64, SetInt64, Integer, AsInt64Value) | |
317 TYPED_DATA_UINT64_NATIVES(GetUint64, SetUint64, Integer) | |
318 TYPED_DATA_NATIVES(GetFloat32, SetFloat32, Double, value) | |
319 TYPED_DATA_NATIVES(GetFloat64, SetFloat64, Double, value) | |
320 TYPED_DATA_NATIVES(GetFloat32x4, SetFloat32x4, Float32x4, value) | |
321 | |
322 | |
323 DEFINE_NATIVE_ENTRY(ByteData_ToEndianInt16, 2) { | |
324 GET_NON_NULL_NATIVE_ARGUMENT(Smi, host_value, arguments->NativeArgAt(0)); | |
325 GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1)); | |
326 int16_t value = host_value.Value(); | |
327 if (little_endian.value()) { | |
328 value = Utils::HostToLittleEndian16(value); | |
329 } else { | |
330 value = Utils::HostToBigEndian16(value); | |
331 } | |
332 return Smi::New(value); | |
333 } | |
334 | |
335 | |
336 DEFINE_NATIVE_ENTRY(ByteData_ToEndianUint16, 2) { | |
337 GET_NON_NULL_NATIVE_ARGUMENT(Smi, host_value, arguments->NativeArgAt(0)); | |
338 GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1)); | |
339 uint16_t value = host_value.Value(); | |
340 if (little_endian.value()) { | |
341 return Smi::New(Utils::HostToLittleEndian16(value)); | |
342 } | |
343 return Smi::New(Utils::HostToBigEndian16(value)); | |
344 } | |
345 | |
346 | |
347 DEFINE_NATIVE_ENTRY(ByteData_ToEndianInt32, 2) { | |
348 GET_NON_NULL_NATIVE_ARGUMENT(Integer, host_value, arguments->NativeArgAt(0)); | |
349 GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1)); | |
350 ASSERT(host_value.AsInt64Value() <= kMaxInt32); | |
351 int32_t value = host_value.AsInt64Value(); | |
352 if (little_endian.value()) { | |
353 value = Utils::HostToLittleEndian32(value); | |
354 } else { | |
355 value = Utils::HostToBigEndian32(value); | |
356 } | |
357 return Integer::New(value); | |
358 } | |
359 | |
360 | |
361 DEFINE_NATIVE_ENTRY(ByteData_ToEndianUint32, 2) { | |
362 GET_NON_NULL_NATIVE_ARGUMENT(Integer, host_value, arguments->NativeArgAt(0)); | |
363 GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1)); | |
364 ASSERT(host_value.AsInt64Value() <= kMaxUint32); | |
365 uint32_t value = host_value.AsInt64Value(); | |
366 if (little_endian.value()) { | |
367 value = Utils::HostToLittleEndian32(value); | |
368 } else { | |
369 value = Utils::HostToBigEndian32(value); | |
370 } | |
371 return Integer::New(value); | |
372 } | |
373 | |
374 | |
375 DEFINE_NATIVE_ENTRY(ByteData_ToEndianInt64, 2) { | |
376 GET_NON_NULL_NATIVE_ARGUMENT(Integer, host_value, arguments->NativeArgAt(0)); | |
377 GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1)); | |
378 int64_t value = host_value.AsInt64Value(); | |
379 if (little_endian.value()) { | |
380 value = Utils::HostToLittleEndian64(value); | |
381 } else { | |
382 value = Utils::HostToBigEndian64(value); | |
383 } | |
384 return Integer::New(value); | |
385 } | |
386 | |
387 | |
388 DEFINE_NATIVE_ENTRY(ByteData_ToEndianUint64, 2) { | |
389 GET_NON_NULL_NATIVE_ARGUMENT(Integer, host_value, arguments->NativeArgAt(0)); | |
390 GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1)); | |
391 uint64_t value; | |
392 if (host_value.IsBigint()) { | |
393 const Bigint& bigint = Bigint::Cast(host_value); | |
394 ASSERT(BigintOperations::FitsIntoUint64(bigint)); | |
395 value = BigintOperations::AbsToUint64(bigint); | |
396 } else { | |
397 ASSERT(host_value.IsMint() || host_value.IsSmi()); | |
398 value = host_value.AsInt64Value(); | |
399 } | |
400 if (little_endian.value()) { | |
401 value = Utils::HostToLittleEndian64(value); | |
402 } else { | |
403 value = Utils::HostToBigEndian64(value); | |
404 } | |
405 if (value > static_cast<uint64_t>(Mint::kMaxValue)) { | |
406 return BigintOperations::NewFromUint64(value); | |
407 } else if (value > static_cast<uint64_t>(Smi::kMaxValue)) { | |
408 return Mint::New(value); | |
409 } | |
410 return Smi::New(value); | |
411 } | |
412 | |
413 | |
414 DEFINE_NATIVE_ENTRY(ByteData_ToEndianFloat32, 2) { | |
415 GET_NON_NULL_NATIVE_ARGUMENT(Double, host_value, arguments->NativeArgAt(0)); | |
416 GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1)); | |
417 float value = host_value.value(); | |
418 if (little_endian.value()) { | |
419 value = bit_cast<float>( | |
420 Utils::HostToLittleEndian32(bit_cast<uint32_t>(value))); | |
421 } else { | |
422 value = bit_cast<float>( | |
423 Utils::HostToBigEndian32(bit_cast<uint32_t>(value))); | |
424 } | |
425 return Double::New(value); | |
426 } | |
427 | |
428 | |
429 DEFINE_NATIVE_ENTRY(ByteData_ToEndianFloat64, 2) { | |
430 GET_NON_NULL_NATIVE_ARGUMENT(Double, host_value, arguments->NativeArgAt(0)); | |
431 GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1)); | |
432 double value = host_value.value(); | |
433 if (little_endian.value()) { | |
434 value = bit_cast<double>( | |
435 Utils::HostToLittleEndian64(bit_cast<uint64_t>(value))); | |
436 } else { | |
437 value = bit_cast<double>( | |
438 Utils::HostToBigEndian64(bit_cast<uint64_t>(value))); | |
439 } | |
440 return Double::New(value); | |
441 } | |
442 | |
443 } // namespace dart | |
OLD | NEW |