parser: `Visitor::visit` does not accepts `null`
[nit.git] / src / native_interface / frontier.nit
index b972e18..3d12896 100644 (file)
@@ -24,6 +24,103 @@ import ni_metamodel
 
 import syntax # FIXME: to remove since it breaks modularity
 
+# Transitive variable through the frontier file
+# Represents a variable going from Nit to C or from C to Nit
+abstract class NiVariable
+       fun ni_from_name : String is abstract
+       fun ni_to_name : String is abstract
+       fun ni_type : MMType is abstract
+
+       # needs to be boxed or unboxed
+       # anything using the GC
+       fun needs_preparation : Bool
+       do
+               return ni_type.local_class.primitive_info == null or
+                       not ni_type.local_class.primitive_info.tagged or
+                       ni_type.is_nullable
+       end
+
+       # prepare variable to callback to Nit
+       fun prepare_for_nit( fc : FunctionCompiler )
+       do
+               if needs_preparation then
+                       fc.decls.add( "val_t {ni_to_name};\n" )
+                       fc.exprs.add( "{ni_type.assign_from_friendly( ni_to_name, ni_from_name )};\n" )
+               end
+       end
+
+       fun prepare_for_c( fc : FunctionCompiler )
+       do
+               if needs_preparation then
+                       ni_type.compile_new_local_ref( ni_to_name, fc, not self isa ReturnVariable ) # TODO
+                       fc.exprs.add( "{ni_type.assign_to_friendly( ni_to_name, ni_from_name )};\n" )
+               end
+       end
+
+       # format of the variable to callback to Nit
+       fun as_arg_to_nit : String
+       do
+               if needs_preparation then
+                       return ni_to_name
+               else if ( ni_type.local_class.primitive_info != null or ni_type.local_class.global.is_extern ) and
+                       not ni_type.is_nullable then # int, float, point/void* ...
+                       return ni_type.boxtype(ni_from_name)
+               else
+                       return "{ni_from_name}->ref.val"
+               end
+       end
+
+       # format of the variable to call C implementation functions
+       fun as_arg_to_c : String
+       do
+               if needs_preparation then
+                       return ni_to_name
+               else
+                       return ni_type.unboxtype( ni_from_name )
+               end
+       end
+end
+
+redef class MMParam
+       super NiVariable
+
+       redef fun ni_from_name do return name.to_s
+       redef fun ni_to_name do return "trans___{name}"
+       redef fun ni_type do return mmtype
+end
+
+class ReceiverVariable
+       super NiVariable
+
+       redef fun ni_from_name do return "recv"
+       redef fun ni_to_name do return "trans_recv"
+
+       redef var ni_type : MMType
+       init ( t : MMType ) do ni_type = t
+end
+
+class ReturnVariable
+       super NiVariable
+
+       redef fun ni_from_name do return "orig_return"
+       redef fun ni_to_name do return "trans_return"
+
+       redef var ni_type : MMType
+       init ( t : MMType ) do ni_type = t
+
+       # used only by friendly callbacks to Nit
+       redef fun prepare_for_c( fc )
+       do
+               fc.decls.add( "val_t {ni_from_name};\n" )
+               ni_type.compile_new_local_ref( ni_to_name, fc, true )
+       end
+       redef fun prepare_for_nit( fc )
+       do
+               ni_type.compile_new_local_ref( ni_from_name, fc, false )
+               fc.decls.add( "val_t {ni_to_name};\n" )
+       end
+end
+
 redef class MMSrcModule
        fun compile_frontier( v : FrontierVisitor )
        do
@@ -45,8 +142,12 @@ redef class MMSrcModule
                        native_header = "{directory.path}/{name}_nit.h"
                end
                if native_header.file_exists then
-                       v.body.add( "#include \"{native_header.path_from_parent}\"\n" )
-                       v.header.add( "#include \"{native_header.path_from_parent}\"\n" )
+                       var path = "..".join_path(native_header).simplify_path
+                       v.body.add( "#include \"{path}\"\n" )
+                       v.header.add( "#include \"{path}\"\n" )
+               end
+               if uses_ffi then
+                       v.header.add( "#include <{cname}._ffi.h>\n" )
                end
 
                for local_class in local_classes do
@@ -59,15 +160,10 @@ redef class MMSrcModule
                                        prop.compile_extern_to_frontier( v )
                                end
                        end
-
-                       ### extern classes
-                       # if class is extern and defined here first
-                       if local_class.global.intro == local_class and
-                          local_class.global.is_extern then
-                               local_class.compile_defaut_extern_type( v )
-                       end
                end
 
+               v.compile_cached
+
                v.header.add( "#endif\n" )
        end
 end
