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 -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 {
148 uint16_t value_UInt16;
150 uint32_t value_UInt32;
158 var used_types
= collect_mtypes
159 for t
in used_types
do
160 if not t
.is_cprimitive
then
161 ecc
.header_c_types
.add
"typedef void* {t.cname};\n"
165 # TODO callbacks & casts
167 for nclassdef
in n_classdefs
do for npropdef
in nclassdef
.n_propdefs
do
168 if npropdef
isa AMethPropdef and npropdef
.supported_by_dynamic_ffi
then
169 npropdef
.mpropdef
.compile_foreign_code_entry ecc
173 ecc
.write_as_foreign_lib_api
(mmodule
, compdir
)
178 # Collect all `MType` use in extern methods within this module
179 private fun collect_mtypes
: Set[MType]
181 var used_types
= new HashSet[MType]
184 for nclassdef
in n_classdefs
do for npropdef
in nclassdef
.n_propdefs
do
185 if npropdef
isa AMethPropdef and npropdef
.supported_by_dynamic_ffi
then
186 var fcs
= npropdef
.foreign_callbacks
187 used_types
.add_all fcs
.types
195 redef class CCompilationUnit
196 # Write this compilation unit as the API of a foreign code library
197 private fun write_as_foreign_lib_api
(mmodule
: MModule, compdir
: String)
199 # The FFI expects the support header to end with `._nitni.h`
200 var base_name
= mmodule
.c_name
+ "._nitni"
201 var guard
= mmodule
.c_name
.to_s
.to_upper
+ "_API_H"
202 var header_comment
= """
204 Public API to foreign code of the Nit module {{{mmodule.name}}}
209 var h_file
= base_name
+".h"
210 var stream
= new FileWriter.open
(compdir
/h_file
)
211 stream
.write header_comment
216 compile_header_core stream
224 var c_file
= base_name
+".c"
225 stream
= new FileWriter.open
(compdir
/c_file
)
226 stream
.write header_comment
228 #include "{{{h_file}}}"
230 compile_body_core stream
233 # Only the C files needs compiling
234 files
.add compdir
/ c_file
238 redef class MMethodDef
239 # Name of the entry point to the implementation function in the foreign lib
240 fun foreign_lib_entry_cname
: String do return "entry__{cname}"
242 # Compile the standardized entry point as part of the foreign lib API
243 private fun compile_foreign_code_entry
(ecc
: CCompilationUnit)
245 var msignature
= msignature
246 if msignature
== null then return
249 var return_mtype
= msignature
.return_mtype
250 if mproperty
.is_init
then return_mtype
= mclassdef
.mclass
.mclass_type
253 if return_mtype
!= null then
254 c_return_type
= return_mtype
.cname_blind
255 else c_return_type
= "void"
257 var is_init
= mproperty
.is_init
260 var params
= new Array[String]
261 if not is_init
then params
.add mclassdef
.mclass
.mclass_type
.cname_blind
262 for param
in msignature
.mparameters
do params
.add param
.mtype
.cname_blind
264 # Declare the implementation function as extern
265 var impl_cname
= mproperty
.build_cname
(mclassdef
.mclass
.mclass_type
,
266 mclassdef
.mmodule
, "___impl", long_signature
)
267 ecc
.body_decl
.add
"extern {c_return_type} {impl_cname}({params.join(", ")});\n"
269 # Declare the entry function
270 var foreign_lib_entry_cname
= "int {foreign_lib_entry_cname}(int argc, nit_call_arg *argv, nit_call_arg *result)"
271 var fc
= new CFunction(foreign_lib_entry_cname
)
273 # Check argument count on the library side
275 # This may detect inconsistencies between the interpreter and the generated code.
276 var expected_argc
= msignature
.arity
277 if not is_init
then expected_argc
+= 1
280 if (argc != {{{expected_argc}}}) {
281 printf("Invalid argument count in `{{{mproperty.full_name}}}`, expected %d, got %d.\\n",
282 argc, {{{expected_argc}}});
287 # Unpack and prepare args for the user code
289 var args_for_call
= new Array[String]
291 var mtype
= mclassdef
.mclass
.mclass_type
292 var arg_name
= "arg___self"
294 fc
.decls
.add
" {mtype.cname_blind} {arg_name};\n"
295 fc
.exprs
.add
" {arg_name} = argv[{k}].{mtype.call_arg_field};\n"
296 args_for_call
.add arg_name
300 for param
in msignature
.mparameters
do
301 var mtype
= param
.mtype
302 var arg_name
= "arg__"+param
.name
304 fc
.decls
.add
" {mtype.cname_blind} {arg_name};\n"
305 fc
.exprs
.add
" {arg_name} = argv[{k}].{mtype.call_arg_field};\n"
306 args_for_call
.add arg_name
311 # Call implementation function
312 var args_compressed
= args_for_call
.join
(", ")
313 var method_call
= "{impl_cname}({args_compressed})"
314 if return_mtype
!= null then
316 {{{return_mtype.cname_blind}}} return_value;
319 return_value = {{{method_call}}};
320 result->{{{return_mtype.call_arg_field}}} = return_value;
323 fc
.exprs
.add
" {method_call};\n"
326 fc
.exprs
.add
" return 0;\n"
328 ecc
.add_exported_function fc
333 # The interpreter FFI use `void*` to represent intern data types
334 redef fun cname_blind
do return "void*"
336 # Field to store this type in the C structure `nit_call_arg`
337 private fun call_arg_field
: String do return "value_Pointer"
340 redef class MClassType
341 redef fun call_arg_field
343 if is_cprimitive
and mclass
.kind
!= extern_kind
then
344 return "value_{name}"
349 redef class ExternFile
350 # Compile this source file
351 private fun compile
(v
: NaiveInterpreter, mmodule
: MModule,
352 object_files
: Array[String]): Bool is abstract
355 redef class ExternCFile
356 redef fun compile
(v
, mmodule
, object_files
)
358 var compile_dir
= v
.compile_dir
359 var cflags
= mmodule
.cflags
[""].join
(" ")
360 var obj
= compile_dir
/ filename
.basename
(".c") + ".o"
362 var cmd
= "{v.c_compiler} -Wall -c -fPIC -I {compile_dir} -g -o {obj} {filename} {cflags}"
363 if sys
.system
(cmd
) != 0 then
364 v
.fatal
"FFI Error: Failed to compile C code using `{cmd}`"