Skip to content

Commit

Permalink
rpython: simplify and probably remove a performance bottleneck
Browse files Browse the repository at this point in the history
Traverse environments without recursion.  Unwrap the key once.
Do not wrap DEBUG-EVAL on each evaluation.
Spare two intermediate MalLists for the 'do' special form.
  • Loading branch information
asarhaddon committed Oct 16, 2024
1 parent b463373 commit 3fe9d20
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 134 deletions.
23 changes: 7 additions & 16 deletions impls/rpython/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,17 @@ def __init__(self, outer=None, binds=None, exprs=None):
else:
self.data[bind.value] = exprs[i]

def find(self, key):
assert isinstance(key, MalSym)
if key.value in self.data: return self
elif self.outer: return self.outer.find(key)
else: return None

def set(self, key, value):
assert isinstance(key, MalSym)
assert isinstance(value, MalType)
self.data[key.value] = value
return value

def get(self, key):
assert isinstance(key, MalSym)
env = self.find(key)
if not env: throw_str("'" + str(key.value) + "' not found")
return env.data[key.value]

def get_or_None(self, key):
assert isinstance(key, MalSym)
env = self.find(key)
if not env: return None
return env.data[key.value]
assert isinstance(key, unicode)
env = self
while True:
value = env.data.get(key)
if value is not None: return value
env = env.outer
if env is None: return None
18 changes: 7 additions & 11 deletions impls/rpython/step2_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@ def READ(str):
return reader.read_str(str)

# eval
def eval_ast(ast, env):
assert isinstance(ast, MalList)
res = []
for a in ast.values:
res.append(EVAL(a, env))
return MalList(res)

def EVAL(ast, env):
# print(u"EVAL: " + printer._pr_str(ast))
if types._symbol_Q(ast):
Expand All @@ -41,10 +34,13 @@ def EVAL(ast, env):
else:
# apply list
if len(ast) == 0: return ast
el = eval_ast(ast, env)
f = el.values[0]
f = EVAL(a0, env)
args_list = []
for i in range(1, len(ast)):
args_list.append(EVAL(ast[i], env))
args = MalList(args_list)
if isinstance(f, MalFunc):
return f.apply(el.values[1:])
return f.apply(args)
else:
raise Exception("%s is not callable" % f)

Expand All @@ -53,7 +49,7 @@ def PRINT(exp):
return printer._pr_str(exp)

# repl
repl_env = {}
repl_env = {}
def REP(str, env):
return PRINT(EVAL(READ(str), env))

Expand Down
23 changes: 11 additions & 12 deletions impls/rpython/step3_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,15 @@ def READ(str):
return reader.read_str(str)

# eval
def eval_ast(ast, env):
assert isinstance(ast, MalList)
res = []
for a in ast.values:
res.append(EVAL(a, env))
return MalList(res)

def EVAL(ast, env):
if env.get_or_None(MalSym(u"DEBUG-EVAL")) not in (None, nil, false):
if env.get(u"DEBUG-EVAL") not in (None, nil, false):
print(u"EVAL: " + printer._pr_str(ast))
if types._symbol_Q(ast):
assert isinstance(ast, MalSym)
return env.get(ast)
value = env.get(ast.value)
if value is None:
throw_str("'" + str(ast.value) + "' not found")
return value
elif types._vector_Q(ast):
res = []
for a in ast.values:
Expand Down Expand Up @@ -56,10 +52,13 @@ def EVAL(ast, env):
let_env.set(a1[i], EVAL(a1[i+1], let_env))
return EVAL(a2, let_env)
else:
el = eval_ast(ast, env)
f = el.values[0]
f = EVAL(a0, env)
args_list = []
for i in range(1, len(ast)):
args_list.append(EVAL(ast[i], env))
args = MalList(args_list)
if isinstance(f, MalFunc):
return f.apply(el.values[1:])
return f.apply(args)
else:
raise Exception("%s is not callable" % f)

Expand Down
30 changes: 16 additions & 14 deletions impls/rpython/step4_if_fn_do.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,15 @@ def READ(str):
return reader.read_str(str)

# eval
def eval_ast(ast, env):
assert isinstance(ast, MalList)
res = []
for a in ast.values:
res.append(EVAL(a, env))
return MalList(res)

