@@ -1688,26 +1688,48 @@ pub fn try_lower_extern_func_call(
16881688 // an arrow-bound exported value (hono's `mergePath` from utils/url.js,
16891689 // any `export const foo = () => …` cross-module use).
16901690 if ctx. imported_vars . contains ( name) {
1691- ctx. pending_declares . push ( ( fname. clone ( ) , DOUBLE , vec ! [ ] ) ) ;
1692- let closure_box = ctx. block ( ) . call ( DOUBLE , & fname, & [ ] ) ;
1693- let mut lowered_args: Vec < String > = Vec :: with_capacity ( args. len ( ) ) ;
1694- for a in args {
1695- lowered_args. push ( lower_expr ( ctx, a) ?) ;
1696- }
1697- if lowered_args. len ( ) > 16 {
1691+ if args. len ( ) > 16 {
16981692 anyhow:: bail!(
16991693 "perry-codegen Phase D.1: closure call with {} args (max 16)" ,
1700- lowered_args . len( )
1694+ args . len( )
17011695 ) ;
17021696 }
1703- let blk = ctx. block ( ) ;
1704- let closure_handle = unbox_to_i64 ( blk, & closure_box) ;
1705- let runtime_fn = format ! ( "js_closure_call{}" , lowered_args. len( ) ) ;
1706- let mut call_args: Vec < ( crate :: types:: LlvmType , & str ) > = vec ! [ ( I64 , & closure_handle) ] ;
1707- for v in & lowered_args {
1708- call_args. push ( ( DOUBLE , v. as_str ( ) ) ) ;
1709- }
1710- return Ok ( Some ( blk. call ( DOUBLE , & runtime_fn, & call_args) ) ) ;
1697+ ctx. pending_declares . push ( ( fname. clone ( ) , DOUBLE , vec ! [ ] ) ) ;
1698+ // Fetch the callee before evaluating arguments, as JavaScript requires,
1699+ // but keep that closure rooted while argument expressions run. Next's
1700+ // App Route exports are commonly `export const GET = ...`; constructing
1701+ // a request argument can allocate and evacuate the closure before this
1702+ // path finally dispatches it (#8036).
1703+ let closure_box = ctx. block ( ) . call ( DOUBLE , & fname, & [ ] ) ;
1704+ let arg_refs: Vec < & Expr > = args. iter ( ) . collect ( ) ;
1705+ let lowered_args = std:: cell:: RefCell :: new ( Vec :: < String > :: new ( ) ) ;
1706+ let result = crate :: rooting:: with_rooted_accumulator (
1707+ ctx,
1708+ crate :: rooting:: Repr :: Boxed ,
1709+ & closure_box,
1710+ crate :: rooting:: any_operand_may_collect ( ctx, args. iter ( ) ) ,
1711+ |ctx, _closure| {
1712+ crate :: rooting:: with_operands_rooted ( ctx, & arg_refs, |_ctx, vals| {
1713+ * lowered_args. borrow_mut ( ) = vals. to_vec ( ) ;
1714+ Ok ( ( ) )
1715+ } )
1716+ } ,
1717+ |ctx, closure_box| {
1718+ // Re-read and unbox only after every collecting argument and
1719+ // after the argument group's own re-reads have completed.
1720+ let lowered = lowered_args. borrow ( ) ;
1721+ let blk = ctx. block ( ) ;
1722+ let closure_handle = unbox_to_i64 ( blk, closure_box) ;
1723+ let runtime_fn = format ! ( "js_closure_call{}" , lowered. len( ) ) ;
1724+ let mut call_args: Vec < ( crate :: types:: LlvmType , & str ) > =
1725+ vec ! [ ( I64 , & closure_handle) ] ;
1726+ for value in lowered. iter ( ) {
1727+ call_args. push ( ( DOUBLE , value. as_str ( ) ) ) ;
1728+ }
1729+ Ok ( blk. call ( DOUBLE , & runtime_fn, & call_args) )
1730+ } ,
1731+ ) ?;
1732+ return Ok ( Some ( result) ) ;
17111733 }
17121734 // Record the cross-module call so the caller can add a `declare`
17131735 // line for it after the &mut LlFunction borrow is released. The
0 commit comments