1

implement garbage collection

This commit is contained in:
Christoph
2021-08-31 15:19:02 +02:00
parent e7576694e8
commit b9fbc45494
9 changed files with 228 additions and 128 deletions

View File

@ -1,8 +1,8 @@
from simpleparser import parse
from objspace import ObjectSpace
import compile
from disass import disassemble
import compile
from objspace import ObjectSpace
class ByteCodeError(Exception):
pass
@ -46,7 +46,6 @@ class Interpreter(object):
w_condition = stack.pop()
if self.space.isfalse(w_condition):
pc += oparg
continue
elif compile.hasarg(opcode):
oparg = ord(code[pc])
pc += 1
@ -62,20 +61,24 @@ class Interpreter(object):
stack.append(obj)
elif opcode == compile.MAKE_OBJECT_CALL:
self.run(bytecode.subbytecodes[oparg], stack[-1])
# Project -----
elif opcode == compile.INT_LITERAL:
w_value = self.space.newint(oparg)
stack.append(w_value)
elif opcode == compile.BOOL_LITERAL: # Project: Boolean
elif opcode == compile.BOOL_LITERAL:
w_value = self.space.newbool(oparg) # oparg is 1 or 0
stack.append(w_value)
elif opcode == compile.STRING_LITERAL: # Project: String
elif opcode == compile.STRING_LITERAL:
value = bytecode.symbols[oparg]
w_value = self.space.newstring(value)
stack.append(w_value)
elif opcode == compile.DOUBLE_LITERAL: # Project: Double
elif opcode == compile.DOUBLE_LITERAL:
value = bytecode.symbols[oparg]
w_value = self.space.newdouble(value)
stack.append(w_value)
# -------------
elif opcode == compile.MAKE_FUNCTION:
bc = bytecode.subbytecodes[oparg]
w_method = self.space.definemethod(name=bc.name, code=bc, w_target=w_context)
@ -85,7 +88,7 @@ class Interpreter(object):
w_method = self.space.getvalue(stack[-1], name)
stack.append(w_method)
elif opcode == compile.METHOD_CALL:
arguments_w = [stack.pop() for n in range(oparg)]
arguments_w = [stack.pop() for _ in range(oparg)]
arguments_w.reverse()
#
w_method = stack.pop()
@ -94,7 +97,7 @@ class Interpreter(object):
stack.append(w_result)
elif opcode == compile.PRIMITIVE_METHOD_CALL:
nargs = self.space.get_number_of_arguments_of_primitive(oparg)
arguments_w = [stack.pop() for n in range(nargs)]
arguments_w = [stack.pop() for _ in range(nargs)]
arguments_w.reverse()
w_receiver = stack.pop()
w_result = self.space.call_primitive(oparg, w_receiver, arguments_w)
@ -126,6 +129,11 @@ class Interpreter(object):
stack.append(w_context)
elif opcode == compile.DUP:
stack.append(stack[-1])
# Project
elif opcode == compile.GC:
self.space.gc(w_context)
else:
raise ByteCodeError('Invalid bytecode')
assert pc == len(code)

View File

