Merge: Intro the light FFI and use it in nith
authorJean Privat <jean@pryen.org>
Mon, 11 May 2015 23:22:31 +0000 (19:22 -0400)
committerJean Privat <jean@pryen.org>
Mon, 11 May 2015 23:22:31 +0000 (19:22 -0400)
The new light FFI use only features that should be easy to implement in new/alternative engines to quickly achieve a bootstrap. For this reason, core features of the Nit standard library should be limited to use the light FFI.

## Features of the light FFI

* **Extern methods** implemented in C, nested within the Nit code.
  The body of these method is copied directly to the generated C files for compilation.
  Also supports extern `new` factories.
* Module level **C code blocks**, both "C Body" (the default) and "C Header".
  They will be copied to the beginning of the generated C files.
* Automatic transformation of Nit **primitive types** from/to their equivalent in C.
* **Extern classes** to create a Nit class around a C pointer.
  Allows to specify the equivalent C type of the Nit extern class.

## Features of the full FFI

* More foreign languages: **C++, Java and Objective-C**.
* **Callbacks** to Nit from foreign codes.
  The callbacks are declared in Nit using the `import` annotation on extern methods.
  They are then generated on demand for the target foreign language.
* **Static Nit types** in C for precise typing and static typing errors in C.
* **Propagating public code blocks** at the module level (C Header).
  This allows to use extern classes in foreign code in other modules
  without having to import the related headers.
  This is optional in C as it is easy to find the correct importation.
  However it is important in Java and other complex FFIs.
* **Reference pinning** of Nit objects from foreign code.
  This ensure that objects referenced from foreign code are not liberated by the GC.
* FFI **annotations**:
    * `cflags`, `ldflags` and `cppflags` pass arguments to the C and C++
      compilers and linker.
    * `pkgconfig` calls the `pkg-config` program to get the arguments
      to pass to the C copiler and linker.
    * `extra_java_files` adds Java source file to the compilation chain.

## Light FFI only compilation

Compilers using the module `compiler_ffi::light_only` do not compile extern method with callbacks. This is a good heuristic to determine whether the method uses the full FFI of the light FFI.

The limitation of this heuristic is on 3 features: static Nit types, propagating public code blocks and reference pinning. These features do not required any declaration on the Nit side so they are not reliably detectable by the compiler. Using these features will cause GGC to raise errors on unfound types and functions.

In the case of public code block propagation, the user can fix it by importing the needed C headers in each module. In the other cases, static Nit types and reference pinning, they are used for callbacks, the method should probably declare callbacks. Still, there is some very rare situations where these features could be used correctly and the method would still be recognized as light FFI. If this is becomes a problem, we could add an annotation such as `is light_ffi` to force the heuristic.

> This PR should be read commit by commit. The first 4 commits separate modules in 2 their light/full versions. The only code modification is adding supers, redefs and tweaking the imports.

Pull-Request: #1323
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

12 files changed:
src/compiler/compiler_ffi/compiler_ffi.nit [moved from src/compiler/compiler_ffi.nit with 63% similarity]
src/compiler/compiler_ffi/light.nit [new file with mode: 0644]
src/compiler/compiler_ffi/light_only.nit [new file with mode: 0644]
src/ffi/c.nit
src/ffi/extern_classes.nit
src/ffi/ffi.nit
src/ffi/ffi_base.nit
src/ffi/light_c.nit [new file with mode: 0644]
src/ffi/light_ffi.nit [new file with mode: 0644]
src/ffi/light_ffi_base.nit [new file with mode: 0644]
src/nith.nit
src/nitni/nitni_base.nit

similarity index 63%
rename from src/compiler/compiler_ffi.nit
rename to src/compiler/compiler_ffi/compiler_ffi.nit
index 42423f9..8b8f9de 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# FFI support for the compilers
+# Full FFI support for the compiler
 module compiler_ffi
 
-intrude import abstract_compiler
-intrude import ffi
+intrude import light
 import nitni
 
 redef class MModule
        private var foreign_callbacks = new ForeignCallbackSet
-       var nitni_ccu: nullable CCompilationUnit = null
-
-       private fun nmodule(v: AbstractCompilerVisitor): nullable AModule
-       do
-               return v.compiler.modelbuilder.mmodule2node(self)
-       end
 
        redef fun finalize_ffi(compiler: AbstractCompiler)
        do
                if not uses_ffi then return
 
-               var v = compiler.new_visitor
-               var n = nmodule(v)
-               if n == null then return
-               n.ensure_compile_ffi_wrapper
-               finalize_ffi_wrapper(v.compiler.modelbuilder.compile_dir, v.compiler.mainmodule)
-               for file in ffi_files do v.compiler.extern_bodies.add(file)
-
-               ensure_compile_nitni_base(v)
-
-               nitni_ccu.header_c_types.add("#include \"{c_name}._ffi.h\"\n")
-               nitni_ccu.header_c_types.add """
-extern void nitni_global_ref_incr(void*);
-extern void nitni_global_ref_decr(void*);
-"""
-
-               var cflags = self.cflags[""].join(" ")
-               nitni_ccu.write_as_nitni(self, v.compiler.modelbuilder.compile_dir)
-
-               for file in nitni_ccu.files do
-                       var f = new ExternCFile(file, cflags)
-                       f.pkgconfigs.add_all pkgconfigs
-                       v.compiler.extern_bodies.add(f)
-               end
+               super
 
-               # reset FFI things so the next compilation job, if any, starts with a clean context
-               # FIXME clean and rationalize this
-               nitni_ccu = null
-               compiled_ffi_methods.clear
-               ffi_ccu = null
-               ffi_files.clear
                compiled_callbacks.clear
                #Do not reset `foreign_callbacks` and `ffi_callbacks` because they are computed in previous phases
        end
 
-       private fun ensure_compile_nitni_base(v: AbstractCompilerVisitor)
-       do
-               if nitni_ccu != null then return
-
-               nitni_ccu = new CCompilationUnit
-       end
-
-       redef fun collect_linker_libs
-       do
-               if not self.ldflags.keys.has("") then return null
-               return self.ldflags[""]
-       end
-
        private var compiled_callbacks = new Array[NitniCallback]
 
        # Returns true if callbacks has yet to be generated and register it as being generated
@@ -93,23 +45,12 @@ extern void nitni_global_ref_decr(void*);
 end
 
 redef class AMethPropdef
-       private fun compile_ffi_support_to_c(v: AbstractCompilerVisitor)
+       redef fun compile_ffi_support_to_c(v)
        do
+               super
+
                var mmodule = mpropdef.mclassdef.mmodule
                var mainmodule = v.compiler.mainmodule
-               var amodule = v.compiler.modelbuilder.mmodule2node(mmodule)
-               var mclass_type = mpropdef.mclassdef.bound_mtype
-
-               # Declare as extern
-               var csignature = mpropdef.mproperty.build_csignature(mclass_type, mmodule, "___impl", long_signature, internal_call_context)
-               v.declare_once("{csignature};")
-
-               # FFI part
-               amodule.ensure_compile_ffi_wrapper
-               compile_ffi_method(mmodule)
-
-               # nitni - Compile missing callbacks
-               mmodule.ensure_compile_nitni_base(v)
                var ccu = mmodule.nitni_ccu.as(not null)
 
                for mtype in foreign_callbacks.types do
@@ -137,178 +78,71 @@ redef class AMethPropdef
                # manage nitni callback set
                mmodule.foreign_callbacks.join(foreign_callbacks)
        end
+end
 
-       redef fun compile_externmeth_to_c(v, mpropdef, arguments)
+redef class MExplicitCall
+       private fun compile_extern_callback(v: AbstractCompilerVisitor, ccu: CCompilationUnit, compile_implementation_too: Bool)
        do
-               # if using the old native interface fallback on previous implementation
-               if n_extern_code_block == null then return super
+               var mproperty = mproperty
+               assert mproperty isa MMethod
 