def EVAL(ast, env):
if env.get_or_None(MalSym(u"DEBUG-EVAL")) not in (None, nil, false):
if env.get(u"DEBUG-EVAL") not in (None, nil, false):
print(u"EVAL: " + printer._pr_str(ast))
if types._symbol_Q(ast):
assert isinstance(ast, MalSym)
return env.get(ast)
value = env.get(ast.value)
if value is None:
throw_str("'" + str(ast.value) + "' not found")
return value
elif types._vector_Q(ast):
res = []
for a in ast.values:
Expand Down Expand Up @@ -59,8 +55,11 @@ def EVAL(ast, env):
let_env.set(a1[i], EVAL(a1[i+1], let_env))
return EVAL(a2, let_env)
elif u"do" == a0sym:
el = eval_ast(ast.rest(), env)
return el.values[-1]
if len(ast) == 0:
return nil
for i in range(1, len(ast) - 1):
EVAL(ast[i], env)
return EVAL(ast[-1], env)
elif u"if" == a0sym:
a1, a2 = ast[1], ast[2]
cond = EVAL(a1, env)
Expand All @@ -73,10 +72,13 @@ def EVAL(ast, env):
a1, a2 = ast[1], ast[2]
return MalFunc(None, a2, env, a1, EVAL)
else:
el = eval_ast(ast, env)
f = el.values[0]
f = EVAL(a0, env)
args_list = []
for i in range(1, len(ast)):
args_list.append(EVAL(ast[i], env))
args = MalList(args_list)
if isinstance(f, MalFunc):
return f.apply(el.rest())
return f.apply(args)
else:
raise Exception("%s is not callable" % f)

Expand Down
29 changes: 14 additions & 15 deletions impls/rpython/step5_tco.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,16 @@ def READ(str):
return reader.read_str(str)

# eval
def eval_ast(ast, env):
assert isinstance(ast, MalList)
res = []
for a in ast.values:
res.append(EVAL(a, env))
return MalList(res)

def EVAL(ast, env):
while True:
if env.get_or_None(MalSym(u"DEBUG-EVAL")) not in (None, nil, false):
if env.get(u"DEBUG-EVAL") not in (None, nil, false):
print(u"EVAL: " + printer._pr_str(ast))
if types._symbol_Q(ast):
assert isinstance(ast, MalSym)
return env.get(ast)
value = env.get(ast.value)
if value is None:
throw_str("'" + str(ast.value) + "' not found")
return value
elif types._vector_Q(ast):
res = []
for a in ast.values:
Expand Down Expand Up @@ -63,8 +59,8 @@ def EVAL(ast, env):
elif u"do" == a0sym:
if len(ast) == 0:
return nil
elif len(ast) > 1:
eval_ast(ast.slice2(1, len(ast)-1), env)
for i in range(1, len(ast) - 1):
EVAL(ast[i], env)
ast = ast[-1] # Continue loop (TCO)
elif u"if" == a0sym:
a1, a2 = ast[1], ast[2]
Expand All @@ -78,14 +74,17 @@ def EVAL(ast, env):
a1, a2 = ast[1], ast[2]
return MalFunc(None, a2, env, a1, EVAL)
else:
el = eval_ast(ast, env)
f = el.values[0]
f = EVAL(a0, env)
args_list = []
for i in range(1, len(ast)):
args_list.append(EVAL(ast[i], env))
args = MalList(args_list)
if isinstance(f, MalFunc):
if f.ast:
ast = f.ast
env = f.gen_env(el.rest()) # Continue loop (TCO)
env = f.gen_env(args) # Continue loop (TCO)
else:
return f.apply(el.rest())
return f.apply(args)
else:
raise Exception("%s is not callable" % f)

Expand Down
29 changes: 14 additions & 15 deletions impls/rpython/step6_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,16 @@ def READ(str):
return reader.read_str(str)

# eval
def eval_ast(ast, env):
assert isinstance(ast, MalList)
res = []
for a in ast.values:
res.append(EVAL(a, env))
return MalList(res)

def EVAL(ast, env):
while True:
if env.get_or_None(MalSym(u"DEBUG-EVAL")) not in (None, nil, false):
if env.get(u"DEBUG-EVAL") not in (None, nil, false):
print(u"EVAL: " + printer._pr_str(ast))
if types._symbol_Q(ast):
assert isinstance(ast, MalSym)
return env.get(ast)
value = env.get(ast.value)
if value is None:
throw_str("'" + str(ast.value) + "' not found")
return value
elif types._vector_Q(ast):
res = []
for a in ast.values:
Expand Down Expand Up @@ -63,8 +59,8 @@ def EVAL(ast, env):
elif u"do" == a0sym:
if len(ast) == 0:
return nil
elif len(ast) > 1:
eval_ast(ast.slice2(1, len(ast)-1), env)
for i in range(1, len(ast) - 1):
EVAL(ast[i], env)
ast = ast[-1] # Continue loop (TCO)
elif u"if" == a0sym:
a1, a2 = ast[1], ast[2]
Expand All @@ -78,14 +74,17 @@ def EVAL(ast, env):
a1, a2 = ast[1], ast[2]
return MalFunc(None, a2, env, a1, EVAL)
else:
el = eval_ast(ast, env)
f = el.values[0]
f = EVAL(a0, env)
args_list = []
for i in range(1, len(ast)):
args_list.append(EVAL(ast[i], env))
args = MalList(args_list)
if isinstance(f, MalFunc):
if f.ast:
ast = f.ast
env = f.gen_env(el.rest()) # Continue loop (TCO)
env = f.gen_env(args) # Continue loop (TCO)
else:
return f.apply(el.rest())
return f.apply(args)
else:
raise Exception("%s is not callable" % f)

