separate_compiler: Refactored class compilation
authorLouis-Vincent Boudreault <lv.boudreault95@gmail.com>
Wed, 4 Dec 2019 21:59:00 +0000 (16:59 -0500)
committerLouis-Vincent Boudreault <lv.boudreault95@gmail.com>
Wed, 29 Jan 2020 00:19:18 +0000 (19:19 -0500)
Before this update, the method `compile_class_to_c` handled every part
of the class compilation process even special case (universal class).
By doing so, we can't refined this method efficiently without
reimplementing the entire method. This is a pain, since each time we
need to add universal class we have to modify the original code instead
of refining the compiler.

The method `compile_class_to_c` has been divided in three main parts:

1. the class's vft compilation (same for every type of class)
2. the compilation of universal class specifics (change alot through
time). This part must managed every part of a universal type, except its
vft compilation which already handled by the 1st step.
3. the compilation of the default `NEW` allocator (apply only for
non-universal type).

Moreover, the state of a class compilation is stored in a new class
called `ClassCompilationInfo`, which has different flag to specify if
the class is dead/alive or need_corpse. This choice was made to prevent
dataclump in each method involved with the class compilation.

With this new way of managing class compilation, we can add new
universal class via refinement instead of code rewriting.

Signed-off-by: Louis-Vincent Boudreault <lv.boudreault95@gmail.com>

src/compiler/separate_compiler.nit
src/compiler/separate_erasure_compiler.nit

index 4716dae..4fa8fd8 100644 (file)
@@ -825,23 +825,14 @@ class SeparateCompiler
                v.add_decl("\};")
        end
 
-       # Globally compile the table of the class mclass
-       # In a link-time optimisation compiler, tables are globally computed
-       # In a true separate compiler (a with dynamic loading) you cannot do this unfortnally
-       fun compile_class_to_c(mclass: MClass)
+       protected fun compile_class_vft(ccinfo: ClassCompilationInfo, v: SeparateCompilerVisitor)
        do
-               if mclass.is_broken then return
-
-               var mtype = mclass.intro.bound_mtype
-               var c_name = mclass.c_name
-
-               var v = new_visitor
-
+               var mclass = ccinfo.mclass
+               var mtype = ccinfo.mtype
                var rta = runtime_type_analysis
-               var is_dead = rta != null and not rta.live_classes.has(mclass)
-               # While the class may be dead, some part of separately compiled code may use symbols associated to the class, so
-               # in order to compile and link correctly the C code, these symbols should be declared and defined.
-               var need_corpse = is_dead and mtype.is_c_primitive or mclass.kind == extern_kind or mclass.kind == enum_kind
+               var c_name = ccinfo.mclass.c_name
+               var is_dead = ccinfo.is_dead
+               var need_corpse = ccinfo.need_corpse
 
                v.add_decl("/* runtime class {c_name}: {mclass.full_name} (dead={is_dead}; need_corpse={need_corpse})*/")
 
@@ -873,11 +864,24 @@ class SeparateCompiler
                        v.add_decl("\}")
                        v.add_decl("\};")
                end
+       end
+
+       # Given a `MClass`, if it's a universal class and if it needs to be handle
+       # specifically by the compiler, this function will compile it and return
+       # true. Otherwise, no C code will be written in the visitor and the value
+       # false will be returned.
+       fun compile_class_if_universal(ccinfo: ClassCompilationInfo, v: SeparateCompilerVisitor): Bool
+       do
+               var mclass = ccinfo.mclass
+               var mtype = ccinfo.mtype
+               var c_name = ccinfo.mclass.c_name
+               var is_dead = ccinfo.is_dead
+               var need_corpse = ccinfo.need_corpse
 
                if mtype.is_c_primitive or mtype.mclass.name == "Pointer" then
                        # Is a primitive type or the Pointer class, not any other extern class
 
-                       if mtype.is_tagged then return
+                       if mtype.is_tagged then return true
 
                        #Build instance struct
                        self.header.add_decl("struct instance_{c_name} \{")
@@ -887,7 +891,7 @@ class SeparateCompiler
                        self.header.add_decl("\};")
 
                        # Pointer is needed by extern types, live or not
-                       if is_dead and mtype.mclass.name != "Pointer" then return
+                       if is_dead and mtype.mclass.name != "Pointer" then return true
 
                        #Build BOX
                        self.provide_declaration("BOX_{c_name}", "val* BOX_{c_name}({mtype.ctype_extern});")
@@ -905,7 +909,7 @@ class SeparateCompiler
                        v.add("\}")
 
                        # A Pointer class also need its constructor
