Repository navigation
Port upstream EVM memory resize perf fix (PLT-1111) #100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,14 +40,17 @@ func NewMemory() *Memory { | |
| } | ||
|
|
||
| func (m *Memory) Store() []byte { | ||
| return m.store | ||
| // Clamp the capacity so callers cannot append into the unallocated tail of | ||
| // the backing array, which Resize may later hand back as zeroed memory. | ||
| return m.store[:len(m.store):len(m.store)] | ||
| } | ||
|
|
||
| // Free returns the memory to the pool. | ||
| func (m *Memory) Free() { | ||
| // To reduce peak allocation, return only smaller memory instances to the pool. | ||
| const maxBufferSize = 16 << 10 | ||
| if cap(m.store) <= maxBufferSize { | ||
| clear(m.store) | ||
| m.store = m.store[:0] | ||
| m.lastGasCost = 0 | ||
| memoryPool.Put(m) | ||
|
|
@@ -80,10 +83,14 @@ func (m *Memory) Set32(offset uint64, val *uint256.Int) { | |
| val.PutUint256(m.store[offset:]) | ||
| } | ||
|
|
||
| // Resize resizes the memory to size | ||
| // Resize grows the memory to the requested size. | ||
| func (m *Memory) Resize(size uint64) { | ||
| if uint64(m.Len()) < size { | ||
| m.store = append(m.store, make([]byte, size-uint64(m.Len()))...) | ||
| if uint64(len(m.store)) < size { | ||
| if uint64(cap(m.store)) >= size { | ||
| m.store = m.store[:size] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This fast path is correct, but it makes an implicit invariant load-bearing for consensus: bytes in The risk is that nothing in the code says so. A future change that shrinks There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This fast path is only sound because |
||
| } else { | ||
| m.store = append(m.store, make([]byte, size-uint64(len(m.store)))...) | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -105,8 +112,11 @@ func (m *Memory) GetPtr(offset, size uint64) []byte { | |
| return nil | ||
| } | ||
|
|
||
| // memory is always resized before being accessed, no need to check bounds | ||
| return m.store[offset : offset+size] | ||
| // memory is always resized before being accessed, no need to check bounds. | ||
| // The capacity is clamped so callers cannot append into the unallocated | ||
| // tail of the backing array, which Resize may later hand back as zeroed | ||
| // memory. | ||
| return m.store[offset : offset+size : offset+size] | ||
| } | ||
|
|
||
| // Len returns the length of the backing slice | ||
|
|
@@ -116,7 +126,8 @@ func (m *Memory) Len() int { | |
|
|
||
| // Data returns the backing slice | ||
| func (m *Memory) Data() []byte { | ||
| return m.store | ||
| // Capacity is clamped, see Store. | ||
| return m.store[:len(m.store):len(m.store)] | ||
| } | ||
|
|
||
| // Copy copies data from the src position slice into the dst position. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -83,3 +83,45 @@ func TestMemoryCopy(t *testing.T) { | |
| } | ||
| } | ||
| } | ||
|
|
||
| func BenchmarkResize(b *testing.B) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] The PR body correctly identifies the Consider adding a test that exercises the pool reuse cycle directly: |
||
| memory := NewMemory() | ||
| for i := range b.N { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] This benchmark never frees or resets, so Upstream's version has the same shape, so this is fine to keep as-is for port fidelity. If you'd rather bound it, cycling the size (e.g. |
||
| memory.Resize(uint64(i)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] |
||
| } | ||
| } | ||
|
|
||
| // TestMemoryViewCapacityClamped verifies that the slices handed out by GetPtr, | ||
| // Data and Store cannot be appended into the unallocated tail of the backing | ||
| // array. Resize reslices within capacity, so a stray append by a caller (e.g. a | ||
| // custom precompile) would otherwise make newly expanded memory read non-zero, | ||
| // breaking the EVM invariant that fresh memory is zeroed. | ||
| func TestMemoryViewCapacityClamped(t *testing.T) { | ||
| m := NewMemory() | ||
| m.Resize(128) // grow the backing array... | ||
| m.store = m.store[:32] // ...then shrink the view, leaving spare capacity | ||
| if cap(m.store) < 128 { | ||
| t.Fatalf("test precondition: want spare capacity, have cap %d", cap(m.store)) | ||
| } | ||
|
|
||
| for _, tc := range []struct { | ||
| name string | ||
| view []byte | ||
| }{ | ||
| {"GetPtr", m.GetPtr(0, 32)}, | ||
| {"GetPtr offset", m.GetPtr(16, 16)}, | ||
| {"Data", m.Data()}, | ||
| {"Store", m.Store()}, | ||
| } { | ||
| if have, want := cap(tc.view), len(tc.view); have != want { | ||
| t.Errorf("%s: capacity not clamped: have %d, want %d", tc.name, have, want) | ||
| } | ||
| // Appending must reallocate rather than write into m.store's tail. | ||
| _ = append(tc.view, 0xff) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] |
||
| } | ||
|
|
||
| m.Resize(128) | ||
| if want := make([]byte, 96); !bytes.Equal(m.store[32:], want) { | ||
| t.Errorf("expanded memory not zeroed: %#x", m.store[32:]) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] The three-index clamping in
Store,DataandGetPtris defensive hardening that upstream ethereum#33056 does not include. It's cheap and correct, and I agree with the reasoning (no current caller appends — I checkedGetMemoryCopyPaddedand all tracer consumers), but it does add fork-local divergence in a file that will keep receiving cherry-picks. Consider noting in the commit message that these three lines are Sei-local additions, or upstreaming them, so future merges don't silently drop them.