From d6d98741ce1f410ea61f98de4d69d0b838c7d786 Mon Sep 17 00:00:00 2001 From: Louis-Vincent Boudreault Date: Wed, 4 Dec 2019 16:59:00 -0500 Subject: [PATCH] separate_compiler: Refactored class compilation 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 --- src/compiler/separate_compiler.nit | 114 +++++++++++++---- src/compiler/separate_erasure_compiler.nit | 183 +++++++++++++++++----------- 2 files changed, 204 insertions(+), 93 deletions(-) diff --git a/src/compiler/separate_compiler.nit b/src/compiler/separate_compiler.nit index 4716dae..4fa8fd8 100644 --- a/src/compiler/separate_compiler.nit +++ b/src/compiler/separate_compiler.nit @@ -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 diff --git a/src/compiler/separate_erasure_compiler.nit b/src/compiler/separate_erasure_compiler.nit index 8e0a0b2..635e601 100644 --- a/src/compiler/separate_erasure_compiler.nit +++ b/src/compiler/separate_erasure_compiler.nit @@ -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 -- 1.7.9.5