-                       if mtype.mclass.name != "Pointer" then return
+                       if mtype.mclass.name != "Pointer" then return true
 
                        v = new_visitor
                        self.provide_declaration("NEW_{c_name}", "{mtype.ctype} NEW_{c_name}(const struct type* type);")
@@ -926,7 +930,7 @@ class SeparateCompiler
                                v.add("return {res};")
                        end
                        v.add("\}")
-                       return
+                       return true
                else if mclass.name == "NativeArray" then
                        #Build instance struct
                        self.header.add_decl("struct instance_{c_name} \{")
@@ -953,7 +957,7 @@ class SeparateCompiler
                        v.add("{res}->length = length;")
                        v.add("return (val*){res};")
                        v.add("\}")
-                       return
+                       return true
                else if mclass.name == "RoutineRef" then
                        self.header.add_decl("struct instance_{c_name} \{")
                        self.header.add_decl("const struct type *type;")
@@ -976,7 +980,7 @@ class SeparateCompiler
                        v.add("{res}->method = method;")
                        v.add("return (val*){res};")
                        v.add("\}")
-                       return
+                       return true
                else if mtype.mclass.kind == extern_kind and mtype.mclass.name != "CString" then
                        # Is an extern class (other than Pointer and CString)
                        # Pointer is caught in a previous `if`, and CString is internal
@@ -1001,8 +1005,17 @@ class SeparateCompiler
                                v.add("return {res};")
                        end
                        v.add("\}")
-                       return
+                       return true
                end
+               return false
+       end
+
+       protected fun compile_default_new(ccinfo: ClassCompilationInfo, v: SeparateCompilerVisitor)
+       do
+               var mclass = ccinfo.mclass
+               var mtype = ccinfo.mtype
+               var c_name = ccinfo.mclass.c_name
+               var is_dead = ccinfo.is_dead
 
                #Build NEW
                self.provide_declaration("NEW_{c_name}", "{mtype.ctype} NEW_{c_name}(const struct type* type);")
@@ -1036,6 +1049,35 @@ class SeparateCompiler
                        v.add("return {res};")
                end
                v.add("\}")
+
+       end
+
+       protected fun build_class_compilation_info(mclass: MClass): ClassCompilationInfo
+       do
+               var mtype = mclass.intro.bound_mtype
+               var rta = runtime_type_analysis
+               var is_dead = rta != null and not rta.live_classes.has(mclass)
+
+               # While the class may be dead, some part of separately compiled code may use symbols associated to the class, so
+               # in order to compile and link correctly the C code, these symbols should be declared and defined.
+               var need_corpse = is_dead and mtype.is_c_primitive or mclass.kind == extern_kind or mclass.kind == enum_kind
+
+               var compilation_info = new ClassCompilationInfo(mclass, is_dead, need_corpse)
+               return compilation_info
+       end
+
+       # Globally compile the table of the class mclass
+       # In a link-time optimisation compiler, tables are globally computed
+       # In a true separate compiler (a with dynamic loading) you cannot do this unfortnally
+       fun compile_class_to_c(mclass: MClass)
+       do
+               var v = new_visitor
+               var class_info = build_class_compilation_info(mclass)
+               compile_class_vft(class_info, v)
+               var is_already_managed = compile_class_if_universal(class_info, v)
+               if not is_already_managed then
+                       compile_default_new(class_info, v)
+               end
        end
 
        # Compile structures used to map tagged primitive values to their classes and types.
@@ -2551,6 +2593,34 @@ class SeparateRuntimeFunction
        end
 end
 
+# Encapsulates every information needed to compile a class.
+#
+# The compilation of a class is done by several methods, two of those are
+# mandatory :
+# - compile_class_to_c : starts the compilation process
+# - compile_class_vft : generate the virtual function table
+# And one of them is optional :
+# - compile_class_if_universal : compiles the rest of the class if its a universal
+# type. Universal type are handle in a case-basis, this is why they need special treatment.
+# Generally, universal class will have special structure and a custom allocator.
+#
+# Throughout each step of the class compilation process, some information must be share.
+# This class encapsulates the compilation process state.
+# (except vft), eg
+class ClassCompilationInfo
+       var mclass: MClass # class to compile
+       var is_dead: Bool
+       var need_corpse: Bool
+
+       # Shortcut to access the class's bound type.
+       var mtype: MClassType is noinit
+
+       init
+       do
+               mtype = mclass.intro.bound_mtype
+       end
+end
+
 class SeparateThunkFunction
        super ThunkFunction
        super SeparateRuntimeFunction
index 8e0a0b2..635e601 100644 (file)
@@ -194,74 +194,12 @@ class SeparateErasureCompiler
                self.header.add_decl("typedef struct instance \{ const struct class *class; nitattribute_t attrs[1]; \} val; /* general C type representing a Nit instance. */")
        end
 
