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
5 changes: 3 additions & 2 deletions crates/perry-codegen/src/expr/new_dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,10 +455,10 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
// also supported because the helper falls back to a
// class_id=0 empty-object allocation when no synthetic id
// exists (preserves the pre-fix baseline).
// Also route PropertyGet callees through `js_new_function_construct`:
// Also route PropertyGet / IndexGet callees through `js_new_function_construct`:
// covers `new date.constructor(value)` (date-fns
// `constructFrom`) and generic `new obj.factory(...)` shapes
// where `obj.factory` resolves to a closure pointer at
// where `obj.factory` or `ctors[i]` resolves to a closure pointer at
// runtime. The runtime helper detects the global Date /
// Array / Object thunks and dispatches into the matching
// real factory; non-matching closures still get the
Expand All @@ -468,6 +468,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
Expr::FuncRef(_)
| Expr::LocalGet(_)
| Expr::PropertyGet { .. }
| Expr::IndexGet { .. }
| Expr::Closure { .. }
);
if routes_through_function_construct {
Expand Down
11 changes: 11 additions & 0 deletions crates/perry-hir/src/lower/expr_new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,17 @@ pub(super) fn lower_new(ctx: &mut LoweringContext, new_expr: &ast::NewExpr) -> R
return Ok(expr);
}
}
if obj_name == "globalThis"
&& ctx.lookup_local("globalThis").is_none()
&& is_fetch_constructor_name(prop_ident.sym.as_ref())
{
ctx.uses_fetch = true;
return Ok(Expr::New {
class_name: prop_ident.sym.to_string(),
args: lower_optional_args(ctx, new_expr.args.as_deref())?,
type_args: Vec::new(),
});
}

