From: Jean Privat Date: Mon, 11 May 2015 23:22:31 +0000 (-0400) Subject: Merge: Intro the light FFI and use it in nith X-Git-Tag: v0.7.5~64 X-Git-Url: http://nitlanguage.org?hp=90770bf2f054980d2c8326ff04975ff44d911732 Merge: Intro the light FFI and use it in nith 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 Reviewed-by: Jean Privat Reviewed-by: Alexandre Terrasa --- diff --git a/src/compiler/compiler_ffi.nit b/src/compiler/compiler_ffi/compiler_ffi.nit similarity index 63% rename from src/compiler/compiler_ffi.nit rename to src/compiler/compiler_ffi/compiler_ffi.nit index 42423f9..8b8f9de 100644 --- a/src/compiler/compiler_ffi.nit +++ b/src/compiler/compiler_ffi/compiler_ffi.nit @@ -14,73 +14,25 @@ # 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 index 0000000..dbe819a --- /dev/null +++ b/src/compiler/compiler_ffi/light.nit @@ -0,0 +1,281 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2013-2015 Alexis Laferrière +# +# 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 index 0000000..3fc162d --- /dev/null +++ b/src/compiler/compiler_ffi/light_only.nit @@ -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 diff --git a/src/ffi/c.nit b/src/ffi/c.nit index 495d4e6..e09a80f 100644 --- a/src/ffi/c.nit +++ b/src/ffi/c.nit @@ -18,94 +18,19 @@ 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 diff --git a/src/ffi/extern_classes.nit b/src/ffi/extern_classes.nit index 6b53008..c0e826b 100644 --- a/src/ffi/extern_classes.nit +++ b/src/ffi/extern_classes.nit @@ -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]) diff --git a/src/ffi/ffi.nit b/src/ffi/ffi.nit index 93acf66..e58d32a 100644 --- a/src/ffi/ffi.nit +++ b/src/ffi/ffi.nit @@ -14,9 +14,28 @@ # 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 \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) diff --git a/src/ffi/ffi_base.nit b/src/ffi/ffi_base.nit index b8a8103..77a982d 100644 --- a/src/ffi/ffi_base.nit +++ b/src/ffi/ffi_base.nit @@ -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}", ["", "", "\"{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 index 0000000..86ec651 --- /dev/null +++ b/src/ffi/light_c.nit @@ -0,0 +1,138 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2012-2014 Alexis Laferrière +# +# 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 index 0000000..2245a92 --- /dev/null +++ b/src/ffi/light_ffi.nit @@ -0,0 +1,131 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2013 Alexis Laferrière +# +# 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 \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 index 0000000..6791e5c --- /dev/null +++ b/src/ffi/light_ffi_base.nit @@ -0,0 +1,214 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2012 Alexis Laferrière +# +# 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}", ["", "", "\"{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 diff --git a/src/nith.nit b/src/nith.nit index bf12e9d..18a0f1f 100644 --- a/src/nith.nit +++ b/src/nith.nit @@ -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 diff --git a/src/nitni/nitni_base.nit b/src/nitni/nitni_base.nit index 616d4ea..a1f49a7 100644 --- a/src/nitni/nitni_base.nit +++ b/src/nitni/nitni_base.nit @@ -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