-       redef fun compile_class_to_c(mclass: MClass)
+       redef fun compile_class_if_universal(ccinfo, v)
        do
-               var mtype = mclass.intro.bound_mtype
+               var mclass = ccinfo.mclass
+               var mtype = ccinfo.mtype
                var c_name = mclass.c_name
-
-               var class_table = self.class_tables[mclass]
-               var v = self.new_visitor
-
-               var rta = runtime_type_analysis
-               var is_dead = false # mclass.kind == abstract_kind or mclass.kind == interface_kind
-               if not is_dead and rta != null and not rta.live_classes.has(mclass) and not mtype.is_c_primitive and mclass.name != "NativeArray" then
-                       is_dead = true
-               end
-
-               v.add_decl("/* runtime class {c_name} */")
-
-               self.provide_declaration("class_{c_name}", "extern const struct class class_{c_name};")
-               v.add_decl("extern const struct type_table type_table_{c_name};")
-
-               # Build class vft
-               v.add_decl("const struct class class_{c_name} = \{")
-               v.add_decl("{class_ids[mclass]},")
-               v.add_decl("\"{mclass.name}\", /* class_name_string */")
-               v.add_decl("{self.box_kind_of(mclass)}, /* box_kind */")
-               v.add_decl("{class_colors[mclass]},")
-               if not is_dead then
-                       if build_class_vts_table(mclass) then
-                               v.require_declaration("vts_table_{c_name}")
-                               v.add_decl("&vts_table_{c_name},")
-                       else
-                               v.add_decl("NULL,")
-                       end
-                       v.add_decl("&type_table_{c_name},")
-                       v.add_decl("\{")
-                       var vft = self.method_tables.get_or_null(mclass)
-                       if vft != null then for i in [0 .. vft.length[ do
-                               var mpropdef = vft[i]
-                               if mpropdef == null then
-                                       v.add_decl("NULL, /* empty */")
-                               else
-                                       assert mpropdef isa MMethodDef
-                                       if rta != null and not rta.live_methoddefs.has(mpropdef) then
-                                               v.add_decl("NULL, /* DEAD {mclass.intro_mmodule}:{mclass}:{mpropdef} */")
-                                               continue
-                                       end
-                                       var rf = mpropdef.virtual_runtime_function
-                                       v.require_declaration(rf.c_name)
-                                       v.add_decl("(nitmethod_t){rf.c_name}, /* pointer to {mpropdef.full_name} */")
-                               end
-                       end
-                       v.add_decl("\}")
-               end
-               v.add_decl("\};")
-
-               # Build class type table
-
-               v.add_decl("const struct type_table type_table_{c_name} = \{")
-               v.add_decl("{class_table.length},")
-               v.add_decl("\{")
-               for msuper in class_table do
-                       if msuper == null then
-                               v.add_decl("-1, /* empty */")
-                       else
-                               v.add_decl("{self.class_ids[msuper]}, /* {msuper} */")
-                       end
-               end
-               v.add_decl("\}")
-               v.add_decl("\};")
+               var is_dead = ccinfo.is_dead
 
                if mtype.is_c_primitive or mtype.mclass.name == "Pointer" then
                        #Build instance struct
@@ -281,7 +219,7 @@ class SeparateErasureCompiler
                        v.add("return (val*)res;")
                        v.add("\}")
 
-                       if mtype.mclass.name != "Pointer" then return
+                       if mtype.mclass.name != "Pointer" then return true
 
                        v = new_visitor
                        self.provide_declaration("NEW_{c_name}", "{mtype.ctype} NEW_{c_name}();")
@@ -299,7 +237,7 @@ class SeparateErasureCompiler
                                v.add("return {res};")
                        end
                        v.add("\}")
-                       return
+                       return true
                else if mclass.name == "NativeArray" then
                        #Build instance struct
                        self.header.add_decl("struct instance_{c_name} \{")
@@ -321,7 +259,7 @@ class SeparateErasureCompiler
                        v.add("{res}->length = length;")
                        v.add("return (val*){res};")
                        v.add("\}")
-                       return
+                       return true
                 else if mclass.name == "RoutineRef" then
                         self.header.add_decl("struct instance_{c_name} \{")
                         self.header.add_decl("const struct class *class;")
@@ -341,7 +279,7 @@ class SeparateErasureCompiler
                         v.add("{res}->method = method;")
                         v.add("return (val*){res};")
                         v.add("\}")
-                        return
+                        return true
                else if mtype.mclass.kind == extern_kind and mtype.mclass.name != "CString" then
                        var pointer_type = mainmodule.pointer_type
 
@@ -361,8 +299,84 @@ class SeparateErasureCompiler
                                v.add("return {res};")
                        end
                        v.add("\}")
-                       return
+                       return true
                end
+               return false
+       end
+
+       redef fun compile_class_vft(ccinfo, v)
+       do
+               var mclass = ccinfo.mclass
+               var mtype = ccinfo.mtype
+               var c_name = mclass.c_name
+               var is_dead = ccinfo.is_dead
+               var rta = runtime_type_analysis
+
+               # Build class vft
+               self.provide_declaration("class_{c_name}", "extern const struct class class_{c_name};")
+
+               v.add_decl("const struct class class_{c_name} = \{")
+               v.add_decl("{class_ids[mclass]},")
+               v.add_decl("\"{mclass.name}\", /* class_name_string */")
+               v.add_decl("{self.box_kind_of(mclass)}, /* box_kind */")
+               v.add_decl("{class_colors[mclass]},")
+               if not is_dead then
+                       if build_class_vts_table(mclass) then
+                               v.require_declaration("vts_table_{c_name}")
+                               v.add_decl("&vts_table_{c_name},")
+                       else
+                               v.add_decl("NULL,")
+                       end
+                       v.add_decl("&type_table_{c_name},")
+                       v.add_decl("\{")
+                       var vft = self.method_tables.get_or_null(mclass)
+                       if vft != null then for i in [0 .. vft.length[ do
+                               var mpropdef = vft[i]
+                               if mpropdef == null then
+                                       v.add_decl("NULL, /* empty */")
+                               else
+                                       assert mpropdef isa MMethodDef
+                                       if rta != null and not rta.live_methoddefs.has(mpropdef) then
+                                               v.add_decl("NULL, /* DEAD {mclass.intro_mmodule}:{mclass}:{mpropdef} */")
+                                               continue
+                                       end
+                                       var rf = mpropdef.virtual_runtime_function
+                                       v.require_declaration(rf.c_name)
+                                       v.add_decl("(nitmethod_t){rf.c_name}, /* pointer to {mpropdef.full_name} */")
+                               end
+                       end
+                       v.add_decl("\}")
+               end
+               v.add_decl("\};")
+       end
+
+       protected fun compile_class_type_table(ccinfo: ClassCompilationInfo, v: SeparateCompilerVisitor)
+       do
+               var mclass = ccinfo.mclass
+               var c_name = mclass.c_name
+               var class_table = self.class_tables[mclass]
+
+               # Build class type table
+               v.add_decl("const struct type_table type_table_{c_name} = \{")
+               v.add_decl("{class_table.length},")
+               v.add_decl("\{")
+               for msuper in class_table do
+                       if msuper == null then
+                               v.add_decl("-1, /* empty */")
+                       else
+                               v.add_decl("{self.class_ids[msuper]}, /* {msuper} */")
+                       end
+               end
+               v.add_decl("\}")
+               v.add_decl("\};")
+       end
+
+       redef fun compile_default_new(ccinfo, v)
+       do
+               var mclass = ccinfo.mclass
+               var mtype = ccinfo.mtype
+               var c_name = mclass.c_name
+               var is_dead = ccinfo.is_dead
 
                #Build NEW
                self.provide_declaration("NEW_{c_name}", "{mtype.ctype} NEW_{c_name}(void);")
@@ -391,6 +405,33 @@ class SeparateErasureCompiler
                v.add("\}")
        end
 
+       redef fun build_class_compilation_info(mclass)
+       do
+               var ccinfo = super
+               var mtype = ccinfo.mtype
+               var rta = runtime_type_analysis
+               var is_dead = false # mclass.kind == abstract_kind or mclass.kind == interface_kind
+               if not is_dead and rta != null and not rta.live_classes.has(mclass) and not mtype.is_c_primitive and mclass.name != "NativeArray" then
+                       is_dead = true
+               end
+               ccinfo.is_dead = is_dead
+               return ccinfo
+       end
+
+       redef fun compile_class_to_c(mclass: MClass)
+       do
+               var ccinfo = build_class_compilation_info(mclass)
+               var v = new_visitor
+               v.add_decl("/* runtime class {mclass.c_name} */")
+               self.provide_declaration("class_{mclass.c_name}", "extern const struct class class_{mclass.c_name};")
+               v.add_decl("extern const struct type_table type_table_{mclass.c_name};")
+               self.compile_class_vft(ccinfo, v)
+               self.compile_class_type_table(ccinfo, v)
+               if not self.compile_class_if_universal(ccinfo, v) then
+                       self.compile_default_new(ccinfo, v)
+               end
+       end
+
        private fun build_class_vts_table(mclass: MClass): Bool do
                if self.vt_tables[mclass].is_empty then return false