@@ -92,8 +188,8 @@ redef class MMSrcMethod
                for cast in explicit_casts do
                        v.casts.add( cast )
 
-                       v.types.add( cast.from )
-                       v.types.add( cast.to )
+                       v.types.add( cast.from.direct_type )
+                       v.types.add( cast.to.direct_type )
                end
 
                # adds super
@@ -125,47 +221,34 @@ redef class MMSrcMethod
                var fc = new FunctionCompiler( friendly_super_csignature )
 
                # params
-               var params = new Array[String]
+               var params = new Array[NiVariable]
+               params.add( signature.recv_ni_variable )
+               params.add_all( signature.params )
 
-               # receiver
-               var name_for_sub = "recv___nit"
-               fc.decls.add( "val_t {name_for_sub};\n" )
-               fc.exprs.add( "{signature.recv.assign_from_friendly( name_for_sub, "recv" )};\n" )
-               params.add( name_for_sub )
+               # prepare transition
+               for p in params do p.prepare_for_nit( fc )
 
-               # other params
-               for p in signature.params do
-                       name_for_sub = "{p.name}___nit"
-                       fc.decls.add( "val_t {name_for_sub};\n" )
-                       fc.exprs.add( "{p.mmtype.assign_from_friendly( name_for_sub, p.name.to_s )};\n" )
-                       params.add( name_for_sub )
-               end
+               # extract strings
+               var args = new Array[String]
+               for p in params do args.add( p.as_arg_to_nit )
 
                # hook to generated C
-               var return_type : nullable MMType = null
-
-               if signature.return_type != null then
-                       return_type = signature.return_type
-               end
-
+               var rnv = signature.return_ni_variable
                var s = new Buffer
-               if return_type != null then
-                       return_type.compile_new_local_ref( "return___nitni", fc, true )
-                       fc.decls.add( "val_t return___nit;\n" )
-                       s.append( "return___nit = " )
+               if rnv != null then
+                       rnv.prepare_for_c( fc )
+                       s.append( "{rnv.ni_from_name} = " )
                end
 
-               s.append( "{super_meth_call}( recv___nit )" )
-
-               s.append( "( {params.join( ", " )} );\n" )
+               s.append( "{super_meth_call}( {signature.recv_ni_variable.as_arg_to_nit} )" )
+               s.append( "( {args.join( ", " )} );\n" )
 
                fc.exprs.add( s.to_s )
 
-               # verify and return
-               if return_type != null
-               then
-                       fc.exprs.add( "{return_type.assign_to_friendly( "return___nitni", "return___nit" )};\n" )
-                       fc.exprs.add( "return return___nitni;\n" )
+               # return
+               if rnv != null then
+                       fc.exprs.add( "{rnv.ni_type.assign_to_friendly( rnv.ni_to_name, rnv.ni_from_name )};\n" )
+                       fc.exprs.add( "return {rnv.ni_to_name};\n" )
                end
 
                v.body.append( fc.to_writer )
@@ -176,6 +259,14 @@ redef class MMSrcMethod
        # It handles variables conversions and verification
        fun compile_out_to_frontier( v : FrontierVisitor )
        do
+               # a simple out method can be optimized
+               # To qualify as simple this method must:
+               #       - have no explicit imports (including super and casts)
+               #       - return nothing or return a primitive to C
+               var is_simple = explicit_imports.is_empty and not need_super and
+                       explicit_casts.is_empty and (signature.return_type == null or
+                       signature.return_type.local_class.primitive_info != null )
+
                # header
                v.header.add( "\n/* out/indirect function for {full_name} */\n" )
                v.header.add( "{out_csignature};\n" ) # incoming types boxed
@@ -185,52 +276,46 @@ redef class MMSrcMethod
                var fc = new FunctionCompiler( out_csignature )
 
                # params
-               var params = new List[String]
+               var params = new List[NiVariable]
+               if not is_init then params.add( signature.recv_ni_variable )
+               params.add_all( signature.params )
 
-               if not is_init then
-                       var name_for_impl = "recv___nitni"
-                       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
+               var args = new List[String]
 
-               for p in signature.params do
-                       var name_for_impl = "{p.name}___nitni"
-                       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 )
+               for nv in params do
+                       if not is_simple or nv.ni_type.local_class.primitive_info != null then
+                               nv.prepare_for_c( fc )
+                               args.add( nv.as_arg_to_c )
+                       else
+                               args.add( "NULL" )
+                       end
                end
 
                # call to impl
