nitg/ffi: intro of FFI with C++
[nit.git] / src / common_ffi / cpp.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 # Supports the use of the C++ language through the FFI
18 module cpp
19
20 import extern_classes
21 import c
22
23 redef class FFILanguageAssignationPhase
24 var cpp_language: FFILanguage = new CPPLanguage(self)
25 end
26
27 redef class AModule
28 private var cpp_file: nullable CPPCompilationUnit = null
29 end
30
31 class CPPLanguage
32 super FFILanguage
33
34 redef fun identify_language(n) do return n.is_cpp
35
36 redef fun compile_module_block(block, ecc, nmodule)
37 do
38 if nmodule.cpp_file == null then nmodule.cpp_file = new CPPCompilationUnit
39
40 if block.is_cpp_header then
41 nmodule.cpp_file.header_custom.add(block.location.as_line_pragma)
42 nmodule.cpp_file.header_custom.add(block.code)
43 else if block.is_cpp_body then
44 nmodule.cpp_file.body_custom.add( block.location.as_line_pragma )
45 nmodule.cpp_file.body_custom.add( block.code )
46 end
47 end
48
49 # We call C++ from C using 2 more files (_ffi.c and _ffi.cpp) and multiple generated functions:
50 # 1. The standard C implementation function (___impl) expected by the common FFI
51 # 2. The indirection function (___cpp_impl_mid) is a C function, called from C but implemented as `extern "C"` in C++
52 # 3. The actual C++ implementation function (___cpp_impl)
53 redef fun compile_extern_method(block, m, ecc, nmodule)
54 do
55 if nmodule.cpp_file == null then nmodule.cpp_file = new CPPCompilationUnit
56
57 var mmodule = nmodule.mmodule.as(not null)
58 var mclass_type = m.parent.as(AClassdef).mclass.mclass_type
59 var mproperty = m.mpropdef.mproperty
60
61 # Signature of the indirection function implemented as `extern "C"` in C++
62 var indirection_sig = mproperty.build_csignature(mclass_type, mmodule, "___cpp_impl_mid", long_signature, internal_call_context)
63
64 ## In C file (__ffi.c)
65
66 # Declare the indirection function in C
67 ecc.body_decl.add("{indirection_sig};\n")
68
69 # Call the indirection function from C (___impl)
70 var fc: CFunction = new ExternCFunction(m, mmodule)
71 fc.exprs.add(mproperty.build_ccall(mclass_type, mmodule, "___cpp_impl_mid", long_signature, cpp_call_context, null))
72 fc.exprs.add("\n")
73 ecc.add_exported_function( fc )
74
75 ## In C++ file (__ffi.cpp)
76
77 # Declare the indirection function in C++
78 nmodule.cpp_file.header_decl.add("extern \"C\" \{\n")
79 nmodule.cpp_file.header_decl.add("{indirection_sig};\n")
80 nmodule.cpp_file.header_decl.add("\}\n")
81
82 # Implement the indirection function as extern in C++
83 # Will convert C arguments to C++ and call the C++ implementation function.
84 fc = new CFunction(indirection_sig)
85 if not mproperty.is_init then
86 var param_name = "recv"
87 var type_name = to_cpp_call_context.name_mtype(mclass_type)
88 if mclass_type.mclass.ftype isa ForeignCppType then
89 fc.exprs.add("{type_name} {param_name}_for_cpp = static_cast<{type_name}>({param_name});\n")
90 else
91 fc.exprs.add("{type_name} {param_name}_for_cpp = {param_name};\n")
92 end
93 end
94 for param in m.mpropdef.msignature.mparameters do
95 var param_name = param.name
96 var type_name = to_cpp_call_context.name_mtype(param.mtype)
97 if mclass_type.mclass.ftype isa ForeignCppType then
98 fc.exprs.add("{type_name} {param_name}_for_cpp = static_cast<{type_name}>({param_name});\n")
99 else
100 fc.exprs.add("{type_name} {param_name}_for_cpp = {param_name};\n")
101 end
102 end
103 fc.exprs.add(mproperty.build_ccall(mclass_type, mmodule, "___cpp_impl", long_signature, cpp_call_context, "_for_cpp"))
104 fc.exprs.add("\n")
105 nmodule.cpp_file.add_local_function(fc)
106
107 # Custom C++, the body of the Nit C++ method is copied to its own C++ function
108 var cpp_signature = mproperty.build_csignature(mclass_type, mmodule, "___cpp_impl", long_signature, cpp_call_context)
109 fc = new CFunction(cpp_signature)
110 fc.decls.add( block.location.as_line_pragma )
111 fc.exprs.add( block.code )
112 nmodule.cpp_file.add_local_function( fc )
113 end
114
115 redef fun compile_extern_class(block, m, ecc, nmodule) do end
116
117 redef fun get_ftype(block, m) do return new ForeignCppType(block.code)
118
119 redef fun compile_to_files(nmodule, compdir)
120 do
121 var cpp_file = nmodule.cpp_file
122 assert cpp_file != null
123
124 # write .cpp and .hpp file
125 cpp_file.header_custom.add("extern \"C\" \{\n")
126 cpp_file.header_custom.add("#include \"{nmodule.mmodule.name}._ffi.h\"\n")
127 cpp_file.header_custom.add("\}\n")
128
129 var file = cpp_file.write_to_files(nmodule, compdir)
130
131 # add complation to makefile
132 nmodule.ffi_files.add(file)
133
134 # add linked option to support C++
135 nmodule.c_linker_options = "{nmodule.c_linker_options} -lstdc++"
136 end
137
138 redef fun compile_callback(callback, nmodule, mmodule, ecc)
139 do
140 callback.compile_callback_to_cpp(nmodule, mmodule)
141 end
142 end
143
144 redef class AExternCodeBlock
145 fun is_cpp : Bool do return language_name != null and
146 (language_name_lowered == "c++" or language_name_lowered.has_prefix("c++ "))
147
148 fun is_cpp_body : Bool do return language_name != null and
149 (language_name_lowered == "c++" or language_name_lowered == "c++ body")
150
151 fun is_cpp_header : Bool do return language_name != null and
152 (language_name_lowered == "c++ header")
153 end
154
155 class CPPCompilationUnit
156 super CCompilationUnit
157
158 fun write_to_files(amodule: AModule, compdir: String): ExternCppFile
159 do
160 var base_name = "{amodule.mmodule.name}._ffi"
161
162 var h_file = "{base_name}.hpp"
163 var guard = "{amodule.cname.to_s.to_upper}_NIT_HPP"
164
165 write_header_to_file(amodule, "{compdir}/{h_file}", new Array[String], guard)
166
167 var c_file = "{base_name}.cpp"
168 write_body_to_file(amodule, "{compdir}/{c_file}", ["<string>", "<iostream>", "\"{h_file}\""])
169
170 files.add("{compdir}/{c_file}")
171
172 return new ExternCppFile("{compdir}/{c_file}")
173 end
174 end
175
176 class ExternCppFile
177 super ExternFile
178
179 redef fun makefile_rule_name do return "{filename.basename("")}.o"
180 redef fun makefile_rule_content do return "g++ -c {filename.basename("")} -o {filename.basename("")}.o"
181 end
182
183 class ForeignCppType
184 super ForeignType
185
186 var cpp_type: String
187
188 init (cpp_type: String)
189 do
190 self.cpp_type = cpp_type
191 end
192 end
193
194 redef class NitniCallback
195 fun compile_callback_to_cpp(nmodule: AModule, mmodule: MModule) do end
196 end
197
198 redef class Object
199 private fun cpp_call_context: CppCallContext do return once new CppCallContext
200 private fun to_cpp_call_context: ToCppCallContext do return once new ToCppCallContext
201 private fun from_cpp_call_context: FromCppCallContext do return once new FromCppCallContext
202 end
203
204 redef class MExplicitCall
205 redef fun compile_callback_to_cpp(nmodule, mmodule)
206 do
207 var mproperty = mproperty
208 assert mproperty isa MMethod
209
210 var cpp_signature = mproperty.build_csignature(recv_mtype, mmodule, null, short_signature, from_cpp_call_context)
211 var ccall = mproperty.build_ccall(recv_mtype, mmodule, null, long_signature, from_cpp_call_context, null)
212 var fc = new CFunction(cpp_signature)
213 fc.exprs.add(ccall)
214 nmodule.cpp_file.add_local_function( fc )
215 end
216 end
217
218 private class CppCallContext
219 super CallContext
220
221 redef fun name_mtype(mtype)
222 do
223 if mtype isa MClassType then
224 var ftype = mtype.mclass.ftype
225 if ftype isa ForeignCppType then
226 return ftype.cpp_type
227 end
228 end
229
230 return mtype.cname
231 end
232 end
233
234 class ToCppCallContext
235 super CppCallContext
236
237 redef fun cast_to(mtype, name)
238 do
239 if mtype isa MClassType and mtype.mclass.ftype isa ForeignCppType then
240 return "(void*)({name})"
241 else return name
242 end
243 end
244
245 private class FromCppCallContext
246 super CppCallContext
247
248 redef fun cast_from(mtype, name)
249 do
250 if mtype isa MClassType and mtype.mclass.ftype isa ForeignCppType then
251 return "static_cast<{name_mtype(mtype)}>({name})"
252 else return name
253 end
254 end