ni: adds system to manage native local references to Nit objects
authorAlexis Laferrière <alexis.laf@xymus.net>
Fri, 30 Mar 2012 19:55:36 +0000 (15:55 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Fri, 30 Mar 2012 19:55:36 +0000 (15:55 -0400)
This ensures that references to Nit objects from C within an extern
method are preserved when the GC is invoqued. Theses references
are registered automatically when any callback to the Nit system is
made. They are registered in a stack, local to the currently executing
extern method (ie the one on the top of the execution stack). All
registered references are cleared on the function return.

Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

clib/gc.c
clib/nit_common.h
clib/nit_main.c
src/compiling/compiling_icode.nit
src/native_interface/frontier.nit
src/native_interface/ni_metamodel.nit

index eb9df80..2cb8675 100644 (file)
--- a/clib/gc.c
+++ b/clib/gc.c
@@ -146,6 +146,7 @@ static void GC_collect(void) {
        struct stack_frame_t *frame = stack_frame_head;
        GC_static_object *staticObject = staticObjects.top;
        val_t object;
+       struct nitni_ref_array_link *local_ref_array_link; /* for native interface */
 
        gc_allocation_pointer = gc_heap_pointer;
        gc_scavenging_pointer = gc_heap_pointer;
@@ -167,6 +168,20 @@ static void GC_collect(void) {
                                frame->REG[j] = GC_evacuation((obj_t)object);
                        }
                }
+
+               /* Process C references to Nit objects */
+               local_ref_array_link = frame->nitni_local_ref_head;
+               while ( local_ref_array_link != NULL )
+               {
+                       for (j = 0; j < local_ref_array_link->count; j++) {
+                               object = local_ref_array_link->reg[j]->val;
+                               if (!ISNULL(object) && ISOBJ(object)) {
+                                       local_ref_array_link->reg[j]->val = GC_evacuation((obj_t)object);
+                               }
+                       }
+                       local_ref_array_link = local_ref_array_link->next;
+               }
+
                if (frame == frame->prev) break;
                frame = frame->prev;
        }
index 7e0fce8..b5d8252 100644 (file)
@@ -123,6 +123,29 @@ extern val_t G_sys;
 extern int glob_argc;
 extern char ** glob_argv;
 
+/* Native reference to Nit objects */
+/* This structure is used to represent every Nit type in extern methods and custom C code. */
+struct nitni_ref {
+       val_t val; /* reference to the real Nit object, is kept up-to-date by GC */
+};
+
+/* This structure is used by extern methods to keep track of local references to Nit objects */
+/* These references make sure an object is not collected by the GC while
+ * this extern methods is on the call stack. */
+/* This takes the form of an array link, each link of size 8 to avoid multiple mallocs. */
+#define NITNI_REF_ARRAY_LINK_SIZE 8
+struct nitni_ref_array_link {
+       struct nitni_ref *reg[ NITNI_REF_ARRAY_LINK_SIZE ];
+       int count; /* nubmer of elements in this link */
+       struct nitni_ref_array_link *next; /* next link in the list */
+};
+
+/* Register reference to Nit object with the latest extern method called. */
+extern void nitni_local_ref_add( struct nitni_ref *ref );
+
+/* Clean all references associated to the current (but returning) extern method. */
+extern void nitni_local_ref_clean( void );
+
 /* Stack frames.
  * Are used to:
  * - store local variables (REGS) of functions
@@ -137,8 +160,9 @@ struct stack_frame_t {
        struct stack_frame_t *closure_ctx; /* closure context (for functions that have closure parameters) */
        fun_t *closure_funs; /* closure functions (for functions that have closure parameters) */
        int has_broke; /* has an escape occured? 0 if false, label_idx (>0) if true */
+       struct nitni_ref_array_link *nitni_local_ref_head; /* points to head of array link contaning local variables used via nitni */
        int REG_size; /* number of local variables */
-       val_t REG[1]; /* local variables (flexible array) */
+       val_t REG[1]; /* local variables (flexible array, this must be the last variable is extended in fra struct */
 };
 extern struct stack_frame_t *stack_frame_head;
 
index 630847f..6f7daba 100644 (file)
@@ -159,3 +159,56 @@ void nit_abort(const char* s, const char* msg, const char* loc, int line) {
        fprintf(stderr, ")\n");
        nit_exit(1);
 }
