@@ -286,85 +286,88 @@ struct CLPatchPointInfo {
286286 state : FrameState ,
287287}
288288
289- /// Compile a function using the Cranelift backend
289+ /// Compile a function using the Cranelift backend.
290+ ///
291+ /// Each HIR function has entry blocks (interpreter entry, JIT entries) that Jump
292+ /// to a merge block. We compile:
293+ /// - The **body** (merge block + everything after) as one Cranelift function with
294+ /// signature `(EC, CFP, SP, merge_param0..N) -> VALUE` using the `tail` calling convention.
295+ /// - Each **entry point** as a separate tiny Cranelift function that materializes
296+ /// the merge params and `return_call_indirect`s the body.
290297fn gen_function ( cb : & mut CodeBlock , iseq : IseqPtr , version : IseqVersionRef , function : & Function ) -> Result < ( IseqCodePtrs , Vec < CodePtr > , Vec < IseqCallRef > ) , CompileError > {
291- // Count max params across all blocks to determine function ABI params
292- let num_jit_args = max_num_params ( function) ;
293- let mut cl = CraneliftBuilder :: new ( num_jit_args) ;
298+ let reverse_post_order = function. rpo ( ) ;
299+
300+ // Find the merge block — first non-entry, non-entries_super block in RPO
301+ let merge_block_id = reverse_post_order. iter ( ) . copied ( )
302+ . find ( |& bid| bid != function. entries_block && !function. is_entry_block ( bid) )
303+ . expect ( "Function must have a merge block" ) ;
304+ let num_merge_params = function. block ( merge_block_id) . params ( ) . count ( ) ;
305+
306+ // === Step 1: Compile the body function ===
307+ // Signature: (EC, CFP, SP, merge_param0..N) -> VALUE, tail calling convention
308+ // num_args = extra params beyond EC, CFP = SP + merge_params
309+ let body_extra_args = 1 + num_merge_params; // SP, then merge params
310+ let mut cl = CraneliftBuilder :: new ( body_extra_args) ;
294311
295312 // Cranelift operands indexed by HIR InsnId
296313 let mut cl_opnds: Vec < Option < CLValue > > = vec ! [ None ; function. num_insns( ) ] ;
297314 let iseq_calls: Vec < IseqCallRef > = Vec :: new ( ) ;
298- // Patch points to emit after Cranelift compilation
299315 let mut patch_points: Vec < CLPatchPointInfo > = Vec :: new ( ) ;
300- // Call target cells for lazy JIT-to-JIT stubs: (cell_ptr, num_jit_args)
301316 let mut cl_call_cells: Vec < ( * const CLCallTargetCell , usize ) > = Vec :: new ( ) ;
302317
303- cl. build ( |builder, isa, side_exit_blocks, value_pool, ec_var, cfp_var, sp_var, next_var| {
304- // Map HIR blocks → Cranelift blocks
305- let reverse_post_order = function. rpo ( ) ;
318+ cl. build ( |builder, isa, side_exit_blocks, value_pool, ec_var, cfp_var, sp_var, _next_var| {
306319 let mut hir_to_cl: Vec < Option < CLBlock > > = vec ! [ None ; function. num_blocks( ) ] ;
307320
308- // Create Cranelift Variables for each ABI arg so LoadArg can use them
309- // from any block (not just the entry block).
310- let arg_vars: Vec < Variable > = ( 0 ..num_jit_args) . map ( |i| {
311- let var = Variable :: from_u32 ( ( * next_var + i) as u32 ) ;
312- builder. declare_var ( var, cl_types:: I64 ) ;
313- var
314- } ) . collect ( ) ;
315- * next_var += num_jit_args;
316-
317- // Create all Cranelift blocks for HIR blocks
321+ // Create Cranelift blocks for non-entry HIR blocks (skip entries_block and entry blocks)
318322 for & block_id in reverse_post_order. iter ( ) {
319- if block_id == function. entries_block { continue ; }
323+ if block_id == function. entries_block || function . is_entry_block ( block_id ) { continue ; }
320324 let cl_block = builder. create_block ( ) ;
321325 hir_to_cl[ block_id. 0 ] = Some ( cl_block) ;
322326
323- // Add block parameters for HIR block params (SSA phi values)
324- let block = function. block ( block_id) ;
325- for _ in block. params ( ) {
326- builder. append_block_param ( cl_block, cl_types:: I64 ) ;
327+ // For the merge block, don't add block params — its params come from ABI.
328+ // For other blocks, add block params as SSA phi values.
329+ if block_id != merge_block_id {
330+ let block = function. block ( block_id) ;
331+ for _ in block. params ( ) {
332+ builder. append_block_param ( cl_block, cl_types:: I64 ) ;
333+ }
327334 }
328335 }
329336
330- // Emit each HIR block
331- let mut is_first_block = true ;
337+ // Emit each non-entry HIR block
338+ let mut is_first_body_block = true ;
332339 for & block_id in reverse_post_order. iter ( ) {
333- if block_id == function. entries_block { continue ; }
340+ if block_id == function. entries_block || function . is_entry_block ( block_id ) { continue ; }
334341
335342 let cl_block = hir_to_cl[ block_id. 0 ] . unwrap ( ) ;
336343 builder. switch_to_block ( cl_block) ;
337344
338345 let block = function. block ( block_id) ;
339346
340- // On the first block (function entry), append the ABI params
341- // and store EC, CFP, SP, and arg values into Variables.
342- if is_first_block {
347+ // The merge block (first body block) gets its params from function ABI
348+ if is_first_body_block {
343349 builder. append_block_params_for_function_params ( cl_block) ;
344350 let all_params: Vec < CLValue > = builder. block_params ( cl_block) . to_vec ( ) ;
345- // Layout: [block_param_0..N, EC, CFP, arg0, arg1, ...]
346- let num_block_params = block . params ( ) . count ( ) ;
347- let ec_val = all_params[ num_block_params ] ;
348- let cfp_val = all_params[ num_block_params + 1 ] ;
351+ // ABI layout: ( EC, CFP, SP, merge_param_0, merge_param_1, ...)
352+ let ec_val = all_params [ 0 ] ;
353+ let cfp_val = all_params[ 1 ] ;
354+ let sp_val = all_params[ 2 ] ;
349355 builder. def_var ( ec_var, ec_val) ;
350356 builder. def_var ( cfp_var, cfp_val) ;
351-
352- // Load SP from [CFP + RUBY_OFFSET_CFP_SP]
353- let sp_val = builder. ins ( ) . load ( cl_types:: I64 , MemFlags :: trusted ( ) , cfp_val, Offset32 :: new ( RUBY_OFFSET_CFP_SP ) ) ;
354357 builder. def_var ( sp_var, sp_val) ;
355358
356- // Store ABI args into Variables so any block can access them
357- for ( i , & var ) in arg_vars . iter ( ) . enumerate ( ) {
358- builder . def_var ( var , all_params[ num_block_params + 2 + i ] ) ;
359+ // Map merge block params from ABI params [3..]
360+ for ( idx , & insn_id ) in block . params ( ) . enumerate ( ) {
361+ cl_opnds [ insn_id . 0 ] = Some ( all_params[ 3 + idx ] ) ;
359362 }
360363
361- is_first_block = false ;
362- }
363-
364- // Map block parameters (SSA phi values)
365- let block_params : Vec < CLValue > = builder . block_params ( cl_block ) . to_vec ( ) ;
366- for ( idx , & insn_id ) in block . params ( ) . enumerate ( ) {
367- cl_opnds [ insn_id . 0 ] = Some ( block_params [ idx ] ) ;
364+ is_first_body_block = false ;
365+ } else {
366+ // Non-merge blocks: map block params from Cranelift block params
367+ let block_params : Vec < CLValue > = builder . block_params ( cl_block ) . to_vec ( ) ;
368+ for ( idx , & insn_id ) in block . params ( ) . enumerate ( ) {
369+ cl_opnds [ insn_id . 0 ] = Some ( block_params [ idx ] ) ;
370+ }
368371 }
369372
370373 // Compile all instructions in this block
@@ -422,9 +425,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
422425
423426 // === Parameters ===
424427 Insn :: Param => { } // handled above via block params
425- Insn :: LoadArg { idx, .. } => {
426- cl_opnds[ insn_id. 0 ] = Some ( builder. use_var ( arg_vars[ idx as usize ] ) ) ;
427- }
428+ Insn :: LoadArg { .. } => { } // handled in entry point functions
428429
429430 // === Snapshots (no-op) ===
430431 Insn :: Snapshot { .. } => { }
@@ -1264,8 +1265,137 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
12641265 }
12651266 } ) ;
12661267
1267- // Compile and copy to CodeBlock
1268- let ( start_ptr, gc_offsets) = cl. compile ( cb) ?;
1268+ // Compile body and copy to CodeBlock
1269+ let ( body_ptr, gc_offsets) = cl. compile ( cb) ?;
1270+ let body_addr = body_ptr. raw_ptr ( cb) as u64 ;
1271+
1272+ // === Step 2: Compile entry point functions ===
1273+ // Each entry block becomes a tiny function that materializes params and
1274+ // return_call_indirect's the body function.
1275+ let mut jit_entry_ptrs: Vec < CodePtr > = Vec :: new ( ) ;
1276+ let mut interpreter_entry_ptr: Option < CodePtr > = None ;
1277+
1278+ for & block_id in reverse_post_order. iter ( ) {
1279+ if block_id == function. entries_block { continue ; }
1280+ if !function. is_entry_block ( block_id) { continue ; }
1281+
1282+ let block = function. block ( block_id) ;
1283+ let is_interpreter_entry = !block. insns ( ) . any ( |& id| matches ! ( function. find( id) , Insn :: LoadArg { .. } ) ) ;
1284+
1285+ // Count the number of ABI args this entry expects (for JIT entries: LoadArg count)
1286+ let entry_num_args = block. insns ( ) . filter ( |& & id| matches ! ( function. find( id) , Insn :: LoadArg { .. } ) ) . count ( ) ;
1287+
1288+ // Entry function signature: (EC, CFP, args...) -> VALUE
1289+ // Use the default calling convention so the entry trampoline can call us.
1290+ let mut entry_cl = CraneliftBuilder :: new ( entry_num_args) ;
1291+
1292+ entry_cl. build ( |builder, isa, _side_exit_blocks, _value_pool, ec_var, cfp_var, sp_var, _next_var| {
1293+ let entry = builder. create_block ( ) ;
1294+ builder. append_block_params_for_function_params ( entry) ;
1295+ builder. switch_to_block ( entry) ;
1296+
1297+ let params: Vec < CLValue > = builder. block_params ( entry) . to_vec ( ) ;
1298+ let ec_val = params[ 0 ] ;
1299+ let cfp_val = params[ 1 ] ;
1300+ builder. def_var ( ec_var, ec_val) ;
1301+ builder. def_var ( cfp_var, cfp_val) ;
1302+
1303+ // Load SP from CFP
1304+ let sp_val = builder. ins ( ) . load ( cl_types:: I64 , MemFlags :: trusted ( ) , cfp_val, Offset32 :: new ( RUBY_OFFSET_CFP_SP ) ) ;
1305+ builder. def_var ( sp_var, sp_val) ;
1306+
1307+ // Build the merge params by executing this entry block's instructions
1308+ let mut entry_opnds: Vec < Option < CLValue > > = vec ! [ None ; function. num_insns( ) ] ;
1309+
1310+ for & insn_id in block. insns ( ) {
1311+ match function. find ( insn_id) {
1312+ Insn :: LoadArg { idx, .. } => {
1313+ // JIT entry: arg comes from ABI param at position 2+idx
1314+ entry_opnds[ insn_id. 0 ] = Some ( params[ 2 + idx as usize ] ) ;
1315+ }
1316+ Insn :: LoadSelf => {
1317+ let v = builder. ins ( ) . load ( cl_types:: I64 , MemFlags :: trusted ( ) , cfp_val, Offset32 :: new ( RUBY_OFFSET_CFP_SELF ) ) ;
1318+ entry_opnds[ insn_id. 0 ] = Some ( v) ;
1319+ }
1320+ Insn :: LoadSP => {
1321+ entry_opnds[ insn_id. 0 ] = Some ( sp_val) ;
1322+ }
1323+ Insn :: LoadEC => {
1324+ entry_opnds[ insn_id. 0 ] = Some ( ec_val) ;
1325+ }
1326+ Insn :: Const { val : Const :: Value ( val) } => {
1327+ entry_opnds[ insn_id. 0 ] = Some ( builder. ins ( ) . iconst ( cl_types:: I64 , val. as_i64 ( ) ) ) ;
1328+ }
1329+ Insn :: Const { val : Const :: CPtr ( ptr) } => {
1330+ entry_opnds[ insn_id. 0 ] = Some ( builder. ins ( ) . iconst ( cl_types:: I64 , ptr as i64 ) ) ;
1331+ }
1332+ Insn :: LoadField { recv, id : _, offset, .. } => {
1333+ let base = entry_opnds[ recv. 0 ] . unwrap ( ) ;
1334+ let v = builder. ins ( ) . load ( cl_types:: I64 , MemFlags :: trusted ( ) , base, Offset32 :: new ( offset) ) ;
1335+ entry_opnds[ insn_id. 0 ] = Some ( v) ;
1336+ }
1337+ Insn :: GetEP { level } => {
1338+ let mut ep = builder. ins ( ) . load ( cl_types:: I64 , MemFlags :: trusted ( ) , cfp_val, Offset32 :: new ( RUBY_OFFSET_CFP_EP ) ) ;
1339+ for _ in 0 ..level {
1340+ let specval_offset = SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL ;
1341+ ep = builder. ins ( ) . load ( cl_types:: I64 , MemFlags :: trusted ( ) , ep, Offset32 :: new ( specval_offset) ) ;
1342+ let mask = builder. ins ( ) . iconst ( cl_types:: I64 , !0x03i64 ) ;
1343+ ep = builder. ins ( ) . band ( ep, mask) ;
1344+ }
1345+ entry_opnds[ insn_id. 0 ] = Some ( ep) ;
1346+ }
1347+ Insn :: EntryPoint { .. } => { } // no-op
1348+ Insn :: Snapshot { .. } => { } // no-op
1349+ Insn :: Jump ( target) => {
1350+ // This is the jump to the merge block — build the tail call
1351+ let mut body_args = vec ! [ ec_val, cfp_val, sp_val] ;
1352+ for & arg_id in target. args . iter ( ) {
1353+ body_args. push ( entry_opnds[ arg_id. 0 ] . unwrap_or_else ( || {
1354+ panic ! ( "Missing entry opnd for {arg_id} in entry block {block_id}" )
1355+ } ) ) ;
1356+ }
1357+
1358+ // Call the body function and return its result
1359+ let body_sig_params = 3 + num_merge_params; // EC, CFP, SP, merge_params
1360+ let mut sig = cranelift_codegen:: ir:: Signature :: new ( isa. default_call_conv ( ) ) ;
1361+ for _ in 0 ..body_sig_params {
1362+ sig. params . push ( cranelift_codegen:: ir:: AbiParam :: new ( cl_types:: I64 ) ) ;
1363+ }
1364+ sig. returns . push ( cranelift_codegen:: ir:: AbiParam :: new ( cl_types:: I64 ) ) ;
1365+ let sig_ref = builder. import_signature ( sig) ;
1366+ let body_addr_val = builder. ins ( ) . iconst ( cl_types:: I64 , body_addr as i64 ) ;
1367+ let call = builder. ins ( ) . call_indirect ( sig_ref, body_addr_val, & body_args) ;
1368+ let ret = builder. inst_results ( call) [ 0 ] ;
1369+ builder. ins ( ) . return_ ( & [ ret] ) ;
1370+ }
1371+ other => {
1372+ // Skip unknown instructions in entry blocks
1373+ }
1374+ }
1375+ }
1376+
1377+ builder. seal_block ( entry) ;
1378+ } ) ;
1379+
1380+ let ( entry_ptr, _) = entry_cl. compile ( cb) ?;
1381+
1382+ if is_interpreter_entry {
1383+ interpreter_entry_ptr = Some ( entry_ptr) ;
1384+ }
1385+
1386+ // JIT entries: record the pointer
1387+ // The jit_entry_idx is determined by the EntryPoint instruction
1388+ for & insn_id in block. insns ( ) {
1389+ if let Insn :: EntryPoint { jit_entry_idx : Some ( idx) } = function. find ( insn_id) {
1390+ while jit_entry_ptrs. len ( ) <= idx {
1391+ jit_entry_ptrs. push ( entry_ptr) ;
1392+ }
1393+ jit_entry_ptrs[ idx] = entry_ptr;
1394+ }
1395+ }
1396+ }
1397+
1398+ let start_ptr = interpreter_entry_ptr. unwrap_or ( body_ptr) ;
12691399
12701400 // Generate lazy compile stubs for SendDirect call cells
12711401 for & ( cell_ptr, num_args) in cl_call_cells. iter ( ) {
@@ -1340,8 +1470,8 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
13401470 }
13411471 }
13421472
1343- // TODO: support jit_entry_ptrs for JIT-to-JIT calls
1344- Ok ( ( IseqCodePtrs { start_ptr, jit_entry_ptrs : vec ! [ ] } , gc_offsets, iseq_calls) )
1473+ // Entry points compiled above, jit_entry_ptrs populated
1474+ Ok ( ( IseqCodePtrs { start_ptr, jit_entry_ptrs } , gc_offsets, iseq_calls) )
13451475}
13461476
13471477/// Save SP to CFP for Cranelift builder
0 commit comments