1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Compiles extern code within a module to a static library, as needed
16 module on_demand_compiler
22 import naive_interpreter
24 redef class AMethPropdef
25 # Does this method definition use the FFI and is it supported by the interpreter?
27 # * Must use the nested foreign code block of the FFI.
28 # * Must not have callbacks.
29 # * Must be implemented in C.
30 fun supported_by_dynamic_ffi
: Bool
32 var n_extern_code_block
= n_extern_code_block
33 if not (n_extern_calls
== null and n_extern_code_block
!= null and
34 n_extern_code_block
.is_c
) then return false
36 for mparam
in mpropdef
.msignature
.mparameters
do
37 var mtype
= mparam
.mtype
38 if not mtype
.is_cprimitive
then
47 redef class NaiveInterpreter
48 # Where to store generated C and extracted code
50 # TODO make customizable and delete when execution completes
51 private var compile_dir
= "nit_compile"
53 # Path of the compiled foreign code library
55 # TODO change the ".so" extension per platform.
56 fun foreign_code_lib_path
(mmodule
: MModule): String
58 return compile_dir
/ mmodule
.c_name
+ ".so"
61 # External compiler used to generate the foreign code library
62 private var c_compiler
= "gcc"
67 # Compile user FFI code and a standardized API into a `.so` file
69 # Returns `true` on success.
70 fun compile_foreign_lib
(v
: NaiveInterpreter): Bool
73 assert mmodule
!= null
75 var compile_dir
= v
.compile_dir
76 var foreign_code_lib_path
= v
.foreign_code_lib_path
(mmodule
)
78 if not compile_dir
.file_exists
then compile_dir
.mkdir
80 # Compile the common FFI part
81 ensure_compile_ffi_wrapper
82 for mclassdef
in mmodule
.mclassdefs
do for mpropdef
in mclassdef
.mpropdefs
do
83 var anode
= v
.modelbuilder
.mpropdef2node
(mpropdef
)
84 if mpropdef
isa MMethodDef and anode
isa AMethPropdef and anode
.supported_by_dynamic_ffi
then
85 anode
.compile_ffi_method
(mmodule
)
88 mmodule
.finalize_ffi_wrapper
(compile_dir
, mmodule
)
90 # Compile the standard API and its implementation for the .so file
91 var ccu
= compile_foreign_lib_api
(compile_dir
)
93 var srcs
= [for file
in ccu
.files
do new ExternCFile(file
, ""): ExternFile]
94 srcs
.add_all mmodule
.ffi_files
96 if mmodule
.pkgconfigs
.not_empty
then
97 fatal
(v
, "NOT YET IMPLEMENTED annotation `pkgconfig`")
101 var ldflags
= mmodule
.ldflags
[""].join
(" ")
104 # Compile each source file to an object file (or equivalent)
105 var object_files
= new Array[String]
107 f
.compile
(v
, mmodule
, object_files
)
110 # Link everything in a shared library
111 # TODO customize the compiler
112 var cmd
= "{v.c_compiler} -Wall -shared -Wl,-soname,{mmodule.name}.so -g -o {foreign_code_lib_path} {object_files.join(" ")} {ldflags}"
113 if sys
.system
(cmd
) != 0 then
114 v
.fatal
"FFI Error: Failed to link native code using `{cmd}`"
121 # Compile the standard API of the `.so` file
123 # * The shared structure `nit_call_arg`.
124 # * Standardized implementation functions entrypoints that relay calls
125 # to the FFI implementation functions.
126 private fun compile_foreign_lib_api
(compdir
: String): CCompilationUnit
128 var mmodule
= mmodule
129 assert mmodule
!= null
131 # ready extern code compiler
132 var ecc
= new CCompilationUnit
134 ecc
.body_decl
.add
"""
138 #include <inttypes.h>
140 // C structure behind `CallArg` from the interpreter
141 typedef union nit_call_arg {
153 var used_types
= collect_mtypes
154 for t
in used_types
do
155 if not t
.is_cprimitive
then
156 ecc
.header_c_types
.add
"typedef void* {t.cname};\n"
160 # TODO callbacks & casts
162 for nclassdef
in n_classdefs
do for npropdef
in nclassdef
.n_propdefs
do
163 if npropdef
isa AMethPropdef and npropdef
.supported_by_dynamic_ffi
then
164 npropdef
.mpropdef
.compile_foreign_code_entry ecc
168 ecc
.write_as_foreign_lib_api
(mmodule
, compdir
)
173 # Collect all `MType` use in extern methods within this module
174 private fun collect_mtypes
: Set[MType]
176 var used_types
= new HashSet[MType]
179 for nclassdef
in n_classdefs
do for npropdef
in nclassdef
.n_propdefs
do
180 if npropdef
isa AMethPropdef and npropdef
.supported_by_dynamic_ffi
then
181 var fcs
= npropdef
.foreign_callbacks
182 used_types
.add_all fcs
.types
190 redef class CCompilationUnit
191 # Write this compilation unit as the API of a foreign code library
192 private fun write_as_foreign_lib_api
(mmodule
: MModule, compdir
: String)
194 # The FFI expects the support header to end with `._nitni.h`
195 var base_name
= mmodule
.c_name
+ "._nitni"
196 var guard
= mmodule
.c_name
.to_s
.to_upper
+ "_API_H"
197 var header_comment
= """
199 Public API to foreign code of the Nit module {{{mmodule.name}}}
204 var h_file
= base_name
+".h"
205 var stream
= new FileWriter.open
(compdir
/h_file
)
206 stream
.write header_comment
211 compile_header_core stream
219 var c_file
= base_name
+".c"
220 stream
= new FileWriter.open
(compdir
/c_file
)
221 stream
.write header_comment
223 #include "{{{h_file}}}"
225 compile_body_core stream
228 # Only the C files needs compiling
229 files
.add compdir
/ c_file
233 redef class MMethodDef
234 # Name of the entry point to the implementation function in the foreign lib
235 fun foreign_lib_entry_cname
: String do return "entry__{cname}"
237 # Compile the standardized entry point as part of the foreign lib API
238 private fun compile_foreign_code_entry
(ecc
: CCompilationUnit)
240 var msignature
= msignature
241 if msignature
== null then return
244 var return_mtype
= msignature
.return_mtype
245 if mproperty
.is_init
then return_mtype
= mclassdef
.mclass
.mclass_type
248 if return_mtype
!= null then
249 c_return_type
= return_mtype
.cname_blind
250 else c_return_type
= "void"
252 var is_init
= mproperty
.is_init
255 var params
= new Array[String]
256 if not is_init
then params
.add mclassdef
.mclass
.mclass_type
.cname_blind
257 for param
in msignature
.mparameters
do params
.add param
.mtype
.cname_blind
259 # Declare the implementation function as extern
260 var impl_cname
= mproperty
.build_cname
(mclassdef
.mclass
.mclass_type
,
261 mclassdef
.mmodule
, "___impl", long_signature
)
262 ecc
.body_decl
.add
"extern {c_return_type} {impl_cname}({params.join(", ")});\n"
264 # Declare the entry function
265 var foreign_lib_entry_cname
= "int {foreign_lib_entry_cname}(int argc, nit_call_arg *argv, nit_call_arg *result)"
266 var fc
= new CFunction(foreign_lib_entry_cname
)
268 # Check argument count on the library side
270 # This may detect inconsistencies between the interpreter and the generated code.
271 var expected_argc
= msignature
.arity
272 if not is_init
then expected_argc
+= 1
275 if (argc != {{{expected_argc}}}) {
276 printf("Invalid argument count in `{{{mproperty.full_name}}}`, expected %d, got %d.\\n",
277 argc, {{{expected_argc}}});
282 # Unpack and prepare args for the user code
284 var args_for_call
= new Array[String]
286 var mtype
= mclassdef
.mclass
.mclass_type
287 var arg_name
= "arg___self"
289 fc
.decls
.add
" {mtype.cname_blind} {arg_name};\n"
290 fc
.exprs
.add
" {arg_name} = argv[{k}].{mtype.call_arg_field};\n"
291 args_for_call
.add arg_name
295 for param
in msignature
.mparameters
do
296 var mtype
= param
.mtype
297 var arg_name
= "arg__"+param
.name
299 fc
.decls
.add
" {mtype.cname_blind} {arg_name};\n"
300 fc
.exprs
.add
" {arg_name} = argv[{k}].{mtype.call_arg_field};\n"
301 args_for_call
.add arg_name
306 # Call implementation function
307 var args_compressed
= args_for_call
.join
(", ")
308 var method_call
= "{impl_cname}({args_compressed})"
309 if return_mtype
!= null then
311 {{{return_mtype.cname_blind}}} return_value;
314 return_value = {{{method_call}}};
315 result->{{{return_mtype.call_arg_field}}} = return_value;
318 fc
.exprs
.add
" {method_call};\n"
321 fc
.exprs
.add
" return 0;\n"
323 ecc
.add_exported_function fc
328 # The interpreter FFI use `void*` to represent intern data types
329 redef fun cname_blind
do return "void*"
331 # Field to store this type in the C structure `nit_call_arg`
332 private fun call_arg_field
: String do return "value_Pointer"
335 redef class MClassType
336 redef fun call_arg_field
338 if is_cprimitive
and mclass
.kind
!= extern_kind
then
339 return "value_{name}"
344 redef class ExternFile
345 # Compile this source file
346 private fun compile
(v
: NaiveInterpreter, mmodule
: MModule,
347 object_files
: Array[String]): Bool is abstract
350 redef class ExternCFile
351 redef fun compile
(v
, mmodule
, object_files
)
353 var compile_dir
= v
.compile_dir
354 var cflags
= mmodule
.cflags
[""].join
(" ")
355 var obj
= compile_dir
/ filename
.basename
(".c") + ".o"
357 var cmd
= "{v.c_compiler} -Wall -c -fPIC -I {compile_dir} -g -o {obj} {filename} {cflags}"
358 if sys
.system
(cmd
) != 0 then
359 v
.fatal
"FFI Error: Failed to compile C code using `{cmd}`"