From d6222a340cd1715736f747ba8128043049705195 Mon Sep 17 00:00:00 2001 From: Bernhard Urban-Forster Date: Tue, 14 Jan 2020 21:59:48 +0100 Subject: [PATCH] [interp] initial infrastructure for whitebox testing of optimization passes (mono/mono#18268) Commit migrated from https://github.com/mono/mono/commit/05f2651a12ba5eae1de8b488c4026145622b265a --- src/mono/mono/mini/.gitignore | 1 + src/mono/mono/mini/Makefile.am.in | 17 ++ src/mono/mono/mini/interp/transform.c | 205 ++++----------------- src/mono/mono/mini/interp/transform.h | 189 ++++++++++++++++++++ src/mono/mono/mini/interp/whitebox-snippets.il | 36 ++++ src/mono/mono/mini/interp/whitebox.c | 236 +++++++++++++++++++++++++ 6 files changed, 513 insertions(+), 171 deletions(-) create mode 100644 src/mono/mono/mini/interp/transform.h create mode 100644 src/mono/mono/mini/interp/whitebox-snippets.il create mode 100644 src/mono/mono/mini/interp/whitebox.c diff --git a/src/mono/mono/mini/.gitignore b/src/mono/mono/mini/.gitignore index af434f4..9a71425 100644 --- a/src/mono/mono/mini/.gitignore +++ b/src/mono/mono/mini/.gitignore @@ -34,3 +34,4 @@ /buildver-sgen.h /buildver-boehm.h /regressiontests.out +/test-mono-interp-whitebox diff --git a/src/mono/mono/mini/Makefile.am.in b/src/mono/mono/mini/Makefile.am.in index 15568a9..c56d31f 100755 --- a/src/mono/mono/mini/Makefile.am.in +++ b/src/mono/mono/mini/Makefile.am.in @@ -779,6 +779,20 @@ libmonosgen_2_0_la_CFLAGS = $(mono_sgen_CFLAGS) @CXX_ADD_CFLAGS@ libmonosgen_2_0_la_LIBADD = libmini.la $(interp_libs_with_mini) $(dbg_libs_with_mini) $(sgen_libs) $(LIBMONO_DTRACE_OBJECT) $(LLVMMONOF) libmonosgen_2_0_la_LDFLAGS = $(libmonoldflags) $(monobin_platform_ldflags) +noinst_LIBRARIES += libmaintest.a + +libmaintest_a_SOURCES = interp/whitebox.c +libmaintest_a_CFLAGS = $(AM_CFLAGS) @CXX_ADD_CFLAGS@ + +WHITEBOX_ASSEMBLY=interp/whitebox-snippets.exe +test_mono_interp_whitebox_SOURCES = +test_mono_interp_whitebox_CFLAGS = $(AM_CFLAGS) @CXX_REMOVE_CFLAGS@ +test_mono_interp_whitebox_LDADD = interp/libmaintest_a-whitebox.$(OBJEXT) libmini.la $(interp_libs_with_mini) $(dbg_libs_with_mini) $(sgen_libs) +test_mono_interp_whitebox_LDFLAGS = $(libmonoldflags) $(monobin_platform_ldflags) +test_mono_interp_whitebox_DEPENDENCIES = $(WHITEBOX_ASSEMBLY) + +check_PROGRAMS = test-mono-interp-whitebox + endif # !ENABLE_MSVC_ONLY libmonoincludedir = $(includedir)/mono-$(API_VER)/mono/jit @@ -900,6 +914,9 @@ rcheck: mono $(regtests) richeck: mono $(regtests) $(INTERP_RUNTIME) --regression $(regtests) +interp-whitebox: test-mono-interp-whitebox + MONO_PATH=$(CLASS) ./$< $(WHITEBOX_ASSEMBLY) + mixedcheck: mono mixed.exe $(MINI_RUNTIME) --interp=jit=JitClass mixed.exe diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index ede4709..4acba8d 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -31,181 +30,12 @@ #include "mintops.h" #include "interp-internals.h" #include "interp.h" - -#define INTERP_INST_FLAG_SEQ_POINT_NONEMPTY_STACK 1 -#define INTERP_INST_FLAG_SEQ_POINT_METHOD_ENTRY 2 -#define INTERP_INST_FLAG_SEQ_POINT_METHOD_EXIT 4 -#define INTERP_INST_FLAG_SEQ_POINT_NESTED_CALL 8 -#define INTERP_INST_FLAG_RECORD_CALL_PATCH 16 - -#define INTERP_LOCAL_FLAG_INDIRECT 1 -#define INTERP_LOCAL_FLAG_DEAD 2 +#include "transform.h" MonoInterpStats mono_interp_stats; #define DEBUG 0 -typedef struct InterpInst InterpInst; - -typedef struct -{ - MonoClass *klass; - unsigned char type; - unsigned char flags; -} StackInfo; - -#define STACK_VALUE_NONE 0 -#define STACK_VALUE_LOCAL 1 -#define STACK_VALUE_ARG 2 -#define STACK_VALUE_I4 3 -#define STACK_VALUE_I8 4 - -// StackValue contains data to construct an InterpInst that is equivalent with the contents -// of the stack slot / local / argument. -typedef struct { - // Indicates the type of the stored information. It can be a local, argument or a constant - int type; - // Holds the local index or the actual constant value - union { - int local; - int arg; - gint32 i; - gint64 l; - }; -} StackValue; - -typedef struct -{ - // This indicates what is currently stored in this stack slot. This can be a constant - // or the copy of a local / argument. - StackValue val; - // The instruction that pushed this stack slot. If ins is null, we can't remove the usage - // of the stack slot, because we can't clear the instruction that set it. - InterpInst *ins; -} StackContentInfo; - -struct InterpInst { - guint16 opcode; - InterpInst *next, *prev; - // If this is -1, this instruction is not logically associated with an IL offset, it is - // part of the IL instruction associated with the previous interp instruction. - int il_offset; - guint32 flags; - guint16 data [MONO_ZERO_LEN_ARRAY]; -}; - -typedef struct { - guint8 *ip; - GSList *preds; - GSList *seq_points; - SeqPoint *last_seq_point; - - // This will hold a list of last sequence points of incoming basic blocks - SeqPoint **pred_seq_points; - guint num_pred_seq_points; -} InterpBasicBlock; - -typedef enum { - RELOC_SHORT_BRANCH, - RELOC_LONG_BRANCH, - RELOC_SWITCH -} RelocType; - -typedef struct { - RelocType type; - /* In the interpreter IR */ - int offset; - /* In the IL code */ - int target; -} Reloc; - -typedef struct { - MonoType *type; - int mt; - int flags; - int offset; -} InterpLocal; - -typedef struct -{ - MonoMethod *method; - MonoMethod *inlined_method; - MonoMethodHeader *header; - InterpMethod *rtm; - const unsigned char *il_code; - const unsigned char *ip; - const unsigned char *in_start; - InterpInst *last_ins, *first_ins; - int code_size; - int *in_offsets; - int current_il_offset; - StackInfo **stack_state; - int *stack_height; - int *vt_stack_size; - unsigned char *is_bb_start; - unsigned short *new_code; - unsigned short *new_code_end; - unsigned int max_code_size; - StackInfo *stack; - StackInfo *sp; - unsigned int max_stack_height; - unsigned int stack_capacity; - unsigned int vt_sp; - unsigned int max_vt_sp; - unsigned int total_locals_size; - InterpLocal *locals; - unsigned int locals_size; - unsigned int locals_capacity; - int n_data_items; - int max_data_items; - void **data_items; - GHashTable *data_hash; -#ifdef ENABLE_EXPERIMENT_TIERED - GHashTable *patchsite_hash; -#endif - int *clause_indexes; - gboolean gen_sdb_seq_points; - GPtrArray *seq_points; - InterpBasicBlock **offset_to_bb; - InterpBasicBlock *entry_bb; - MonoMemPool *mempool; - GList *basic_blocks; - GPtrArray *relocs; - gboolean verbose_level; - GArray *line_numbers; -} TransformData; - -#define STACK_TYPE_I4 0 -#define STACK_TYPE_I8 1 -#define STACK_TYPE_R4 2 -#define STACK_TYPE_R8 3 -#define STACK_TYPE_O 4 -#define STACK_TYPE_VT 5 -#define STACK_TYPE_MP 6 -#define STACK_TYPE_F 7 - -static const char *stack_type_string [] = { "I4", "I8", "R4", "R8", "O ", "VT", "MP", "F " }; - -#if SIZEOF_VOID_P == 8 -#define STACK_TYPE_I STACK_TYPE_I8 -#else -#define STACK_TYPE_I STACK_TYPE_I4 -#endif - -static int stack_type [] = { - STACK_TYPE_I4, /*I1*/ - STACK_TYPE_I4, /*U1*/ - STACK_TYPE_I4, /*I2*/ - STACK_TYPE_I4, /*U2*/ - STACK_TYPE_I4, /*I4*/ - STACK_TYPE_I8, /*I8*/ - STACK_TYPE_R4, /*R4*/ - STACK_TYPE_R8, /*R8*/ - STACK_TYPE_O, /*O*/ - STACK_TYPE_MP, /*P*/ - STACK_TYPE_VT -}; - #if SIZEOF_VOID_P == 8 #define MINT_NEG_P MINT_NEG_I8 #define MINT_NOT_P MINT_NOT_I8 @@ -1054,6 +884,21 @@ mono_interp_print_code (InterpMethod *imethod) dump_mint_code ((const guint16*)start, (const guint16*)(start + jinfo->code_size)); } +/* For debug use */ +void +mono_interp_print_td_code (TransformData *td) +{ + InterpInst *ins = td->first_ins; + + char *name = mono_method_full_name (td->method, TRUE); + g_print ("IR for \"%s\"\n", name); + g_free (name); + while (ins) { + dump_interp_inst_newline (ins); + ins = ins->next; + } +} + static MonoMethodHeader* interp_method_get_header (MonoMethod* method, MonoError *error) @@ -2987,6 +2832,12 @@ interp_method_compute_offsets (TransformData *td, InterpMethod *imethod, MonoMet td->total_locals_size = offset; } +void +mono_test_interp_method_compute_offsets (TransformData *td, InterpMethod *imethod, MonoMethodSignature *signature, MonoMethodHeader *header) +{ + interp_method_compute_offsets (td, imethod, signature, header); +} + /* Return false is failure to init basic blocks due to being in inline method */ static gboolean init_bb_start (TransformData *td, MonoMethodHeader *header, gboolean inlining) @@ -7328,6 +7179,12 @@ retry: g_free (local_ref_count); } +void +mono_test_interp_cprop (TransformData *td) +{ + interp_cprop (td); +} + static void interp_super_instructions (TransformData *td) { @@ -7583,6 +7440,12 @@ exit: mono_mempool_destroy (td->mempool); } +gboolean +mono_test_interp_generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, MonoGenericContext *generic_context, MonoError *error) +{ + return generate_code (td, method, header, generic_context, error); +} + static mono_mutex_t calc_section; #ifdef ENABLE_EXPERIMENT_TIERED diff --git a/src/mono/mono/mini/interp/transform.h b/src/mono/mono/mini/interp/transform.h new file mode 100644 index 0000000..9f40463 --- /dev/null +++ b/src/mono/mono/mini/interp/transform.h @@ -0,0 +1,189 @@ +#ifndef __MONO_MINI_INTERP_TRANSFORM_H__ +#define __MONO_MINI_INTERP_TRANSFORM_H__ +#include +#include +#include "interp-internals.h" + +#define INTERP_INST_FLAG_SEQ_POINT_NONEMPTY_STACK 1 +#define INTERP_INST_FLAG_SEQ_POINT_METHOD_ENTRY 2 +#define INTERP_INST_FLAG_SEQ_POINT_METHOD_EXIT 4 +#define INTERP_INST_FLAG_SEQ_POINT_NESTED_CALL 8 +#define INTERP_INST_FLAG_RECORD_CALL_PATCH 16 + +#define INTERP_LOCAL_FLAG_INDIRECT 1 +#define INTERP_LOCAL_FLAG_DEAD 2 + +typedef struct InterpInst InterpInst; + +typedef struct +{ + MonoClass *klass; + unsigned char type; + unsigned char flags; +} StackInfo; + +#define STACK_VALUE_NONE 0 +#define STACK_VALUE_LOCAL 1 +#define STACK_VALUE_ARG 2 +#define STACK_VALUE_I4 3 +#define STACK_VALUE_I8 4 + +// StackValue contains data to construct an InterpInst that is equivalent with the contents +// of the stack slot / local / argument. +typedef struct { + // Indicates the type of the stored information. It can be a local, argument or a constant + int type; + // Holds the local index or the actual constant value + union { + int local; + int arg; + gint32 i; + gint64 l; + }; +} StackValue; + +typedef struct +{ + // This indicates what is currently stored in this stack slot. This can be a constant + // or the copy of a local / argument. + StackValue val; + // The instruction that pushed this stack slot. If ins is null, we can't remove the usage + // of the stack slot, because we can't clear the instruction that set it. + InterpInst *ins; +} StackContentInfo; + +struct InterpInst { + guint16 opcode; + InterpInst *next, *prev; + // If this is -1, this instruction is not logically associated with an IL offset, it is + // part of the IL instruction associated with the previous interp instruction. + int il_offset; + guint32 flags; + guint16 data [MONO_ZERO_LEN_ARRAY]; +}; + +typedef struct { + guint8 *ip; + GSList *preds; + GSList *seq_points; + SeqPoint *last_seq_point; + + // This will hold a list of last sequence points of incoming basic blocks + SeqPoint **pred_seq_points; + guint num_pred_seq_points; +} InterpBasicBlock; + +typedef enum { + RELOC_SHORT_BRANCH, + RELOC_LONG_BRANCH, + RELOC_SWITCH +} RelocType; + +typedef struct { + RelocType type; + /* In the interpreter IR */ + int offset; + /* In the IL code */ + int target; +} Reloc; + +typedef struct { + MonoType *type; + int mt; + int flags; + int offset; +} InterpLocal; + +typedef struct +{ + MonoMethod *method; + MonoMethod *inlined_method; + MonoMethodHeader *header; + InterpMethod *rtm; + const unsigned char *il_code; + const unsigned char *ip; + const unsigned char *in_start; + InterpInst *last_ins, *first_ins; + int code_size; + int *in_offsets; + int current_il_offset; + StackInfo **stack_state; + int *stack_height; + int *vt_stack_size; + unsigned char *is_bb_start; + unsigned short *new_code; + unsigned short *new_code_end; + unsigned int max_code_size; + StackInfo *stack; + StackInfo *sp; + unsigned int max_stack_height; + unsigned int stack_capacity; + unsigned int vt_sp; + unsigned int max_vt_sp; + unsigned int total_locals_size; + InterpLocal *locals; + unsigned int locals_size; + unsigned int locals_capacity; + int n_data_items; + int max_data_items; + void **data_items; + GHashTable *data_hash; +#ifdef ENABLE_EXPERIMENT_TIERED + GHashTable *patchsite_hash; +#endif + int *clause_indexes; + gboolean gen_sdb_seq_points; + GPtrArray *seq_points; + InterpBasicBlock **offset_to_bb; + InterpBasicBlock *entry_bb; + MonoMemPool *mempool; + GList *basic_blocks; + GPtrArray *relocs; + gboolean verbose_level; + GArray *line_numbers; +} TransformData; + +#define STACK_TYPE_I4 0 +#define STACK_TYPE_I8 1 +#define STACK_TYPE_R4 2 +#define STACK_TYPE_R8 3 +#define STACK_TYPE_O 4 +#define STACK_TYPE_VT 5 +#define STACK_TYPE_MP 6 +#define STACK_TYPE_F 7 + +static const char *stack_type_string [] = { "I4", "I8", "R4", "R8", "O ", "VT", "MP", "F " }; + +#if SIZEOF_VOID_P == 8 +#define STACK_TYPE_I STACK_TYPE_I8 +#else +#define STACK_TYPE_I STACK_TYPE_I4 +#endif + +static int stack_type [] = { + STACK_TYPE_I4, /*I1*/ + STACK_TYPE_I4, /*U1*/ + STACK_TYPE_I4, /*I2*/ + STACK_TYPE_I4, /*U2*/ + STACK_TYPE_I4, /*I4*/ + STACK_TYPE_I8, /*I8*/ + STACK_TYPE_R4, /*R4*/ + STACK_TYPE_R8, /*R8*/ + STACK_TYPE_O, /*O*/ + STACK_TYPE_MP, /*P*/ + STACK_TYPE_VT +}; + +/* test exports for white box testing */ +void +mono_test_interp_cprop (TransformData *td); +gboolean +mono_test_interp_generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, MonoGenericContext *generic_context, MonoError *error); +void +mono_test_interp_method_compute_offsets (TransformData *td, InterpMethod *imethod, MonoMethodSignature *signature, MonoMethodHeader *header); + +/* debugging aid */ +void +mono_interp_print_td_code (TransformData *td); + +#endif /* __MONO_MINI_INTERP_TRANSFORM_H__ */ diff --git a/src/mono/mono/mini/interp/whitebox-snippets.il b/src/mono/mono/mini/interp/whitebox-snippets.il new file mode 100644 index 0000000..554c28e --- /dev/null +++ b/src/mono/mono/mini/interp/whitebox-snippets.il @@ -0,0 +1,36 @@ +.assembly snippets {} +.assembly extern mscorlib {} + +.class public auto ansi sealed beforefieldinit Snippets { + + .method static public int32 Main(string[] args) il managed { + .entrypoint + ldc.i4.s 0x1337 + ret + } + + .method public hidebysig static int32 no_inline_int32 () cil managed noinlining { + .maxstack 0 + ldc.i4 0x1337 + ret + } + + .method public hidebysig static int32 test_cprop_add_consts () cil managed { + ldc.i4 0x1122 + ldc.i4 0x3344 + add + ret + } + + .method public hidebysig static int32 test_cprop_ldloc_stloc () cil managed { + .locals init ( + int32 i + ) + call int32 class Snippets::no_inline_int32 () + stloc 0 + ldloc 0 + ldloc 0 + add + ret + } +} diff --git a/src/mono/mono/mini/interp/whitebox.c b/src/mono/mono/mini/interp/whitebox.c new file mode 100644 index 0000000..24db2dc --- /dev/null +++ b/src/mono/mono/mini/interp/whitebox.c @@ -0,0 +1,236 @@ +#include +#include +#include "transform.h" +#include "mintops.h" +#include +#include +#include + +/* return value of "0" equals success */ +typedef int (*TestVerifier)(TransformData *td); + +typedef struct +{ + const char *test_name; + /* function pointer to result verifier */ + TestVerifier verify_td; +} TestItem; + +static MonoMemPool *mp = NULL; +static GList *test_list = NULL; +static const char *verbose_method_name = NULL; + +static void +print_td (TransformData *td) +{ + if (!td->verbose_level) + return; + + mono_interp_print_td_code (td); +} + +static int +expect (InterpInst **ins, InterpInst **current, guint16 opcode) +{ + g_assert (ins); + + if (!*ins) + return 1; + + while ((*ins)->opcode == MINT_NOP) + *ins = (*ins)->next; + + if ((*ins)->opcode == opcode) { + if (current) + *current = *ins; + + *ins = (*ins)->next; + return 0; + } + return 2; +} + +static int +verify_cprop_add_consts (TransformData *td) +{ + mono_test_interp_cprop (td); + print_td (td); + + InterpInst *ins = td->first_ins, *current; + if (expect (&ins, ¤t, MINT_LDC_I4)) + return 1; + if (READ32 (¤t->data [0]) != 0x4466) + return 2; + if (expect (&ins, NULL, MINT_RET)) + return 3; + + return 0; +} + +static int +verify_cprop_ldloc_stloc (TransformData *td) +{ + mono_test_interp_cprop (td); + print_td (td); + + InterpInst *ins = td->first_ins; + if (expect (&ins, NULL, MINT_INITLOCALS)) + return 1; + if (expect (&ins, NULL, MINT_CALL)) + return 2; + if (expect (&ins, NULL, MINT_STLOC_NP_I4)) + return 3; + if (expect (&ins, NULL, MINT_LDLOC_I4)) + return 4; + if (expect (&ins, NULL, MINT_ADD_I4)) + return 5; + if (expect (&ins, NULL, MINT_RET)) + return 6; + + return 0; +} + +static void +new_test (const char *name, TestVerifier verifier) +{ + TestItem *ti = g_malloc (sizeof (TestItem)); + ti->test_name = name; + ti->verify_td = verifier; + + test_list = g_list_append_mempool (mp, test_list, ti); +} + +static MonoImage * +load_assembly (const char *path, MonoDomain *root_domain) +{ + MonoAssemblyOpenRequest req; + mono_assembly_request_prepare_open (&req, MONO_ASMCTX_DEFAULT, mono_domain_default_alc (root_domain)); + MonoAssembly *ass = mono_assembly_request_open (path, &req, NULL); + if (!ass) + g_error ("failed to load assembly: %s", path); + return mono_assembly_get_image_internal (ass); +} + +static MonoMethod * +lookup_method_from_image (MonoImage *image, const char *name) +{ + for (int i = 0; i < mono_image_get_table_rows (image, MONO_TABLE_METHOD); i++) { + ERROR_DECL (error); + MonoMethod *method = mono_get_method_checked (image, MONO_TOKEN_METHOD_DEF | (i + 1), NULL, NULL, error); + if (strcmp (method->name, name) == 0) { + mono_class_init_internal (method->klass); + return method; + } + } + g_error ("method \"%s\" does not exist in assembly \"%s\\n", name, image->assembly_name); +} + +static int +determine_verbose_level (TransformData *td) +{ + if (!verbose_method_name) + return 0; + + if (!strcmp ("ALL", verbose_method_name)) + return 4; + + MonoMethod *method = td->method; + const char *name = verbose_method_name; + + if ((strchr (name, '.') > name) || strchr (name, ':')) { + MonoMethodDesc *desc = mono_method_desc_new (name, TRUE); + int match = mono_method_desc_full_match (desc, method); + mono_method_desc_free (desc); + if (match) + return 4; + } else { + if (strcmp (method->name, name) == 0) + return 4; + } + + return 0; +} + +static TransformData * +transform_method (MonoDomain *domain, MonoImage *image, TestItem *ti) +{ + ERROR_DECL (error); + MonoMethod *method = lookup_method_from_image (image, ti->test_name); + MonoMethodHeader *header = mono_method_get_header_checked (method, error);;; + MonoMethodSignature *signature = mono_method_signature_internal (method); + + InterpMethod *rtm = g_new0 (InterpMethod, 1); + rtm->method = method; + rtm->domain = domain; + /* TODO: init more fields of `rtm` */ + + TransformData *td = g_new0 (TransformData, 1); + td->method = method; + td->verbose_level = determine_verbose_level (td); + td->mempool = mp; + td->rtm = rtm; + td->stack_height = (int*)g_malloc(header->code_size * sizeof(int)); + td->clause_indexes = (int*)g_malloc (header->code_size * sizeof (int)); + td->is_bb_start = (guint8*)g_malloc0(header->code_size); + td->data_items = NULL; + td->data_hash = g_hash_table_new (NULL, NULL); + /* TODO: init more fields of `td` */ + + mono_test_interp_method_compute_offsets (td, rtm, signature, header); + + td->stack = (StackInfo*)g_malloc0 ((header->max_stack + 1) * sizeof (td->stack [0])); + td->stack_capacity = header->max_stack + 1; + td->sp = td->stack; + td->max_stack_height = 0; + + mono_test_interp_generate_code (td, method, header, NULL, error); + + mono_metadata_free_mh (header); + return td; +} + +int +main (int argc, char* argv[]) +{ + if (argc < 2) + g_error ("need to pass whitebox assembly"); + + int test_failed = 0, test_success = 0; + char *whitebox_assembly = argv [1]; + mp = mono_mempool_new (); + + /* test list */ + new_test ("test_cprop_add_consts", verify_cprop_add_consts); + new_test ("test_cprop_ldloc_stloc", verify_cprop_ldloc_stloc); + + /* init mono runtime */ + g_set_prgname (argv [0]); + mono_set_rootdir (); + mono_config_parse (NULL); + MonoDomain *root_domain = mini_init ("whitebox", NULL); + mono_gc_set_stack_end (&root_domain); + + verbose_method_name = g_getenv ("MONO_VERBOSE_METHOD"); + + g_print ("interp opt white box testing suite with %s, running %d tests\n", whitebox_assembly, g_list_length (test_list)); + + MonoImage *image = load_assembly (whitebox_assembly, root_domain); + + for (GList *iter = test_list; iter; iter = iter->next) { + TestItem *ti = (TestItem *) iter->data; + TransformData *td = transform_method (root_domain, image, ti); + int result = ti->verify_td (td); + g_print ("test \"%s\": %d\n", ti->test_name, result); + free (td); + + if (!!result) + test_failed++; + else + test_success++; + } + + g_print ("\nSUMMARY: %d / %d passed\n", test_success, test_success + test_failed); + + /* TODO: shut runtime down, release resources, etc. */ + return test_failed; +} -- 2.7.4