-               var return_type : nullable MMType = null
-
-               if signature.return_type != null then
-                       return_type = signature.return_type
-               else if is_init then
-                       return_type = local_class.get_type
+               var rnv = signature.return_ni_variable
+               if rnv == null and is_init then
+                       rnv = new ReturnVariable( signature.recv )
                end
 
                var s = new Buffer
-               if return_type != null then
-                       # 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 = " )
+               if rnv != null then
+                       rnv.prepare_for_nit( fc )
+                       s.append( "{rnv.ni_from_name} = " )
                end
 
-               s.append( "{extern_name.as(not null)}( {params.join( ", " )} );\n" )
+               s.append( "{extern_name.as(not null)}( {args.join( ", " )} );\n" )
 
                fc.exprs.add( s.to_s )
 
-               if return_type != null then
-                       fc.exprs.add( "{return_type.assign_from_friendly( "return___nit", "return___nitni" )};\n" )
+               if rnv != null then
+                       fc.exprs.add( "{rnv.ni_type.assign_from_friendly( rnv.ni_to_name, rnv.ni_from_name )};\n" )
                end
 
                fc.exprs.add( "nitni_local_ref_clean(  );\n" )
 
                # return
-               if return_type != null then
-                       fc.exprs.add( "return return___nit;\n" )
+               if rnv != null then
+                       fc.exprs.add( "return {rnv.ni_to_name};\n" )
                end
 
                v.body.append( fc.to_writer )
@@ -238,30 +323,33 @@ redef class MMSrcMethod
 
 end
 
-redef class MMLocalClass
-       # Defines a defaut type for special of pointers in frontier.
-       # Can be overriden in the custime .nit.h file, as seen with nits.
-       fun compile_defaut_extern_type( v : FrontierVisitor )
+redef class MMSignature
+       var recv_ni_variable : ReceiverVariable
+       var return_ni_variable : nullable ReturnVariable
+       redef init( params, return_type, recv_type )
        do
-               v.header.add( "#ifndef {get_type.friendly_extern_name}\n" )
-               v.header.add( "\ttypedef void* {get_type.friendly_extern_name};\n" )
-               v.header.add( "#endif\n\n" )
+               super
+
+               if return_type != null then
+                       return_ni_variable = new ReturnVariable( return_type )
+               else
+                       return_ni_variable = null
+               end
+               recv_ni_variable = new ReceiverVariable( recv_type )
        end
-end
 
-redef class MMSignature
        fun compile_frontier( v : FrontierVisitor )
        do
                # receiver
-               v.types.add( recv )
+               v.types.add( recv.direct_type )
 
                # params
-               for p in params do v.types.add( p.mmtype )
+               for p in params do v.types.add( p.mmtype.direct_type )
 
                # return
                var rt = return_type
                if rt != null then
-                       v.types.add( rt )
+                       v.types.add( rt.direct_type )
                end
        end
 end
@@ -291,6 +379,7 @@ class FrontierVisitor
 
        var cprogram : CProgram
 
+       # compiles cached types and callbacks
        fun compile_cached
        do
                # types
@@ -318,21 +407,6 @@ class FrontierVisitor
        end
 end
 
-redef class String
-       # return path from one level deeper
-       # could be moved to stdlib.file
-       fun path_from_parent : String
-       do
-               if self[0] == '/' # is_absolute
-               then
-                       return self
-               else
-                       return "../{self}"
-               end
-
-       end
-end
-
 redef class MMImportedCast
        # Defines functions to cast types and verify the type of an object.
        fun compile_to_frontier( v : FrontierVisitor )
@@ -459,7 +533,7 @@ redef class MMType
        # To be nested within a condition.
        fun compile_condition_isa( var_name : String ) : String
        do
-               return "( ISOBJ( {var_name} ) ? OBJISA( {var_name}, {local_class.cname} ): VALISA( {var_name}, {local_class.cname} ) )"
+               return "( ISOBJ( {var_name} ) ? ISNULL( {var_name} ) || OBJISA( {var_name}, {local_class.cname} ): VALISA( {var_name}, {local_class.cname} ) )"
        end
 
        # Defines a friendly type in C for a given Nit type.
@@ -481,14 +555,28 @@ redef class MMType
 
                        # add null version, as a struct
                        if is_nullable then
-                               var null_getter = "null_{as_notnull.mangled_name}"
-                               var fc = new FunctionCompiler( "{name} {null_getter}()" )
-                               v.header_top.add( "{name} {null_getter}();\n" )
+                               var local_null_getter = local_friendly_null_getter_from( mmmodule )
+
+                               v.header_top.add( "#ifndef {friendly_null_getter}\n" )
+                               v.header_top.add( "#define {friendly_null_getter} {local_null_getter}\n" )
+                               v.header_top.add( "#endif\n" )
+
+                               v.header_top.add( "{name} {local_null_getter}();\n" )
+
+                               var fc = new FunctionCompiler( "{name} {local_null_getter}()" )
                                compile_new_local_ref( "n", fc, true )
                                fc.exprs.add( "return n;\n" )
                                v.body.append( fc.to_writer )
                        end
 