@ -109,10 +109,10 @@ import simpleast
# ---------- bytecodes ----------
BOOL_LITERAL = 1 # 1 or 0 # Project: Boolean
BOOL_LITERAL = 1 # 1 or 0
INT_LITERAL = 2 # integer value
STRING_LITERAL = 3 # Project: String
DOUBLE_LITERAL = 17 # Project: Double
STRING_LITERAL = 3
DOUBLE_LITERAL = 17
ASSIGNMENT = 4 # index of attrname
METHOD_LOOKUP = 5 # index of method name
METHOD_CALL = 6 # number of arguments
@ -129,6 +129,7 @@ SET_LOCAL = 16 # index of attrname (optimization)
IMPLICIT_SELF = 32 # (no argument)
POP = 33 # (no argument)
DUP = 34 # (no argument)
GC = 35 # (no argument)
opcode_names = [None] * 256
for key, value in list(globals().items()):
@ -191,9 +192,9 @@ def compile(ast, argumentnames=[], name=None):
stack_effects = {
BOOL_LITERAL: 1, # Project: Boolean
STRING_LITERAL: 1, # Project: String
DOUBLE_LITERAL: 1, # Project: Double
BOOL_LITERAL: 1,
STRING_LITERAL: 1,
DOUBLE_LITERAL: 1,
INT_LITERAL: 1,
ASSIGNMENT: -1,
METHOD_LOOKUP: 1,
@ -208,6 +209,7 @@ stack_effects = {
IMPLICIT_SELF: 1,
POP: -1,
DUP: 1,
GC: 0,
}
@ -257,6 +259,7 @@ class Compiler(object):
stackeffect = stack_effects[opcode]
else:
assert stackeffect != sys.maxsize
self.stack_effect(stackeffect)
def get_position(self):
@ -289,18 +292,24 @@ class Compiler(object):
def compile_IntLiteral(self, astnode, needsresult):
self.emit(INT_LITERAL, astnode.value)
# Project: Boolean
# Project -----
def compile_BooleanLiteral(self, astnode, needsresult):
self.emit(BOOL_LITERAL, astnode.value)
# Project: String
def compile_StringLiteral(self, astnode, needsresult):
self.emit(STRING_LITERAL, self.lookup_symbol(astnode.value)) # save string value to symboltable
# Project: Double
def compile_DoubleLiteral(self, astnode, needsresult):
self.emit(DOUBLE_LITERAL, self.lookup_symbol(astnode.value))
def compile_GCStatement(self, astnode, needsresult):
self.emit(GC)
if needsresult:
self.emit(BOOL_LITERAL, True)
# -------------
def compile_ImplicitSelf(self, astnode, needsresult):
self.emit(IMPLICIT_SELF)
@ -370,7 +379,7 @@ class Compiler(object):
for statement in astnode.statements[:-1]:
self.compile(statement, needsresult=False)
laststatement = astnode.statements[-1]
self.compile(laststatement, needsresult)
self.compile(laststatement, needsresult) # return last result
def compile_FunctionDefinition(self, astnode, needsresult):
bytecode = compile(astnode.block, astnode.arguments, astnode.name)

View File

@ -0,0 +1,18 @@
def mark(w_context):
w_context.mark = True
if not hasattr(w_context, "slots"): # skip primitive objects
return
for name, obj in w_context.slots.items():
if name != "__parent__": # only descent
mark(obj)
def sweep(objects):
objects[:] = filter(lambda obj: obj.mark, objects) # inplace
def clear_marks(objects):
for obj in objects:
obj.mark = False

View File

@ -2,6 +2,9 @@ from c3computation import compute_C3_mro as c3
class AbstractObject(object):
def __init__(self):
self.mark = False # gc marker
def call(self, w_receiver, args_w):
return self
@ -24,30 +27,9 @@ class AbstractObject(object):
return c3(self)
class PrimitiveObject(AbstractObject):
def __init__(self, value, trait, space=None):
self.value = value
self._trait = trait
self.space = space
def getparents(self):
if self.space is None:
return [] # for tests
trait = self.space.getbuiltin(self._trait)
assert trait is not None, 'O_o bogus state'
return [trait]
def hasslot(self, name):
return False
def __str__(self):
return str(self.value)
__repr__ = __str__
class W_NormalObject(AbstractObject):
def __init__(self, name=None, slots=None, parents=None, space=None):
super().__init__()
self.space = space
self.name = name
if slots:
@ -93,44 +75,59 @@ class W_NormalObject(AbstractObject):
slots=self.slots.copy())
class W_Integer(PrimitiveObject):
def __init__(self, value, space=None):
super().__init__(int(value), "inttrait", space)
# Project -----
class PrimitiveObject(AbstractObject):
def __init__(self, value, trait, space=None):
super().__init__()
self.value = value
self._trait = trait
self.space = space
def istrue(self):
return self.value != 0
def getparents(self):
if self.space is None:
return [] # for tests
trait = self.space.getbuiltin(self._trait)
assert trait is not None, 'O_o bogus state'
return [trait]
# Project: Boolean
class W_Boolean(PrimitiveObject): # don't know if extending is good idea
def __init__(self, value, space=None):
super().__init__(int(value), "booltrait", space=space)
def hasslot(self, name):
return False
def __str__(self):
return str(bool(self.value))
return str(self.value)
__repr__ = __str__
def istrue(self):
return self.value != 0
return bool(self.value)
class W_Integer(PrimitiveObject):
def __init__(self, value, space=None):
super().__init__(int(value), "inttrait", space)
class W_Boolean(PrimitiveObject):
def __init__(self, value, space=None):
super().__init__(int(value), "booltrait", space=space)
def __str__(self):
return str(bool(self.value)) # true instead of 1
__repr__ = __str__
# Project: String
class W_String(PrimitiveObject):
def __init__(self, value, space=None):
super().__init__(str(value), "strtrait", space)
def istrue(self):
return self.value != ""
# Project: Double
class W_Double(PrimitiveObject):
def __init__(self, value, space=None):
super().__init__(float(value), "doubletrait", space)
def istrue(self):
return self.value != 0.
# -------------
class W_Method(W_NormalObject):