-               var mmodule = mpropdef.mclassdef.mmodule
-               mmodule.uses_ffi = true
+               # In nitni files, declare internal function as extern
+               var full_friendly_csignature = mproperty.build_csignature(recv_mtype, v.compiler.mainmodule, null, long_signature, internal_call_context)
+               ccu.header_decl.add("extern {full_friendly_csignature};\n")
 
-               var mclass_type = mpropdef.mclassdef.bound_mtype
+               if not compile_implementation_too then return
 
-               # Outgoing code in compiler
-               var externname = mpropdef.mproperty.build_cname(mpropdef.mclassdef.bound_mtype, mmodule, "___impl", long_signature)
-               var recv_var: nullable RuntimeVariable = null
-               var return_mtype = mpropdef.msignature.return_mtype
-               if return_mtype != null then
-                       return_mtype = return_mtype.anchor_to(mmodule, mclass_type)
-                       recv_var = v.new_var(return_mtype)
-               end
+               # Internally, implement internal function
+               var nitni_visitor = v.compiler.new_visitor
+               nitni_visitor.frame = v.frame
+               var msignature = mproperty.lookup_first_definition(v.compiler.mainmodule, recv_mtype).msignature
+               var csignature_blind = mproperty.build_csignature(recv_mtype, v.compiler.mainmodule, null, long_signature, internal_call_context)
 