+
+/* Register reference to Nit object with the latest extern method called. */
+void nitni_local_ref_add( struct nitni_ref *ref ) {
+       struct nitni_ref_array_link **link_p;
+       struct nitni_ref_array_link * link = NULL;
+
+       /* find usable link or link to create */
+       link_p = &( stack_frame_head->nitni_local_ref_head );
+       while ( (*link_p) != NULL &&
+                       (*link_p)->count >= NITNI_REF_ARRAY_LINK_SIZE ) {
+               link_p = &((*link_p)->next);
+       }
+
+       /* create link if needed */
+       if ( *link_p == NULL ) {
+               link = malloc( sizeof(struct nitni_ref_array_link) );
+               link->count = 0;
+               link->next = NULL;
+
+               (*link_p) = link;
+       } else {
+               link = *link_p;
+       }
+
+       /* add to link */
+       link->reg[ link->count ] = ref;
+       link->count ++;
+}
+
+/* Clean all references associated to the current (but returning) extern method. */
+void nitni_local_ref_clean( void ) {
+       struct nitni_ref_array_link * link,
+               * last_link;
+       int i;
+
+       link = stack_frame_head->nitni_local_ref_head;
+       while ( link != NULL )
+       {
+               for ( i = 0; i < link->count; i ++ ) {
+                       free( link->reg[ i ] );
+               }
+
+               last_link = link;
+               if ( link->count == NITNI_REF_ARRAY_LINK_SIZE )
+                       link = link->next;
+               else
+                       link = NULL;
+
+               free(last_link);
+       }
+
+       stack_frame_head->nitni_local_ref_head = NULL;
+}
index 169d411..0101447 100644 (file)
@@ -295,6 +295,7 @@ redef class IRoutine
                v.add_instr("fra.me.meth = LOCATE_{v.basecname};")
                v.add_instr("fra.me.has_broke = 0;")
                v.add_instr("fra.me.REG_size = {std_slots_nb};")