let is_net_module =
obj_name == "net" || ctx.lookup_builtin_module_alias(obj_name) == Some("net");
Expand Down
3 changes: 3 additions & 0 deletions crates/perry-runtime/src/closure/dynamic_props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ pub fn is_closure_ptr(ptr: usize) -> bool {
if ptr < 0x10000 {
return false;
}
if ptr % std::mem::align_of::<ClosureHeader>() != 0 {
return false;
}
unsafe {
let type_tag = *((ptr as *const u8).add(12) as *const u32);
type_tag == CLOSURE_MAGIC
Expand Down
30 changes: 30 additions & 0 deletions crates/perry-runtime/src/object/class_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,9 @@ pub(super) fn identify_global_builtin_constructor(func_value: f64) -> Option<&'s
if ptr.is_null() {
return None;
}
if (ptr as usize) % std::mem::align_of::<crate::closure::ClosureHeader>() != 0 {
return None;
}
if !is_valid_obj_ptr(ptr as *const u8) {
return None;
}
Expand Down Expand Up @@ -699,6 +702,7 @@ pub(super) fn identify_global_builtin_constructor(func_value: f64) -> Option<&'s
|| func_ptr == global_this_object_thunk as *const u8 as usize
|| func_ptr == global_this_date_thunk as *const u8 as usize
|| func_ptr == global_this_blob_thunk as *const u8 as usize
|| func_ptr == global_this_file_thunk as *const u8 as usize
|| func_ptr == global_this_headers_thunk as *const u8 as usize
|| func_ptr == global_this_request_thunk as *const u8 as usize
|| func_ptr == global_this_response_thunk as *const u8 as usize
Expand Down Expand Up @@ -1399,6 +1403,26 @@ pub unsafe extern "C" fn js_new_function_construct(
.unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED));
return crate::object::global_this_blob_thunk(std::ptr::null(), parts, options);
}
"File" => {
let parts = args
.first()
.copied()
.unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED));
let name = args
.get(1)
.copied()
.unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED));
let options = args
.get(2)
.copied()
.unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED));
return crate::object::global_this_file_thunk(
std::ptr::null(),
parts,
name,
options,
);
}
"Headers" => {
let init = args
.first()
Expand Down Expand Up @@ -1867,6 +1891,9 @@ fn is_callable_function_value(value: f64) -> bool {
if ptr.is_null() {
return false;
}
if (ptr as usize) % std::mem::align_of::<crate::closure::ClosureHeader>() != 0 {
return false;
}
if !is_valid_obj_ptr(ptr as *const u8) {
return false;
}
Expand All @@ -1880,6 +1907,9 @@ fn is_arrow_function_value(value: f64) -> bool {
return false;
}
let ptr = jv.as_pointer() as *const crate::closure::ClosureHeader;
if (ptr as usize) % std::mem::align_of::<crate::closure::ClosureHeader>() != 0 {
return false;
}
if ptr.is_null() || !is_valid_obj_ptr(ptr as *const u8) {
return false;
}
Expand Down
18 changes: 18 additions & 0 deletions crates/perry-runtime/src/object/global_fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type FetchWithOptionsFn = unsafe extern "C" fn(
) -> *mut crate::promise::Promise;

type FetchBlobNewFn = unsafe extern "C" fn(f64, f64) -> f64;
type FetchFileNewFn = unsafe extern "C" fn(f64, f64, f64, f64) -> f64;
type FetchHeadersNewFn = extern "C" fn() -> f64;
type FetchHeadersInitFromValueFn = unsafe extern "C" fn(f64, f64) -> f64;
type FetchRequestNewFn = unsafe extern "C" fn(
Expand Down Expand Up @@ -45,6 +46,7 @@ type FetchResponseStaticErrorFn = extern "C" fn() -> f64;

static GLOBAL_FETCH_WITH_OPTIONS: AtomicPtr<()> = AtomicPtr::new(null_mut());
static GLOBAL_FETCH_BLOB_NEW: AtomicPtr<()> = AtomicPtr::new(null_mut());
static GLOBAL_FETCH_FILE_NEW: AtomicPtr<()> = AtomicPtr::new(null_mut());
static GLOBAL_FETCH_HEADERS_NEW: AtomicPtr<()> = AtomicPtr::new(null_mut());
static GLOBAL_FETCH_HEADERS_INIT_FROM_VALUE: AtomicPtr<()> = AtomicPtr::new(null_mut());
static GLOBAL_FETCH_REQUEST_NEW: AtomicPtr<()> = AtomicPtr::new(null_mut());
Expand All @@ -61,6 +63,7 @@ pub extern "C" fn js_register_global_fetch_with_options(f: FetchWithOptionsFn) {
#[no_mangle]
pub extern "C" fn js_register_global_fetch_constructors(
blob_new: FetchBlobNewFn,
file_new: FetchFileNewFn,
headers_new: FetchHeadersNewFn,
headers_init_from_value: FetchHeadersInitFromValueFn,
request_new: FetchRequestNewFn,
Expand All @@ -70,6 +73,7 @@ pub extern "C" fn js_register_global_fetch_constructors(
response_static_error: FetchResponseStaticErrorFn,
) {
GLOBAL_FETCH_BLOB_NEW.store(blob_new as *mut (), Ordering::Release);
GLOBAL_FETCH_FILE_NEW.store(file_new as *mut (), Ordering::Release);
GLOBAL_FETCH_HEADERS_NEW.store(headers_new as *mut (), Ordering::Release);
GLOBAL_FETCH_HEADERS_INIT_FROM_VALUE
.store(headers_init_from_value as *mut (), Ordering::Release);
Expand Down Expand Up @@ -166,6 +170,20 @@ pub(super) fn call_global_blob_new(parts: f64, type_value: f64) -> f64 {
warn_unregistered_fetch_symbol("js_blob_new")
}

pub(super) fn call_global_file_new(
parts: f64,
name: f64,
type_value: f64,
last_modified: f64,
) -> f64 {
let f = GLOBAL_FETCH_FILE_NEW.load(Ordering::Acquire);
if !f.is_null() {
let func: FetchFileNewFn = unsafe { std::mem::transmute(f) };
return unsafe { func(parts, name, type_value, last_modified) };
}
warn_unregistered_fetch_symbol("js_file_new")
}

pub(super) fn call_global_headers_new() -> f64 {
let f = GLOBAL_FETCH_HEADERS_NEW.load(Ordering::Acquire);
if !f.is_null() {
Expand Down
20 changes: 20 additions & 0 deletions crates/perry-runtime/src/object/global_this.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,22 @@ pub(crate) extern "C" fn global_this_blob_thunk(
super::global_fetch::call_global_blob_new(parts, type_value)
}

pub(crate) extern "C" fn global_this_file_thunk(
_closure: *const crate::closure::ClosureHeader,
parts: f64,
name: f64,
options: f64,
) -> f64 {
let type_value = global_this_fetch_option(options, b"type");
let last_modified = global_this_fetch_option(options, b"lastModified");
let last_modified = if last_modified.to_bits() == crate::value::TAG_UNDEFINED {
f64::NAN
} else {
last_modified
};
super::global_fetch::call_global_file_new(parts, name, type_value, last_modified)
}

pub(crate) extern "C" fn global_this_headers_thunk(
_closure: *const crate::closure::ClosureHeader,
init: f64,
Expand Down Expand Up @@ -2228,6 +2244,7 @@ pub(crate) fn populate_global_this_builtins(singleton: *mut ObjectHeader) {
}
"Date" => global_this_date_thunk as *const u8,
"Blob" => global_this_blob_thunk as *const u8,
"File" => global_this_file_thunk as *const u8,
"Headers" => global_this_headers_thunk as *const u8,
"Request" => global_this_request_thunk as *const u8,
"Response" => global_this_response_thunk as *const u8,
Expand Down Expand Up @@ -2261,6 +2278,9 @@ pub(crate) fn populate_global_this_builtins(singleton: *mut ObjectHeader) {
"Blob" | "Request" | "Response" => {
crate::closure::js_register_closure_arity(func_ptr, 2);
}
"File" => {
crate::closure::js_register_closure_arity(func_ptr, 3);
}
"Error" | "TypeError" | "RangeError" | "ReferenceError" | "SyntaxError"
| "EvalError" | "URIError" => {
crate::closure::js_register_closure_arity(func_ptr, 1);
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-stdlib/src/common/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2616,6 +2616,7 @@ pub unsafe extern "C" fn js_stdlib_init_dispatch() {
#[cfg(feature = "http-client")]
fn js_register_global_fetch_constructors(
blob_new: unsafe extern "C" fn(f64, f64) -> f64,
file_new: unsafe extern "C" fn(f64, f64, f64, f64) -> f64,
headers_new: extern "C" fn() -> f64,
headers_init_from_value: unsafe extern "C" fn(f64, f64) -> f64,
request_new: unsafe extern "C" fn(
Expand Down Expand Up @@ -2675,6 +2676,7 @@ pub unsafe extern "C" fn js_stdlib_init_dispatch() {
#[cfg(feature = "http-client")]
js_register_global_fetch_constructors(
crate::fetch_blob::js_blob_new,
crate::fetch_blob::js_file_new,
crate::fetch::js_headers_new,
crate::fetch::js_headers_init_from_value,
crate::fetch::js_request_new,
Expand Down
6 changes: 6 additions & 0 deletions crates/perry-stdlib/src/fetch/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,12 @@ pub fn dispatch_blob_property(blob_id: usize, prop: &str) -> Option<f64> {
js_string_from_bytes(blob.content_type.as_ptr(), blob.content_type.len() as u32);
JSValue::string_ptr(p).bits()
}
"name" => {
let name = blob.file_name.as_ref()?;
let p = js_string_from_bytes(name.as_ptr(), name.len() as u32);
JSValue::string_ptr(p).bits()
}
"lastModified" => return blob.last_modified_ms,
_ => return None,
};
Some(f64::from_bits(bits))
Expand Down
8 changes: 8 additions & 0 deletions test-parity/node-suite/buffer/blob-file/global-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { File as BufferFile } from "node:buffer";

console.log("typeof File:", typeof File);
console.log("File.name:", File.name);
console.log("File.length:", File.length);
console.log("typeof globalThis.File:", typeof globalThis.File);
console.log("global File identity:", globalThis.File === File);
console.log("buffer File identity:", BufferFile === File, BufferFile === globalThis.File);
Expand All @@ -20,3 +21,10 @@ const rebound = new FileCtor(["xy"], "y.txt", {
});
console.log("rebound fields:", rebound.name, rebound.type, rebound.size, rebound.lastModified);
console.log("rebound text:", await rebound.text());

const dynamic = new ([globalThis.File][0])(["zz"], "z.txt", {
type: "text/dynamic",
lastModified: 789,
});
console.log("dynamic fields:", dynamic.name, dynamic.type, dynamic.size, dynamic.lastModified);
console.log("dynamic text:", await dynamic.text());