-               v.adapt_signature(mpropdef, arguments)
-               v.unbox_signature_extern(mpropdef, arguments)
-
-               var arguments_for_c = new Array[String]
-               for a in [0..arguments.length[ do
-                       var arg = arguments[a]
-                       var param_mtype: MType
-                       if a == 0 then
-                               param_mtype = mpropdef.mclassdef.mclass.mclass_type
-                       else param_mtype = mpropdef.msignature.mparameters[a-1].mtype
-
-                       param_mtype = param_mtype.anchor_to(mmodule, mclass_type)
-
-                       if param_mtype.is_cprimitive then
-                               arguments_for_c.add(arg.name)
-                       else
-                               v.add("struct nitni_instance* var_for_c_{a};")
-                               v.add("var_for_c_{a} = nit_alloc(sizeof(struct nitni_instance));")
-                               v.add("var_for_c_{a}->value = {arg.name};")
-                               arguments_for_c.add("var_for_c_{a}")
-                       end
-               end
+               nitni_visitor.add_decl("/* nitni callback for {mproperty.full_name} */")
+               nitni_visitor.add_decl("{csignature_blind} \{")
 
-               if recv_var == null then
-                       v.add("{externname}({arguments_for_c.join(", ")});")
+               var vars = new Array[RuntimeVariable]
+               var mtype: MType = recv_mtype
+               var recv_var = null
+               if mproperty.is_init then
+                       var recv_mtype = recv_mtype
+                       recv_var = nitni_visitor.init_instance_or_extern(recv_mtype)
+                       nitni_visitor.add("{mtype.ctype} recv /* var self: {mtype} */;")
+                       nitni_visitor.add("recv = {recv_var};")
                else
-                       assert return_mtype != null
-                       if return_mtype.is_cprimitive then
-                               v.add("{recv_var} = {externname}({arguments_for_c.join(", ")});")
-                       else
-                               v.add("struct nitni_instance* ret_var;")
-                               v.add("ret_var = {externname}({arguments_for_c.join(", ")});")
-                               v.add("{recv_var} = ret_var->value;")
-                       end
-                       recv_var = v.box_extern(recv_var, return_mtype)
-                       v.ret(recv_var)
+                       mtype = mtype.anchor_to(v.compiler.mainmodule, recv_mtype)
+                       recv_var = nitni_visitor.var_from_c("recv", mtype)
+                       recv_var = nitni_visitor.box_extern(recv_var, mtype)
                end
 
-               compile_ffi_support_to_c(v)
-               return true
-       end
-
-       redef fun compile_externinit_to_c(v, mpropdef, arguments)
-       do
-               # if using the old native interface fallback on previous implementation
-               if n_extern_code_block == null then return super
-
-               var mmodule = mpropdef.mclassdef.mmodule
-               mmodule.uses_ffi = true
-
-               var mclass_type = mpropdef.mclassdef.bound_mtype
-
-               var externname = mpropdef.mproperty.build_cname(mpropdef.mclassdef.bound_mtype, mmodule, "___impl", long_signature)
-               var return_mtype = arguments.first.mtype
-               var recv_var = v.new_var(return_mtype)
-
-               v.adapt_signature(mpropdef, arguments)
-               v.unbox_signature_extern(mpropdef, arguments)
-
-               arguments.shift
-
-               var arguments_for_c = new Array[String]
-               for a in [0..arguments.length[ do
-                       var arg = arguments[a]
-                       var param_mtype: MType
-                       param_mtype = mpropdef.msignature.mparameters[a].mtype
-                       param_mtype = param_mtype.anchor_to(mmodule, mclass_type)
-
-                       if param_mtype.is_cprimitive then
-                               arguments_for_c.add(arg.name)
-                       else
-                               v.add("struct nitni_instance* var_for_c_{a};")
-                               v.add("var_for_c_{a} = nit_alloc(sizeof(struct nitni_instance));")
-                               v.add("var_for_c_{a}->value = {arg.name};")
-                               arguments_for_c.add("var_for_c_{a}")
-                       end
-               end
+               vars.add(recv_var)
 
-               if return_mtype.is_cprimitive then
-                       v.add("{recv_var} = {externname}({arguments_for_c.join(", ")});")
-               else
-                       v.add("struct nitni_instance* ret_var;")
-                       v.add("ret_var = {externname}({arguments_for_c.join(", ")});")
-                       v.add("{recv_var} = ret_var->value;")
+               for p in msignature.mparameters do
+                       var arg_mtype = p.mtype.anchor_to(v.compiler.mainmodule, recv_mtype)
+                       var arg = nitni_visitor.var_from_c(p.name, arg_mtype)
+                       arg = nitni_visitor.box_extern(arg, arg_mtype)
+                       vars.add(arg)
                end
-               recv_var = v.box_extern(recv_var, return_mtype)
-               v.ret(recv_var)
 
-               compile_ffi_support_to_c(v)
-               return true
-       end
-end
-
-redef class CCompilationUnit
-       fun write_as_nitni(mmodule: MModule, compdir: String)
-       do
-               var base_name = "{mmodule.c_name}._nitni"
-
-               var h_file = "{base_name}.h"
-               write_header_to_file( mmodule, "{compdir}/{h_file}", new Array[String],
-                       "{mmodule.c_name.to_s.to_upper}_NITG_NITNI_H")
-
-               var c_file = "{base_name}.c"
-               write_body_to_file( mmodule, "{compdir}/{c_file}", ["\"{h_file}\""] )
-
-               files.add( "{compdir}/{c_file}" )
-       end
-end
-
-redef class AbstractCompiler
-       # Cache to avoid multiple compilation of NULL values
-       # see FIXME in `MNullableType#compile_extern_helper_functions`
-       private var compiled_null_types = new Array[MNullableType]
-end
+               var ret_var = nitni_visitor.send(mproperty, vars)
 
-redef class AbstractCompilerVisitor
-       # Create a `RuntimeVariable` for this C variable originating from C user code
-       private fun var_from_c(name: String, mtype: MType): RuntimeVariable
-       do
-               if mtype.is_cprimitive then
-                       return new RuntimeVariable(name, mtype, mtype)
-               else
-                       return new RuntimeVariable("{name}->value", mtype, mtype)
+               var return_mtype = msignature.return_mtype
+               if mproperty.is_init then
+                       if recv_mtype.mclass.kind != extern_kind then ret_var = recv_var
+                       return_mtype = recv_mtype
                end
-       end
-
-       # Return a `RuntimeVarible` to C user code
-       private fun ret_to_c(src: RuntimeVariable, mtype: MType)
-       do
-               if mtype.is_cprimitive then
-                       add("return {src};")
-               else
-                       add("struct nitni_instance* ret_for_c;")
-                       add("ret_for_c = nit_alloc(sizeof(struct nitni_instance));")
-                       add("ret_for_c->value = {src};")
-                       add("return ret_for_c;")
+               if return_mtype != null then
+                       assert ret_var != null
+                       return_mtype = return_mtype.anchor_to(v.compiler.mainmodule, recv_mtype)
+                       ret_var = nitni_visitor.autobox(ret_var, return_mtype)
+                       ret_var = nitni_visitor.unbox_extern(ret_var, return_mtype)
+                       nitni_visitor.ret_to_c(ret_var, return_mtype)
                end
+               nitni_visitor.add("\}")
        end
 end
 
 redef class MType
-       private fun compile_extern_type(v: AbstractCompilerVisitor, ccu: CCompilationUnit)
-       do
-               assert not is_cprimitive
-
-               # define friendly type
-               ccu.header_c_types.add("#ifndef NIT_TYPE_{cname}\n")
-               ccu.header_c_types.add("#define NIT_TYPE_{cname} 1\n")
-               ccu.header_c_types.add("typedef struct nitni_instance *{cname};\n")
-               ccu.header_c_types.add("#endif\n")
-       end
-
        private fun compile_extern_helper_functions(v: AbstractCompilerVisitor, ccu: CCompilationUnit, compile_implementation_too: Bool)
        do
                # actually, we do not need to do anything when using the bohem garbage collector
@@ -362,68 +196,6 @@ redef class MNullableType
        end
 end
 
-redef class MExplicitCall
-       private fun compile_extern_callback(v: AbstractCompilerVisitor, ccu: CCompilationUnit, compile_implementation_too: Bool)
-       do
-               var mproperty = mproperty
-               assert mproperty isa MMethod
-
-               # In nitni files, declare internal function as extern
-               var full_friendly_csignature = mproperty.build_csignature(recv_mtype, v.compiler.mainmodule, null, long_signature, internal_call_context)
-               ccu.header_decl.add("extern {full_friendly_csignature};\n")
-
-               if not compile_implementation_too then return
-
-               # Internally, implement internal function
-               var nitni_visitor = v.compiler.new_visitor
-               nitni_visitor.frame = v.frame
-               var msignature = mproperty.lookup_first_definition(v.compiler.mainmodule, recv_mtype).msignature
-               var csignature_blind = mproperty.build_csignature(recv_mtype, v.compiler.mainmodule, null, long_signature, internal_call_context)
-
-               nitni_visitor.add_decl("/* nitni callback for {mproperty.full_name} */")
-               nitni_visitor.add_decl("{csignature_blind} \{")
-
-               var vars = new Array[RuntimeVariable]
-               var mtype: MType = recv_mtype
-               var recv_var = null
-               if mproperty.is_init then
-                       var recv_mtype = recv_mtype
-                       recv_var = nitni_visitor.init_instance_or_extern(recv_mtype)
-                       nitni_visitor.add("{mtype.ctype} recv /* var self: {mtype} */;")
-                       nitni_visitor.add("recv = {recv_var};")
-               else
-                       mtype = mtype.anchor_to(v.compiler.mainmodule, recv_mtype)
-                       recv_var = nitni_visitor.var_from_c("recv", mtype)
-                       recv_var = nitni_visitor.box_extern(recv_var, mtype)
-               end
-
-               vars.add(recv_var)
-
-               for p in msignature.mparameters do
-                       var arg_mtype = p.mtype.anchor_to(v.compiler.mainmodule, recv_mtype)
-                       var arg = nitni_visitor.var_from_c(p.name, arg_mtype)
-                       arg = nitni_visitor.box_extern(arg, arg_mtype)
-                       vars.add(arg)
-               end
-
-               var ret_var = nitni_visitor.send(mproperty, vars)
-
-               var return_mtype = msignature.return_mtype
-               if mproperty.is_init then
-                       if recv_mtype.mclass.kind != extern_kind then ret_var = recv_var
-                       return_mtype = recv_mtype
-               end
-               if return_mtype != null then
-                       assert ret_var != null
-                       return_mtype = return_mtype.anchor_to(v.compiler.mainmodule, recv_mtype)
-                       ret_var = nitni_visitor.autobox(ret_var, return_mtype)
-                       ret_var = nitni_visitor.unbox_extern(ret_var, return_mtype)
-                       nitni_visitor.ret_to_c(ret_var, return_mtype)
-               end
-               nitni_visitor.add("\}")
-       end
-end
-
 redef class MExplicitSuper
        private fun compile_extern_callback(v: AbstractCompilerVisitor, ccu: CCompilationUnit, compile_implementation_too: Bool)
        do
diff --git a/src/compiler/compiler_ffi/light.nit b/src/compiler/compiler_ffi/light.nit
new file mode 100644 (file)
index 0000000..dbe819a
--- /dev/null
@@ -0,0 +1,281 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013-2015 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Light FFI support for the compiler
+module light
+
+intrude import abstract_compiler
+intrude import ffi::light_ffi
+
+redef class MModule
+       # `CCompilationUnit` used for nitni signatures and code specific to the compiler
+       var nitni_ccu: nullable CCompilationUnit = null
+
+       private fun nmodule(v: AbstractCompilerVisitor): nullable AModule
+       do
+               return v.compiler.modelbuilder.mmodule2node(self)
+       end
+
+       redef fun finalize_ffi(compiler: AbstractCompiler)
+       do
+               if not uses_ffi then return
+
+               var v = compiler.new_visitor
+               var n = nmodule(v)
+               if n == null then return
+               n.ensure_compile_ffi_wrapper
+               finalize_ffi_wrapper(v.compiler.modelbuilder.compile_dir, v.compiler.mainmodule)
+               for file in ffi_files do v.compiler.extern_bodies.add(file)
+
+               ensure_compile_nitni_base(v)
+
+               nitni_ccu.header_c_types.add("#include \"{c_name}._ffi.h\"\n")
+               nitni_ccu.header_c_types.add """
+extern void nitni_global_ref_incr(void*);
+extern void nitni_global_ref_decr(void*);
+"""
+
+               var cflags = self.cflags[""].join(" ")
+               nitni_ccu.write_as_nitni(self, v.compiler.modelbuilder.compile_dir)
+
+               for file in nitni_ccu.files do
+                       var f = new ExternCFile(file, cflags)
+                       f.pkgconfigs.add_all pkgconfigs
+                       v.compiler.extern_bodies.add(f)
+               end
+
+               # reset FFI things so the next compilation job, if any, starts with a clean context
+               # FIXME clean and rationalize this
+               nitni_ccu = null
+               compiled_ffi_methods.clear
+               ffi_ccu = null
+               ffi_files.clear
+       end
+
+       private fun ensure_compile_nitni_base(v: AbstractCompilerVisitor)
+       do
+               if nitni_ccu != null then return
+
+               nitni_ccu = new CCompilationUnit
+       end
+
+       redef fun collect_linker_libs
+       do
+               if not self.ldflags.keys.has("") then return null
+               return self.ldflags[""]
+       end
+end
+
+redef class AMethPropdef
+       private fun compile_ffi_support_to_c(v: AbstractCompilerVisitor)
+       do
+               var mmodule = mpropdef.mclassdef.mmodule
+               var amodule = v.compiler.modelbuilder.mmodule2node(mmodule)
+               var mclass_type = mpropdef.mclassdef.bound_mtype
+
+               # Declare as extern
+               var csignature = mpropdef.mproperty.build_csignature(mclass_type, mmodule, "___impl", long_signature, internal_call_context)
+               v.declare_once("{csignature};")
+
+               # FFI part
+               amodule.ensure_compile_ffi_wrapper
+               compile_ffi_method(mmodule)
+
+               # nitni - Compile missing callbacks
+               mmodule.ensure_compile_nitni_base(v)
+       end
+
+       # Should we compile the extern method `self`?
+       #
+       # Returns false when restricting to the light FFI on methods using callbacks.
+       fun accept_externmeth: Bool do return true
+
+       redef fun compile_externmeth_to_c(v, mpropdef, arguments)
+       do
+               # if using the old native interface fallback on previous implementation
+               if n_extern_code_block == null then return super
+
+               if not accept_externmeth then return false
+
+               var mmodule = mpropdef.mclassdef.mmodule
+               mmodule.uses_ffi = true
+
+               var mclass_type = mpropdef.mclassdef.bound_mtype
+
+               # Outgoing code in compiler
+               var externname = mpropdef.mproperty.build_cname(mpropdef.mclassdef.bound_mtype, mmodule, "___impl", long_signature)
+               var recv_var: nullable RuntimeVariable = null
+               var return_mtype = mpropdef.msignature.return_mtype
+               if return_mtype != null then
+                       return_mtype = return_mtype.anchor_to(mmodule, mclass_type)
+                       recv_var = v.new_var(return_mtype)
+               end
+
+               v.adapt_signature(mpropdef, arguments)
+               v.unbox_signature_extern(mpropdef, arguments)
+
+               var arguments_for_c = new Array[String]
+               for a in [0..arguments.length[ do
+                       var arg = arguments[a]
+                       var param_mtype: MType
+                       if a == 0 then
+                               param_mtype = mpropdef.mclassdef.mclass.mclass_type
+                       else param_mtype = mpropdef.msignature.mparameters[a-1].mtype
+
+                       param_mtype = param_mtype.anchor_to(mmodule, mclass_type)
+
+                       if param_mtype.is_cprimitive then
+                               arguments_for_c.add(arg.name)
+                       else
+                               v.add("struct nitni_instance* var_for_c_{a};")
+                               v.add("var_for_c_{a} = nit_alloc(sizeof(struct nitni_instance));")
+                               v.add("var_for_c_{a}->value = {arg.name};")
+                               arguments_for_c.add("var_for_c_{a}")
+                       end
+               end
+
+               if recv_var == null then
+                       v.add("{externname}({arguments_for_c.join(", ")});")
+               else
+                       assert return_mtype != null
+                       if return_mtype.is_cprimitive then
+                               v.add("{recv_var} = {externname}({arguments_for_c.join(", ")});")
+                       else
+                               v.add("struct nitni_instance* ret_var;")
+                               v.add("ret_var = {externname}({arguments_for_c.join(", ")});")
+                               v.add("{recv_var} = ret_var->value;")
+                       end
+                       recv_var = v.box_extern(recv_var, return_mtype)
+                       v.ret(recv_var)
+               end
+
+               compile_ffi_support_to_c(v)
+               return true
+       end
+
+       redef fun compile_externinit_to_c(v, mpropdef, arguments)
+       do
+               # if using the old native interface fallback on previous implementation
+               if n_extern_code_block == null then return super
+
+               if not accept_externmeth then return false
+
+               var mmodule = mpropdef.mclassdef.mmodule
+               mmodule.uses_ffi = true
+
+               var mclass_type = mpropdef.mclassdef.bound_mtype
+
+               var externname = mpropdef.mproperty.build_cname(mpropdef.mclassdef.bound_mtype, mmodule, "___impl", long_signature)
+               var return_mtype = arguments.first.mtype
+               var recv_var = v.new_var(return_mtype)
+
+               v.adapt_signature(mpropdef, arguments)
+               v.unbox_signature_extern(mpropdef, arguments)
+
+               arguments.shift
+
+               var arguments_for_c = new Array[String]
+               for a in [0..arguments.length[ do
+                       var arg = arguments[a]
+                       var param_mtype: MType
+                       param_mtype = mpropdef.msignature.mparameters[a].mtype
+                       param_mtype = param_mtype.anchor_to(mmodule, mclass_type)
+
+                       if param_mtype.is_cprimitive then
+                               arguments_for_c.add(arg.name)
+                       else
+                               v.add("struct nitni_instance* var_for_c_{a};")
+                               v.add("var_for_c_{a} = nit_alloc(sizeof(struct nitni_instance));")
+                               v.add("var_for_c_{a}->value = {arg.name};")
+                               arguments_for_c.add("var_for_c_{a}")
+                       end
+               end
+
+               if return_mtype.is_cprimitive then
+                       v.add("{recv_var} = {externname}({arguments_for_c.join(", ")});")
+               else
+                       v.add("struct nitni_instance* ret_var;")
+                       v.add("ret_var = {externname}({arguments_for_c.join(", ")});")
+                       v.add("{recv_var} = ret_var->value;")
+               end
+               recv_var = v.box_extern(recv_var, return_mtype)
+               v.ret(recv_var)
+
+               compile_ffi_support_to_c(v)
+               return true
+       end
+end
+
+redef class CCompilationUnit
+       # Compile a `_nitni` files, used to implement nitni features for the compiler
+       fun write_as_nitni(mmodule: MModule, compdir: String)
+       do
+               var base_name = "{mmodule.c_name}._nitni"
+
+               var h_file = "{base_name}.h"
+               write_header_to_file( mmodule, "{compdir}/{h_file}", new Array[String],
+                       "{mmodule.c_name.to_s.to_upper}_NITG_NITNI_H")
+
+               var c_file = "{base_name}.c"
+               write_body_to_file( mmodule, "{compdir}/{c_file}", ["\"{h_file}\""] )
+
+               files.add( "{compdir}/{c_file}" )
+       end
+end
+
+redef class AbstractCompiler
+       # Cache to avoid multiple compilation of NULL values
+       # see FIXME in `MNullableType#compile_extern_helper_functions`
+       private var compiled_null_types = new Array[MNullableType]
+end
+
+redef class AbstractCompilerVisitor
+       # Create a `RuntimeVariable` for this C variable originating from C user code
+       private fun var_from_c(name: String, mtype: MType): RuntimeVariable
+       do
+               if mtype.is_cprimitive then
+                       return new RuntimeVariable(name, mtype, mtype)
+               else
+                       return new RuntimeVariable("{name}->value", mtype, mtype)
+               end
+       end
+
+       # Return a `RuntimeVarible` to C user code
+       private fun ret_to_c(src: RuntimeVariable, mtype: MType)
+       do
+               if mtype.is_cprimitive then
+                       add("return {src};")
+               else
+                       add("struct nitni_instance* ret_for_c;")
+                       add("ret_for_c = nit_alloc(sizeof(struct nitni_instance));")
+                       add("ret_for_c->value = {src};")
+                       add("return ret_for_c;")
+               end
+       end
+end
+
+redef class MType
+       private fun compile_extern_type(v: AbstractCompilerVisitor, ccu: CCompilationUnit)
+       do
+               assert not is_cprimitive
+
+               # define friendly type
+               ccu.header_c_types.add("#ifndef NIT_TYPE_{cname}\n")
+               ccu.header_c_types.add("#define NIT_TYPE_{cname} 1\n")
+               ccu.header_c_types.add("typedef struct nitni_instance *{cname};\n")
+               ccu.header_c_types.add("#endif\n")
+       end
+end
diff --git a/src/compiler/compiler_ffi/light_only.nit b/src/compiler/compiler_ffi/light_only.nit
new file mode 100644 (file)
index 0000000..3fc162d
--- /dev/null
@@ -0,0 +1,26 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Compiler support for the light FFI only, detects unsupported usage of callbacks
+module light_only
+
+import light
+
+redef class MClassType
+       redef fun cname_normal_class do return "void*"
+end
+
+redef class AMethPropdef
+       redef fun accept_externmeth do return n_extern_calls == null
+end
index 495d4e6..e09a80f 100644 (file)
 module c
 
 import ffi_base
+import light_c
 
-redef class FFILanguageAssignationPhase
-       var c_language: FFILanguage = new CLanguage(self)
-end
-
-class CLanguage
-       super FFILanguage
-
-       redef fun identify_language(n) do return n.is_c
-
-       redef fun compile_module_block(block, ecc, mmodule)
-       do
-               if block.is_c_header then
-                       ecc.header_custom.add block.location.as_line_pragma
-                       ecc.header_custom.add "\n"
-                       ecc.header_custom.add block.code
-               else if block.is_c_body then
-                       ecc.body_impl.add block.location.as_line_pragma
-                       ecc.body_impl.add "\n"
-                       ecc.body_impl.add block.code
-               end
-       end
-
-       redef fun compile_extern_method(block, m, ecc, mmodule)
-       do
-               var fc = new ExternCFunction(m, mmodule)
-               fc.decls.add( block.location.as_line_pragma )
-               fc.exprs.add( block.code )
-               ecc.add_exported_function( fc )
-       end
-
-       redef fun compile_extern_class(block, m, ecc, mmodule) do end
-
-       redef fun get_ftype(block, m) do return new ForeignCType(block.code)
-
+redef class CLanguage
        redef fun compile_callback(callback, mmodule, mainmodule, ecc)
        do
                callback.compile_callback_to_c(mainmodule, ecc)
        end
 end
 
-redef class AExternCodeBlock
-       fun is_c: Bool do return language_name == null or
-               language_name_lowered == "c" or language_name_lowered.has_prefix( "c " )
-
-       fun is_c_body: Bool do return language_name == null or
-               language_name_lowered == "c" or language_name_lowered ==  "c body"
-
-       fun is_c_header: Bool do return language_name_lowered == "c header"
-end
-
-redef class Location
-       fun as_line_pragma: String do return "#line {line_start-1} \"{file.filename}\"\n"
-end
-
-redef class MModule
-       # FIXME make nullable the key of `cflags`, `ldflags` and `cppflags` when
-       # supported by the bootstrap
-
-       # Custom options for the C compiler (CFLAGS)
-       var cflags = new MultiHashMap[String, String]
-
-       # Custom options for the C linker (LDFLAGS)
-       var ldflags = new MultiHashMap[String, String]
-
-       # Additional libraries needed for the compilation
-       # Will be used with pkg-config
-       var pkgconfigs = new Array[String]
-end
-
-class ForeignCType
-       super ForeignType
-
-       redef var ctype: String
-end
-
 redef class NitniCallback
        fun compile_callback_to_c(mmodule: MModule, ffi_ccu: CCompilationUnit) do end
 end
 
-redef class Object
-       # Context when calling user C code from generated code
-       fun to_c_call_context: ToCCallContext do return once new ToCCallContext
-
-       # Context when calling generated code from user C code
-       fun from_c_call_context: FromCCallContext do return once new FromCCallContext
-end
-
 redef class MExplicitCall
        redef fun compile_callback_to_c(mmodule, ffi_ccu)
        do
@@ -116,41 +41,3 @@ redef class MExplicitCall
                ffi_ccu.body_decl.add("#define {friendly_cname} {full_cname}\n")
        end
 end
-
-# Context when calling user C code from generated code
-class ToCCallContext
-       super CallContext
-
-       # TODO: private init because singleton instance (see `to_c_call_context`)
-
-       redef fun name_mtype(mtype)
-       do
-               if mtype isa MClassType and mtype.mclass.kind == extern_kind then return "void *"
-               return mtype.cname
-       end
-end
-
-# Context when calling generated code from user C code
-class FromCCallContext
-       super CallContext
-
-       # TODO: private init because singleton instance (see `from_c_call_context`)
-
-       redef fun name_mtype(mtype) do return mtype.cname
-end
-
-class ExternCFunction
-       super CFunction
-
-       var method: AMethPropdef
-
-       init (method: AMethPropdef, mmodule: MModule)
-       do
-               self.method = method
-
-               var recv_mtype = method.mpropdef.mclassdef.bound_mtype
-               var csignature = method.mpropdef.mproperty.build_csignature(recv_mtype, mmodule, "___impl", long_signature, from_c_call_context)
-
-               super( csignature )
-       end
-end
index 6b53008..c0e826b 100644 (file)
@@ -17,7 +17,8 @@
 # Manages all extern classes and their associated foreign type.
 module extern_classes
 
-import ffi_base
+import light_ffi_base
+import modelize
 
 redef class ToolContext
        var extern_classes_typing_phase_ast: Phase = new ExternClassesTypingPhaseAst(self, [ffi_language_assignation_phase, modelize_class_phase])
index 93acf66..e58d32a 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# FFI concers common between the compilers and the interpreter.
-# Offers services to compile modules using foreign code. Mainly allows
-# to wrap foreign code in Nit methods.
+# Full FFI support, independent of the compiler
+#
+# The full FFI support all the features of the light FFI and more:
+#
+# * More foreign languages: **C++, Java and Objective-C**.
+# * **Callbacks** to Nit from foreign codes.
+#   The callbacks are declared in Nit using the `import` annotation on extern methods.
+#   They are then generated on demand for the target foreign language.
+# * **Static Nit types** in C for precise typing and static typing errors in C.
+# * **Propagating public code blocks** at the module level (C Header).
+#   This allows to use extern classes in foreign code in other modules
+#   without having to import the related headers.
+#   This is optional in C as it is easy to find the correct importation.
+#   However it is important in Java and other complex FFIs.
+# * **Reference pinning** of Nit objects from foreign code.
+#   This ensure that objects referenced from foreign code are not liberated by the GC.
+# * FFI **annotations**:
+#   * `cflags`, `ldflags` and `cppflags` pass arguments to the C and C++
+# compilers and linker.
+#   * `pkgconfig` calls the `pkg-config` program to get the arguments
+# to pass to the C copiler and linker.
+#   * `extra_java_files` adds Java source file to the compilation chain.
 module ffi
 
 import modelbuilder
@@ -33,19 +52,11 @@ import cpp
 import java
 import extra_java_files
 import objc
+intrude import light_ffi
 
 redef class MModule
-       # Does this module uses the FFI?
-       var uses_ffi: Bool = false
-
-       # C compilation unit for the FFI files
-       private var ffi_ccu: nullable CCompilationUnit = null
-
-       # Foreign language used in this AModule
-       private var present_languages = new HashSet[FFILanguage]
-
        # Complete the compilation of the FFI code
-       fun finalize_ffi_wrapper(compdir: String, mainmodule: MModule)
+       redef fun finalize_ffi_wrapper(compdir, mainmodule)
        do
                for language in ffi_callbacks.keys do
                        for callback in ffi_callbacks[language] do
@@ -60,82 +71,10 @@ redef class MModule
                        if mod.uses_ffi then ffi_ccu.header_custom.add("#include \"{mod.c_name}._ffi.h\"\n")
                end
 
-               var cflags = self.cflags[""].join(" ")
-
-               ffi_ccu.write_as_impl(self, compdir)
-               for filename in ffi_ccu.files do
-                       var f = new ExternCFile(filename, cflags)
-                       f.pkgconfigs.add_all pkgconfigs
-                       ffi_files.add(f)
-               end
-       end
-
-       # Avoid the compile a ffi propdef more than once
-       # See `AMethPropdef::compile_ffi_method`
-       # FIXME find a better way
-       private var compiled_ffi_methods = new HashSet[AMethPropdef]
-end
-
-redef class AModule
-
-       # Ensures all of the general foreign code of the module has been analyzed.
-       # Manages header blocks, extern class types and foreign dependancies between modules
-       fun ensure_compile_ffi_wrapper
-       do
-               var mmodule = mmodule
-               if mmodule == null or mmodule.ffi_ccu != null then return
-
-               # ready extern code compiler
-               var ffi_ccu = new CCompilationUnit
-               mmodule.ffi_ccu = ffi_ccu
-
-               # generate code
-               for block in n_extern_code_blocks do
-                       var language = block.language
-                       assert language != null
-                       mmodule.present_languages.add(language)
-                       language.compile_module_block(block, ffi_ccu, mmodule)
-               end
-
-               ffi_ccu.header_c_base.add( "#include \"{mmodule.c_name}._nitni.h\"\n" )
-
-               ffi_ccu.body_decl.add("#ifdef ANDROID\n")
-               ffi_ccu.body_decl.add(" #include <android/log.h>\n")
-               ffi_ccu.body_decl.add(" #define PRINT_ERROR(...) (void)__android_log_print(ANDROID_LOG_WARN, \"Nit\", __VA_ARGS__)\n")
-               ffi_ccu.body_decl.add("#else\n")
-               ffi_ccu.body_decl.add(" #define PRINT_ERROR(...) fprintf(stderr, __VA_ARGS__)\n")
-               ffi_ccu.body_decl.add("#endif\n")
-
-               for nclassdef in n_classdefs do
-                       # Does it declares an extern type?
-                       if nclassdef isa AStdClassdef and nclassdef.n_extern_code_block != null then
-                               mmodule.uses_ffi = true
-                               var language = nclassdef.n_extern_code_block.language
-                               assert language != null
-                               mmodule.present_languages.add(language)
-                               nclassdef.n_extern_code_block.language.compile_extern_class(
-                                       nclassdef.n_extern_code_block.as(not null), nclassdef, ffi_ccu, mmodule)
-                       end
-               end
+               super
        end
 end
 
-redef class AMethPropdef
-       # Compile the necessary wrapper around this extern method or constructor
-       fun compile_ffi_method(mmodule: MModule)
-       do
-               assert n_extern_code_block != null
-
-               if mmodule.compiled_ffi_methods.has(self) then return
-               mmodule.compiled_ffi_methods.add self
-
-               var language = n_extern_code_block.language
-               assert language != null
-               mmodule.present_languages.add(language)
-               n_extern_code_block.language.compile_extern_method(
-                       n_extern_code_block.as(not null), self, mmodule.ffi_ccu.as(not null), mmodule)
-       end
-end
 
 redef class VerifyNitniCallbacksPhase
        redef fun process_npropdef(npropdef)
index b8a8103..77a982d 100644 (file)
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Tools and utilities for language targetted FFI implementations
+# Tools and utilities for implement FFI with different languages
 module ffi_base
 
 import c_tools
@@ -22,184 +22,17 @@ import parser
 import modelbuilder
 import nitni
 
-redef class ToolContext
-       var ffi_language_assignation_phase: Phase = new FFILanguageAssignationPhase(self, null)
-end
-
-class FFILanguageAssignationPhase
-       super Phase
-
-       # All supported languages
-       var languages = new Array[FFILanguage]
-
-       redef fun process_nmodule(nmodule)
-       do
-               for block in nmodule.n_extern_code_blocks do
-                       verify_foreign_code_on_node( block )
-               end
-       end
-
-       redef fun process_npropdef(npropdef)
-       do
-               if npropdef isa AMethPropdef then
-                       var code_block = npropdef.n_extern_code_block
-                       if code_block != null then
-                               verify_foreign_code_on_node( code_block )
-                       end
-               end
-       end
-
-       redef fun process_nclassdef(nclassdef)
-       do
-               if nclassdef isa AStdClassdef and nclassdef.n_extern_code_block != null then
-                       verify_foreign_code_on_node( nclassdef.n_extern_code_block.as(not null) )
-               end
-       end
-
-       private fun verify_foreign_code_on_node(n: AExternCodeBlock)
-       do
-               var found = false
-               for v in languages do
-                       var identified = v.identify_language(n)
-                       if identified then
-                               if found and identified then
-                                       toolcontext.error(n.location, "FFI Error: two languages identified as possible handlers.")
-                               end
-                               n.language = v
-                               found = true
-                       end
-               end
-
-               if not found then toolcontext.error(n.location, "FFI Error: unsupported language.")
-       end
-end
+import light_ffi_base
 
 redef class MModule
-       var ffi_files = new Array[ExternFile]
-
        # Callbacks used by this module, classified by language
        var ffi_callbacks = new HashMap[FFILanguage, Set[NitniCallback]]
 end
 
-redef class AExternCodeBlock
-       fun language_name: nullable String do
-               if n_in_language == null then return null
-               return n_in_language.n_string.without_quotes
-       end
-       fun language_name_lowered: nullable String do
-               if language_name == null then return null
-               return language_name.to_lower
-       end
-
-       fun code: String do return n_extern_code_segment.without_guard
-
-       var language: nullable FFILanguage = null
-end
-
 # Visitor for a specific languages. Works kinda like a `Phase` and is executed
 # by a `Phase`.
-class FFILanguage
-       var ffi_language_assignation_phase: FFILanguageAssignationPhase
-
-       init
-       do
-               ffi_language_assignation_phase.languages.add(self)
-       end
-
-       # Is this `block` written in this language?
-       fun identify_language(block: AExternCodeBlock ): Bool is abstract
-
-       # Generate wrapper code for this module/header code block
-       fun compile_module_block(block: AExternCodeBlock, ecc: CCompilationUnit, mmodule: MModule) is abstract
-
-       # Generate wrapper code for this extern method
-       fun compile_extern_method(block: AExternCodeBlock, m: AMethPropdef,
-               ecc: CCompilationUnit, nmodule: MModule) is abstract
-
-       # Generate wrapper code for this extern class
-       fun compile_extern_class(block: AExternCodeBlock, m: AClassdef,
-               ecc: CCompilationUnit, mmodule: MModule) is abstract
-
-       # Get the foreign type of this extern class definition
-       fun get_ftype(block: AExternCodeBlock, m: AClassdef): ForeignType is abstract
-
+redef class FFILanguage
        # Generate the code to offer this callback if foreign code
        fun compile_callback(callback: NitniCallback, mmodule: MModule,
                mainmmodule: MModule, ecc: CCompilationUnit) is abstract
-
-       # Complete compilation of generated code
-       fun compile_to_files(mmodule: MModule, directory: String) do end
-end
-
-redef class TString
-       # Returns the content of this node without both of the surrounding "
-       fun without_quotes: String
-       do
-               assert text.length >= 2
-               return text.substring(1, text.length-2)
-       end
-end
-
-redef class TExternCodeSegment
-       # Returns the content of this node without the surrounding `{ and `}
-       fun without_guard: String
-       do
-               assert text.length >= 4
-               return text.substring(2, text.length-4)
-       end
-end
-
-redef class CCompilationUnit
-       fun write_as_impl(mmodule: MModule, compdir: String)
-       do
-               var base_name = "{mmodule.c_name}._ffi"
-
-               var h_file = "{base_name}.h"
-               var guard = "{mmodule.c_name.to_upper}_NIT_H"
-               write_header_to_file(mmodule, "{compdir}/{h_file}", new Array[String], guard)
-
-               var c_file = "{base_name}.c"
-               write_body_to_file(mmodule, "{compdir}/{c_file}", ["<stdlib.h>", "<stdio.h>", "\"{h_file}\""])
-
-               files.add( "{compdir}/{c_file}" )
-       end
-
-       fun write_header_to_file(mmodule: MModule, file: String, includes: Array[String], guard: String)
-       do
-               var stream = new FileWriter.open( file )
-
-               # header comments
-               var module_info = "/*\n\tExtern implementation of Nit module {mmodule.name}\n*/\n"
-
-               stream.write( module_info )
-
-               stream.write( "#ifndef {guard}\n" )
-               stream.write( "#define {guard}\n\n" )
-
-               for incl in includes do stream.write( "#include {incl}\n" )
-
-               compile_header_core( stream )
-
-               # header file guard close
-               stream.write( "#endif\n" )
-               stream.close
-       end
-
-       fun write_body_to_file(mmodule: MModule, file: String, includes: Array[String])
-       do
-               var stream = new FileWriter.open(file)
-
-               var module_info = "/*\n\tExtern implementation of Nit module {mmodule.name}\n*/\n"
-
-               stream.write( module_info )
-               for incl in includes do stream.write( "#include {incl}\n" )
-
-               compile_body_core( stream )
-
-               stream.close
-       end
-end
-
-class ForeignType
-       fun ctype: String do return "void*"
 end
diff --git a/src/ffi/light_c.nit b/src/ffi/light_c.nit
new file mode 100644 (file)
index 0000000..86ec651
--- /dev/null
@@ -0,0 +1,138 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2012-2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Support for nesting C code within a Nit program using its FFI
+module light_c
+
+import modelize::modelize_property
+
+import light_ffi_base
+
+redef class FFILanguageAssignationPhase
+       var c_language: FFILanguage = new CLanguage(self)
+end
+
+class CLanguage
+       super FFILanguage
+
+       redef fun identify_language(n) do return n.is_c
+
+       redef fun compile_module_block(block, ecc, mmodule)
+       do
+               if block.is_c_header then
+                       ecc.header_custom.add block.location.as_line_pragma
+                       ecc.header_custom.add "\n"
+                       ecc.header_custom.add block.code
+               else if block.is_c_body then
+                       ecc.body_impl.add block.location.as_line_pragma
+                       ecc.body_impl.add "\n"
+                       ecc.body_impl.add block.code
+               end
+       end
+
+       redef fun compile_extern_method(block, m, ecc, mmodule)
+       do
+               var fc = new ExternCFunction(m, mmodule)
+               fc.decls.add( block.location.as_line_pragma )
+               fc.exprs.add( block.code )
+               ecc.add_exported_function( fc )
+       end
+
+       redef fun compile_extern_class(block, m, ecc, mmodule) do end
+
+       redef fun get_ftype(block, m) do return new ForeignCType(block.code)
+end
+
+redef class AExternCodeBlock
+       fun is_c: Bool do return language_name == null or
+               language_name_lowered == "c" or language_name_lowered.has_prefix( "c " )
+
+       fun is_c_body: Bool do return language_name == null or
+               language_name_lowered == "c" or language_name_lowered ==  "c body"
+
+       fun is_c_header: Bool do return language_name_lowered == "c header"
+end
+
+redef class Location
+       fun as_line_pragma: String do return "#line {line_start-1} \"{file.filename}\"\n"
+end
+
+redef class MModule
+       # FIXME make nullable the key of `cflags`, `ldflags` and `cppflags` when
+       # supported by the bootstrap
+
+       # Custom options for the C compiler (CFLAGS)
+       var cflags = new MultiHashMap[String, String]
+
+       # Custom options for the C linker (LDFLAGS)
+       var ldflags = new MultiHashMap[String, String]
+
+       # Additional libraries needed for the compilation
+       # Will be used with pkg-config
+       var pkgconfigs = new Array[String]
+end
+
+class ForeignCType
+       super ForeignType
+
+       redef var ctype: String
+end
+
+redef class Object
+       # Context when calling user C code from generated code
+       fun to_c_call_context: ToCCallContext do return once new ToCCallContext
+
+       # Context when calling generated code from user C code
+       fun from_c_call_context: FromCCallContext do return once new FromCCallContext
+end
+
+# Context when calling user C code from generated code
+class ToCCallContext
+       super CallContext
+
+       # TODO: private init because singleton instance (see `to_c_call_context`)
+
+       redef fun name_mtype(mtype)
+       do
+               if mtype isa MClassType and mtype.mclass.kind == extern_kind then return "void *"
+               return mtype.cname
+       end
+end
+
+# Context when calling generated code from user C code
+class FromCCallContext
+       super CallContext
+
+       # TODO: private init because singleton instance (see `from_c_call_context`)
+
+       redef fun name_mtype(mtype) do return mtype.cname
+end
+
+class ExternCFunction
+       super CFunction
+
+       var method: AMethPropdef
+
+       init (method: AMethPropdef, mmodule: MModule)
+       do
+               self.method = method
+
+               var recv_mtype = method.mpropdef.mclassdef.bound_mtype
+               var csignature = method.mpropdef.mproperty.build_csignature(recv_mtype, mmodule, "___impl", long_signature, from_c_call_context)
+
+               super( csignature )
+       end
+end
diff --git a/src/ffi/light_ffi.nit b/src/ffi/light_ffi.nit
new file mode 100644 (file)
index 0000000..2245a92
--- /dev/null
@@ -0,0 +1,131 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Light FFI support, independent of the compiler
+#
+# The light FFI offers only basic FFI features:
+#
+# * **Extern methods** implemented in C, nested within the Nit code.
+#   The body of these method is copied directly to the generated C files for compilation.
+#   Also supports extern `new` factories.
+# * Module level **C code blocks**, both "C Body" (the default) and "C Header".
+#   They will be copied to the beginning of the generated C files.
+# * Automatic transformation of Nit **primitive types** from/to their equivalent in C.
+# * **Extern classes** to create a Nit class around a C pointer.
+#   Allows to specify the equivalent C type of the Nit extern class.
+#
+# These limited features should be easy to implement in new/alternative engines
+# to quickly achieve a bootstrap. For this reason, core features of the Nit
+# standard library should be limited to use the light FFI.
+module light_ffi
+
+import modelbuilder
+
+import nitni::nitni_utilities
+
+intrude import light_ffi_base
+import extern_classes
+import light_c
+
+redef class MModule
+       # Does this module uses the FFI?
+       var uses_ffi: Bool = false
+
+       # C compilation unit for the FFI files
+       private var ffi_ccu: nullable CCompilationUnit = null
+
+       # Foreign language used in this AModule
+       private var present_languages = new HashSet[FFILanguage]
+
+       # Complete the compilation of the FFI code
+       fun finalize_ffi_wrapper(compdir: String, mainmodule: MModule)
+       do
+               var cflags = self.cflags[""].join(" ")
+
+               ffi_ccu.write_as_impl(self, compdir)
+               for filename in ffi_ccu.files do
+                       var f = new ExternCFile(filename, cflags)
+                       f.pkgconfigs.add_all pkgconfigs
+                       ffi_files.add(f)
+               end
+       end
+
+       # Avoid the compile a ffi propdef more than once
+       # See `AMethPropdef::compile_ffi_method`
+       # FIXME find a better way
+       private var compiled_ffi_methods = new HashSet[AMethPropdef]
+end
+
+redef class AModule
+
+       # Ensures all of the general foreign code of the module has been analyzed.
+       # Manages header blocks, extern class types and foreign dependancies between modules
+       fun ensure_compile_ffi_wrapper
+       do
+               var mmodule = mmodule
+               if mmodule == null or mmodule.ffi_ccu != null then return
+
+               # ready extern code compiler
+               var ffi_ccu = new CCompilationUnit
+               mmodule.ffi_ccu = ffi_ccu
+
+               # generate code
+               for block in n_extern_code_blocks do
+                       var language = block.language
+                       assert language != null
+                       mmodule.present_languages.add(language)
+                       language.compile_module_block(block, ffi_ccu, mmodule)
+               end
+
+               ffi_ccu.header_c_base.add( "#include \"{mmodule.c_name}._nitni.h\"\n" )
+
+               ffi_ccu.body_decl.add("#ifdef ANDROID\n")
+               ffi_ccu.body_decl.add(" #include <android/log.h>\n")
+               ffi_ccu.body_decl.add(" #define PRINT_ERROR(...) (void)__android_log_print(ANDROID_LOG_WARN, \"Nit\", __VA_ARGS__)\n")
+               ffi_ccu.body_decl.add("#else\n")
+               ffi_ccu.body_decl.add(" #define PRINT_ERROR(...) fprintf(stderr, __VA_ARGS__)\n")
+               ffi_ccu.body_decl.add("#endif\n")
+
+               for nclassdef in n_classdefs do
+                       # Does it declares an extern type?
+                       if nclassdef isa AStdClassdef and nclassdef.n_extern_code_block != null then
+                               mmodule.uses_ffi = true
+                               var language = nclassdef.n_extern_code_block.language
+                               assert language != null
+                               mmodule.present_languages.add(language)
+                               nclassdef.n_extern_code_block.language.compile_extern_class(
+                                       nclassdef.n_extern_code_block.as(not null), nclassdef, ffi_ccu, mmodule)
+                       end
+               end
+       end
+end
+
+redef class AMethPropdef
+       # Compile the necessary wrapper around this extern method or constructor
+       fun compile_ffi_method(mmodule: MModule)
+       do
+               assert n_extern_code_block != null
+
+               if mmodule.compiled_ffi_methods.has(self) then return
+               mmodule.compiled_ffi_methods.add self
+
+               var language = n_extern_code_block.language
+               assert language != null
+               mmodule.present_languages.add(language)
+               n_extern_code_block.language.compile_extern_method(
+                       n_extern_code_block.as(not null), self, mmodule.ffi_ccu.as(not null), mmodule)
+       end
+end
diff --git a/src/ffi/light_ffi_base.nit b/src/ffi/light_ffi_base.nit
new file mode 100644 (file)
index 0000000..6791e5c
--- /dev/null
@@ -0,0 +1,214 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2012 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Tools and utilities for implement FFI with different languages
+module light_ffi_base
+
+import c_tools
+import parser
+import modelbuilder
+import nitni::nitni_utilities
+
+redef class ToolContext
+       # Phase that assign a `FFILanguage` to all `AExternCodeBlock`
+       var ffi_language_assignation_phase: Phase = new FFILanguageAssignationPhase(self, null)
+end
+
+# Phase that assign a `FFILanguage` to all `AExternCodeBlock`
+#
+# It will also report errors when using an unknown foreign langages.
+class FFILanguageAssignationPhase
+       super Phase
+
+       # All supported languages
+       var languages = new Array[FFILanguage]
+
+       redef fun process_nmodule(nmodule)
+       do
+               for block in nmodule.n_extern_code_blocks do
+                       verify_foreign_code_on_node( block )
+               end
+       end
+
+       redef fun process_npropdef(npropdef)
+       do
+               if npropdef isa AMethPropdef then
+                       var code_block = npropdef.n_extern_code_block
+                       if code_block != null then
+                               verify_foreign_code_on_node( code_block )
+                       end
+               end
+       end
+
+       redef fun process_nclassdef(nclassdef)
+       do
+               if nclassdef isa AStdClassdef and nclassdef.n_extern_code_block != null then
+                       verify_foreign_code_on_node( nclassdef.n_extern_code_block.as(not null) )
+               end
+       end
+
+       private fun verify_foreign_code_on_node(n: AExternCodeBlock)
+       do
+               var found = false
+               for v in languages do
+                       var identified = v.identify_language(n)
+                       if identified then
+                               if found and identified then
+                                       toolcontext.error(n.location, "FFI Error: two languages identified as possible handlers.")
+                               end
+                               n.language = v
+                               found = true
+                       end
+               end
+
+               if not found then toolcontext.error(n.location, "FFI Error: unsupported language.")
+       end
+end
+
+redef class MModule
+       # All FFI files linked to this module
+       var ffi_files = new Array[ExternFile]
+end
+
+redef class AExternCodeBlock
+       # User entered name for the language of this block
+       fun language_name: nullable String do
+               if n_in_language == null then return null
+               return n_in_language.n_string.without_quotes
+       end
+
+       # `language_name`, in lower case
+       protected fun language_name_lowered: nullable String do
+               if language_name == null then return null
+               return language_name.to_lower
+       end
+
+       # User entered foreign code in the block
+       fun code: String do return n_extern_code_segment.without_guard
+
+       # `FFILanguage` assigned to this block
+       var language: nullable FFILanguage = null
+end
+
+# Visitor for a specific languages. Works kinda like a `Phase` and is executed
+# by a `Phase`.
+class FFILanguage
+       # `FFILanguageAssignationPhase` assigning `self` to `AExternCodeBlock`s
+       var ffi_language_assignation_phase: FFILanguageAssignationPhase
+
+       init
+       do
+               ffi_language_assignation_phase.languages.add(self)
+       end
+
+       # Is this `block` written in this language?
+       fun identify_language(block: AExternCodeBlock ): Bool is abstract
+
+       # Generate wrapper code for this module/header code block
+       fun compile_module_block(block: AExternCodeBlock, ecc: CCompilationUnit, mmodule: MModule) is abstract
+
+       # Generate wrapper code for this extern method
+       fun compile_extern_method(block: AExternCodeBlock, m: AMethPropdef,
+               ecc: CCompilationUnit, nmodule: MModule) is abstract
+
+       # Generate wrapper code for this extern class
+       fun compile_extern_class(block: AExternCodeBlock, m: AClassdef,
+               ecc: CCompilationUnit, mmodule: MModule) is abstract
+
+       # Get the foreign type of this extern class definition
+       fun get_ftype(block: AExternCodeBlock, m: AClassdef): ForeignType is abstract
+
+       # Complete compilation of generated code
+       fun compile_to_files(mmodule: MModule, directory: String) do end
+end
+
+redef class TString
+       # Returns the content of this node without both of the surrounding "
+       fun without_quotes: String
+       do
+               assert text.length >= 2
+               return text.substring(1, text.length-2)
+       end
+end
+
+redef class TExternCodeSegment
+       # Returns the content of this node without the surrounding `{ and `}
+       fun without_guard: String
+       do
+               assert text.length >= 4
+               return text.substring(2, text.length-4)
+       end
+end
+
+redef class CCompilationUnit
+       # Compile as `_ffi` files which contains the implementation of extern methods
+       fun write_as_impl(mmodule: MModule, compdir: String)
+       do
+               var base_name = "{mmodule.c_name}._ffi"
+
+               var h_file = "{base_name}.h"
+               var guard = "{mmodule.c_name.to_upper}_NIT_H"
+               write_header_to_file(mmodule, "{compdir}/{h_file}", new Array[String], guard)
+
+               var c_file = "{base_name}.c"
+               write_body_to_file(mmodule, "{compdir}/{c_file}", ["<stdlib.h>", "<stdio.h>", "\"{h_file}\""])
+
+               files.add( "{compdir}/{c_file}" )
+       end
+
+       # Write the header part to `file` including all `includes` using the `guard`
+       fun write_header_to_file(mmodule: MModule, file: String, includes: Array[String], guard: String)
+       do
+               var stream = new FileWriter.open( file )
+
+               # header comments
+               var module_info = "/*\n\tExtern implementation of Nit module {mmodule.name}\n*/\n"
+
+               stream.write( module_info )
+
+               stream.write( "#ifndef {guard}\n" )
+               stream.write( "#define {guard}\n\n" )
+
+               for incl in includes do stream.write( "#include {incl}\n" )
+
+               compile_header_core( stream )
+
+               # header file guard close
+               stream.write( "#endif\n" )
+               stream.close
+       end
+
+       # Write the body part to `file` including all `includes`
+       fun write_body_to_file(mmodule: MModule, file: String, includes: Array[String])
+       do
+               var stream = new FileWriter.open(file)
+
+               var module_info = "/*\n\tExtern implementation of Nit module {mmodule.name}\n*/\n"
+
+               stream.write( module_info )
+               for incl in includes do stream.write( "#include {incl}\n" )
+
+               compile_body_core( stream )
+
+               stream.close
+       end
+end
+
+# Foreign equivalent types of extern classes
+class ForeignType
+       # C type of `self`, by default it is `void*`
+       fun ctype: String do return "void*"
+end
index bf12e9d..18a0f1f 100644 (file)
@@ -23,6 +23,8 @@ import transform
 import rapid_type_analysis
 import compiler::separate_erasure_compiler
 
+import compiler::compiler_ffi::light_only
+
 redef class ToolContext
        redef fun process_options(args)
        do
index 616d4ea..a1f49a7 100644 (file)
@@ -98,7 +98,7 @@ redef class MClassType
                        assert ctype != null
                        return ctype
                end
-               return mangled_cname
+               return cname_normal_class
        end
 
        redef fun cname_blind do
@@ -112,6 +112,9 @@ redef class MClassType
                return "struct nitni_instance *"
        end
 
+       # Name of this type in C for normal classes (not extern and not primitive)
+       protected fun cname_normal_class: String do return mangled_cname
+
        redef fun mangled_cname do return mclass.name
 
        redef fun is_cprimitive do return mclass.kind == extern_kind or