src/niti: use a real NativeString
[nit.git] / src / interpreter / dynamic_loading_ffi / dynamic_loading_ffi.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 # Execute FFI code by creating and loading shared libraries
16 #
17 # Triggers the compilation of the library when needed.
18 module dynamic_loading_ffi is ldflags "-ldl"
19
20 import naive_interpreter
21
22 import on_demand_compiler
23 import ffi
24
25 in "C Header" `{
26 #include <dlfcn.h>
27 #include <inttypes.h>
28
29 // C structure behind `CallArg`
30 typedef union nit_call_arg {
31 long value_Int;
32 int value_Bool;
33 uint32_t value_Char;
34 uint8_t value_Byte;
35 double value_Float;
36 void* value_Pointer;
37 } nit_call_arg;
38
39 // Signature shared by all entry points
40 typedef int (*nit_foreign_lib_entry)(int, nit_call_arg*, nit_call_arg*);
41 `}
42
43 # Single cell in the list of arguments sent to foreign code (and received)
44 #
45 # Each cell can hold all primitive types and pointers to Nit class instances.
46 #
47 # Since this is a C pointer, it acts as both a single cell and an array.
48 private extern class CallArg `{ nit_call_arg* `}
49
50 # Initialize an array of `CallArg` of `length` elements
51 new (length: Int) `{ return calloc(length, sizeof(nit_call_arg)); `}
52
53 # Get the element at `index` after `self`
54 fun [](index: Int): CallArg `{ return self + index; `}
55
56 # The `Int` held by this cell
57 fun int: Int `{ return self->value_Int; `}
58
59 # The `Int` held by this cell
60 fun int=(value: Int) `{ self->value_Int = value; `}
61
62 # The `Bool` held by this cell
63 fun bool: Bool `{ return self->value_Bool; `}
64
65 # The `Bool` held by this cell
66 fun bool=(value: Bool) `{ self->value_Bool = value; `}
67
68 # The `Char` held by this cell
69 fun char: Char `{ return self->value_Char; `}
70
71 # The `Char` held by this cell
72 fun char=(value: Char) `{ self->value_Char = value; `}
73
74 # The `Byte` held by this cell
75 fun byte: Byte `{ return self->value_Byte; `}
76
77 # The `Byte` held by this cell
78 fun byte=(value: Byte) `{ self->value_Byte = value; `}
79
80 # The `Float` held by this cell
81 fun float: Float `{ return self->value_Float; `}
82
83 # The `Float` held by this cell
84 fun float=(value: Float) `{ self->value_Float = value; `}
85
86 # The `Pointer` held by this cell
87 fun pointer: Pointer `{ return self->value_Pointer; `}
88
89 # The `Pointer` held by this cell
90 fun pointer=(value: Pointer) `{ self->value_Pointer = value; `}
91
92 # The `Instance` held by this cell
93 fun instance: Instance `{ return (Instance)self->value_Pointer; `}
94
95 # The `Instance` held by this cell
96 fun instance=(value: Instance) `{ self->value_Pointer = value; `}
97
98 # The `NativeString` held by this cell
99 fun native_string: NativeString `{ return (char*)self->value_Pointer; `}
100
101 # Set the content of this cell according to `static_type`
102 #
103 # Opposite of `to_instance`.
104 fun from_static_type(value: Instance, static_type: MType)
105 do
106 if static_type.name == "Int" then
107 assert value isa PrimitiveInstance[Int]
108 self.int = value.val
109 else if static_type.name == "Bool" then
110 assert value isa PrimitiveInstance[Bool]
111 self.bool = value.val
112 else if static_type.name == "Char" then
113 assert value isa PrimitiveInstance[Char]
114 self.char = value.val
115 else if static_type.name == "Byte" then
116 assert value isa PrimitiveInstance[Byte]
117 self.byte = value.val
118 else if static_type.name == "Float" then
119 assert value isa PrimitiveInstance[Float]
120 self.float = value.val
121 else if static_type.name == "NativeString" then
122 assert value isa PrimitiveInstance[NativeString]
123 self.pointer = value.val
124 else if static_type isa MClassType and static_type.mclass.kind == extern_kind then
125 assert value isa PrimitiveInstance[Pointer] else print value.class_name
126 self.pointer = value.val
127 else
128 self.instance = value
129 end
130 end
131
132 # Get the content of this cell as an `Instance` of the `static_type`
133 #
134 # Opposite of `from_static_type`.
135 fun to_instance(static_type: MType, v: NaiveInterpreter): Instance
136 do
137 var name = static_type.name
138 if name == "Int" then
139 return v.int_instance(self.int)
140 else if name == "Bool" then
141 return if self.bool then
142 v.true_instance
143 else v.false_instance
144 else if name == "Char" then
145 return v.char_instance(self.char)
146 else if name == "Byte" then
147 return v.byte_instance(self.byte)
148 else if name == "Float" then
149 return v.float_instance(self.float)
150 else if name == "NativeString" then
151 var instance = new PrimitiveInstance[NativeString](static_type, self.native_string)
152 v.init_instance_primitive instance
153 return instance
154 else if static_type isa MClassType and static_type.mclass.kind == extern_kind then
155 # We tag it with the most precise known type
156 var instance = new PrimitiveInstance[Pointer](static_type, self.pointer)
157 v.init_instance_primitive instance
158 return instance
159 else
160 return self.instance
161 end
162 end
163 end
164
165 # Handle to foreign code library
166 private extern class ForeignCodeLib
167 # Open and load the library at `path`
168 new dlopen(path: NativeString) `{
169 return dlopen(path, RTLD_LOCAL | RTLD_NOW);
170 `}
171
172 # Find the `ForeignCodeEntry` at `symbol_name`
173 fun dlsym(symbol_name: NativeString): ForeignCodeEntry `{
174 return dlsym(self, symbol_name);
175 `}
176 end
177
178 private fun dlerror: NativeString `{ return dlerror(); `}
179
180 # Handle to an implementation function in a `ForeignCodeLib`
181 private extern class ForeignCodeEntry`{ nit_foreign_lib_entry `}
182
183 # Invoke the implementation function by passing `args` and receive the return value
184 fun call(argc: Int, args, ret: CallArg): Bool `{
185 return ((nit_foreign_lib_entry)self)(argc, args, ret);
186 `}
187 end
188
189 redef class AMethPropdef
190 # Handle to the entrypoint of this method in the foreign code library
191 private var foreign_entry_cache: nullable ForeignCodeEntry = null
192
193 redef fun call_extern(v, mpropdef, args, frame)
194 do
195 # Fallback the default error if this method is not supported
196 if not supported_by_dynamic_ffi then return super
197
198 var entry = foreign_entry_cache
199 if entry == null then
200 # Get handle to foreign code lib
201 var amodule = v.modelbuilder.mmodule2node(mpropdef.mclassdef.mmodule)
202 assert amodule != null
203
204 var lib = amodule.foreign_code_lib(v)
205 if lib == null then return v.error_instance
206
207 # Get handle to implementation function
208 entry = lib.dlsym(mpropdef.foreign_lib_entry_cname.to_cstring)
209 if entry.address_is_null then
210 print mpropdef.foreign_lib_entry_cname
211 v.fatal "FFI Error: Cannot find method {mpropdef.name} in foreign code library."
212 return v.error_instance
213 end
214
215 foreign_entry_cache = entry
216 end
217
218 # Prepare to send args to foreign code lib
219 var is_init = mpropdef.mproperty.is_init
220 if is_init then args.shift
221 var native_args_length = args.length
222
223 var native_args = new CallArg(args.length)
224 var a = 0
225 if not is_init then
226 var arg = args[a]
227 var native_arg = native_args[a]
228 native_arg.from_static_type(arg, mpropdef.mclassdef.mclass.mclass_type)
229 a += 1
230 end
231 for param in mpropdef.msignature.mparameters do
232 var arg = args[a]
233 var native_arg = native_args[a]
234 native_arg.from_static_type(arg, param.mtype)
235 a += 1
236 end
237
238 # Allocate memory for the return value
239 var native_return = new CallArg(1)
240 var error = entry.call(native_args_length, native_args, native_return)
241
242 if error then
243 v.fatal "FFI Error: Native code library reported an error"
244 return null
245 end
246
247 # Get the result
248 var return_mtype = mpropdef.msignature.return_mtype
249 if is_init then return_mtype = mpropdef.mclassdef.mclass.mclass_type
250
251 var return_value
252 if return_mtype == null then
253 return_value = null
254 else
255 return_value = native_return.to_instance(return_mtype, v)
256 end
257
258 native_args.free
259 native_return.free
260
261 return return_value
262 end
263 end
264
265 redef class AModule
266
267 private var foreign_code_lib_cache: nullable ForeignCodeLib = null
268
269 # Handle to the external library with FFI code
270 private fun foreign_code_lib(v: NaiveInterpreter): nullable ForeignCodeLib
271 do
272 var lib = foreign_code_lib_cache
273 if lib != null then return lib
274
275 var mmodule = mmodule
276 assert mmodule != null
277
278 var foreign_code_lib_path = v.foreign_code_lib_path(mmodule)
279
280 # Compile lib
281 compile_foreign_lib v
282
283 lib = new ForeignCodeLib.dlopen(foreign_code_lib_path.to_cstring)
284 if lib.address_is_null then
285 v.fatal "FFI Error: Cannot load foreign code library for {mmodule.name}: {dlerror.to_s}"
286 return null
287 end
288
289 foreign_code_lib_cache = lib
290 return lib
291 end
292 end