| import unittest |
| |
| from typing import List |
| |
| from mypy.ordered_dict import OrderedDict |
| |
| from mypy.test.helpers import assert_string_arrays_equal |
| |
| from mypyc.ir.ops import ( |
| BasicBlock, Goto, Return, Integer, Assign, IncRef, DecRef, Branch, |
| Call, Unbox, Box, TupleGet, GetAttr, SetAttr, Op, Value, CallC, IntOp, LoadMem, |
| GetElementPtr, LoadAddress, ComparisonOp, SetMem, Register |
| ) |
| from mypyc.ir.rtypes import ( |
| RTuple, RInstance, RType, int_rprimitive, bool_rprimitive, list_rprimitive, |
| dict_rprimitive, object_rprimitive, c_int_rprimitive, short_int_rprimitive, int32_rprimitive, |
| int64_rprimitive, RStruct, pointer_rprimitive |
| ) |
| from mypyc.ir.func_ir import FuncIR, FuncDecl, RuntimeArg, FuncSignature |
| from mypyc.ir.class_ir import ClassIR |
| from mypyc.ir.pprint import generate_names_for_ir |
| from mypyc.irbuild.vtable import compute_vtable |
| from mypyc.codegen.emit import Emitter, EmitterContext |
| from mypyc.codegen.emitfunc import generate_native_function, FunctionEmitterVisitor |
| from mypyc.primitives.registry import binary_ops |
| from mypyc.primitives.misc_ops import none_object_op |
| from mypyc.primitives.list_ops import ( |
| list_get_item_op, list_set_item_op, list_append_op |
| ) |
| from mypyc.primitives.dict_ops import ( |
| dict_new_op, dict_update_op, dict_get_item_op, dict_set_item_op |
| ) |
| from mypyc.primitives.int_ops import int_neg_op |
| from mypyc.subtype import is_subtype |
| from mypyc.namegen import NameGenerator |
| |
| |
| class TestFunctionEmitterVisitor(unittest.TestCase): |
| def setUp(self) -> None: |
| self.registers = [] # type: List[Register] |
| |
| def add_local(name: str, rtype: RType) -> Register: |
| reg = Register(rtype, name) |
| self.registers.append(reg) |
| return reg |
| |
| self.n = add_local('n', int_rprimitive) |
| self.m = add_local('m', int_rprimitive) |
| self.k = add_local('k', int_rprimitive) |
| self.l = add_local('l', list_rprimitive) # noqa |
| self.ll = add_local('ll', list_rprimitive) |
| self.o = add_local('o', object_rprimitive) |
| self.o2 = add_local('o2', object_rprimitive) |
| self.d = add_local('d', dict_rprimitive) |
| self.b = add_local('b', bool_rprimitive) |
| self.s1 = add_local('s1', short_int_rprimitive) |
| self.s2 = add_local('s2', short_int_rprimitive) |
| self.i32 = add_local('i32', int32_rprimitive) |
| self.i32_1 = add_local('i32_1', int32_rprimitive) |
| self.i64 = add_local('i64', int64_rprimitive) |
| self.i64_1 = add_local('i64_1', int64_rprimitive) |
| self.ptr = add_local('ptr', pointer_rprimitive) |
| self.t = add_local('t', RTuple([int_rprimitive, bool_rprimitive])) |
| self.tt = add_local( |
| 'tt', RTuple([RTuple([int_rprimitive, bool_rprimitive]), bool_rprimitive])) |
| ir = ClassIR('A', 'mod') |
| ir.attributes = OrderedDict([('x', bool_rprimitive), ('y', int_rprimitive)]) |
| compute_vtable(ir) |
| ir.mro = [ir] |
| self.r = add_local('r', RInstance(ir)) |
| |
| self.context = EmitterContext(NameGenerator([['mod']])) |
| |
| def test_goto(self) -> None: |
| self.assert_emit(Goto(BasicBlock(2)), |
| "goto CPyL2;") |
| |
| def test_return(self) -> None: |
| self.assert_emit(Return(self.m), |
| "return cpy_r_m;") |
| |
| def test_integer(self) -> None: |
| self.assert_emit(Assign(self.n, Integer(5)), |
| "cpy_r_n = 10;") |
| self.assert_emit(Assign(self.i32, Integer(5, c_int_rprimitive)), |
| "cpy_r_i32 = 5;") |
| |
| def test_tuple_get(self) -> None: |
| self.assert_emit(TupleGet(self.t, 1, 0), 'cpy_r_r0 = cpy_r_t.f1;') |
| |
| def test_load_None(self) -> None: |
| self.assert_emit(LoadAddress(none_object_op.type, none_object_op.src, 0), |
| "cpy_r_r0 = (PyObject *)&_Py_NoneStruct;") |
| |
| def test_assign_int(self) -> None: |
| self.assert_emit(Assign(self.m, self.n), |
| "cpy_r_m = cpy_r_n;") |
| |
| def test_int_add(self) -> None: |
| self.assert_emit_binary_op( |
| '+', self.n, self.m, self.k, |
| "cpy_r_r0 = CPyTagged_Add(cpy_r_m, cpy_r_k);") |
| |
| def test_int_sub(self) -> None: |
| self.assert_emit_binary_op( |
| '-', self.n, self.m, self.k, |
| "cpy_r_r0 = CPyTagged_Subtract(cpy_r_m, cpy_r_k);") |
| |
| def test_int_neg(self) -> None: |
| self.assert_emit(CallC(int_neg_op.c_function_name, [self.m], int_neg_op.return_type, |
| int_neg_op.steals, int_neg_op.is_borrowed, int_neg_op.is_borrowed, |
| int_neg_op.error_kind, 55), |
| "cpy_r_r0 = CPyTagged_Negate(cpy_r_m);") |
| |
| def test_branch(self) -> None: |
| self.assert_emit(Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL), |
| """if (cpy_r_b) { |
| goto CPyL8; |
| } else |
| goto CPyL9; |
| """) |
| b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL) |
| b.negated = True |
| self.assert_emit(b, |
| """if (!cpy_r_b) { |
| goto CPyL8; |
| } else |
| goto CPyL9; |
| """) |
| |
| def test_call(self) -> None: |
| decl = FuncDecl('myfn', None, 'mod', |
| FuncSignature([RuntimeArg('m', int_rprimitive)], int_rprimitive)) |
| self.assert_emit(Call(decl, [self.m], 55), |
| "cpy_r_r0 = CPyDef_myfn(cpy_r_m);") |
| |
| def test_call_two_args(self) -> None: |
| decl = FuncDecl('myfn', None, 'mod', |
| FuncSignature([RuntimeArg('m', int_rprimitive), |
| RuntimeArg('n', int_rprimitive)], |
| int_rprimitive)) |
| self.assert_emit(Call(decl, [self.m, self.k], 55), |
| "cpy_r_r0 = CPyDef_myfn(cpy_r_m, cpy_r_k);") |
| |
| def test_inc_ref(self) -> None: |
| self.assert_emit(IncRef(self.m), |
| "CPyTagged_IncRef(cpy_r_m);") |
| |
| def test_dec_ref(self) -> None: |
| self.assert_emit(DecRef(self.m), |
| "CPyTagged_DecRef(cpy_r_m);") |
| |
| def test_dec_ref_tuple(self) -> None: |
| self.assert_emit(DecRef(self.t), 'CPyTagged_DecRef(cpy_r_t.f0);') |
| |
| def test_dec_ref_tuple_nested(self) -> None: |
| self.assert_emit(DecRef(self.tt), 'CPyTagged_DecRef(cpy_r_tt.f0.f0);') |
| |
| def test_list_get_item(self) -> None: |
| self.assert_emit(CallC(list_get_item_op.c_function_name, [self.m, self.k], |
| list_get_item_op.return_type, list_get_item_op.steals, |
| list_get_item_op.is_borrowed, list_get_item_op.error_kind, 55), |
| """cpy_r_r0 = CPyList_GetItem(cpy_r_m, cpy_r_k);""") |
| |
| def test_list_set_item(self) -> None: |
| self.assert_emit(CallC(list_set_item_op.c_function_name, [self.l, self.n, self.o], |
| list_set_item_op.return_type, list_set_item_op.steals, |
| list_set_item_op.is_borrowed, list_set_item_op.error_kind, 55), |
| """cpy_r_r0 = CPyList_SetItem(cpy_r_l, cpy_r_n, cpy_r_o);""") |
| |
| def test_box(self) -> None: |
| self.assert_emit(Box(self.n), |
| """cpy_r_r0 = CPyTagged_StealAsObject(cpy_r_n);""") |
| |
| def test_unbox(self) -> None: |
| self.assert_emit(Unbox(self.m, int_rprimitive, 55), |
| """if (likely(PyLong_Check(cpy_r_m))) |
| cpy_r_r0 = CPyTagged_FromObject(cpy_r_m); |
| else { |
| CPy_TypeError("int", cpy_r_m); |
| cpy_r_r0 = CPY_INT_TAG; |
| } |
| """) |
| |
| def test_list_append(self) -> None: |
| self.assert_emit(CallC(list_append_op.c_function_name, [self.l, self.o], |
| list_append_op.return_type, list_append_op.steals, |
| list_append_op.is_borrowed, list_append_op.error_kind, 1), |
| """cpy_r_r0 = PyList_Append(cpy_r_l, cpy_r_o);""") |
| |
| def test_get_attr(self) -> None: |
| self.assert_emit( |
| GetAttr(self.r, 'y', 1), |
| """cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_y; |
| if (unlikely(((mod___AObject *)cpy_r_r)->_y == CPY_INT_TAG)) { |
| PyErr_SetString(PyExc_AttributeError, "attribute 'y' of 'A' undefined"); |
| } else { |
| CPyTagged_IncRef(((mod___AObject *)cpy_r_r)->_y); |
| } |
| """) |
| |
| def test_set_attr(self) -> None: |
| self.assert_emit( |
| SetAttr(self.r, 'y', self.m, 1), |
| """if (((mod___AObject *)cpy_r_r)->_y != CPY_INT_TAG) { |
| CPyTagged_DecRef(((mod___AObject *)cpy_r_r)->_y); |
| } |
| ((mod___AObject *)cpy_r_r)->_y = cpy_r_m; |
| cpy_r_r0 = 1; |
| """) |
| |
| def test_dict_get_item(self) -> None: |
| self.assert_emit(CallC(dict_get_item_op.c_function_name, [self.d, self.o2], |
| dict_get_item_op.return_type, dict_get_item_op.steals, |
| dict_get_item_op.is_borrowed, dict_get_item_op.error_kind, 1), |
| """cpy_r_r0 = CPyDict_GetItem(cpy_r_d, cpy_r_o2);""") |
| |
| def test_dict_set_item(self) -> None: |
| self.assert_emit(CallC(dict_set_item_op.c_function_name, [self.d, self.o, self.o2], |
| dict_set_item_op.return_type, dict_set_item_op.steals, |
| dict_set_item_op.is_borrowed, dict_set_item_op.error_kind, 1), |
| """cpy_r_r0 = CPyDict_SetItem(cpy_r_d, cpy_r_o, cpy_r_o2);""") |
| |
| def test_dict_update(self) -> None: |
| self.assert_emit(CallC(dict_update_op.c_function_name, [self.d, self.o], |
| dict_update_op.return_type, dict_update_op.steals, |
| dict_update_op.is_borrowed, dict_update_op.error_kind, 1), |
| """cpy_r_r0 = CPyDict_Update(cpy_r_d, cpy_r_o);""") |
| |
| def test_new_dict(self) -> None: |
| self.assert_emit(CallC(dict_new_op.c_function_name, [], dict_new_op.return_type, |
| dict_new_op.steals, dict_new_op.is_borrowed, |
| dict_new_op.error_kind, 1), |
| """cpy_r_r0 = PyDict_New();""") |
| |
| def test_dict_contains(self) -> None: |
| self.assert_emit_binary_op( |
| 'in', self.b, self.o, self.d, |
| """cpy_r_r0 = PyDict_Contains(cpy_r_d, cpy_r_o);""") |
| |
| def test_int_op(self) -> None: |
| self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.ADD, 1), |
| """cpy_r_r0 = cpy_r_s1 + cpy_r_s2;""") |
| self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.SUB, 1), |
| """cpy_r_r0 = cpy_r_s1 - cpy_r_s2;""") |
| self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.MUL, 1), |
| """cpy_r_r0 = cpy_r_s1 * cpy_r_s2;""") |
| self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.DIV, 1), |
| """cpy_r_r0 = cpy_r_s1 / cpy_r_s2;""") |
| self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.MOD, 1), |
| """cpy_r_r0 = cpy_r_s1 % cpy_r_s2;""") |
| self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.AND, 1), |
| """cpy_r_r0 = cpy_r_s1 & cpy_r_s2;""") |
| self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.OR, 1), |
| """cpy_r_r0 = cpy_r_s1 | cpy_r_s2;""") |
| self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.XOR, 1), |
| """cpy_r_r0 = cpy_r_s1 ^ cpy_r_s2;""") |
| self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.LEFT_SHIFT, 1), |
| """cpy_r_r0 = cpy_r_s1 << cpy_r_s2;""") |
| self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.RIGHT_SHIFT, 1), |
| """cpy_r_r0 = cpy_r_s1 >> cpy_r_s2;""") |
| |
| def test_comparison_op(self) -> None: |
| # signed |
| self.assert_emit(ComparisonOp(self.s1, self.s2, ComparisonOp.SLT, 1), |
| """cpy_r_r0 = (Py_ssize_t)cpy_r_s1 < (Py_ssize_t)cpy_r_s2;""") |
| self.assert_emit(ComparisonOp(self.i32, self.i32_1, ComparisonOp.SLT, 1), |
| """cpy_r_r0 = cpy_r_i32 < cpy_r_i32_1;""") |
| self.assert_emit(ComparisonOp(self.i64, self.i64_1, ComparisonOp.SLT, 1), |
| """cpy_r_r0 = cpy_r_i64 < cpy_r_i64_1;""") |
| # unsigned |
| self.assert_emit(ComparisonOp(self.s1, self.s2, ComparisonOp.ULT, 1), |
| """cpy_r_r0 = cpy_r_s1 < cpy_r_s2;""") |
| self.assert_emit(ComparisonOp(self.i32, self.i32_1, ComparisonOp.ULT, 1), |
| """cpy_r_r0 = (uint32_t)cpy_r_i32 < (uint32_t)cpy_r_i32_1;""") |
| self.assert_emit(ComparisonOp(self.i64, self.i64_1, ComparisonOp.ULT, 1), |
| """cpy_r_r0 = (uint64_t)cpy_r_i64 < (uint64_t)cpy_r_i64_1;""") |
| |
| # object type |
| self.assert_emit(ComparisonOp(self.o, self.o2, ComparisonOp.EQ, 1), |
| """cpy_r_r0 = cpy_r_o == cpy_r_o2;""") |
| self.assert_emit(ComparisonOp(self.o, self.o2, ComparisonOp.NEQ, 1), |
| """cpy_r_r0 = cpy_r_o != cpy_r_o2;""") |
| |
| def test_load_mem(self) -> None: |
| self.assert_emit(LoadMem(bool_rprimitive, self.ptr, None), |
| """cpy_r_r0 = *(char *)cpy_r_ptr;""") |
| self.assert_emit(LoadMem(bool_rprimitive, self.ptr, self.s1), |
| """cpy_r_r0 = *(char *)cpy_r_ptr;""") |
| |
| def test_set_mem(self) -> None: |
| self.assert_emit(SetMem(bool_rprimitive, self.ptr, self.b, None), |
| """*(char *)cpy_r_ptr = cpy_r_b;""") |
| |
| def test_get_element_ptr(self) -> None: |
| r = RStruct("Foo", ["b", "i32", "i64"], [bool_rprimitive, |
| int32_rprimitive, int64_rprimitive]) |
| self.assert_emit(GetElementPtr(self.o, r, "b"), |
| """cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->b;""") |
| self.assert_emit(GetElementPtr(self.o, r, "i32"), |
| """cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->i32;""") |
| self.assert_emit(GetElementPtr(self.o, r, "i64"), |
| """cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->i64;""") |
| |
| def test_load_address(self) -> None: |
| self.assert_emit(LoadAddress(object_rprimitive, "PyDict_Type"), |
| """cpy_r_r0 = (PyObject *)&PyDict_Type;""") |
| |
| def assert_emit(self, op: Op, expected: str) -> None: |
| block = BasicBlock(0) |
| block.ops.append(op) |
| value_names = generate_names_for_ir(self.registers, [block]) |
| emitter = Emitter(self.context, value_names) |
| declarations = Emitter(self.context, value_names) |
| emitter.fragments = [] |
| declarations.fragments = [] |
| |
| visitor = FunctionEmitterVisitor(emitter, declarations, 'prog.py', 'prog') |
| |
| op.accept(visitor) |
| frags = declarations.fragments + emitter.fragments |
| actual_lines = [line.strip(' ') for line in frags] |
| assert all(line.endswith('\n') for line in actual_lines) |
| actual_lines = [line.rstrip('\n') for line in actual_lines] |
| expected_lines = expected.rstrip().split('\n') |
| expected_lines = [line.strip(' ') for line in expected_lines] |
| assert_string_arrays_equal(expected_lines, actual_lines, |
| msg='Generated code unexpected') |
| |
| def assert_emit_binary_op(self, |
| op: str, |
| dest: Value, |
| left: Value, |
| right: Value, |
| expected: str) -> None: |
| if op in binary_ops: |
| ops = binary_ops[op] |
| for desc in ops: |
| if (is_subtype(left.type, desc.arg_types[0]) |
| and is_subtype(right.type, desc.arg_types[1])): |
| args = [left, right] |
| if desc.ordering is not None: |
| args = [args[i] for i in desc.ordering] |
| self.assert_emit(CallC(desc.c_function_name, args, desc.return_type, |
| desc.steals, desc.is_borrowed, |
| desc.error_kind, 55), expected) |
| return |
| else: |
| assert False, 'Could not find matching op' |
| |
| |
| class TestGenerateFunction(unittest.TestCase): |
| def setUp(self) -> None: |
| self.arg = RuntimeArg('arg', int_rprimitive) |
| self.reg = Register(int_rprimitive, 'arg') |
| self.block = BasicBlock(0) |
| |
| def test_simple(self) -> None: |
| self.block.ops.append(Return(self.reg)) |
| fn = FuncIR(FuncDecl('myfunc', None, 'mod', FuncSignature([self.arg], int_rprimitive)), |
| [self.reg], |
| [self.block]) |
| value_names = generate_names_for_ir(fn.arg_regs, fn.blocks) |
| emitter = Emitter(EmitterContext(NameGenerator([['mod']])), value_names) |
| generate_native_function(fn, emitter, 'prog.py', 'prog') |
| result = emitter.fragments |
| assert_string_arrays_equal( |
| [ |
| 'CPyTagged CPyDef_myfunc(CPyTagged cpy_r_arg) {\n', |
| 'CPyL0: ;\n', |
| ' return cpy_r_arg;\n', |
| '}\n', |
| ], |
| result, msg='Generated code invalid') |
| |
| def test_register(self) -> None: |
| reg = Register(int_rprimitive) |
| op = Assign(reg, Integer(5)) |
| self.block.ops.append(op) |
| fn = FuncIR(FuncDecl('myfunc', None, 'mod', FuncSignature([self.arg], list_rprimitive)), |
| [self.reg], |
| [self.block]) |
| value_names = generate_names_for_ir(fn.arg_regs, fn.blocks) |
| emitter = Emitter(EmitterContext(NameGenerator([['mod']])), value_names) |
| generate_native_function(fn, emitter, 'prog.py', 'prog') |
| result = emitter.fragments |
| assert_string_arrays_equal( |
| [ |
| 'PyObject *CPyDef_myfunc(CPyTagged cpy_r_arg) {\n', |
| ' CPyTagged cpy_r_r0;\n', |
| 'CPyL0: ;\n', |
| ' cpy_r_r0 = 10;\n', |
| '}\n', |
| ], |
| result, msg='Generated code invalid') |