View File

@ -1,14 +1,15 @@
import primitives
from garbagecollection import mark, sweep, clear_marks
from objmodel import W_Integer, W_Boolean, W_String, W_Double
from objmodel import W_Method
from objmodel import W_NormalObject
import primitives
class ObjectSpace(object):
def __init__(self, interpreter):
self.interpreter = interpreter
self.objects = []
def setup_builtins(self, builtincode=None):
if builtincode is None:
@ -20,6 +21,7 @@ class ObjectSpace(object):
ast = parse(builtincode)
self.interpreter.eval(ast, w_builtins)
self.objects.clear() # remove all builtins, lobby from list
def _load_default_builtins(self):
import os
@ -40,32 +42,43 @@ class ObjectSpace(object):
slots = {}
else:
slots = {'__parent__': self.getbuiltins()}
return W_NormalObject(name=name, slots=slots)
return W_NormalObject(name=name, slots=slots) # lobby isn't collected
def newobject(self, name, slots, parentnames):
return W_NormalObject(space=self, name=name,
slots=slots, parents=parentnames)
self.objects.append(W_NormalObject(space=self, name=name,
slots=slots, parents=parentnames))
return self.objects[-1]
# Project -----
def newint(self, value):
return W_Integer(value, space=self)
self.objects.append(W_Integer(value, space=self))
return self.objects[-1]
# Project: Boolean
def newbool(self, value):
return W_Boolean(value, space=self)
self.objects.append(W_Boolean(value, space=self))
return self.objects[-1]
# Project: String
def newstring(self, value):
return W_String(value, space=self)
self.objects.append(W_String(value, space=self))
return self.objects[-1]
# Project: Double
def newdouble(self, value):
return W_Double(value, space=self)
self.objects.append(W_Double(value, space=self))
return self.objects[-1]
def gc(self, w_context):
clear_marks(self.objects)
mark(w_context)
sweep(self.objects)
# -------------
def definemethod(self, name, code, w_target):
w_meth = W_Method(code, name=name,
slots={'__parent__': w_target},
space=self)
return w_meth
self.objects.append(W_Method(code, name=name,
slots={'__parent__': w_target},
space=self))
return self.objects[-1]
def execute(self, code, w_context):
return self.interpreter.run(code, w_context)

View File

