Author: Armin Rigo <ar...@tunes.org> Branch: Changeset: r76713:ef621d60ea13 Date: 2015-04-04 18:32 +0200 http://bitbucket.org/pypy/pypy/changeset/ef621d60ea13/
Log: Issue #2017: trying to limit the virtual address space consumption of having tons of greenlets. diff --git a/rpython/memory/gctransform/shadowstack.py b/rpython/memory/gctransform/shadowstack.py --- a/rpython/memory/gctransform/shadowstack.py +++ b/rpython/memory/gctransform/shadowstack.py @@ -270,15 +270,16 @@ class ShadowStackPool(object): """Manages a pool of shadowstacks. The MAX most recently used - shadowstacks are fully allocated and can be directly jumped into. + shadowstacks are fully allocated and can be directly jumped into + (called "full stacks" below). The rest are stored in a more virtual-memory-friendly way, i.e. with just the right amount malloced. Before they can run, they - must be copied into a full shadowstack. XXX NOT IMPLEMENTED SO FAR! + must be copied into a full shadowstack. """ _alloc_flavor_ = "raw" root_stack_depth = 163840 - #MAX = 20 not implemented yet + MAX = 20 def __init__(self, gcdata): self.unused_full_stack = llmemory.NULL @@ -299,13 +300,21 @@ forget_current_state(), and then call restore_state_from() or start_fresh_new_state(). """ - self._prepare_unused_stack() + fresh_free_fullstack = shadowstackref.prepare_free_slot() + if self.unused_full_stack: + if fresh_free_fullstack: + llmemory.raw_free(fresh_free_fullstack) + elif fresh_free_fullstack: + self.unused_full_stack = fresh_free_fullstack + else: + self._prepare_unused_stack() + # shadowstackref.base = self.gcdata.root_stack_base shadowstackref.top = self.gcdata.root_stack_top shadowstackref.context = ncontext ll_assert(shadowstackref.base <= shadowstackref.top, "save_current_state_away: broken shadowstack") - #shadowstackref.fullstack = True + shadowstackref.attach() # # cannot use llop.gc_writebarrier() here, because # we are in a minimally-transformed GC helper :-/ @@ -328,6 +337,7 @@ ll_assert(bool(shadowstackref.base), "empty shadowstackref!") ll_assert(shadowstackref.base <= shadowstackref.top, "restore_state_from: broken shadowstack") + self.unused_full_stack = shadowstackref.rebuild(self.unused_full_stack) self.gcdata.root_stack_base = shadowstackref.base self.gcdata.root_stack_top = shadowstackref.top self._cleanup(shadowstackref) @@ -343,26 +353,115 @@ shadowstackref.context = llmemory.NULL def _prepare_unused_stack(self): + ll_assert(self.unused_full_stack == llmemory.NULL, + "already an unused_full_stack") + root_stack_size = sizeofaddr * self.root_stack_depth + self.unused_full_stack = llmemory.raw_malloc(root_stack_size) if self.unused_full_stack == llmemory.NULL: - root_stack_size = sizeofaddr * self.root_stack_depth - self.unused_full_stack = llmemory.raw_malloc(root_stack_size) - if self.unused_full_stack == llmemory.NULL: - raise MemoryError + raise MemoryError def get_shadowstackref(root_walker, gctransformer): if hasattr(gctransformer, '_SHADOWSTACKREF'): return gctransformer._SHADOWSTACKREF + # Helpers to same virtual address space by limiting to MAX the + # number of full shadow stacks. If there are more, we compact + # them into a separately-allocated zone of memory of just the right + # size. See the comments in the definition of fullstack_cache below. + + def ll_prepare_free_slot(_unused): + """Free up a slot in the array of MAX entries, ready for storing + a new shadowstackref. Return the memory of the now-unused full + shadowstack. + """ + index = fullstack_cache[0] + if index > 0: + return llmemory.NULL # there is already at least one free slot + # + # make a compact copy in one old entry and return the + # original full-sized memory + index = -index + ll_assert(index > 0, "prepare_free_slot: cache[0] == 0") + compacting = lltype.cast_int_to_ptr(SHADOWSTACKREFPTR, + fullstack_cache[index]) + index += 1 + if index >= ShadowStackPool.MAX: + index = 1 + fullstack_cache[0] = -index # update to the next value in order + # + compacting.detach() + original = compacting.base + size = compacting.top - original + new = llmemory.raw_malloc(size) + if new == llmemory.NULL: + return llmemory.NULL + llmemory.raw_memcopy(original, new, size) + compacting.base = new + compacting.top = new + size + return original + + def ll_attach(shadowstackref): + """After prepare_free_slot(), store a shadowstackref in that slot.""" + index = fullstack_cache[0] + ll_assert(index > 0, "fullstack attach: no free slot") + fullstack_cache[0] = fullstack_cache[index] + fullstack_cache[index] = lltype.cast_ptr_to_int(shadowstackref) + ll_assert(shadowstackref.fsindex == 0, "fullstack attach: already one?") + shadowstackref.fsindex = index # > 0 + + def ll_detach(shadowstackref): + """Detach a shadowstackref from the array of MAX entries.""" + index = shadowstackref.fsindex + ll_assert(index > 0, "detach: unattached shadowstackref") + ll_assert(fullstack_cache[index] == + lltype.cast_ptr_to_int(shadowstackref), + "detach: bad fullstack_cache") + shadowstackref.fsindex = 0 + fullstack_cache[index] = fullstack_cache[0] + fullstack_cache[0] = index + + def ll_rebuild(shadowstackref, fullstack_base): + if shadowstackref.fsindex > 0: + shadowstackref.detach() + return fullstack_base + else: + # make an expanded copy of the compact shadowstack stored in + # 'shadowstackref' and free that + compact = shadowstackref.base + size = shadowstackref.top - compact + shadowstackref.base = fullstack_base + shadowstackref.top = fullstack_base + size + llmemory.raw_memcopy(compact, fullstack_base, size) + llmemory.raw_free(compact) + return llmemory.NULL + SHADOWSTACKREFPTR = lltype.Ptr(lltype.GcForwardReference()) SHADOWSTACKREF = lltype.GcStruct('ShadowStackRef', - ('base', llmemory.Address), - ('top', llmemory.Address), - ('context', llmemory.Address), - #('fullstack', lltype.Bool), - rtti=True) + ('base', llmemory.Address), + ('top', llmemory.Address), + ('context', llmemory.Address), + ('fsindex', lltype.Signed), + rtti=True, + adtmeths={'prepare_free_slot': ll_prepare_free_slot, + 'attach': ll_attach, + 'detach': ll_detach, + 'rebuild': ll_rebuild}) SHADOWSTACKREFPTR.TO.become(SHADOWSTACKREF) + # Items 1..MAX-1 of the following array can be SHADOWSTACKREF + # addresses cast to integer. Or, they are small numbers and they + # make up a free list, rooted in item 0, which goes on until + # terminated with a negative item. This negative item gives (the + # opposite of) the index of the entry we try to remove next. + # Initially all items are in this free list and the end is '-1'. + fullstack_cache = lltype.malloc(lltype.Array(lltype.Signed), + ShadowStackPool.MAX, + flavor='raw', immortal=True) + for i in range(len(fullstack_cache) - 1): + fullstack_cache[i] = i + 1 + fullstack_cache[len(fullstack_cache) - 1] = -1 + def customtrace(gc, obj, callback, arg): obj = llmemory.cast_adr_to_ptr(obj, SHADOWSTACKREFPTR) addr = obj.top @@ -384,6 +483,8 @@ h = llmemory.cast_adr_to_ptr(h, _c.handle) shadowstackref.context = llmemory.NULL # + if shadowstackref.fsindex > 0: + shadowstackref.detach() base = shadowstackref.base shadowstackref.base = llmemory.NULL shadowstackref.top = llmemory.NULL _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit