Beispiel #1
0
class GC(object):
    """
    Simple semi-space copying GC

        fromspace:
            heap we are currently allocating objects in
        tospace:
            heap to copy live data to when fromspace is full
        forwarding_table:
            vector of bits, indicating for each pointer-sized word whether
            it contains a forwarding pointer. See the 'copy' method for
            an additional explanation
    """

    layout = [
        ('fromspace', 'BumpAllocator[]'),
        ('tospace',   'BumpAllocator[]'),
        ('forwarding_table', 'BitVector[]'),
    ]

    @jit
    def __init__(self, initial_size=1024**2):
        self.fromspace = BumpAllocator(initial_size)
        self.tospace = BumpAllocator(initial_size)
        self.init_forwarding_table()
        #self.finalizers = {}

    @jit
    def init_forwarding_table(self):
        """Initialize an empty forwarding table for the heap"""
        self.forwarding_table = BitVector(self.fromspace.size())

    @jit('gc -> Pointer[int8] -> int64')
    def offset(self, obj):
        """Offset of object in heap, used for marking forwarded objects"""
        return (obj - self.fromspace.bottom()) >> ptr_size

    @jit('gc -> Pointer[int8] -> uint64')
    def forwarded(self, obj):
        """Whether this object was copied to tospace"""
        return self.offset(obj) in self.forwarding_table

    @jit('allocator -> size -> Pointer[int8]')
    def alloc(self, size, top_of_stack):
        obj = self.fromspace.alloc(size)
        if obj == NULL:
            # fromspace is full, collect garbage
            self.collect(size, top_of_stack)
            obj = self.fromspace.alloc(size)
            # assert obj != NULL # MemoryError should have been raised by
            #                    # collect, unless `obj` really is too large

        return obj

    @jit('gc -> int64 -> Pointer[StackFrame[]] -> void')
    def collect(self, size, top_of_stack):
        """
        Collect garbage by copying all live data to tospace, starting with
        `roots`. If there is no or little garbage and we're running out of heap
        space, grow the heap. `size` indicates the size of the requested memory.

        This method may raise MemoryError.

        For every root, we determine the trace function and call it on the root.
        The trace function traces the object itself (since only it knows its
        size), and traces its children:

            def __flypy_trace__(self, gc):
                obj = gc.trace((int8 *) self, sizeof(self))
                obj.field1 = obj.field1.trace(gc)
                ...
                obj.fieldN = obj.fieldN.trace(gc)
                return obj
        """
        # Copy all live data
        for item in roots.find_roots(top_of_stack):
            obj = head(item)
            trace = head(tail(item))
            trace(obj)

        # Swap spaces and adjust heap if necessary
        self.fromspace, self.tospace = self.tospace, self.fromspace
        if self.resize_heap(size):
            self.init_forwarding_table()
        else:
            self.forwarding_table.clear()

    @jit
    def resize_heap(self, mininum_size):
        """
        Adjust heap size based on occupation ratio.
        Returns True if the heap got resized.

        NOTE: This may raise MemoryError
        """
        ratio = occupation(self.fromspace, self.fromspace.offset + mininum_size)
        size = self.fromspace.size() + mininum_size
        if ratio > OCCUPATION_THRESHOLD_HI:
            self.fromspace.resize(int(size * GROW_FACTOR))
        elif ratio < OCCUPATION_THRESHOLD_LO:
            self.fromspace.resize(int(size / GROW_FACTOR))
        else:
            return False

        return True

    @jit('gc -> Pointer[int8] -> int64 -> Pointer[int8]')
    def trace(self, obj, size, trace_children):
        """
        Mark the reachable object.  We first check if the object is already
        copied, in which case we're done. Otherwise, we copy the object to the
        tospace, and we trace the children. The copy operation will mark
        the object as copied in the forwarding table.

        We call __flypy_trace__ to mark GC-tracked children. This method
        is automatically generated, and allows us to avoid type tagging values
        to determine pointer locations. It also patches pointers to copied
        objects.
        """
        #assert self.fromspace.bottom() <= obj < self.fromspace.top

        if self.forwarded(obj):
            # Object already copied, use forwarding pointer
            forward_ptr_ptr = cast(obj, Pointer[Pointer[int8]])
            dst_obj = forward_ptr_ptr[0]
            return dst_obj

        copied_obj = self.copy(obj, size)
        trace_children(copied_obj)

        return copied_obj

    @jit('gc -> Pointer[int8] -> int64 -> Pointer[int8]')
    def copy(self, obj, size):
        """
        Copy the object (excluding its children!) to the tospace.

        This writes a forwarding pointer in the fromspace after having
        copied the object, and remembers doing so by writing a bit in the
        forwarding table corresponding to the offset of the object.

        We could determine the forwarding address for any object that has
        pointers using the type, for instance by overwriting the first pointer
        in the object with the new pointer, and checking in which address space
        that pointer points.

        However, we support tagless representations, and we may not have any
        reference, like a mutable value with only integers. We cannot
        safely determine whether an overwritten location is a forwarding
        address, or actually a value that happens to fall in the range
        of the to-space.
        """
        dst_obj = self.tospace.alloc(size, type)
        memcpy(obj, dst_obj, size)

        forward_ptr_ptr = cast(obj, Pointer[Pointer[int8]])
        forward_ptr_ptr[0] = dst_obj
        self.forwarding_table.mark(self.offset(obj))

        return dst_obj
Beispiel #2
0
 def init_forwarding_table(self):
     """Initialize an empty forwarding table for the heap"""
     self.forwarding_table = BitVector(self.fromspace.size())
