f3ac5819c92157f9504a722edfa2feeebe7bb63f
[nit.git] / src / compiler_ffi.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # FFI support for the compilers
18 module compiler_ffi
19
20 intrude import abstract_compiler
21 intrude import common_ffi
22 import nitni
23
24 redef class MModule
25 private var foreign_callbacks = new ForeignCallbackSet
26 var nitni_ccu: nullable CCompilationUnit = null
27
28 private fun nmodule(v: AbstractCompilerVisitor): nullable AModule
29 do
30 var m2n = v.compiler.modelbuilder.mmodule2nmodule
31 return m2n.get_or_null(self)
32 end
33
34 redef fun finalize_ffi(compiler: AbstractCompiler)
35 do
36 if not uses_ffi then return
37
38 var v = compiler.new_visitor
39 var n = nmodule(v)
40 if n == null then return
41 n.ensure_compile_ffi_wrapper
42 finalize_ffi_wrapper(v.compiler.modelbuilder.compile_dir, v.compiler.mainmodule)
43 for file in ffi_files do v.compiler.extern_bodies.add(file)
44
45 ensure_compile_nitni_base(v)
46
47 nitni_ccu.header_c_types.add("#include \"{name}._ffi.h\"\n")
48 nitni_ccu.header_c_types.add """
49 extern void nitni_global_ref_incr(void*);
50 extern void nitni_global_ref_decr(void*);
51 """
52
53 nitni_ccu.write_as_nitni(self, v.compiler.modelbuilder.compile_dir)
54
55 for file in nitni_ccu.files do
56 v.compiler.extern_bodies.add(new ExternCFile(file, c_compiler_options))
57 end
58
59 # reset FFI things so the next compilation job, if any, starts with a clean context
60 # FIXME clean and rationalize this
61 nitni_ccu = null
62 compiled_ffi_methods.clear
63 ffi_ccu = null
64 ffi_files.clear
65 compiled_callbacks.clear
66 #Do not reset `foreign_callbacks` and `ffi_callbacks` because they are computed in previous phases
67 end
68
69 private fun ensure_compile_nitni_base(v: AbstractCompilerVisitor)
70 do
71 if nitni_ccu != null then return
72
73 nitni_ccu = new CCompilationUnit
74 end
75
76 redef fun collect_linker_libs
77 do
78 var s = c_linker_options
79 if s.is_empty then return null
80 var res = new ArraySet[String]
81 res.add s
82 return res
83 end
84
85 private var compiled_callbacks: Array[NitniCallback] = new Array[NitniCallback]
86
87 # Returns true if callbacks has yet to be generated and register it as being generated
88 private fun check_callback_compilation(cb: NitniCallback): Bool
89 do
90 var compiled = compiled_callbacks.has(cb)
91 if not compiled then compiled_callbacks.add(cb)
92 return not compiled
93 end
94 end
95
96 redef class AMethPropdef
97 private fun compile_ffi_support_to_c(v: AbstractCompilerVisitor)
98 do
99 var mmodule = mpropdef.mclassdef.mmodule
100 var mainmodule = v.compiler.mainmodule
101 var amainmodule = v.compiler.modelbuilder.mmodule2nmodule[mainmodule]
102 var amodule = v.compiler.modelbuilder.mmodule2nmodule[mmodule]
103 var mclass_type = mpropdef.mclassdef.bound_mtype
104
105 # Declare as extern
106 var csignature = mpropdef.mproperty.build_csignature(mclass_type, mmodule, "___impl", long_signature, internal_call_context)
107 v.declare_once("{csignature};")
108
109 # FFI part
110 amodule.ensure_compile_ffi_wrapper
111 compile_ffi_method(mmodule)
112
113 assert self isa AExternPropdef
114
115 # nitni - Compile missing callbacks
116 mmodule.ensure_compile_nitni_base(v)
117 var ccu = mmodule.nitni_ccu.as(not null)
118
119 for mtype in foreign_callbacks.types do
120 if not mtype.is_cprimitive then
121 mtype.compile_extern_type(v, ccu)
122
123 # has callbacks already been compiled? (this may very well happen with global compilation)
124 if mmodule.check_callback_compilation(mtype) then mtype.compile_extern_helper_functions(v, ccu)
125 end
126 end
127
128 # compile callbacks
129 for cb in foreign_callbacks.callbacks do if mainmodule.check_callback_compilation(cb) then
130 cb.compile_extern_callback(v, ccu)
131 end
132
133 for cb in foreign_callbacks.supers do if mainmodule.check_callback_compilation(cb) then
134 cb.compile_extern_callback(v, ccu)
135 end
136
137 for cb in foreign_callbacks.casts do if mainmodule.check_callback_compilation(cb) then
138 cb.compile_extern_callbacks(v, ccu)
139 end
140
141 # manage nitni callback set
142 mmodule.foreign_callbacks.join(foreign_callbacks)
143 end
144
145 redef fun compile_externmeth_to_c(v, mpropdef, arguments)
146 do
147 var mmodule = mpropdef.mclassdef.mmodule
148
149 # if using the old native interface fallback on previous implementation
150 var nextern = self.n_extern
151 if nextern != null then
152 super
153 return
154 end
155
156 mmodule.uses_ffi = true
157
158 var mclass_type = mpropdef.mclassdef.bound_mtype
159
160 # Outgoing code in compiler
161 var externname = mpropdef.mproperty.build_cname(mpropdef.mclassdef.bound_mtype, mmodule, "___impl", long_signature)
162 var recv_var: nullable RuntimeVariable = null
163 var return_mtype = mpropdef.msignature.return_mtype
164 if return_mtype != null then
165 return_mtype = return_mtype.anchor_to(mmodule, mclass_type)
166 recv_var = v.new_var(return_mtype)
167 end
168
169 v.adapt_signature(mpropdef, arguments)
170
171 var arguments_for_c = new Array[String]
172 for a in [0..arguments.length[ do
173 var arg = arguments[a]
174 var param_mtype: MType
175 if a == 0 then
176 param_mtype = mpropdef.mclassdef.mclass.mclass_type
177 else param_mtype = mpropdef.msignature.mparameters[a-1].mtype
178
179 param_mtype = param_mtype.anchor_to(mmodule, mclass_type)
180
181 if param_mtype.is_cprimitive then
182 arguments_for_c.add(arg.name)
183 else
184 v.add("struct nitni_instance* var_for_c_{a};")
185 v.add("var_for_c_{a} = nit_alloc(sizeof(struct nitni_instance));")
186 v.add("var_for_c_{a}->value = {arg.name};")
187 arguments_for_c.add("var_for_c_{a}")
188 end
189 end
190
191 if recv_var == null then
192 v.add("{externname}({arguments_for_c.join(", ")});")
193 else
194 assert return_mtype != null
195 if return_mtype.is_cprimitive then
196 v.add("{recv_var} = {externname}({arguments_for_c.join(", ")});")
197 else
198 v.add("struct nitni_instance* ret_var;")
199 v.add("ret_var = {externname}({arguments_for_c.join(", ")});")
200 v.add("{recv_var} = ret_var->value;")
201 end
202 v.ret(recv_var)
203 end
204
205 compile_ffi_support_to_c(v)
206 end
207
208 redef fun compile_externinit_to_c(v, mpropdef, arguments)
209 do
210 var mmodule = mpropdef.mclassdef.mmodule
211
212 # if using the old native interface fallback on previous implementation
213 var nextern = self.n_extern
214 if nextern != null then
215 super
216 return
217 end
218
219 mmodule.uses_ffi = true
220
221 var mclass_type = mpropdef.mclassdef.bound_mtype
222
223 var externname = mpropdef.mproperty.build_cname(mpropdef.mclassdef.bound_mtype, mmodule, "___impl", long_signature)
224 var return_mtype = arguments.first.mtype
225 var recv_var = v.new_var(return_mtype)
226
227 v.adapt_signature(mpropdef, arguments)
228
229 arguments.shift
230
231 var arguments_for_c = new Array[String]
232 for a in [0..arguments.length[ do
233 var arg = arguments[a]
234 var param_mtype: MType
235 param_mtype = mpropdef.msignature.mparameters[a].mtype
236 param_mtype = param_mtype.anchor_to(mmodule, mclass_type)
237
238 if param_mtype.is_cprimitive then
239 arguments_for_c.add(arg.name)
240 else
241 v.add("struct nitni_instance* var_for_c_{a};")
242 v.add("var_for_c_{a} = nit_alloc(sizeof(struct nitni_instance));")
243 v.add("var_for_c_{a}->value = {arg.name};")
244 arguments_for_c.add("var_for_c_{a}")
245 end
246 end
247
248 if return_mtype.is_cprimitive then
249 v.add("{recv_var} = {externname}({arguments_for_c.join(", ")});")
250 else
251 v.add("struct nitni_instance* ret_var;")
252 v.add("ret_var = {externname}({arguments_for_c.join(", ")});")
253 v.add("{recv_var} = ret_var->value;")
254 end
255 v.ret(recv_var)
256
257 compile_ffi_support_to_c(v)
258 end
259 end
260
261 redef class CCompilationUnit
262 fun write_as_nitni(mmodule: MModule, compdir: String)
263 do
264 var base_name = "{mmodule.name}._nitni"
265
266 var h_file = "{base_name}.h"
267 write_header_to_file( mmodule, "{compdir}/{h_file}", new Array[String],
268 "{mmodule.cname.to_s.to_upper}_NITG_NITNI_H")
269
270 var c_file = "{base_name}.c"
271 write_body_to_file( mmodule, "{compdir}/{c_file}", ["\"{h_file}\""] )
272
273 files.add( "{compdir}/{c_file}" )
274 end
275 end
276
277 redef class AbstractCompiler
278 # Cache to avoid multiple compilation of NULL values
279 # see FIXME in `MNullableType#compile_extern_helper_functions`
280 private var compiled_null_types = new Array[MNullableType]
281 end
282
283 redef class AbstractCompilerVisitor
284 # Create a `RuntimeVariable` for this C variable originating from C user code
285 private fun var_from_c(name: String, mtype: MType): RuntimeVariable
286 do
287 if mtype.is_cprimitive then
288 return new RuntimeVariable(name, mtype, mtype)
289 else
290 return new RuntimeVariable("{name}->value", mtype, mtype)
291 end
292 end
293
294 # Return a `RuntimeVarible` to C user code
295 private fun ret_to_c(src: RuntimeVariable, mtype: MType)
296 do
297 if mtype.is_cprimitive then
298 add("return {src};")
299 else
300 add("struct nitni_instance* ret_for_c;")
301 add("ret_for_c = nit_alloc(sizeof(struct nitni_instance));")
302 add("ret_for_c->value = {src};")
303 add("return ret_for_c;")
304 end
305 end
306 end
307
308 redef class MType
309 private fun compile_extern_type(v: AbstractCompilerVisitor, ccu: CCompilationUnit)
310 do
311 assert not is_cprimitive
312
313 # define friendly type
314 ccu.header_c_types.add("#ifndef NIT_TYPE_{cname}\n")
315 ccu.header_c_types.add("#define NIT_TYPE_{cname} 1\n")
316 ccu.header_c_types.add("typedef struct nitni_instance *{cname};\n")
317 ccu.header_c_types.add("#endif\n")
318 end
319
320 private fun compile_extern_helper_functions(v: AbstractCompilerVisitor, ccu: CCompilationUnit)
321 do
322 # actually, we do not need to do anything when using the bohem garbage collector
323 var call_context = from_c_call_context
324
325 # incr_ref
326 ccu.header_decl.add "#ifndef {mangled_cname}_incr_ref\n"
327 ccu.header_decl.add " #define {mangled_cname}_incr_ref(from) nitni_global_ref_incr(({call_context.name_mtype(self)})(from))\n"
328 ccu.header_decl.add "#endif\n"
329
330 # decr_ref
331 ccu.header_decl.add "#ifndef {mangled_cname}_decr_ref\n"
332 ccu.header_decl.add " #define {mangled_cname}_decr_ref(from) nitni_global_ref_decr(({call_context.name_mtype(self)})(from))\n"
333 ccu.header_decl.add "#endif\n"
334 end
335 end
336
337 redef class MNullableType
338 redef fun compile_extern_helper_functions(v, ccu)
339 do
340 super
341
342 var base_cname = "null_{mtype.mangled_cname}"
343 var full_cname = "NIT_NULL___{base_cname}"
344
345 # In nitni files, declare internal function as extern
346 var full_friendly_csignature = "{cname_blind} {full_cname}()"
347 ccu.header_decl.add("extern {full_friendly_csignature};\n")
348
349 # In nitni files, #define friendly as extern
350 ccu.header_decl.add("#define {base_cname} {full_cname}\n")
351
352 # FIXME: This is ugly an broke the separate compilation principle
353 # The real function MUST be compiled only once, #define pragma only protect the compiler, not the loader
354 # However, I am not sure of the right approach here (eg. week refs are ugly)
355 if v.compiler.compiled_null_types.has(self) then return
356 v.compiler.compiled_null_types.add self
357
358 # Internally, implement internal function
359 var nitni_visitor = v.compiler.new_visitor
360 nitni_visitor.frame = v.frame
361 var full_internal_csignature = "{cname_blind} {full_cname}()"
362 nitni_visitor.add("{full_internal_csignature} \{")
363 nitni_visitor.add("struct nitni_instance* ret_for_c;")
364 nitni_visitor.add("ret_for_c = nit_alloc(sizeof(struct nitni_instance));")
365 nitni_visitor.add("ret_for_c->value = NULL;")
366 nitni_visitor.add("return ret_for_c;")
367 nitni_visitor.add("\}")
368 end
369 end
370
371 redef class MExplicitCall
372 private fun compile_extern_callback(v: AbstractCompilerVisitor, ccu: CCompilationUnit)
373 do
374 var mproperty = mproperty
375 assert mproperty isa MMethod
376
377 # In nitni files, declare internal function as extern
378 var full_friendly_csignature = mproperty.build_csignature(recv_mtype, v.compiler.mainmodule, null, long_signature, internal_call_context)
379 ccu.header_decl.add("extern {full_friendly_csignature};\n")
380
381 # Internally, implement internal function
382 var nitni_visitor = v.compiler.new_visitor
383 nitni_visitor.frame = v.frame
384 var msignature = mproperty.lookup_first_definition(v.compiler.mainmodule, recv_mtype).msignature
385 var csignature_blind = mproperty.build_csignature(recv_mtype, v.compiler.mainmodule, null, long_signature, internal_call_context)
386
387 nitni_visitor.add_decl("/* nitni callback for {mproperty.full_name} */")
388 nitni_visitor.add_decl("{csignature_blind} \{")
389
390 var vars = new Array[RuntimeVariable]
391 var mtype: MType = recv_mtype
392 var recv_var = null
393 if mproperty.is_init then
394 if recv_mtype.mclass.kind == extern_kind then
395 recv_var = nitni_visitor.new_var(mtype)
396 else
397 var recv_mtype = recv_mtype
398 recv_var = nitni_visitor.init_instance(recv_mtype)
399 nitni_visitor.add("{mtype.ctype} recv /* var self: {mtype} */;")
400 nitni_visitor.add("recv = {recv_var};")
401 end
402 else
403 mtype = mtype.anchor_to(v.compiler.mainmodule, recv_mtype)
404 recv_var = nitni_visitor.var_from_c("recv", mtype)
405 end
406
407 vars.add(recv_var)
408
409 for p in msignature.mparameters do
410 var arg_mtype = p.mtype.anchor_to(v.compiler.mainmodule, recv_mtype)
411 var arg = nitni_visitor.var_from_c(p.name, arg_mtype)
412 vars.add(arg)
413 end
414
415 var ret_var = nitni_visitor.send(mproperty, vars)
416
417 var return_mtype = msignature.return_mtype
418 if mproperty.is_init then
419 if recv_mtype.mclass.kind != extern_kind then ret_var = recv_var
420 return_mtype = recv_mtype
421 end
422 if return_mtype != null then
423 assert ret_var != null
424 return_mtype = return_mtype.anchor_to(v.compiler.mainmodule, recv_mtype)
425 ret_var = nitni_visitor.autobox(ret_var, return_mtype)
426 nitni_visitor.ret_to_c(ret_var, return_mtype)
427 end
428 nitni_visitor.add("\}")
429 end
430 end
431
432 redef class MExplicitSuper
433 private fun compile_extern_callback(v: AbstractCompilerVisitor, ccu: CCompilationUnit)
434 do
435 var mproperty = from.mproperty
436 assert mproperty isa MMethod
437 var mclass_type = from.mclassdef.mclass.mclass_type
438 var mmodule = from.mclassdef.mmodule
439
440 # In nitni files, declare internal function as extern
441 var internal_csignature = mproperty.build_csignature(mclass_type, v.compiler.mainmodule, "___super", long_signature, internal_call_context)
442 ccu.header_decl.add("extern {internal_csignature};\n")
443
444 # In nitni files, #define friendly as extern
445 var friendly_cname = mproperty.build_cname(mclass_type, v.compiler.mainmodule, "___super", short_signature)
446 var internal_cname = mproperty.build_cname(mclass_type, v.compiler.mainmodule, "___super", long_signature)
447 ccu.header_decl.add("#define {friendly_cname} {internal_cname}\n")
448
449 # Internally, implement internal function
450 var nitni_visitor = v.compiler.new_visitor
451 nitni_visitor.frame = v.frame
452 var msignature = mproperty.lookup_first_definition(v.compiler.mainmodule, mclass_type).msignature
453
454 var csignature_blind = mproperty.build_csignature(mclass_type, v.compiler.mainmodule, "___super", long_signature, internal_call_context)
455
456 nitni_visitor.add_decl("/* nitni callback to super for {mproperty.full_name} */")
457 nitni_visitor.add_decl("{csignature_blind} \{")
458
459 var vars = new Array[RuntimeVariable]
460
461 var recv_var = nitni_visitor.var_from_c("recv", mclass_type)
462 vars.add(recv_var)
463
464 for p in msignature.mparameters do
465 var arg_mtype = v.anchor(p.mtype)
466 var arg = nitni_visitor.var_from_c(p.name, arg_mtype)
467 vars.add(arg)
468 end
469
470 var ret_var = nitni_visitor.supercall(from.as(MMethodDef), mclass_type, vars)
471
472 var return_mtype = msignature.return_mtype
473 if return_mtype != null then
474 assert ret_var != null
475 return_mtype = v.anchor(return_mtype)
476 nitni_visitor.ret_to_c(ret_var, return_mtype)
477 end
478 nitni_visitor.add("\}")
479 end
480 end
481
482 redef class MExplicitCast
483 private fun compile_extern_callbacks(v: AbstractCompilerVisitor, ccu: CCompilationUnit)
484 do
485 var from = from
486 var to = to
487
488 #
489 ## check type
490 #
491
492 # In nitni files, declare internal function as extern
493 var full_friendly_csignature = "int {v.compiler.mainmodule.name }___{from.mangled_cname}_is_a_{to.mangled_cname}({from.cname_blind})"
494 ccu.header_decl.add("extern {full_friendly_csignature};\n")
495
496 # In nitni files, #define friendly as extern
497 ccu.header_decl.add("#define {check_cname} {v.compiler.mainmodule.name}___{check_cname}\n")
498
499 # Internally, implement internal function
500 var nitni_visitor = v.compiler.new_visitor
501 nitni_visitor.frame = v.frame
502
503 var full_internal_csignature = "int {v.compiler.mainmodule.name }___{from.mangled_cname}_is_a_{to.mangled_cname}({from.cname_blind} from)"
504 nitni_visitor.add_decl("/* nitni check for {from} to {to} */")
505 nitni_visitor.add_decl("{full_internal_csignature} \{")
506
507 var from_var = new RuntimeVariable("from->value", from, from)
508 var recv_var = nitni_visitor.type_test(from_var, to, "FFI isa")
509 nitni_visitor.add("return {recv_var};")
510
511 nitni_visitor.add("\}")
512
513 # special checks
514 if from == to.as_nullable then
515 # format A_is_null
516 ccu.header_decl.add("#define {from.mangled_cname}_is_null !{from.mangled_cname}_is_a_{to.mangled_cname}\n")
517 end
518
519 #
520 ## cast
521 #
522
523 # In nitni files, declare internal function as extern
524 full_friendly_csignature = "{to.cname_blind} {v.compiler.mainmodule.name }___{from.mangled_cname}_as_{to.mangled_cname}({from.cname_blind})"
525 ccu.header_decl.add("extern {full_friendly_csignature};\n")
526
527 # In nitni files, #define friendly as extern
528 ccu.header_decl.add("#define {cast_cname} {v.compiler.mainmodule.name}___{cast_cname}\n")
529
530 # Internally, implement internal function
531 nitni_visitor = v.compiler.new_visitor
532 nitni_visitor.frame = v.frame
533
534 full_internal_csignature = "{to.cname_blind} {v.compiler.mainmodule.name }___{from.mangled_cname}_as_{to.mangled_cname}({from.cname_blind} from)"
535 nitni_visitor.add_decl("/* nitni cast for {from} to {to} */")
536 nitni_visitor.add_decl("{full_internal_csignature} \{")
537
538 from_var = nitni_visitor.var_from_c("from", from)
539
540 ## test type
541 var check = nitni_visitor.type_test(from_var, to, "FFI cast")
542 nitni_visitor.add("if (!{check}) \{")
543 nitni_visitor.add_abort("FFI cast failed")
544 nitni_visitor.add("\}")
545
546 ## internal cast
547 recv_var = nitni_visitor.autobox(from_var, to)
548
549 nitni_visitor.ret_to_c(recv_var, to)
550
551 nitni_visitor.add("\}")
552
553 # special casts
554 if from.as_nullable == to then
555 # format A_as_nullable
556 ccu.header_decl.add("#define {from.mangled_cname}_as_nullable {from.mangled_cname}_as_{to.mangled_cname}\n")
557 end
558
559 if from == to.as_nullable then
560 # format A_as_nullable
561 ccu.header_decl.add("#define {to.mangled_cname}_as_not_nullable {from.mangled_cname}_as_{to.mangled_cname}\n")
562 end
563 end
564 end