1- import { strict as assert } from "assert" ;
1+ import { strict as assert } from "node:assert" ;
2+
23import { arrayify } from "./bytes.ts" ;
34import { Dispatch , type Opcode } from "./dispatch.ts" ;
45import { Stack } from "./state.ts" ;
@@ -146,7 +147,7 @@ export const London = Istanbul.fork({
146147// PUSH0: [0x5F, 1, 0],
147148// });
148149
149- class Printer {
150+ export class Printer {
150151 // https://gist.github.com/leommoore/4526808
151152 // https://en.wikipedia.org/wiki/ANSI_escape_code
152153 c = Object . fromEntries ( Object . entries ( {
@@ -158,23 +159,35 @@ class Printer {
158159 blue : 34 ,
159160 magenta : 35 ,
160161 cyan : 36 ,
161- } ) . map ( ( [ name , color ] ) => [ name , ( text : unknown ) => `\x1b[${ color } m${ text } \x1b[0m` ] ) ) ;
162+ // }).map(([name, color]) => [name, (text: unknown) => `\x1b[${color}m${text}\x1b[0m`]));
163+ } ) . map ( ( [ name , color ] ) => [ name , ( text : unknown ) => text ] ) ) ;
164+
165+ readonly #opts;
166+ constructor ( opts : {
167+ expandSingleCopyLocal ?: true ,
168+ expandArgParam ?: true ,
169+ showPcNumber ?: true ,
170+ } = { } ) {
171+ this . #opts = opts ;
172+ }
162173
163174 strExpr ( expr : unknown ) : string {
164175 if ( expr instanceof Lit )
165176 return this . c . blue ( `${ expr . value } n` ) ;
166177 else if ( expr instanceof SExpr )
167178 return `${ this . c . cyan ( expr . ex + '(' ) } ${ expr . args . map ( e => this . strExpr ( e ) ) . join ( ', ' ) } ` + this . c . cyan ( ')' ) ;
168179 else if ( expr instanceof Param )
169- return `$${ expr . id } _${ expr . trigger } ${ expr . index } ` + ( expr . jumpdest ? '*jd' : '' ) + ( expr . arg === undefined ? '' : `:${ this . strExpr ( expr . arg ) } ` )
180+ return `$${ expr . id } _${ expr . trigger } ${ expr . index } ` + ( expr . jumpdest ? '*jd' : '' ) + ( this . #opts . expandArgParam && expr . arg !== undefined ? `:${ this . strExpr ( expr . arg ) } ` : '' )
170181 else if ( expr instanceof Local )
171- return this . c . cyan ( `%${ expr . id } ` ) + ( expr . copies === 1 ? `:${ this . strExpr ( expr . expr ) } ` : '' ) ;
182+ return this . c . cyan ( `%${ expr . id } ` ) + ( this . #opts . expandSingleCopyLocal && expr . copies === 1 ? `:${ this . strExpr ( expr . expr ) } ` : '' ) ;
172183 else
173184 return `${ expr } ` ;
174185 }
175186
176187 strInst ( inst : unknown ) : string {
177- const pc = ( ) => this . c . dim ( ' // :' + ( inst as Inst ) . pc ) ;
188+ const pc = this . #opts. showPcNumber
189+ ? ( ) => this . c . dim ( ' // :' + ( inst as Inst ) . pc )
190+ : ( ) => '' ;
178191 if ( inst instanceof Def ) {
179192 const t = ( inst . local . rettarget ? 'rettarget' : '' ) ;
180193 return this . c . cyan ( `%${ inst . local . id } ` ) + this . c . dim ( `|${ inst . local . copies } :${ inst . local . uses } ` ) + ` := ${ this . strExpr ( inst . local . expr ) } ;${ pc ( ) } ` + t ;
@@ -187,7 +200,7 @@ class Printer {
187200 }
188201}
189202
190- export class SExpr {
203+ class SExpr {
191204 readonly ex : string ;
192205 readonly args : Local [ ] ;
193206 constructor ( ex : string , args : Local [ ] ) {
@@ -296,16 +309,20 @@ class ParamStack extends Stack<Local> {
296309 }
297310} ;
298311
299- class Block {
312+ export class Block {
300313 private readonly state ;
301314 readonly pcend : number ;
302- readonly targets : number [ ] ;
303- constructor ( pcend : number , state : { insts : Inst [ ] , stack : ParamStack } , targets : number [ ] ) {
315+ readonly targets ;
316+ constructor ( pcend : number , state : { insts : Inst [ ] , stack : ParamStack } , targets : { pc : number , pushpc : number , dynamic : boolean } [ ] ) {
304317 this . pcend = pcend ;
305318 this . state = state ;
306319 this . targets = targets ;
307320 }
308321
322+ get id ( ) {
323+ return this . state . stack . args . id ;
324+ }
325+
309326 get insts ( ) : Inst [ ] {
310327 return this . state . insts ;
311328 }
@@ -350,13 +367,16 @@ export class Sevm {
350367 this . bytecode = arrayify ( bytecode ) ;
351368 }
352369
370+ id = 0 ;
371+
353372 exec ( pc0 : number , args : Stack < Local > ) : Block {
354373 // let prevop = undefined;
355374 const state = { insts : [ ] as Inst [ ] , stack : new ParamStack ( args ) } satisfies State ;
375+ state . stack . args . id = this . id ++ ;
356376
357377 // https://stackoverflow.com/questions/72659865/in-typescript-why-is-an-empty-array-inferred-as-any-when-noimplicitany-is-t
358378
359- const targets : number [ ] = [ ] ;
379+ const targets : { pc : number , pushpc : number , dynamic : boolean } [ ] = [ ] ;
360380 let op ;
361381 for ( op of Frontier . decode ( this . bytecode , pc0 ) ) {
362382 // if (op.mnemonic === 'JUMP' || op.mnemonic === 'JUMPI') {
@@ -388,7 +408,7 @@ export class Sevm {
388408 const dest = local . expr . value ;
389409 // console.log(dest);
390410 // TODO dest is in range and less than has valid jump type
391- targets . push ( Number ( dest ) ) ;
411+ targets . push ( { pc : Number ( dest ) , pushpc : op . pc , dynamic : false } ) ;
392412 } else {
393413 assert ( local instanceof Param ) ;
394414 local . jumpdest = true ;
@@ -401,24 +421,24 @@ export class Sevm {
401421 // console.log('to', t);
402422 local . arg . rettarget = true ;
403423 // TODO dest is in range and less than has valid jump type
404- targets . push ( t ) ;
405424 const h = findHeaderPc ( local . arg . pc , [ ...this . blocks . keys ( ) ] ) ;
406- console . log ( 'header' , h ) ;
425+ // console.log('header', h);
407426 const [ { pcend } ] = this . blocks . get ( h ) ! ;
408- console . log ( pcend ) ;
427+ // console.log(pcend);
409428 if ( pcend === t ) {
410- console . log ( 'ret' ) ;
429+ // console.log('ret');
411430 last . isret = true ;
412431 }
432+ targets . push ( { pc : t , pushpc : local . arg . pc , dynamic : true } ) ;
413433 }
414434 }
415435 if ( op . mnemonic === 'JUMPI' ) {
416436 // TODO check target pc is within bytecode boundaries
417- targets . push ( op . pc + 1 ) ;
437+ targets . push ( { pc : op . pc + 1 , pushpc : pc0 , dynamic : false } ) ;
418438 }
419439 break ;
420440 } else if ( this . bytecode [ op . nextpc ] === Frontier . def . JUMPDEST . op ) {
421- targets . push ( op . nextpc ) ;
441+ targets . push ( { pc : op . nextpc , pushpc : pc0 , dynamic : false } ) ;
422442 break ;
423443 }
424444 }
@@ -431,11 +451,12 @@ export class Sevm {
431451
432452 // }
433453
434- const frames = [ { pc : 0 , args : new Stack < Local > ( ) } ] ;
454+ const frames = [ { pc : 0 , args : new Stack < Local > ( ) , path : [ 0 ] } ] ;
435455
456+ console . log ( 'flowchart TD' ) ;
436457 while ( frames . length > 0 ) {
437- const { pc, args } = frames . pop ( ) ! ;
438- console . log ( 'visiting' , pc ) ;
458+ const { pc, args, path } = frames . pop ( ) ! ;
459+ // console.log('visiting', pc);
439460 const block = this . exec ( pc , args ) ;
440461 {
441462 let clones = this . blocks . get ( pc ) ;
@@ -448,7 +469,17 @@ export class Sevm {
448469
449470 // TODO avoid cycling on loops
450471 // TODO back-propagate used args
451- frames . push ( ...block . targets . map ( pc => ( { pc, args : new Stack ( [ ...block . outs , ...block . unused ] ) } ) ) ) ;
472+ for ( const destpc of block . targets ) {
473+ destpc . args = new Stack ( [ ...block . outs , ...block . unused ] ) ;
474+ // const arrow = `${pc}->${destpc.pc}`;
475+ const d = destpc . dynamic ? '==' : '--' ;
476+ console . log ( ' pc' + pc , ` ${ d } ${ destpc . pushpc } ${ d } > ` , 'pc' + destpc . pc ) ;
477+ const p = destpc . dynamic ? [ ] : path ;
478+ if ( ! p . includes ( destpc . pc ) ) {
479+ frames . push ( { pc : destpc . pc , args : destpc . args , path : [ ...p , destpc . pc ] } ) ;
480+ }
481+ }
482+ // frames.push(...block.targets.map(({ pc }) => ({ pc, args: new Stack([...block.outs, ...block.unused]) })));
452483 }
453484
454485 return this . blocks ;
@@ -461,19 +492,22 @@ export function run(bytecode: Parameters<typeof arrayify>[0]) {
461492 for ( const [ pc , blocks ] of bbs . entries ( ) ) {
462493 for ( const block of blocks ) {
463494 console . log ( p . c . yellow ( 'block_' + pc ) , block . params . length , '|= ' + block . params . map ( e => p . strExpr ( e ) ) . join ( ' | ' ) + '' ) ;
495+ console . log ( ' ' , block . id )
464496 // console.log('visited', visits.get(pc)!.count)
465497 for ( const inst of block . insts ) {
466498 console . log ( ` ${ p . strInst ( inst ) } ` ) ;
467499 }
468500 const un = '|= unused ' + `${ block . unused . map ( e => p . strExpr ( e ) ) . join ( ' | ' ) } ` ;
469501 console . log ( ' ->' , block . targets , '|= outs' , `${ block . outs . map ( e => p . strExpr ( e ) ) . join ( ' | ' ) } ` , un ) ;
470502 for ( const pcdest of block . targets ) {
471- const t = bbs . get ( pcdest ) ;
503+ const t = bbs . get ( pcdest . pc ) ;
472504 assert ( t !== undefined ) ;
473505 // if (t.params.length > block.outs.length) {
474506 // console.log(' not enough args');
475507 // }
476508 }
477509 }
478510 }
511+
512+ return bbs ;
479513}
0 commit comments