Skip to content

Memory scoping#2197

Merged
iamdefinitelyahuman merged 13 commits into
vyperlang:masterfrom
iamdefinitelyahuman:feat-mem-scoping
Oct 14, 2020
Merged

Memory scoping#2197
iamdefinitelyahuman merged 13 commits into
vyperlang:masterfrom
iamdefinitelyahuman:feat-mem-scoping

Conversation

@iamdefinitelyahuman

Copy link
Copy Markdown
Contributor

What I did

Deallocate internal memory variables between statements.

How I did it

Upon exiting internal_memory_scope, all internal variables declared within the scope are deallocated. The scope is applied prior to parsing each statement node.

How to verify it

Run tests.

Cute Animal Picture

image

@lgtm-com

lgtm-com Bot commented Oct 13, 2020

Copy link
Copy Markdown
Contributor

This pull request introduces 2 alerts when merging 1ba75c5 into 192d991 - view on LGTM.com

new alerts:

  • 2 for Unguarded next in generator

@fubuloubu fubuloubu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be some tests in here?

Comment thread vyper/parser/context.py Outdated
Comment thread vyper/parser/stmt.py
@fubuloubu

Copy link
Copy Markdown
Contributor

p.s. is there a way to use an infinite generator without lgtm complaining?

@codecov-io

codecov-io commented Oct 14, 2020

Copy link
Copy Markdown

Codecov Report

Merging #2197 into master will decrease coverage by 0.02%.
The diff coverage is 95.06%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2197      +/-   ##
==========================================
- Coverage   85.47%   85.45%   -0.03%     
==========================================
  Files          83       83              
  Lines        8503     8511       +8     
  Branches     2053     2057       +4     
