From: Alexis Laferrière Date: Thu, 28 Nov 2013 19:19:04 +0000 (-0500) Subject: nitg: intro the common_ffi module and support for the C language X-Git-Tag: v0.6.4~24^2~4 X-Git-Url: http://nitlanguage.org nitg: intro the common_ffi module and support for the C language Signed-off-by: Alexis Laferrière --- diff --git a/src/c_tools.nit b/src/c_tools.nit index 663a059..e05f4bf 100644 --- a/src/c_tools.nit +++ b/src/c_tools.nit @@ -45,7 +45,7 @@ class CCompilationUnit var body_impl : Writer = new Writer # files to compile TODO check is appropriate - #var files = new Array[String] + var files = new Array[String] fun add_local_function( efc : CFunction ) do diff --git a/src/common_ffi/c.nit b/src/common_ffi/c.nit new file mode 100644 index 0000000..661928c --- /dev/null +++ b/src/common_ffi/c.nit @@ -0,0 +1,167 @@ +# 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 c + +import 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, nmodule) + do + if block.is_c_header then + ecc.header_custom.add( block.location.as_line_pragma ) + ecc.header_custom.add( block.code ) + else if block.is_c_body then + ecc.body_custom.add( block.location.as_line_pragma ) + ecc.body_impl.add( block.code ) + end + end + + redef fun compile_extern_method(block, m, ecc, nmodule) + do + var fc = new ExternCFunction(m, nmodule.mmodule.as(not null)) + 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, nmodule) do end + + redef fun get_ftype(block, m) do return new ForeignCType(block.code) + + redef fun compile_callback(callback, nmodule, mmodule, ecc) + do + callback.compile_callback_to_c(mmodule, 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} \"{file.filename}\"\n" +end + +redef class AModule + var c_compiler_options writable = "" + var c_linker_options writable = "" +end + +# An extern C file to compile +class ExternCFile + super ExternFile + + init (filename, cflags: String) + do + super filename + + self.cflags = cflags + end + + # Additionnal specific CC compiler -c flags + var cflags: String + + redef fun hash do return filename.hash + redef fun ==(o) do return o isa ExternCFile and filename == o.filename +end + +class ForeignCType + super ForeignType + + redef var ctype: String + + init(ctype: String) + do + self.ctype = ctype + end +end + +redef class NitniCallback + fun compile_callback_to_c(nmodule: 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 + var mproperty = mproperty.as(MMethod) + + var full_cname = mproperty.build_cname(recv_mtype, mmodule, null, long_signature) + var friendly_cname = mproperty.build_cname(recv_mtype, mmodule, null, short_signature) + 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 + + private init do end + + 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 + + private init do end + + redef fun name_mtype(mtype) do return mtype.cname +end + +class ExternCFunction + super CFunction + + var method: AExternPropdef + + init (method: AExternPropdef, 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/common_ffi/common_ffi.nit b/src/common_ffi/common_ffi.nit new file mode 100644 index 0000000..24cbcda --- /dev/null +++ b/src/common_ffi/common_ffi.nit @@ -0,0 +1,144 @@ +# 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. + +# 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. +module common_ffi + +import parser +import modelbuilder + +import nitni + +import ffi_base +import extern_classes +import header_dependency +import c + +redef class MModule + # Does this module uses the FFI? + var uses_ffi: Bool = false +end + +redef class AModule + # 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] + + # Callbacks used locally + var ffi_callbacks = new HashMap[FFILanguage, Set[NitniCallback]] + + # 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 + if ffi_ccu != null then return + + # ready extern code compiler + var ffi_ccu = new CCompilationUnit + self.ffi_ccu = ffi_ccu + + # generate code + for block in n_extern_code_blocks do + var language = block.language + assert language != null + present_languages.add(language) + language.compile_module_block(block, ffi_ccu, self) + end + + ffi_ccu.header_c_base.add( "#include \"{mmodule.name}._nitni.h\"\n" ) + + # include dependancies FFI + for mod in mmodule.header_dependencies do + if mod.uses_ffi then ffi_ccu.header_custom.add("#include \"{mod.name}._ffi.h\"\n") + end + + 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 + present_languages.add(language) + nclassdef.n_extern_code_block.language.compile_extern_class( + nclassdef.n_extern_code_block.as(not null), nclassdef, ffi_ccu, self) + end + end + end + + # Complete the compilation of the FFI code + fun finalize_ffi_wrapper(compdir: String, mainmodule: MModule) + do + ensure_compile_ffi_wrapper + + for language in present_languages do if ffi_callbacks.keys.has(language) then + for callback in ffi_callbacks[language] do + language.compile_callback(callback, self, mainmodule, ffi_ccu.as(not null)) + end + + language.compile_to_files(self, compdir) + end + + ffi_ccu.write_as_impl(self, compdir) + for filename in ffi_ccu.files do ffi_files.add(new ExternCFile(filename, self.c_compiler_options)) + end +end + +redef class AExternPropdef + private var ffi_has_been_compiled = false + + # Compile the necessary wrapper around this extern method or constructor + fun compile_ffi_method(amodule: AModule) + do + assert n_extern_code_block != null + + if ffi_has_been_compiled then return + ffi_has_been_compiled = true + + amodule.ensure_compile_ffi_wrapper + + var language = n_extern_code_block.language + assert language != null + amodule.present_languages.add(language) + n_extern_code_block.language.compile_extern_method( + n_extern_code_block.as(not null), self, amodule.ffi_ccu.as(not null), amodule) + end +end + +redef class VerifyNitniCallbacksPhase + redef fun process_npropdef(npropdef) + do + super + + if not npropdef isa AExternPropdef then return + + var code_block = npropdef.n_extern_code_block + if code_block == null then return + + var lang = code_block.language + assert lang != null + + # Associate callbacks used by an extern method to its foreign language + for callback in npropdef.foreign_callbacks.all do + var map = npropdef.parent.parent.as(AModule).ffi_callbacks + if not map.keys.has(lang) then map[lang] = new HashSet[NitniCallback] + map[lang].add(callback) + end + end +end diff --git a/src/common_ffi/extern_classes.nit b/src/common_ffi/extern_classes.nit new file mode 100644 index 0000000..0ab7b96 --- /dev/null +++ b/src/common_ffi/extern_classes.nit @@ -0,0 +1,134 @@ +# 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. + +# Manages all extern classes and their associated foreign type. +module extern_classes + +import ffi_base + +redef class ToolContext + var extern_classes_typing_phase_ast: Phase = new ExternClassesTypingPhaseAst(self, [ffi_language_assignation_phase]) + + var extern_classes_typing_phase_model: Phase = new ExternClassesTypingPhaseModel(self, [extern_classes_typing_phase_ast, modelize_class_phase]) +end + +# Assigns the `ftype` to class definitions, work on the AST only +private class ExternClassesTypingPhaseAst + super Phase + + redef fun process_nclassdef(nclassdef) + do + if not nclassdef isa AStdClassdef then return + + var code_block = nclassdef.n_extern_code_block + if code_block == null then return + + if nclassdef.n_kwredef != null then + # A redef cannot specifiy a different extern type + toolcontext.error(nclassdef.location, "Only the introduction of a class can specify an extern type.") + return + end + + var ftype = code_block.language.get_ftype(code_block, nclassdef) + nclassdef.ftype_cache = ftype + nclassdef.ftype_computed = true + end +end + +redef class AClassdef + private var ftype_cache: nullable ForeignType = null + private var ftype_computed = false + + # Associated extern type when defined on this classdef + fun ftype: nullable ForeignType + do + return ftype_cache + end +end + +private class ExternClassesTypingPhaseModel + super Phase + + redef fun process_nclassdef(nclassdef) + do + if not nclassdef isa AStdClassdef then return + + var mclassdef = nclassdef.mclassdef + var mclass = nclassdef.mclass + + # We only need to do this once per class + if mclass.intro != mclassdef then return + + if mclass.kind != extern_kind then return + + mclass.compute_ftype(self) + end +end + +redef class MClass + private var ftype_cache: nullable ForeignType = null + private var ftype_computed = false + + # Extern type associated to this class according to specialisation + fun ftype: nullable ForeignType do return ftype_cache + + redef fun ctype do return ftype_cache.ctype + + # Type in C of the extern class + # If not defined in the intro of this class will look in super-classes + # FIXME this only supports type definition at intro, extend to superclasses by redef + private fun compute_ftype(v: ExternClassesTypingPhaseModel): nullable ForeignType + do + if ftype_computed then return ftype_cache + if kind != extern_kind then return null + + # base case + if name == "Pointer" then + ftype_cache = new ForeignType + ftype_computed = true + return ftype_cache + end + + var intro_nclassdef = v.toolcontext.modelbuilder.mclassdef2nclassdef[intro] + var ftype = intro_nclassdef.ftype + if ftype == null then + var ftype_b: nullable ForeignType = null # FIXME hack to circumvent bug where ftype is typed null + + # look in super classes + for s in in_hierarchy(intro.mmodule).direct_greaters do + var super_ftype = s.compute_ftype(v) + if super_ftype != null then + if ftype_b == null then + ftype_b = super_ftype + continue + else + # detect conflict + if super_ftype != ftype_b then + v.toolcontext.error(null, "Extern type conflict in {self}") + return null + end + end + end + end + + ftype = ftype_b + end + + ftype_cache = ftype + ftype_computed = true + return ftype + end +end diff --git a/src/common_ffi/ffi_base.nit b/src/common_ffi/ffi_base.nit new file mode 100644 index 0000000..86f53ea --- /dev/null +++ b/src/common_ffi/ffi_base.nit @@ -0,0 +1,209 @@ +# 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 language targetted FFI implementations +module ffi_base + +import c_tools +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 AExternPropdef 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, "Two languages identified as possible handlers.") + end + n.language = v + found = true + end + end + + if not found then toolcontext.error(n.location, "Unsupported language.") + end +end + +redef class AModule + var ffi_files = new Array[ExternFile] +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 + init(ffi_language_assignation_phase: FFILanguageAssignationPhase) + 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, nmodule: AModule) is abstract + + # Generate wrapper code for this extern method + fun compile_extern_method(block: AExternCodeBlock, m: AExternPropdef, + ecc: CCompilationUnit, nmodule: AModule) is abstract + + # Generate wrapper code for this extern class + fun compile_extern_class(block: AExternCodeBlock, m: AClassdef, + ecc: CCompilationUnit, nmodule: AModule) is abstract + + # Get the foreign type of this extern class definition + fun get_ftype(block: AExternCodeBlock, m: AClassdef): ForeignType is abstract + + # Generate the code to offer this callback if foreign code + fun compile_callback(callback: NitniCallback, nmodule: AModule, + mainmmodule: MModule, ecc: CCompilationUnit) is abstract + + # Complete compilation of generated code + fun compile_to_files(amodule: AModule, 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 + +# An extern file to compile +class ExternFile + # The filename of the file + var filename: String + + fun makefile_rule_name: String is abstract + fun makefile_rule_content: String is abstract +end + +redef class CCompilationUnit + fun write_as_impl( amodule: AModule, compdir: String ) + do + var base_name = "{amodule.mmodule.name}._ffi" + + var h_file = "{base_name}.h" + var guard = "{amodule.cname.to_s.to_upper}_NIT_H" + write_header_to_file( amodule, "{compdir}/{h_file}", new Array[String], guard) + + var c_file = "{base_name}.c" + write_body_to_file( amodule, "{compdir}/{c_file}", ["", "", "\"{h_file}\""] ) + + files.add( "{compdir}/{c_file}" ) + end + + fun write_header_to_file(amodule: AModule, file: String, includes: Array[String], guard: String) + do + var stream = new OFStream.open( file ) + + # header comments + var module_info = "/*\n\tExtern implementation of Nit module {amodule.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(amodule: AModule, file: String, includes: Array[String]) + do + var stream = new OFStream.open(file) + + var module_info = "/*\n\tExtern implementation of Nit module {amodule.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/common_ffi/header_dependency.nit b/src/common_ffi/header_dependency.nit new file mode 100644 index 0000000..de247e5 --- /dev/null +++ b/src/common_ffi/header_dependency.nit @@ -0,0 +1,77 @@ +# 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. + +# Tracks which modules has public header code that must be imported +# by user modules. +module header_dependency + +import ffi_base +import c + +redef class ToolContext + var header_dependancy_phase: Phase = new HeaderDependancyPhase(self, [ffi_language_assignation_phase, modelize_class_phase]) +end + +redef class AModule + private fun has_public_c_header: Bool do + for code_block in n_extern_code_blocks do if code_block.is_c_header then return true + return false + end +end + +redef class MModule + private var header_dependencies_cache: nullable Array[MModule] = null + fun header_dependencies: Array[MModule] + do + var cache = header_dependencies_cache + assert cache != null + return cache + end + + private fun compute_header_dependencies(v: HeaderDependancyPhase) + do + if header_dependencies_cache != null then return + + var header_dependencies = new Array[MModule] + + # gather from importation + for m in in_importation.direct_greaters do + m.compute_header_dependencies(v) + + # does the super module has inherited dependancies? + var hd = m.header_dependencies + if not hd.is_empty then + header_dependencies.add_all(hd) + end + + # does the super module itself has extern dependancies? + var amodule = v.toolcontext.modelbuilder.mmodule2nmodule[m] + if amodule.has_public_c_header then header_dependencies.add(m) + end + + header_dependencies_cache = header_dependencies + end +end + +private class HeaderDependancyPhase + super Phase + + redef fun process_nmodule(nmodule) + do + var mmodule = nmodule.mmodule + mmodule.compute_header_dependencies(self) + end +end