From 5cc52342a9a399bc8390f4561554ad3f251f4d80 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 28 Mar 2024 12:46:24 +0100 Subject: [PATCH] remove deep sink allocations --- evm/evm-codec/src/codecs/array.ts | 17 +++--- evm/evm-codec/src/codecs/primitives.ts | 4 +- evm/evm-codec/src/codecs/struct.ts | 9 ++-- evm/evm-codec/src/sink.ts | 74 ++++++++++++++++++-------- evm/evm-codec/test/array.test.ts | 12 +++++ evm/evm-codec/test/sink.test.ts | 5 ++ 6 files changed, 79 insertions(+), 42 deletions(-) diff --git a/evm/evm-codec/src/codecs/array.ts b/evm/evm-codec/src/codecs/array.ts index 71659afcc..c223955f7 100644 --- a/evm/evm-codec/src/codecs/array.ts +++ b/evm/evm-codec/src/codecs/array.ts @@ -8,15 +8,12 @@ export class ArrayCodec implements Codec { constructor(public readonly item: Codec) {} encode(sink: Sink, val: T[]) { - sink.offset(); - sink.u32(val.length); - const tempSink = new Sink(val.length); + sink.dynamicOffset(val.length); for (let i = 0; i < val.length; i++) { - this.item.encode(tempSink, val[i]); + this.item.encode(sink, val[i]); } sink.increaseSize(WORD_SIZE); - sink.append(tempSink); - sink.jumpBack(); + sink.endDynamic(); } decode(src: Src): T[] { @@ -58,13 +55,11 @@ export class FixedArrayCodec implements Codec { } private encodeDynamic(sink: Sink, val: T[]) { - sink.offset(); - const tempSink = new Sink(this.size); + sink.offset(this.size); for (let i = 0; i < val.length; i++) { - this.item.encode(tempSink, val[i]); + this.item.encode(sink, val[i]); } - sink.append(tempSink); - sink.jumpBack(); + sink.endDynamic(); } decode(src: Src): T[] { diff --git a/evm/evm-codec/src/codecs/primitives.ts b/evm/evm-codec/src/codecs/primitives.ts index 72b380f23..6992cb390 100644 --- a/evm/evm-codec/src/codecs/primitives.ts +++ b/evm/evm-codec/src/codecs/primitives.ts @@ -140,7 +140,7 @@ export const string = { encode(sink: Sink, val: string) { sink.offset(); sink.string(val); - sink.jumpBack(); + sink.endDynamic(); }, decode(src: Src): string { return src.string(); @@ -152,7 +152,7 @@ export const bytes = { encode(sink: Sink, val: Uint8Array) { sink.offset(); sink.bytes(val); - sink.jumpBack(); + sink.endDynamic(); }, decode(src: Src): Uint8Array { return src.bytes(); diff --git a/evm/evm-codec/src/codecs/struct.ts b/evm/evm-codec/src/codecs/struct.ts index 56956eccd..8263230ec 100644 --- a/evm/evm-codec/src/codecs/struct.ts +++ b/evm/evm-codec/src/codecs/struct.ts @@ -35,15 +35,12 @@ export class StructCodec } private encodeDynamic(sink: Sink, val: StructTypes): void { - sink.offset(); - const tempSink = new Sink(this.childrenSlotsCount); + sink.offset(this.childrenSlotsCount); for (let i in this.components) { let prop = this.components[i]; - prop.encode(tempSink, val[i]); + prop.encode(sink, val[i]); } - - sink.append(tempSink); - sink.jumpBack(); + sink.endDynamic(); } public decode(src: Src): StructTypes { diff --git a/evm/evm-codec/src/sink.ts b/evm/evm-codec/src/sink.ts index 01dfd867a..c71f0823a 100644 --- a/evm/evm-codec/src/sink.ts +++ b/evm/evm-codec/src/sink.ts @@ -1,13 +1,17 @@ +import assert from "node:assert"; import { WORD_SIZE } from "./codec"; export class Sink { private pos = 0; - private previousPos = 0; private buf: Buffer; private view: DataView; - public size = 0; + private stack: { start: number; prev: number; size: number }[] = []; constructor(fields: number, capacity: number = 1280) { - this.size = fields * WORD_SIZE; + this.stack.push({ + start: 0, + prev: 0, + size: fields * WORD_SIZE, + }); this.buf = Buffer.alloc(capacity); this.view = new DataView( this.buf.buffer, @@ -17,7 +21,11 @@ export class Sink { } result(): Buffer { - return this.buf.subarray(0, this.size); + assert( + this.stack.length === 1, + "Cannot get result during dynamic encoding" + ); + return this.buf.subarray(0, this.size()); } toString() { @@ -30,6 +38,10 @@ export class Sink { } } + size() { + return this.stack.at(-1)!.size; + } + private _allocate(cap: number): void { cap = Math.max(cap, this.buf.length * 2); let buf = Buffer.alloc(cap); @@ -128,7 +140,7 @@ export class Sink { this.reserve(reservedSize); this.buf.set(val, this.pos); this.pos += reservedSize; - this.size += reservedSize + WORD_SIZE; + this.increaseSize(reservedSize + WORD_SIZE); } staticBytes(len: number, val: Uint8Array) { @@ -156,36 +168,52 @@ export class Sink { this.reserve(reservedSize); this.buf.write(val, this.pos); this.pos += reservedSize; - this.size += reservedSize + WORD_SIZE; + this.increaseSize(reservedSize + WORD_SIZE); } bool(val: boolean) { this.u8(val ? 1 : 0); } - offset() { - const ptr = this.size; + offset(slotsCount = 0) { + const ptr = this.size(); this.u32(ptr); - this.previousPos = this.pos; - this.reserve(ptr); - this.pos = ptr; + const _start = this.start(); + this.startDynamic(_start + ptr, slotsCount); + this.pos = _start + ptr; } - increaseSize(size: number) { - this.size += size; + dynamicOffset(slotsCount: number) { + const ptr = this.size(); + this.u32(ptr); + const _start = this.start(); + this.startDynamic(_start + ptr + WORD_SIZE, slotsCount); + this.pos = _start + ptr; + this.u32(slotsCount); } - jumpBack() { - if (this.previousPos === 0) { - throw new Error("no jump destination found"); - } - this.pos = this.previousPos; + private start() { + return this.stack.at(-1)!.start; + } + + public increaseSize(amount: number) { + this.stack.at(-1)!.size += amount; + } + + private startDynamic(start: number, slotsCount: number) { + const size = slotsCount * WORD_SIZE; + this.reserve(start + size); + this.stack.push({ + start, + prev: this.pos, + size, + }); } - append(sink: Sink) { - this.reserve(sink.size); - this.buf.set(sink.result(), this.pos); - this.size += sink.size; - this.pos += sink.pos; + endDynamic() { + assert(this.stack.length > 1, "No dynamic encoding started"); + const { prev, size } = this.stack.pop()!; + this.increaseSize(size); + this.pos = prev; } } diff --git a/evm/evm-codec/test/array.test.ts b/evm/evm-codec/test/array.test.ts index 38e824aff..4a3ba6435 100644 --- a/evm/evm-codec/test/array.test.ts +++ b/evm/evm-codec/test/array.test.ts @@ -76,6 +76,18 @@ describe("dynamic size array", () => { expect(arr.decode(new Src(sink.result()))).toStrictEqual([1, 2, -3, -4, 5]); }); + it("array of arrays", () => { + const arr = array(array(int8)); + const sink = new Sink(1); + const data = [ + [1, 2, -3, -4, 5], + [1, 2, -3, -4, 5], + ]; + arr.encode(sink, data); + compareTypes(sink, [{ type: "int8[][]" }], [data]); + expect(arr.decode(new Src(sink.result()))).toStrictEqual(data); + }); + it("dynamic types encoding", () => { const arr = array(string); const sink = new Sink(1); diff --git a/evm/evm-codec/test/sink.test.ts b/evm/evm-codec/test/sink.test.ts index cf4b7ff60..55e1abdbf 100644 --- a/evm/evm-codec/test/sink.test.ts +++ b/evm/evm-codec/test/sink.test.ts @@ -67,6 +67,7 @@ describe("sink", () => { const sink = new Sink(1); sink.offset(); sink.string("hello"); + sink.endDynamic(); compareTypes(sink, [{ type: "string" }], ["hello"]); }); @@ -74,6 +75,7 @@ describe("sink", () => { const sink = new Sink(1); sink.offset(); sink.string("this string length is 32 bytes!!"); + sink.endDynamic(); compareTypes( sink, [{ type: "string" }], @@ -85,6 +87,7 @@ describe("sink", () => { const sink = new Sink(1); sink.offset(); sink.string("this string length is 33 bytes!!!"); + sink.endDynamic(); compareTypes( sink, [{ type: "string" }], @@ -96,6 +99,7 @@ describe("sink", () => { const sink = new Sink(1); sink.offset(); sink.string("привет 👍"); + sink.endDynamic(); compareTypes(sink, [{ type: "string" }], ["привет 👍"]); }); }); @@ -106,6 +110,7 @@ describe("sink", () => { const buffer = Buffer.alloc(150); buffer.fill("xd"); sink.bytes(buffer); + sink.endDynamic(); compareTypes(sink, [{ type: "bytes" }], [`0x${buffer.toString("hex")}`]); }); });