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
23 import debugger_socket
# To linearize `ToolContext::init`
25 redef class ToolContext
28 var opt_compile_dir
= new OptionString("Directory used to generate temporary files", "--compile-dir")
30 init do option_context
.add_option opt_compile_dir
33 redef class AMethPropdef
34 # Does this method definition use the FFI and is it supported by the interpreter?
36 # * Must use the nested foreign code block of the FFI.
37 # * Must not have callbacks.
38 # * Must be implemented in C.
39 fun supported_by_dynamic_ffi
: Bool
41 var n_extern_code_block
= n_extern_code_block
42 if not (n_extern_calls
== null and n_extern_code_block
!= null and
43 n_extern_code_block
.is_c
) then return false
45 for mparam
in mpropdef
.msignature
.mparameters
do
46 var mtype
= mparam
.mtype
47 if not mtype
.is_cprimitive
then
56 redef class NaiveInterpreter
57 redef fun start
(mainmodule
)
61 # Delete temporary files
62 var compile_dir
= compile_dir
63 if compile_dir
.file_exists
then compile_dir
.rmdir
66 # Where to store generated C and extracted code
67 private var compile_dir
: String is lazy
do
68 # Prioritize the user supplied directory
69 var opt
= modelbuilder
.toolcontext
.opt_compile_dir
.value
70 if opt
!= null then return opt
71 return "/tmp/niti_ffi_{process_id}"
74 # Identifier for this process, unique between running interpreters
75 private fun process_id
: Int `{ return getpid(); `}
77 # Path of the compiled foreign code library
79 # TODO change the ".so" extension per platform.
80 fun foreign_code_lib_path(mmodule: MModule): String
82 return compile_dir / mmodule.c_name + ".so"
85 # External compiler used to generate the foreign code library
86 private var c_compiler = "gcc"
91 # Compile user FFI code and a standardized API into a `.so
` file
93 # Returns `true` on success.
94 fun compile_foreign_lib(v: NaiveInterpreter): Bool
97 assert mmodule != null
99 var compile_dir = v.compile_dir
100 var foreign_code_lib_path = v.foreign_code_lib_path(mmodule)
102 if not compile_dir.file_exists then compile_dir.mkdir
104 # Compile the common FFI part
105 ensure_compile_ffi_wrapper
106 for mclassdef in mmodule.mclassdefs do for mpropdef in mclassdef.mpropdefs do
107 var anode = v.modelbuilder.mpropdef2node(mpropdef)
108 if mpropdef isa MMethodDef and anode isa AMethPropdef and anode.supported_by_dynamic_ffi then
109 anode.compile_ffi_method(mmodule)
112 mmodule.finalize_ffi_wrapper(compile_dir, mmodule)
114 # Compile the standard API and its implementation for the .so file
115 var ccu = compile_foreign_lib_api(compile_dir)
117 var srcs = [for file in ccu.files do new ExternCFile(file, ""): ExternFile]
118 srcs.add_all mmodule.ffi_files
120 if mmodule.pkgconfigs.not_empty then
121 fatal(v, "NOT YET IMPLEMENTED annotation `pkgconfig
`")
125 var ldflags = mmodule.ldflags[""].join(" ")
128 # Compile each source file to an object file (or equivalent)
129 var object_files = new Array[String]
131 f.compile(v, mmodule, object_files)
134 # Link everything in a shared library
135 # TODO customize the compiler
136 var cmd = "{v.c_compiler} -Wall -shared -o {foreign_code_lib_path} {object_files.join(" ")} {ldflags}"
137 if sys.system(cmd) != 0 then
138 v.fatal "FFI Error: Failed to link native code using `{cmd}`"
145 # Compile the standard API of the `.so
` file
147 # * The shared structure `nit_call_arg
`.
148 # * Standardized implementation functions entrypoints that relay calls
149 # to the FFI implementation functions.
150 private fun compile_foreign_lib_api(compdir: String): CCompilationUnit
152 var mmodule = mmodule
153 assert mmodule != null
155 # ready extern code compiler
156 var ecc = new CCompilationUnit
158 ecc.body_decl.add """
162 #include <inttypes.h>
164 // C structure behind `CallArg` from the interpreter
165 typedef union nit_call_arg {
172 uint16_t value_UInt16;
174 uint32_t value_UInt32;
182 var used_types = collect_mtypes
183 for t in used_types do
184 if not t.is_cprimitive then
185 ecc.header_c_types.add "typedef void* {t.cname};\n"
189 # TODO callbacks & casts
191 for nclassdef in n_classdefs do for npropdef in nclassdef.n_propdefs do
192 if npropdef isa AMethPropdef and npropdef.supported_by_dynamic_ffi then
193 npropdef.mpropdef.compile_foreign_code_entry ecc
197 ecc.write_as_foreign_lib_api(mmodule, compdir)
202 # Collect all `MType` use in extern methods within this module
203 private fun collect_mtypes: Set[MType]
205 var used_types = new HashSet[MType]
208 for nclassdef in n_classdefs do for npropdef in nclassdef.n_propdefs do
209 if npropdef isa AMethPropdef and npropdef.supported_by_dynamic_ffi then
210 var fcs = npropdef.foreign_callbacks
211 used_types.add_all fcs.types
219 redef class CCompilationUnit
220 # Write this compilation unit as the API of a foreign code library
221 private fun write_as_foreign_lib_api(mmodule: MModule, compdir: String)
223 # The FFI expects the support header to end with `._nitni
.h
`
224 var base_name = mmodule.c_name + "._nitni"
225 var guard = mmodule.c_name.to_s.to_upper + "_API_H"
226 var header_comment = """
228 Public API to foreign code of the Nit module {{{mmodule.name}}}
233 var h_file = base_name+".h"
234 var stream = new FileWriter.open(compdir/h_file)
235 stream.write header_comment
240 compile_header_core stream
248 var c_file = base_name+".c"
249 stream = new FileWriter.open(compdir/c_file)
250 stream.write header_comment
252 #include "{{{h_file}}}"
254 compile_body_core stream
257 # Only the C files needs compiling
258 files.add compdir / c_file
262 redef class MMethodDef
263 # Name of the entry point to the implementation function in the foreign lib
264 fun foreign_lib_entry_cname: String do return "entry__{cname}"
266 # Compile the standardized entry point as part of the foreign lib API
267 private fun compile_foreign_code_entry(ecc: CCompilationUnit)
269 var msignature = msignature
270 if msignature == null then return
273 var return_mtype = msignature.return_mtype
274 if mproperty.is_init then return_mtype = mclassdef.mclass.mclass_type
277 if return_mtype != null then
278 c_return_type = return_mtype.cname_blind
279 else c_return_type = "void"
281 var is_init = mproperty.is_init
284 var params = new Array[String]
285 if not is_init then params.add mclassdef.mclass.mclass_type.cname_blind
286 for param in msignature.mparameters do params.add param.mtype.cname_blind
288 # Declare the implementation function as extern
289 var impl_cname = mproperty.build_cname(mclassdef.bound_mtype,
290 mclassdef.mmodule, "___impl", long_signature)
291 ecc.body_decl.add "extern {c_return_type} {impl_cname}({params.join(", ")});\n"
293 # Declare the entry function
294 var foreign_lib_entry_cname = "int {foreign_lib_entry_cname}(int argc, nit_call_arg *argv, nit_call_arg *result)"
295 var fc = new CFunction(foreign_lib_entry_cname)
297 # Check argument count on the library side
299 # This may detect inconsistencies between the interpreter and the generated code.
300 var expected_argc = msignature.arity
301 if not is_init then expected_argc += 1
304 if (argc != {{{expected_argc}}}) {
305 printf("Invalid argument count in `{{{mproperty.full_name}}}`, expected %d, got %d.\\n",
306 argc, {{{expected_argc}}});
311 # Unpack and prepare args for the user code
313 var args_for_call = new Array[String]
315 var mtype = mclassdef.mclass.mclass_type
316 var arg_name = "arg___self"
318 fc.decls.add " {mtype.cname_blind} {arg_name};\n"
319 fc.exprs.add " {arg_name} = argv[{k}].{mtype.call_arg_field};\n"
320 args_for_call.add arg_name
324 for param in msignature.mparameters do
325 var mtype = param.mtype
326 var arg_name = "arg__"+param.name
328 fc.decls.add " {mtype.cname_blind} {arg_name};\n"
329 fc.exprs.add " {arg_name} = argv[{k}].{mtype.call_arg_field};\n"
330 args_for_call.add arg_name
335 # Call implementation function
336 var args_compressed = args_for_call.join(", ")
337 var method_call = "{impl_cname}({args_compressed})"
338 if return_mtype != null then
340 {{{return_mtype.cname_blind}}} return_value;
343 return_value = {{{method_call}}};
344 result->{{{return_mtype.call_arg_field}}} = return_value;
347 fc.exprs.add " {method_call};\n"
350 fc.exprs.add " return 0;\n"
352 ecc.add_exported_function fc
357 # The interpreter FFI use `void
*` to represent intern data types
358 redef fun cname_blind do return "void*"
360 # Field to store this type in the C structure `nit_call_arg
`
361 private fun call_arg_field: String do return "value_Pointer"
364 redef class MClassType
365 redef fun call_arg_field
367 if is_cprimitive and mclass.kind != extern_kind then
368 return "value_{name}"
373 redef class ExternFile
374 # Compile this source file
375 private fun compile(v: NaiveInterpreter, mmodule: MModule,
376 object_files: Array[String]): Bool is abstract
379 redef class ExternCFile
380 redef fun compile(v, mmodule, object_files)
382 var compile_dir = v.compile_dir
383 var cflags = mmodule.cflags[""].join(" ")
384 var obj = compile_dir / filename.basename(".c") + ".o"
386 var cmd = "{v.c_compiler} -Wall -c -fPIC -I {compile_dir} -g -o {obj} {filename} {cflags}"
387 if sys.system(cmd) != 0 then
388 v.fatal "FFI Error: Failed to compile C code using `{cmd}`"