Beispiel #3
0
class GC(object):
    """
    Simple semi-space copying GC

        fromspace:
            heap we are currently allocating objects in
        tospace:
            heap to copy live data to when fromspace is full
        forwarding_table:
            vector of bits, indicating for each pointer-sized word whether
            it contains a forwarding pointer. See the 'copy' method for
            an additional explanation
    """

    layout = [
        ('fromspace', 'BumpAllocator[]'),
        ('tospace', 'BumpAllocator[]'),
        ('forwarding_table', 'BitVector[]'),
    ]

    @jit
    def __init__(self, initial_size=1024**2):
        self.fromspace = BumpAllocator(initial_size)
        self.tospace = BumpAllocator(initial_size)
        self.init_forwarding_table()
        #self.finalizers = {}

    @jit
    def init_forwarding_table(self):
        """Initialize an empty forwarding table for the heap"""
        self.forwarding_table = BitVector(self.fromspace.size())

    @jit('gc -> Pointer[int8] -> int64')
    def offset(self, obj):
        """Offset of object in heap, used for marking forwarded objects"""
        return (obj - self.fromspace.bottom()) >> ptr_size

    @jit('gc -> Pointer[int8] -> uint64')
    def forwarded(self, obj):
        """Whether this object was copied to tospace"""
        return self.offset(obj) in self.forwarding_table

    @jit('allocator -> size -> Pointer[int8]')
    def alloc(self, size, top_of_stack):
        obj = self.fromspace.alloc(size)
        if obj == NULL:
            # fromspace is full, collect garbage
            self.collect(size, top_of_stack)
            obj = self.fromspace.alloc(size)
            # assert obj != NULL # MemoryError should have been raised by
            #                    # collect, unless `obj` really is too large

        return obj

    @jit('gc -> int64 -> Pointer[StackFrame[]] -> void')
    def collect(self, size, top_of_stack):
        """
        Collect garbage by copying all live data to tospace, starting with
        `roots`. If there is no or little garbage and we're running out of heap
        space, grow the heap. `size` indicates the size of the requested memory.

        This method may raise MemoryError.

        For every root, we determine the trace function and call it on the root.
        The trace function traces the object itself (since only it knows its
        size), and traces its children:

            def __flypy_trace__(self, gc):
                obj = gc.trace((int8 *) self, sizeof(self))
                obj.field1 = obj.field1.trace(gc)
                ...
                obj.fieldN = obj.fieldN.trace(gc)
                return obj
        """
        # Copy all live data
        for item in roots.find_roots(top_of_stack):
            obj = head(item)
            trace = head(tail(item))
            trace(obj)

        # Swap spaces and adjust heap if necessary
        self.fromspace, self.tospace = self.tospace, self.fromspace
        if self.resize_heap(size):
            self.init_forwarding_table()
        else:
            self.forwarding_table.clear()

    @jit
    def resize_heap(self, mininum_size):
        """
        Adjust heap size based on occupation ratio.
        Returns True if the heap got resized.

        NOTE: This may raise MemoryError
        """
        ratio = occupation(self.fromspace,
                           self.fromspace.offset + mininum_size)
        size = self.fromspace.size() + mininum_size
        if ratio > OCCUPATION_THRESHOLD_HI:
            self.fromspace.resize(int(size * GROW_FACTOR))
        elif ratio < OCCUPATION_THRESHOLD_LO:
            self.fromspace.resize(int(size / GROW_FACTOR))
        else:
            return False

        return True

    @jit('gc -> Pointer[int8] -> int64 -> Pointer[int8]')
    def trace(self, obj, size, trace_children):
        """
        Mark the reachable object.  We first check if the object is already
        copied, in which case we're done. Otherwise, we copy the object to the
        tospace, and we trace the children. The copy operation will mark
        the object as copied in the forwarding table.

        We call __flypy_trace__ to mark GC-tracked children. This method
        is automatically generated, and allows us to avoid type tagging values
        to determine pointer locations. It also patches pointers to copied
        objects.
        """
        #assert self.fromspace.bottom() <= obj < self.fromspace.top

        if self.forwarded(obj):
            # Object already copied, use forwarding pointer
            forward_ptr_ptr = cast(obj, Pointer[Pointer[int8]])
            dst_obj = forward_ptr_ptr[0]
            return dst_obj

        copied_obj = self.copy(obj, size)
        trace_children(copied_obj)

        return copied_obj

    @jit('gc -> Pointer[int8] -> int64 -> Pointer[int8]')
    def copy(self, obj, size):
        """
        Copy the object (excluding its children!) to the tospace.

        This writes a forwarding pointer in the fromspace after having
        copied the object, and remembers doing so by writing a bit in the
        forwarding table corresponding to the offset of the object.

        We could determine the forwarding address for any object that has
        pointers using the type, for instance by overwriting the first pointer
        in the object with the new pointer, and checking in which address space
        that pointer points.

        However, we support tagless representations, and we may not have any
        reference, like a mutable value with only integers. We cannot
        safely determine whether an overwritten location is a forwarding
        address, or actually a value that happens to fall in the range
        of the to-space.
        """
        dst_obj = self.tospace.alloc(size, type)
        memcpy(obj, dst_obj, size)

        forward_ptr_ptr = cast(obj, Pointer[Pointer[int8]])
        forward_ptr_ptr[0] = dst_obj
        self.forwarding_table.mark(self.offset(obj))

        return dst_obj
Beispiel #4
0
 def f(n):
     v = BitVector(100)
     set(v)
     return check(v)
Beispiel #5
0
 def init_forwarding_table(self):
     """Initialize an empty forwarding table for the heap"""
     self.forwarding_table = BitVector(self.fromspace.size())