11use super :: {
2- NativeBackend , STATUS_CONTINUE , STATUS_ERROR , STATUS_HALTED , STATUS_TRACE_EXIT , STATUS_WAITING ,
3- STATUS_YIELDED ,
2+ NativeBackend , STATUS_CONTINUE , STATUS_ERROR , STATUS_HALTED , STATUS_OUT_OF_FUEL ,
3+ STATUS_TRACE_EXIT , STATUS_WAITING , STATUS_YIELDED ,
44} ;
55use crate :: vm:: jit:: TraceStep ;
66use crate :: vm:: { HostCallExecOutcome , Value , Vm , VmError , VmResult } ;
@@ -11,8 +11,11 @@ pub(super) struct AArch64Backend;
1111impl NativeBackend for AArch64Backend {
1212 type ExecutableMemory = BackendExecutableMemory ;
1313
14- fn emit_trace_bytes ( trace : & crate :: vm:: jit:: JitTrace ) -> VmResult < Vec < u8 > > {
15- emit_native_trace_bytes ( trace)
14+ fn emit_trace_bytes (
15+ trace : & crate :: vm:: jit:: JitTrace ,
16+ fuel_check_interval : Option < u32 > ,
17+ ) -> VmResult < Vec < u8 > > {
18+ emit_native_trace_bytes ( trace, fuel_check_interval)
1619 }
1720
1821 fn executable_memory_from_code ( code : & [ u8 ] ) -> VmResult < Self :: ExecutableMemory > {
@@ -86,6 +89,9 @@ struct NativeStackLayout {
8689 vm_program_constants_ptr_offset : i32 ,
8790 vm_program_constants_len_offset : i32 ,
8891 vm_ip_offset : i32 ,
92+ vm_fuel_enabled_offset : i32 ,
93+ vm_fuel_remaining_offset : i32 ,
94+ vm_fuel_ops_until_check_offset : i32 ,
8995 stack_vec : VecLayout ,
9096 value : ValueLayout ,
9197}
@@ -126,15 +132,38 @@ fn trace_ip_to_u64(ip: usize, context: &str) -> VmResult<u64> {
126132 u64:: try_from ( ip) . map_err ( |_| VmError :: JitNative ( format ! ( "{context} exceeds 64-bit range" ) ) )
127133}
128134
129- fn emit_native_trace_bytes ( trace : & crate :: vm:: jit:: JitTrace ) -> VmResult < Vec < u8 > > {
135+ fn emit_native_trace_bytes (
136+ trace : & crate :: vm:: jit:: JitTrace ,
137+ fuel_check_interval : Option < u32 > ,
138+ ) -> VmResult < Vec < u8 > > {
139+ if matches ! ( fuel_check_interval, Some ( 0 ) ) {
140+ return Err ( VmError :: InvalidFuelCheckInterval ( 0 ) ) ;
141+ }
142+
130143 let layout = detect_native_stack_layout ( ) ?;
131144 let mut code = Vec :: with_capacity ( 1024 ) ;
132145 let mut status_checks = Vec :: new ( ) ;
133146
134147 emit_native_prologue ( & mut code) ;
135148 emit_status_continue ( & mut code) ;
136149
137- for step in & trace. steps {
150+ for ( step_index, step) in trace. steps . iter ( ) . enumerate ( ) {
151+ if let Some ( interval) = fuel_check_interval {
152+ let stride = interval as usize ;
153+ if step_index % stride == 0 {
154+ let remaining = trace. steps . len ( ) . saturating_sub ( step_index) ;
155+ let chunk_len = remaining. min ( stride) as u32 ;
156+ emit_step_with_status ! (
157+ code,
158+ status_checks => emit_native_fuel_tick_inline(
159+ & mut code,
160+ layout,
161+ chunk_len,
162+ interval,
163+ )
164+ ) ;
165+ }
166+ }
138167 match step {
139168 TraceStep :: Nop => { }
140169 TraceStep :: Ldc ( index) => {
@@ -359,6 +388,64 @@ fn emit_status_halted(code: &mut Vec<u8>) {
359388 emit_mov_imm64 ( code, 0 , STATUS_HALTED as i64 as u64 ) ;
360389}
361390
391+ fn emit_native_fuel_tick_inline (
392+ code : & mut Vec < u8 > ,
393+ layout : NativeStackLayout ,
394+ steps_to_advance : u32 ,
395+ fuel_check_interval : u32 ,
396+ ) -> VmResult < ( ) > {
397+ if steps_to_advance == 0 {
398+ return Ok ( ( ) ) ;
399+ }
400+
401+ emit_ldr_b_disp ( code, 14 , VM_REG , layout. vm_fuel_enabled_offset ) ?;
402+ emit_cmp_imm ( code, 14 , 0 ) ?;
403+ let metering_disabled = emit_b_cond_placeholder ( code, Cond :: Eq ) ;
404+
405+ emit_ldr_w_disp ( code, 15 , VM_REG , layout. vm_fuel_ops_until_check_offset ) ?;
406+ emit_mov_imm64 ( code, 16 , u64:: from ( steps_to_advance) ) ;
407+ emit_cmp_reg ( code, 15 , 16 ) ;
408+ let countdown = emit_b_cond_placeholder ( code, Cond :: Hi ) ;
409+
410+ emit_ldr_x_disp ( code, 17 , VM_REG , layout. vm_fuel_remaining_offset ) ?;
411+ emit_mov_imm64 ( code, 16 , u64:: from ( fuel_check_interval) ) ;
412+ emit_mov_reg ( code, 18 , 16 ) ;
413+ emit_cmp_reg ( code, 17 , 18 ) ;
414+ let out_of_fuel = emit_b_cond_placeholder ( code, Cond :: Lo ) ;
415+ emit_sub_reg ( code, 17 , 17 , 18 ) ;
416+ emit_str_x_disp ( code, 17 , VM_REG , layout. vm_fuel_remaining_offset ) ?;
417+ emit_mov_imm64 ( code, 18 , u64:: from ( steps_to_advance) ) ;
418+ emit_sub_reg ( code, 18 , 18 , 15 ) ;
419+ emit_mov_imm64 ( code, 16 , u64:: from ( fuel_check_interval) ) ;
420+ emit_sub_reg ( code, 16 , 16 , 18 ) ;
421+ emit_str_w_disp ( code, 16 , VM_REG , layout. vm_fuel_ops_until_check_offset ) ?;
422+ emit_status_continue ( code) ;
423+ let charge_done = emit_b_placeholder ( code) ;
424+
425+ let countdown_label = code. len ( ) ;
426+ emit_mov_imm64 ( code, 16 , u64:: from ( steps_to_advance) ) ;
427+ emit_sub_reg ( code, 15 , 15 , 16 ) ;
428+ emit_str_w_disp ( code, 15 , VM_REG , layout. vm_fuel_ops_until_check_offset ) ?;
429+ emit_status_continue ( code) ;
430+ let countdown_done = emit_b_placeholder ( code) ;
431+
432+ let disabled_label = code. len ( ) ;
433+ emit_status_continue ( code) ;
434+ let disabled_done = emit_b_placeholder ( code) ;
435+
436+ let out_of_fuel_label = code. len ( ) ;
437+ emit_mov_imm64 ( code, 0 , STATUS_OUT_OF_FUEL as i64 as u64 ) ;
438+
439+ let done_label = code. len ( ) ;
440+ patch_b_cond_rel19 ( code, metering_disabled, disabled_label) ?;
441+ patch_b_cond_rel19 ( code, countdown, countdown_label) ?;
442+ patch_b_cond_rel19 ( code, out_of_fuel, out_of_fuel_label) ?;
443+ patch_b_rel26 ( code, charge_done, done_label) ?;
444+ patch_b_rel26 ( code, countdown_done, done_label) ?;
445+ patch_b_rel26 ( code, disabled_done, done_label) ?;
446+ Ok ( ( ) )
447+ }
448+
362449#[ derive( Clone , Copy ) ]
363450enum NativeBinaryNumericOp {
364451 Add ,
@@ -1489,6 +1576,18 @@ fn detect_native_stack_layout_uncached() -> VmResult<NativeStackLayout> {
14891576 "Vm::program_constants_len offset" ,
14901577 ) ?;
14911578 let vm_ip_offset = usize_to_i32 ( std:: mem:: offset_of!( Vm , ip) , "Vm::ip offset" ) ?;
1579+ let vm_fuel_enabled_offset = usize_to_i32 (
1580+ std:: mem:: offset_of!( Vm , fuel_enabled) ,
1581+ "Vm::fuel_enabled offset" ,
1582+ ) ?;
1583+ let vm_fuel_remaining_offset = usize_to_i32 (
1584+ std:: mem:: offset_of!( Vm , fuel_remaining) ,
1585+ "Vm::fuel_remaining offset" ,
1586+ ) ?;
1587+ let vm_fuel_ops_until_check_offset = usize_to_i32 (
1588+ std:: mem:: offset_of!( Vm , fuel_ops_until_check) ,
1589+ "Vm::fuel_ops_until_check offset" ,
1590+ ) ?;
14921591 let stack_vec = detect_vec_layout ( ) ?;
14931592 let value = detect_value_layout ( ) ?;
14941593 Ok ( NativeStackLayout {
@@ -1497,6 +1596,9 @@ fn detect_native_stack_layout_uncached() -> VmResult<NativeStackLayout> {
14971596 vm_program_constants_ptr_offset,
14981597 vm_program_constants_len_offset,
14991598 vm_ip_offset,
1599+ vm_fuel_enabled_offset,
1600+ vm_fuel_remaining_offset,
1601+ vm_fuel_ops_until_check_offset,
15001602 stack_vec,
15011603 value,
15021604 } )
@@ -2495,9 +2597,22 @@ mod tests {
24952597 }
24962598 }
24972599
2600+ fn build_trace ( steps : Vec < TraceStep > ) -> JitTrace {
2601+ JitTrace {
2602+ id : 0 ,
2603+ root_ip : 0 ,
2604+ start_line : None ,
2605+ has_call : false ,
2606+ has_yielding_call : false ,
2607+ steps,
2608+ terminal : JitTraceTerminal :: LoopBack ,
2609+ executions : 0 ,
2610+ }
2611+ }
2612+
24982613 fn execute_single_step ( vm : & mut Vm , step : TraceStep ) -> VmResult < i32 > {
24992614 let trace = build_single_step_trace ( step) ;
2500- let code = emit_native_trace_bytes ( & trace) ?;
2615+ let code = emit_native_trace_bytes ( & trace, None ) ?;
25012616 let memory = BackendExecutableMemory :: from_code ( & code) ?;
25022617 let entry = unsafe { std:: mem:: transmute :: < * mut u8 , NativeEntry > ( memory. ptr ) } ;
25032618 clear_bridge_error ( ) ;
@@ -2507,7 +2622,7 @@ mod tests {
25072622 }
25082623
25092624 fn execute_trace ( vm : & mut Vm , trace : JitTrace ) -> VmResult < i32 > {
2510- let code = emit_native_trace_bytes ( & trace) ?;
2625+ let code = emit_native_trace_bytes ( & trace, None ) ?;
25112626 let memory = BackendExecutableMemory :: from_code ( & code) ?;
25122627 let entry = unsafe { std:: mem:: transmute :: < * mut u8 , NativeEntry > ( memory. ptr ) } ;
25132628 clear_bridge_error ( ) ;
@@ -2520,7 +2635,7 @@ mod tests {
25202635 vm : & mut Vm ,
25212636 trace : JitTrace ,
25222637 ) -> VmResult < ( i32 , BackendExecutableMemory ) > {
2523- let code = emit_native_trace_bytes ( & trace) ?;
2638+ let code = emit_native_trace_bytes ( & trace, None ) ?;
25242639 let memory = BackendExecutableMemory :: from_code ( & code) ?;
25252640 let entry = unsafe { std:: mem:: transmute :: < * mut u8 , NativeEntry > ( memory. ptr ) } ;
25262641 clear_bridge_error ( ) ;
@@ -2683,7 +2798,7 @@ mod tests {
26832798 #[ test]
26842799 fn add_step_emits_no_helper_call ( ) {
26852800 let trace = build_single_step_trace ( TraceStep :: Add ) ;
2686- let code = emit_native_trace_bytes ( & trace) . expect ( "trace should compile" ) ;
2801+ let code = emit_native_trace_bytes ( & trace, None ) . expect ( "trace should compile" ) ;
26872802 let call_count = code
26882803 . chunks_exact ( 4 )
26892804 . filter ( |chunk| {
@@ -2693,14 +2808,41 @@ mod tests {
26932808 assert_eq ! ( call_count, 0 , "add should not emit helper calls" ) ;
26942809 }
26952810
2811+ #[ test]
2812+ fn fuel_tick_emission_is_optional ( ) {
2813+ let trace = build_single_step_trace ( TraceStep :: Add ) ;
2814+ let without_fuel_checks = emit_native_trace_bytes ( & trace, None ) . expect ( "trace should compile" ) ;
2815+ let with_fuel_checks =
2816+ emit_native_trace_bytes ( & trace, Some ( 1 ) ) . expect ( "trace should compile" ) ;
2817+ assert ! (
2818+ with_fuel_checks. len( ) > without_fuel_checks. len( ) ,
2819+ "fuel checks should add machine code; without={} with={}" ,
2820+ without_fuel_checks. len( ) ,
2821+ with_fuel_checks. len( )
2822+ ) ;
2823+ }
2824+
2825+ #[ test]
2826+ fn larger_fuel_intervals_emit_fewer_tick_blocks ( ) {
2827+ let trace = build_trace ( vec ! [ TraceStep :: Nop ; 32 ] ) ;
2828+ let every_step = emit_native_trace_bytes ( & trace, Some ( 1 ) ) . expect ( "trace should compile" ) ;
2829+ let every_eight = emit_native_trace_bytes ( & trace, Some ( 8 ) ) . expect ( "trace should compile" ) ;
2830+ assert ! (
2831+ every_eight. len( ) < every_step. len( ) ,
2832+ "expected fewer fuel tick blocks for interval=8; interval=1={} interval=8={}" ,
2833+ every_step. len( ) ,
2834+ every_eight. len( )
2835+ ) ;
2836+ }
2837+
26962838 #[ test]
26972839 fn call_step_emits_helper_call ( ) {
26982840 let trace = build_single_step_trace ( TraceStep :: Call {
26992841 index : 0 ,
27002842 argc : 1 ,
27012843 call_ip : 0 ,
27022844 } ) ;
2703- let code = emit_native_trace_bytes ( & trace) . expect ( "trace should compile" ) ;
2845+ let code = emit_native_trace_bytes ( & trace, None ) . expect ( "trace should compile" ) ;
27042846 let call_count = code
27052847 . chunks_exact ( 4 )
27062848 . filter ( |chunk| {
@@ -2846,7 +2988,7 @@ mod tests {
28462988 #[ test]
28472989 fn jump_to_root_step_returns_trace_exit ( ) {
28482990 let trace = build_single_step_trace ( TraceStep :: JumpToRoot ) ;
2849- let code = emit_native_trace_bytes ( & trace) . expect ( "jump trace should compile" ) ;
2991+ let code = emit_native_trace_bytes ( & trace, None ) . expect ( "jump trace should compile" ) ;
28502992 let mut found_cbnz = false ;
28512993 for chunk in code. chunks_exact ( 4 ) {
28522994 let insn = u32:: from_le_bytes ( [ chunk[ 0 ] , chunk[ 1 ] , chunk[ 2 ] , chunk[ 3 ] ] ) ;
@@ -2905,7 +3047,7 @@ mod tests {
29053047 executions : 0 ,
29063048 } ;
29073049
2908- let code = emit_native_trace_bytes ( & trace) . expect ( "all steps should emit" ) ;
3050+ let code = emit_native_trace_bytes ( & trace, None ) . expect ( "all steps should emit" ) ;
29093051 assert ! ( !code. is_empty( ) ) ;
29103052 }
29113053
0 commit comments