Skip to main content

pipa/builtins/
typedarray.rs

1use crate::host::HostFunction;
2use crate::object::object::{JSObject, ObjectType, TypedArrayKind};
3use crate::runtime::context::JSContext;
4use crate::value::JSValue;
5
6#[derive(Debug)]
7pub struct ArrayBufferData {
8    pub data: Vec<u8>,
9}
10
11fn create_array_buffer(ctx: &mut JSContext, length: usize) -> JSValue {
12    let mut obj = JSObject::new_typed(ObjectType::ArrayBuffer);
13    let data = ArrayBufferData {
14        data: vec![0; length],
15    };
16
17    obj.set(ctx.intern("byteLength"), JSValue::new_int(length as i64));
18
19    let data_ptr = Box::into_raw(Box::new(data));
20    obj.set_array_buffer_data(data_ptr as usize);
21    let ptr = Box::into_raw(Box::new(obj)) as usize;
22    JSValue::new_object(ptr)
23}
24
25fn get_array_buffer_data(obj: &JSObject) -> Option<&mut ArrayBufferData> {
26    obj.get_array_buffer_data()
27        .map(|ptr| unsafe { &mut *(ptr as *mut ArrayBufferData) })
28}
29
30fn create_typed_array(
31    ctx: &mut JSContext,
32    kind: TypedArrayKind,
33    buffer: JSValue,
34    byte_offset: usize,
35    length: Option<usize>,
36) -> Result<JSValue, String> {
37    if !buffer.is_object() {
38        return Err("TypedArray constructor requires ArrayBuffer".to_string());
39    }
40
41    let buffer_obj = buffer.as_object();
42    let buffer_data = get_array_buffer_data(&buffer_obj).ok_or("Invalid ArrayBuffer")?;
43
44    let bytes_per_element = kind.bytes_per_element();
45
46    if byte_offset % bytes_per_element != 0 {
47        return Err(format!(
48            "byteOffset must be a multiple of {}",
49            bytes_per_element
50        ));
51    }
52
53    let byte_length = buffer_data.data.len();
54    let remaining_bytes = byte_length.saturating_sub(byte_offset);
55
56    let element_length = match length {
57        Some(len) => len,
58        None => remaining_bytes / bytes_per_element,
59    };
60
61    let required_bytes = byte_offset + element_length * bytes_per_element;
62    if required_bytes > byte_length {
63        return Err("TypedArray extends beyond ArrayBuffer bounds".to_string());
64    }
65
66    let mut obj = JSObject::new_typed(ObjectType::TypedArray);
67    obj.set_typed_array_kind(kind);
68
69    obj.set(ctx.intern("buffer"), buffer);
70    obj.set(
71        ctx.intern("byteOffset"),
72        JSValue::new_int(byte_offset as i64),
73    );
74    obj.set(
75        ctx.intern("byteLength"),
76        JSValue::new_int((element_length * bytes_per_element) as i64),
77    );
78    obj.set(
79        ctx.intern("length"),
80        JSValue::new_int(element_length as i64),
81    );
82
83    let ptr = Box::into_raw(Box::new(obj)) as usize;
84    Ok(JSValue::new_object(ptr))
85}
86
87fn typed_array_from_args(
88    ctx: &mut JSContext,
89    kind: TypedArrayKind,
90    args: &[JSValue],
91) -> Result<JSValue, String> {
92    if args.is_empty() {
93        let buffer = create_array_buffer(ctx, 0);
94        return create_typed_array(ctx, kind, buffer, 0, Some(0));
95    }
96
97    let first_arg = &args[0];
98
99    if first_arg.is_object() {
100        let obj = first_arg.as_object();
101
102        if obj.obj_type() == ObjectType::ArrayBuffer {
103            let byte_offset = if args.len() > 1 {
104                args[1].get_int() as usize
105            } else {
106                0
107            };
108            let length = if args.len() > 2 {
109                Some(args[2].get_int() as usize)
110            } else {
111                None
112            };
113            return create_typed_array(ctx, kind, *first_arg, byte_offset, length);
114        }
115
116        if obj.obj_type() == ObjectType::TypedArray {
117            let src_len = obj
118                .get(ctx.intern("length"))
119                .map(|v| v.get_int() as usize)
120                .unwrap_or(0);
121
122            let bytes_per_element = kind.bytes_per_element();
123            let buffer = create_array_buffer(ctx, src_len * bytes_per_element);
124            let result = create_typed_array(ctx, kind, buffer, 0, Some(src_len))?;
125
126            return Ok(result);
127        }
128
129        let len = obj.get_array_elements().map(|e| e.len()).unwrap_or(0);
130
131        let bytes_per_element = kind.bytes_per_element();
132        let buffer = create_array_buffer(ctx, len * bytes_per_element);
133        let result = create_typed_array(ctx, kind, buffer, 0, Some(len))?;
134
135        return Ok(result);
136    }
137
138    if first_arg.is_int() || first_arg.is_float() {
139        let length = first_arg.get_int() as usize;
140        let bytes_per_element = kind.bytes_per_element();
141        let buffer = create_array_buffer(ctx, length * bytes_per_element);
142        return create_typed_array(ctx, kind, buffer, 0, Some(length));
143    }
144
145    Err("Invalid TypedArray constructor argument".to_string())
146}
147
148fn array_buffer_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
149    let length = if args.is_empty() {
150        0
151    } else {
152        args[0].get_int() as usize
153    };
154    create_array_buffer(ctx, length)
155}
156
157fn data_view_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
158    if args.is_empty() || !args[0].is_object() {
159        return JSValue::undefined();
160    }
161
162    let buffer = &args[0];
163    let byte_offset = if args.len() > 1 {
164        args[1].get_int() as usize
165    } else {
166        0
167    };
168
169    let mut obj = JSObject::new_typed(ObjectType::DataView);
170    obj.set(ctx.intern("buffer"), *buffer);
171    obj.set(
172        ctx.intern("byteOffset"),
173        JSValue::new_int(byte_offset as i64),
174    );
175
176    if let Some(buffer_obj) = if buffer.is_object() {
177        Some(buffer.as_object())
178    } else {
179        None
180    } {
181        if let Some(data) = get_array_buffer_data(&buffer_obj) {
182            let byte_length = if args.len() > 2 {
183                args[2].get_int() as usize
184            } else {
185                data.data.len().saturating_sub(byte_offset)
186            };
187            obj.set(
188                ctx.intern("byteLength"),
189                JSValue::new_int(byte_length as i64),
190            );
191        }
192    }
193
194    let ptr = Box::into_raw(Box::new(obj)) as usize;
195    JSValue::new_object(ptr)
196}
197
198fn int8_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
199    typed_array_from_args(ctx, TypedArrayKind::Int8, args).unwrap_or(JSValue::undefined())
200}
201
202fn uint8_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
203    typed_array_from_args(ctx, TypedArrayKind::Uint8, args).unwrap_or(JSValue::undefined())
204}
205
206fn uint8_clamped_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
207    typed_array_from_args(ctx, TypedArrayKind::Uint8Clamped, args).unwrap_or(JSValue::undefined())
208}
209
210fn int16_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
211    typed_array_from_args(ctx, TypedArrayKind::Int16, args).unwrap_or(JSValue::undefined())
212}
213
214fn uint16_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
215    typed_array_from_args(ctx, TypedArrayKind::Uint16, args).unwrap_or(JSValue::undefined())
216}
217
218fn int32_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
219    typed_array_from_args(ctx, TypedArrayKind::Int32, args).unwrap_or(JSValue::undefined())
220}
221
222fn uint32_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
223    typed_array_from_args(ctx, TypedArrayKind::Uint32, args).unwrap_or(JSValue::undefined())
224}
225
226fn float32_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
227    typed_array_from_args(ctx, TypedArrayKind::Float32, args).unwrap_or(JSValue::undefined())
228}
229
230fn float64_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
231    typed_array_from_args(ctx, TypedArrayKind::Float64, args).unwrap_or(JSValue::undefined())
232}
233
234fn bigint64_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
235    typed_array_from_args(ctx, TypedArrayKind::BigInt64, args).unwrap_or(JSValue::undefined())
236}
237
238fn biguint64_array_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
239    typed_array_from_args(ctx, TypedArrayKind::BigUint64, args).unwrap_or(JSValue::undefined())
240}
241
242fn typed_array_get_buffer(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
243    if args.is_empty() || !args[0].is_object() {
244        return JSValue::undefined();
245    }
246    let obj = args[0].as_object();
247    obj.get(ctx.intern("buffer"))
248        .unwrap_or(JSValue::undefined())
249}
250
251fn typed_array_get_byte_length(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
252    if args.is_empty() || !args[0].is_object() {
253        return JSValue::undefined();
254    }
255    let obj = args[0].as_object();
256    obj.get(ctx.intern("byteLength"))
257        .unwrap_or(JSValue::undefined())
258}
259
260fn typed_array_get_byte_offset(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
261    if args.is_empty() || !args[0].is_object() {
262        return JSValue::undefined();
263    }
264    let obj = args[0].as_object();
265    obj.get(ctx.intern("byteOffset"))
266        .unwrap_or(JSValue::undefined())
267}
268
269fn typed_array_get_length(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
270    if args.is_empty() || !args[0].is_object() {
271        return JSValue::undefined();
272    }
273    let obj = args[0].as_object();
274    obj.get(ctx.intern("length"))
275        .unwrap_or(JSValue::undefined())
276}
277
278fn array_buffer_get_byte_length(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
279    if args.is_empty() || !args[0].is_object() {
280        return JSValue::undefined();
281    }
282    let obj = args[0].as_object();
283    obj.get(ctx.intern("byteLength"))
284        .unwrap_or(JSValue::undefined())
285}
286
287fn data_view_get_buffer(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
288    if args.is_empty() || !args[0].is_object() {
289        return JSValue::undefined();
290    }
291    let obj = args[0].as_object();
292    obj.get(ctx.intern("buffer"))
293        .unwrap_or(JSValue::undefined())
294}
295
296fn data_view_get_byte_length(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
297    if args.is_empty() || !args[0].is_object() {
298        return JSValue::undefined();
299    }
300    let obj = args[0].as_object();
301    obj.get(ctx.intern("byteLength"))
302        .unwrap_or(JSValue::undefined())
303}
304
305fn data_view_get_byte_offset(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
306    if args.is_empty() || !args[0].is_object() {
307        return JSValue::undefined();
308    }
309    let obj = args[0].as_object();
310    obj.get(ctx.intern("byteOffset"))
311        .unwrap_or(JSValue::undefined())
312}
313
314fn data_view_get_int8(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
315    if args.len() < 2 {
316        return JSValue::undefined();
317    }
318    let view = &args[0];
319    let offset = args[1].get_int() as usize;
320
321    if !view.is_object() {
322        return JSValue::undefined();
323    }
324
325    let view_obj = view.as_object();
326    let buffer = view_obj.get(ctx.intern("buffer"));
327    let byte_offset = view_obj
328        .get(ctx.intern("byteOffset"))
329        .map(|v| v.get_int() as usize)
330        .unwrap_or(0);
331
332    if let Some(buf) = buffer {
333        if let Some(buf_obj) = if buf.is_object() {
334            Some(buf.as_object())
335        } else {
336            None
337        } {
338            if let Some(data) = get_array_buffer_data(&buf_obj) {
339                let idx = byte_offset + offset;
340                if idx < data.data.len() {
341                    return JSValue::new_int(data.data[idx] as i8 as i64);
342                }
343            }
344        }
345    }
346    JSValue::undefined()
347}
348
349fn data_view_get_uint8(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
350    if args.len() < 2 {
351        return JSValue::undefined();
352    }
353    let view = &args[0];
354    let offset = args[1].get_int() as usize;
355
356    if !view.is_object() {
357        return JSValue::undefined();
358    }
359
360    let view_obj = view.as_object();
361    let buffer = view_obj.get(ctx.intern("buffer"));
362    let byte_offset = view_obj
363        .get(ctx.intern("byteOffset"))
364        .map(|v| v.get_int() as usize)
365        .unwrap_or(0);
366
367    if let Some(buf) = buffer {
368        if let Some(buf_obj) = if buf.is_object() {
369            Some(buf.as_object())
370        } else {
371            None
372        } {
373            if let Some(data) = get_array_buffer_data(&buf_obj) {
374                let idx = byte_offset + offset;
375                if idx < data.data.len() {
376                    return JSValue::new_int(data.data[idx] as i64);
377                }
378            }
379        }
380    }
381    JSValue::undefined()
382}
383
384fn data_view_set_int8(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
385    if args.len() < 3 {
386        return JSValue::undefined();
387    }
388    let view = &args[0];
389    let offset = args[1].get_int() as usize;
390    let value = args[2].get_int() as i8 as u8;
391
392    if !view.is_object() {
393        return JSValue::undefined();
394    }
395
396    let view_obj = view.as_object();
397    let buffer = view_obj.get(ctx.intern("buffer"));
398    let byte_offset = view_obj
399        .get(ctx.intern("byteOffset"))
400        .map(|v| v.get_int() as usize)
401        .unwrap_or(0);
402
403    if let Some(buf) = buffer {
404        if let Some(buf_obj) = if buf.is_object() {
405            Some(buf.as_object())
406        } else {
407            None
408        } {
409            if let Some(data) = get_array_buffer_data(&buf_obj) {
410                let idx = byte_offset + offset;
411                if idx < data.data.len() {
412                    data.data[idx] = value;
413                }
414            }
415        }
416    }
417    JSValue::undefined()
418}
419
420fn data_view_set_uint8(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
421    if args.len() < 3 {
422        return JSValue::undefined();
423    }
424    let view = &args[0];
425    let offset = args[1].get_int() as usize;
426    let value = args[2].get_int() as u8;
427
428    if !view.is_object() {
429        return JSValue::undefined();
430    }
431
432    let view_obj = view.as_object();
433    let buffer = view_obj.get(ctx.intern("buffer"));
434    let byte_offset = view_obj
435        .get(ctx.intern("byteOffset"))
436        .map(|v| v.get_int() as usize)
437        .unwrap_or(0);
438
439    if let Some(buf) = buffer {
440        if let Some(buf_obj) = if buf.is_object() {
441            Some(buf.as_object())
442        } else {
443            None
444        } {
445            if let Some(data) = get_array_buffer_data(&buf_obj) {
446                let idx = byte_offset + offset;
447                if idx < data.data.len() {
448                    data.data[idx] = value;
449                }
450            }
451        }
452    }
453    JSValue::undefined()
454}
455
456fn create_builtin_function(ctx: &mut JSContext, name: &str) -> JSValue {
457    let mut func = crate::object::function::JSFunction::new_builtin(ctx.intern(name), 1);
458    func.set_builtin_marker(ctx, name);
459    let ptr = Box::into_raw(Box::new(func)) as usize;
460    ctx.runtime_mut().gc_heap_mut().track_function(ptr);
461    JSValue::new_function(ptr)
462}
463
464pub fn init_typed_array(ctx: &mut JSContext) {
465    let global = ctx.global();
466    if !global.is_object() {
467        return;
468    }
469    let global_obj = global.as_object_mut();
470
471    global_obj.set(
472        ctx.intern("ArrayBuffer"),
473        create_builtin_function(ctx, "ArrayBuffer"),
474    );
475
476    global_obj.set(
477        ctx.intern("DataView"),
478        create_builtin_function(ctx, "DataView"),
479    );
480
481    global_obj.set(
482        ctx.intern("Int8Array"),
483        create_builtin_function(ctx, "Int8Array"),
484    );
485    global_obj.set(
486        ctx.intern("Uint8Array"),
487        create_builtin_function(ctx, "Uint8Array"),
488    );
489    global_obj.set(
490        ctx.intern("Uint8ClampedArray"),
491        create_builtin_function(ctx, "Uint8ClampedArray"),
492    );
493    global_obj.set(
494        ctx.intern("Int16Array"),
495        create_builtin_function(ctx, "Int16Array"),
496    );
497    global_obj.set(
498        ctx.intern("Uint16Array"),
499        create_builtin_function(ctx, "Uint16Array"),
500    );
501    global_obj.set(
502        ctx.intern("Int32Array"),
503        create_builtin_function(ctx, "Int32Array"),
504    );
505    global_obj.set(
506        ctx.intern("Uint32Array"),
507        create_builtin_function(ctx, "Uint32Array"),
508    );
509    global_obj.set(
510        ctx.intern("Float32Array"),
511        create_builtin_function(ctx, "Float32Array"),
512    );
513    global_obj.set(
514        ctx.intern("Float64Array"),
515        create_builtin_function(ctx, "Float64Array"),
516    );
517    global_obj.set(
518        ctx.intern("BigInt64Array"),
519        create_builtin_function(ctx, "BigInt64Array"),
520    );
521    global_obj.set(
522        ctx.intern("BigUint64Array"),
523        create_builtin_function(ctx, "BigUint64Array"),
524    );
525}
526
527pub fn register_builtins(ctx: &mut JSContext) {
528    ctx.register_builtin(
529        "ArrayBuffer",
530        HostFunction::new("ArrayBuffer", 1, array_buffer_constructor),
531    );
532    ctx.register_builtin(
533        "DataView",
534        HostFunction::new("DataView", 1, data_view_constructor),
535    );
536
537    ctx.register_builtin(
538        "Int8Array",
539        HostFunction::new("Int8Array", 1, int8_array_constructor),
540    );
541    ctx.register_builtin(
542        "Uint8Array",
543        HostFunction::new("Uint8Array", 1, uint8_array_constructor),
544    );
545    ctx.register_builtin(
546        "Uint8ClampedArray",
547        HostFunction::new("Uint8ClampedArray", 1, uint8_clamped_array_constructor),
548    );
549    ctx.register_builtin(
550        "Int16Array",
551        HostFunction::new("Int16Array", 1, int16_array_constructor),
552    );
553    ctx.register_builtin(
554        "Uint16Array",
555        HostFunction::new("Uint16Array", 1, uint16_array_constructor),
556    );
557    ctx.register_builtin(
558        "Int32Array",
559        HostFunction::new("Int32Array", 1, int32_array_constructor),
560    );
561    ctx.register_builtin(
562        "Uint32Array",
563        HostFunction::new("Uint32Array", 1, uint32_array_constructor),
564    );
565    ctx.register_builtin(
566        "Float32Array",
567        HostFunction::new("Float32Array", 1, float32_array_constructor),
568    );
569    ctx.register_builtin(
570        "Float64Array",
571        HostFunction::new("Float64Array", 1, float64_array_constructor),
572    );
573    ctx.register_builtin(
574        "BigInt64Array",
575        HostFunction::new("BigInt64Array", 1, bigint64_array_constructor),
576    );
577    ctx.register_builtin(
578        "BigUint64Array",
579        HostFunction::new("BigUint64Array", 1, biguint64_array_constructor),
580    );
581
582    ctx.register_builtin(
583        "typedarray_buffer",
584        HostFunction::new("buffer", 0, typed_array_get_buffer),
585    );
586    ctx.register_builtin(
587        "typedarray_byteLength",
588        HostFunction::new("byteLength", 0, typed_array_get_byte_length),
589    );
590    ctx.register_builtin(
591        "typedarray_byteOffset",
592        HostFunction::new("byteOffset", 0, typed_array_get_byte_offset),
593    );
594    ctx.register_builtin(
595        "typedarray_length",
596        HostFunction::new("length", 0, typed_array_get_length),
597    );
598
599    ctx.register_builtin(
600        "arraybuffer_byteLength",
601        HostFunction::new("byteLength", 0, array_buffer_get_byte_length),
602    );
603
604    ctx.register_builtin(
605        "dataview_buffer",
606        HostFunction::new("buffer", 0, data_view_get_buffer),
607    );
608    ctx.register_builtin(
609        "dataview_byteLength",
610        HostFunction::new("byteLength", 0, data_view_get_byte_length),
611    );
612    ctx.register_builtin(
613        "dataview_byteOffset",
614        HostFunction::new("byteOffset", 0, data_view_get_byte_offset),
615    );
616    ctx.register_builtin(
617        "dataview_getInt8",
618        HostFunction::new("getInt8", 1, data_view_get_int8),
619    );
620    ctx.register_builtin(
621        "dataview_getUint8",
622        HostFunction::new("getUint8", 1, data_view_get_uint8),
623    );
624    ctx.register_builtin(
625        "dataview_setInt8",
626        HostFunction::new("setInt8", 2, data_view_set_int8),
627    );
628    ctx.register_builtin(
629        "dataview_setUint8",
630        HostFunction::new("setUint8", 2, data_view_set_uint8),
631    );
632}