aboutsummaryrefslogtreecommitdiff
path: root/src/lj_ccallback.c
diff options
context:
space:
mode:
authorMike Pall <mike>2011-11-14 14:15:57 +0100
committerMike Pall <mike>2011-11-14 14:18:25 +0100
commit71d00a56dbab6c29c0346093dbe530d7b7608be4 (patch)
tree5e28e19b4d2f20168d5ee0e4fe500b1e2b233c1c /src/lj_ccallback.c
parente9eb4fdb4a08baaa2d9190187a6c38d5b3f8b091 (diff)
downloadluajit-71d00a56dbab6c29c0346093dbe530d7b7608be4.tar.gz
luajit-71d00a56dbab6c29c0346093dbe530d7b7608be4.tar.bz2
luajit-71d00a56dbab6c29c0346093dbe530d7b7608be4.zip
FFI: Add callback support (for x86/x64).
Diffstat (limited to 'src/lj_ccallback.c')
-rw-r--r--src/lj_ccallback.c461
1 files changed, 461 insertions, 0 deletions
diff --git a/src/lj_ccallback.c b/src/lj_ccallback.c
new file mode 100644
index 00000000..8aab7f36
--- /dev/null
+++ b/src/lj_ccallback.c
@@ -0,0 +1,461 @@
1/*
2** FFI C callback handling.
3** Copyright (C) 2005-2011 Mike Pall. See Copyright Notice in luajit.h
4*/
5
6#include "lj_obj.h"
7
8#if LJ_HASFFI
9
10#include "lj_gc.h"
11#include "lj_err.h"
12#include "lj_tab.h"
13#include "lj_state.h"
14#include "lj_frame.h"
15#include "lj_ctype.h"
16#include "lj_cconv.h"
17#include "lj_ccall.h"
18#include "lj_ccallback.h"
19#include "lj_target.h"
20#include "lj_vm.h"
21
22/* -- Target-specific handling of callback slots -------------------------- */
23
24#define CALLBACK_MCODE_SIZE (LJ_PAGESIZE * LJ_NUM_CBPAGE)
25
26#if LJ_TARGET_X86ORX64
27
28#define CALLBACK_MCODE_HEAD (LJ_64 ? 8 : 0)
29#define CALLBACK_MCODE_GROUP (-2+1+2+5+(LJ_64 ? 6 : 5))
30
31#define CALLBACK_SLOT2OFS(slot) \
32 (CALLBACK_MCODE_HEAD + CALLBACK_MCODE_GROUP*((slot)/32) + 4*(slot))
33
34static MSize CALLBACK_OFS2SLOT(MSize ofs)
35{
36 MSize group;
37 ofs -= CALLBACK_MCODE_HEAD;
38 group = ofs / (32*4 + CALLBACK_MCODE_GROUP);
39 return (ofs % (32*4 + CALLBACK_MCODE_GROUP))/4 + group*32;
40}
41
42#define CALLBACK_MAX_SLOT \
43 (((CALLBACK_MCODE_SIZE-CALLBACK_MCODE_HEAD)/(CALLBACK_MCODE_GROUP+4*32))*32)
44
45#else
46
47/* Missing support for this architecture. */
48#define CALLBACK_SLOT2OFS(slot) (0*(slot))
49#define CALLBACK_OFS2SLOT(ofs) (0*(ofs))
50#define CALLBACK_MAX_SLOT 0
51
52#endif
53
54/* Convert callback slot number to callback function pointer. */
55static void *callback_slot2ptr(CTState *cts, MSize slot)
56{
57 return (uint8_t *)cts->cb.mcode + CALLBACK_SLOT2OFS(slot);
58}
59
60/* Convert callback function pointer to slot number. */
61MSize lj_ccallback_ptr2slot(CTState *cts, void *p)
62{
63 uintptr_t ofs = (uintptr_t)((uint8_t *)p -(uint8_t *)cts->cb.mcode);
64 if (ofs < CALLBACK_MCODE_SIZE) {
65 MSize slot = CALLBACK_OFS2SLOT((MSize)ofs);
66 if (CALLBACK_SLOT2OFS(slot) == (MSize)ofs)
67 return slot;
68 }
69 return ~0u; /* Not a known callback function pointer. */
70}
71
72#if LJ_TARGET_X86ORX64
73/* Initialize machine code for callback function pointers. */
74static void callback_mcode_init(global_State *g, uint8_t *page)
75{
76 uint8_t *p = page;
77 uint8_t *target = (uint8_t *)(void *)lj_vm_ffi_callback;
78 MSize slot;
79#if LJ_64
80 *(void **)p = target; p += 8;
81#endif
82 for (slot = 0; slot < CALLBACK_MAX_SLOT; slot++) {
83 /* mov al, slot; jmp group */
84 *p++ = XI_MOVrib | RID_EAX; *p++ = (uint8_t)slot;
85 if ((slot & 31) == 31 || slot == CALLBACK_MAX_SLOT-1) {
86 /* push ebp/rbp; mov ah, slot>>8; mov ebp, &g. */
87 *p++ = XI_PUSH + RID_EBP;
88 *p++ = XI_MOVrib | (RID_EAX+4); *p++ = (uint8_t)(slot >> 8);
89 *p++ = XI_MOVri | RID_EBP;
90 *(int32_t *)p = i32ptr(g); p += 4;
91#if LJ_64
92 /* jmp [rip-pageofs] where lj_vm_ffi_callback is stored. */
93 *p++ = XI_GROUP5; *p++ = XM_OFS0 + (XOg_JMP<<3) + RID_EBP;
94 *(int32_t *)p = (int32_t)(page-(p+4)); p += 4;
95#else
96 /* jmp lj_vm_ffi_callback. */
97 *p++ = XI_JMP; *(int32_t *)p = target-(p+4); p += 4;
98#endif
99 } else {
100 *p++ = XI_JMPs; *p++ = (uint8_t)((2+2)*(31-(slot&31)) - 2);
101 }
102 }
103 lua_assert(p - page <= CALLBACK_MCODE_SIZE);
104}
105#else
106/* Missing support for this architecture. */
107#define callback_mcode_init(g, p) UNUSED(p)
108#endif
109
110/* -- Machine code management --------------------------------------------- */
111
112#if LJ_TARGET_WINDOWS
113
114#define WIN32_LEAN_AND_MEAN
115#include <windows.h>
116
117#elif LJ_TARGET_POSIX
118
119#include <sys/mman.h>
120#ifndef MAP_ANONYMOUS
121#define MAP_ANONYMOUS MAP_ANON
122#endif
123
124#endif
125
126/* Allocate and initialize area for callback function pointers. */
127static void callback_mcode_new(CTState *cts)
128{
129 size_t sz = (size_t)CALLBACK_MCODE_SIZE;
130 void *p;
131 if (CALLBACK_MAX_SLOT == 0)
132 lj_err_caller(cts->L, LJ_ERR_FFI_CBACKOV);
133#if LJ_TARGET_WINDOWS
134 p = VirtualAlloc(NULL, sz, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
135 if (!p)
136 lj_err_caller(cts->L, LJ_ERR_FFI_CBACKOV);
137#elif LJ_TARGET_POSIX
138 p = mmap(NULL, sz, (PROT_READ|PROT_WRITE), MAP_PRIVATE|MAP_ANONYMOUS,
139 -1, 0);
140 if (p == MAP_FAILED)
141 lj_err_caller(cts->L, LJ_ERR_FFI_CBACKOV);
142#else
143 /* Fallback allocator. Fails if memory is not executable by default. */
144 p = lj_mem_new(cts->L, sz);
145#endif
146 cts->cb.mcode = p;
147 callback_mcode_init(cts->g, p);
148#if LJ_TARGET_WINDOWS
149 {
150 DWORD oprot;
151 VirtualProtect(p, sz, PAGE_EXECUTE_READ, &oprot);
152 }
153#elif LJ_TARGET_POSIX
154 mprotect(p, sz, (PROT_READ|PROT_EXEC));
155#endif
156}
157
158/* Free area for callback function pointers. */
159void lj_ccallback_mcode_free(CTState *cts)
160{
161 size_t sz = (size_t)CALLBACK_MCODE_SIZE;
162 void *p = cts->cb.mcode;
163 if (p == NULL) return;
164#if LJ_TARGET_WINDOWS
165 VirtualFree(p, 0, MEM_RELEASE);
166 UNUSED(sz);
167#elif LJ_TARGET_POSIX
168 munmap(p, sz);
169#else
170 lj_mem_free(cts->g, p, sz);
171#endif
172}
173
174/* -- C callback entry ---------------------------------------------------- */
175
176/* Target-specific handling of register arguments. Similar to lj_ccall.c. */
177#if LJ_TARGET_X86
178
179#define CALLBACK_HANDLE_REGARG \
180 if (!isfp) { /* Only non-FP values may be passed in registers. */ \
181 if (n > 1) { /* Anything > 32 bit is passed on the stack. */ \
182 if (!LJ_ABI_WIN) ngpr = maxgpr; /* Prevent reordering. */ \
183 } else if (ngpr + 1 <= maxgpr) { \
184 sp = &cts->cb.gpr[ngpr]; \
185 ngpr += n; \
186 goto done; \
187 } \
188 }
189
190#elif LJ_TARGET_X64 && LJ_ABI_WIN
191
192/* Windows/x64 argument registers are strictly positional (use ngpr). */
193#define CALLBACK_HANDLE_REGARG \
194 if (isfp) { \
195 if (ngpr < 4) { sp = &cts->cb.fpr[ngpr++]; nfpr = ngpr; goto done; } \
196 } else { \
197 if (ngpr < 4) { sp = &cts->cb.gpr[ngpr++]; goto done; } \
198 }
199
200#elif LJ_TARGET_X64
201
202#define CALLBACK_HANDLE_REGARG \
203 if (isfp) { \
204 if (nfpr + n <= CCALL_NARG_FPR) { \
205 sp = &cts->cb.fpr[nfpr]; \
206 nfpr += n; \
207 goto done; \
208 } \
209 } else { \
210 if (ngpr + n <= maxgpr) { \
211 sp = &cts->cb.gpr[ngpr]; \
212 ngpr += n; \
213 goto done; \
214 } \
215 }
216
217#elif LJ_TARGET_ARM
218
219#define CALLBACK_HANDLE_REGARG \
220 UNUSED(ngpr); UNUSED(maxgpr); goto done; /* NYI */
221
222#elif LJ_TARGET_PPC
223
224#define CALLBACK_HANDLE_REGARG \
225 UNUSED(ngpr); UNUSED(nfpr); UNUSED(maxgpr); goto done; /* NYI */
226#define CALLBACK_HANDLE_RET /* NYI */
227
228#else
229#error "Missing calling convention definitions for this architecture"
230#endif
231
232/* Convert and push callback arguments to Lua stack. */
233static void callback_conv_args(CTState *cts, lua_State *L)
234{
235 TValue *o = L->top;
236 intptr_t *stack = cts->cb.stack;
237 MSize slot = cts->cb.slot;
238 CTypeID id = 0, rid, fid;
239 CType *ct;
240 GCfunc *fn;
241 MSize ngpr = 0, nsp = 0, maxgpr = CCALL_NARG_GPR;
242#if CCALL_NARG_FPR
243 MSize nfpr = 0;
244#endif
245
246 if (slot < cts->cb.sizeid && (id = cts->cb.cbid[slot]) != 0) {
247 ct = ctype_get(cts, id);
248 rid = ctype_cid(ct->info);
249 fn = funcV(lj_tab_getint(cts->miscmap, (int32_t)slot));
250 } else { /* Must set up frame first, before throwing the error. */
251 ct = NULL;
252 rid = 0;
253 fn = (GCfunc *)L;
254 }
255 o->u32.lo = LJ_CONT_FFI_CALLBACK; /* Continuation returns from callback. */
256 o->u32.hi = rid; /* Return type. x86: +(spadj<<16). */
257 o++;
258 setframe_gc(o, obj2gco(fn));
259 setframe_ftsz(o, (int)((char *)(o+1) - (char *)L->base) + FRAME_CONT);
260 L->top = L->base = ++o;
261 if (!ct)
262 lj_err_caller(cts->L, LJ_ERR_FFI_BADCBACK);
263 if (isluafunc(fn))
264 setcframe_pc(L->cframe, proto_bc(funcproto(fn))+1);
265 lj_state_checkstack(L, LUA_MINSTACK); /* May throw. */
266 o = L->base; /* Might have been reallocated. */
267
268#if LJ_TARGET_X86
269 /* x86 has several different calling conventions. */
270 switch (ctype_cconv(ct->info)) {
271 case CTCC_FASTCALL: maxgpr = 2; break;
272 case CTCC_THISCALL: maxgpr = 1; break;
273 default: maxgpr = 0; break;
274 }
275#endif
276
277 fid = ct->sib;
278 while (fid) {
279 CType *ctf = ctype_get(cts, fid);
280 if (!ctype_isattrib(ctf->info)) {
281 CType *cta;
282 void *sp;
283 CTSize sz;
284 int isfp;
285 MSize n;
286 lua_assert(ctype_isfield(ctf->info));
287 cta = ctype_rawchild(cts, ctf);
288 if (ctype_isenum(cta->info)) cta = ctype_child(cts, cta);
289 isfp = ctype_isfp(cta->info);
290 sz = (cta->size + CTSIZE_PTR-1) & ~(CTSIZE_PTR-1);
291 n = sz / CTSIZE_PTR; /* Number of GPRs or stack slots needed. */
292
293 CALLBACK_HANDLE_REGARG /* Handle register arguments. */
294
295 /* Otherwise pass argument on stack. */
296 if (CCALL_ALIGN_STACKARG && LJ_32 && sz == 8)
297 nsp = (nsp + 1) & ~1u; /* Align 64 bit argument on stack. */
298 sp = &stack[nsp];
299 nsp += n;
300
301 done:
302 if (LJ_BE && cta->size < CTSIZE_PTR)
303 sp = (void *)((uint8_t *)sp + CTSIZE_PTR-cta->size);
304 lj_cconv_tv_ct(cts, cta, 0, o++, sp);
305 }
306 fid = ctf->sib;
307 }
308 L->top = o;
309#if LJ_TARGET_X86
310 /* Store stack adjustment for returns from fastcall/stdcall callbacks. */
311 switch (ctype_cconv(ct->info)) {
312 case CTCC_FASTCALL: case CTCC_STDCALL:
313 (L->base-2)->u32.hi |= (nsp << (16+2));
314 break;
315 }
316#endif
317}
318
319/* Convert Lua object to callback result. */
320static void callback_conv_result(CTState *cts, lua_State *L, TValue *o)
321{
322 CType *ctr = ctype_raw(cts, (uint16_t)(L->base-2)->u32.hi);
323#if LJ_TARGET_X86
324 cts->cb.gpr[2] = 0;
325#endif
326 if (!ctype_isvoid(ctr->info)) {
327 uint8_t *dp = (uint8_t *)&cts->cb.gpr[0];
328#ifdef CALLBACK_HANDLE_RET
329 CALLBACK_HANDLE_RET
330#endif
331#if CCALL_NUM_FPR
332 if (ctype_isfp(ctr->info))
333 dp = (uint8_t *)&cts->cb.fpr[0];
334#endif
335 lj_cconv_ct_tv(cts, ctr, dp, o, 0);
336 /* Extend returned integers to (at least) 32 bits. */
337 if (ctype_isinteger_or_bool(ctr->info) && ctr->size < 4) {
338 if (ctr->info & CTF_UNSIGNED)
339 *(uint32_t *)dp = ctr->size == 1 ? (uint32_t)*(uint8_t *)dp :
340 (uint32_t)*(uint16_t *)dp;
341 else
342 *(int32_t *)dp = ctr->size == 1 ? (int32_t)*(int8_t *)dp :
343 (int32_t)*(int16_t *)dp;
344 }
345#if LJ_TARGET_X86
346 if (ctype_isfp(ctr->info))
347 cts->cb.gpr[2] = ctr->size == sizeof(float) ? 1 : 2;
348#endif
349 }
350}
351
352/* Enter callback. */
353lua_State * LJ_FASTCALL lj_ccallback_enter(CTState *cts, void *cf)
354{
355 lua_State *L = cts->L;
356 lua_assert(L != NULL);
357 if (gcref(cts->g->jit_L))
358 lj_err_caller(gco2th(gcref(cts->g->jit_L)), LJ_ERR_FFI_BADCBACK);
359 /* Setup C frame. */
360 cframe_prev(cf) = L->cframe;
361 setcframe_L(cf, L);
362 cframe_errfunc(cf) = -1;
363 cframe_nres(cf) = 0;
364 L->cframe = cf;
365 callback_conv_args(cts, L);
366 return L; /* Now call the function on this stack. */
367}
368
369/* Leave callback. */
370void LJ_FASTCALL lj_ccallback_leave(CTState *cts, TValue *o)
371{
372 lua_State *L = cts->L;
373 GCfunc *fn;
374 TValue *obase = L->base;
375 L->base = L->top; /* Keep continuation frame for throwing errors. */
376 /* PC of RET* is lost. Point to last line for result conv. errors. */
377 fn = curr_func(L);
378 if (isluafunc(fn)) {
379 GCproto *pt = funcproto(fn);
380 setcframe_pc(L->cframe, proto_bc(pt)+pt->sizebc);
381 }
382 callback_conv_result(cts, L, o);
383 /* Finally drop C frame and continuation frame. */
384 L->cframe = cframe_prev(L->cframe);
385 L->top -= 2;
386 L->base = obase;
387}
388
389/* -- C callback management ----------------------------------------------- */
390
391/* Get an unused slot in the callback slot table. */
392static MSize callback_slot_new(CTState *cts, CType *ct)
393{
394 CTypeID id = ctype_typeid(cts, ct);
395 CTypeID1 *cbid = cts->cb.cbid;
396 MSize top;
397 for (top = cts->cb.topid; top < cts->cb.sizeid; top++)
398 if (LJ_LIKELY(cbid[top] == 0))
399 goto found;
400#if CALLBACK_MAX_SLOT
401 if (top >= CALLBACK_MAX_SLOT)
402#endif
403 lj_err_caller(cts->L, LJ_ERR_FFI_CBACKOV);
404 if (!cts->cb.mcode)
405 callback_mcode_new(cts);
406 lj_mem_growvec(cts->L, cbid, cts->cb.sizeid, CALLBACK_MAX_SLOT, CTypeID1);
407 cts->cb.cbid = cbid;
408 memset(cbid+top, 0, (cts->cb.sizeid-top)*sizeof(CTypeID1));
409found:
410 cbid[top] = id;
411 cts->cb.topid = top+1;
412 return top;
413}
414
415/* Check for function pointer and supported argument/result types. */
416static CType *callback_checkfunc(CTState *cts, CType *ct)
417{
418 int narg = 0;
419 if (!ctype_isptr(ct->info) || (LJ_64 && ct->size != CTSIZE_PTR))
420 return NULL;
421 ct = ctype_rawchild(cts, ct);
422 if (ctype_isfunc(ct->info)) {
423 CType *ctr = ctype_rawchild(cts, ct);
424 CTypeID fid = ct->sib;
425 if (!(ctype_isvoid(ctr->info) || ctype_isenum(ctr->info) ||
426 ctype_isptr(ctr->info) || (ctype_isnum(ctr->info) && ctr->size <= 8)))
427 return NULL;
428 if ((ct->info & CTF_VARARG))
429 return NULL;
430 while (fid) {
431 CType *ctf = ctype_get(cts, fid);
432 if (!ctype_isattrib(ctf->info)) {
433 CType *cta;
434 lua_assert(ctype_isfield(ctf->info));
435 cta = ctype_rawchild(cts, ctf);
436 if (!(ctype_isenum(cta->info) || ctype_isptr(cta->info) ||
437 (ctype_isnum(cta->info) && cta->size <= 8)) ||
438 ++narg >= LUA_MINSTACK-3)
439 return NULL;
440 }
441 fid = ctf->sib;
442 }
443 return ct;
444 }
445 return NULL;
446}
447
448/* Create a new callback and return the callback function pointer. */
449void *lj_ccallback_new(CTState *cts, CType *ct, GCfunc *fn)
450{
451 ct = callback_checkfunc(cts, ct);
452 if (ct) {
453 MSize slot = callback_slot_new(cts, ct);
454 GCtab *t = cts->miscmap;
455 setfuncV(cts->L, lj_tab_setint(cts->L, t, (int32_t)slot), fn);
456 return callback_slot2ptr(cts, slot);
457 }
458 return NULL; /* Bad conversion. */
459}
460
461#endif