Property definitions

nitc $ AbstractCompiler :: defaultinit
# Singleton that store the knowledge about the compilation process
abstract class AbstractCompiler
	type VISITOR: AbstractCompilerVisitor

	# Table corresponding c_names to nit names (methods)
	var names = new HashMap[String, String]

	# The main module of the program currently compiled
	# Is assigned during the separate compilation
	var mainmodule: MModule is writable

	# The real main module of the program
	var realmainmodule: MModule is noinit

	# The modelbuilder used to know the model and the AST
	var modelbuilder: ModelBuilder is protected writable

	# The associated toolchain
	#
	# Set by `modelbuilder.write_and_make` and permit sub-routines to access the current toolchain if required.
	var toolchain: Toolchain is noinit

	# Is hardening asked? (see --hardening)
	fun hardening: Bool do return self.modelbuilder.toolcontext.opt_hardening.value

	# The targeted specific platform
	var target_platform: Platform is noinit

	# All methods who already has a callref_thunk generated for
	var compiled_callref_thunk = new HashSet[MMethodDef]

	var all_routine_types_name: Set[String] do
		var res = new HashSet[String]
		for name in ["Fun", "Proc", "FunRef", "ProcRef"] do
			# Currently there's 20 arity per func type
			for i in [0..20[ do
				res.add("{name}{i}")
			end
		end
		return res
	end

	init
	do
		self.realmainmodule = mainmodule
		target_platform = mainmodule.target_platform or else new Platform
	end

	# Do the full code generation of the program `mainmodule`
	# It is the main method usually called after the instantiation
	fun do_compilation is abstract

	# Force the creation of a new file
	# The point is to avoid contamination between must-be-compiled-separately files
	fun new_file(name: String): CodeFile
	do
		if modelbuilder.toolcontext.opt_group_c_files.value then
			if self.files.is_empty then
				var f = new CodeFile(mainmodule.c_name)
				self.files.add(f)
			end
			return self.files.first
		end
		var f = new CodeFile(name)
		self.files.add(f)
		return f
	end

	# The list of all associated files
	# Used to generate .c files
	var files = new Array[CodeFile]

	# Initialize a visitor specific for a compiler engine
	fun new_visitor: VISITOR is abstract

	# Where global declaration are stored (the main .h)
	var header: CodeWriter is writable, noinit

	# Additionnal linker script for `ld`.
	# Mainly used to do specific link-time symbol resolution
	var linker_script = new Array[String]

	# Provide a declaration that can be requested (before or latter) by a visitor
	fun provide_declaration(key: String, s: String)
	do
		if self.provided_declarations.has_key(key) then
			assert self.provided_declarations[key] == s
		end
		self.provided_declarations[key] = s
	end

	private var provided_declarations = new HashMap[String, String]

	private var requirers_of_declarations = new HashMap[String, ANode]

	# Builds the .c and .h files to be used when generating a Stack Trace
	# Binds the generated C function names to Nit function names
	fun build_c_to_nit_bindings
	do
		var compile_dir = toolchain.compile_dir

		var stream = new FileWriter.open("{compile_dir}/c_functions_hash.c")
		stream.write("#include <string.h>\n")
		stream.write("#include <stdlib.h>\n")
		stream.write("#include \"c_functions_hash.h\"\n")
		stream.write("typedef struct C_Nit_Names\{char* name; char* nit_name;\}C_Nit_Names;\n")
		stream.write("const char* get_nit_name(register const char* procproc, register unsigned int len)\{\n")
		stream.write("char* procname = malloc(len+1);")
		stream.write("memcpy(procname, procproc, len);")
		stream.write("procname[len] = '\\0';")
		stream.write("static const C_Nit_Names map[{names.length}] = \{\n")
		for i in names.keys do
			stream.write("\{\"")
			stream.write(i.escape_to_c)
			stream.write("\",\"")
			stream.write(names[i].escape_to_c)
			stream.write("\"\},\n")
		end
		stream.write("\};\n")
		stream.write("int i;")
		stream.write("for(i = 0; i < {names.length}; i++)\{")
		stream.write("if(strcmp(procname,map[i].name) == 0)\{")
		stream.write("free(procname);")
		stream.write("return map[i].nit_name;")
		stream.write("\}")
		stream.write("\}")
		stream.write("free(procname);")
		stream.write("return NULL;")
		stream.write("\}\n")
		stream.close

		stream = new FileWriter.open("{compile_dir}/c_functions_hash.h")
		stream.write("const char* get_nit_name(register const char* procname, register unsigned int len);\n")
		stream.close

		extern_bodies.add(new ExternCFile("c_functions_hash.c", ""))
	end

	# Compile C headers
	# This method call compile_header_strucs method that has to be refined
	fun compile_header do
		self.header.add_decl("#include <stdlib.h>")
		self.header.add_decl("#include <stdio.h>")
		self.header.add_decl("#include <string.h>")
		# longjmp !
		self.header.add_decl("#include <setjmp.h>\n")
		self.header.add_decl("#include <sys/types.h>\n")
		self.header.add_decl("#include <unistd.h>\n")
		self.header.add_decl("#include <stdint.h>\n")
		self.header.add_decl("#ifdef __linux__")
		self.header.add_decl("	#include <endian.h>")
		self.header.add_decl("#endif")
		self.header.add_decl("#include <inttypes.h>\n")
		self.header.add_decl("#include \"gc_chooser.h\"")
		if modelbuilder.toolcontext.opt_trace.value then self.header.add_decl("#include \"traces.h\"")
		self.header.add_decl("#ifdef __APPLE__")
		self.header.add_decl("	#include <TargetConditionals.h>")
		self.header.add_decl("	#include <syslog.h>")
		self.header.add_decl("	#include <libkern/OSByteOrder.h>")
		self.header.add_decl("	#define be32toh(x) OSSwapBigToHostInt32(x)")
		self.header.add_decl("#endif")
		self.header.add_decl("#ifdef _WIN32")
		self.header.add_decl("	#define be32toh(val) _byteswap_ulong(val)")
		self.header.add_decl("#endif")
		self.header.add_decl("#ifdef ANDROID")
		self.header.add_decl("	#ifndef be32toh")
		self.header.add_decl("		#define be32toh(val) betoh32(val)")
		self.header.add_decl("	#endif")
		self.header.add_decl("	#include <android/log.h>")
		self.header.add_decl("	#define PRINT_ERROR(...) (void)__android_log_print(ANDROID_LOG_WARN, \"Nit\", __VA_ARGS__)")
		self.header.add_decl("#elif TARGET_OS_IPHONE")
		self.header.add_decl("	#define PRINT_ERROR(...) syslog(LOG_ERR, __VA_ARGS__)")
		self.header.add_decl("#else")
		self.header.add_decl("	#define PRINT_ERROR(...) fprintf(stderr, __VA_ARGS__)")
		self.header.add_decl("#endif")

		compile_header_structs
		compile_nitni_structs
		compile_catch_stack

		var gccd_disable = modelbuilder.toolcontext.opt_no_gcc_directive.value
		if gccd_disable.has("noreturn") or gccd_disable.has("all") then
			# Signal handler function prototype
			self.header.add_decl("void fatal_exit(int);")
		else
			self.header.add_decl("void fatal_exit(int) __attribute__ ((noreturn));")
		end

		if gccd_disable.has("likely") or gccd_disable.has("all") then
			self.header.add_decl("#define likely(x)       (x)")
			self.header.add_decl("#define unlikely(x)     (x)")
		else if gccd_disable.has("correct-likely") then
			# invert the `likely` definition
			# Used by masochists to bench the worst case
			self.header.add_decl("#define likely(x)       __builtin_expect((x),0)")
			self.header.add_decl("#define unlikely(x)     __builtin_expect((x),1)")
		else
			self.header.add_decl("#define likely(x)       __builtin_expect((x),1)")
			self.header.add_decl("#define unlikely(x)     __builtin_expect((x),0)")
		end

		# Global variable used by intern methods
		self.header.add_decl("extern int glob_argc;")
		self.header.add_decl("extern char **glob_argv;")
		self.header.add_decl("extern val *glob_sys;")
	end

	# Stack stocking environment for longjumps
	protected fun compile_catch_stack do
		self.header.add_decl """
struct catch_stack_t {
	int cursor;
	int currentSize;
	jmp_buf *envs;
};
extern struct catch_stack_t *getCatchStack();
"""
	end

	# Declaration of structures for live Nit types
	protected fun compile_header_structs is abstract

	# Declaration of structures for nitni undelying the FFI
	protected fun compile_nitni_structs
	do
		self.header.add_decl """
/* Native reference to Nit objects */
/* This structure is used to represent every Nit type in extern methods and custom C code. */
struct nitni_ref {
	struct nitni_ref *next,
		*prev; /* adjacent global references in global list */
	int count; /* number of time this global reference has been marked */
};

/* List of global references from C code to Nit objects */
/* Instanciated empty at init of Nit system and filled explicitly by user in C code */
struct nitni_global_ref_list_t {
	struct nitni_ref *head, *tail;
};
extern struct nitni_global_ref_list_t *nitni_global_ref_list;

/* Initializer of global reference list */
extern void nitni_global_ref_list_init();

/* Intern function to add a global reference to the list */
extern void nitni_global_ref_add( struct nitni_ref *ref );

/* Intern function to remove a global reference from the list */
extern void nitni_global_ref_remove( struct nitni_ref *ref );

/* Increase count on an existing global reference */
extern void nitni_global_ref_incr( struct nitni_ref *ref );

/* Decrease count on an existing global reference */
extern void nitni_global_ref_decr( struct nitni_ref *ref );
"""
	end

	fun compile_finalizer_function
	do
		var finalizable_type = mainmodule.finalizable_type
		if finalizable_type == null then return

		var finalize_meth = mainmodule.try_get_primitive_method("finalize", finalizable_type.mclass)

		if finalize_meth == null then
			modelbuilder.toolcontext.error(null, "Error: the `Finalizable` class does not declare the `finalize` method.")
			return
		end

		var v = self.new_visitor
		v.add_decl "void gc_finalize (void *obj, void *client_data) \{"
		var recv = v.new_expr("obj", finalizable_type)
		v.send(finalize_meth, [recv])
		v.add "\}"
	end

	# Hook to add specif piece of code before the the main C function.
	#
	# Is called by `compile_main_function`
	fun compile_before_main(v: VISITOR)
	do
	end

	# Hook to add specif piece of code at the begin on the main C function.
	#
	# Is called by `compile_main_function`
	fun compile_begin_main(v: VISITOR)
	do
	end

	# Generate the main C function.
	#
	# This function:
	#
	# * allocate the Sys object if it exists
	# * call init if is exists
	# * call main if it exists
	fun compile_main_function
	do
		var v = self.new_visitor
		v.add_decl("#include <signal.h>")
		var platform = target_platform

		var no_main = platform.no_main or modelbuilder.toolcontext.opt_no_main.value

		if platform.supports_libunwind then
			v.add_decl("#ifndef NO_STACKTRACE")
			v.add_decl("#define UNW_LOCAL_ONLY")
			v.add_decl("#include <libunwind.h>")
			v.add_decl("#include \"c_functions_hash.h\"")
			v.add_decl("#endif")
		end
		v.add_decl("int glob_argc;")
		v.add_decl("char **glob_argv;")
		v.add_decl("val *glob_sys;")

		# Store catch stack in thread local storage
		v.add_decl """
#if defined(TARGET_OS_IPHONE)
	// Use pthread_key_create and others for iOS
	#include <pthread.h>

	static pthread_key_t catch_stack_key;
	static pthread_once_t catch_stack_key_created = PTHREAD_ONCE_INIT;

	static void create_catch_stack()
	{
		pthread_key_create(&catch_stack_key, NULL);
	}

	struct catch_stack_t *getCatchStack()
	{
		pthread_once(&catch_stack_key_created, &create_catch_stack);
		struct catch_stack_t *data = pthread_getspecific(catch_stack_key);
		if (data == NULL) {
			data = malloc(sizeof(struct catch_stack_t));
			data->cursor = -1;
			data->currentSize = 0;
			data->envs = NULL;
			pthread_setspecific(catch_stack_key, data);
		}
		return data;
	}
#else
	// Use __thread when available
	__thread struct catch_stack_t catchStack = {-1, 0, NULL};

	struct catch_stack_t *getCatchStack()
	{
		return &catchStack;
	}
#endif
"""

		if self.modelbuilder.toolcontext.opt_typing_test_metrics.value then
			for tag in count_type_test_tags do
				v.add_decl("long count_type_test_resolved_{tag};")
				v.add_decl("long count_type_test_unresolved_{tag};")
				v.add_decl("long count_type_test_skipped_{tag};")
				v.compiler.header.add_decl("extern long count_type_test_resolved_{tag};")
				v.compiler.header.add_decl("extern long count_type_test_unresolved_{tag};")
				v.compiler.header.add_decl("extern long count_type_test_skipped_{tag};")
			end
		end

		if self.modelbuilder.toolcontext.opt_invocation_metrics.value then
			v.add_decl("long count_invoke_by_tables;")
			v.add_decl("long count_invoke_by_direct;")
			v.add_decl("long count_invoke_by_inline;")
			v.compiler.header.add_decl("extern long count_invoke_by_tables;")
			v.compiler.header.add_decl("extern long count_invoke_by_direct;")
			v.compiler.header.add_decl("extern long count_invoke_by_inline;")
		end

		if self.modelbuilder.toolcontext.opt_isset_checks_metrics.value then
			v.add_decl("long count_attr_reads = 0;")
			v.add_decl("long count_isset_checks = 0;")
			v.compiler.header.add_decl("extern long count_attr_reads;")
			v.compiler.header.add_decl("extern long count_isset_checks;")
		end

		v.add_decl("static void show_backtrace(void) \{")
		if platform.supports_libunwind then
			v.add_decl("#ifndef NO_STACKTRACE")
			v.add_decl("char* opt = getenv(\"NIT_NO_STACK\");")
			v.add_decl("unw_cursor_t cursor;")
			v.add_decl("if(opt==NULL)\{")
			v.add_decl("unw_context_t uc;")
			v.add_decl("unw_word_t ip;")
			v.add_decl("char* procname = malloc(sizeof(char) * 100);")
			v.add_decl("unw_getcontext(&uc);")
			v.add_decl("unw_init_local(&cursor, &uc);")
			v.add_decl("PRINT_ERROR(\"-------------------------------------------------\\n\");")
			v.add_decl("PRINT_ERROR(\"--   Stack Trace   ------------------------------\\n\");")
			v.add_decl("PRINT_ERROR(\"-------------------------------------------------\\n\");")
			v.add_decl("while (unw_step(&cursor) > 0) \{")
			v.add_decl("	unw_get_proc_name(&cursor, procname, 100, &ip);")
			v.add_decl("	const char* recv = get_nit_name(procname, strlen(procname));")
			v.add_decl("	if (recv != NULL)\{")
			v.add_decl("		PRINT_ERROR(\"` %s\\n\", recv);")
			v.add_decl("	\}else\{")
			v.add_decl("		PRINT_ERROR(\"` %s\\n\", procname);")
			v.add_decl("	\}")
			v.add_decl("\}")
			v.add_decl("PRINT_ERROR(\"-------------------------------------------------\\n\");")
			v.add_decl("free(procname);")
			v.add_decl("\}")
			v.add_decl("#endif /* NO_STACKTRACE */")
		end
		v.add_decl("\}")

		v.add_decl("void sig_handler(int signo)\{")
		v.add_decl "#ifdef _WIN32"
		v.add_decl "PRINT_ERROR(\"Caught signal : %s\\n\", signo);"
		v.add_decl "#else"
		v.add_decl("PRINT_ERROR(\"Caught signal : %s\\n\", strsignal(signo));")
		v.add_decl "#endif"
		v.add_decl("show_backtrace();")
		# rethrows
		v.add_decl("signal(signo, SIG_DFL);")
		v.add_decl "#ifndef _WIN32"
		v.add_decl("kill(getpid(), signo);")
		v.add_decl "#endif"
		v.add_decl("\}")

		v.add_decl("void fatal_exit(int status) \{")
		v.add_decl("show_backtrace();")
		v.add_decl("exit(status);")
		v.add_decl("\}")

		compile_before_main(v)

		if no_main then
			v.add_decl("int nit_main(int argc, char** argv) \{")
		else
			v.add_decl("int main(int argc, char** argv) \{")
		end

		compile_begin_main(v)

		v.add "#if !defined(__ANDROID__) && !defined(TARGET_OS_IPHONE)"
		v.add("signal(SIGABRT, sig_handler);")
		v.add("signal(SIGFPE, sig_handler);")
		v.add("signal(SIGILL, sig_handler);")
		v.add("signal(SIGINT, sig_handler);")
		v.add("signal(SIGTERM, sig_handler);")
		v.add("signal(SIGSEGV, sig_handler);")
		v.add "#endif"
		v.add "#ifndef _WIN32"
		v.add("signal(SIGPIPE, SIG_IGN);")
		v.add "#endif"

		v.add("glob_argc = argc; glob_argv = argv;")
		v.add("initialize_gc_option();")

		v.add "initialize_nitni_global_refs();"

		var main_type = mainmodule.sys_type
		if main_type != null then
			var mainmodule = v.compiler.mainmodule
			var glob_sys = v.init_instance(main_type)
			v.add("glob_sys = {glob_sys};")
			var main_init = mainmodule.try_get_primitive_method("init", main_type.mclass)
			if main_init != null then
				v.send(main_init, [glob_sys])
			end
			var main_method = mainmodule.try_get_primitive_method("run", main_type.mclass) or else
				mainmodule.try_get_primitive_method("main", main_type.mclass)
			if main_method != null then
				v.send(main_method, [glob_sys])
			end
		end

		if self.modelbuilder.toolcontext.opt_typing_test_metrics.value then
			v.add_decl("long count_type_test_resolved_total = 0;")
			v.add_decl("long count_type_test_unresolved_total = 0;")
			v.add_decl("long count_type_test_skipped_total = 0;")
			v.add_decl("long count_type_test_total_total = 0;")
			for tag in count_type_test_tags do
				v.add_decl("long count_type_test_total_{tag};")
				v.add("count_type_test_total_{tag} = count_type_test_resolved_{tag} + count_type_test_unresolved_{tag} + count_type_test_skipped_{tag};")
				v.add("count_type_test_resolved_total += count_type_test_resolved_{tag};")
				v.add("count_type_test_unresolved_total += count_type_test_unresolved_{tag};")
				v.add("count_type_test_skipped_total += count_type_test_skipped_{tag};")
				v.add("count_type_test_total_total += count_type_test_total_{tag};")
			end
			v.add("printf(\"# dynamic count_type_test: total %l\\n\");")
			v.add("printf(\"\\tresolved\\tunresolved\\tskipped\\ttotal\\n\");")
			var tags = count_type_test_tags.to_a
			tags.add("total")
			for tag in tags do
				v.add("printf(\"{tag}\");")
				v.add("printf(\"\\t%ld (%.2f%%)\", count_type_test_resolved_{tag}, 100.0*count_type_test_resolved_{tag}/count_type_test_total_total);")
				v.add("printf(\"\\t%ld (%.2f%%)\", count_type_test_unresolved_{tag}, 100.0*count_type_test_unresolved_{tag}/count_type_test_total_total);")
				v.add("printf(\"\\t%ld (%.2f%%)\", count_type_test_skipped_{tag}, 100.0*count_type_test_skipped_{tag}/count_type_test_total_total);")
				v.add("printf(\"\\t%ld (%.2f%%)\\n\", count_type_test_total_{tag}, 100.0*count_type_test_total_{tag}/count_type_test_total_total);")
			end
		end

		if self.modelbuilder.toolcontext.opt_invocation_metrics.value then
			v.add_decl("long count_invoke_total;")
			v.add("count_invoke_total = count_invoke_by_tables + count_invoke_by_direct + count_invoke_by_inline;")
			v.add("printf(\"# dynamic count_invocation: total %ld\\n\", count_invoke_total);")
			v.add("printf(\"by table: %ld (%.2f%%)\\n\", count_invoke_by_tables, 100.0*count_invoke_by_tables/count_invoke_total);")
			v.add("printf(\"direct:   %ld (%.2f%%)\\n\", count_invoke_by_direct, 100.0*count_invoke_by_direct/count_invoke_total);")
			v.add("printf(\"inlined:  %ld (%.2f%%)\\n\", count_invoke_by_inline, 100.0*count_invoke_by_inline/count_invoke_total);")
		end

		if self.modelbuilder.toolcontext.opt_isset_checks_metrics.value then
			v.add("printf(\"# dynamic attribute reads: %ld\\n\", count_attr_reads);")
			v.add("printf(\"# dynamic isset checks: %ld\\n\", count_isset_checks);")
		end

		v.add("return 0;")
		v.add("\}")

		for m in mainmodule.in_importation.greaters do
			var f = "FILE_"+m.c_name
			v.add "const char {f}[] = \"{m.location.file.filename.escape_to_c}\";"
			provide_declaration(f, "extern const char {f}[];")
		end
	end

	# Copile all C functions related to the [incr|decr]_ref features of the FFI
	fun compile_nitni_global_ref_functions
	do
		var v = self.new_visitor
		v.add """
struct nitni_global_ref_list_t *nitni_global_ref_list;
void initialize_nitni_global_refs() {
	nitni_global_ref_list = (struct nitni_global_ref_list_t*)nit_alloc(sizeof(struct nitni_global_ref_list_t));
	nitni_global_ref_list->head = NULL;
	nitni_global_ref_list->tail = NULL;
}

void nitni_global_ref_add( struct nitni_ref *ref ) {
	if ( nitni_global_ref_list->head == NULL ) {
		nitni_global_ref_list->head = ref;
		ref->prev = NULL;
	} else {
		nitni_global_ref_list->tail->next = ref;
		ref->prev = nitni_global_ref_list->tail;
	}
	nitni_global_ref_list->tail = ref;

	ref->next = NULL;
}

void nitni_global_ref_remove( struct nitni_ref *ref ) {
	if ( ref->prev == NULL ) {
		nitni_global_ref_list->head = ref->next;
	} else {
		ref->prev->next = ref->next;
	}

	if ( ref->next == NULL ) {
		nitni_global_ref_list->tail = ref->prev;
	} else {
		ref->next->prev = ref->prev;
	}
}

extern void nitni_global_ref_incr( struct nitni_ref *ref ) {
	if ( ref->count == 0 ) /* not registered */
	{
		/* add to list */
		nitni_global_ref_add( ref );
	}

	ref->count ++;
}

extern void nitni_global_ref_decr( struct nitni_ref *ref ) {
	if ( ref->count == 1 ) /* was last reference */
	{
		/* remove from list */
		nitni_global_ref_remove( ref );
	}

	ref->count --;
}
"""
	end

	# List of additional files required to compile (FFI)
	var extern_bodies = new Array[ExternFile]

	# List of source files to copy over to the compile dir
	var files_to_copy = new Array[String]

	# This is used to avoid adding an extern file more than once
	private var seen_extern = new ArraySet[String]

	# Generate code that initialize the attributes on a new instance
	fun generate_init_attr(v: VISITOR, recv: RuntimeVariable, mtype: MClassType)
	do
		var cds = mtype.collect_mclassdefs(self.mainmodule).to_a
		self.mainmodule.linearize_mclassdefs(cds)
		for cd in cds do
			for npropdef in modelbuilder.collect_attr_propdef(cd) do
				npropdef.init_expr(v, recv)
			end
		end
	end

	# Generate code that check if an attribute is correctly initialized
	fun generate_check_attr(v: VISITOR, recv: RuntimeVariable, mtype: MClassType)
	do
		var cds = mtype.collect_mclassdefs(self.mainmodule).to_a
		self.mainmodule.linearize_mclassdefs(cds)
		for cd in cds do
			for npropdef in modelbuilder.collect_attr_propdef(cd) do
				npropdef.check_expr(v, recv)
			end
		end
	end

	# stats

	var count_type_test_tags: Array[String] = ["isa", "as", "auto", "covariance", "erasure"]
	var count_type_test_resolved: HashMap[String, Int] = init_count_type_test_tags
	var count_type_test_unresolved: HashMap[String, Int] = init_count_type_test_tags
	var count_type_test_skipped: HashMap[String, Int] = init_count_type_test_tags

	protected fun init_count_type_test_tags: HashMap[String, Int]
	do
		var res = new HashMap[String, Int]
		for tag in count_type_test_tags do
			res[tag] = 0
		end
		return res
	end

	# Display stats about compilation process
	#
	# Metrics used:
	#
	# * type tests against resolved types (`x isa Collection[Animal]`)
	# * type tests against unresolved types (`x isa Collection[E]`)
	# * type tests skipped
	# * type tests total
	fun display_stats
	do
		if self.modelbuilder.toolcontext.opt_typing_test_metrics.value then
			print "# static count_type_test"
			print "\tresolved:\tunresolved\tskipped\ttotal"
			var count_type_test_total = init_count_type_test_tags
			count_type_test_resolved["total"] = 0
			count_type_test_unresolved["total"] = 0
			count_type_test_skipped["total"] = 0
			count_type_test_total["total"] = 0
			for tag in count_type_test_tags do
				count_type_test_total[tag] = count_type_test_resolved[tag] + count_type_test_unresolved[tag] + count_type_test_skipped[tag]
				count_type_test_resolved["total"] += count_type_test_resolved[tag]
				count_type_test_unresolved["total"] += count_type_test_unresolved[tag]
				count_type_test_skipped["total"] += count_type_test_skipped[tag]
				count_type_test_total["total"] += count_type_test_total[tag]
			end
			var count_type_test = count_type_test_total["total"]
			var tags = count_type_test_tags.to_a
			tags.add("total")
			for tag in tags do
				printn tag
				printn "\t{count_type_test_resolved[tag]} ({div(count_type_test_resolved[tag],count_type_test)}%)"
				printn "\t{count_type_test_unresolved[tag]} ({div(count_type_test_unresolved[tag],count_type_test)}%)"
				printn "\t{count_type_test_skipped[tag]} ({div(count_type_test_skipped[tag],count_type_test)}%)"
				printn "\t{count_type_test_total[tag]} ({div(count_type_test_total[tag],count_type_test)}%)"
				print ""
			end
		end
	end

	fun finalize_ffi_for_module(mmodule: MModule) do mmodule.finalize_ffi(self)
end
src/compiler/abstract_compiler.nit:601,1--1275,3