fx.reflection: implement dynamic function calling

This commit is contained in:
2026-05-04 16:33:16 +01:00
parent 85ff8b7eaa
commit bf7d8cfb75
7 changed files with 854 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
#include <platform/callvm.h>
#include <stdlib.h>
#if 0
switch (arg->v_type.t_primitive) {
case FX_VALUE_TYPE_DOUBLE:
break;
default:
callvm_push_int(&vm, (uintptr_t)arg->v_pointer);
break;
}
#endif
void callvm_reset(struct callvm *vm, unsigned int max_fixed_args)
{
vm->vm_arg_int_count = 0;
vm->vm_arg_double_count = 0;
vm->vm_arg_fixed = max_fixed_args;
vm->vm_arg_excess_count = 0;
}
static void expand_excess(struct callvm *vm)
{
size_t new_capacity = vm->vm_arg_excess_max * 2;
if (!new_capacity) {
new_capacity = 4;
}
void *buf = realloc(
vm->vm_arg_excess,
new_capacity * sizeof *vm->vm_arg_excess);
if (!buf) {
return;
}
vm->vm_arg_excess = buf;
vm->vm_arg_excess_max = new_capacity;
}
static void push_excess(struct callvm *vm, uintptr_t value)
{
if (vm->vm_arg_excess_count + 1 > vm->vm_arg_excess_max) {
expand_excess(vm);
}
vm->vm_arg_excess[vm->vm_arg_excess_count++] = value;
vm->vm_arg_count++;
}
static void push_int(struct callvm *vm, uintptr_t value)
{
if (vm->vm_arg_int_count >= MAX_INT_ARGS) {
push_excess(vm, value);
return;
}
vm->vm_arg_int[vm->vm_arg_int_count++] = value;
vm->vm_arg_count++;
}
static void push_double(struct callvm *vm, double value)
{
if (vm->vm_arg_double_count >= MAX_DOUBLE_ARGS) {
push_excess(vm, *(uintptr_t *)&value);
return;
}
vm->vm_arg_double[vm->vm_arg_double_count++] = value;
vm->vm_arg_count++;
}
void callvm_push(struct callvm *vm, const fx_value *value)
{
if (vm->vm_arg_count >= vm->vm_arg_fixed) {
push_excess(vm, (uintptr_t)value->v_pointer);
return;
}
switch (value->v_type.t_primitive) {
case FX_VALUE_TYPE_DOUBLE:
push_double(vm, value->v_double);
break;
default:
push_int(vm, (uintptr_t)value->v_pointer);
break;
}
}
extern uintptr_t callvm_invoke_i(fx_function_impl impl, struct callvm *vm);
extern double callvm_invoke_d(fx_function_impl impl, struct callvm *vm);
extern void callvm_invoke_v(fx_function_impl impl, struct callvm *vm);
fx_value callvm_invoke(
struct callvm *vm,
fx_function_impl impl,
fx_value_type return_type)
{
switch (return_type) {
case FX_VALUE_TYPE_NONE:
callvm_invoke_v(impl, vm);
break;
case FX_VALUE_TYPE_DOUBLE:
return FX_VALUE_DOUBLE(callvm_invoke_d(impl, vm));
default:
return FX_VALUE_INT(callvm_invoke_i(impl, vm));
}
return FX_VALUE_EMPTY;
}
@@ -0,0 +1,178 @@
.global _callvm_invoke_i
.align 4
# x0 = (function ptr) impl
# x1 = (struct callvm) context
_callvm_invoke_i:
# save the stack frame and link pointer
stp x29, x30, [sp, #-16]!
mov x29, sp
# store function pointer for later
mov x16, x0
mov x15, x1
# First, set up the fixed arguments
# x8 = fixed arg count
ldr x8, [x15, #0]
# arg[0]
cmp x8, #0
ble .iv.idone
ldr x0, [x15, #48]
# arg[1]
cmp x8, #1
ble .iv.idone
ldr x1, [x15, #56]
# arg[2]
cmp x8, #2
ble .iv.idone
ldr x2, [x15, #64]
# arg[3]
cmp x8, #3
ble .iv.idone
ldr x3, [x15, #72]
# arg[4]
cmp x8, #4
ble .iv.idone
ldr x4, [x15, #80]
# arg[5]
cmp x8, #5
ble .iv.idone
ldr x5, [x15, #88]
# arg[6]
cmp x8, #6
ble .iv.idone
ldr x6, [x15, #96]
# arg[7]
cmp x8, #7
ble .iv.idone
ldr x7, [x15, #104]
.iv.idone:
# Next, set up the fixed double arguments
# x8 = fixed double arg count
ldr x8, [x15, #8]
# arg[0]
cmp x8, #0
ble .iv.vdone
ldr d0, [x15, #112]
# arg[1]
cmp x8, #1
ble .iv.vdone
ldr d1, [x15, #120]
# arg[2]
cmp x8, #2
ble .iv.vdone
ldr d2, [x15, #128]
# arg[3]
cmp x8, #3
ble .iv.vdone
ldr d3, [x15, #136]
# arg[4]
cmp x8, #4
ble .iv.vdone
ldr d4, [x15, #144]
# arg[5]
cmp x8, #5
ble .iv.vdone
ldr d5, [x15, #152]
# arg[6]
cmp x8, #6
ble .iv.vdone
ldr d6, [x15, #160]
# arg[7]
cmp x8, #7
ble .iv.vdone
ldr d7, [x15, #168]
.iv.vdone:
# Finally, set up the variable arguments
# x8 = excess arg count
ldr x8, [x15, #32]
# calculate the amount of stack space needed for the varargs
mov x10, #0x10
mul x12, x8, x10
# x19 is caller-saved, and we'll need x12 later
str x19, [sp, #-16]!
mov x19, x12
# allocate the stack space
sub sp, sp, x19
mov x13, sp
# get a pointer to the end of the stack vararg buffer
#mov x14, #8
#mul x14, x14, x11
#add x13, x13, x14
# x8: number of var args (decrements with every iteration)
# x9: arg src buffer (increments with every iteration)
# x13: var arg dest pointer (starts at the end, decrements with every iteration)
ldr x9, [x15, #176]
.iv.loop:
cmp x8, #0
beq .iv.loop.end
# read the arg value from the src pointer
ldr x14, [x9]
# write it to the stack, and increment the dest pointer
str x14, [x13]
add x13, x13, #8
# increment the src pointer, decrement the arg count
sub x8, x8, #1
add x9, x9, #8
b .iv.loop
.iv.loop.end:
# call the function implementation
blr x16
# de-allocate the stack varargs buffer (the size is now stored in x19)
add sp, sp, x19
# restore the saved value of x19,
ldr x19, [sp], #16
# restore the saved stack frame and link pointer
ldp x29,x30, [sp], #16
ret
.global _callvm_invoke_d
.align 4
# x0 = (function ptr) impl
# x1 = (struct callvm) context
_callvm_invoke_d:
b _callvm_invoke_i
.global _callvm_invoke_v
.align 4
# x0 = (function ptr) impl
# x1 = (struct callvm) context
_callvm_invoke_v:
b _callvm_invoke_i
@@ -0,0 +1,221 @@
.global _fx_function_invoke_i
.align 4
# x0 = (function ptr) impl
# x1 = (fx_value[]) args
# x2 = (size_t) nr args
_fx_function_invoke_i:
# save the stack frame and link pointer
stp x29, x30, [sp, #-16]!
# store function pointer for later
str x0, [sp, #-16]!
# move arg array of of x1 and x2, so that they can be initialised for
# the function call
mov x8, x1
mov x9, x2
# arg[0]
cmp x9, #0
ble .i.done
ldr x0, [x8, #8]
# arg[1]
cmp x9, #1
ble .i.done
ldr x1, [x8, #24]
# arg[2]
cmp x9, #2
ble .i.done
ldr x2, [x8, #40]
# arg[3]
cmp x9, #3
ble .i.done
ldr x3, [x8, #56]
# arg[4]
cmp x9, #4
ble .i.done
ldr x4, [x8, #72]
# arg[5]
cmp x9, #5
ble .i.done
ldr x5, [x8, #88]
# arg[6]
cmp x9, #6
ble .i.done
ldr x6, [x8, #104]
# arg[7]
cmp x9, #7
ble .i.done
ldr x7, [x8, #120]
.i.done:
ldr x8, [sp], #16
blr x8
# restore the saved stack frame and link pointer
ldp x29,x30, [sp], #16
ret
.global _fx_function_invoke_d
.align 4
_fx_function_invoke_d:
mov x0, 888
ret
.global _fx_function_invoke_v
.align 4
_fx_function_invoke_v:
ret
.global _fx_function_invoke_iv
.align 4
# x0 = (function ptr) impl
# x1 = (fx_value[]) args
# x2 = (size_t) nr fixed args
# x3 = (size_t) nr var args
_fx_function_invoke_iv:
# save the stack frame and link pointer
stp x29, x30, [sp, #-16]!
mov x29, sp
# store function pointer for later
mov x15, x0
# move arg array of of x1,x2,x3, so that they can be initialised for
# the function call
mov x9, x1
mov x10, x2
mov x11, x3
# First, set up the fixed arguments
# arg[0]
cmp x10, #0
ble .iv.done
ldr x0, [x9, #8]
add x9, x9, #16
# arg[1]
cmp x10, #1
ble .iv.done
ldr x1, [x9, #8]
add x9, x9, #16
# arg[2]
cmp x10, #2
ble .iv.done
ldr x2, [x9, #8]
add x9, x9, #16
# arg[3]
cmp x10, #3
ble .iv.done
ldr x3, [x9, #8]
add x9, x9, #16
# arg[4]
cmp x10, #4
ble .iv.done
ldr x4, [x9, #8]
add x9, x9, #16
# arg[5]
cmp x10, #5
ble .iv.done
ldr x5, [x9, #8]
add x9, x9, #16
# arg[6]
cmp x10, #6
ble .iv.done
ldr x6, [x9, #8]
add x9, x9, #16
# arg[7]
cmp x10, #7
ble .iv.done
ldr x7, [x9, #8]
add x9, x9, #16
.iv.done:
# Next, set up the variable arguments
# calculate the amount of stack space needed for the varargs
mov x10, #0x10
mul x12, x11, x10
#and x12, x12, #0x10
#mov x10, #0x10
#add x12, x12, x10
# x19 is caller-saved, and we'll need x12 later
str x19, [sp, #-16]!
mov x19, x12
# allocate the stack space
sub sp, sp, x19
mov x13, sp
# get a pointer to the end of the stack vararg buffer
#mov x14, #8
#mul x14, x14, x11
#add x13, x13, x14
# x9: arg src buffer (starts at varargs, increments with every iteration)
# x11: number of var args (decrements with every iteration)
# x13: var arg dest pointer (starts at the end, decrements with every iteration)
.iv.loop:
cmp x11, #0
beq .iv.loop.end
# read the arg value from the src pointer
ldr x14, [x9, #8]
# write it to the stack, and increment the dest pointer
str x14, [x13]
add x13, x13, #8
# increment the src pointer, decrement the arg count
sub x11, x11, #1
add x9, x9, #16
b .iv.loop
.iv.loop.end:
# call the function implementation
blr x15
# de-allocate the stack varargs buffer (the size is now stored in x19)
add sp, sp, x19
# restore the saved value of x19,
ldr x19, [sp], #16
# restore the saved stack frame and link pointer
ldp x29,x30, [sp], #16
ret
.global _fx_function_invoke_dv
.align 4
_fx_function_invoke_dv:
ret
.global _fx_function_invoke_vv
.align 4
_fx_function_invoke_vv:
ret
@@ -0,0 +1,36 @@
#ifndef FX_REFLECTION_DARWIN_ARM64_CALLVM_H_
#define FX_REFLECTION_DARWIN_ARM64_CALLVM_H_
#include <fx/reflection/function.h>
#include <fx/value.h>
#include <stdint.h>
#define MAX_FIXED_ARGS ((unsigned int)-1)
#define MAX_DOUBLE_ARGS 8
#define MAX_INT_ARGS 8
/* dyn-dispatch.S depends on the layout of this struct */
struct callvm {
uint64_t vm_arg_int_count;
uint64_t vm_arg_double_count;
/* any args pushed after this limit is reached will be stored in the
* excess buffer. used for calling varargs functions */
uint64_t vm_arg_count, vm_arg_fixed;
uint64_t vm_arg_excess_count;
uint64_t vm_arg_excess_max;
uintptr_t vm_arg_int[MAX_INT_ARGS];
double vm_arg_double[MAX_DOUBLE_ARGS];
uintptr_t *vm_arg_excess;
};
extern void callvm_reset(struct callvm *vm, unsigned int max_fixed_args);
extern void callvm_push(struct callvm *vm, const fx_value *value);
extern fx_value callvm_invoke(
struct callvm *vm,
fx_function_impl impl,
fx_value_type return_type);
#endif