Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/perry-codegen/src/collectors/pointer_locals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ pub fn collect_pointer_typed_locals(
| Expr::BufferFrom { .. }
| Expr::BufferFromArrayBuffer { .. }
| Expr::BufferConcat(_)
| Expr::BufferConcatWithLength { .. }
| Expr::Uint8ArrayNew(_)
| Expr::Uint8ArrayFrom(_)
| Expr::TextEncoderEncode(_) => Some(Type::Named("Uint8Array".into())),
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-codegen/src/expr/array_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
let buf_handle = blk.call(I64, "js_buffer_concat", &[(I64, &arr_handle)]);
Ok(nanbox_pointer_inline(blk, &buf_handle))
}
Expr::BufferConcatWithLength { list, total_length } => {
let arr_box = lower_expr(ctx, list)?;
let total_box = lower_expr(ctx, total_length)?;
let blk = ctx.block();
let arr_handle = unbox_to_i64(blk, &arr_box);
let buf_handle = blk.call(
I64,
"js_buffer_concat_with_length",
&[(I64, &arr_handle), (DOUBLE, &total_box)],
);
Ok(nanbox_pointer_inline(blk, &buf_handle))
}

// #1177: `buf.slice(start?, end?)` on a statically buffer-producing
// receiver — emitted by the HIR fold at `expr_call/mod.rs:5396` when
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/expr/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ fn hash_input_is_buffer(ctx: &FnCtx<'_>, e: &Expr) -> bool {
| Expr::BufferAlloc { .. }
| Expr::BufferAllocUnsafe(_)
| Expr::BufferConcat(_)
| Expr::BufferConcatWithLength { .. }
| Expr::CryptoRandomBytes(_)
) {
return true;
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1745,6 +1745,7 @@ pub(crate) fn lower_expr(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
| Expr::AggregateErrorNew { .. }
| Expr::RegExpLastIndex(..)
| Expr::BufferConcat(..)
| Expr::BufferConcatWithLength { .. }
| Expr::BufferSlice { .. }
| Expr::BufferIsBuffer(..)
| Expr::BufferIsEncoding(..)
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/runtime_decls/stdlib_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ pub fn declare_stdlib_ffi(module: &mut LlModule) {
module.declare_function("js_buffer_byte_length", I32, &[I64]);
module.declare_function("js_buffer_byte_length_value", I32, &[DOUBLE, DOUBLE]);
module.declare_function("js_buffer_concat", I64, &[I64]);
module.declare_function("js_buffer_concat_with_length", I64, &[I64, DOUBLE]);
module.declare_function("js_buffer_copy", I32, &[I64, I64, I32, I32, I32]);
module.declare_function("js_buffer_equals", I32, &[I64, I64]);
module.declare_function("js_buffer_fill", I64, &[I64, I32]);
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/type_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ pub(crate) fn refine_type_from_init(ctx: &FnCtx<'_>, init: &Expr) -> Option<HirT
| Expr::BufferAlloc { .. }
| Expr::BufferAllocUnsafe(_)
| Expr::BufferConcat(_)
| Expr::BufferConcatWithLength { .. }
| Expr::CryptoRandomBytes(_) => Some(HirType::Named("Uint8Array".into())),
// Compare results are now NaN-boxed booleans (TAG_TRUE/FALSE).
// Type-refining the local as Boolean lets is_numeric_expr
Expand Down
4 changes: 4 additions & 0 deletions crates/perry-hir/src/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,10 @@ pub(crate) fn collect_assigned_locals_expr(expr: &Expr, assigned: &mut Vec<Local
| Expr::BufferLength(expr) => {
collect_assigned_locals_expr(expr, assigned);
}
Expr::BufferConcatWithLength { list, total_length } => {
collect_assigned_locals_expr(list, assigned);
collect_assigned_locals_expr(total_length, assigned);
}
Expr::BufferByteLength { data, encoding } => {
collect_assigned_locals_expr(data, assigned);
if let Some(enc) = encoding {
Expand Down
9 changes: 7 additions & 2 deletions crates/perry-hir/src/ir/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,8 +982,13 @@ pub enum Expr {
},
BufferAllocUnsafe(Box<Expr>), // Buffer.allocUnsafe(size) -> Buffer
BufferConcat(Box<Expr>), // Buffer.concat(list) -> Buffer
BufferIsBuffer(Box<Expr>), // Buffer.isBuffer(obj) -> boolean
BufferIsEncoding(Box<Expr>), // Buffer.isEncoding(encoding) -> boolean
BufferConcatWithLength {
// Buffer.concat(list, totalLength) -> Buffer
list: Box<Expr>,
total_length: Box<Expr>,
},
BufferIsBuffer(Box<Expr>), // Buffer.isBuffer(obj) -> boolean
BufferIsEncoding(Box<Expr>), // Buffer.isEncoding(encoding) -> boolean
BufferByteLength {
data: Box<Expr>,
encoding: Option<Box<Expr>>,
Expand Down
1 change: 1 addition & 0 deletions crates/perry-hir/src/lower/expr_call/array_only_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ pub(super) fn try_array_only_methods(
if matches!(
&array_expr,
Expr::BufferConcat(_)
| Expr::BufferConcatWithLength { .. }
| Expr::BufferFrom { .. }
| Expr::BufferSlice { .. }
) {
Expand Down
12 changes: 9 additions & 3 deletions crates/perry-hir/src/lower/expr_call/module_static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1038,9 +1038,15 @@ pub(super) fn try_module_static_methods(
}
"concat" => {
if !args.is_empty() {
return Ok(Ok(Expr::BufferConcat(Box::new(
args.into_iter().next().unwrap(),
))));
let mut args_iter = args.into_iter();
let list = args_iter.next().unwrap();
if let Some(total_length) = args_iter.next() {
return Ok(Ok(Expr::BufferConcatWithLength {
list: Box::new(list),
total_length: Box::new(total_length),
}));
}
return Ok(Ok(Expr::BufferConcat(Box::new(list))));
}
}
"of" => {
Expand Down
6 changes: 6 additions & 0 deletions crates/perry-hir/src/lower/expr_call/native_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,12 @@ pub(super) fn try_native_module_methods(
}
"concat" => {
let list = args.first().cloned().unwrap_or(Expr::Array(vec![]));
if let Some(total_length) = args.get(1).cloned() {
return Ok(Ok(Expr::BufferConcatWithLength {
list: Box::new(list),
total_length: Box::new(total_length),
}));
}
return Ok(Ok(Expr::BufferConcat(Box::new(list))));
}
"of" => {
Expand Down
1 change: 1 addition & 0 deletions crates/perry-hir/src/lower/expr_call/url_date_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ pub(super) fn try_url_date_weakref_instance(
| Expr::BufferAlloc { .. }
| Expr::BufferAllocUnsafe(_)
| Expr::BufferConcat(_)
| Expr::BufferConcatWithLength { .. }
) {
return Ok(Ok(Expr::Call {
callee: Box::new(Expr::PropertyGet {
Expand Down
1 change: 1 addition & 0 deletions crates/perry-hir/src/stable_hash/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ impl SH for Expr {
Expr::BufferAlloc { size, fill, encoding } => { tag(h, 216); size.as_ref().hash(h); fill.hash(h); encoding.hash(h); }
Expr::BufferAllocUnsafe(e) => { tag(h, 217); e.as_ref().hash(h); }
Expr::BufferConcat(e) => { tag(h, 218); e.as_ref().hash(h); }
Expr::BufferConcatWithLength { list, total_length } => { tag(h, 11222); list.as_ref().hash(h); total_length.as_ref().hash(h); }
Expr::BufferIsBuffer(e) => { tag(h, 219); e.as_ref().hash(h); }
Expr::BufferIsEncoding(e) => { tag(h, 11219); e.as_ref().hash(h); }
Expr::BufferByteLength { data, encoding } => { tag(h, 220); data.as_ref().hash(h); encoding.hash(h); }
Expand Down
4 changes: 4 additions & 0 deletions crates/perry-hir/src/walker/expr_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ where
| Expr::TemplateRaw(v) => {
f(v);
}
Expr::BufferConcatWithLength { list, total_length } => {
f(list);
f(total_length);
}

Expr::UrlCanParseWithBase { input, base } => {
f(input);
Expand Down
4 changes: 4 additions & 0 deletions crates/perry-hir/src/walker/expr_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ where
| Expr::TemplateRaw(v) => {
f(v);
}
Expr::BufferConcatWithLength { list, total_length } => {
f(list);
f(total_length);
}

Expr::UrlCanParseWithBase { input, base } => {
f(input);
Expand Down
93 changes: 79 additions & 14 deletions crates/perry-runtime/src/buffer/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,9 +853,53 @@ pub extern "C" fn js_buffer_alloc_unsafe(size: i32) -> *mut BufferHeader {
buf
}

/// Concatenate multiple buffers
#[no_mangle]
pub extern "C" fn js_buffer_concat(arr_ptr: *const ArrayHeader) -> *mut BufferHeader {
fn throw_buffer_concat_invalid_arg_type(index: usize) -> ! {
static REGISTER_TYPE_ERROR: std::sync::Once = std::sync::Once::new();
REGISTER_TYPE_ERROR.call_once(|| {
crate::object::js_register_class_extends_error(crate::error::CLASS_ID_TYPE_ERROR);
});

let obj = crate::object::js_object_alloc(crate::error::CLASS_ID_TYPE_ERROR, 4);
unsafe {
let set = |key: &[u8], value: f64| {
let key_ptr = crate::string::js_string_from_bytes(key.as_ptr(), key.len() as u32);
crate::object::js_object_set_field_by_name(obj, key_ptr, value);
};
let str_val = |s: &[u8]| -> f64 {
let ptr = crate::string::js_string_from_bytes(s.as_ptr(), s.len() as u32);
f64::from_bits(crate::JSValue::string_ptr(ptr).bits())
};
let message =
format!("The \"list[{index}]\" argument must be an instance of Buffer or Uint8Array");
set(b"name", str_val(b"TypeError"));
set(b"code", str_val(b"ERR_INVALID_ARG_TYPE"));
set(b"message", str_val(message.as_bytes()));
}
crate::exception::js_throw(crate::value::js_nanbox_pointer(obj as i64))
}

fn normalize_buffer_concat_total_length(total_length: f64) -> Option<usize> {
let jsval = crate::JSValue::from_bits(total_length.to_bits());
if jsval.is_undefined() {
return None;
}
if jsval.is_int32() {
return Some((jsval.as_int32().max(0)) as usize);
}
if jsval.is_bool() {
return Some(if jsval.as_bool() { 1 } else { 0 });
}
if jsval.is_null() || total_length.is_nan() || !total_length.is_finite() || total_length <= 0.0
{
return Some(0);
}
Some((total_length.trunc() as usize).min(u32::MAX as usize))
}

fn js_buffer_concat_impl(
arr_ptr: *const ArrayHeader,
requested_total_length: Option<usize>,
) -> *mut BufferHeader {
// Strip NaN-boxing tags if present
let arr_ptr = {
let bits = arr_ptr as u64;
Expand Down Expand Up @@ -884,34 +928,55 @@ pub extern "C" fn js_buffer_concat(arr_ptr: *const ArrayHeader) -> *mut BufferHe
}
};

// Calculate total size
let mut total_size: usize = 0;
let mut actual_total_size: usize = 0;
for i in 0..len {
let raw_bits = strip_nanbox((*arr_data.add(i)).to_bits());
let buf_ptr = raw_bits as *const BufferHeader;
if !buf_ptr.is_null() && raw_bits >= 0x1000 {
total_size += (*buf_ptr).length as usize;
if raw_bits < 0x1000 || !is_registered_buffer(raw_bits as usize) {
throw_buffer_concat_invalid_arg_type(i);
}
let buf_ptr = raw_bits as *const BufferHeader;
actual_total_size = actual_total_size.saturating_add((*buf_ptr).length as usize);
}
let total_size = requested_total_length.unwrap_or(actual_total_size);
let total_size = total_size.min(u32::MAX as usize);

// Allocate result buffer
let result = buffer_alloc(total_size as u32);
(*result).length = total_size as u32;
ptr::write_bytes(buffer_data_mut(result), 0, total_size);

// Copy data
let mut offset: usize = 0;
for i in 0..len {
let raw_bits = strip_nanbox((*arr_data.add(i)).to_bits());
let buf_ptr = raw_bits as *const BufferHeader;
if !buf_ptr.is_null() && raw_bits >= 0x1000 {
let buf_len = (*buf_ptr).length as usize;
let src_data = buffer_data(buf_ptr);
let dst_data = buffer_data_mut(result).add(offset);
ptr::copy_nonoverlapping(src_data, dst_data, buf_len);
offset += buf_len;
let buf_len = (*buf_ptr).length as usize;
let remaining = total_size.saturating_sub(offset);
if remaining == 0 {
break;
}
let copy_len = buf_len.min(remaining);
let src_data = buffer_data(buf_ptr);
let dst_data = buffer_data_mut(result).add(offset);
ptr::copy_nonoverlapping(src_data, dst_data, copy_len);
offset += copy_len;
}

result
}
}

/// Concatenate multiple buffers.
#[no_mangle]
pub extern "C" fn js_buffer_concat(arr_ptr: *const ArrayHeader) -> *mut BufferHeader {
js_buffer_concat_impl(arr_ptr, None)
}

/// Concatenate multiple buffers using Node's optional totalLength semantics.
#[no_mangle]
pub extern "C" fn js_buffer_concat_with_length(
arr_ptr: *const ArrayHeader,
total_length: f64,
) -> *mut BufferHeader {
js_buffer_concat_impl(arr_ptr, normalize_buffer_concat_total_length(total_length))
}
5 changes: 3 additions & 2 deletions crates/perry-runtime/src/buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ pub use header::{
// ---- Re-exports: Buffer.from / alloc / concat (FFI) ----
pub use from::{
js_array_buffer_new, js_buffer_alloc, js_buffer_alloc_fill_value, js_buffer_alloc_unsafe,
js_buffer_concat, js_buffer_fill, js_buffer_fill_range, js_buffer_fill_value_range,
js_buffer_from_array, js_buffer_from_arraybuffer_slice, js_buffer_from_string,
js_buffer_concat, js_buffer_concat_with_length, js_buffer_fill, js_buffer_fill_range,
js_buffer_fill_value_range, js_buffer_from_array, js_buffer_from_arraybuffer_slice,
js_buffer_from_string,
js_buffer_from_value, js_data_view_new, js_encoding_tag_from_value, js_shared_array_buffer_new,
js_uint8array_alloc, js_uint8array_from_array, js_uint8array_new,
};
Expand Down
7 changes: 6 additions & 1 deletion crates/perry-runtime/src/object/native_module_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,12 @@ pub(crate) unsafe fn dispatch_native_module_method(
}
("buffer.Buffer", "concat") => {
let arr = ptr_addr(arg(0)) as *const crate::array::ArrayHeader;
ptr_to_f64(crate::buffer::js_buffer_concat(arr) as *const u8)
let buf = if args_len >= 2 {
crate::buffer::js_buffer_concat_with_length(arr, arg(1))
} else {
crate::buffer::js_buffer_concat(arr)
};
ptr_to_f64(buf as *const u8)
}
("buffer.Buffer", "of") => {
let arr = pack_args();
Expand Down
Loading