From be5c83ba8e57bc67beee41bc2c7227e6b8ebd9d5 Mon Sep 17 00:00:00 2001
From: Kimplul <kimi.h.kuparinen@gmail.com>
Date: Wed, 9 Apr 2025 22:20:01 +0300
Subject: add callr_i/l/f/d

---
 include/ejit/ejit.h   |  33 ++++++++----
 src/common.h          |   6 ++-
 src/compile/compile.c |  73 +++++++++++++++++++++-----
 src/ejit.c            | 142 ++++++++++++++++++++++++++++++++++----------------
 src/interp.c          |  41 +++++++++++----
 tests/calli.c         |  41 +++++++++++++++
 tests/callr_i.c       |  42 +++++++++++++++
 7 files changed, 298 insertions(+), 80 deletions(-)
 create mode 100644 tests/calli.c
 create mode 100644 tests/callr_i.c

diff --git a/include/ejit/ejit.h b/include/ejit/ejit.h
index 5baaab6..ab06d8f 100644
--- a/include/ejit/ejit.h
+++ b/include/ejit/ejit.h
@@ -460,20 +460,33 @@ void ejit_tailr(struct ejit_func *s, struct ejit_gpr target,
 void ejit_taili(struct ejit_func *s, struct ejit_func *f,
 		size_t argc, const struct ejit_operand args[argc]);
 
-void ejit_calli(struct ejit_func *s, struct ejit_func *f, size_t argc,
-                const struct ejit_operand args[argc]);
+/* return type can be deduced */
+void ejit_calli(struct ejit_func *s, struct ejit_func *f,
+		size_t argc, const struct ejit_operand args[argc]);
 
-void ejit_escapei_i(struct ejit_func *s, ejit_escape_i_t f, size_t argc,
-                  const struct ejit_operand args[argc]);
+void ejit_callr_i(struct ejit_func *s, struct ejit_gpr target,
+		size_t argc, const struct ejit_operand args[argc]);
 
-void ejit_escapei_l(struct ejit_func *s, ejit_escape_l_t f, size_t argc,
-                  const struct ejit_operand args[argc]);
+void ejit_callr_l(struct ejit_func *s, struct ejit_gpr target,
+		size_t argc, const struct ejit_operand args[argc]);
 
-void ejit_escapei_f(struct ejit_func *s, ejit_escape_f_t f, size_t argc,
-                    const struct ejit_operand args[argc]);
+void ejit_callr_f(struct ejit_func *s, struct ejit_gpr target,
+		size_t argc, const struct ejit_operand args[argc]);
 
-void ejit_escapei_d(struct ejit_func *s, ejit_escape_d_t f, size_t argc,
-                    const struct ejit_operand args[argc]);
+void ejit_callr_d(struct ejit_func *s, struct ejit_gpr target,
+		size_t argc, const struct ejit_operand args[argc]);
+
+void ejit_escapei_i(struct ejit_func *s, ejit_escape_i_t f,
+		size_t argc, const struct ejit_operand args[argc]);
+
+void ejit_escapei_l(struct ejit_func *s, ejit_escape_l_t f,
+		size_t argc, const struct ejit_operand args[argc]);
+
+void ejit_escapei_f(struct ejit_func *s, ejit_escape_f_t f,
+		size_t argc, const struct ejit_operand args[argc]);
+
+void ejit_escapei_d(struct ejit_func *s, ejit_escape_d_t f,
+		size_t argc, const struct ejit_operand args[argc]);
 
 void ejit_ret(struct ejit_func *s);
 void ejit_retr(struct ejit_func *s, struct ejit_gpr r0);
diff --git a/src/common.h b/src/common.h
index 333c794..dc970f0 100644
--- a/src/common.h
+++ b/src/common.h
@@ -218,8 +218,12 @@ enum ejit_opcode {
 	EJIT_OP_ESCAPEI_F,
 	EJIT_OP_ESCAPEI_D,
 
-	EJIT_OP_CALLI,
+	EJIT_OP_CALLR_I,
+	EJIT_OP_CALLR_L,
+	EJIT_OP_CALLR_F,
+	EJIT_OP_CALLR_D,
 
+	EJIT_OP_CALLI,
 	EJIT_OP_TAILR,
 	EJIT_OP_TAILI,
 
diff --git a/src/compile/compile.c b/src/compile/compile.c
index 60059d5..bfcb12d 100644
--- a/src/compile/compile.c
+++ b/src/compile/compile.c
@@ -2476,17 +2476,10 @@ static size_t compile_fn_body(struct ejit_func *f, jit_state_t *j, void *arena,
 			break;
 		}
 
+		case EJIT_OP_ESCAPEI_I:
 		case EJIT_OP_ESCAPEI_L:
-#if __WORDSIZE == 64
-			  /* fallthrough */
-#else
-			  assert(0 && "trying to compile escapei_l on 32bit arch");
-			  break;
-#endif
-
-		case EJIT_OP_ESCAPEI_D:
 		case EJIT_OP_ESCAPEI_F:
-		case EJIT_OP_ESCAPEI_I: {
+		case EJIT_OP_ESCAPEI_D: {
 			save_caller_save_regs(f, j);
 
 			jit_operand_t args[2] = {
@@ -2550,15 +2543,25 @@ static size_t compile_fn_body(struct ejit_func *f, jit_state_t *j, void *arena,
 
 			assert(operands_len(&direct) <= 2);
 			jit_gpr_t r = getloc(f, j, i.r1, 0);
-			jit_ldxi(j, JIT_R0, r, offsetof(struct ejit_func, direct_call));
+
 #if defined(DEBUG)
-			/** @todo other checks? */
-			jit_reloc_t assert_reloc = jit_bnei(j, JIT_R0, 0); /* null */
+			jit_ldxi(j, JIT_R1, r, offsetof(struct ejit_func, rtype));
+			jit_reloc_t rtype_reloc = jit_beqi(j, JIT_R1, f->rtype);
+			jit_calli_1(j, assert_helper,
+					jit_operand_imm(JIT_OPERAND_ABI_POINTER,
+						(jit_imm_t)"trying to tail call different rtype"));
+
+			jit_patch_here(j, rtype_reloc);
+
+			jit_ldxi(j, JIT_R1, r, offsetof(struct ejit_func, direct_call));
+			jit_reloc_t direct_reloc = jit_bnei(j, JIT_R1, 0); /* null */
 			jit_calli_1(j, assert_helper,
 					jit_operand_imm(JIT_OPERAND_ABI_POINTER,
 						(jit_imm_t)"trying to tail call interpreted function"));
-			jit_patch_here(j, assert_reloc);
+			jit_patch_here(j, direct_reloc);
 #endif
+
+			jit_ldxi(j, JIT_R0, r, offsetof(struct ejit_func, direct_call));
 			jit_operand_t regs[2] = {
 				jit_operand_gpr(JIT_OPERAND_ABI_WORD, JIT_R1),
 				jit_operand_gpr(JIT_OPERAND_ABI_WORD, JIT_R2)
@@ -2591,6 +2594,50 @@ static size_t compile_fn_body(struct ejit_func *f, jit_state_t *j, void *arena,
 			break;
 		}
 
+		case EJIT_OP_CALLR_I:
+		case EJIT_OP_CALLR_L:
+		case EJIT_OP_CALLR_F:
+		case EJIT_OP_CALLR_D: {
+			save_caller_save_regs(f, j);
+
+			jit_gpr_t target = getgpr(f, i.r1, 0);
+
+			/* check if there's a direct call avaiable */
+			jit_ldxi(j, JIT_R1, target, offsetof(struct ejit_func, direct_call));
+			jit_reloc_t direct_reloc = jit_beqi(j, JIT_R0, 0);
+			/* we can do a jit -> jit call */
+			jit_callr(j, JIT_R1, operands_len(&direct), direct.buf);
+			jit_reloc_t out_reloc = jit_jmp(j);
+
+			jit_patch_here(j, direct_reloc);
+
+			/* we must do a jit -> bytecode call */
+			jit_operand_t args[3] = {
+				jit_operand_gpr(JIT_OPERAND_ABI_POINTER, JIT_R1),
+				jit_operand_imm(JIT_OPERAND_ABI_WORD, operands_len(&src) / 2),
+				/* compile_imm_call populate JIT_R0 with the
+				 * argument stack address */
+				jit_operand_gpr(JIT_OPERAND_ABI_POINTER, JIT_R0)
+			};
+			void *call = NULL;
+			switch (i.op) {
+			case EJIT_OP_CALLR_I: call = ejit_run_func_i; break;
+			case EJIT_OP_CALLR_L: call = ejit_run_func_l; break;
+			case EJIT_OP_CALLR_F: call = ejit_run_func_f; break;
+			case EJIT_OP_CALLR_D: call = ejit_run_func_d; break;
+			default: abort();
+			}
+
+			compile_imm_call(j, &src, &dst, call, 3, args);
+			jit_patch_here(j, out_reloc);
+			restore_caller_save_regs(f, j);
+
+			operands_reset(&src);
+			operands_reset(&dst);
+			operands_reset(&direct);
+			break;
+		}
+
 		case EJIT_OP_CALLI: {
 			save_caller_save_regs(f, j);
 
diff --git a/src/ejit.c b/src/ejit.c
index 0701b90..e8ff99e 100644
--- a/src/ejit.c
+++ b/src/ejit.c
@@ -545,10 +545,22 @@ void ejit_calli(struct ejit_func *s, struct ejit_func *f, size_t argc,
 
 	for (size_t i = 0; i < argc; ++i) {
 		switch (args[i].kind) {
-		case EJIT_OPERAND_GPR: emit_insn_ar(s, EJIT_OP_ARG, i, args[i].type, EJIT_GPR(args[i].r)); break;
-		case EJIT_OPERAND_FPR: emit_insn_af(s, EJIT_OP_ARG_F, i, args[i].type, EJIT_FPR(args[i].r)); break;
-		case EJIT_OPERAND_IMM: emit_insn_ai(s, EJIT_OP_ARG_I, i, args[i].type, args[i].r); break;
-		case EJIT_OPERAND_FLT: emit_insn_ad(s, EJIT_OP_ARG_FI, i, args[i].type, args[i].d); break;
+		case EJIT_OPERAND_GPR:
+			emit_insn_ar(s, EJIT_OP_ARG, i, args[i].type, EJIT_GPR(args[i].r));
+			break;
+
+		case EJIT_OPERAND_FPR:
+			emit_insn_af(s, EJIT_OP_ARG_F, i, args[i].type, EJIT_FPR(args[i].r));
+			break;
+
+		case EJIT_OPERAND_IMM:
+			emit_insn_ai(s, EJIT_OP_ARG_I, i, args[i].type, args[i].r);
+			break;
+
+		case EJIT_OPERAND_FLT:
+			emit_insn_ad(s, EJIT_OP_ARG_FI, i, args[i].type, args[i].d);
+			break;
+
 		default: abort();
 		}
 	}
@@ -556,73 +568,113 @@ void ejit_calli(struct ejit_func *s, struct ejit_func *f, size_t argc,
 	emit_insn_op(s, EJIT_OP_CALLI, f);
 }
 
-void ejit_escapei_i(struct ejit_func *s, ejit_escape_i_t f, size_t argc,
-                  const struct ejit_operand args[argc])
+static void ejit_callr(struct ejit_func *s, enum ejit_opcode op, struct ejit_gpr target,
+		size_t argc, const struct ejit_operand args[argc])
 {
+	s->use_64 = op == EJIT_OP_CALLR_L;
 	s->max_args = argc > s->max_args ? argc : s->max_args;
+
 	for (size_t i = 0; i < argc; ++i) {
 		switch (args[i].kind) {
-		case EJIT_OPERAND_GPR: emit_insn_ar(s, EJIT_OP_ARG, i, args[i].type, EJIT_GPR(args[i].r)); break;
-		case EJIT_OPERAND_FPR: emit_insn_af(s, EJIT_OP_ARG_F, i, args[i].type, EJIT_FPR(args[i].r)); break;
-		case EJIT_OPERAND_IMM: emit_insn_ai(s, EJIT_OP_ARG_I, i, args[i].type, args[i].r); break;
-		case EJIT_OPERAND_FLT: emit_insn_ad(s, EJIT_OP_ARG_FI, i, args[i].type, args[i].d); break;
+		case EJIT_OPERAND_GPR:
+			emit_insn_ar(s, EJIT_OP_ARG, i, args[i].type, EJIT_GPR(args[i].r));
+			break;
+
+		case EJIT_OPERAND_FPR:
+			emit_insn_af(s, EJIT_OP_ARG_F, i, args[i].type, EJIT_FPR(args[i].r));
+			break;
+
+		case EJIT_OPERAND_IMM:
+			emit_insn_ai(s, EJIT_OP_ARG_I, i, args[i].type, args[i].r);
+			break;
+
+		case EJIT_OPERAND_FLT:
+			emit_insn_ad(s, EJIT_OP_ARG_FI, i, args[i].type, args[i].d);
+			break;
+
 		default: abort();
 		}
 	}
 
-	emit_insn_op(s, EJIT_OP_ESCAPEI_I, f);
+	emit_insn_oxr(s, op, target);
 }
 
-void ejit_escapei_l(struct ejit_func *s, ejit_escape_l_t f, size_t argc,
-                  const struct ejit_operand args[argc])
+void ejit_callr_i(struct ejit_func *s, struct ejit_gpr target,
+		size_t argc, const struct ejit_operand args[argc])
 {
-	s->use_64 = true;
-	s->max_args = argc > s->max_args ? argc : s->max_args;
-	for (size_t i = 0; i < argc; ++i) {
-		switch (args[i].kind) {
-		case EJIT_OPERAND_GPR: emit_insn_ar(s, EJIT_OP_ARG, i, args[i].type, EJIT_GPR(args[i].r)); break;
-		case EJIT_OPERAND_FPR: emit_insn_af(s, EJIT_OP_ARG_F, i, args[i].type, EJIT_FPR(args[i].r)); break;
-		case EJIT_OPERAND_IMM: emit_insn_ai(s, EJIT_OP_ARG_I, i, args[i].type, args[i].r); break;
-		case EJIT_OPERAND_FLT: emit_insn_ad(s, EJIT_OP_ARG_FI, i, args[i].type, args[i].d); break;
-		default: abort();
-		}
-	}
+	ejit_callr(s, EJIT_OP_CALLR_I, target, argc, args);
+}
+
+void ejit_callr_l(struct ejit_func *s, struct ejit_gpr target,
+		size_t argc, const struct ejit_operand args[argc])
+{
+	ejit_callr(s, EJIT_OP_CALLR_L, target, argc, args);
+}
 
-	emit_insn_op(s, EJIT_OP_ESCAPEI_L, f);
+void ejit_callr_f(struct ejit_func *s, struct ejit_gpr target,
+		size_t argc, const struct ejit_operand args[argc])
+{
+	ejit_callr(s, EJIT_OP_CALLR_F, target, argc, args);
 }
 
-void ejit_escapei_f(struct ejit_func *s, ejit_escape_f_t f, size_t argc,
-                    const struct ejit_operand args[argc])
+void ejit_callr_d(struct ejit_func *s, struct ejit_gpr target,
+		size_t argc, const struct ejit_operand args[argc])
 {
+	ejit_callr(s, EJIT_OP_CALLR_D, target, argc, args);
+}
+
+static void ejit_escapei(struct ejit_func *s, enum ejit_opcode op, void *f,
+		size_t argc, const struct ejit_operand args[argc])
+{
+	s->use_64 = op == EJIT_OP_ESCAPEI_L;
 	s->max_args = argc > s->max_args ? argc : s->max_args;
 	for (size_t i = 0; i < argc; ++i) {
 		switch (args[i].kind) {
-		case EJIT_OPERAND_GPR: emit_insn_ar(s, EJIT_OP_ARG, i, args[i].type, EJIT_GPR(args[i].r)); break;
-		case EJIT_OPERAND_FPR: emit_insn_af(s, EJIT_OP_ARG_F, i, args[i].type, EJIT_FPR(args[i].r)); break;
-		case EJIT_OPERAND_IMM: emit_insn_ai(s, EJIT_OP_ARG_I, i, args[i].type, args[i].r); break;
-		case EJIT_OPERAND_FLT: emit_insn_ad(s, EJIT_OP_ARG_FI, i, args[i].type, args[i].d); break;
+		case EJIT_OPERAND_GPR:
+			emit_insn_ar(s, EJIT_OP_ARG, i, args[i].type, EJIT_GPR(args[i].r));
+			break;
+
+		case EJIT_OPERAND_FPR:
+			emit_insn_af(s, EJIT_OP_ARG_F, i, args[i].type, EJIT_FPR(args[i].r));
+			break;
+
+		case EJIT_OPERAND_IMM:
+			emit_insn_ai(s, EJIT_OP_ARG_I, i, args[i].type, args[i].r);
+			break;
+
+		case EJIT_OPERAND_FLT:
+			emit_insn_ad(s, EJIT_OP_ARG_FI, i, args[i].type, args[i].d);
+			break;
+
 		default: abort();
 		}
 	}
 
-	emit_insn_op(s, EJIT_OP_ESCAPEI_F, f);
+	emit_insn_op(s, op, f);
 }
 
-void ejit_escapei_d(struct ejit_func *s, ejit_escape_d_t f, size_t argc,
-                    const struct ejit_operand args[argc])
+void ejit_escapei_i(struct ejit_func *s, ejit_escape_i_t f,
+		size_t argc, const struct ejit_operand args[argc])
 {
-	s->max_args = argc > s->max_args ? argc : s->max_args;
-	for (size_t i = 0; i < argc; ++i) {
-		switch (args[i].kind) {
-		case EJIT_OPERAND_GPR: emit_insn_ar(s, EJIT_OP_ARG, i, args[i].type, EJIT_GPR(args[i].r)); break;
-		case EJIT_OPERAND_FPR: emit_insn_af(s, EJIT_OP_ARG_F, i, args[i].type, EJIT_FPR(args[i].r)); break;
-		case EJIT_OPERAND_IMM: emit_insn_ai(s, EJIT_OP_ARG_I, i, args[i].type, args[i].r); break;
-		case EJIT_OPERAND_FLT: emit_insn_ad(s, EJIT_OP_ARG_FI, i, args[i].type, args[i].d); break;
-		default: abort();
-		}
-	}
+	ejit_escapei(s, EJIT_OP_ESCAPEI_I, f, argc, args);
+}
+
+void ejit_escapei_l(struct ejit_func *s, ejit_escape_l_t f,
+		size_t argc, const struct ejit_operand args[argc])
+{
+	ejit_escapei(s, EJIT_OP_ESCAPEI_L, f, argc, args);
+}
 
-	emit_insn_op(s, EJIT_OP_ESCAPEI_D, f);
+void ejit_escapei_f(struct ejit_func *s, ejit_escape_f_t f,
+		size_t argc, const struct ejit_operand args[argc])
+{
+	ejit_escapei(s, EJIT_OP_ESCAPEI_F, f, argc, args);
+}
+
+void ejit_escapei_d(struct ejit_func *s, ejit_escape_d_t f,
+		size_t argc, const struct ejit_operand args[argc])
+{
+	ejit_escapei(s, EJIT_OP_ESCAPEI_D, f, argc, args);
 }
 
 void ejit_retval(struct ejit_func *s, struct ejit_gpr r0)
diff --git a/src/interp.c b/src/interp.c
index 268bfb3..6f94f98 100644
--- a/src/interp.c
+++ b/src/interp.c
@@ -213,14 +213,18 @@ union interp_ret ejit_run(struct ejit_func *f, size_t paramc, struct ejit_arg pa
 		[EJIT_OP_PARAM] = &&PARAM,
 		[EJIT_OP_PARAM_F] = &&PARAM_F,
 
+		[EJIT_OP_CALLR_I] = &&CALLR_I,
+		[EJIT_OP_CALLR_L] = &&CALLR_L,
+		[EJIT_OP_CALLR_F] = &&CALLR_F,
+		[EJIT_OP_CALLR_D] = &&CALLR_D,
 		[EJIT_OP_CALLI] = &&CALLI,
 
 		[EJIT_OP_TAILR] = &&TAILR,
 		[EJIT_OP_TAILI] = &&TAILI,
 
 		[EJIT_OP_ESCAPEI_I] = &&ESCAPEI_I,
-		[EJIT_OP_ESCAPEI_F] = &&ESCAPEI_F,
 		[EJIT_OP_ESCAPEI_L] = &&ESCAPEI_L,
+		[EJIT_OP_ESCAPEI_F] = &&ESCAPEI_F,
 		[EJIT_OP_ESCAPEI_D] = &&ESCAPEI_D,
 
 		[EJIT_OP_START] = &&START,
@@ -1084,33 +1088,48 @@ top:
 	goto top;
 	DISPATCH();
 
+	DO(CALLR_I);
+	retval = ejit_run((struct ejit_func *)gpr[i.r1], argc, args, NULL);
+	argc = 0;
+	DISPATCH();
+
+	DO(CALLR_L);
+	retval = ejit_run((struct ejit_func *)gpr[i.r1], argc, args, NULL);
+	argc = 0;
+	DISPATCH();
+
+	DO(CALLR_F);
+	retval = ejit_run((struct ejit_func *)gpr[i.r1], argc, args, NULL);
+	argc = 0;
+	DISPATCH();
+
+	DO(CALLR_D);
+	retval = ejit_run((struct ejit_func *)gpr[i.r1], argc, args, NULL);
+	argc = 0;
+	DISPATCH();
+
 	DO(CALLI);
-	struct ejit_func *f = i.p;
-	retval = ejit_run(f, argc, args, NULL);
+	retval = ejit_run((struct ejit_func *)i.p, argc, args, NULL);
 	argc = 0;
 	DISPATCH();
 
 	DO(ESCAPEI_I);
-	ejit_escape_i_t f = i.p;
-	retval.i = f(argc, args);
+	retval.i = ((ejit_escape_i_t)i.p)(argc, args);
 	argc = 0;
 	DISPATCH();
 
 	DO(ESCAPEI_L);
-	ejit_escape_l_t f = i.p;
-	retval.i = f(argc, args);
+	retval.i = ((ejit_escape_l_t)i.p)(argc, args);
 	argc = 0;
 	DISPATCH();
 
 	DO(ESCAPEI_F);
-	ejit_escape_f_t f = i.p;
-	retval.f = f(argc, args);
+	retval.f = ((ejit_escape_f_t)i.p)(argc, args);
 	argc = 0;
 	DISPATCH();
 
 	DO(ESCAPEI_D);
-	ejit_escape_d_t f = i.p;
-	retval.f = f(argc, args);
+	retval.f = ((ejit_escape_d_t)i.p)(argc, args);
 	argc = 0;
 	DISPATCH();
 
diff --git a/tests/calli.c b/tests/calli.c
new file mode 100644
index 0000000..991e97d
--- /dev/null
+++ b/tests/calli.c
@@ -0,0 +1,41 @@
+#include <ejit/ejit.h>
+#include <assert.h>
+#include "do_jit.h"
+
+struct ejit_func *compile(bool do_jit)
+{
+	struct ejit_operand operands[2] = {
+		EJIT_OPERAND_GPR(0, EJIT_TYPE(long)),
+		EJIT_OPERAND_GPR(1, EJIT_TYPE(long))
+	};
+	struct ejit_func *f = ejit_create_func(EJIT_TYPE(long), 2, operands);
+	ejit_addr(f, EJIT_GPR(0), EJIT_GPR(0), EJIT_GPR(1));
+	ejit_retr(f, EJIT_GPR(0));
+	ejit_select_compile_func(f, 2, 0, EJIT_USE64(long), do_jit, true);
+	return f;
+}
+
+int main(int argc, char *argv[])
+{
+	(void)argv;
+	bool do_jit = argc > 1;
+	struct ejit_func *target = compile(do_jit);
+
+	struct ejit_operand operands[2] = {
+		EJIT_OPERAND_GPR(0, EJIT_TYPE(long)),
+		EJIT_OPERAND_GPR(1, EJIT_TYPE(long))
+	};
+
+	struct ejit_func *f = ejit_create_func(EJIT_TYPE(long), 2, operands);
+	ejit_calli(f, target, 2, operands);
+	ejit_retval(f, EJIT_GPR(0));
+	ejit_retr(f, EJIT_GPR(0));
+	ejit_select_compile_func(f, 2, 0, EJIT_USE64(long), do_jit, true);
+
+	assert(erfi2(f,
+	                       EJIT_ARG(42, long),
+	                       EJIT_ARG(69, long)) == 111);
+
+	ejit_destroy_func(target);
+	ejit_destroy_func(f);
+}
diff --git a/tests/callr_i.c b/tests/callr_i.c
new file mode 100644
index 0000000..00b5374
--- /dev/null
+++ b/tests/callr_i.c
@@ -0,0 +1,42 @@
+#include <ejit/ejit.h>
+#include <assert.h>
+#include "do_jit.h"
+
+struct ejit_func *compile(bool do_jit)
+{
+	struct ejit_operand operands[2] = {
+		EJIT_OPERAND_GPR(0, EJIT_TYPE(long)),
+		EJIT_OPERAND_GPR(1, EJIT_TYPE(long))
+	};
+	struct ejit_func *f = ejit_create_func(EJIT_TYPE(long), 2, operands);
+	ejit_addr(f, EJIT_GPR(0), EJIT_GPR(0), EJIT_GPR(1));
+	ejit_retr(f, EJIT_GPR(0));
+	ejit_select_compile_func(f, 2, 0, EJIT_USE64(long), do_jit, true);
+	return f;
+}
+
+int main(int argc, char *argv[])
+{
+	(void)argv;
+	bool do_jit = argc > 1;
+	struct ejit_func *target = compile(do_jit);
+
+	struct ejit_operand operands[2] = {
+		EJIT_OPERAND_GPR(0, EJIT_TYPE(long)),
+		EJIT_OPERAND_GPR(1, EJIT_TYPE(long))
+	};
+
+	struct ejit_func *f = ejit_create_func(EJIT_TYPE(long), 2, operands);
+	ejit_movi(f, EJIT_GPR(2), (uintptr_t)target);
+	ejit_callr_i(f, EJIT_GPR(2), 2, operands);
+	ejit_retval(f, EJIT_GPR(0));
+	ejit_retr(f, EJIT_GPR(0));
+	ejit_select_compile_func(f, 3, 0, EJIT_USE64(long), do_jit, true);
+
+	assert(erfi2(f,
+	                       EJIT_ARG(42, long),
+	                       EJIT_ARG(69, long)) == 111);
+
+	ejit_destroy_func(target);
+	ejit_destroy_func(f);
+}
-- 
cgit v1.2.3