@ -93,7 +93,7 @@ class IntLiteral(Expression):
self.value = int(value)
# Project: Boolean
# Project -----
class BooleanLiteral(Expression):
""" A boolean literal (like "false") """
@ -103,7 +103,6 @@ class BooleanLiteral(Expression):
self.value = value == "true"
# Project: String
class StringLiteral(Expression):
""" A string literal (like "hello world") """
@ -113,7 +112,6 @@ class StringLiteral(Expression):
self.value = str(value)
# Project: Double
class DoubleLiteral(Expression):
""" A double literal (like "1.0", ".0", "1.", "+1.0") """
@ -123,6 +121,9 @@ class DoubleLiteral(Expression):
self.value = float(value)
# -------------
class MethodCall(Expression):
""" A call to a method with name 'methodname' on 'receiver' with
'arguments' (which is a list of expression ASTs).
@ -172,6 +173,11 @@ class Statement(AstNode):
""" Base class of all statement nodes. """
# Project
class GCStatement(Statement):
""" Triggers Garbage Collection """
class Assignment(Statement):
""" An assignement: lvalue attrname = expression.

View File

@ -158,7 +158,7 @@ OpenBracket = r'[\[\(\{]'
CloseBracket = r'[\]\)\}]'
# ____________________________________________________________
# Project: Boolean, String, Double
# Project
Boolean = r"true|false"
String = group(make_single_string(r"\'"), make_single_string(r'\"'))
@ -166,11 +166,8 @@ String = group(make_single_string(r"\'"), make_single_string(r'\"'))
_sign = r"([+-])?"
_int = r"(([1-9][0-9]*)|0)"
_dec = r"(([0-9]*[1-9])|0)"
Double = group(_sign + group(_int, r"") + r"\." + _dec,
_sign + _int + r"\." + group(_dec, r""))
# ____________________________________________________________
# Project: Sugar
Double = group(_sign + group(_int, r"") + r"\." + _dec, # 0.1 / .1
_sign + _int + r"\." + group(_dec, r"")) # 1.0 / 1.
Plus = r'\+'
Minus = r'-'
@ -179,6 +176,8 @@ Divide = r'/'
Increment = r'\+\+'
Modulo = r'%'
GC = r'gc'
# ____________________________________________________________
# Keywords
@ -189,10 +188,11 @@ Def = r'def'
Object = r'object'
tokens = ["If", "Else", "While", "Def", "Object", "Ignore",
"String", "Boolean", "Double", # Project: Boolean, String, Double
"String", "Boolean", "Double",
"Number", # after Double
"GC",
"NewlineAndWhitespace", "OpenBracket", "CloseBracket", "Comma", "Assign", "Colon",
"Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Project: Sugar
"Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo",
"Name", "PrimitiveName"]

View File