+                       # reference incr
+                       var incr_name = "{as_notnull.mangled_name}_incr_ref"
+                       v.header_top.add( "#define {incr_name}( x ) nitni_global_ref_incr( (struct nitni_ref*)(x) )\n" )
+
+                       # reference decr
+                       var decr_name = "{as_notnull.mangled_name}_decr_ref"
+                       v.header_top.add( "#define {decr_name}( x ) nitni_global_ref_decr( (struct nitni_ref*)(x) )\n" )
+
                        v.header_top.add( "#endif\n" )
                end
        end
@@ -501,11 +589,23 @@ redef class MMType
                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" )
+                       fc.exprs.add( "{var_name}->ref.count = 0;\n" )
                        if stack_it then
                                fc.exprs.add( "nitni_local_ref_add( (struct nitni_ref *){var_name} );\n" )
                        end
                end
        end
+
+       # compiles a stub local reference for unused references
+       # allows to maintain static typing but avoids malloc and free
+       fun compile_stub_local_ref( var_name : String, fc : FunctionCompiler )
+       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} = ({type_name})NULL;\n" )
+               end
+       end
 end
 
 redef class MMExplicitImport
@@ -527,58 +627,41 @@ redef class MMExplicitImport
                var fc = new FunctionCompiler( method.frontier_csignature_from( v.mmmodule, local_class ) )
 
                # params
-               var params = new Array[String]
-
-               # if not init, add receiver
-               if not method.is_init then
-                       var name_for_sub = "recv___nit"
-                       fc.decls.add( "val_t {name_for_sub};\n" )
-                       fc.exprs.add( "{signature.recv.assign_from_friendly( name_for_sub, "recv" )};\n" )
-                       params.add( name_for_sub )
-               end
-
-               for p in signature.params do
-                       var name_for_sub = "{p.name}___nit"
-                       fc.decls.add( "val_t {name_for_sub};\n" )
-                       fc.exprs.add( "{p.mmtype.assign_from_friendly( name_for_sub, p.name.to_s )};\n" )
-                       params.add( name_for_sub )
-               end
+               var params = new Array[NiVariable]
+               if not method.is_init then params.add( signature.recv_ni_variable )
+               params.add_all( signature.params )
 
-               # call to nit
-               var return_type : nullable MMType = null
+               for nv in params do nv.prepare_for_nit( fc )
 
                # handles return of method or constructor
-               if method.signature.return_type != null then
-                       return_type = method.signature.return_type
-               else if method.is_init then
-                       return_type = method.local_class.get_type
+               var rnv = signature.return_ni_variable
+               if rnv == null and method.is_init then
+                       rnv = new ReturnVariable( signature.recv )
                end
-
                var s = new Buffer
-               if return_type != null then
-                       return_type.compile_new_local_ref( "result___nitni", fc, true )
-                       fc.decls.add( "val_t result___nit;\n" )
-                       s.append( "result___nit = " )
+               if rnv != null then
+                       rnv.prepare_for_c( fc )
+                       s.append( "{rnv.ni_from_name} = " )
                end
 
                # hook to generated C code
                if method.is_init then
                        s.append( "NEW_{local_class}_{method.global.intro.cname}" )
                else
-                       s.append( "{method.global.meth_call}( recv___nit )" )
+                       s.append( "{method.global.meth_call}( {signature.recv_ni_variable.as_arg_to_nit} )" )
                end
 
-               s.append( "( {params.join( ", " )} );\n" )
+               var args = new Array[String]
+               for p in params do args.add( p.as_arg_to_nit )
+
+               s.append( "( {args.join( ", " )} );\n" )
 
                fc.exprs.add( s.to_s )
 
                # return
-               if return_type != null then
-                       var result_name_nitni = "result___nitni"
-                       var result_name_nit = "result___nit"
-
-                       fc.exprs.add( "{return_type.assign_to_friendly( result_name_nitni, result_name_nit )};\n" )
-                       fc.exprs.add( "return {result_name_nitni};\n" )
+               if rnv != null then
+                       fc.exprs.add( "{rnv.ni_type.assign_to_friendly( rnv.ni_to_name, rnv.ni_from_name )};\n" )
+                       fc.exprs.add( "return {rnv.ni_to_name};\n" )
                end
 
                v.body.append( fc.to_writer )