1 | //===-- ExternalFunctions.cpp - Implement External Functions --------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file contains both code to deal with invoking "external" functions, but |
10 | // also contains code that implements "exported" external functions. |
11 | // |
12 | // There are currently two mechanisms for handling external functions in the |
13 | // Interpreter. The first is to implement lle_* wrapper functions that are |
14 | // specific to well-known library functions which manually translate the |
15 | // arguments from GenericValues and make the call. If such a wrapper does |
16 | // not exist, and libffi is available, then the Interpreter will attempt to |
17 | // invoke the function using libffi, after finding its address. |
18 | // |
19 | //===----------------------------------------------------------------------===// |
20 | |
21 | #include "Interpreter.h" |
22 | #include "llvm/ADT/APInt.h" |
23 | #include "llvm/ADT/ArrayRef.h" |
24 | #include "llvm/Config/config.h" // Detect libffi |
25 | #include "llvm/ExecutionEngine/GenericValue.h" |
26 | #include "llvm/IR/DataLayout.h" |
27 | #include "llvm/IR/DerivedTypes.h" |
28 | #include "llvm/IR/Function.h" |
29 | #include "llvm/IR/Type.h" |
30 | #include "llvm/Support/Casting.h" |
31 | #include "llvm/Support/DynamicLibrary.h" |
32 | #include "llvm/Support/ErrorHandling.h" |
33 | #include "llvm/Support/Mutex.h" |
34 | #include "llvm/Support/raw_ostream.h" |
35 | #include <cassert> |
36 | #include <cmath> |
37 | #include <csignal> |
38 | #include <cstdint> |
39 | #include <cstdio> |
40 | #include <cstring> |
41 | #include <map> |
42 | #include <mutex> |
43 | #include <string> |
44 | #include <utility> |
45 | #include <vector> |
46 | |
47 | #ifdef HAVE_FFI_CALL |
48 | #ifdef HAVE_FFI_H |
49 | #include <ffi.h> |
50 | #define USE_LIBFFI |
51 | #elif HAVE_FFI_FFI_H |
52 | #include <ffi/ffi.h> |
53 | #define USE_LIBFFI |
54 | #endif |
55 | #endif |
56 | |
57 | using namespace llvm; |
58 | |
59 | namespace { |
60 | |
61 | typedef GenericValue (*ExFunc)(FunctionType *, ArrayRef<GenericValue>); |
62 | typedef void (*RawFunc)(); |
63 | |
64 | struct Functions { |
65 | sys::Mutex Lock; |
66 | std::map<const Function *, ExFunc> ExportedFunctions; |
67 | std::map<std::string, ExFunc> FuncNames; |
68 | #ifdef USE_LIBFFI |
69 | std::map<const Function *, RawFunc> RawFunctions; |
70 | #endif |
71 | }; |
72 | |
73 | Functions &getFunctions() { |
74 | static Functions F; |
75 | return F; |
76 | } |
77 | |
78 | } // anonymous namespace |
79 | |
80 | static Interpreter *TheInterpreter; |
81 | |
82 | static char getTypeID(Type *Ty) { |
83 | switch (Ty->getTypeID()) { |
84 | case Type::VoidTyID: return 'V'; |
85 | case Type::IntegerTyID: |
86 | switch (cast<IntegerType>(Val: Ty)->getBitWidth()) { |
87 | case 1: return 'o'; |
88 | case 8: return 'B'; |
89 | case 16: return 'S'; |
90 | case 32: return 'I'; |
91 | case 64: return 'L'; |
92 | default: return 'N'; |
93 | } |
94 | case Type::FloatTyID: return 'F'; |
95 | case Type::DoubleTyID: return 'D'; |
96 | case Type::PointerTyID: return 'P'; |
97 | case Type::FunctionTyID:return 'M'; |
98 | case Type::StructTyID: return 'T'; |
99 | case Type::ArrayTyID: return 'A'; |
100 | default: return 'U'; |
101 | } |
102 | } |
103 | |
104 | // Try to find address of external function given a Function object. |
105 | // Please note, that interpreter doesn't know how to assemble a |
106 | // real call in general case (this is JIT job), that's why it assumes, |
107 | // that all external functions has the same (and pretty "general") signature. |
108 | // The typical example of such functions are "lle_X_" ones. |
109 | static ExFunc lookupFunction(const Function *F) { |
110 | // Function not found, look it up... start by figuring out what the |
111 | // composite function name should be. |
112 | std::string ExtName = "lle_" ; |
113 | FunctionType *FT = F->getFunctionType(); |
114 | ExtName += getTypeID(Ty: FT->getReturnType()); |
115 | for (Type *T : FT->params()) |
116 | ExtName += getTypeID(Ty: T); |
117 | ExtName += ("_" + F->getName()).str(); |
118 | |
119 | auto &Fns = getFunctions(); |
120 | sys::ScopedLock Writer(Fns.Lock); |
121 | ExFunc FnPtr = Fns.FuncNames[ExtName]; |
122 | if (!FnPtr) |
123 | FnPtr = Fns.FuncNames[("lle_X_" + F->getName()).str()]; |
124 | if (!FnPtr) // Try calling a generic function... if it exists... |
125 | FnPtr = (ExFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol( |
126 | symbolName: ("lle_X_" + F->getName()).str()); |
127 | if (FnPtr) |
128 | Fns.ExportedFunctions.insert(x: std::make_pair(x&: F, y&: FnPtr)); // Cache for later |
129 | return FnPtr; |
130 | } |
131 | |
132 | #ifdef USE_LIBFFI |
133 | static ffi_type *ffiTypeFor(Type *Ty) { |
134 | switch (Ty->getTypeID()) { |
135 | case Type::VoidTyID: return &ffi_type_void; |
136 | case Type::IntegerTyID: |
137 | switch (cast<IntegerType>(Ty)->getBitWidth()) { |
138 | case 8: return &ffi_type_sint8; |
139 | case 16: return &ffi_type_sint16; |
140 | case 32: return &ffi_type_sint32; |
141 | case 64: return &ffi_type_sint64; |
142 | } |
143 | llvm_unreachable("Unhandled integer type bitwidth" ); |
144 | case Type::FloatTyID: return &ffi_type_float; |
145 | case Type::DoubleTyID: return &ffi_type_double; |
146 | case Type::PointerTyID: return &ffi_type_pointer; |
147 | default: break; |
148 | } |
149 | // TODO: Support other types such as StructTyID, ArrayTyID, OpaqueTyID, etc. |
150 | report_fatal_error("Type could not be mapped for use with libffi." ); |
151 | return NULL; |
152 | } |
153 | |
154 | static void *ffiValueFor(Type *Ty, const GenericValue &AV, |
155 | void *ArgDataPtr) { |
156 | switch (Ty->getTypeID()) { |
157 | case Type::IntegerTyID: |
158 | switch (cast<IntegerType>(Ty)->getBitWidth()) { |
159 | case 8: { |
160 | int8_t *I8Ptr = (int8_t *) ArgDataPtr; |
161 | *I8Ptr = (int8_t) AV.IntVal.getZExtValue(); |
162 | return ArgDataPtr; |
163 | } |
164 | case 16: { |
165 | int16_t *I16Ptr = (int16_t *) ArgDataPtr; |
166 | *I16Ptr = (int16_t) AV.IntVal.getZExtValue(); |
167 | return ArgDataPtr; |
168 | } |
169 | case 32: { |
170 | int32_t *I32Ptr = (int32_t *) ArgDataPtr; |
171 | *I32Ptr = (int32_t) AV.IntVal.getZExtValue(); |
172 | return ArgDataPtr; |
173 | } |
174 | case 64: { |
175 | int64_t *I64Ptr = (int64_t *) ArgDataPtr; |
176 | *I64Ptr = (int64_t) AV.IntVal.getZExtValue(); |
177 | return ArgDataPtr; |
178 | } |
179 | } |
180 | llvm_unreachable("Unhandled integer type bitwidth" ); |
181 | case Type::FloatTyID: { |
182 | float *FloatPtr = (float *) ArgDataPtr; |
183 | *FloatPtr = AV.FloatVal; |
184 | return ArgDataPtr; |
185 | } |
186 | case Type::DoubleTyID: { |
187 | double *DoublePtr = (double *) ArgDataPtr; |
188 | *DoublePtr = AV.DoubleVal; |
189 | return ArgDataPtr; |
190 | } |
191 | case Type::PointerTyID: { |
192 | void **PtrPtr = (void **) ArgDataPtr; |
193 | *PtrPtr = GVTOP(AV); |
194 | return ArgDataPtr; |
195 | } |
196 | default: break; |
197 | } |
198 | // TODO: Support other types such as StructTyID, ArrayTyID, OpaqueTyID, etc. |
199 | report_fatal_error("Type value could not be mapped for use with libffi." ); |
200 | return NULL; |
201 | } |
202 | |
203 | static bool ffiInvoke(RawFunc Fn, Function *F, ArrayRef<GenericValue> ArgVals, |
204 | const DataLayout &TD, GenericValue &Result) { |
205 | ffi_cif cif; |
206 | FunctionType *FTy = F->getFunctionType(); |
207 | const unsigned NumArgs = F->arg_size(); |
208 | |
209 | // TODO: We don't have type information about the remaining arguments, because |
210 | // this information is never passed into ExecutionEngine::runFunction(). |
211 | if (ArgVals.size() > NumArgs && F->isVarArg()) { |
212 | report_fatal_error("Calling external var arg function '" + F->getName() |
213 | + "' is not supported by the Interpreter." ); |
214 | } |
215 | |
216 | unsigned ArgBytes = 0; |
217 | |
218 | std::vector<ffi_type*> args(NumArgs); |
219 | for (Function::const_arg_iterator A = F->arg_begin(), E = F->arg_end(); |
220 | A != E; ++A) { |
221 | const unsigned ArgNo = A->getArgNo(); |
222 | Type *ArgTy = FTy->getParamType(ArgNo); |
223 | args[ArgNo] = ffiTypeFor(ArgTy); |
224 | ArgBytes += TD.getTypeStoreSize(ArgTy); |
225 | } |
226 | |
227 | SmallVector<uint8_t, 128> ArgData; |
228 | ArgData.resize(ArgBytes); |
229 | uint8_t *ArgDataPtr = ArgData.data(); |
230 | SmallVector<void*, 16> values(NumArgs); |
231 | for (Function::const_arg_iterator A = F->arg_begin(), E = F->arg_end(); |
232 | A != E; ++A) { |
233 | const unsigned ArgNo = A->getArgNo(); |
234 | Type *ArgTy = FTy->getParamType(ArgNo); |
235 | values[ArgNo] = ffiValueFor(ArgTy, ArgVals[ArgNo], ArgDataPtr); |
236 | ArgDataPtr += TD.getTypeStoreSize(ArgTy); |
237 | } |
238 | |
239 | Type *RetTy = FTy->getReturnType(); |
240 | ffi_type *rtype = ffiTypeFor(RetTy); |
241 | |
242 | if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, NumArgs, rtype, args.data()) == |
243 | FFI_OK) { |
244 | SmallVector<uint8_t, 128> ret; |
245 | if (RetTy->getTypeID() != Type::VoidTyID) |
246 | ret.resize(TD.getTypeStoreSize(RetTy)); |
247 | ffi_call(&cif, Fn, ret.data(), values.data()); |
248 | switch (RetTy->getTypeID()) { |
249 | case Type::IntegerTyID: |
250 | switch (cast<IntegerType>(RetTy)->getBitWidth()) { |
251 | case 8: Result.IntVal = APInt(8 , *(int8_t *) ret.data()); break; |
252 | case 16: Result.IntVal = APInt(16, *(int16_t*) ret.data()); break; |
253 | case 32: Result.IntVal = APInt(32, *(int32_t*) ret.data()); break; |
254 | case 64: Result.IntVal = APInt(64, *(int64_t*) ret.data()); break; |
255 | } |
256 | break; |
257 | case Type::FloatTyID: Result.FloatVal = *(float *) ret.data(); break; |
258 | case Type::DoubleTyID: Result.DoubleVal = *(double*) ret.data(); break; |
259 | case Type::PointerTyID: Result.PointerVal = *(void **) ret.data(); break; |
260 | default: break; |
261 | } |
262 | return true; |
263 | } |
264 | |
265 | return false; |
266 | } |
267 | #endif // USE_LIBFFI |
268 | |
269 | GenericValue Interpreter::callExternalFunction(Function *F, |
270 | ArrayRef<GenericValue> ArgVals) { |
271 | TheInterpreter = this; |
272 | |
273 | auto &Fns = getFunctions(); |
274 | std::unique_lock<sys::Mutex> Guard(Fns.Lock); |
275 | |
276 | // Do a lookup to see if the function is in our cache... this should just be a |
277 | // deferred annotation! |
278 | std::map<const Function *, ExFunc>::iterator FI = |
279 | Fns.ExportedFunctions.find(x: F); |
280 | if (ExFunc Fn = (FI == Fns.ExportedFunctions.end()) ? lookupFunction(F) |
281 | : FI->second) { |
282 | Guard.unlock(); |
283 | return Fn(F->getFunctionType(), ArgVals); |
284 | } |
285 | |
286 | #ifdef USE_LIBFFI |
287 | std::map<const Function *, RawFunc>::iterator RF = Fns.RawFunctions.find(F); |
288 | RawFunc RawFn; |
289 | if (RF == Fns.RawFunctions.end()) { |
290 | RawFn = (RawFunc)(intptr_t) |
291 | sys::DynamicLibrary::SearchForAddressOfSymbol(std::string(F->getName())); |
292 | if (!RawFn) |
293 | RawFn = (RawFunc)(intptr_t)getPointerToGlobalIfAvailable(F); |
294 | if (RawFn != 0) |
295 | Fns.RawFunctions.insert(std::make_pair(F, RawFn)); // Cache for later |
296 | } else { |
297 | RawFn = RF->second; |
298 | } |
299 | |
300 | Guard.unlock(); |
301 | |
302 | GenericValue Result; |
303 | if (RawFn != 0 && ffiInvoke(RawFn, F, ArgVals, getDataLayout(), Result)) |
304 | return Result; |
305 | #endif // USE_LIBFFI |
306 | |
307 | if (F->getName() == "__main" ) |
308 | errs() << "Tried to execute an unknown external function: " |
309 | << *F->getType() << " __main\n" ; |
310 | else |
311 | report_fatal_error(reason: "Tried to execute an unknown external function: " + |
312 | F->getName()); |
313 | #ifndef USE_LIBFFI |
314 | errs() << "Recompiling LLVM with --enable-libffi might help.\n" ; |
315 | #endif |
316 | return GenericValue(); |
317 | } |
318 | |
319 | //===----------------------------------------------------------------------===// |
320 | // Functions "exported" to the running application... |
321 | // |
322 | |
323 | // void atexit(Function*) |
324 | static GenericValue lle_X_atexit(FunctionType *FT, |
325 | ArrayRef<GenericValue> Args) { |
326 | assert(Args.size() == 1); |
327 | TheInterpreter->addAtExitHandler(F: (Function*)GVTOP(GV: Args[0])); |
328 | GenericValue GV; |
329 | GV.IntVal = 0; |
330 | return GV; |
331 | } |
332 | |
333 | // void exit(int) |
334 | static GenericValue lle_X_exit(FunctionType *FT, ArrayRef<GenericValue> Args) { |
335 | TheInterpreter->exitCalled(GV: Args[0]); |
336 | return GenericValue(); |
337 | } |
338 | |
339 | // void abort(void) |
340 | static GenericValue lle_X_abort(FunctionType *FT, ArrayRef<GenericValue> Args) { |
341 | //FIXME: should we report or raise here? |
342 | //report_fatal_error("Interpreted program raised SIGABRT"); |
343 | raise (SIGABRT); |
344 | return GenericValue(); |
345 | } |
346 | |
347 | // Silence warnings about sprintf. (See also |
348 | // https://github.com/llvm/llvm-project/issues/58086) |
349 | #if defined(__clang__) |
350 | #pragma clang diagnostic push |
351 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
352 | #endif |
353 | // int sprintf(char *, const char *, ...) - a very rough implementation to make |
354 | // output useful. |
355 | static GenericValue lle_X_sprintf(FunctionType *FT, |
356 | ArrayRef<GenericValue> Args) { |
357 | char *OutputBuffer = (char *)GVTOP(GV: Args[0]); |
358 | const char *FmtStr = (const char *)GVTOP(GV: Args[1]); |
359 | unsigned ArgNo = 2; |
360 | |
361 | // printf should return # chars printed. This is completely incorrect, but |
362 | // close enough for now. |
363 | GenericValue GV; |
364 | GV.IntVal = APInt(32, strlen(s: FmtStr)); |
365 | while (true) { |
366 | switch (*FmtStr) { |
367 | case 0: return GV; // Null terminator... |
368 | default: // Normal nonspecial character |
369 | sprintf(s: OutputBuffer++, format: "%c" , *FmtStr++); |
370 | break; |
371 | case '\\': { // Handle escape codes |
372 | sprintf(s: OutputBuffer, format: "%c%c" , *FmtStr, *(FmtStr+1)); |
373 | FmtStr += 2; OutputBuffer += 2; |
374 | break; |
375 | } |
376 | case '%': { // Handle format specifiers |
377 | char FmtBuf[100] = "" , Buffer[1000] = "" ; |
378 | char *FB = FmtBuf; |
379 | *FB++ = *FmtStr++; |
380 | char Last = *FB++ = *FmtStr++; |
381 | unsigned HowLong = 0; |
382 | while (Last != 'c' && Last != 'd' && Last != 'i' && Last != 'u' && |
383 | Last != 'o' && Last != 'x' && Last != 'X' && Last != 'e' && |
384 | Last != 'E' && Last != 'g' && Last != 'G' && Last != 'f' && |
385 | Last != 'p' && Last != 's' && Last != '%') { |
386 | if (Last == 'l' || Last == 'L') HowLong++; // Keep track of l's |
387 | Last = *FB++ = *FmtStr++; |
388 | } |
389 | *FB = 0; |
390 | |
391 | switch (Last) { |
392 | case '%': |
393 | memcpy(dest: Buffer, src: "%" , n: 2); break; |
394 | case 'c': |
395 | sprintf(s: Buffer, format: FmtBuf, uint32_t(Args[ArgNo++].IntVal.getZExtValue())); |
396 | break; |
397 | case 'd': case 'i': |
398 | case 'u': case 'o': |
399 | case 'x': case 'X': |
400 | if (HowLong >= 1) { |
401 | if (HowLong == 1 && |
402 | TheInterpreter->getDataLayout().getPointerSizeInBits() == 64 && |
403 | sizeof(long) < sizeof(int64_t)) { |
404 | // Make sure we use %lld with a 64 bit argument because we might be |
405 | // compiling LLI on a 32 bit compiler. |
406 | unsigned Size = strlen(s: FmtBuf); |
407 | FmtBuf[Size] = FmtBuf[Size-1]; |
408 | FmtBuf[Size+1] = 0; |
409 | FmtBuf[Size-1] = 'l'; |
410 | } |
411 | sprintf(s: Buffer, format: FmtBuf, Args[ArgNo++].IntVal.getZExtValue()); |
412 | } else |
413 | sprintf(s: Buffer, format: FmtBuf,uint32_t(Args[ArgNo++].IntVal.getZExtValue())); |
414 | break; |
415 | case 'e': case 'E': case 'g': case 'G': case 'f': |
416 | sprintf(s: Buffer, format: FmtBuf, Args[ArgNo++].DoubleVal); break; |
417 | case 'p': |
418 | sprintf(s: Buffer, format: FmtBuf, (void*)GVTOP(GV: Args[ArgNo++])); break; |
419 | case 's': |
420 | sprintf(s: Buffer, format: FmtBuf, (char*)GVTOP(GV: Args[ArgNo++])); break; |
421 | default: |
422 | errs() << "<unknown printf code '" << *FmtStr << "'!>" ; |
423 | ArgNo++; break; |
424 | } |
425 | size_t Len = strlen(s: Buffer); |
426 | memcpy(dest: OutputBuffer, src: Buffer, n: Len + 1); |
427 | OutputBuffer += Len; |
428 | } |
429 | break; |
430 | } |
431 | } |
432 | return GV; |
433 | } |
434 | #if defined(__clang__) |
435 | #pragma clang diagnostic pop |
436 | #endif |
437 | |
438 | // int printf(const char *, ...) - a very rough implementation to make output |
439 | // useful. |
440 | static GenericValue lle_X_printf(FunctionType *FT, |
441 | ArrayRef<GenericValue> Args) { |
442 | char Buffer[10000]; |
443 | std::vector<GenericValue> NewArgs; |
444 | NewArgs.push_back(x: PTOGV(P: (void*)&Buffer[0])); |
445 | llvm::append_range(C&: NewArgs, R&: Args); |
446 | GenericValue GV = lle_X_sprintf(FT, Args: NewArgs); |
447 | outs() << Buffer; |
448 | return GV; |
449 | } |
450 | |
451 | // int sscanf(const char *format, ...); |
452 | static GenericValue lle_X_sscanf(FunctionType *FT, |
453 | ArrayRef<GenericValue> args) { |
454 | assert(args.size() < 10 && "Only handle up to 10 args to sscanf right now!" ); |
455 | |
456 | char *Args[10]; |
457 | for (unsigned i = 0; i < args.size(); ++i) |
458 | Args[i] = (char*)GVTOP(GV: args[i]); |
459 | |
460 | GenericValue GV; |
461 | GV.IntVal = APInt(32, sscanf(s: Args[0], format: Args[1], Args[2], Args[3], Args[4], |
462 | Args[5], Args[6], Args[7], Args[8], Args[9])); |
463 | return GV; |
464 | } |
465 | |
466 | // int scanf(const char *format, ...); |
467 | static GenericValue lle_X_scanf(FunctionType *FT, ArrayRef<GenericValue> args) { |
468 | assert(args.size() < 10 && "Only handle up to 10 args to scanf right now!" ); |
469 | |
470 | char *Args[10]; |
471 | for (unsigned i = 0; i < args.size(); ++i) |
472 | Args[i] = (char*)GVTOP(GV: args[i]); |
473 | |
474 | GenericValue GV; |
475 | GV.IntVal = APInt(32, scanf( format: Args[0], Args[1], Args[2], Args[3], Args[4], |
476 | Args[5], Args[6], Args[7], Args[8], Args[9])); |
477 | return GV; |
478 | } |
479 | |
480 | // int fprintf(FILE *, const char *, ...) - a very rough implementation to make |
481 | // output useful. |
482 | static GenericValue lle_X_fprintf(FunctionType *FT, |
483 | ArrayRef<GenericValue> Args) { |
484 | assert(Args.size() >= 2); |
485 | char Buffer[10000]; |
486 | std::vector<GenericValue> NewArgs; |
487 | NewArgs.push_back(x: PTOGV(P: Buffer)); |
488 | NewArgs.insert(position: NewArgs.end(), first: Args.begin()+1, last: Args.end()); |
489 | GenericValue GV = lle_X_sprintf(FT, Args: NewArgs); |
490 | |
491 | fputs(s: Buffer, stream: (FILE *) GVTOP(GV: Args[0])); |
492 | return GV; |
493 | } |
494 | |
495 | static GenericValue lle_X_memset(FunctionType *FT, |
496 | ArrayRef<GenericValue> Args) { |
497 | int val = (int)Args[1].IntVal.getSExtValue(); |
498 | size_t len = (size_t)Args[2].IntVal.getZExtValue(); |
499 | memset(s: (void *)GVTOP(GV: Args[0]), c: val, n: len); |
500 | // llvm.memset.* returns void, lle_X_* returns GenericValue, |
501 | // so here we return GenericValue with IntVal set to zero |
502 | GenericValue GV; |
503 | GV.IntVal = 0; |
504 | return GV; |
505 | } |
506 | |
507 | static GenericValue lle_X_memcpy(FunctionType *FT, |
508 | ArrayRef<GenericValue> Args) { |
509 | memcpy(dest: GVTOP(GV: Args[0]), src: GVTOP(GV: Args[1]), |
510 | n: (size_t)(Args[2].IntVal.getLimitedValue())); |
511 | |
512 | // llvm.memcpy* returns void, lle_X_* returns GenericValue, |
513 | // so here we return GenericValue with IntVal set to zero |
514 | GenericValue GV; |
515 | GV.IntVal = 0; |
516 | return GV; |
517 | } |
518 | |
519 | void Interpreter::initializeExternalFunctions() { |
520 | auto &Fns = getFunctions(); |
521 | sys::ScopedLock Writer(Fns.Lock); |
522 | Fns.FuncNames["lle_X_atexit" ] = lle_X_atexit; |
523 | Fns.FuncNames["lle_X_exit" ] = lle_X_exit; |
524 | Fns.FuncNames["lle_X_abort" ] = lle_X_abort; |
525 | |
526 | Fns.FuncNames["lle_X_printf" ] = lle_X_printf; |
527 | Fns.FuncNames["lle_X_sprintf" ] = lle_X_sprintf; |
528 | Fns.FuncNames["lle_X_sscanf" ] = lle_X_sscanf; |
529 | Fns.FuncNames["lle_X_scanf" ] = lle_X_scanf; |
530 | Fns.FuncNames["lle_X_fprintf" ] = lle_X_fprintf; |
531 | Fns.FuncNames["lle_X_memset" ] = lle_X_memset; |
532 | Fns.FuncNames["lle_X_memcpy" ] = lle_X_memcpy; |
533 | } |
534 | |