5953647e8bf2d1f8f1f206f623bbde797116ba05
[nit.git] / src / interpreter / dynamic_loading_ffi / on_demand_compiler.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Compiles extern code within a module to a static library, as needed
16 module on_demand_compiler
17
18 import modelbuilder
19 import c_tools
20 import nitni
21 import ffi
22 import naive_interpreter
23
24 redef class AMethPropdef
25 # Does this method definition use the FFI and is it supported by the interpreter?
26 #
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
31 do
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
35
36 for mparam in mpropdef.msignature.mparameters do
37 var mtype = mparam.mtype
38 if not mtype.is_cprimitive then
39 return false
40 end
41 end
42
43 return true
44 end
45 end
46
47 redef class NaiveInterpreter
48 # Where to store generated C and extracted code
49 #
50 # TODO make customizable and delete when execution completes
51 private var compile_dir = "nit_compile"
52
53 # Path of the compiled foreign code library
54 #
55 # TODO change the ".so" extension per platform.
56 fun foreign_code_lib_path(mmodule: MModule): String
57 do
58 return compile_dir / mmodule.c_name + ".so"
59 end
60
61 # External compiler used to generate the foreign code library
62 private var c_compiler = "gcc"
63 end
64
65 redef class AModule
66
67 # Compile user FFI code and a standardized API into a `.so` file
68 #
69 # Returns `true` on success.
70 fun compile_foreign_lib(v: NaiveInterpreter): Bool
71 do
72 var mmodule = mmodule
73 assert mmodule != null
74
75 var compile_dir = v.compile_dir
76 var foreign_code_lib_path = v.foreign_code_lib_path(mmodule)
77
78 if not compile_dir.file_exists then compile_dir.mkdir
79
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)
86 end
87 end
88 mmodule.finalize_ffi_wrapper(compile_dir, mmodule)
89
90 # Compile the standard API and its implementation for the .so file
91 var ccu = compile_foreign_lib_api(compile_dir)
92
93 var srcs = [for file in ccu.files do new ExternCFile(file, ""): ExternFile]
94 srcs.add_all mmodule.ffi_files
95
96 if mmodule.pkgconfigs.not_empty then
97 fatal(v, "NOT YET IMPLEMENTED annotation `pkgconfig`")
98 return false
99 end
100
101 var ldflags = mmodule.ldflags[""].join(" ")
102 # TODO pkgconfig
103
104 # Compile each source file to an object file (or equivalent)
105 var object_files = new Array[String]
106 for f in srcs do
107 f.compile(v, mmodule, object_files)
108 end
109
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}`"
115 return false
116 end
117
118 return true
119 end
120
121 # Compile the standard API of the `.so` file
122 #
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
127 do
128 var mmodule = mmodule
129 assert mmodule != null
130
131 # ready extern code compiler
132 var ecc = new CCompilationUnit
133
134 ecc.body_decl.add """
135
136 #include <string.h>
137 #include <stdio.h>
138 #include <inttypes.h>
139
140 // C structure behind `CallArg` from the interpreter
141 typedef union nit_call_arg {
142 long value_Int;
143 int value_Bool;
144 uint32_t value_Char;
145 uint8_t value_Byte;
146 double value_Float;
147 void* value_Pointer;
148 } nit_call_arg;
149
150 """
151
152 # types
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"
157 end
158 end
159
160 # TODO callbacks & casts
161
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
165 end
166 end
167
168 ecc.write_as_foreign_lib_api(mmodule, compdir)
169
170 return ecc
171 end
172
173 # Collect all `MType` use in extern methods within this module
174 private fun collect_mtypes: Set[MType]
175 do
176 var used_types = new HashSet[MType]
177
178 # collect callbacks
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
183 end
184 end
185
186 return used_types
187 end
188 end
189
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)
193 do
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 = """
198 /*
199 Public API to foreign code of the Nit module {{{mmodule.name}}}
200 */
201 """
202
203 # Header file
204 var h_file = base_name+".h"
205 var stream = new FileWriter.open(compdir/h_file)
206 stream.write header_comment
207 stream.write """
208 #ifndef {{{guard}}}
209 #define {{{guard}}}
210 """
211 compile_header_core stream
212 stream.write """
213
214 #endif
215 """
216 stream.close
217
218 # Body file
219 var c_file = base_name+".c"
220 stream = new FileWriter.open(compdir/c_file)
221 stream.write header_comment
222 stream.write """
223 #include "{{{h_file}}}"
224 """
225 compile_body_core stream
226 stream.close
227
228 # Only the C files needs compiling
229 files.add compdir / c_file
230 end
231 end
232
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}"
236
237 # Compile the standardized entry point as part of the foreign lib API
238 private fun compile_foreign_code_entry(ecc: CCompilationUnit)
239 do
240 var msignature = msignature
241 if msignature == null then return
242
243 # Return type
244 var return_mtype = msignature.return_mtype
245 if mproperty.is_init then return_mtype = mclassdef.mclass.mclass_type
246
247 var c_return_type
248 if return_mtype != null then
249 c_return_type = return_mtype.cname_blind
250 else c_return_type = "void"
251
252 var is_init = mproperty.is_init
253
254 # Params
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
258
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"
263
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)
267
268 # Check argument count on the library side
269 #
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
273
274 fc.exprs.add """
275 if (argc != {{{expected_argc}}}) {
276 printf("Invalid argument count in `{{{mproperty.full_name}}}`, expected %d, got %d.\\n",
277 argc, {{{expected_argc}}});
278 return 1;
279 }
280 """
281
282 # Unpack and prepare args for the user code
283 var k = 0
284 var args_for_call = new Array[String]
285 if not is_init then
286 var mtype = mclassdef.mclass.mclass_type
287 var arg_name = "arg___self"
288
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
292
293 k += 1
294 end
295 for param in msignature.mparameters do
296 var mtype = param.mtype
297 var arg_name = "arg__"+param.name
298
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
302
303 k += 1
304 end
305
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
310 fc.decls.add """
311 {{{return_mtype.cname_blind}}} return_value;
312 """
313 fc.exprs.add """
314 return_value = {{{method_call}}};
315 result->{{{return_mtype.call_arg_field}}} = return_value;
316 """
317 else
318 fc.exprs.add " {method_call};\n"
319 end
320
321 fc.exprs.add " return 0;\n"
322
323 ecc.add_exported_function fc
324 end
325 end
326
327 redef class MType
328 # The interpreter FFI use `void*` to represent intern data types
329 redef fun cname_blind do return "void*"
330
331 # Field to store this type in the C structure `nit_call_arg`
332 private fun call_arg_field: String do return "value_Pointer"
333 end
334
335 redef class MClassType
336 redef fun call_arg_field
337 do
338 if is_cprimitive and mclass.kind != extern_kind then
339 return "value_{name}"
340 else return super
341 end
342 end
343
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
348 end
349
350 redef class ExternCFile
351 redef fun compile(v, mmodule, object_files)
352 do
353 var compile_dir = v.compile_dir
354 var cflags = mmodule.cflags[""].join(" ")
355 var obj = compile_dir / filename.basename(".c") + ".o"
356
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}`"
360 return false
361 end
362
363 object_files.add obj
364 return true
365 end
366 end