From 94a2d5f5fa3ed9056d907b6f0649a2fe5b239b2b Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 15 Aug 2015 11:04:12 -0400 Subject: [PATCH] Implement class attribute access through instances. --- artiq/compiler/builtins.py | 6 ++- artiq/compiler/ir.py | 31 +++++++++++++++- .../compiler/transforms/artiq_ir_generator.py | 15 +++++++- .../compiler/transforms/asttyped_rewriter.py | 4 +- artiq/compiler/transforms/inferencer.py | 20 +++++++--- .../compiler/transforms/llvm_ir_generator.py | 30 +++++++++------ artiq/compiler/types.py | 37 +++++++++++++++++-- lit-test/test/integration/instance.py | 16 ++++++++ 8 files changed, 130 insertions(+), 29 deletions(-) create mode 100644 lit-test/test/integration/instance.py diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 60c7af718..16e24381c 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -202,5 +202,7 @@ def is_collection(typ): def is_allocated(typ): return typ.fold(False, lambda accum, typ: - is_list(typ) or is_str(typ) or types.is_function(typ) or - is_exception(typ) or types.is_constructor(typ)) + accum or not (is_none(typ) or is_bool(typ) or is_int(typ) or + is_float(typ) or is_range(typ) or + types.is_c_function(typ) or types.is_rpc_function(typ) or + types.is_value(typ))) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 0f3ca3131..90d5ef1a1 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -554,7 +554,7 @@ class GetLocal(Instruction): self.var_name = var_name def opcode(self): - return "getlocal({})".format(self.var_name) + return "getlocal({})".format(repr(self.var_name)) def environment(self): return self.operands[0] @@ -582,7 +582,7 @@ class SetLocal(Instruction): self.var_name = var_name def opcode(self): - return "setlocal({})".format(self.var_name) + return "setlocal({})".format(repr(self.var_name)) def environment(self): return self.operands[0] @@ -590,6 +590,33 @@ class SetLocal(Instruction): def value(self): return self.operands[1] +class GetConstructor(Instruction): + """ + An intruction that loads a local variable with the given type + from an environment, possibly going through multiple levels of indirection. + + :ivar var_name: (string) variable name + """ + + """ + :param env: (:class:`Value`) local environment + :param var_name: (string) local variable name + :param var_type: (:class:`types.Type`) local variable type + """ + def __init__(self, env, var_name, var_type, name=""): + assert isinstance(env, Value) + assert isinstance(env.type, TEnvironment) + assert isinstance(var_name, str) + assert isinstance(var_type, types.Type) + super().__init__([env], var_type, name) + self.var_name = var_name + + def opcode(self): + return "getconstructor({})".format(repr(self.var_name)) + + def environment(self): + return self.operands[0] + class GetAttr(Instruction): """ An intruction that loads an attribute from an object, diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 01fd558b2..c61449c35 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -711,6 +711,14 @@ class ARTIQIRGenerator(algorithm.Visitor): finally: self.current_assign = old_assign + if node.attr not in node.type.find().attributes: + # A class attribute. Get the constructor (class object) and + # extract the attribute from it. + constructor = obj.type.constructor + obj = self.append(ir.GetConstructor(self._env_for(constructor.name), + constructor.name, constructor, + name="constructor." + constructor.name)) + if self.current_assign is None: return self.append(ir.GetAttr(obj, node.attr, name="{}.{}".format(_readable_name(obj), node.attr))) @@ -1398,10 +1406,13 @@ class ARTIQIRGenerator(algorithm.Visitor): assert False def visit_CallT(self, node): - if types.is_builtin(node.func.type): + typ = node.func.type.find() + + if types.is_constructor(typ) and not types.is_exn_constructor(typ): + return self.append(ir.Alloc([], typ.instance)) + elif types.is_builtin(typ): return self.visit_builtin_call(node) else: - typ = node.func.type.find() func = self.visit(node.func) args = [None] * (len(typ.args) + len(typ.optargs)) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 5f02431c4..15f74f8ca 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -277,14 +277,14 @@ class ASTTypedRewriter(algorithm.Transformer): # Its attributes are those of the class environment, but wrapped # appropriately so that they are linked to the class from which they # originate. - instance_type = types.TMono(node.name) - instance_type.attributes = OrderedDict({}) # TODO + instance_type = types.TInstance(node.name) # The second type is the type of the constructor itself (in other words, # the class object): it is simply a singleton type that has the class # environment as attributes. constructor_type = types.TConstructor(instance_type) constructor_type.attributes = extractor.typing_env + instance_type.constructor = constructor_type self.env_stack[-1][node.name] = constructor_type diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 5c906939d..2400723d0 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -91,6 +91,11 @@ class Inferencer(algorithm.Visitor): # assumes no free type variables in .attributes self._unify(node.type, object_type.attributes[node.attr], node.loc, None) + elif types.is_instance(object_type) and \ + node.attr in object_type.constructor.attributes: + # assumes no free type variables in .attributes + self._unify(node.type, object_type.constructor.attributes[node.attr], + node.loc, None) else: diag = diagnostic.Diagnostic("error", "type {type} does not have an attribute '{attr}'", @@ -680,19 +685,24 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) return - if types.is_var(node.func.type): + typ = node.func.type.find() + + if types.is_var(typ): return # not enough info yet - elif types.is_builtin(node.func.type): + elif types.is_constructor(typ) and not types.is_exn_constructor(typ): + self._unify(node.type, typ.find().instance, + node.loc, None) + return + elif types.is_builtin(typ): return self.visit_builtin_call(node) - elif not types.is_function(node.func.type): + elif not types.is_function(typ): diag = diagnostic.Diagnostic("error", "cannot call this expression of type {type}", - {"type": types.TypePrinter().name(node.func.type)}, + {"type": types.TypePrinter().name(typ)}, node.func.loc, []) self.engine.process(diag) return - typ = node.func.type.find() passed_args = dict() if len(node.args) > typ.arity(): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 5b2dfb929..e068dcc05 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -224,7 +224,9 @@ class LLVMIRGenerator: return llty.as_pointer() else: # Catch-all for exceptions and custom classes if builtins.is_exception(typ): - name = 'Exception' # they all share layout + name = "class.Exception" # they all share layout + elif types.is_constructor(typ): + name = "class.{}".format(typ.name) else: name = typ.name @@ -437,24 +439,23 @@ class LLVMIRGenerator: size=llsize) llvalue = self.llbuilder.insert_value(llvalue, llalloc, 1, name=insn.name) return llvalue - elif builtins.is_exception(insn.type) or types.is_constructor(insn.type): + elif not builtins.is_allocated(insn.type): + llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) + for index, elt in enumerate(insn.operands): + llvalue = self.llbuilder.insert_value(llvalue, self.map(elt), index) + llvalue.name = insn.name + return llvalue + else: # catchall for exceptions and custom (allocated) classes llalloc = self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True)) for index, operand in enumerate(insn.operands): lloperand = self.map(operand) llfieldptr = self.llbuilder.gep(llalloc, [self.llindex(0), self.llindex(index)]) self.llbuilder.store(lloperand, llfieldptr) return llalloc - elif builtins.is_allocated(insn.type): - assert False - else: # immutable - llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) - for index, elt in enumerate(insn.operands): - llvalue = self.llbuilder.insert_value(llvalue, self.map(elt), index) - llvalue.name = insn.name - return llvalue - def llptr_to_var(self, llenv, env_ty, var_name): - if var_name in env_ty.params: + def llptr_to_var(self, llenv, env_ty, var_name, var_type=None): + if var_name in env_ty.params and (var_type is None or + env_ty.params[var_name] == var_type): var_index = list(env_ty.params.keys()).index(var_name) return self.llbuilder.gep(llenv, [self.llindex(0), self.llindex(var_index)]) else: @@ -468,6 +469,11 @@ class LLVMIRGenerator: llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) return self.llbuilder.load(llptr) + def process_GetConstructor(self, insn): + env = insn.environment() + llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name, insn.type) + return self.llbuilder.load(llptr) + def process_SetLocal(self, insn): env = insn.environment() llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 93b3b3d20..152b71cc8 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -300,7 +300,7 @@ class TBuiltin(Type): return fn(accum, self) def __repr__(self): - return "py2llvm.types.TBuiltin(%s)" % repr(self.name) + return "py2llvm.types.{}({})".format(type(self).__name__, repr(self.name)) def __eq__(self, other): return isinstance(other, TBuiltin) and \ @@ -316,9 +316,9 @@ class TBuiltinFunction(TBuiltin): class TConstructor(TBuiltin): """ - A type of a constructor of a builtin class, e.g. ``list``. + A type of a constructor of a class, e.g. ``list``. Note that this is not the same as the type of an instance of - the class, which is ``TMono("list", ...)``. + the class, which is ``TMono("list", ...)`` (or a descendant). :ivar instance: (:class:`Type`) the type of the instance created by this constructor @@ -331,11 +331,29 @@ class TConstructor(TBuiltin): class TExceptionConstructor(TConstructor): """ - A type of a constructor of a builtin exception, e.g. ``Exception``. + A type of a constructor of an exception, e.g. ``Exception``. Note that this is not the same as the type of an instance of the class, which is ``TMono("Exception", ...)``. """ +class TInstance(TMono): + """ + A type of an instance of a user-defined class. + + :ivar constructor: (:class:`TConstructor`) + the type of the constructor with which this instance + was created + """ + + def __init__(self, name, attributes=OrderedDict()): + super().__init__(name) + self.attributes = attributes + + def __repr__(self): + return "py2llvm.types.TInstance({}, {]})".format( + repr(self.name), repr(self.attributes)) + + class TValue(Type): """ A type-level value (such as the integer denoting width of @@ -426,6 +444,17 @@ def is_exn_constructor(typ, name=None): else: return isinstance(typ, TExceptionConstructor) +def is_instance(typ, name=None): + typ = typ.find() + if name is not None: + return isinstance(typ, TInstance) and \ + typ.name == name + else: + return isinstance(typ, TInstance) + +def is_value(typ): + return isinstance(typ.find(), TValue) + def get_value(typ): typ = typ.find() if isinstance(typ, TVar): diff --git a/lit-test/test/integration/instance.py b/lit-test/test/integration/instance.py new file mode 100644 index 000000000..7d55f3c28 --- /dev/null +++ b/lit-test/test/integration/instance.py @@ -0,0 +1,16 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s + +class c: + a = 1 + +i = c() + +# CHECK-L: a 1 +print("a", i.a) + +def f(): + c = None + # CHECK-L: shadow a 1 + print("shadow a", i.a) +f()