1
0
mirror of https://github.com/janet-lang/janet synced 2025-07-07 12:32:55 +00:00

Add first iteration of call hooks.

Hooks are per thread (not per fiber) for performance, and are exposed as
callback functions. Hooks cannot be nested, so hooks are disabled inside
other hooks.
This commit is contained in:
Calvin Rose 2024-12-06 15:50:12 -06:00
parent 83e8aab289
commit 1bda40b644
4 changed files with 88 additions and 2 deletions

View File

@ -428,6 +428,16 @@ JANET_CORE_FN(cfun_debug_step,
return out;
}
JANET_CORE_FN(cfun_debug_hook,
"(debug/hook hookfn)",
"Add a hook that will be called on certain runtime events.") {
janet_arity(argc, 0, 1);
JanetFunction *func = janet_optfunction(argv, argc, 0, NULL);
janet_vm.hook = func;
janet_vm.hook_reset = func;
return janet_wrap_nil();
}
/* Module entry point */
void janet_lib_debug(JanetTable *env) {
JanetRegExt debug_cfuns[] = {
@ -440,6 +450,7 @@ void janet_lib_debug(JanetTable *env) {
JANET_CORE_REG("debug/stacktrace", cfun_debug_stacktrace),
JANET_CORE_REG("debug/lineage", cfun_debug_lineage),
JANET_CORE_REG("debug/step", cfun_debug_step),
JANET_CORE_REG("debug/hook", cfun_debug_hook),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, debug_cfuns);

View File

@ -591,6 +591,8 @@ void janet_collect(void) {
#ifdef JANET_EV
janet_ev_mark();
#endif
if (janet_vm.hook != NULL) janet_mark(janet_wrap_function(janet_vm.hook));
if (janet_vm.hook_reset != NULL) janet_mark(janet_wrap_function(janet_vm.hook_reset));
janet_mark_fiber(janet_vm.root_fiber);
for (i = 0; i < orig_rootcount; i++)
janet_mark(janet_vm.roots[i]);

View File

@ -87,6 +87,10 @@ struct JanetVM {
/* How many VM stacks have been entered */
int stackn;
/* Debug hook for advanced tracing */
JanetFunction *hook;
JanetFunction *hook_reset; /* In case of error/signal inside a hook */
/* If this flag is true, suspend on function calls and backwards jumps.
* When this occurs, this flag will be reset to 0. */
volatile JanetAtomicInt auto_suspend;

View File

@ -80,12 +80,22 @@
func = janet_stack_frame(stack)->func; \
} while (0)
#define vm_return(sig, val) do { \
janet_vm.return_reg[0] = (val); \
Janet val2 = (val); \
janet_vm.return_reg[0] = val2; \
vm_commit(); \
if (janet_vm.hook) { \
vm_do_hook_return(val2); \
janet_vm.return_reg[0] = val2; \
} \
return (sig); \
} while (0)
#define vm_return_no_restore(sig, val) do { \
janet_vm.return_reg[0] = (val); \
Janet val2 = (val); \
janet_vm.return_reg[0] = val2; \
if (janet_vm.hook) { \
vm_do_hook_return(val2); \
janet_vm.return_reg[0] = val2; \
} \
return (sig); \
} while (0)
@ -280,6 +290,36 @@ static Janet call_nonfn(JanetFiber *fiber, Janet callee) {
return janet_method_invoke(callee, argc, fiber->data + fiber->stacktop);
}
static void vm_do_hook(int32_t argc, const Janet *argv) {
JanetFunction *old_hook = janet_vm.hook;
janet_vm.hook = NULL;
janet_call(old_hook, argc, argv);
janet_vm.hook = old_hook;
}
static void vm_do_hook_call(Janet callee, int32_t argc, const Janet *argv) {
Janet argvv[3];
argvv[0] = janet_ckeywordv("call");
argvv[1] = callee;
argvv[2] = janet_wrap_tuple(janet_tuple_n(argv, argc));
vm_do_hook(3, argvv);
}
static void vm_do_hook_tailcall(Janet callee, int32_t argc, const Janet *argv) {
Janet argvv[3];
argvv[0] = janet_ckeywordv("tailcall");
argvv[1] = callee;
argvv[2] = janet_wrap_tuple(janet_tuple_n(argv, argc));
vm_do_hook(3, argvv);
}
static void vm_do_hook_return(Janet result) {
Janet argvv[2];
argvv[0] = janet_ckeywordv("return");
argvv[1] = result;
vm_do_hook(2, argvv);
}
/* Method lookup could potentially handle tables specially... */
static Janet method_to_fun(Janet method, Janet obj) {
return janet_get(obj, method);
@ -663,6 +703,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
if (entrance_frame) vm_return_no_restore(JANET_SIGNAL_OK, retval);
vm_restore();
stack[A] = retval;
if (janet_vm.hook) {
vm_do_hook_return(retval);
}
vm_checkgc_pcnext();
}
@ -673,6 +716,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
if (entrance_frame) vm_return_no_restore(JANET_SIGNAL_OK, retval);
vm_restore();
stack[A] = retval;
if (janet_vm.hook) {
vm_do_hook_return(retval);
}
vm_checkgc_pcnext();
}
@ -1013,6 +1059,10 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
if (fiber->stacktop > fiber->maxstack) {
vm_throw("stack overflow");
}
if (janet_vm.hook) {
vm_commit();
vm_do_hook_call(callee, fiber->stacktop - fiber->stackstart, fiber->data + fiber->stackstart);
}
if (janet_checktype(callee, JANET_KEYWORD)) {
vm_commit();
callee = resolve_method(callee, fiber);
@ -1039,10 +1089,16 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
janet_fiber_popframe(fiber);
stack = fiber->data + fiber->frame;
stack[A] = ret;
if (janet_vm.hook) {
vm_do_hook_return(stack[A]);
}
vm_checkgc_pcnext();
} else {
vm_commit();
stack[A] = call_nonfn(fiber, callee);
if (janet_vm.hook) {
vm_do_hook_return(stack[A]);
}
vm_pcnext();
}
}
@ -1053,6 +1109,10 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
if (fiber->stacktop > fiber->maxstack) {
vm_throw("stack overflow");
}
if (janet_vm.hook) {
vm_commit();
vm_do_hook_tailcall(callee, fiber->stacktop - fiber->stackstart, fiber->data + fiber->stackstart);
}
if (janet_checktype(callee, JANET_KEYWORD)) {
vm_commit();
callee = resolve_method(callee, fiber);
@ -1089,6 +1149,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
}
vm_restore();
stack[A] = retreg;
if (janet_vm.hook) {
vm_do_hook_return(stack[A]);
}
vm_checkgc_pcnext();
}
}
@ -1440,6 +1503,8 @@ void janet_restore(JanetTryState *state) {
janet_vm.fiber = state->vm_fiber;
janet_vm.signal_buf = state->vm_jmp_buf;
janet_vm.return_reg = state->vm_return_reg;
/* In case of error/signal thrown when inside a temporarily disabled hook */
janet_vm.hook = janet_vm.hook_reset;
}
static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *out) {
@ -1631,6 +1696,10 @@ int janet_init(void) {
/* Dynamic bindings */
janet_vm.top_dyns = NULL;
/* Hooks */
janet_vm.hook = NULL;
janet_vm.hook_reset = NULL;
/* Seed RNG */
janet_rng_seed(janet_default_rng(), 0);