def run(self, env: EvalEnv) -> None: lhs = env[self.lhs] rhs = env[self.rhs] if self.op == Binop.SYM_EQ: assert isinstance(lhs, SSym) and isinstance(rhs, SSym) env[self.dest] = SBool(lhs == rhs) elif self.op == Binop.PTR_EQ: env[self.dest] = SBool(lhs.address() == rhs.address()) else: assert isinstance(lhs, SNum) and isinstance(rhs, SNum) if self.op == Binop.ADD: env[self.dest] = SNum(lhs.value + rhs.value) elif self.op == Binop.SUB: env[self.dest] = SNum(lhs.value - rhs.value) elif self.op == Binop.MUL: env[self.dest] = SNum(lhs.value * rhs.value) elif self.op == Binop.DIV: assert rhs.value != 0 env[self.dest] = SNum(lhs.value // rhs.value) elif self.op == Binop.MOD: assert rhs.value != 0 env[self.dest] = SNum(lhs.value % rhs.value) elif self.op == Binop.NUM_EQ: env[self.dest] = SBool(lhs == rhs) elif self.op == Binop.NUM_LT: env[self.dest] = SBool(lhs < rhs) else: raise ValueError(f"Unexpected op {self.op}")
def test_dataflow_unstable_type(self) -> None: func, blocks = make_branch_func_object() bb0, bb1, bb2, bb3 = blocks opt = FunctionOptimizer(func) opt.dataflow(bytecode.EvalEnv()) after_0 = (TypeMap({Var('v0'): SchemeNum}), ValueMap({Var('v0'): SNum(42)})) self.assertEqual(opt.block_input_maps(bb1), after_0) self.assertEqual(opt.block_input_maps(bb2), after_0) self.assertEqual( opt.block_input_maps(bb3), (TypeMap({Var('v0'): SchemeObject}), ValueMap()), ) self.assertEqual( opt.info, { id(bb0): [(TypeMap(), ValueMap()), after_0, after_0, after_0], id(bb1): [ after_0, (TypeMap({Var('v0'): SchemeNum }), ValueMap({Var('v0'): SNum(69)})), (TypeMap({Var('v0'): SchemeNum }), ValueMap({Var('v0'): SNum(69)})), ], id(bb2): [ after_0, (TypeMap({Var('v0'): SchemeSym }), ValueMap({Var('v0'): SSym('hi')})), (TypeMap({Var('v0'): SchemeSym }), ValueMap({Var('v0'): SSym('hi')})), ], id(bb3): [ (TypeMap({Var('v0'): SchemeObject}), ValueMap()), (TypeMap({Var('v0'): SchemeObject}), ValueMap()), ], })
def run_abstract(self, env: EvalEnv, types: TypeMap, values: ValueMap) -> None: # Type-based transfer function if self.op in (Binop.ADD, Binop.SUB, Binop.MUL, Binop.DIV, Binop.MOD): types[self.dest] = scheme_types.SchemeNum elif self.op in (Binop.SYM_EQ, Binop.PTR_EQ, Binop.NUM_EQ, Binop.NUM_LT): types[self.dest] = scheme_types.SchemeBool else: raise ValueError(f"Unexpected op {self.op}") lhs, rhs = values[self.lhs], values[self.rhs] values[self.dest] = None if lhs is None or rhs is None: # Cannot do any constant folding return if self.op == Binop.SYM_EQ: if not (isinstance(lhs, SSym) and isinstance(rhs, SSym)): print("Unexpected args to SYM_EQ {self} ({lhs}, {rhs})") return types[self.dest] = scheme_types.SchemeBool values[self.dest] = SBool(lhs == rhs) elif self.op == Binop.PTR_EQ: types[self.dest] = scheme_types.SchemeBool values[self.dest] = SBool(lhs.address() == rhs.address()) else: if not (isinstance(lhs, SNum) and isinstance(rhs, SNum)): print("Unexpected args to arith {self} ({lhs}, {rhs})") return res: Value if self.op == Binop.ADD: res = SNum(lhs.value + rhs.value) elif self.op == Binop.SUB: res = SNum(lhs.value - rhs.value) elif self.op == Binop.MUL: res = SNum(lhs.value * rhs.value) elif self.op == Binop.DIV: if rhs.value == 0: print("Unexpected div by zero {self} ({rhs})") return res = SNum(lhs.value // rhs.value) elif self.op == Binop.MOD: if rhs.value == 0: print("Unexpected mod by zero {self} ({rhs})") return res = SNum(lhs.value % rhs.value) elif self.op == Binop.NUM_EQ: res = SBool(lhs == rhs) elif self.op == Binop.NUM_LT: res = SBool(lhs < rhs) else: raise ValueError(f"Unexpected op {self.op}") values[self.dest] = res types[self.dest] = scheme_types.get_type(res)
def make_func() -> Function: bb0 = BasicBlock("bb0") bb1 = BasicBlock("bb1") bb0.add_inst(bytecode.CopyInst(Var('v0'), NumLit(SNum(42)))) bb0.add_inst( bytecode.BinopInst(Var('v1'), Binop.ADD, Var('v0'), NumLit(SNum(69)))) bb0.add_inst(bytecode.JmpInst(bb1)) bb1.add_inst(bytecode.ReturnInst(Var('v1'))) return Function([], bb0)
def test_function_def(self) -> None: prog = '(define (funcy spam egg) (+ spam egg)) (funcy 42 43)' self.assertEqual([ SFunction( SSym('funcy'), [SSym('spam'), SSym('egg')], sexp.to_slist([SCall(SSym('+'), [SSym('spam'), SSym('egg')])])), SCall(SSym('funcy'), [SNum(42), SNum(43)]) ], sexp.parse(prog))
def run_abstract(self, env: EvalEnv, types: TypeMap, values: ValueMap) -> None: ty = types[self.func] val = values[self.func] types[self.dest] = scheme_types.SchemeNum if isinstance(ty, SchemeFunctionType) and ty.arity is not None: values[self.dest] = SNum(ty.arity) elif isinstance(val, sexp.SFunction): values[self.dest] = SNum(len(val.params)) else: values[self.dest] = None
def test_lambda_called_inline(self) -> None: self.maxDiff = None prog = '((lambda (spam egg) (+ spam egg)) 42 43)' self.assertEqual([ SCall( SFunction(SSym('__lambda0'), [SSym('spam'), SSym('egg')], sexp.to_slist( [SCall(SSym('+'), [SSym('spam'), SSym('egg')])]), is_lambda=True), [SNum(42), SNum(43)]) ], sexp.parse(prog))
def run_abstract(self, env: EvalEnv, types: TypeMap, values: ValueMap) -> None: ty = types[self.addr] types[self.dest] = scheme_types.SchemeNum vect = values[self.addr] if vect is None: values[self.dest] = None if isinstance(ty, scheme_types.SchemeVectType) and ty.length: values[self.dest] = SNum(ty.length) elif isinstance(vect, SVect): values[self.dest] = SNum(len(vect.items)) else: print(f"Unexpected length param {self} ({vect})")
def make_branch_func_object() -> Tuple[Function, Tuple[BasicBlock, ...]]: bb0 = BasicBlock("bb0") bb1 = BasicBlock("bb1") bb2 = BasicBlock("bb2") bb3 = BasicBlock("bb3") bb0.add_inst(bytecode.CopyInst(Var('v0'), NumLit(SNum(42)))) bb0.add_inst(bytecode.BrInst(Var('x'), bb1)) bb0.add_inst(bytecode.JmpInst(bb2)) bb1.add_inst( bytecode.BinopInst(Var('v0'), Binop.ADD, Var('v0'), NumLit(SNum(27)))) bb1.add_inst(bytecode.JmpInst(bb3)) bb2.add_inst(bytecode.CopyInst(Var('v0'), SymLit(SSym('hi')))) bb2.add_inst(bytecode.JmpInst(bb3)) bb3.add_inst(bytecode.ReturnInst(Var('v0'))) return Function([Var('x')], bb0), (bb0, bb1, bb2, bb3)
def test_parse_atoms(self) -> None: self.assertEqual(sexp.parse("hi"), [SSym("hi")]) self.assertEqual(sexp.parse("hi hey hoi"), [ SSym("hi"), SSym("hey"), SSym("hoi"), ]) self.assertEqual(sexp.parse("42 foo"), [SNum(42), SSym("foo")])
def test_intrinsics(self) -> None: env = bytecode.EvalEnv() runner.add_intrinsics(env) self.assertEqual(run(env, '(inst/typeof 42)'), SSym('number')) self.assertEqual(run(env, '(inst/typeof [1 2 3])'), SSym('vector')) self.assertEqual(run(env, "(inst/typeof 'hi)"), SSym('symbol')) with self.assertRaises(errors.Trap, msg="(trap)"): run(env, '(inst/trap)') self.assertEqual(run(env, '(inst/length (inst/alloc 42))'), SNum(42)) self.assertEqual( run(env, '(inst/load (inst/store (inst/alloc 8) 3 42) 3)'), SNum(42)) self.assertEqual(run(env, '(inst/+ 18 24)'), SNum(18 + 24)) self.assertEqual(run(env, '(inst/- 18 24)'), SNum(18 - 24)) self.assertEqual(run(env, '(inst/* 18 24)'), SNum(18 * 24)) self.assertEqual(run(env, '(inst// 18 24)'), SNum(18 // 24)) self.assertEqual(run(env, '(inst/% 18 24)'), SNum(18 % 24)) self.assertEqual(run(env, '(inst/number= 18 18)'), SBool(True)) self.assertEqual(run(env, '(inst/number= 18 -18)'), SBool(False)) self.assertEqual(run(env, "(inst/symbol= 'hi 'hey)"), SBool(False)) self.assertEqual(run(env, "(inst/symbol= 'hi 'hi)"), SBool(True)) self.assertEqual(run(env, "(inst/pointer= [] 0)"), SBool(False)) # Use a copy of the environment, since (lambda) adds a name to # the environment. self.assertEqual( run(bytecode.EvalEnv({}, dict(env._global_env)), '((lambda (x) (inst/pointer= x x)) [1])'), SBool(True)) self.assertEqual(run(env, '(inst/number< -1 0)'), SBool(True))
def test_specialize_value(self) -> None: env = get_builtins() func = env._global_env[SSym('+')] assert isinstance(func, sexp.SFunction) assert func.code code = copy.deepcopy(func.code) opt = FunctionOptimizer(code) opt.specialization = (SchemeNum, SchemeNum) opt.inputs = (SNum(42), SNum(27)) opt.optimize(env) self.assertEqual( str(code), ''' function (? a b) entry=bb0 bb0: return 69 '''.strip())
def test_quote(self) -> None: self.assertEqual([Quote(SSym('spam'))], sexp.parse("'spam")) self.assertEqual([Quote(Nil)], sexp.parse("'()")) self.assertEqual([Quote(Nil)], sexp.parse("(quote ())")) self.assertEqual([ Quote(sexp.to_slist( [SSym('if'), SBool(True), SNum(2), SNum(3)])) ], sexp.parse("'(if true 2 3)")) self.assertEqual( [Quote(sexp.to_slist([SNum(1), SNum(2), SNum(3)]))], sexp.parse("(quote (1 2 3))")) self.assertEqual(sexp.parse("'(1 2 3)"), sexp.parse("(quote (1 2 3))")) self.assertEqual(str(sexp.parse("(quote (1 2 3))")[0]), "'(1 2 3)")
def test_lambda(self) -> None: prog = '(lambda (spam egg) (+ spam egg)) (lambda () 42)' self.assertEqual([ SFunction( SSym('__lambda0'), [SSym('spam'), SSym('egg')], sexp.to_slist([SCall(SSym('+'), [SSym('spam'), SSym('egg')])]), is_lambda=True), SFunction(SSym('__lambda1'), [], sexp.to_slist([SNum(42)]), is_lambda=True), ], sexp.parse(prog))
def test_call_specialized(self) -> None: bb0 = bytecode.BasicBlock("bb0") bb0.add_inst(bytecode.ReturnInst(BoolLit(SBool(False)))) byte_func = bytecode.Function([Var("x")], bb0) bb0_specialized = bytecode.BasicBlock("bb0") bb0_specialized.add_inst(bytecode.ReturnInst(BoolLit(SBool(True)))) byte_func_specialized = bytecode.Function([Var("x")], bb0_specialized) func = sexp.SFunction( SSym("func"), [SSym("x")], sexp.to_slist([]), code=byte_func, is_lambda=False, specializations={ (scheme_types.SchemeSym, ): byte_func_specialized }, ) env = bytecode.EvalEnv(local_env={Var('f'): func}) bytecode.CallInst(Var('y'), Var('f'), [NumLit(SNum(42))]).run(env) assert env[Var('y')] == SBool(False) bytecode.CallInst(Var('y'), Var('f'), [SymLit(SSym('x'))]).run(env) assert env[Var('y')] == SBool(True) bytecode.CallInst(Var('y'), Var('f'), [SymLit(SSym('x'))], specialization=(scheme_types.SchemeSym, )).run(env) assert env[Var('y')] == SBool(True) # If specialization not found, fall back to dynamic dispatch bytecode.CallInst(Var('y'), Var('f'), [SymLit(SSym('x'))], specialization=(scheme_types.SchemeNum, )).run(env) self.assertEqual(env[Var('y')], SBool(True)) bytecode.CallInst(Var('y'), Var('f'), [NumLit(SNum(42))], specialization=(scheme_types.SchemeNum, )).run(env) self.assertEqual(env[Var('y')], SBool(False))
def test_block_transfer(self) -> None: func = make_func() opt = FunctionOptimizer(func) data = opt.block_transfer(bytecode.EvalEnv(), func.start, TypeMap(), ValueMap()) self.assertEqual(data, [ (TypeMap(), ValueMap()), (TypeMap({Var('v0'): SchemeNum}), ValueMap({Var('v0'): SNum(42)})), (TypeMap({ Var('v0'): SchemeNum, Var('v1'): SchemeNum }), ValueMap({ Var('v0'): SNum(42), Var('v1'): SNum(111) })), (TypeMap({ Var('v0'): SchemeNum, Var('v1'): SchemeNum }), ValueMap({ Var('v0'): SNum(42), Var('v1'): SNum(111) })), ])
def test_to_slist(self) -> None: self.assertEqual( sexp.to_slist([SNum(1), SNum(2), SNum(3)]), SPair( SNum(1), SPair( SNum(2), SPair( SNum(3), Nil, ), ), ))
def test_comments(self) -> None: prog = """ ;;; We want to define a cool function here! (define ; hi ;; A function name (cool-func x y) ; wow ;; branches are cheaper than subtraction, right? :P (if (= x y) 0 (- x y))) """ self.assertEqual([ SFunction( SSym('cool-func'), [SSym('x'), SSym('y')], sexp.to_slist([ SConditional( SCall(SSym('='), [SSym('x'), SSym('y')]), SNum(0), SCall(SSym('-'), [SSym('x'), SSym('y')])) ])) ], sexp.parse(prog))
def test_parse_list(self) -> None: self.assertEqual(sexp.parse("()"), [Nil]) self.assertEqual( sexp.parse("(func 2 3)"), [SCall(SSym('func'), [SNum(2), SNum(3)])], )
def test_builtins(self) -> None: env = bytecode.EvalEnv() runner.add_intrinsics(env) runner.add_builtins(env) with self.assertRaises(errors.Trap, msg="(trap)"): run(env, '(trap)') with self.assertRaises(errors.Trap, msg="(trap)"): run(env, '(assert false)') self.assertEqual(run(env, '(typeof 42)'), SSym('number')) self.assertEqual(run(env, '(typeof [1 2 3])'), SSym('vector')) self.assertEqual(run(env, "(typeof 'hi)"), SSym('symbol')) self.assertEqual(run(env, '(not true)'), SBool(False)) self.assertEqual(run(env, '(not false)'), SBool(True)) with self.assertRaises(errors.Trap, msg="(trap)"): run(env, '(not 42)') self.assertEqual(run(env, '(number? 42)'), SBool(True)) self.assertEqual(run(env, '(number? [])'), SBool(False)) self.assertEqual(run(env, "(symbol? 'hi)"), SBool(True)) self.assertEqual(run(env, '(symbol? 42)'), SBool(False)) self.assertEqual(run(env, '(vector? [])'), SBool(True)) self.assertEqual(run(env, '(vector? 42)'), SBool(False)) # Use a copy of the environment, since (lambda) adds a name to # the environment. self.assertEqual( run(bytecode.EvalEnv({}, dict(env._global_env)), '(function? (lambda () []))'), SBool(True)) self.assertEqual(run(env, '(function? 42)'), SBool(False)) self.assertEqual(run(env, '(bool? true)'), SBool(True)) self.assertEqual(run(env, '(bool? false)'), SBool(True)) self.assertEqual(run(env, '(bool? 42)'), SBool(False)) self.assertEqual(run(env, '(pair? 42)'), SBool(False)) self.assertEqual(run(env, '(pair? [])'), SBool(False)) self.assertEqual(run(env, '(pair? [1])'), SBool(False)) self.assertEqual(run(env, '(pair? [1 2])'), SBool(True)) self.assertEqual(run(env, '(nil? [1 2])'), SBool(False)) self.assertEqual(run(env, '(nil? [])'), SBool(True)) self.assertEqual(run(env, '(+ 39 13)'), SNum(39 + 13)) self.assertEqual(run(env, '(- 39 13)'), SNum(39 - 13)) self.assertEqual(run(env, '(* 39 13)'), SNum(39 * 13)) self.assertEqual(run(env, '(/ 39 13)'), SNum(39 // 13)) self.assertEqual(run(env, '(% 39 13)'), SNum(39 % 13)) for op in '+-*/%': with self.assertRaises(errors.Trap, msg="(trap)"): run(env, f'({op} 39 [])') with self.assertRaises(errors.Trap, msg="(trap)"): run(env, f'({op} [] 13)') with self.assertRaises(errors.Trap, msg="(trap)"): run(env, '(/ 1 0)') with self.assertRaises(errors.Trap, msg="(trap)"): run(env, '(% 1 0)') with self.assertRaises(errors.Trap, msg="(trap)"): run(env, '(symbol= 1 0)') self.assertEqual(run(env, "(symbol= 'a 'b)"), SBool(False)) self.assertEqual(run(env, "(symbol= 'a 'a)"), SBool(True)) # Use a copy of the environment, since (lambda) adds a name to # the environment. self.assertEqual( run(bytecode.EvalEnv({}, dict(env._global_env)), "(pointer= (lambda () 0) (lambda () 0))"), SBool(False)) self.assertEqual(run(env, "(define (func) 42) (pointer= func func)"), SBool(True)) self.assertEqual(run(env, "(pointer= 0 0)"), SBool(True)) with self.assertRaises(errors.Trap, msg="(trap)"): run(env, "(number= 'num 1)") self.assertEqual(run(env, "(number= 42 43)"), SBool(False)) self.assertEqual(run(env, "(number= 42 42)"), SBool(True)) with self.assertRaises(errors.Trap, msg="(trap)"): run(env, "(number< 'num 1)") self.assertEqual(run(env, "(number< 42 42)"), SBool(False)) self.assertEqual(run(env, "(number< 42 43)"), SBool(True)) self.assertEqual(run(env, '(vector-length [1 2 3])'), SNum(3)) self.assertEqual(run(env, '(vector-index [1 2 3] 1)'), SNum(2)) self.assertEqual(run(env, '(vector-set! [1 2 3] 1 42)'), SVect([SNum(1), SNum(42), SNum(3)])) self.assertEqual(run(env, '(vector-make 4 9)'), SVect([SNum(9), SNum(9), SNum(9), SNum(9)]))
def test_vector(self) -> None: self.assertEqual(sexp.parse("[]"), [SVect([])]) self.assertEqual( sexp.parse("[1 [2 [3 []]]]"), [SVect([SNum(1), SVect([SNum(2), SVect([SNum(3), SVect([])])])])])
def run(self, env: EvalEnv) -> None: func = env[self.func] assert isinstance(func, sexp.SFunction), func env[self.dest] = SNum(len(func.params))
def test_conditional(self) -> None: prog = '(if true 42 43) (if false 44 45)' self.assertEqual([ SConditional(SBool(True), SNum(42), SNum(43)), SConditional(SBool(False), SNum(44), SNum(45)), ], sexp.parse(prog))
def run(self, env: EvalEnv) -> None: vect = env[self.addr] assert isinstance(vect, SVect), vect env[self.dest] = SNum(len(vect.items))
def test_example_inlined(self) -> None: """ function list? (v0) entry=bb0 bb0: v1 = typeof v0 v2 = sym_eq v1 'vector brn v2 false v3 = length v0 v4 = num_eq v3 0 br v4 true v5 = num_eq v3 2 brn v5 false v0 = load v0 [1] jmp bb0 true: return 'true false: return 'false """ bb0 = bytecode.BasicBlock("bb0") true = bytecode.BasicBlock("true") false = bytecode.BasicBlock("false") is_list = bytecode.Function([Var("v0")], bb0) # These tests are just to test the API bb0.add_inst(bytecode.TypeofInst(Var("v1"), Var("v0"))) bb0.add_inst( bytecode.BinopInst(Var("v2"), Binop.SYM_EQ, Var("v1"), SymLit(SSym("vector")))) bb0.add_inst(bytecode.BrnInst(Var("v2"), false)) bb0.add_inst(bytecode.LengthInst(Var("v3"), Var("v0"))) bb0.add_inst( bytecode.BinopInst(Var("v4"), Binop.NUM_EQ, Var("v3"), NumLit(SNum(0)))) bb0.add_inst(bytecode.BrInst(Var("v4"), true)) bb0.add_inst( bytecode.BinopInst(Var("v5"), Binop.NUM_EQ, Var("v3"), NumLit(SNum(2)))) bb0.add_inst(bytecode.BrnInst(Var("v5"), false)) bb0.add_inst(bytecode.LoadInst(Var("v0"), Var("v0"), NumLit(SNum(1)))) bb0.add_inst(bytecode.JmpInst(bb0)) true.add_inst(bytecode.ReturnInst(BoolLit(SBool(True)))) false.add_inst(bytecode.ReturnInst(BoolLit(SBool(False)))) env = bytecode.EvalEnv(local_env={Var("v0"): SNum(42)}) gen = bytecode.ResultGenerator(is_list.run(env)) gen.run() self.assertEqual(gen.value, SBool(False)) env = bytecode.EvalEnv(local_env={ Var("v0"): SVect([SNum(42), SVect([SNum(69), SVect([])])]) }) gen = bytecode.ResultGenerator(is_list.run(env)) gen.run() self.assertEqual(gen.value, SBool(True))