]> git.99rst.org Git - openwrt-packages.git/commitdiff
luajit2: update to 2026-07-31
authorJosef Schlehofer <redacted>
Sat, 15 Aug 2026 10:12:39 +0000 (12:12 +0200)
committerJosef Schlehofer <redacted>
Sat, 15 Aug 2026 10:26:13 +0000 (12:26 +0200)
Also refresh 020-riscv64-support.patch from the current state of
openresty/luajit2#236. The previous snapshot defined
asm_tail_prep(ASMState *as), but since openresty/luajit2 commit
68354f44 the callers pass a second TraceNo argument, so the riscv64
build failed with:

  lj_asm.c:2559:5: error: too many arguments to function 'asm_tail_prep'

The refreshed patch carries the upstream adaptation and restores the
build. The other four patches apply unchanged.

Signed-off-by: Josef Schlehofer <redacted>
lang/lua/luajit2/Makefile
lang/lua/luajit2/patches/020-riscv64-support.patch

index c61183cced919b2462cf7b4b86ecdb999dc183e7..a46dfee89567ec8f1ae197a1605ca7266fba8c72 100644 (file)
@@ -1,14 +1,14 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=luajit2
-PKG_SOURCE_DATE:=2026-02-27
+PKG_SOURCE_DATE:=2026-07-31
 PKG_VERSION:=2.1.$(subst -,.,$(PKG_SOURCE_DATE))
-PKG_RELEASE:=2
+PKG_RELEASE:=1
 
 PKG_SOURCE_PROTO:=git
 PKG_SOURCE_URL:=https://github.com/openresty/luajit2
-PKG_MIRROR_HASH:=057f6c02cdfac3f7cacc21fcac45ff74084cabb4a4db2cc589ed332bb1aeddbb
-PKG_SOURCE_VERSION:=928290a470d35eb01324d5275b22fb86b13602a2
+PKG_MIRROR_HASH:=cf3ae553640fcd6400bc545d87c9a7a3317b9e944216bd76433b84eabb756c3e
+PKG_SOURCE_VERSION:=52f52587b37867ab19236eb6917001c2d6b662e7
 
 PKG_MAINTAINER:=Javier Marcet <javier@marcet.info>
 PKG_LICENSE:=MIT
index ee4d560e9621e4f593e7b16710536dd9d93c1b39..06b14dad5872908c712d8a3f8d9674da9d7ba505 100644 (file)
@@ -1,6 +1,6 @@
-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.
@@ -11,6 +11,10 @@ The patch adds comprehensive RISC-V64 JIT implementation including:
 - 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
 
@@ -28,13 +32,11 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
  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>
@@ -470,10 +472,10 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +------------------------------------------------------------------------------
 +-- 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
@@ -486,7 +488,7 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +  version =   "1.5.0",
 +  vernum =     10500,
 +  release =   "2022-07-12",
-+  author =    "Mike Pall",
++  author =    "gns",
 +  license =   "MIT",
 +}
 +
@@ -1452,7 +1454,7 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +------------------------------------------------------------------------------
 +-- 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.
@@ -1467,7 +1469,7 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +------------------------------------------------------------------------------
 +-- 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.
@@ -1588,11 +1590,11 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +------------------------------------------------------------------------------
 +-- 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.
 +--
@@ -2570,7 +2572,7 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +----------------------------------------------------------------------------
 +-- 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
@@ -2583,10 +2585,9 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +  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
  
@@ -2691,7 +2692,7 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
  /* 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). */
@@ -2752,7 +2753,7 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
  #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 */
@@ -2773,7 +2774,7 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
  
  #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
@@ -2806,12 +2807,12 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
  #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 --------------------------------------- */
@@ -2908,18 +2909,30 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +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;
 +}
@@ -2937,7 +2950,6 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +  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. */
@@ -3319,14 +3331,18 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +    } 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);
@@ -4055,7 +4071,7 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +    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);
@@ -4320,7 +4336,8 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +  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:
@@ -4730,33 +4747,37 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +/* 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 --------------------------------------------------------- */
@@ -4831,14 +4852,6 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +  }
 +      /* 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);
@@ -4846,51 +4859,46 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +}
 --- 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: \
@@ -4899,7 +4907,8 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +      break; \
 +    } \
 +    default: lj_assertL(0, "Invalid ret mix %d", mix.val); \
-+  }
++  } \
++}
 +
 +#define CCALL_HANDLE_COMPLEXRET \
 +  /* Complex values are returned in 1 or 2 FPRs. */ \
@@ -4925,7 +4934,7 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +    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; \
 +  }
 +
@@ -4934,66 +4943,121 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +    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
  
@@ -5004,29 +5068,35 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +/* 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 {
@@ -5045,7 +5115,8 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +  };
 +} 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 };
@@ -5054,111 +5125,146 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +    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
  
@@ -5166,40 +5272,17 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
    /* 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
@@ -5212,114 +5295,6 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
      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 {
@@ -5442,12 +5417,12 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
        (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)
@@ -5764,6 +5739,8 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +/* 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);
@@ -6122,6 +6099,22 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
  #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 @@
@@ -6180,7 +6173,7 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 @@ -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
@@ -6720,6 +6713,17 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +};
 +
 +#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
@@ -6746,12 +6750,12 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
    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
@@ -9261,6 +9265,46 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +  |
 +  |
 +  |//-----------------------------------------------------------------------
++  |//-- 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 --------------------------------------------
 +  |//-----------------------------------------------------------------------
 +  |
@@ -9585,8 +9629,10 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +    |  // 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
@@ -9854,21 +9900,26 @@ Co-authored-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
 +    |  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
git clone https://git.99rst.org/PROJECT