@@ -156,6 +156,316 @@ impl Assembler {
156156 self . instructions . is_empty ( )
157157 }
158158
159+ /// Jump threading: if Label(X) is immediately followed by JumpTo(Y)
160+ /// (skipping Comments), rewrite any JumpTo(X)/JumpITo(X)/PushLabel(X) to
161+ /// target Y instead. Iterates to a fixed point for chains.
162+ pub fn thread_jumps ( & mut self ) {
163+ use std:: collections:: HashMap ;
164+ loop {
165+ // Build redirect map: label X → label Y when Label(X) is followed by JumpTo(Y)
166+ let mut redirects: HashMap < String , String > = HashMap :: new ( ) ;
167+ let mut i = 0 ;
168+ while i < self . instructions . len ( ) {
169+ if let AsmInstruction :: Label ( ref label) = self . instructions [ i] {
170+ // Find next non-comment instruction after this label
171+ let mut j = i + 1 ;
172+ while j < self . instructions . len ( ) {
173+ if matches ! ( self . instructions[ j] , AsmInstruction :: Comment ( _) ) {
174+ j += 1 ;
175+ } else {
176+ break ;
177+ }
178+ }
179+ if j < self . instructions . len ( ) {
180+ if let AsmInstruction :: JumpTo ( ref target) = self . instructions [ j] {
181+ if target != label {
182+ redirects. insert ( label. clone ( ) , target. clone ( ) ) ;
183+ }
184+ }
185+ }
186+ }
187+ i += 1 ;
188+ }
189+
190+ if redirects. is_empty ( ) {
191+ break ;
192+ }
193+
194+ // Apply redirects
195+ let mut changed = false ;
196+ for inst in & mut self . instructions {
197+ let target = match inst {
198+ AsmInstruction :: JumpTo ( ref t)
199+ | AsmInstruction :: JumpITo ( ref t)
200+ | AsmInstruction :: PushLabel ( ref t) => redirects. get ( t) . cloned ( ) ,
201+ _ => None ,
202+ } ;
203+ if let Some ( new_target) = target {
204+ match inst {
205+ AsmInstruction :: JumpTo ( ref mut t)
206+ | AsmInstruction :: JumpITo ( ref mut t)
207+ | AsmInstruction :: PushLabel ( ref mut t) => {
208+ * t = new_target;
209+ changed = true ;
210+ }
211+ _ => { }
212+ }
213+ }
214+ }
215+
216+ if !changed {
217+ break ;
218+ }
219+ }
220+
221+ // Remove dead labels: Label(X) that no jump/push references anymore.
222+ // Also remove the JumpTo that follows a dead label (and intervening comments).
223+ use std:: collections:: HashSet ;
224+ let referenced: HashSet < String > = self
225+ . instructions
226+ . iter ( )
227+ . filter_map ( |inst| match inst {
228+ AsmInstruction :: JumpTo ( t)
229+ | AsmInstruction :: JumpITo ( t)
230+ | AsmInstruction :: PushLabel ( t) => Some ( t. clone ( ) ) ,
231+ _ => None ,
232+ } )
233+ . collect ( ) ;
234+
235+ let mut keep = vec ! [ true ; self . instructions. len( ) ] ;
236+ let mut i = 0 ;
237+ while i < self . instructions . len ( ) {
238+ if let AsmInstruction :: Label ( ref label) = self . instructions [ i] {
239+ if !referenced. contains ( label) {
240+ // Mark label and subsequent comments + JumpTo for removal
241+ keep[ i] = false ;
242+ let mut j = i + 1 ;
243+ while j < self . instructions . len ( ) {
244+ match & self . instructions [ j] {
245+ AsmInstruction :: Comment ( _) => {
246+ keep[ j] = false ;
247+ j += 1 ;
248+ }
249+ AsmInstruction :: JumpTo ( _) => {
250+ keep[ j] = false ;
251+ break ;
252+ }
253+ _ => break ,
254+ }
255+ }
256+ }
257+ }
258+ i += 1 ;
259+ }
260+
261+ let mut idx = 0 ;
262+ self . instructions . retain ( |_| {
263+ let k = keep[ idx] ;
264+ idx += 1 ;
265+ k
266+ } ) ;
267+ }
268+
269+ /// Eliminate SWAP1+POP cleanup chains that precede a halting sequence.
270+ ///
271+ /// Within a basic block (between labels), if a contiguous chain of SWAP1+POP
272+ /// pairs is followed by a "clean terminal" sequence (only Push/MSTORE/RETURN/
273+ /// REVERT/STOP, no DUPs or SWAPs), the chain can be removed. The SWAP1+POP
274+ /// chain preserves TOS while removing elements below it; since the terminal
275+ /// sequence only uses TOS and freshly pushed values, the removed elements
276+ /// are never accessed. RETURN/REVERT/STOP halts execution so leftover stack
277+ /// junk is harmless.
278+ pub fn eliminate_pre_halt_cleanup ( & mut self ) {
279+ use std:: collections:: HashSet ;
280+
281+ // Phase 1: Identify "halting labels" — labels whose basic block ends
282+ // with RETURN/REVERT/STOP (or JUMP to another halting label).
283+ // Iterate to a fixed point for chains.
284+ let mut halting_labels: HashSet < String > = HashSet :: new ( ) ;
285+ loop {
286+ let mut changed = false ;
287+ let mut current_label: Option < String > = None ;
288+ for inst in & self . instructions {
289+ match inst {
290+ AsmInstruction :: Label ( name) => {
291+ current_label = Some ( name. clone ( ) ) ;
292+ }
293+ AsmInstruction :: Op ( Opcode :: Return )
294+ | AsmInstruction :: Op ( Opcode :: Revert )
295+ | AsmInstruction :: Op ( Opcode :: Stop )
296+ | AsmInstruction :: Op ( Opcode :: Invalid ) => {
297+ if let Some ( ref label) = current_label {
298+ if halting_labels. insert ( label. clone ( ) ) {
299+ changed = true ;
300+ }
301+ }
302+ }
303+ AsmInstruction :: JumpTo ( target) => {
304+ if halting_labels. contains ( target) {
305+ if let Some ( ref label) = current_label {
306+ if halting_labels. insert ( label. clone ( ) ) {
307+ changed = true ;
308+ }
309+ }
310+ }
311+ }
312+ // JumpITo doesn't unconditionally halt — skip
313+ _ => { }
314+ }
315+ }
316+ if !changed {
317+ break ;
318+ }
319+ }
320+
321+ // Phase 2: For each basic block, check if it ends in a halt or
322+ // unconditional jump to a halting label. If so, remove SWAP1+POP
323+ // chains that immediately precede the terminal sequence.
324+ let len = self . instructions . len ( ) ;
325+ let mut keep = vec ! [ true ; len] ;
326+
327+ // Find block boundaries and terminal instructions
328+ let mut i = 0 ;
329+ while i < len {
330+ // Find the end of this basic block: next Label, or end of instructions
331+ let block_start = i;
332+ let mut block_end = i;
333+ let mut terminal_idx = None ;
334+
335+ let mut j = if matches ! ( & self . instructions[ i] , AsmInstruction :: Label ( _) ) {
336+ i + 1
337+ } else {
338+ i
339+ } ;
340+
341+ while j < len {
342+ match & self . instructions [ j] {
343+ AsmInstruction :: Label ( name) => {
344+ // Fallthrough to next block — if the target is halting,
345+ // use the label position as the boundary so the backward
346+ // walk finds the SWAP1+POP chain at the end of this block.
347+ if halting_labels. contains ( name) {
348+ terminal_idx = Some ( j) ;
349+ }
350+ block_end = j;
351+ break ;
352+ }
353+ AsmInstruction :: Op ( Opcode :: Return )
354+ | AsmInstruction :: Op ( Opcode :: Revert )
355+ | AsmInstruction :: Op ( Opcode :: Stop )
356+ | AsmInstruction :: Op ( Opcode :: Invalid ) => {
357+ terminal_idx = Some ( j) ;
358+ // Mark everything after the halt in this block as dead
359+ let mut k = j + 1 ;
360+ while k < len && !matches ! ( & self . instructions[ k] , AsmInstruction :: Label ( _) )
361+ {
362+ keep[ k] = false ;
363+ k += 1 ;
364+ }
365+ block_end = k;
366+ break ;
367+ }
368+ AsmInstruction :: JumpTo ( target) => {
369+ if halting_labels. contains ( target) {
370+ terminal_idx = Some ( j) ;
371+ }
372+ block_end = j + 1 ;
373+ break ;
374+ }
375+ AsmInstruction :: JumpITo ( _) => {
376+ // Conditional jump — not a clean terminal
377+ block_end = j + 1 ;
378+ break ;
379+ }
380+ _ => {
381+ j += 1 ;
382+ }
383+ }
384+ }
385+ if j >= len {
386+ block_end = len;
387+ }
388+
389+ // If we found a terminal, walk backward to find the "clean terminal"
390+ // start, then further backward to find SWAP1+POP chain
391+ if let Some ( term) = terminal_idx {
392+ // Walk backward past the clean terminal sequence
393+ // (Push, Push0, MStore, MStore8, Comments — no DUP/SWAP/other)
394+ let mut setup_start = term;
395+ while setup_start > block_start {
396+ let prev = setup_start - 1 ;
397+ match & self . instructions [ prev] {
398+ AsmInstruction :: Op ( Opcode :: MStore | Opcode :: MStore8 | Opcode :: Push0 )
399+ | AsmInstruction :: Push ( _)
400+ | AsmInstruction :: Comment ( _) => {
401+ setup_start = prev;
402+ }
403+ _ => break ,
404+ }
405+ }
406+
407+ // Check for redundant DUP1 before the clean terminal: DUP1 copies
408+ // TOS for MStore consumption, but if RETURN follows the original
409+ // copy is never used and the DUP1 can be removed.
410+ if setup_start > block_start {
411+ let prev = setup_start - 1 ;
412+ if matches ! ( & self . instructions[ prev] , AsmInstruction :: Op ( Opcode :: Dup1 ) ) {
413+ keep[ prev] = false ;
414+ setup_start = prev;
415+ }
416+ }
417+
418+ // Walk backward past SWAP1+POP chain
419+ let mut chain_start = setup_start;
420+ while chain_start >= block_start + 2 {
421+ let pop_idx = chain_start - 1 ;
422+ let swap_idx = chain_start - 2 ;
423+ // Skip comments between swap and pop
424+ if matches ! ( & self . instructions[ pop_idx] , AsmInstruction :: Op ( Opcode :: Pop ) )
425+ && matches ! (
426+ & self . instructions[ swap_idx] ,
427+ AsmInstruction :: Op ( Opcode :: Swap1 )
428+ )
429+ {
430+ chain_start = swap_idx;
431+ } else if matches ! ( & self . instructions[ pop_idx] , AsmInstruction :: Comment ( _) ) {
432+ // Skip comment and try again
433+ chain_start = pop_idx;
434+ } else {
435+ break ;
436+ }
437+ }
438+
439+ // Mark SWAP1+POP pairs in the chain for removal
440+ if chain_start < setup_start {
441+ let mut k = chain_start;
442+ while k < setup_start {
443+ match & self . instructions [ k] {
444+ AsmInstruction :: Op ( Opcode :: Swap1 ) | AsmInstruction :: Op ( Opcode :: Pop ) => {
445+ keep[ k] = false ;
446+ }
447+ _ => { } // keep comments
448+ }
449+ k += 1 ;
450+ }
451+ }
452+ }
453+
454+ i = if block_end > block_start {
455+ block_end
456+ } else {
457+ block_start + 1
458+ } ;
459+ }
460+
461+ let mut idx = 0 ;
462+ self . instructions . retain ( |_| {
463+ let k = keep[ idx] ;
464+ idx += 1 ;
465+ k
466+ } ) ;
467+ }
468+
159469 /// Assemble into final bytecode, resolving all labels to offsets.
160470 ///
161471 /// Tries PUSH1 (short) jumps first. If any label offset >= 256,
0 commit comments