Skip to content
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

Memory scoping #2197

Merged

Conversation

iamdefinitelyahuman
Copy link
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
Copy link
Contributor

lgtm-com bot commented Oct 13, 2020

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

new alerts:

  • 2 for Unguarded next in generator

Copy link
Member

@fubuloubu fubuloubu left a comment

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?

vyper/parser/context.py Outdated Show resolved Hide resolved
vyper/parser/stmt.py Show resolved Hide resolved
@fubuloubu
Copy link
Member

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

@codecov-io
Copy link

codecov-io commented Oct 14, 2020

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
Copy link
Contributor

lgtm-com bot commented Oct 14, 2020

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

new alerts:

  • 2 for Unguarded next in generator

@codecov-commenter
Copy link

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
Copy link

codecov-io commented Oct 14, 2020

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
Copy link
Contributor

lgtm-com bot commented Oct 14, 2020

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
Copy link
Contributor

lgtm-com bot commented Oct 14, 2020

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
Copy link
Contributor

lgtm-com bot commented Oct 14, 2020

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
Member

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
Copy link
Member

@charles-cooper charles-cooper left a comment

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
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
Contributor Author

Choose a reason for hiding this comment

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

Good catch!

self.position = position
self.size = size

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

def partially_allocate(self, size: int) -> int:
Copy link
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?

@@ -124,3 +154,8 @@ def deallocate_memory(self, pos: int, size: int) -> None:
else:
active = next_slot
i += 1

last = self.deallocated_mem[-1]
Copy link
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


if not self.deallocated_mem or self.deallocated_mem[-1].position < pos:
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
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().

before_value = self.next_mem
self.next_mem += size
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
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
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
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
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.

@@ -59,6 +61,6 @@ def parse_function(code, sigs, origcode, global_ctx, is_contract_payable, _vars=
)

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
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().

Internal memory scope context manager.

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

Choose a reason for hiding this comment

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

exiting

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
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
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
Member

Choose a reason for hiding this comment

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

weird. gotcha

pos=var_pos,
typ=typ,
mutable=True,
blockscopes=self._scopes.copy(),
Copy link
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
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