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