Skip to content

Commit 6b26958

Browse files
committed
wip
1 parent a495fe2 commit 6b26958

11 files changed

Lines changed: 730 additions & 183 deletions

File tree

bin/sevm.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env node
2+
3+
import { getHashes, hash } from 'node:crypto';
4+
5+
import js_sha3 from 'js-sha3';
6+
7+
function main() {
8+
console.log(process.versions);
9+
console.log(getHashes());
10+
11+
console.log(js_sha3.keccak256(''));
12+
console.log(js_sha3.keccak256(new Uint8Array()));
13+
console.log(hash('sha3-256', ''));
14+
console.log(hash('sha3-256', new Uint8Array()));
15+
console.log(hash('keccak-256', ''));
16+
}
17+
18+
main();

src/ast.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
2+
3+
4+
function renderBlock(pc: number, indent: number) {
5+
const p = new Printer();
6+
const [block] = bbs.get(pc)!;
7+
console.log(' '.repeat(indent) + p.c.yellow('block_' + pc), block.params.length, '|= ' + block.params.map(e => p.strExpr(e)).join(' | ') + '');
8+
// console.log(' ', block.id)
9+
// console.log('visited', visits.get(pc)!.count)
10+
for (const inst of block.insts) {
11+
console.log(' '.repeat(indent) + `${p.strInst(inst)}`);
12+
}
13+
const un = '|= unused ' + `${block.unused.map(e => p.strExpr(e)).join(' | ')}`;
14+
// console.log(' ->', block.targets, '|= outs', `${block.outs.map(e => p.strExpr(e)).join(' | ')}`, un);
15+
for (const pcdest of block.targets) {
16+
const t = bbs.get(pcdest.pc);
17+
assert(t !== undefined);
18+
// if (t.params.length > block.outs.length) {
19+
// console.log(' not enough args');
20+
// }
21+
}
22+
}
23+
24+
function renderAst(blocks: { pc: number }[], indent = 0) {
25+
26+
for (const b of blocks) {
27+
renderBlock(b.pc, indent);
28+
if ('inst' in b && b.inst === 'if') {
29+
if ('tb' in b)
30+
renderAst(b.tb, indent + 2);
31+
if ('fb' in b) {
32+
console.log(' '.repeat(indent) + 'else');
33+
renderAst(b.fb, indent + 2);
34+
}
35+
}
36+
}
37+
}
38+
39+
function part(xs: number[]) {
40+
const cont = [];
41+
const nested = [];
42+
for (const x of xs) {
43+
if (preds.get(x)!.size >= 2) {
44+
cont.push(x);
45+
} else {
46+
nested.push(x);
47+
}
48+
}
49+
return { nested, cont };
50+
}
51+
52+
function ast(node: number): unknown[] {
53+
const xs = tree.get(node);
54+
if (xs === undefined) {
55+
return [{ inst: node, pc: node }];
56+
}
57+
const { nested, cont } = part(xs);
58+
const blocks = [];
59+
if (nested.length === 1) {
60+
blocks.push({ inst: 'if', pc: node, tb: ast(nested[0]) });
61+
} else if (nested.length === 2) {
62+
blocks.push({ inst: 'if', pc: node, tb: ast(nested[0]), fb: ast(nested[1]) });
63+
}
64+
if (cont.length > 0) {
65+
blocks.push(...ast(cont[0]));
66+
}
67+
return blocks;
68+
}
69+
70+

src/dispatch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export class Opcode {
8989
return `${pc}: <${opcode}>${this.mnemonic}${pushData}`;
9090
}
9191
}
92+
9293
/**
9394
*
9495
*/

src/fork.ts renamed to src/sevm.ts

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { strict as assert } from "assert";
1+
import { strict as assert } from "node:assert";
2+
23
import { arrayify } from "./bytes.ts";
34
import { Dispatch, type Opcode } from "./dispatch.ts";
45
import { 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

Comments
 (0)