-From dff0c180819449c742ed77b2cb542b5a95c1f0d5 Mon Sep 17 00:00:00 2001
+From e00e8aaa56093d8d1a5f89b7fa7dd1c09dc7a4c1 Mon Sep 17 00:00:00 2001
From: gns <wangbingzhen.riscv@isrc.iscas.ac.cn>
-Date: Thu, 27 Nov 2025 20:48:19 +0800
+Date: Tue, 16 Jun 2026 09:10:36 +0000
Subject: [PATCH] Add RISC-V 64 support
This backports RISC-V 64 support from openresty/luajit2 PR #236.
- JIT compiler, assembler, and emitter implementations
- FFI calling conventions and Linux-specific support
+Refreshed against the current state of the pull request, which adapts
+asm_tail_prep() to the TraceNo argument added by openresty/luajit2 commit
+68354f44 and so restores the build on riscv64.
+
Original work by gns from PLCT Lab, ISCAS.
See: https://github.com/openresty/luajit2/pull/236
ifeq (,$(findstring Windows,$(OS)))
--- /dev/null
+++ b/dynasm/dasm_riscv.h
-@@ -0,0 +1,435 @@
+@@ -0,0 +1,433 @@
+/*
+** DynASM RISC-V encoding engine.
-+** Copyright (C) 2005-2025 Mike Pall. All rights reserved.
++** Copyright (C) 2022-2026 ISRC, ISCAS. All rights reserved.
+** Released under the MIT license. See dynasm.lua for full copyright notice.
-+**
-+** Contributed by gns from PLCT Lab, ISCAS.
+*/
+
+#include <stddef.h>
+------------------------------------------------------------------------------
+-- DynASM RISC-V module.
+--
-+-- Copyright (C) 2005-2025 Mike Pall. All rights reserved.
++-- Copyright (C) 2022-2026 ISRC, ISCAS.
+-- See dynasm.lua for full copyright notice.
+--
-+-- Contributed by gns from PLCT Lab, ISCAS.
++-- Contributed by gns from PLCT Lab, ISRC, ISCAS.
+------------------------------------------------------------------------------
+
+local riscv32 = riscv32
+ version = "1.5.0",
+ vernum = 10500,
+ release = "2022-07-12",
-+ author = "Mike Pall",
++ author = "gns",
+ license = "MIT",
+}
+
+------------------------------------------------------------------------------
+-- DynASM RISC-V 32 module.
+--
-+-- Copyright (C) 2005-2025 Mike Pall. All rights reserved.
++-- Copyright (C) 2022-2026 ISRC, ISCAS. All rights reserved.
+-- See dynasm.lua for full copyright notice.
+------------------------------------------------------------------------------
+-- This module just sets 32 bit mode for the combined RISC-V module.
+------------------------------------------------------------------------------
+-- DynASM RISC-V 64 module.
+--
-+-- Copyright (C) 2005-2025 Mike Pall. All rights reserved.
++-- Copyright (C) 2022-2026 ISRC, ISCAS. All rights reserved.
+-- See dynasm.lua for full copyright notice.
+------------------------------------------------------------------------------
+-- This module just sets 64 bit mode for the combined RISC-V module.
+------------------------------------------------------------------------------
+-- LuaJIT RISC-V disassembler module.
+--
-+-- Copyright (C) 2005-2025 Mike Pall. All rights reserved.
++-- Copyright (C) 2022-2026 ISRC, ISCAS. All rights reserved.
+-- Released under the MIT license. See Copyright Notice in luajit.h
+--
+-- Contributed by Milos Poletanovic from Syrmia.com.
-+-- Contributed by gns from PLCT Lab, ISCAS.
++-- Contributed by gns from PLCT Lab, ISRC, ISCAS.
+------------------------------------------------------------------------------
+-- This is a helper module used by the LuaJIT machine code dumper module.
+--
+----------------------------------------------------------------------------
+-- LuaJIT RISC-V 64 disassembler wrapper module.
+--
-+-- Copyright (C) 2005-2025 Mike Pall. All rights reserved.
++-- Copyright (C) 2022-2026 ISRC, ISCAS. All rights reserved.
+-- Released under the MIT license. See Copyright Notice in luajit.h
+----------------------------------------------------------------------------
+-- This module just exports the default riscv little-endian functions from the
+ disass = dis_riscv.disass,
+ regname = dis_riscv.regname
+}
-\ No newline at end of file
--- a/src/lib_jit.c
+++ b/src/lib_jit.c
-@@ -706,6 +706,104 @@ JIT_PARAMDEF(JIT_PARAMINIT)
+@@ -713,6 +713,104 @@ JIT_PARAMDEF(JIT_PARAMINIT)
#include <sys/utsname.h>
#endif
/* Arch-dependent CPU feature detection. */
static uint32_t jit_cpudetect(void)
{
-@@ -778,6 +876,29 @@ static uint32_t jit_cpudetect(void)
+@@ -785,6 +883,29 @@ static uint32_t jit_cpudetect(void)
#endif
#elif LJ_TARGET_S390X
/* No optional CPU features to detect (for now). */
#else
#error "Architecture not supported (in this version), see: https://luajit.org/status.html#architectures"
#endif
-@@ -506,6 +510,20 @@
+@@ -508,6 +512,20 @@
#define LJ_ARCH_NUMMODE LJ_NUMMODE_DUAL
#define LJ_TARGET_GC64 1
#define LJ_ARCH_NOJIT 1 /* NYI */
#else
#error "No target architecture defined"
-@@ -590,6 +608,10 @@
+@@ -592,6 +610,10 @@
#error "Only n64 ABI supported for MIPS64"
#undef LJ_TARGET_MIPS
#endif
#endif
--- /dev/null
+++ b/src/lj_asm_riscv64.h
-@@ -0,0 +1,2037 @@
+@@ -0,0 +1,2049 @@
+/*
+** RISC-V IR assembler (SSA IR -> machine code).
-+** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice in luajit.h
++** Copyright (C) 2022-2026 ISRC, ISCAS. See Copyright Notice in luajit.h
+**
-+** Contributed by gns from PLCT Lab, ISCAS.
++** Contributed by gns from PLCT Lab, ISRC, ISCAS.
+*/
+
+/* -- Register allocator extensions --------------------------------------- */
+static void asm_exitstub_setup(ASMState *as, ExitNo nexits)
+{
+ ExitNo i;
++ MCode *target = (MCode *)(void *)lj_vm_exit_handler;
+ MCode *mxp = as->mctop;
+ if (mxp - (nexits + 4 + MCLIM_REDZONE) < as->mclim)
+ asm_mclimit(as);
+ for (i = nexits-1; (int32_t)i >= 0; i--)
+ *--mxp = RISCVI_JAL | RISCVF_D(RID_RA) | RISCVF_IMMJ((uintptr_t)(4*(-4-i)));
-+ ptrdiff_t delta = (char *)lj_vm_exit_handler - (char *)(mxp-3);
-+ /* 1: sw ra, 0(sp); auipc+jalr ->vm_exit_handler; lui x0, traceno; jal <1; jal <1; ... */
++ ptrdiff_t delta = (char *)target - (char *)(mxp-3);
++ /* !ind: 1: sw ra, 0(sp); auipc+jalr ->vm_exit_handler; lui x0, traceno; jal <1; jal <1; ...
++ ** ind: 1: sw ra, 0(sp); ld tmp, K64_VXH(gl); jalr tmp; lui x0, traceno; jal <1; jal <1; ...
++ ** Note: RID_TMP is RID_RA!
++ */
+ *--mxp = RISCVI_LUI | RISCVF_IMMU(as->T->traceno);
-+ *--mxp = RISCVI_JALR | RISCVF_D(RID_RA) | RISCVF_S1(RID_TMP)
-+ | RISCVF_IMMI(RISCVF_LO((uintptr_t)(void *)delta));
-+ *--mxp = RISCVI_AUIPC | RISCVF_D(RID_TMP)
-+ | RISCVF_IMMU(RISCVF_HI((uintptr_t)(void *)delta));
++ if (checki32auipc(delta)) {
++ *--mxp = RISCVI_JALR | RISCVF_D(RID_RA) | RISCVF_S1(RID_TMP)
++ | RISCVF_IMMI(RISCVF_LO((uintptr_t)(void *)delta));
++ *--mxp = RISCVI_AUIPC | RISCVF_D(RID_TMP)
++ | RISCVF_IMMU(RISCVF_HI((uintptr_t)(void *)delta));
++ } else {
++ *--mxp = RISCVI_JALR | RISCVF_D(RID_RA) | RISCVF_S1(RID_TMP) | RISCVF_IMMI(0);
++ *--mxp = RISCVI_LD | RISCVF_D(RID_TMP) | RISCVF_S1(RID_GL)
++ | RISCVF_IMMI(glofs(as, &as->J->k64[LJ_K64_VM_EXIT_HANDLER]));
++ lj_assertA(checki12(glofs(as, &as->J->k64[LJ_K64_VM_EXIT_HANDLER])),
++ "exit handler address offset overflow");
++ }
+ *--mxp = RISCVI_SD | RISCVF_S2(RID_RA) | RISCVF_S1(RID_SP);
+ as->mctop = mxp;
+}
+ MCode *p = as->mcp;
+ if (LJ_UNLIKELY(p == as->invmcp)) {
+ as->loopinv = 1;
-+ as->mcp = ++p;
+ *p = RISCVI_JAL | RISCVF_IMMJ((char *)target - (char *)p);
+ riscvi = riscvi^RISCVF_FUNCT3(1); /* Invert cond. */
+ target = p - 1; /* Patch target later in asm_loop_fixup. */
+ } else {
+ Reg left = ra_alloc1(as, lref, RSET_FPR);
+ Reg dest = ra_dest(as, ir, RSET_GPR);
-+ RISCVIns riscvi = irt_is64(ir->t) ?
-+ (st == IRT_NUM ?
-+ (irt_isi64(ir->t) ? RISCVI_FCVT_L_D : RISCVI_FCVT_LU_D) :
-+ (irt_isi64(ir->t) ? RISCVI_FCVT_L_S : RISCVI_FCVT_LU_S)) :
-+ (st == IRT_NUM ?
-+ (irt_isint(ir->t) ? RISCVI_FCVT_W_D : RISCVI_FCVT_WU_D) :
-+ (irt_isint(ir->t) ? RISCVI_FCVT_W_S : RISCVI_FCVT_WU_S));
-+ emit_ds(as, riscvi|RISCVF_RM(RISCVRM_RTZ), dest, left);
++ lj_assertA(!irt_isu32(ir->t), "bad CONV u32.fp emitted");
++ if (irt_isu64(ir->t)) {
++ MCLabel l_end = emit_label(as);
++ emit_ds(as, st == IRT_NUM ? RISCVI_FCVT_L_D : RISCVI_FCVT_L_S, dest, left);
++ emit_branch(as, RISCVI_BNE, dest, RID_ZERO, l_end, -1);
++ emit_ds(as, st == IRT_NUM ? RISCVI_FCVT_LU_D : RISCVI_FCVT_LU_S, dest, left);
++ } else {
++ RISCVIns riscvi = irt_is64(ir->t) ?
++ (st == IRT_NUM ? RISCVI_FCVT_L_D : RISCVI_FCVT_L_S) :
++ (st == IRT_NUM ? RISCVI_FCVT_W_D : RISCVI_FCVT_W_S);
++ emit_ds(as, riscvi|RISCVF_RM(RISCVRM_RTZ), dest, left);
++ }
+ }
+ } else if (st >= IRT_I8 && st <= IRT_U16) { /* Extend to 32 bit integer. */
+ Reg dest = ra_dest(as, ir, RSET_GPR);
+ emit_ds(as, RISCVI_FCVT_D_L, ftmp, RID_TMP);
+ }
+ emit_ds(as, riscvi, RID_TMP, left);
-+ emit_branch(as, RISCVI_BLT, RID_ZERO, RID_TMP, l_end, 0);
++ emit_branch(as, RISCVI_BLT, RID_ZERO, RID_TMP, l_end, -1);
+ emit_dsi(as, RISCVI_ADDI, RID_TMP, RID_TMP, -1075);
+ emit_dsi(as, RISCVI_ANDI, RID_TMP, RID_TMP, 0x7ff);
+ emit_dsi(as, RISCVI_SRLI, RID_TMP, RID_TMP, 52);
+ Reg left = ra_alloc1(as, ir->op1, RSET_GPR);
+ uint32_t shmsk = irt_is64(ir->t) ? 63 : 31;
+ if (irref_isk(ir->op2)) { /* Constant shifts. */
-+ uint32_t shift = (uint32_t)(IR(ir->op2)->i & shmsk);
++ IRIns *irr = IR(ir->op2);
++ uint32_t shift = (uint32_t) irr->o == IR_KINT ? (uint32_t)irr->i : (uint32_t)ir_kint64(irr)->u64;
+ switch (riscvik) {
+ case RISCVI_SRAI: case RISCVI_SRLI: case RISCVI_SLLI:
+ case RISCVI_SRAIW: case RISCVI_SLLIW: case RISCVI_SRLIW:
+/* Fixup the tail code. */
+static void asm_tail_fixup(ASMState *as, TraceNo lnk)
+{
-+ MCode *p = as->mctop;
-+ MCode *target = lnk ? traceref(as->J,lnk)->mcode : (MCode *)lj_vm_exit_interp;
++ MCode *mcp = as->mctail;
++ MCode *target;
+ int32_t spadj = as->T->spadjust;
-+ if (spadj == 0) {
-+ p[-3] = RISCVI_NOP;
-+ // as->mctop = p-2;
++ if (spadj) { /* Emit stack adjustment */
++ *mcp++ = RISCVI_ADDI | RISCVF_D(RID_SP) | RISCVF_S1(RID_SP) | RISCVF_IMMI(spadj);
++ }
++ /* Emit exit jump. */
++ target = lnk ? traceref(as->J,lnk)->mcode : (MCode *)lj_vm_exit_interp;
++ ptrdiff_t delta = (char *)target - (char *)mcp;
++ if (lnk || checki32auipc(delta)) {
++ *mcp++ = RISCVI_AUIPC | RISCVF_D(RID_TMP) | RISCVF_IMMU(RISCVF_HI(delta));
++ *mcp++ = RISCVI_JALR | RISCVF_S1(RID_TMP) | RISCVF_IMMI(RISCVF_LO(delta));
+ } else {
-+ /* Patch stack adjustment. */
-+ p[-3] = RISCVI_ADDI | RISCVF_D(RID_SP) | RISCVF_S1(RID_SP) | RISCVF_IMMI(spadj);
++ *mcp++ = RISCVI_LD | RISCVF_D(RID_TMP) | RISCVF_S1(RID_GL) | RISCVF_IMMI(glofs(as, &as->J->k64[LJ_K64_VM_EXIT_INTERP]));
++ *mcp++ = RISCVI_JALR | RISCVF_S1(RID_TMP);
+ }
-+ /* Patch exit jump. */
-+ ptrdiff_t delta = (char *)target - (char *)(p - 2);
-+ p[-2] = RISCVI_AUIPC | RISCVF_D(RID_TMP) | RISCVF_IMMU(RISCVF_HI(delta));
-+ p[-1] = RISCVI_JALR | RISCVF_S1(RID_TMP) | RISCVF_IMMI(RISCVF_LO(delta));
++ while (as->mctop > mcp) *--as->mctop = RISCVI_NOP; /* NOP out unused space. */
+}
+
+/* Prepare tail of code. */
-+static void asm_tail_prep(ASMState *as)
++static void asm_tail_prep(ASMState *as, TraceNo lnk)
+{
-+ MCode *p = as->mctop - 2; /* Leave room for exitstub. */
++ MCode *p = as->mctop - 1; /* Leave room for exitstub. */
+ if (as->loopref) {
-+ as->invmcp = as->mcp = p;
++ as->invmcp = as->mcp = p; /* A single jump */
+ } else {
-+ as->mcp = p-1; /* Leave room for stack pointer adjustment. */
++ as->mcp = (p -= 2); /* Stack pointer adjustment and AUIPC+JALR */
+ as->invmcp = NULL;
++ p[0] = p[1] = p[2] = RISCVI_EBREAK;
+ }
-+ p[0] = p[1] = RISCVI_NOP; /* Prevent load/store merging. */
++ as->mctail = p;
+}
+
+/* -- Trace setup --------------------------------------------------------- */
+ }
+ /* Ignore jump slot overflow. Child trace is simply not attached. */
+ }
-+ } else if (p+2 == pe) {
-+ if (p[0] == RISCVI_NOP && p[1] == RISCVI_NOP) {
-+ ptrdiff_t delta = (char *)target - (char *)p;
-+ lj_assertJ(checki32(delta), "jump target out of range");
-+ p[0] = RISCVI_AUIPC | RISCVF_D(RID_TMP) | RISCVF_IMMU(RISCVF_HI(delta));
-+ p[1] = RISCVI_JALR | RISCVF_S1(RID_TMP) | RISCVF_IMMI(RISCVF_LO(delta));
-+ if (!cstart) cstart = p;
-+ }
+ }
+ }
+ if (cstart) lj_mcode_sync(cstart, px+1);
+}
--- a/src/lj_ccall.c
+++ b/src/lj_ccall.c
-@@ -687,6 +687,146 @@
+@@ -689,6 +689,197 @@
if (ngpr < maxgpr) { dp = &cc->gpr[ngpr++]; goto done; } \
}
+#elif LJ_TARGET_RISCV64
+/* -- RISC-V lp64d calling conventions ------------------------------------ */
+
++/* Forward declarations for functions used by macros below. */
++#define CCALL_MIX_MAXFIELDS 2 /* MIX state machine has exactly 2 slots. */
++typedef struct {
++ CTSize offset;
++ uint8_t esz;
++ uint8_t isfp;
++} CCallMixField;
++
++typedef struct {
++ uint8_t nfields;
++ CCallMixField fields[CCALL_MIX_MAXFIELDS];
++} CCallMixFieldList;
++
++static CTSize ccall_place_mix_fields(CCallState *cc, CCallMixFieldList *list,
++ CTSize sz, uint8_t *src,
++ MSize *pnfpr, MSize *pngpr, int isva);
++static void ccall_unplace_mix_fields(CCallState *cc, CCallMixFieldList *list,
++ uint8_t *dp);
++
+#define CCALL_HANDLE_STRUCTRET \
-+ /* Return structs of size > 16 by reference. */ \
-+ cc->retref = !(sz <= 16); \
++ /* Return by reference only if >16 bytes and not classifiable as MIX. */ \
++ cc->retref = !ccall_classify_struct(cts, ctr, NULL, 0).ispod && sz > 16; \
+ if (cc->retref) cc->gpr[ngpr++] = (GPRArg)dp;
+
+#define CCALL_HANDLE_STRUCTRET2 \
-+ CCallStructClass cl = ccall_classify_struct(cts, ctr); \
++{ \
++ CCallMixFieldList mixlist = {0}; \
++ CCallStructClass cl = ccall_classify_struct(cts, ctr, &mixlist, 0); \
+ CCallStructMix mix = cl.mix; \
+ switch (mix.val) { \
-+ case MIX_IX: { \
-+ ((intptr_t *)dp)[0] = cc->gpr[0]; \
-+ break; \
-+ } \
-+ case MIX_FX: case MIX_DX: \
-+ case MIX_FF: case MIX_FD: \
-+ case MIX_DF: case MIX_DD: { \
-+ eCCallStructMixElem es[2] = { mix.e1, mix.e2 }; \
-+ for (int ti = 0; ti < 2; ti++) { \
-+ if (es[ti] == MIX_ELEM_FLOAT) { \
-+ ((float *)dp)[ti] = cc->fpr[ti].f; \
-+ } else /*if (es[ti] == MIX_ELEM_DOUBLE)*/ { \
-+ ((double *)dp)[ti] = cc->fpr[ti].d; \
-+ } \
-+ } \
-+ break; \
-+ } \
-+ case MIX_FI: case MIX_DI: \
-+ case MIX_IF: case MIX_ID: { \
-+ eCCallStructMixElem es[2] = { mix.e1, mix.e2 }; \
-+ for (int ti = 0; ti < 2; ti++) { \
-+ if (es[ti] == MIX_ELEM_FLOAT) { \
-+ ((float *)dp)[ti] = cc->fpr[0].f; \
-+ } else if (es[ti] == MIX_ELEM_DOUBLE) { \
-+ ((double *)dp)[ti] = cc->fpr[0].d; \
-+ } else /*if (es[ti] == MIX_ELEM_INT)*/ { \
-+ ((intptr_t *)dp)[ti] = cc->gpr[0]; \
-+ } \
-+ } \
++ case MIX_IX: case MIX_FX: \
++ case MIX_FF: case MIX_IF: case MIX_FI: { \
++ ccall_unplace_mix_fields(cc, &mixlist, (uint8_t *)dp); \
+ break; \
+ } \
+ case MIX_UNINIT: \
+ break; \
+ } \
+ default: lj_assertL(0, "Invalid ret mix %d", mix.val); \
-+ }
++ } \
++}
+
+#define CCALL_HANDLE_COMPLEXRET \
+ /* Complex values are returned in 1 or 2 FPRs. */ \
+ mix = (CCallStructMix){ .val = MIX_FF }; \
+ sz = CTSIZE_PTR; \
+ } else /*if (sz == 2*sizeof(double))*/ { \
-+ mix = (CCallStructMix){ .val = MIX_DD }; \
++ mix = (CCallStructMix){ .val = MIX_FF }; \
+ sz = 2*CTSIZE_PTR; \
+ }
+
+ sp = (uint8_t *)&cc->fpr[0].f;
+
+#define CCALL_HANDLE_STRUCTARG \
-+ /* Pass structs of size >16 by reference. */ \
-+ CCallStructClass cl = ccall_classify_struct(cts, d); \
++ /* Pass structs of size >16 by reference if not classifiable as MIX. */ \
++ CCallMixFieldList mixlist = {0}; \
++ CCallStructClass cl = ccall_classify_struct(cts, d, &mixlist, 0); \
+ uint8_t ispod = cl.ispod; \
+ mix = cl.mix; \
++ /* Check if MIX registers available; fall back to integer conv if not */ \
++ if (ispod && mix.val != MIX_FAILED && mix.val != MIX_UNINIT) { \
++ int need_fpr = 0, need_gpr = 0; \
++ switch (mix.val) { \
++ case MIX_FX: need_fpr = 1; break; \
++ case MIX_FF: need_fpr = 2; break; \
++ case MIX_FI: case MIX_IF: need_fpr = 1; need_gpr = 1; break; \
++ default: lj_assertCTS(0, "Invalid MIX %d", mix.val); \
++ } \
++ if ((need_fpr && nfpr + need_fpr > CCALL_NARG_FPR) || \
++ (need_gpr && ngpr + need_gpr > CCALL_NARG_GPR)) \
++ mix.val = MIX_FAILED; /* Fall back to integer convention */ \
++ } \
++ /* Integer convention >2xXLEN: pass by reference */ \
++ if (ispod && (mix.val == MIX_FAILED || mix.val == MIX_UNINIT) && sz > 16) \
++ ispod = 0; /* Route to by-reference path below. */ \
+ if (!ispod && sz > 16) { \
+ rp = cdataptr(lj_cdata_new(cts, did, sz)); \
+ sz = CTSIZE_PTR; \
++ } else if (ispod) { \
++ /* POD struct: convert to stack, then place fields in registers. */ \
++ MSize al = (1u << ctype_align(ccall_struct_align(cts, d))) - 1; \
++ MSize ns = (nsp + al) & ~al; \
++ if (ns + sz > CCALL_SIZE_STACK) goto err_nyi; \
++ uint8_t *src = (uint8_t *)cc->stack + ns; \
++ lj_cconv_ct_tv(cts, d, src, o, CCF_ARG(narg)); \
++ if (mix.val == MIX_FAILED || mix.val == MIX_UNINIT) { \
++ /* Integer convention: place 8-byte chunks in GPRs, rest on stack. */ \
++ int nchunks = (sz + 7) >> 3; \
++ int chunks_avail = CCALL_NARG_GPR - (int)ngpr; \
++ int i; \
++ for (i = 0; i < chunks_avail && i < nchunks; i++) \
++ cc->gpr[ngpr++] = ((GPRArg *)src)[i]; \
++ if (i < nchunks) { \
++ memmove(src, src + i * 8, sz - i * 8); \
++ nsp = ns + (nchunks - i) * 8; \
++ } \
++ } else { \
++ /* MIX convention: place fields from flat descriptor list. */ \
++ CTSize tail = ccall_place_mix_fields(cc, &mixlist, sz, \
++ src, &nfpr, &ngpr, isva); \
++ if (tail < sz) { \
++ memmove(src, src + tail, sz - tail); \
++ nsp = ns + (sz - tail); \
++ } \
++ } \
++ continue; \
+ }
+
-+
+#define CCALL_HANDLE_REGARG \
-+ if (!isva) { /* Try determine MIX registers. */ \
-+ int n2 = 0; \
-+ switch (mix.val) { \
-+ case MIX_UNINIT: \
-+ if (isfp) { \
-+ n2 = 1; \
-+ break; \
-+ } \
-+ /* fallthrough */ \
-+ case MIX_FAILED: \
-+ /* MIX_[IFD]X are just like a standalone element */ \
-+ case MIX_IX: goto reghandle_gpr; \
-+ case MIX_FX: case MIX_DX: \
-+ n2 = 1; \
-+ break; \
-+ /* MIX_[FD][FD] are just like two standalone elements */ \
-+ /* fix float later */ \
-+ case MIX_FF: case MIX_DD: \
-+ case MIX_FD: case MIX_DF: \
-+ n2 = 2; \
-+ break; \
-+ /* Setup MIX_I[FD] or MIX[FD]I on stack first, fix later */ \
-+ default: goto reghandle_exit; \
-+ } \
-+ if (nfpr + n2 <= CCALL_NARG_FPR) { \
++ if (!isva) { \
++ if (isfp && nfpr + 1 <= CCALL_NARG_FPR) { \
+ dp = &cc->fpr[nfpr]; \
-+ nfpr += n2; \
++ nfpr += 1; \
+ goto done; \
-+ } else { \
-+ if (ngpr + n2 <= maxgpr) { \
-+ dp = &cc->gpr[ngpr]; \
-+ ngpr += n2; \
-+ goto done; \
-+ } \
+ } \
-+ } else { /* Try to pass argument in GPRs. */ \
-+ reghandle_gpr: \
-+ if (ngpr + n <= maxgpr) { \
-+ dp = &cc->gpr[ngpr]; \
-+ ngpr += n; \
-+ goto done; \
-+ } \
-+ reghandle_exit: \
++ } \
++ if (ngpr + n <= maxgpr) { \
++ dp = &cc->gpr[ngpr]; \
++ ngpr += n; \
++ goto done; \
++ }
++
++/* Place MIX struct fields from a flat descriptor list into registers.
++** Returns byte offset of first unplaced field (sz if all placed). */
++static CTSize ccall_place_mix_fields(CCallState *cc, CCallMixFieldList *list,
++ CTSize sz, uint8_t *src,
++ MSize *pnfpr, MSize *pngpr,
++ int isva)
++{
++ for (int i = 0; i < list->nfields; i++) {
++ CCallMixField *f = &list->fields[i];
++ uint64_t val;
++ if (f->isfp) {
++ val = (f->esz == 4) ? (*(uint32_t *)(src + f->offset) | U64x(ffffffff,00000000))
++ : *(uint64_t *)(src + f->offset);
++ if (!isva && *pnfpr < CCALL_NARG_FPR) cc->fpr[(*pnfpr)++].u = val;
++ else if (*pngpr < CCALL_NARG_GPR) cc->gpr[(*pngpr)++] = val;
++ else return f->offset;
++ } else {
++ val = (f->esz < 8) ? (uint64_t)(int64_t)*(int32_t *)(src + f->offset)
++ : *(uint64_t *)(src + f->offset);
++ if (*pngpr < CCALL_NARG_GPR) cc->gpr[(*pngpr)++] = val;
++ else return f->offset;
++ }
+ }
++ return sz; /* all placed */
++}
++
++/* Reconstruct MIX struct fields from registers using flat descriptor list. */
++static void ccall_unplace_mix_fields(CCallState *cc, CCallMixFieldList *list,
++ uint8_t *dp)
++{
++ int gpi = 0, fpi = 0;
++ for (int i = 0; i < list->nfields; i++) {
++ CCallMixField *f = &list->fields[i];
++ if (f->isfp) {
++ if (f->esz == 4) *(float *)(dp + f->offset) = cc->fpr[fpi++].f;
++ else *(double *)(dp + f->offset) = cc->fpr[fpi++].d;
++ } else {
++ if (f->esz < 8) *(int32_t *)(dp + f->offset) = (int32_t)cc->gpr[gpi++];
++ else *(int64_t *)(dp + f->offset) = cc->gpr[gpi++];
++ }
++ }
++}
+
#else
#error "Missing calling convention definitions for this architecture"
#endif
-@@ -1055,6 +1195,164 @@ static void ccall_copy_struct(CCallState
+@@ -1085,6 +1276,206 @@ static void ccall_copy_struct(CCallState
#endif
+/* RISC-V 64 LP64D fp reg struct classification. */
+/* X: unknown/uninit, F: float, D: double, I: integer */
+
++/* Flat descriptor for a single field element produced by classification.
++** Place/unplace iterate this list instead of re-walking the ctype tree. */
++#define CCALL_MIX_MAXFIELDS 2 /* MIX state machine has exactly 2 slots. */
++
++/* Add a field descriptor to the flat list (if space remains). */
++static LJ_AINLINE void ccall_add_mix_field(CCallMixFieldList *list,
++ CTSize offset, CTSize esz, int isfp)
++{
++ if (list && list->nfields < CCALL_MIX_MAXFIELDS) {
++ CCallMixField *f = &list->fields[list->nfields++];
++ f->offset = offset;
++ f->esz = (uint8_t)esz;
++ f->isfp = (uint8_t)isfp;
++ }
++}
++
+typedef enum eCCallStructMixElem {
+ MIX_ELEM_UNINIT = 0,
+ MIX_ELEM_FLOAT = 1,
-+ MIX_ELEM_DOUBLE = 2,
-+ MIX_ELEM_INT = 3,
++ MIX_ELEM_INT = 2,
+} eCCallStructMixElem;
+typedef enum eCCallStructMix {
-+ MIX_UNINIT = 0, // i.e. MIX_XX
-+ MIX_FX = 1,
-+ MIX_DX = 2,
-+ MIX_IX = 3,
-+ MIX_XF = 4,
-+ MIX_FF = 5,
-+ MIX_DF = 6,
-+ MIX_IF = 7,
-+ MIX_XD = 8,
-+ MIX_FD = 9,
-+ MIX_DD = 10,
-+ MIX_ID = 11,
-+ MIX_XI = 12,
-+ MIX_FI = 13,
-+ MIX_DI = 14,
-+ MIX_FAILED = 15, // MIX_II but that's not mixed
++ MIX_UNINIT = 0, /* XX */
++ MIX_FX = 1, /* F X — single float/double */
++ MIX_IX = 2, /* I X — single int */
++ MIX_FF = 5, /* F F — two floats/doubles */
++ MIX_IF = 6, /* I F — int then float/double */
++ MIX_FI = 9, /* F I — float/double then int */
++ MIX_FAILED = 10 /* anything else */
+} eCCallStructMix;
+
+typedef union CCallStructMix {
+ };
+} CCallStructClass;
+
-+static CCallStructClass ccall_classify_struct(CTState *cts, CType *ct)
++static CCallStructClass ccall_classify_struct(CTState *cts, CType *ct,
++ CCallMixFieldList *list, CTSize base_ofs)
+{
+ CTSize sz = ct->size;
+ CCallStructMix mix = { .val = MIX_UNINIT };
+ unsigned int m = 1;
+ CType *sct;
+ ct = ctype_get(cts, ct->sib);
-+ if (ctype_isfield(ct->info)) {
-+ sct = ctype_rawchild(cts, ct);
-+ if (ctype_isarray(sct->info)) {
-+ CType *cct = ctype_rawchild(cts, sct);
-+ if (!cct->size) continue;
-+ m = sct->size / cct->size;
-+ sct = cct;
-+ }
-+ if (ctype_isfp(sct->info)) {
-+ while (m--) {
-+ /* Mix state trans: fp
-+ * mix XX -> mix = [FD]X
-+ * mix X[IFD] -> mix = FAILED
-+ * mix [IFD]X -> mix = [IFD][FD]
-+ * mix [IFD][IFD] -> mix = FAILED
-+ */
-+ eCCallStructMixElem ne = (sct->size == 4) ? MIX_ELEM_FLOAT : MIX_ELEM_DOUBLE;
-+ if (mix.val == MIX_UNINIT) {
-+ mix = (CCallStructMix){ .e1 = ne, .e2 = MIX_ELEM_UNINIT };
-+ } else {
-+ eCCallStructMixElem o1 = mix.e1, o2 = mix.e2, n2 = ne;
-+ mix = (o2 != MIX_ELEM_UNINIT ? (CCallStructMix){ .val = MIX_FAILED }
-+ : (CCallStructMix){ .e1 = o1, .e2 = n2 });
-+ }
-+ }
-+ } else if (ctype_iscomplex(sct->info)) {
-+ while (m--) {
-+ /* Mix state trans: complex
-+ * mix XX -> mix = [FD][FD]
-+ * mix other -> mix = FAILED
-+ */
-+ eCCallStructMixElem ne = (sct->size == 8) ? MIX_ELEM_FLOAT : MIX_ELEM_DOUBLE;
-+ mix = (mix.val == MIX_UNINIT) ? (CCallStructMix){ .e1 = ne, .e2 = ne }
-+ : (CCallStructMix){ .val = MIX_FAILED };
-+ }
-+ } else if (ctype_isinteger_or_bool(sct->info) || ctype_isenum(sct->info)) {
-+ while (m--) {
-+ /* Mix state trans: int
-+ * mix XX -> mix = IX
-+ * mix X[IFD] -> mix = FAILED
-+ * mix [IFD]X -> mix = [FD]I; this auto fails II
-+ * mix [IFD][IFD] -> mix = FAILED
-+ */
-+ if (mix.val == MIX_UNINIT) {
-+ mix = (CCallStructMix){ .e1 = MIX_ELEM_INT, .e2 = MIX_ELEM_UNINIT };
-+ } else {
-+ eCCallStructMixElem o1 = mix.e1, o2 = mix.e2, n2 = MIX_ELEM_INT;
-+ mix = (o2 != MIX_ELEM_UNINIT ? (CCallStructMix){ .val = MIX_FAILED }
-+ : (CCallStructMix){ .e1 = o1, .e2 = n2 });
-+ }
-+ }
-+ } else if (ctype_isstruct(sct->info)) {
-+ goto substruct;
-+ } else {
-+ goto not_ag;
-+ }
-+ } else if (ctype_isbitfield(ct->info)) {
-+ goto not_ag;
-+ } else if (ctype_isxattrib(ct->info, CTA_SUBTYPE)) {
-+ sct = ctype_rawchild(cts, ct);
-+ substruct:
-+ if (sct->size > 0) {
-+ CCallStructClass s = ccall_classify_struct(cts, sct);
-+ CCallStructMix smix = s.mix;
-+ uint8_t spod = s.ispod;
-+ if (smix.val == MIX_FAILED) mix.val = MIX_FAILED;
-+ if (!spod) goto not_ag;
-+ while (m--) {
-+ /* Mix state transfer: substruct
-+ * mix XX, smix any -> mix = smix
-+ * mix X[IFD], smix any -> mix = FAILED
-+ * mix [IFD]X, smix [XIFD]X -> mix = [IFD][XIFD]
-+ * smix other -> mix = FAILED
-+ * mix [IFD][IFD], smix XX -> mix = mix
-+ * smix other -> mix = FAILED; this keep II fail
-+ */
-+ if (mix.val == MIX_UNINIT) {
-+ mix = smix;
-+ } else {
-+ eCCallStructMixElem o1 = mix.e1, o2 = mix.e2;
-+ eCCallStructMixElem n1 = smix.e1, n2 = smix.e2;
-+ if (o2 != MIX_ELEM_UNINIT) {
-+ mix = (smix.val != MIX_UNINIT) ? mix
-+ : (CCallStructMix){ .val = MIX_FAILED };
-+ } else {
-+ mix = (n2 != MIX_ELEM_UNINIT) ? (CCallStructMix){ .val = MIX_FAILED }
-+ : (CCallStructMix){ .e1 = o1, .e2 = n1 };
-+ }
-+ }
-+ }
++ {
++ CTSize fofs = ct->size; /* Field offset within this struct. */
++ if (ctype_isfield(ct->info)) {
++ sct = ctype_rawchild(cts, ct);
++ if (ctype_isarray(sct->info) && !sct->size) continue;
++ while (ctype_isarray(sct->info)) {
++ CType *cct = ctype_rawchild(cts, sct);
++ m *= sct->size / cct->size;
++ sct = cct;
++ }
++ {
++ CTSize esz = sct->size;
++ if (ctype_isfp(sct->info)) {
++ unsigned int mi;
++ for (mi = 0; mi < m; mi++) {
++ /* Mix state trans: fp (float or double)
++ * mix XX -> mix = FX
++ * mix X[IF] -> mix = FAILED
++ * mix [IF]X -> mix = [IF]F
++ * mix [IF][IF] -> mix = FAILED
++ */
++ eCCallStructMixElem ne = MIX_ELEM_FLOAT;
++ if (mix.val == MIX_UNINIT) {
++ mix = (CCallStructMix){ .e1 = ne, .e2 = MIX_ELEM_UNINIT };
++ } else {
++ eCCallStructMixElem o1 = mix.e1, o2 = mix.e2, n2 = ne;
++ mix = (o2 != MIX_ELEM_UNINIT ? (CCallStructMix){ .val = MIX_FAILED }
++ : (CCallStructMix){ .e1 = o1, .e2 = n2 });
++ }
++ ccall_add_mix_field(list, base_ofs + fofs + mi*esz, esz, 1);
++ }
++ } else if (ctype_iscomplex(sct->info)) {
++ unsigned int mi;
++ CType *esct = ctype_rawchild(cts, sct);
++ CTSize cesz = esct->size;
++ for (mi = 0; mi < m; mi++) {
++ /* Mix state trans: complex
++ * mix XX -> mix = FF
++ * mix other -> mix = FAILED
++ */
++ eCCallStructMixElem ne = MIX_ELEM_FLOAT;
++ mix = (mix.val == MIX_UNINIT) ? (CCallStructMix){ .e1 = ne, .e2 = ne }
++ : (CCallStructMix){ .val = MIX_FAILED };
++ ccall_add_mix_field(list, base_ofs + fofs + mi*cesz*2, cesz, 1);
++ ccall_add_mix_field(list, base_ofs + fofs + mi*cesz*2 + cesz, cesz, 1);
++ }
++ } else if (ctype_isinteger_or_bool(sct->info) || ctype_isenum(sct->info)) {
++ unsigned int mi;
++ for (mi = 0; mi < m; mi++) {
++ /* Mix state trans: int
++ * mix XX -> mix = IX
++ * mix X[IF] -> mix = FAILED
++ * mix [IF]X -> mix = [IF]I; this auto fails II
++ * mix [IF][IF] -> mix = FAILED
++ */
++ if (mix.val == MIX_UNINIT) {
++ mix = (CCallStructMix){ .e1 = MIX_ELEM_INT, .e2 = MIX_ELEM_UNINIT };
++ } else {
++ eCCallStructMixElem o1 = mix.e1, o2 = mix.e2, n2 = MIX_ELEM_INT;
++ mix = (o2 != MIX_ELEM_UNINIT ? (CCallStructMix){ .val = MIX_FAILED }
++ : (CCallStructMix){ .e1 = o1, .e2 = n2 });
++ }
++ ccall_add_mix_field(list, base_ofs + fofs + mi*esz, esz, 0);
++ }
++ } else if (ctype_isstruct(sct->info)) {
++ goto substruct;
++ } else {
++ goto not_ag;
++ }
++ }
++ } else if (ctype_isbitfield(ct->info) && ctype_bitbsz(ct->info)) {
++ /* Non-zero bitfield: treat as integer element for MIX classification. */
++ CTSize fofs = ct->size;
++ sct = ctype_rawchild(cts, ct);
++ {
++ /* Mix state trans: bitfield
++ * mix XX -> mix = IX
++ * mix X[IF] -> mix = FAILED
++ * mix [IF]X -> mix = [IF]I
++ * mix [IF][IF] -> mix = FAILED
++ */
++ if (mix.val == MIX_UNINIT) {
++ mix = (CCallStructMix){ .e1 = MIX_ELEM_INT, .e2 = MIX_ELEM_UNINIT };
++ } else {
++ eCCallStructMixElem o1 = mix.e1, o2 = mix.e2, n2 = MIX_ELEM_INT;
++ mix = (o2 != MIX_ELEM_UNINIT ? (CCallStructMix){ .val = MIX_FAILED }
++ : (CCallStructMix){ .e1 = o1, .e2 = n2 });
++ }
++ ccall_add_mix_field(list, base_ofs + fofs, sct->size, 0);
++ }
++ } else if (ctype_isxattrib(ct->info, CTA_SUBTYPE)) {
++ sct = ctype_rawchild(cts, ct);
++ substruct:
++ if (sct->size > 0) {
++ CCallStructClass s = ccall_classify_struct(cts, sct, list,
++ base_ofs + fofs);
++ CCallStructMix smix = s.mix;
++ uint8_t spod = s.ispod;
++ if (smix.val == MIX_FAILED) mix.val = MIX_FAILED;
++ if (!spod) goto not_ag;
++ while (m--) {
++ /* Mix state transfer: substruct
++ * mix XX, smix any -> mix = smix
++ * mix X[IF], smix any -> mix = FAILED
++ * mix [IF]X, smix [XIF]X -> mix = [IF][XIF]
++ * smix other -> mix = FAILED
++ * mix [IF][IF], smix XX -> mix = mix
++ * smix other -> mix = FAILED; this keeps II fail
++ */
++ if (mix.val == MIX_UNINIT) {
++ mix = smix;
++ } else {
++ eCCallStructMixElem o1 = mix.e1, o2 = mix.e2;
++ eCCallStructMixElem n1 = smix.e1, n2 = smix.e2;
++ if (o2 != MIX_ELEM_UNINIT) {
++ mix = (smix.val != MIX_UNINIT) ? (CCallStructMix){ .val = MIX_FAILED }
++ : mix;
++ } else {
++ mix = (n2 != MIX_ELEM_UNINIT) ? (CCallStructMix){ .val = MIX_FAILED }
++ : (CCallStructMix){ .e1 = o1, .e2 = n1 };
++ }
++ }
++ }
++ }
+ }
+ }
+ }
-+ if (MIX_UNINIT < mix.val && mix.val < MIX_FAILED) /* Mixed passing */
++ if (MIX_UNINIT < mix.val && mix.val < MIX_FAILED && mix.e1 != MIX_ELEM_UNINIT) /* Mixed passing */
+ return (CCallStructClass){ .ispod = 1, .mix = mix };
+not_ag: /* Not a float/double aggregate or int/fp mix pair aggregate */
-+ return (CCallStructClass){ .ispod = (sz <= 16), .mix = mix }; /* Return structs of size <= 16 in GPRs. */
++ if (list) list->nfields = 0; /* Invalidate flat list on classification failure. */
++ return (CCallStructClass){ .ispod = (sz <= 16), .mix = mix };
+}
+
+#endif
+
- /* -- Common C call handling ---------------------------------------------- */
-
- /* Infer the destination CTypeID for a vararg argument.
-@@ -1106,6 +1404,7 @@ static int ccall_set_args(lua_State *L,
+ #ifndef ccall_struct_align
+ /* Alignment of pass-by-value structs. */
+ #define ccall_struct_align(cts, ct) ((ct)->info & CTF_ALIGN)
+@@ -1141,6 +1532,7 @@ static int ccall_set_args(lua_State *L,
#endif
#endif
/* Clear unused regs to get some determinism in case of misdeclaration. */
memset(cc->gpr, 0, sizeof(cc->gpr));
#if CCALL_NUM_FPR
-@@ -1168,6 +1467,11 @@ static int ccall_set_args(lua_State *L,
+@@ -1203,6 +1595,9 @@ static int ccall_set_args(lua_State *L,
CType *d;
CTSize sz;
MSize n, isfp = 0, isva = 0;
+#if LJ_TARGET_RISCV64
-+ MSize onsp, mnsp;
+ CCallStructMix mix = { .val = MIX_UNINIT };
-+ int isstack = 0;
+#endif
void *dp, *rp = NULL;
#if LJ_TARGET_PPC && LJ_ARCH_BITS == 64
int isf32 = 0;
-@@ -1216,6 +1520,10 @@ static int ccall_set_args(lua_State *L,
- #if LJ_TARGET_S390X
- onstack = 1;
- #endif
-+#if LJ_TARGET_RISCV64
-+ isstack = 1;
-+ onsp = nsp;
-+#endif
- if (CCALL_ALIGN_STACKARG) { /* Align argument on stack. */
- MSize align = (1u << ctype_align(d->info)) - 1;
- if (rp || (CCALL_PACK_STACKARG && isva && align < CTSIZE_PTR-1))
-@@ -1228,6 +1536,9 @@ static int ccall_set_args(lua_State *L,
- #else
- dp = ((uint8_t *)cc->stack) + nsp;
- #endif
-+#if LJ_TARGET_RISCV64
-+ mnsp = nsp + n * CTSIZE_PTR / 2;
-+#endif
- nsp += CCALL_PACK_STACKARG ? sz : n * CTSIZE_PTR;
- if ((int32_t)nsp > CCALL_SIZE_STACK) { /* Too many arguments. */
- err_nyi:
-@@ -1295,7 +1606,11 @@ static int ccall_set_args(lua_State *L,
+@@ -1333,7 +1728,11 @@ static int ccall_set_args(lua_State *L,
*(int64_t *)dp = (int64_t)*(int32_t *)dp;
}
#endif
if ((ctype_isinteger_or_bool(d->info) || ctype_isenum(d->info)
#if LJ_TARGET_MIPS64
|| (isfp && nsp == 0)
-@@ -1335,6 +1650,107 @@ static int ccall_set_args(lua_State *L,
- CTSize i = (sz >> 2) - 1;
- do { ((uint64_t *)dp)[i] = ((uint32_t *)dp)[i]; } while (i--);
- }
-+#elif LJ_TARGET_RISCV64
-+ switch (mix.val) {
-+ case MIX_UNINIT:
-+ break;
-+ /* Fix MIX values */
-+ case MIX_DF: {
-+ ((uint32_t *)dp)[3] = 0xffffffffu;
-+ break;
-+ }
-+ case MIX_FF:
-+ if (isstack) break;
-+ ((uint64_t *)dp)[1] = 0xffffffff00000000ul | ((uint32_t *)dp)[1];
-+ case MIX_FX:
-+ case MIX_FD: {
-+ ((uint64_t *)dp)[0] = 0xffffffff00000000ul | ((uint32_t *)dp)[0];
-+ break;
-+ }
-+ case MIX_FI: {
-+ lj_assertL(sz == 8 || sz == 16, "invalid MIX_FI size %d", (int)sz);
-+ if (ngpr >= CCALL_NARG_GPR) break;
-+ if (sz == 8) {
-+ FPRArg farg = { .hi = 0xffffffffu, .lo = ((uint32_t *)dp)[0] };
-+ if (!isva && nfpr + 1 <= CCALL_NARG_FPR) {
-+ cc->fpr[nfpr++] = farg;
-+ goto fi_next;
-+ } else if (ngpr + 1 <= CCALL_NARG_GPR) {
-+ cc->gpr[ngpr++] = farg.u;
-+fi_next:
-+ ((uint32_t *)dp)[0] = ((uint32_t *)dp)[1];
-+ ((uint32_t *)dp)[1] = 0;
-+ if (ngpr + 1 <= CCALL_NARG_GPR) {
-+ cc->gpr[ngpr++] = ((uint32_t *)dp)[0];
-+ ((uint32_t *)dp)[0] = 0, nsp = onsp;
-+ }
-+ }
-+ break;
-+ } else /*if (sz == 16)*/ {
-+ ((uint64_t *)dp)[0] |= 0xffffffff00000000ul;
-+ /* fallthrough */
-+ }
-+ }
-+ case MIX_DI: {
-+ lj_assertL(sz == 16, "invalid MIX_DI size %d", (int)sz);
-+ if (ngpr >= CCALL_NARG_GPR) break;
-+ if (!isva && nfpr + 1 <= CCALL_NARG_FPR) {
-+ cc->fpr[nfpr++] = (FPRArg){ .u = ((uint64_t *)dp)[0] };
-+ goto di_next;
-+ } else if (ngpr + 1 <= CCALL_NARG_GPR) {
-+ cc->gpr[ngpr++] = ((uint64_t *)dp)[0];
-+di_next:
-+ ((uint64_t *)dp)[0] = ((uint64_t *)dp)[1];
-+ ((uint64_t *)dp)[1] = 0, nsp = mnsp;
-+ if (ngpr + 1 <= CCALL_NARG_GPR) {
-+ cc->gpr[ngpr++] = ((uint64_t *)dp)[0];
-+ ((uint64_t *)dp)[0] = 0, nsp = onsp;
-+ }
-+ }
-+ break;
-+ }
-+ case MIX_IF: {
-+ lj_assertL(sz == 8 || sz == 16, "invalid MIX_IF size %d", (int)sz);
-+ if (sz == 8) {
-+ FPRArg farg = { .hi = 0xffffffffu, .lo = ((uint32_t *)dp)[1] };
-+ if (ngpr + 1 <= CCALL_NARG_GPR) {
-+ cc->gpr[ngpr++] = ((uint32_t *)dp)[0];
-+ ((uint32_t *)dp)[0] = ((uint32_t *)dp)[1];
-+ ((uint32_t *)dp)[1] = 0;
-+ if (!isva && nfpr + 1 <= CCALL_NARG_FPR) {
-+ cc->fpr[nfpr++] = farg;
-+ goto if_next;
-+ } else if (ngpr + 1 <= CCALL_NARG_GPR) {
-+ cc->gpr[ngpr++] = farg.u;
-+if_next:
-+ ((uint32_t *)dp)[0] = 0, nsp = onsp;
-+ }
-+ }
-+ break;
-+ } else /*if (sz == 16)*/ {
-+ ((uint64_t *)dp)[1] |= 0xffffffff00000000ul;
-+ /* fallthrough */
-+ }
-+ }
-+ case MIX_ID: {
-+ lj_assertL(sz == 16, "invalid MIX_ID size %d", (int)sz);
-+ if (ngpr + 1 <= CCALL_NARG_GPR) {
-+ cc->gpr[ngpr++] = ((uint64_t *)dp)[0];
-+ ((uint64_t *)dp)[0] = ((uint64_t *)dp)[1];
-+ ((uint64_t *)dp)[1] = 0, nsp = mnsp;
-+ if (!isva && nfpr + 1 <= CCALL_NARG_FPR) {
-+ cc->fpr[nfpr++] = (FPRArg){ .u = ((uint64_t *)dp)[0] };
-+ goto id_next;
-+ } else if (ngpr + 1 <= CCALL_NARG_GPR) {
-+ cc->gpr[ngpr++] = ((uint64_t *)dp)[0];
-+id_next:
-+ ((uint64_t *)dp)[0] = 0, nsp = onsp;
-+ }
-+ }
-+ break;
-+ }
-+ default: break;
-+ }
- #else
- UNUSED(isfp);
- #endif
--- a/src/lj_ccall.h
+++ b/src/lj_ccall.h
@@ -157,6 +157,23 @@ typedef union FPRArg {
(LJ_ABI_SOFTFP || ctype_isinteger_or_bool(ctr->info)))
--- /dev/null
+++ b/src/lj_emit_riscv.h
-@@ -0,0 +1,574 @@
+@@ -0,0 +1,576 @@
+/*
+** RISC-V instruction emitter.
-+** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice in luajit.h
++** Copyright (C) 2022-2026 ISRC, ISCAS. See Copyright Notice in luajit.h
+**
-+** Contributed by gns from PLCT Lab, ISCAS.
++** Contributed by gns from PLCT Lab, ISRC, ISCAS.
+*/
+
+static intptr_t get_k64val(ASMState *as, IRRef ref)
+/* Prefer rematerialization of BASE/L from global_State over spills. */
+#define emit_canremat(ref) ((ref) <= REF_BASE)
+
++#define glofs(as, k) \
++ ((intptr_t)((uintptr_t)(k) - (uintptr_t)&J2GG(as->J)->g))
+
+/* Load a 32 bit constant into a GPR. */
+#define emit_loadi(as, r, i) emit_loadk32(as, r, i);
#else
#define JIT_F_CPUSTRING ""
+@@ -364,13 +404,13 @@ enum {
+ #if LJ_TARGET_MIPS
+ LJ_K64_2P31, /* 2^31 */
+ #endif
+-#if LJ_TARGET_ARM64 || LJ_TARGET_MIPS64
++#if LJ_TARGET_ARM64 || LJ_TARGET_MIPS64 || LJ_TARGET_RISCV64
+ LJ_K64_VM_EXIT_HANDLER,
+ LJ_K64_VM_EXIT_INTERP,
+ #endif
+ LJ_K64__MAX,
+ };
+-#define LJ_K64__USED (LJ_TARGET_X86ORX64 || LJ_TARGET_ARM64 || LJ_TARGET_MIPS)
++#define LJ_K64__USED (LJ_TARGET_X86ORX64 || LJ_TARGET_ARM64 || LJ_TARGET_MIPS || LJ_TARGET_RISCV64)
+
+ enum {
+ #if LJ_TARGET_X86ORX64 || LJ_TARGET_MIPS64
--- a/src/lj_mcode.c
+++ b/src/lj_mcode.c
@@ -38,6 +38,12 @@
@@ -0,0 +1,542 @@
+/*
+** Definitions for RISC-V CPUs.
-+** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice in luajit.h
++** Copyright (C) 2022-2026 ISRC, ISCAS. See Copyright Notice in luajit.h
+*/
+
+#ifndef _LJ_TARGET_RISCV_H
+};
+
+#endif
+--- a/src/lj_trace.c
++++ b/src/lj_trace.c
+@@ -351,7 +351,7 @@ void lj_trace_initstate(global_State *g)
+ J->k32[LJ_K32_VM_EXIT_HANDLER] = (uintptr_t)(void *)lj_vm_exit_handler;
+ J->k32[LJ_K32_VM_EXIT_INTERP] = (uintptr_t)(void *)lj_vm_exit_interp;
+ #endif
+-#if LJ_TARGET_ARM64 || LJ_TARGET_MIPS64
++#if LJ_TARGET_ARM64 || LJ_TARGET_MIPS64 || LJ_TARGET_RISCV64
+ J->k64[LJ_K64_VM_EXIT_HANDLER].u64 = (uintptr_t)lj_ptr_sign((void *)lj_vm_exit_handler, 0);
+ J->k64[LJ_K64_VM_EXIT_INTERP].u64 = (uintptr_t)lj_ptr_sign((void *)lj_vm_exit_interp, 0);
+ #endif
--- a/src/lj_vm.h
+++ b/src/lj_vm.h
@@ -37,6 +37,9 @@ LJ_ASMF int lj_vm_cpuid(uint32_t f, uint
uint32_t y, ua, ub;
--- /dev/null
+++ b/src/vm_riscv64.dasc
-@@ -0,0 +1,4810 @@
+@@ -0,0 +1,4857 @@
+|// Low-level VM code for RISC-V 64 CPUs.
+|// Bytecode interpreter, fast functions and helper functions.
-+|// Copyright (C) 2005-2025 Mike Pall. See Copyright Notice in luajit.h
++|// Copyright (C) 2022-2026 ISRC, ISCAS. See Copyright Notice in luajit.h
+|//
-+|// Contributed by gns from PLCT Lab, ISCAS.
++|// Contributed by gns from PLCT Lab, ISRC, ISCAS.
+|
+|.arch riscv64
+|.section code_op, code_sub
+ |
+ |
+ |//-----------------------------------------------------------------------
++ |//-- Number conversion functions --------------------------------------------
++ |//-----------------------------------------------------------------------
++ |
++ |// int64_t lj_vm_num2int_check(double x)
++ |->vm_num2int_check:
++ | fcvt.w.d CRET1, FARG1, rtz
++ | fcvt.d.w FARG2, CRET1
++ | feq.d CARG2, FARG1, FARG2
++ | beqz CARG2, >1
++ | ret
++ |1:
++ | lui CRET1, 0x80000
++ | slli CRET1, CRET1, 8
++ | addi CRET1, CRET1, 0x80
++ | slli CRET1, CRET1, 24
++ | ret
++ |
++ |// int64_t lj_vm_num2i64(double x)
++ |->vm_num2i64:
++ | fcvt.l.d CRET1, FARG1, rtz
++ | ret
++ |
++ |// uint64_t lj_vm_num2u64(double x)
++ |->vm_num2u64:
++ | fcvt.lu.d CRET1, FARG1, rtz
++ | bnez CRET1, >1
++ | fcvt.l.d CRET1, FARG1, rtz
++ |1:
++ | ret
++ |
++ |// int32_t lj_vm_tobit(double x)
++ |->vm_tobit:
++ | lui CARG2, 0x43380 // 2^52 + 2^51.
++ | slli CARG2, CARG2, 32
++ | fmv.d.x FARG2, CARG2
++ | fadd.d FARG2, FARG1, FARG2
++ | fmv.x.w CRET1, FARG2
++ | ret
++ |
++ |//-----------------------------------------------------------------------
+ |//-- Miscellaneous functions --------------------------------------------
+ |//-----------------------------------------------------------------------
+ |
+ | // Check if RA or RD is a cdata.
+ | xori TMP0, CARG3, LJ_TCDATA
+ | xori TMP1, CARG4, LJ_TCDATA
-+ | and TMP0, TMP0, TMP1
-+ | bxeqz TMP0, ->vmeta_equal_cd
++ | seqz TMP0, TMP0
++ | seqz TMP1, TMP1
++ | or TMP0, TMP0, TMP1
++ | bxnez TMP0, ->vmeta_equal_cd
+ |.endif
+ | lui TMP3, (-(BCBIAS_J*4 >> 12)) & 0xfffff // -BCBIAS_J*4
+ | decode_BC4b TMP2
+ | ld TMP0, 0(RB)
+ | lui TMP1, 0x80000
+ | gettp CARG3, TMP0
-+ | bne CARG3, TISNUM, >1
++ | bne CARG3, TISNUM, >2
+ | sext.w TMP0, TMP0
+ | bxeq TMP0, TMP1, ->vmeta_unm // Meta handler deals with -2^31.
+ | negw TMP0, TMP0
++ | beqz TMP0, >3
+ | zext.w TMP0, TMP0
+ | settp_b TMP0, TISNUM
-+ | j >2
+ |1:
++ | ins_next1
++ | sd TMP0, 0(RA)
++ | ins_next2
++ |2:
+ | sltiu TMP3, CARG3, LJ_TISNUM
+ | slli TMP1, TMP1, 32
+ | bxeqz TMP3, ->vmeta_unm
+ | xor TMP0, TMP0, TMP1 // sign => ~sign
-+ |2:
-+ | sd TMP0, 0(RA)
-+ | ins_next
++ | j <1
++ |3:
++ | slli TMP0, TMP1, 32
++ | j <1
+ break;
+ case BC_LEN:
+ | // RA = dst*8, RD = src*8