def test_rtype_nongc_object(): class TestClass(object): _alloc_flavor_ = "raw" def __init__(self, a): self.a = a def method1(self): return self.a def malloc_and_free(a): ci = TestClass(a) b = ci.method1() free_non_gc_object(ci) return b a = RPythonAnnotator() #does not raise: s = a.build_types(malloc_and_free, [annmodel.SomeAddress()]) assert isinstance(s, annmodel.SomeAddress) rtyper = RPythonTyper(a) rtyper.specialize()
def __init__(self, translator): from pypy.rpython.memory.gc.base import choose_gc_from_config super(FrameworkGCTransformer, self).__init__(translator, inline=True) if hasattr(self, 'GC_PARAMS'): # for tests: the GC choice can be specified as class attributes from pypy.rpython.memory.gc.marksweep import MarkSweepGC GCClass = getattr(self, 'GCClass', MarkSweepGC) GC_PARAMS = self.GC_PARAMS else: # for regular translation: pick the GC from the config GCClass, GC_PARAMS = choose_gc_from_config(translator.config) self.layoutbuilder = TransformerLayoutBuilder(self) self.get_type_id = self.layoutbuilder.get_type_id # set up dummy a table, to be overwritten with the real one in finish() type_info_table = lltype._ptr( lltype.Ptr(gctypelayout.GCData.TYPE_INFO_TABLE), "delayed!type_info_table", solid=True) gcdata = gctypelayout.GCData(type_info_table) # initialize the following two fields with a random non-NULL address, # to make the annotator happy. The fields are patched in finish() # to point to a real array. foo = lltype.malloc(lltype.FixedSizeArray(llmemory.Address, 1), immortal=True, zero=True) a_random_address = llmemory.cast_ptr_to_adr(foo) gcdata.static_root_start = a_random_address # patched in finish() gcdata.static_root_nongcend = a_random_address # patched in finish() gcdata.static_root_end = a_random_address # patched in finish() self.gcdata = gcdata self.malloc_fnptr_cache = {} gcdata.gc = GCClass(**GC_PARAMS) root_walker = self.build_root_walker() gcdata.set_query_functions(gcdata.gc) gcdata.gc.set_root_walker(root_walker) self.num_pushs = 0 self.write_barrier_calls = 0 def frameworkgc_setup(): # run-time initialization code root_walker.setup_root_walker() gcdata.gc.setup() bk = self.translator.annotator.bookkeeper # the point of this little dance is to not annotate # self.gcdata.static_root_xyz as constants. XXX is it still needed?? data_classdef = bk.getuniqueclassdef(gctypelayout.GCData) data_classdef.generalize_attr( 'static_root_start', annmodel.SomeAddress()) data_classdef.generalize_attr( 'static_root_nongcend', annmodel.SomeAddress()) data_classdef.generalize_attr( 'static_root_end', annmodel.SomeAddress()) annhelper = annlowlevel.MixLevelHelperAnnotator(self.translator.rtyper) def getfn(ll_function, args_s, s_result, inline=False, minimal_transform=True): graph = annhelper.getgraph(ll_function, args_s, s_result) if minimal_transform: self.need_minimal_transform(graph) if inline: self.graphs_to_inline[graph] = True return annhelper.graph2const(graph) self.frameworkgc_setup_ptr = getfn(frameworkgc_setup, [], annmodel.s_None) if root_walker.need_root_stack: self.incr_stack_ptr = getfn(root_walker.incr_stack, [annmodel.SomeInteger()], annmodel.SomeAddress(), inline = True) self.decr_stack_ptr = getfn(root_walker.decr_stack, [annmodel.SomeInteger()], annmodel.SomeAddress(), inline = True) else: self.incr_stack_ptr = None self.decr_stack_ptr = None self.weakref_deref_ptr = self.inittime_helper( ll_weakref_deref, [llmemory.WeakRefPtr], llmemory.Address) classdef = bk.getuniqueclassdef(GCClass) s_gc = annmodel.SomeInstance(classdef) s_gcref = annmodel.SomePtr(llmemory.GCREF) malloc_fixedsize_clear_meth = GCClass.malloc_fixedsize_clear.im_func self.malloc_fixedsize_clear_ptr = getfn( malloc_fixedsize_clear_meth, [s_gc, annmodel.SomeInteger(nonneg=True), annmodel.SomeInteger(nonneg=True), annmodel.SomeBool(), annmodel.SomeBool(), annmodel.SomeBool()], s_gcref, inline = False) if hasattr(GCClass, 'malloc_fixedsize'): malloc_fixedsize_meth = GCClass.malloc_fixedsize.im_func self.malloc_fixedsize_ptr = getfn( malloc_fixedsize_meth, [s_gc, annmodel.SomeInteger(nonneg=True), annmodel.SomeInteger(nonneg=True), annmodel.SomeBool(), annmodel.SomeBool(), annmodel.SomeBool()], s_gcref, inline = False) else: malloc_fixedsize_meth = None self.malloc_fixedsize_ptr = self.malloc_fixedsize_clear_ptr ## self.malloc_varsize_ptr = getfn( ## GCClass.malloc_varsize.im_func, ## [s_gc] + [annmodel.SomeInteger(nonneg=True) for i in range(5)] ## + [annmodel.SomeBool(), annmodel.SomeBool()], s_gcref) self.malloc_varsize_clear_ptr = getfn( GCClass.malloc_varsize_clear.im_func, [s_gc] + [annmodel.SomeInteger(nonneg=True) for i in range(5)] + [annmodel.SomeBool(), annmodel.SomeBool()], s_gcref) self.collect_ptr = getfn(GCClass.collect.im_func, [s_gc], annmodel.s_None) self.can_move_ptr = getfn(GCClass.can_move.im_func, [s_gc, annmodel.SomeAddress()], annmodel.SomeBool()) # in some GCs we can inline the common case of # malloc_fixedsize(typeid, size, True, False, False) if getattr(GCClass, 'inline_simple_malloc', False): # make a copy of this function so that it gets annotated # independently and the constants are folded inside if malloc_fixedsize_meth is None: malloc_fast_meth = malloc_fixedsize_clear_meth self.malloc_fast_is_clearing = True else: malloc_fast_meth = malloc_fixedsize_meth self.malloc_fast_is_clearing = False malloc_fast = func_with_new_name( malloc_fast_meth, "malloc_fast") s_False = annmodel.SomeBool(); s_False.const = False s_True = annmodel.SomeBool(); s_True .const = True self.malloc_fast_ptr = getfn( malloc_fast, [s_gc, annmodel.SomeInteger(nonneg=True), annmodel.SomeInteger(nonneg=True), s_True, s_False, s_False], s_gcref, inline = True) else: self.malloc_fast_ptr = None # in some GCs we can also inline the common case of # malloc_varsize(typeid, length, (3 constant sizes), True, False) if getattr(GCClass, 'inline_simple_malloc_varsize', False): # make a copy of this function so that it gets annotated # independently and the constants are folded inside malloc_varsize_clear_fast = func_with_new_name( GCClass.malloc_varsize_clear.im_func, "malloc_varsize_clear_fast") s_False = annmodel.SomeBool(); s_False.const = False s_True = annmodel.SomeBool(); s_True .const = True self.malloc_varsize_clear_fast_ptr = getfn( malloc_varsize_clear_fast, [s_gc, annmodel.SomeInteger(nonneg=True), annmodel.SomeInteger(nonneg=True), annmodel.SomeInteger(nonneg=True), annmodel.SomeInteger(nonneg=True), annmodel.SomeInteger(nonneg=True), s_True, s_False], s_gcref, inline = True) else: self.malloc_varsize_clear_fast_ptr = None if getattr(GCClass, 'malloc_varsize_nonmovable', False): malloc_nonmovable = func_with_new_name( GCClass.malloc_varsize_nonmovable.im_func, "malloc_varsize_nonmovable") self.malloc_varsize_nonmovable_ptr = getfn( malloc_nonmovable, [s_gc, annmodel.SomeInteger(nonneg=True), annmodel.SomeInteger(nonneg=True)], s_gcref) else: self.malloc_varsize_nonmovable_ptr = None if getattr(GCClass, 'malloc_varsize_resizable', False): malloc_resizable = func_with_new_name( GCClass.malloc_varsize_resizable.im_func, "malloc_varsize_resizable") self.malloc_varsize_resizable_ptr = getfn( malloc_resizable, [s_gc, annmodel.SomeInteger(nonneg=True), annmodel.SomeInteger(nonneg=True)], s_gcref) else: self.malloc_varsize_resizable_ptr = None if getattr(GCClass, 'realloc', False): self.realloc_ptr = getfn( GCClass.realloc.im_func, [s_gc, s_gcref] + [annmodel.SomeInteger(nonneg=True)] * 4 + [annmodel.SomeBool()], s_gcref) if GCClass.moving_gc: self.id_ptr = getfn(GCClass.id.im_func, [s_gc, s_gcref], annmodel.SomeInteger(), inline = False, minimal_transform = False) else: self.id_ptr = None self.set_max_heap_size_ptr = getfn(GCClass.set_max_heap_size.im_func, [s_gc, annmodel.SomeInteger(nonneg=True)], annmodel.s_None) if GCClass.needs_write_barrier: self.write_barrier_ptr = getfn(GCClass.write_barrier.im_func, [s_gc, annmodel.SomeAddress(), annmodel.SomeAddress()], annmodel.s_None, inline=True) else: self.write_barrier_ptr = None self.statistics_ptr = getfn(GCClass.statistics.im_func, [s_gc, annmodel.SomeInteger()], annmodel.SomeInteger()) # experimental gc_x_* operations s_x_pool = annmodel.SomePtr(marksweep.X_POOL_PTR) s_x_clone = annmodel.SomePtr(marksweep.X_CLONE_PTR) # the x_*() methods use some regular mallocs that must be # transformed in the normal way self.x_swap_pool_ptr = getfn(GCClass.x_swap_pool.im_func, [s_gc, s_x_pool], s_x_pool, minimal_transform = False) self.x_clone_ptr = getfn(GCClass.x_clone.im_func, [s_gc, s_x_clone], annmodel.s_None, minimal_transform = False) # thread support if translator.config.translation.thread: if not hasattr(root_walker, "need_thread_support"): raise Exception("%s does not support threads" % ( root_walker.__class__.__name__,)) root_walker.need_thread_support() self.thread_prepare_ptr = getfn(root_walker.thread_prepare, [], annmodel.s_None) self.thread_run_ptr = getfn(root_walker.thread_run, [], annmodel.s_None, inline=True) self.thread_die_ptr = getfn(root_walker.thread_die, [], annmodel.s_None) annhelper.finish() # at this point, annotate all mix-level helpers annhelper.backend_optimize() self.collect_analyzer = CollectAnalyzer(self.translator) self.collect_analyzer.analyze_all() s_gc = self.translator.annotator.bookkeeper.valueoftype(GCClass) r_gc = self.translator.rtyper.getrepr(s_gc) self.c_const_gc = rmodel.inputconst(r_gc, self.gcdata.gc) self.malloc_zero_filled = GCClass.malloc_zero_filled HDR = self._gc_HDR = self.gcdata.gc.gcheaderbuilder.HDR self._gc_fields = fields = [] for fldname in HDR._names: FLDTYPE = getattr(HDR, fldname) fields.append(('_' + fldname, FLDTYPE))
def need_thread_support(self, gctransformer, getfn): from pypy.module.thread import ll_thread # xxx fish from pypy.rpython.memory.support import AddressDict from pypy.rpython.memory.support import copy_without_null_values gcdata = self.gcdata # the interfacing between the threads and the GC is done via # two completely ad-hoc operations at the moment: # gc_thread_run and gc_thread_die. See docstrings below. shadow_stack_pool = self.shadow_stack_pool SHADOWSTACKREF = get_shadowstackref(gctransformer) # this is a dict {tid: SHADOWSTACKREF}, where the tid for the # current thread may be missing so far gcdata.thread_stacks = None # Return the thread identifier, as an integer. get_tid = ll_thread.get_ident def thread_setup(): tid = get_tid() gcdata.main_tid = tid gcdata.active_tid = tid def thread_run(): """Called whenever the current thread (re-)acquired the GIL. This should ensure that the shadow stack installed in gcdata.root_stack_top/root_stack_base is the one corresponding to the current thread. No GC operation here, e.g. no mallocs or storing in a dict! """ tid = get_tid() if gcdata.active_tid != tid: switch_shadow_stacks(tid) def thread_die(): """Called just before the final GIL release done by a dying thread. After a thread_die(), no more gc operation should occur in this thread. """ tid = get_tid() if tid == gcdata.main_tid: return # ignore calls to thread_die() in the main thread # (which can occur after a fork()). # we need to switch somewhere else, so go to main_tid gcdata.active_tid = gcdata.main_tid thread_stacks = gcdata.thread_stacks new_ref = thread_stacks[gcdata.active_tid] try: del thread_stacks[tid] except KeyError: pass # no more GC operation from here -- switching shadowstack! shadow_stack_pool.forget_current_state() shadow_stack_pool.restore_state_from(new_ref) def switch_shadow_stacks(new_tid): # we have the wrong shadowstack right now, but it should not matter thread_stacks = gcdata.thread_stacks try: if thread_stacks is None: gcdata.thread_stacks = thread_stacks = {} raise KeyError new_ref = thread_stacks[new_tid] except KeyError: new_ref = lltype.nullptr(SHADOWSTACKREF) try: old_ref = thread_stacks[gcdata.active_tid] except KeyError: # first time we ask for a SHADOWSTACKREF for this active_tid old_ref = shadow_stack_pool.allocate(SHADOWSTACKREF) thread_stacks[gcdata.active_tid] = old_ref # # no GC operation from here -- switching shadowstack! shadow_stack_pool.save_current_state_away(old_ref, llmemory.NULL) if new_ref: shadow_stack_pool.restore_state_from(new_ref) else: shadow_stack_pool.start_fresh_new_state() # done # gcdata.active_tid = new_tid switch_shadow_stacks._dont_inline_ = True def thread_after_fork(result_of_fork, opaqueaddr): # we don't need a thread_before_fork in this case, so # opaqueaddr == NULL. This is called after fork(). if result_of_fork == 0: # We are in the child process. Assumes that only the # current thread survived, so frees the shadow stacks # of all the other ones. gcdata.thread_stacks = None # Finally, reset the stored thread IDs, in case it # changed because of fork(). Also change the main # thread to the current one (because there is not any # other left). tid = get_tid() gcdata.main_tid = tid gcdata.active_tid = tid self.thread_setup = thread_setup self.thread_run_ptr = getfn(thread_run, [], annmodel.s_None, inline=True, minimal_transform=False) self.thread_die_ptr = getfn(thread_die, [], annmodel.s_None, minimal_transform=False) # no thread_before_fork_ptr here self.thread_after_fork_ptr = getfn( thread_after_fork, [annmodel.SomeInteger(), annmodel.SomeAddress()], annmodel.s_None, minimal_transform=False)
def need_thread_support(self, gctransformer, getfn): # Threads supported "out of the box" by the rest of the code. # The whole code in this function is only there to support # fork()ing in a multithreaded process :-( # For this, we need to handle gc_thread_start and gc_thread_die # to record the mapping {thread_id: stack_start}, and # gc_thread_before_fork and gc_thread_after_fork to get rid of # all ASM_FRAMEDATA structures that do no belong to the current # thread after a fork(). from pypy.module.thread import ll_thread from pypy.rpython.memory.support import AddressDict from pypy.rpython.memory.support import copy_without_null_values from pypy.annotation import model as annmodel gcdata = self.gcdata def get_aid(): """Return the thread identifier, cast to an (opaque) address.""" return llmemory.cast_int_to_adr(ll_thread.get_ident()) def thread_start(): value = llop.stack_current(llmemory.Address) gcdata.aid2stack.setitem(get_aid(), value) thread_start._always_inline_ = True def thread_setup(): gcdata.aid2stack = AddressDict() gcdata.dead_threads_count = 0 # to also register the main thread's stack thread_start() thread_setup._always_inline_ = True def thread_die(): gcdata.aid2stack.setitem(get_aid(), llmemory.NULL) # from time to time, rehash the dictionary to remove # old NULL entries gcdata.dead_threads_count += 1 if (gcdata.dead_threads_count & 511) == 0: copy = copy_without_null_values(gcdata.aid2stack) gcdata.aid2stack.delete() gcdata.aid2stack = copy def belongs_to_current_thread(framedata): # xxx obscure: the answer is Yes if, as a pointer, framedata # lies between the start of the current stack and the top of it. stack_start = gcdata.aid2stack.get(get_aid(), llmemory.NULL) ll_assert(stack_start != llmemory.NULL, "current thread not found in gcdata.aid2stack!") stack_stop = llop.stack_current(llmemory.Address) return (stack_start <= framedata <= stack_stop or stack_start >= framedata >= stack_stop) def thread_before_fork(): # before fork(): collect all ASM_FRAMEDATA structures that do # not belong to the current thread, and move them out of the # way, i.e. out of the main circular doubly linked list. detached_pieces = llmemory.NULL anchor = llmemory.cast_ptr_to_adr(gcrootanchor) initialframedata = anchor.address[1] while initialframedata != anchor: # while we have not looped back if not belongs_to_current_thread(initialframedata): # Unlink it prev = initialframedata.address[0] next = initialframedata.address[1] prev.address[1] = next next.address[0] = prev # Link it to the singly linked list 'detached_pieces' initialframedata.address[0] = detached_pieces detached_pieces = initialframedata rffi.stackcounter.stacks_counter -= 1 # Then proceed to the next piece of stack initialframedata = initialframedata.address[1] return detached_pieces def thread_after_fork(result_of_fork, detached_pieces): if result_of_fork == 0: # We are in the child process. Assumes that only the # current thread survived. All the detached_pieces # are pointers in other stacks, so have likely been # freed already by the multithreaded library. # Nothing more for us to do. pass else: # We are still in the parent process. The fork() may # have succeeded or not, but that's irrelevant here. # We need to reattach the detached_pieces now, to the # circular doubly linked list at 'gcrootanchor'. The # order is not important. anchor = llmemory.cast_ptr_to_adr(gcrootanchor) while detached_pieces != llmemory.NULL: reattach = detached_pieces detached_pieces = detached_pieces.address[0] a_next = anchor.address[1] reattach.address[0] = anchor reattach.address[1] = a_next anchor.address[1] = reattach a_next.address[0] = reattach rffi.stackcounter.stacks_counter += 1 self.thread_setup = thread_setup self.thread_start_ptr = getfn(thread_start, [], annmodel.s_None, inline=True) self.thread_die_ptr = getfn(thread_die, [], annmodel.s_None) self.thread_before_fork_ptr = getfn(thread_before_fork, [], annmodel.SomeAddress()) self.thread_after_fork_ptr = getfn( thread_after_fork, [annmodel.SomeInteger(), annmodel.SomeAddress()], annmodel.s_None)
def need_stacklet_support(self, gctransformer, getfn): shadow_stack_pool = self.shadow_stack_pool SHADOWSTACKREF = get_shadowstackref(gctransformer) def gc_shadowstackref_new(): ssref = shadow_stack_pool.allocate(SHADOWSTACKREF) return lltype.cast_opaque_ptr(llmemory.GCREF, ssref) def gc_shadowstackref_context(gcref): ssref = lltype.cast_opaque_ptr(lltype.Ptr(SHADOWSTACKREF), gcref) return ssref.context def gc_shadowstackref_destroy(gcref): ssref = lltype.cast_opaque_ptr(lltype.Ptr(SHADOWSTACKREF), gcref) shadow_stack_pool.destroy(ssref) def gc_save_current_state_away(gcref, ncontext): ssref = lltype.cast_opaque_ptr(lltype.Ptr(SHADOWSTACKREF), gcref) shadow_stack_pool.save_current_state_away(ssref, ncontext) def gc_forget_current_state(): shadow_stack_pool.forget_current_state() def gc_restore_state_from(gcref): ssref = lltype.cast_opaque_ptr(lltype.Ptr(SHADOWSTACKREF), gcref) shadow_stack_pool.restore_state_from(ssref) def gc_start_fresh_new_state(): shadow_stack_pool.start_fresh_new_state() s_gcref = annmodel.SomePtr(llmemory.GCREF) s_addr = annmodel.SomeAddress() self.gc_shadowstackref_new_ptr = getfn(gc_shadowstackref_new, [], s_gcref, minimal_transform=False) self.gc_shadowstackref_context_ptr = getfn(gc_shadowstackref_context, [s_gcref], s_addr, inline=True) self.gc_shadowstackref_destroy_ptr = getfn(gc_shadowstackref_destroy, [s_gcref], annmodel.s_None, inline=True) self.gc_save_current_state_away_ptr = getfn(gc_save_current_state_away, [s_gcref, s_addr], annmodel.s_None, inline=True) self.gc_forget_current_state_ptr = getfn(gc_forget_current_state, [], annmodel.s_None, inline=True) self.gc_restore_state_from_ptr = getfn(gc_restore_state_from, [s_gcref], annmodel.s_None, inline=True) self.gc_start_fresh_new_state_ptr = getfn(gc_start_fresh_new_state, [], annmodel.s_None, inline=True) # fish... translator = gctransformer.translator if hasattr(translator, '_jit2gc'): from pypy.rlib._rffi_stacklet import _translate_pointer root_iterator = translator._jit2gc['root_iterator'] root_iterator.translateptr = _translate_pointer