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
13 changes: 7 additions & 6 deletions crates/perry-codegen/src/codegen/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,12 +444,13 @@ pub(super) fn compile_method(
for la in &forwarded {
ctor_args.push((DOUBLE, la.as_str()));
}
ctx.pending_declares.push((
ctor_sym.clone(),
crate::types::VOID,
ctor_param_types,
));
ctx.block().call_void(&ctor_sym, &ctor_args);
// Synthesized default-ctor forwarding to an imported parent
// ctor: discard the return (parent override does not
// replace `this`). Declared DOUBLE to match the symbol's
// real signature (see codegen/mod.rs).
ctx.pending_declares
.push((ctor_sym.clone(), DOUBLE, ctor_param_types));
let _ = ctx.block().call(DOUBLE, &ctor_sym, &ctor_args);
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions crates/perry-codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
param_count: ic.constructor_param_count,
has_own_constructor: ic.has_own_constructor,
has_instance_fields: ic.has_instance_fields,
has_rest: ic.constructor_has_rest,
},
)
})
Expand Down Expand Up @@ -1902,13 +1903,20 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
}

// Constructor: declared as
// `<source_prefix>__<class>_constructor(i64 this, double arg0, …) → void`
// `<source_prefix>__<class>_constructor(double this, double arg0, …) → double`.
// The source module's standalone ctor symbol returns DOUBLE — the
// ECMAScript constructor return-override value (an explicit
// `return <obj/fn>`) or `undefined` for an ordinary ctor. Declaring it
// VOID discarded a returned object/function, so `new Chalk(opts)` (whose
// ctor `return chalkFactory(opts)`) yielded the empty instance instead of
// the factory. The dispatch in `lower_new` applies `js_ctor_return_override`
// to this value.
let ctor_fn = format!("{}__{}_constructor", sanitize(src), sanitize(&ic.name),);
let mut ctor_params: Vec<crate::types::LlvmType> = vec![DOUBLE];
for _ in 0..ic.constructor_param_count {
ctor_params.push(DOUBLE);
}
llmod.declare_function(&ctor_fn, VOID, &ctor_params);
llmod.declare_function(&ctor_fn, DOUBLE, &ctor_params);

// Cross-module static methods. Source modules emit these as static
// functions with no `this` receiver, normally qualified by the source
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-codegen/src/codegen/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,14 @@ pub struct ImportedClass {
pub constructor_param_count: usize,
/// Whether the source class declared its own constructor body.
pub has_own_constructor: bool,
/// Whether the source class's constructor's last declared parameter is
/// `...rest`. Symmetric to `method_has_rest` but for the constructor: the
/// source module compiled `<class>_constructor(this, arg0, …)` expecting
/// the rest slot to receive a PACKED ARRAY of the trailing args. Without
/// this flag the cross-module `new C(a, b, c)` dispatch passed the args
/// positionally, so `arg0 = a` (raw) and `b`/`c` were dropped — a
/// `constructor(...args)` saw `args = a`, length 1.
pub constructor_has_rest: bool,
/// Whether the source class has instance fields that require initializer replay.
pub has_instance_fields: bool,
/// Method names defined on this class.
Expand Down Expand Up @@ -520,6 +528,10 @@ pub(crate) struct ImportedCtor {
pub param_count: usize,
pub has_own_constructor: bool,
pub has_instance_fields: bool,
/// True when the constructor's last declared param is `...rest`. Tells
/// the cross-module `new` dispatch to pack the trailing args into an
/// array for the rest slot rather than passing them positionally.
pub has_rest: bool,
}

impl ImportedCtor {
Expand Down
13 changes: 7 additions & 6 deletions crates/perry-codegen/src/expr/this_super_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -875,12 +875,13 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
for la in &lowered_args {
ctor_args.push((DOUBLE, la.as_str()));
}
ctx.pending_declares.push((
ctor.symbol.clone(),
crate::types::VOID,
ctor_param_types,
));
ctx.block().call_void(&ctor.symbol, &ctor_args);
// `super(...)` to an imported parent: the parent ctor's return
// override does not replace the derived `this`, so discard the
// return. Declared DOUBLE to match the symbol's real signature
// (the source standalone ctor returns DOUBLE — see codegen/mod.rs).
ctx.pending_declares
.push((ctor.symbol.clone(), DOUBLE, ctor_param_types));
let _ = ctx.block().call(DOUBLE, &ctor.symbol, &ctor_args);
}

// After the parent body has run (which may have set `this.config`
Expand Down
105 changes: 75 additions & 30 deletions crates/perry-codegen/src/lower_call/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,50 @@ fn pack_lowered_args_array(ctx: &mut FnCtx<'_>, args: &[String]) -> String {
nanbox_pointer_inline(ctx.block(), &current)
}

/// Marshal the lowered `new`-site args into the value list a cross-module
/// imported constructor symbol expects. The source module compiled the
/// standalone `<class>_constructor(this, p0, …)` with `ctor.param_count`
/// explicit slots. When the constructor's last param is `...rest`
/// (`ctor.has_rest`), that final slot must receive a PACKED ARRAY of every
/// trailing arg — not the first trailing arg passed raw. Mirrors the
/// inline-ctor `inline_constructor_param_values` rest packing and the
/// `method_has_rest` path for imported methods (#672). Returns exactly
/// `ctor.param_count` value strings; missing leading args are padded with
/// `undefined`.
fn marshal_imported_ctor_args(
ctx: &mut FnCtx<'_>,
ctor: &crate::codegen::ImportedCtor,
lowered_args: &[String],
) -> Vec<String> {
let undef = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let param_count = ctor.param_count;
if ctor.has_rest && param_count > 0 {
// The first `param_count - 1` slots are positional; the last slot is
// the rest array packing every remaining arg.
let n_positional = param_count - 1;
let mut out: Vec<String> = Vec::with_capacity(param_count);
for i in 0..n_positional {
out.push(
lowered_args
.get(i)
.cloned()
.unwrap_or_else(|| undef.clone()),
);
}
let tail: Vec<String> = lowered_args.iter().skip(n_positional).cloned().collect();
out.push(pack_lowered_args_array(ctx, &tail));
out
} else {
// No rest: positional, padded to `param_count` with `undefined`.
let mut out: Vec<String> = lowered_args.to_vec();
while out.len() < param_count {
out.push(undef.clone());
}
out.truncate(param_count.max(out.len()));
out
}
}

/// The effective constructor arity for `new <class>(...)`: the class's own
/// ctor params, else — for a subclass with no own ctor — the closest
/// ancestor-with-a-ctor's param count (the synthesized default ctor forwards
Expand Down Expand Up @@ -951,7 +995,7 @@ pub(crate) fn lower_new(ctx: &mut FnCtx<'_>, class_name: &str, args: &[Expr]) ->
let after_idx = ctx.new_block("ctor.return.after");
let after_label = ctx.block_label(after_idx);
ctx.inline_ctor_return.push(crate::expr::InlineCtorReturn {
result_slot: ctor_result_slot,
result_slot: ctor_result_slot.clone(),
after_label,
// A class is "derived" (and thus subject to the stricter
// return-override rules) if it has ANY heritage — a named parent,
Expand Down Expand Up @@ -1377,50 +1421,51 @@ pub(crate) fn lower_new(ctx: &mut FnCtx<'_>, class_name: &str, args: &[Expr]) ->
.filter(|_| effective_class_name != lookup_class)
{
// Walked to an ancestor — call its ctor with this and forwarded args.
let undef_lit =
crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
while lowered_args.len() < ctor.param_count {
lowered_args.push(undef_lit.clone());
}
// `...rest` ctors get the trailing args packed into one array
// for the final slot (mirrors method_has_rest, #672).
let marshalled = marshal_imported_ctor_args(ctx, &ctor, &lowered_args);
let mut ctor_args: Vec<(crate::types::LlvmType, &str)> =
Vec::with_capacity(1 + lowered_args.len());
Vec::with_capacity(1 + marshalled.len());
ctor_args.push((DOUBLE, &obj_box));
let ctor_param_types: Vec<crate::types::LlvmType> = std::iter::once(DOUBLE)
.chain(lowered_args.iter().map(|_| DOUBLE))
.chain(marshalled.iter().map(|_| DOUBLE))
.collect();
for la in &lowered_args {
for la in &marshalled {
ctor_args.push((DOUBLE, la.as_str()));
}
ctx.pending_declares.push((
ctor.symbol.clone(),
crate::types::VOID,
ctor_param_types,
));
ctx.block().call_void(&ctor.symbol, &ctor_args);
// Walked to an ANCESTOR ctor: its return-override does not replace
// the leaf instance, so discard the return value. Declared DOUBLE
// to match the symbol's real signature (see codegen/mod.rs).
ctx.pending_declares
.push((ctor.symbol.clone(), DOUBLE, ctor_param_types));
let _ = ctx.block().call(DOUBLE, &ctor.symbol, &ctor_args);
} else if let Some(ctor) = ctx.imported_class_ctors.get(class_name).cloned() {
// Pad missing optional args with TAG_UNDEFINED so the constructor
// doesn't read garbage from stale registers.
let undef_lit =
crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
while lowered_args.len() < ctor.param_count {
lowered_args.push(undef_lit.clone());
}
// doesn't read garbage from stale registers, and pack the rest
// slot into an array when the ctor's last param is `...rest`.
let marshalled = marshal_imported_ctor_args(ctx, &ctor, &lowered_args);
// Pass `this` as NaN-boxed double (same as compile_method's this_arg).
let mut ctor_args: Vec<(crate::types::LlvmType, &str)> =
Vec::with_capacity(1 + lowered_args.len());
Vec::with_capacity(1 + marshalled.len());
ctor_args.push((DOUBLE, &obj_box));
let ctor_param_types: Vec<crate::types::LlvmType> = std::iter::once(DOUBLE)
.chain(lowered_args.iter().map(|_| DOUBLE))
.chain(marshalled.iter().map(|_| DOUBLE))
.collect();
for la in &lowered_args {
for la in &marshalled {
ctor_args.push((DOUBLE, la.as_str()));
}
ctx.pending_declares.push((
ctor.symbol.clone(),
crate::types::VOID,
ctor_param_types,
));
ctx.block().call_void(&ctor.symbol, &ctor_args);
// The standalone `<class>_constructor` symbol returns DOUBLE: the
// value an explicit `return <obj/fn>` produced (ECMAScript ctor
// return-override) or `undefined` for an ordinary ctor. Capture it
// into `ctor_result_slot` so the return-override applied at the end
// of `lower_new` honors it — chalk's `class Chalk { constructor(o){
// return chalkFactory(o); } }` returns a FUNCTION, so `new Chalk(o)`
// must yield that function, not the empty allocated instance
// ("value is not a function" on `new Chalk(...).red(...)`).
ctx.pending_declares
.push((ctor.symbol.clone(), DOUBLE, ctor_param_types));
let ctor_ret = ctx.block().call(DOUBLE, &ctor.symbol, &ctor_args);
ctx.block().store(DOUBLE, &ctor_ret, &ctor_result_slot);
}
} // end !found_inherited_ctor
}
Expand Down
Loading
Loading