FFI niti: fix extern methods in generic classes
[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 -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 int8_t value_Int8;
147 int16_t value_Int16;
148 uint16_t value_UInt16;
149 int32_t value_Int32;
150 uint32_t value_UInt32;
151 double value_Float;
152 void* value_Pointer;
153 } nit_call_arg;
154
155 """
156
157 # types
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"
162 end
163 end
164
165 # TODO callbacks & casts
166
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
170 end
171 end
172
173 ecc.write_as_foreign_lib_api(mmodule, compdir)
174
175 return ecc
176 end
177
178 # Collect all `MType` use in extern methods within this module
179 private fun collect_mtypes: Set[MType]
180 do
181 var used_types = new HashSet[MType]
182
183 # collect callbacks
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
188 end
189 end
190
191 return used_types
192 end
193 end
194
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)
198 do
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 = """
203 /*
204 Public API to foreign code of the Nit module {{{mmodule.name}}}
205 */
206 """
207
208 # Header file
209 var h_file = base_name+".h"
210 var stream = new FileWriter.open(compdir/h_file)
211 stream.write header_comment
212 stream.write """
213 #ifndef {{{guard}}}
214 #define {{{guard}}}
215 """
216 compile_header_core stream
217 stream.write """
218
219 #endif
220 """
221 stream.close
222
223 # Body file
224 var c_file = base_name+".c"
225 stream = new FileWriter.open(compdir/c_file)
226 stream.write header_comment
227 stream.write """
228 #include "{{{h_file}}}"
229 """
230 compile_body_core stream
231 stream.close
232
233 # Only the C files needs compiling
234 files.add compdir / c_file
235 end
236 end
237
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}"
241
242 # Compile the standardized entry point as part of the foreign lib API
243 private fun compile_foreign_code_entry(ecc: CCompilationUnit)
244 do
245 var msignature = msignature
246 if msignature == null then return
247
248 # Return type
249 var return_mtype = msignature.return_mtype
250 if mproperty.is_init then return_mtype = mclassdef.mclass.mclass_type
251
252 var c_return_type
253 if return_mtype != null then
254 c_return_type = return_mtype.cname_blind
255 else c_return_type = "void"
256
257 var is_init = mproperty.is_init
258
259 # Params
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
263
264 # Declare the implementation function as extern
265 var impl_cname = mproperty.build_cname(mclassdef.bound_mtype,
266 mclassdef.mmodule, "___impl", long_signature)
267 ecc.body_decl.add "extern {c_return_type} {impl_cname}({params.join(", ")});\n"
268
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)
272
273 # Check argument count on the library side
274 #
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
278
279 fc.exprs.add """
280 if (argc != {{{expected_argc}}}) {
281 printf("Invalid argument count in `{{{mproperty.full_name}}}`, expected %d, got %d.\\n",
282 argc, {{{expected_argc}}});
283 return 1;
284 }
285 """
286
287 # Unpack and prepare args for the user code
288 var k = 0
289 var args_for_call = new Array[String]
290 if not is_init then
291 var mtype = mclassdef.mclass.mclass_type
292 var arg_name = "arg___self"
293
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
297
298 k += 1
299 end
300 for param in msignature.mparameters do
301 var mtype = param.mtype
302 var arg_name = "arg__"+param.name
303
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
307
308 k += 1
309 end
310
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
315 fc.decls.add """
316 {{{return_mtype.cname_blind}}} return_value;
317 """
318 fc.exprs.add """
319 return_value = {{{method_call}}};
320 result->{{{return_mtype.call_arg_field}}} = return_value;
321 """
322 else
323 fc.exprs.add " {method_call};\n"
324 end
325
326 fc.exprs.add " return 0;\n"
327
328 ecc.add_exported_function fc
329 end
330 end
331
332 redef class MType
333 # The interpreter FFI use `void*` to represent intern data types
334 redef fun cname_blind do return "void*"
335
336 # Field to store this type in the C structure `nit_call_arg`
337 private fun call_arg_field: String do return "value_Pointer"
338 end
339
340 redef class MClassType
341 redef fun call_arg_field
342 do
343 if is_cprimitive and mclass.kind != extern_kind then
344 return "value_{name}"
345 else return super
346 end
347 end
348
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
353 end
354
355 redef class ExternCFile
356 redef fun compile(v, mmodule, object_files)
357 do
358 var compile_dir = v.compile_dir
359 var cflags = mmodule.cflags[""].join(" ")
360 var obj = compile_dir / filename.basename(".c") + ".o"
361
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}`"
365 return false
366 end
367
368 object_files.add obj
369 return true
370 end
371 end