@@ -10,10 +10,10 @@ use crate::thread::exceptions::{ExceptionKind, Throws};
1010
1111use std:: cell:: UnsafeCell ;
1212use std:: fmt:: { Debug , Formatter } ;
13- use std:: mem;
1413use std:: sync:: atomic:: { AtomicIsize , Ordering } ;
1514
1615use common:: int_types:: { s1, s2, s4, u1, u2, u4} ;
16+ use instructions:: StackLike ;
1717
1818// https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-2.html#jvms-2.6
1919#[ rustfmt:: skip]
@@ -26,12 +26,24 @@ pub struct Frame {
2626 stack : OperandStack ,
2727 // and a reference to the run-time constant pool (§2.5.5)
2828 constant_pool : & ' static ConstantPool ,
29- method : & ' static Method ,
29+
30+ // Fields outside the spec:
31+
32+ method : & ' static Method ,
3033 thread : UnsafeCell < * const JavaThread > ,
3134
3235 // Used to remember the last pc when we return to a frame after a method invocation
3336 cached_pc : AtomicIsize ,
34- pub depth : isize ,
37+
38+ // TODO: depth should never be > 5, could be packed with flags
39+ // Extra depth within the current instruction
40+ //
41+ // When parsing a bytecode instruction, `pc` stays at the beginning of the instruction. This keeps
42+ // track of any additional bytes we read *after* that bytecode (e.g. arguments for the instruction).
43+ //
44+ // The depth is used at the end of an instruction to calculate the offset to the next instruction.
45+ depth : u16 ,
46+ flags : u8 ,
3547}
3648
3749impl Debug for Frame {
@@ -45,6 +57,15 @@ impl Debug for Frame {
4557 }
4658}
4759
60+ // Flags
61+ impl Frame {
62+ const IN_TAIL_CALL : u8 = 0b1 ;
63+
64+ pub fn in_tail_call ( & self ) -> bool {
65+ self . flags & Self :: IN_TAIL_CALL != 0
66+ }
67+ }
68+
4869impl Frame {
4970 /// Create a new `Frame` for a [`Method`] invocation
5071 ///
@@ -70,8 +91,68 @@ impl Frame {
7091 thread : UnsafeCell :: new ( & raw const * thread) ,
7192 cached_pc : AtomicIsize :: default ( ) ,
7293 depth : 0 ,
94+ flags : 0 ,
7395 } )
96+ }
97+
98+ /// Reuse this frame for a tail method call
99+ ///
100+ /// This will replace the original [`LocalStack`] and return it. It must be retained and used in
101+ /// a subsequent call to [`Self::reset_from_tail_call()`].
102+ ///
103+ /// # Safety
104+ ///
105+ /// The current [`OperandStack`] is retained (including its current position), so the stack
106+ /// ***must*** be setup correctly for the target `method`.
107+ pub ( in crate :: thread) unsafe fn swap_for_tail_call (
108+ & mut self ,
109+ method : & ' static Method ,
110+ ) -> LocalStack {
111+ assert ! ( method. parameter_count( ) as usize <= self . locals. total_slots( ) ) ;
112+ assert ! ( !self . has_stashed_depth( ) ) ;
113+
114+ let mut parameter_count = method. parameter_count ( ) as usize ;
115+ if !method. is_static ( ) {
116+ // receiver
117+ parameter_count += 1 ;
74118 }
119+
120+ let locals = unsafe {
121+ LocalStack :: new_with_args (
122+ self . stack_mut ( ) . popn ( parameter_count) ,
123+ method. code . max_locals as usize ,
124+ )
125+ } ;
126+
127+ let old_locals = core:: mem:: replace ( & mut self . locals , locals) ;
128+ self . constant_pool = method
129+ . class ( )
130+ . constant_pool ( )
131+ . expect ( "Methods do not exist on array classes" ) ;
132+
133+ self . depth = self . depth << 8 ;
134+ self . method = method;
135+ self . flags |= Self :: IN_TAIL_CALL ;
136+
137+ old_locals
138+ }
139+
140+ /// Restore this frame to its state prior to a tail call
141+ ///
142+ /// NOTE: The [`OperandStack`] will be left in whatever state the prior method returned with.
143+ pub ( in crate :: thread) fn reset_from_tail_call (
144+ & mut self ,
145+ old_locals : LocalStack ,
146+ method : & ' static Method ,
147+ ) {
148+ self . locals = old_locals;
149+ self . constant_pool = method
150+ . class ( )
151+ . constant_pool ( )
152+ . expect ( "Methods do not exist on array classes" ) ;
153+ self . depth = self . depth >> 8 ;
154+ self . method = method;
155+ self . flags |= !Self :: IN_TAIL_CALL ;
75156 }
76157}
77158
@@ -127,6 +208,19 @@ impl Frame {
127208 pub fn stashed_pc ( & self ) -> isize {
128209 self . cached_pc . load ( Ordering :: Relaxed )
129210 }
211+
212+ fn depth ( & self ) -> isize {
213+ ( self . depth & 0b1111_1111 ) as isize
214+ }
215+
216+ fn inc_depth ( & mut self ) {
217+ assert ! ( self . depth( ) <= u8 :: MAX as isize ) ;
218+ self . depth = ( self . depth & 0xFF00 ) | ( ( self . depth & 0x00FF ) + 1 ) ;
219+ }
220+
221+ fn has_stashed_depth ( & self ) -> bool {
222+ ( self . depth >> 8 ) > 0
223+ }
130224}
131225
132226// Setters
@@ -159,8 +253,8 @@ impl Frame {
159253 pc = thread. pc . load ( Ordering :: Relaxed ) ;
160254 }
161255
162- let ret = self . method . code . code [ ( pc + self . depth ) as usize ] ;
163- self . depth += 1 ;
256+ let ret = self . method . code . code [ ( pc + self . depth ( ) ) as usize ] ;
257+ self . inc_depth ( ) ;
164258
165259 ret
166260 }
@@ -206,17 +300,20 @@ impl Frame {
206300 ///
207301 /// This is used in the `tableswitch` and `lookupswitch` instructions.
208302 pub fn skip_padding ( & mut self ) {
209- let current_pc = self . thread ( ) . pc . load ( Ordering :: Relaxed ) + self . depth ;
303+ let current_pc = self . thread ( ) . pc . load ( Ordering :: Relaxed ) + self . depth ( ) ;
210304
211305 let mut pc = current_pc;
212306 while pc % 4 != 0 {
213307 pc += 1 ;
214- self . depth += 1 ;
308+ self . inc_depth ( ) ;
215309 }
216310 }
217311
218312 pub fn take_cached_depth ( & mut self ) -> isize {
219- mem:: replace ( & mut self . depth , 0 )
313+ let depth = self . depth ( ) ;
314+ self . depth = 0 ;
315+
316+ depth
220317 }
221318
222319 /// Commit the [pc] to the current [`JavaThread`]
@@ -230,7 +327,7 @@ impl Frame {
230327 let _ = self . thread ( ) . pc . fetch_add ( off, Ordering :: Relaxed ) ;
231328 } ,
232329 PcUpdateStrategy :: FromInstruction => {
233- let _ = self . thread ( ) . pc . fetch_add ( self . depth , Ordering :: Relaxed ) ;
330+ let _ = self . thread ( ) . pc . fetch_add ( self . depth ( ) , Ordering :: Relaxed ) ;
234331 } ,
235332 }
236333
0 commit comments