@ -6,10 +6,11 @@ from simplelexer import lex
import simpleast
pg = ParserGenerator(["If", "Else", "While", "Def", "Object", "Number",
"String", "Boolean", "Double", # Project: Boolean, String, Double
"String", "Boolean", "Double",
"GC",
"Name", "Indent", "Dedent", "Newline", "OpenBracket",
"CloseBracket", "Comma", "Assign", "Colon",
"Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Project: Sugar
"Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo",
"PrimitiveName", "EOF"],
# Operator precedence for ambiguous rules, ascending
precedence=[("left", ["Plus", "Minus"]),
@ -63,6 +64,12 @@ def newlines(n):
return None
@pg.production("statement : GC newlines")
@pg.production("statement : GC")
def gcstatement(stmt):
return simpleast.GCStatement()
@pg.production("statement : simplestatement")
@pg.production("statement : ifstatement")
@pg.production("statement : whilestatement")
@ -198,19 +205,16 @@ def number_expression(stmt):
return simpleast.IntLiteral(stmt[0].value)
# Project: Boolean
@pg.production("basic_expression : Boolean")
def boolean_expression(stmt):
return simpleast.BooleanLiteral(stmt[0].value)
# Project: String
@pg.production("basic_expression : String")
def string_expression(stmt):
return simpleast.StringLiteral(stmt[0].value[1:-1]) # cut off delimiters
# Project: Double
@pg.production("basic_expression : Double")
def double_expression(stmt):
return simpleast.DoubleLiteral(stmt[0].value)

View File

@ -4,33 +4,36 @@ from interpreter import Interpreter
def test_reassignment_gc():
ast = parse("""
true
x = 2
y = 3
""")
interpreter = Interpreter()
w_model = interpreter.make_module()
interpreter.eval(ast, w_model)
interpreter.space.gc(w_model)
x = w_model.getvalue("x")
y = w_model.getvalue("y")
assert x in interpreter.space.realm # Wo alle Objekte leben
assert y in interpreter.space.realm
assert x in interpreter.space.objects
assert y in interpreter.space.objects
ast = parse("""
x = y
""")
interpreter.eval(ast, w_model)
interpreter.space.gc()
assert x not in interpreter.space.realm
assert y in interpreter.space.realm
interpreter.space.gc(w_model)
assert x not in interpreter.space.objects
assert y in interpreter.space.objects
ast = parse("""
x = 0
y = 0
""")
interpreter.eval(ast, w_model)
interpreter.space.gc()
assert x not in interpreter.space.realm
assert y not in interpreter.space.realm
interpreter.space.gc(w_model)
assert x not in interpreter.space.objects
assert y not in interpreter.space.objects
def test_chain_gc():
@ -42,40 +45,25 @@ z = y
interpreter = Interpreter()
w_model = interpreter.make_module()
interpreter.eval(ast, w_model)
interpreter.space.gc(w_model)
x = w_model.getvalue("x")
assert x in interpreter.space.realm
assert x in interpreter.space.objects
ast = parse("""
x = 0
""")
interpreter.eval(ast, w_model)
interpreter.space.gc()
assert x in interpreter.space.realm
interpreter.space.gc(w_model)
assert x in interpreter.space.objects
ast = parse("""
y = x
z = y
""")
interpreter.eval(ast, w_model)
interpreter.space.gc()
assert x not in interpreter.space.realm
def test_while_gc():
ast = parse("""
x = 10
while x:
x = x $int_add(-1)
""")
interpreter = Interpreter()
w_model = interpreter.make_module()
interpreter.eval(ast, w_model)
count = len(interpreter.space.realm)
interpreter.space.gc()
assert count - len(interpreter.space.realm) == 10
interpreter.space.gc(w_model)
assert x not in interpreter.space.objects
def test_object_gc():
@ -88,19 +76,76 @@ object x:
interpreter = Interpreter()
w_model = interpreter.make_module()
interpreter.eval(ast, w_model)
interpreter.space.gc(w_model)
a = w_model.getvalue("x").getvalue("a")
b = w_model.getvalue("x").getvalue("b")
c = w_model.getvalue("x").getvalue("c")
assert a in interpreter.space.realm
assert b in interpreter.space.realm
assert c in interpreter.space.realm
x = w_model.getvalue("x")
assert a in interpreter.space.objects
assert b in interpreter.space.objects
assert c in interpreter.space.objects
assert x in interpreter.space.objects
ast = parse("""
x = 0
""")
interpreter.eval(ast, w_model)
interpreter.space.gc()
assert a not in interpreter.space.realm
assert b not in interpreter.space.realm
assert c not in interpreter.space.realm
interpreter.space.gc(w_model)
assert a not in interpreter.space.objects
assert b not in interpreter.space.objects
assert c not in interpreter.space.objects
assert x not in interpreter.space.objects
def test_method_gc():
ast = parse("""
def meth:
1
""")
interpreter = Interpreter()
w_model = interpreter.make_module()
interpreter.eval(ast, w_model)
interpreter.space.gc(w_model)
meth = w_model.getvalue("meth")
assert meth in interpreter.space.objects
ast = parse("""
def meth:
2
""")
interpreter.eval(ast, w_model)
assert meth in interpreter.space.objects
interpreter.space.gc(w_model)
assert meth not in interpreter.space.objects
def test_simple_call_gc():
ast = parse("""
x = 1
gc
""")
interpreter = Interpreter()
w_model = interpreter.make_module()
interpreter.eval(ast, w_model)
x = w_model.getvalue("x")
assert x in interpreter.space.objects
ast = parse("""
x = 2
""")
interpreter.eval(ast, w_model)
assert x in interpreter.space.objects
ast = parse("""
gc
""")
interpreter.eval(ast, w_model)
assert x not in interpreter.space.objects