Expand Down
29 changes: 14 additions & 15 deletions impls/rpython/step7_quote.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,16 @@ def quasiquote(ast):
else:
return ast

def eval_ast(ast, env):
assert isinstance(ast, MalList)
res = []
for a in ast.values:
res.append(EVAL(a, env))
return MalList(res)

def EVAL(ast, env):
while True:
if env.get_or_None(MalSym(u"DEBUG-EVAL")) not in (None, nil, false):
if env.get(u"DEBUG-EVAL") not in (None, nil, false):
print(u"EVAL: " + printer._pr_str(ast))
if types._symbol_Q(ast):
assert isinstance(ast, MalSym)
return env.get(ast)
value = env.get(ast.value)
if value is None:
throw_str("'" + str(ast.value) + "' not found")
return value
elif types._vector_Q(ast):
res = []
for a in ast.values:
Expand Down Expand Up @@ -94,8 +90,8 @@ def EVAL(ast, env):
elif u"do" == a0sym:
if len(ast) == 0:
return nil
elif len(ast) > 1:
eval_ast(ast.slice2(1, len(ast)-1), env)
for i in range(1, len(ast) - 1):
EVAL(ast[i], env)
ast = ast[-1] # Continue loop (TCO)
elif u"if" == a0sym:
a1, a2 = ast[1], ast[2]
Expand All @@ -109,14 +105,17 @@ def EVAL(ast, env):
a1, a2 = ast[1], ast[2]
return MalFunc(None, a2, env, a1, EVAL)
else:
el = eval_ast(ast, env)
f = el.values[0]
f = EVAL(a0, env)
args_list = []
for i in range(1, len(ast)):
args_list.append(EVAL(ast[i], env))
args = MalList(args_list)
if isinstance(f, MalFunc):
if f.ast:
ast = f.ast
env = f.gen_env(el.rest()) # Continue loop (TCO)
env = f.gen_env(args) # Continue loop (TCO)
else:
return f.apply(el.rest())
return f.apply(args)
else:
raise Exception("%s is not callable" % f)

Expand Down
23 changes: 11 additions & 12 deletions impls/rpython/step8_macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,16 @@ def quasiquote(ast):
else:
return ast

def eval_ast(ast, env):
assert isinstance(ast, MalList)
res = []
for a in ast.values:
res.append(EVAL(a, env))
return MalList(res)

def EVAL(ast, env):
while True:
if env.get_or_None(MalSym(u"DEBUG-EVAL")) not in (None, nil, false):
if env.get(u"DEBUG-EVAL") not in (None, nil, false):
print(u"EVAL: " + printer._pr_str(ast))
if types._symbol_Q(ast):
assert isinstance(ast, MalSym)
return env.get(ast)
value = env.get(ast.value)
if value is None:
throw_str("'" + str(ast.value) + "' not found")
return value
elif types._vector_Q(ast):
res = []
for a in ast.values:
Expand Down Expand Up @@ -100,8 +96,8 @@ def EVAL(ast, env):
elif u"do" == a0sym:
if len(ast) == 0:
return nil
elif len(ast) > 1:
eval_ast(ast.slice2(1, len(ast)-1), env)
for i in range(1, len(ast) - 1):
EVAL(ast[i], env)
ast = ast[-1] # Continue loop (TCO)
elif u"if" == a0sym:
a1, a2 = ast[1], ast[2]
Expand All @@ -119,7 +115,10 @@ def EVAL(ast, env):
if f.ismacro:
ast = f.apply(ast.rest()) # Continue loop (TCO)
continue
args = eval_ast(ast.rest(), env)
args_list = []
for i in range(1, len(ast)):
args_list.append(EVAL(ast[i], env))
args = MalList(args_list)
if isinstance(f, MalFunc):
if f.ast:
ast = f.ast
Expand Down
Loading

0 comments on commit 3fe9d20

Please sign in to comment.