+               v.add_instr("fra.me.nitni_local_ref_head = NULL;")
 
                # Declare/initialize local variables
                for i in [0..std_slots_nb[ do
index 232dbfb..b972e18 100644 (file)
@@ -150,7 +150,7 @@ redef class MMSrcMethod
 
                var s = new Buffer
                if return_type != null then
-                       fc.decls.add( "{return_type.friendly_extern_name} return___nitni;\n" )
+                       return_type.compile_new_local_ref( "return___nitni", fc, true )
                        fc.decls.add( "val_t return___nit;\n" )
                        s.append( "return___nit = " )
                end
@@ -189,14 +189,14 @@ redef class MMSrcMethod
 
                if not is_init then
                        var name_for_impl = "recv___nitni"
-                       fc.decls.add( "{signature.recv.friendly_extern_name} {name_for_impl};\n" )
+                       signature.recv.compile_new_local_ref( name_for_impl, fc, true )
                        fc.exprs.add( "{signature.recv.assign_to_friendly( name_for_impl, "recv" )};\n" )
                        params.add( name_for_impl )
                end
 
                for p in signature.params do
                        var name_for_impl = "{p.name}___nitni"
-                       fc.decls.add( "{p.mmtype.friendly_extern_name} {name_for_impl};\n" )
+                       p.mmtype.compile_new_local_ref( name_for_impl, fc, true )
                        fc.exprs.add( "{p.mmtype.assign_to_friendly( name_for_impl, p.name.to_s )};\n" )
                        params.add( name_for_impl )
                end
@@ -212,7 +212,8 @@ redef class MMSrcMethod
 
                var s = new Buffer
                if return_type != null then
-                       fc.decls.add( "{return_type.friendly_extern_name} return___nitni;\n" )
+                       # prepare to receive return but do not stack it here
+                       return_type.compile_new_local_ref( "return___nitni", fc, false )
                        fc.decls.add( "val_t return___nit;\n" )
                        s.append( "return___nitni = " )
                end
@@ -221,9 +222,14 @@ redef class MMSrcMethod
 
                fc.exprs.add( s.to_s )
 
-               # return
                if return_type != null then
                        fc.exprs.add( "{return_type.assign_from_friendly( "return___nit", "return___nitni" )};\n" )
+               end
+
+               fc.exprs.add( "nitni_local_ref_clean(  );\n" )
+
+               # return
+               if return_type != null then
                        fc.exprs.add( "return return___nit;\n" )
                end
 
@@ -366,7 +372,7 @@ redef class MMImportedCast
                var temp_name = "temp"
 
                fc.decls.add( "val_t {temp_name};\n" )
-               fc.decls.add( "{to.friendly_extern_name} {out_name};\n" )
+               to.compile_new_local_ref( out_name, fc, true )
 
                fc.exprs.add( "{from.assign_from_friendly(temp_name, in_name)};\n" )
 
@@ -443,10 +449,10 @@ redef class MMType
        # Is to be nested within another function.
        fun compile_check_isa( fc : FunctionCompiler, name : String )
        do
-               fc.exprs.add( "if ( ! {compile_condition_isa( name )} )\{" )
+               fc.exprs.add( "if ( ! {compile_condition_isa( name )} )\{\n" )
                fc.exprs.add( "\tfprintf( stderr, \"Casting to {self} failed because value is not a {self}.\" );\n" )
                fc.exprs.add( "\tabort();\n" )
-               fc.exprs.add( "\}" )
+               fc.exprs.add( "\}\n" )
        end
 
        # Compiles an expression to verify if an object is of the given type.
@@ -468,28 +474,35 @@ redef class MMType
                        # defines struct
                        v.header_top.add( "#ifndef {guard}\n" )
                        v.header_top.add( "#define {guard}\n" )
-                       v.header_top.add( "typedef struct s_{name}\{\n" )
-                       v.header_top.add( "\tval_t v;\n" )
-                       v.header_top.add( "\} {name};\n" )
-                       v.header_top.add( "#endif\n\n" )
+                       v.header_top.add( "struct s_{name}\{\n" )
+                       v.header_top.add( "\t\tstruct nitni_ref ref; /* real ref struct, must be first */\n" )
+                       v.header_top.add( "\};\n" )
+                       v.header_top.add( "typedef struct s_{name} *{name};\n" )
 
                        # add null version, as a struct
                        if is_nullable then
                                var null_getter = "null_{as_notnull.mangled_name}"
-                               var null_getter_local = "{mmmodule.to_s}_{null_getter}"
-
-                               v.header.add( "{name} {null_getter_local}();\n" )
+                               var fc = new FunctionCompiler( "{name} {null_getter}()" )
+                               v.header_top.add( "{name} {null_getter}();\n" )
+                               compile_new_local_ref( "n", fc, true )
+                               fc.exprs.add( "return n;\n" )
+                               v.body.append( fc.to_writer )
+                       end
 
-                               v.header.add( "#ifndef {null_getter}\n" )
-                               v.header.add( "#define {null_getter} {null_getter_local}\n" )
-                               v.header.add( "#endif\n\n" )
+                       v.header_top.add( "#endif\n" )
+               end
+       end
 
-                               v.body.add( "{name} {null_getter_local}()\n" )
-                               v.body.add( "\{\n" )
-                               v.body.add( "\t{name} n;\n" )
-                               v.body.add( "\tn.v = NIT_NULL;\n" )
-                               v.body.add( "\treturn n;\n" )
-                               v.body.add( "\}\n\n" )
+       fun compile_new_local_ref( var_name : String, fc : FunctionCompiler, stack_it : Bool )
+       do
+               var type_name = friendly_extern_name
+
+               fc.decls.add( "{type_name} {var_name};\n" )
+               if uses_nitni_ref then
+                       fc.exprs.add( "{var_name} = malloc( sizeof( struct s_{type_name} ) );\n" )
+                       fc.exprs.add( "{var_name}->ref.val = NIT_NULL;\n" )
+                       if stack_it then
+                               fc.exprs.add( "nitni_local_ref_add( (struct nitni_ref *){var_name} );\n" )
                        end
                end
        end
@@ -543,7 +556,7 @@ redef class MMExplicitImport
 
                var s = new Buffer
                if return_type != null then
-                       fc.decls.add( "{return_type.friendly_extern_name} result___nitni;\n" )
+                       return_type.compile_new_local_ref( "result___nitni", fc, true )
                        fc.decls.add( "val_t result___nit;\n" )
                        s.append( "result___nit = " )
                end
index f820e54..3cb2ec0 100644 (file)
@@ -56,7 +56,7 @@ redef class MMType
                        not is_nullable then
                        return boxtype( name )
                else
-                       var getter = "{name}.v"
+                       var getter = "{name}->ref.val"
 
                        return boxtype( getter )
                end
@@ -70,7 +70,7 @@ redef class MMType
                        not is_nullable then
                        return "{native_name} = {unboxtype( nit_name )}"
                else
-                       return "{native_name}.v = {unboxtype( nit_name )}"
+                       return "{native_name}->ref.val = {unboxtype( nit_name )}"
                end
        end
 
@@ -82,9 +82,11 @@ redef class MMType
                        not is_nullable then # int, float, point/void* ...
                        return "{nit_name} = {boxtype(native_name)}"
                else
-                       return "{nit_name} = {native_name}.v"
+                       return "{nit_name} = {native_name}->ref.val"
                end
        end
+
+       fun uses_nitni_ref : Bool do return local_class.primitive_info == null or is_nullable
 end
 
 redef class MMMethod