==========================================
+ Hits         7268     7273       +5     
- Misses        733      735       +2     
- Partials      502      503       +1     
Impacted Files Coverage Δ
vyper/parser/memory_allocator.py 83.05% <69.23%> (-5.19%) ⬇️
vyper/codegen/return_.py 100.00% <100.00%> (ø)
vyper/parser/context.py 96.00% <100.00%> (+0.04%) ⬆️
vyper/parser/events.py 93.52% <100.00%> (+0.04%) ⬆️
...er/function_definitions/parse_external_function.py 95.78% <100.00%> (ø)
...yper/parser/function_definitions/parse_function.py 95.83% <100.00%> (ø)
...er/function_definitions/parse_internal_function.py 98.01% <100.00%> (ø)
vyper/parser/stmt.py 80.28% <100.00%> (-0.21%) ⬇️
vyper/signatures/function_signature.py 81.90% <100.00%> (+0.09%) ⬆️
vyper/optimizer.py 84.09% <0.00%> (+0.75%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 192d991...40b23d5. Read the comment docs.

@lgtm-com

lgtm-com Bot commented Oct 14, 2020

Copy link
Copy Markdown
Contributor

This pull request introduces 2 alerts when merging 40b23d5 into 192d991 - view on LGTM.com

new alerts:

  • 2 for Unguarded next in generator

Comment thread vyper/parser/function_definitions/parse_function.py Outdated
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

Merging #2197 into master will decrease coverage by 0.02%.
The diff coverage is 95.06%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2197      +/-   ##
==========================================
- Coverage   85.47%   85.45%   -0.03%     
==========================================
  Files          83       83              
  Lines        8503     8511       +8     
  Branches     2053     2057       +4     
==========================================
+ Hits         7268     7273       +5     
- Misses        733      735       +2     
- Partials      502      503       +1     
Impacted Files Coverage Δ
vyper/parser/memory_allocator.py 83.05% <69.23%> (-5.19%) ⬇️
vyper/codegen/return_.py 100.00% <100.00%> (ø)
vyper/parser/context.py 96.00% <100.00%> (+0.04%) ⬆️
vyper/parser/events.py 93.52% <100.00%> (+0.04%) ⬆️
...er/function_definitions/parse_external_function.py 95.78% <100.00%> (ø)
...yper/parser/function_definitions/parse_function.py 95.83% <100.00%> (ø)
...er/function_definitions/parse_internal_function.py 98.01% <100.00%> (ø)
vyper/parser/stmt.py 80.28% <100.00%> (-0.21%) ⬇️
vyper/signatures/function_signature.py 81.90% <100.00%> (+0.09%) ⬆️
vyper/optimizer.py 84.09% <0.00%> (+0.75%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 192d991...d31231c. Read the comment docs.

@codecov-io

codecov-io commented Oct 14, 2020

Copy link
Copy Markdown

Codecov Report

Merging #2197 into master will decrease coverage by 0.17%.
The diff coverage is 90.56%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2197      +/-   ##
==========================================
- Coverage   85.47%   85.30%   -0.18%     
==========================================
  Files          83       83              
  Lines        8503     8524      +21     
  Branches     2053     2061       +8     
==========================================
+ Hits         7268     7271       +3     
- Misses        733      747      +14     
- Partials      502      506       +4     
Impacted Files Coverage Δ
vyper/parser/memory_allocator.py 63.63% <54.54%> (-24.60%) ⬇️
vyper/codegen/return_.py 100.00% <100.00%> (ø)
vyper/parser/context.py 96.07% <100.00%> (+0.11%) ⬆️
vyper/parser/events.py 93.52% <100.00%> (+0.04%) ⬆️
...er/function_definitions/parse_external_function.py 95.78% <100.00%> (ø)
...yper/parser/function_definitions/parse_function.py 96.00% <100.00%> (+0.16%) ⬆️
...er/function_definitions/parse_internal_function.py 98.01% <100.00%> (ø)
vyper/parser/self_call.py 91.93% <100.00%> (+0.19%) ⬆️
vyper/parser/stmt.py 80.28% <100.00%> (-0.21%) ⬇️
vyper/signatures/function_signature.py 81.90% <100.00%> (+0.09%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 192d991...4685e99. Read the comment docs.

@lgtm-com

lgtm-com Bot commented Oct 14, 2020

Copy link
Copy Markdown
Contributor

This pull request introduces 2 alerts when merging 10a5211 into 192d991 - view on LGTM.com

new alerts:

  • 2 for Unguarded next in generator

@lgtm-com

lgtm-com Bot commented Oct 14, 2020

Copy link
Copy Markdown
Contributor

This pull request introduces 2 alerts when merging 12b388a into 192d991 - view on LGTM.com

new alerts:

  • 2 for Unguarded next in generator

@lgtm-com

lgtm-com Bot commented Oct 14, 2020

Copy link
Copy Markdown
Contributor

This pull request introduces 2 alerts when merging 380cac7 into 192d991 - view on LGTM.com

new alerts:

  • 2 for Unguarded next in generator

@fubuloubu

Copy link
Copy Markdown
Contributor

Shut the hell up lgtm

@iamdefinitelyahuman iamdefinitelyahuman merged commit f0db419 into vyperlang:master Oct 14, 2020
@iamdefinitelyahuman iamdefinitelyahuman deleted the feat-mem-scoping branch October 14, 2020 23:37

@charles-cooper charles-cooper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code look really cleaned up compared to before! found a single issue in partially_allocate (the size < self.size caller invariant might change in the future so it's good hygiene to fix that assertion) and some questions / style suggestions.

int
Position of the newly allocated memory
"""
if size > self.size:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be >=

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

def __repr__(self):
return f"(FreeMemory: pos={self.position}, size={self.size})"

def partially_allocate(self, size: int) -> int:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be a private function?

active = next_slot
i += 1

last = self.deallocated_mem[-1]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could add a comment here

elif not self.deallocated_mem or self.deallocated_mem[-1].position < pos:
# no previously deallocated memory, or this is the highest position deallocated
self.deallocated_mem.append(FreeMemory(position=pos, size=size))
else:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i feel like instead of maintaining the sort invariant ourselves, we could just make a call to list.sort().

self.size_of_mem = max(self.size_of_mem, self.next_mem)
return before_value

def deallocate_memory(self, pos: int, size: int) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about def free()? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually one change i've wanted to make for a long time is to include the units in the name. so, def free_bytes(). makes it easier for the API user to know what is being freed without reading the allocator source.

@@ -101,9 +129,8 @@ def deallocate_memory(self, pos: int, size: int) -> None:
# releasing from the end of the allocated memory - reduce the free memory pointer
if pos + size == self.next_mem:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i had slight confusion reading this because it overlaps with the final clause in this function. i think this function would read better if we removed this branch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, all of this logic can be cleaned up quite a bit.


o.context = context
o.total_gas = o.gas + calc_mem_gas(o.context.memory_allocator.get_next_memory_position())
o.total_gas = o.gas + calc_mem_gas(o.context.memory_allocator.size_of_mem)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be part of the allocator API - allocator.memsize().

Comment thread vyper/parser/context.py
Internal memory scope context manager.

Internal variables that are declared within this context are de-allocated
upon exitting the context.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exiting

Comment thread vyper/parser/context.py
def _new_variable(self, name: str, typ: NodeType, var_pos: int) -> int:
def _new_variable(self, name: str, typ: NodeType, var_size: int, is_internal: bool) -> int:
if is_internal:
var_pos = self.memory_allocator.expand_memory(var_size)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do internal variables get a different allocation strategy?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are places in parser where an assumption is made that internal variables will be allocated sequentially within memory. If we don't do this, we end up with memory corruption. I tried to unravel / refactor that logic but eventually decided it wasn't safe - I can't be sure I've found them all.

By always allocating new internal variables on the edge of memory like this, we don't risk memory corruption but still gain some efficiency by not expanding memory needlessly. In the future when we refactor parser we should eliminate the assumption about how memory is sequenced, and then we can do away with this logic.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird. gotcha

Comment thread vyper/parser/context.py
pos=var_pos,
typ=typ,
mutable=True,
blockscopes=self._scopes.copy(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure why a variable needs to have pointers to all parent scopes. this seems like it should just be a single scope_id.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I just copied this from existing code without thinking 😬

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants