Affix
view release on metacpan or search on metacpan
infix/src/arch/riscv/abi_riscv64.c view on Meta::CPAN
/**
* Copyright (c) 2026 Sanko Robinson
*
* This source code is dual-licensed under the Artistic License 2.0 or the MIT License.
* You may choose to use this code under the terms of either license.
*
* SPDX-License-Identifier: (Artistic-2.0 OR MIT)
*
* The documentation blocks within this file are licensed under the
* Creative Commons Attribution 4.0 International License (CC BY 4.0).
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file abi_riscv64.c
* @brief Implements the FFI logic for the RISC-V RV64GC (lp64d) architecture.
* @ingroup internal_abi_riscv64
*
* @internal
* This file provides the concrete implementation of the `infix_forward_abi_spec`,
* `infix_reverse_abi_spec`, and `infix_direct_forward_abi_spec` for the RISC-V
* RV64 64-bit base integer + IEEE double-precision floating-point ABI (LP64D).
* It follows the ratified "RISC-V ELF psABI Specification" (v1.0).
*
* @section riscv64_rules Key RISC-V psABI Rules Implemented
*
* - **Register Usage:**
* - The first 8 integer/pointer arguments are passed in GPRs (a0-a7).
* - The first 8 floating-point arguments are passed in FPRs (fa0-fa7).
*
* - **Scalar Classification:**
* - `float`/`double` scalars are passed in FPRs.
* - 2xXLEN (16-byte) integer scalars and aggregates are passed in an even-aligned
* GPR pair (aN/aN+1). There is no register/stack split on RISC-V.
* - 2xXLEN (16-byte) floating-point scalars (`long double`) are passed in an
* even-aligned FPR pair.
*
* - **Aggregate Classification:**
* - Aggregates larger than 2xXLEN (16 bytes) are passed by reference (a pointer
* in a GPR). Larger return values are returned via a hidden pointer in a0.
* - Aggregates of at most 2xXLEN bits containing only floating-point members are
* passed in FPRs, one FPR per member, in order.
* - All other aggregates are passed in GPRs.
*
* - **Variadic Calls:**
* - Variadic arguments use the integer calling convention: floats and doubles are
* passed in GPRs, not FPRs.
* - Once any variadic argument has been placed on the stack, all subsequent
* variadic arguments are passed on the stack.
*
* - **Return Values:**
* - Scalars, 2xXLEN scalars, and aggregates up to 16 bytes are returned in
* registers (a0/a1, fa0/fa1). Larger aggregates use the memory strategy.
*
* - **Stack Alignment:** The stack pointer is always 16-byte aligned; stack argument
* slots are 8-byte wide (16-byte arguments are 16-byte aligned).
* @endinternal
*/
#include "arch/riscv/abi_riscv64_common.h"
#include "arch/riscv/abi_riscv64_emitters.h"
#include "common/infix_internals.h"
#include "common/utility.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
/** @internal The GPRs used for the first 8 integer/pointer arguments (a0-a7). */
static const riscv_gpr GPR_ARGS[] = {X_A0_REG, X_A1_REG, X_A2_REG, X_A3_REG, X_A4_REG, X_A5_REG, X_A6_REG, X_A7_REG};
/** @internal The FPRs used for the first 8 floating-point arguments (fa0-fa7). */
static const riscv_fpr FPR_ARGS[] = {
F_FA0_REG, F_FA1_REG, F_FA2_REG, F_FA3_REG, F_FA4_REG, F_FA5_REG, F_FA6_REG, F_FA7_REG};
/** @internal The number of GPRs available for argument passing. */
#define RV_NUM_GPR_ARGS 8
/** @internal The number of FPRs available for argument passing. */
#define RV_NUM_FPR_ARGS 8
/** @internal A safety limit on the number of FP members to classify in an aggregate. */
#define RV_MAX_FLATTENED_FIELDS 32
/** @internal A safety limit on the recursion depth when flattening an aggregate. */
#define RV_MAX_FLATTEN_DEPTH 32
/** @internal Stack space reserved for the three callee-saved context registers (s1/s2/s3). */
#define RV_FWD_SAVED_SIZE 32
/** @internal Stack space reserved for the saved return address in a reverse stub. */
#define RV_REV_SAVED_SIZE 16
/** @internal The classification result for a single argument. */
typedef struct {
infix_arg_location_type type; /**< The physical location (GPR/FPR/Pair/Stack). */
uint8_t reg_index; /**< The first register used (GPR base for MIXED). */
uint8_t reg_index2; /**< The second register (FPR base for MIXED). */
uint32_t num_regs; /**< Number of registers (for FPR aggregates / 2xXLEN). */
uint32_t stack_offset; /**< Byte offset from the stack pointer. */
} rv64_arg_class;
/** @internal The v-table of RISC-V functions for generating forward trampolines. */
static infix_status prepare_forward_call_frame_riscv64(infix_arena_t * arena,
infix_call_frame_layout ** out_layout,
infix_type * ret_type,
infix_type ** arg_types,
size_t num_args,
size_t num_fixed_args,
void * target_fn);
static infix_status generate_forward_prologue_riscv64(code_buffer * buf, infix_call_frame_layout * layout);
static infix_status generate_forward_argument_moves_riscv64(code_buffer * buf,
infix_call_frame_layout * layout,
infix_type ** arg_types,
size_t num_args,
c23_maybe_unused size_t num_fixed_args);
static infix_status generate_forward_call_instruction_riscv64(code_buffer *, infix_call_frame_layout *);
static infix_status generate_forward_epilogue_riscv64(code_buffer * buf,
infix_call_frame_layout * layout,
infix_type * ret_type);
const infix_forward_abi_spec g_riscv64_forward_spec = {
.prepare_forward_call_frame = prepare_forward_call_frame_riscv64,
.generate_forward_prologue = generate_forward_prologue_riscv64,
.generate_forward_argument_moves = generate_forward_argument_moves_riscv64,
.generate_forward_call_instruction = generate_forward_call_instruction_riscv64,
.generate_forward_epilogue = generate_forward_epilogue_riscv64};
/** @internal The v-table of RISC-V functions for generating reverse trampolines. */
static infix_status prepare_reverse_call_frame_riscv64(infix_arena_t * arena,
infix_reverse_call_frame_layout ** out_layout,
infix_reverse_t * context);
static infix_status generate_reverse_prologue_riscv64(code_buffer * buf, infix_reverse_call_frame_layout * layout);
static infix_status generate_reverse_argument_marshalling_riscv64(code_buffer * buf,
infix_reverse_call_frame_layout * layout,
infix_reverse_t * context);
static infix_status generate_reverse_dispatcher_call_riscv64(code_buffer * buf,
infix_reverse_call_frame_layout * layout,
infix_reverse_t * context);
static infix_status generate_reverse_epilogue_riscv64(code_buffer * buf,
infix_reverse_call_frame_layout * layout,
infix_reverse_t * context);
const infix_reverse_abi_spec g_riscv64_reverse_spec = {
.prepare_reverse_call_frame = prepare_reverse_call_frame_riscv64,
.generate_reverse_prologue = generate_reverse_prologue_riscv64,
.generate_reverse_argument_marshalling = generate_reverse_argument_marshalling_riscv64,
.generate_reverse_dispatcher_call = generate_reverse_dispatcher_call_riscv64,
.generate_reverse_epilogue = generate_reverse_epilogue_riscv64};
/** @internal The v-table for the new Direct Marshalling ABI. */
static infix_status prepare_direct_forward_call_frame_riscv64(infix_arena_t * arena,
infix_direct_call_frame_layout ** out_layout,
infix_type * ret_type,
infix_type ** arg_types,
size_t num_args,
infix_direct_arg_handler_t * handlers,
void * target_fn);
static infix_status generate_direct_forward_prologue_riscv64(code_buffer * buf,
infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_argument_moves_riscv64(code_buffer * buf,
infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_call_instruction_riscv64(code_buffer * buf,
infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_epilogue_riscv64(code_buffer * buf,
infix_direct_call_frame_layout * layout,
infix_type * ret_type);
const infix_direct_forward_abi_spec g_riscv64_direct_forward_spec = {
.prepare_direct_forward_call_frame = prepare_direct_forward_call_frame_riscv64,
.generate_direct_forward_prologue = generate_direct_forward_prologue_riscv64,
.generate_direct_forward_argument_moves = generate_direct_forward_argument_moves_riscv64,
.generate_direct_forward_call_instruction = generate_direct_forward_call_instruction_riscv64,
.generate_direct_forward_epilogue = generate_direct_forward_epilogue_riscv64};
//
// Low-level helpers
//
/**
* @internal
* @brief Emit `rd = rs_base + offset`, materializing the offset via the `li` expansion
* when it does not fit in a 12-bit immediate.
*/
static void rv64_emit_compute_addr(code_buffer * buf, uint8_t rd, uint8_t rs_base, int32_t offset) {
if (offset >= -2048 && offset <= 2047) {
infix_riscv64_emit_addi(buf, rd, rs_base, offset);
return;
}
infix_riscv64_emit_load_u64_immediate(buf, rd, (uint64_t)(int64_t)offset);
infix_riscv64_emit_add(buf, rd, rs_base, rd);
}
/** @internal Wide-offset `ld rd, offset(rs_base)`. */
static void rv64_mem_ld(code_buffer * buf, uint8_t rd, uint8_t rs_base, int32_t off) {
if (off >= -2048 && off <= 2047) {
infix_riscv64_emit_ld(buf, rd, rs_base, off);
return;
}
rv64_emit_compute_addr(buf, RV_SCRATCH0_REG, rs_base, off);
infix_riscv64_emit_ld(buf, rd, RV_SCRATCH0_REG, 0);
}
/** @internal Wide-offset `sd data, offset(rs_base)`. */
static void rv64_mem_sd(code_buffer * buf, uint8_t rs_base, uint8_t data, int32_t off) {
if (off >= -2048 && off <= 2047) {
infix_riscv64_emit_sd(buf, rs_base, data, off);
return;
}
rv64_emit_compute_addr(buf, RV_SCRATCH0_REG, rs_base, off);
infix_riscv64_emit_sd(buf, RV_SCRATCH0_REG, data, 0);
}
/** @internal Wide-offset `lw rd, offset(rs_base)`. */
static void rv64_mem_lw(code_buffer * buf, uint8_t rd, uint8_t rs_base, int32_t off) {
if (off >= -2048 && off <= 2047) {
infix_riscv64_emit_lw(buf, rd, rs_base, off);
return;
}
rv64_emit_compute_addr(buf, RV_SCRATCH0_REG, rs_base, off);
infix_riscv64_emit_lw(buf, rd, RV_SCRATCH0_REG, 0);
}
/** @internal Wide-offset `sw data, offset(rs_base)`. */
static void rv64_mem_sw(code_buffer * buf, uint8_t rs_base, uint8_t data, int32_t off) {
if (off >= -2048 && off <= 2047) {
infix_riscv64_emit_sw(buf, rs_base, data, off);
return;
}
rv64_emit_compute_addr(buf, RV_SCRATCH0_REG, rs_base, off);
infix_riscv64_emit_sw(buf, RV_SCRATCH0_REG, data, 0);
}
/** @internal Wide-offset `lbu rd, offset(rs_base)`. */
infix/src/arch/riscv/abi_riscv64.c view on Meta::CPAN
if (remaining & 1) {
if (o > 0)
infix_riscv64_emit_srli(buf, RV_SCRATCH1_REG, rd, (uint8_t)(o * 8));
rv64_mem_sb(buf, base, o > 0 ? RV_SCRATCH1_REG : rd, off + (int32_t)o);
}
}
/**
* @internal
* @brief Store the integer return value held in a0/a1 into the return buffer.
* @details Bytes 0-7 come from a0; bytes 8-15 from a1 (for sizes 9-16).
*/
static void rv64_emit_store_gpr_return(code_buffer * buf, uint8_t base, size_t size) {
if (size <= 8) {
rv64_emit_store_gpr_low_bytes(buf, base, X_A0_REG, 0, size);
return;
}
rv64_mem_sd(buf, base, X_A0_REG, 0);
rv64_emit_store_gpr_low_bytes(buf, base, X_A1_REG, 8, size - 8);
}
/** @internal Store a 4/8-byte floating-point value from an FPR into memory. */
static void rv64_emit_store_fp_value(code_buffer * buf, uint8_t base, uint8_t fa, int32_t off, size_t size) {
if (size == 8)
rv64_mem_fsd(buf, base, fa, off);
else if (size == 4)
rv64_mem_fsw(buf, base, fa, off);
}
/** @internal Load a 4/8-byte floating-point value from memory into an FPR. */
static void rv64_emit_load_fp_value(code_buffer * buf, uint8_t base, uint8_t fa, int32_t off, size_t size) {
if (size == 8)
rv64_mem_fld(buf, fa, base, off);
else if (size == 4)
rv64_mem_flw(buf, fa, base, off);
}
//
// Classification
//
/** @internal A single scalar leaf of an aggregate, in struct order. */
typedef struct {
size_t offset; /**< Byte offset of the leaf within the aggregate. */
size_t size; /**< Leaf size in bytes (1, 2, 4, 8 or 16 for a long double). */
bool is_fp; /**< `true` if the leaf is a `float`/`double` scalar. */
bool is_signed; /**< `true` for signed primitive integer leaves narrower than XLEN. */
} rv64_all_leaf;
/** @internal A flattened, in-order list of every scalar leaf of an aggregate. */
typedef struct {
size_t count;
rv64_all_leaf leaves[RV_MAX_FLATTENED_FIELDS];
} rv64_all_leaf_list;
/**
* @internal
* @brief Recursively flatten every scalar leaf of an aggregate (structs and arrays).
* @details Unions are not flattened; `_Complex` types expand into two leaves of the
* component type. Returns `false` if a leaf exceeds the leaf-table capacity,
* the type graph is pathologically deep, or a malformed (null) node is reached.
*/
static bool rv64_flatten_all_recursive(const infix_type * type,
size_t base_offset,
rv64_all_leaf_list * out,
size_t depth) {
// A recursive call can be made with a NULL type from a malformed aggregate.
if (type == nullptr)
return false; // Terminate this recursion path.
// Give up on pathologically deep type graphs instead of exhausting the stack.
if (depth > RV_MAX_FLATTEN_DEPTH)
return false;
if (type->category == INFIX_TYPE_STRUCT) {
if (type->meta.aggregate_info.members == nullptr)
return false;
for (size_t i = 0; i < type->meta.aggregate_info.num_members; ++i) {
const infix_struct_member * member = &type->meta.aggregate_info.members[i];
if (member->type == nullptr)
return false;
// Check the leaf-table capacity before descending any further.
if (out->count >= RV_MAX_FLATTENED_FIELDS)
return false;
if (!rv64_flatten_all_recursive(member->type, base_offset + member->offset, out, depth + 1))
return false;
}
return true;
}
if (type->category == INFIX_TYPE_ARRAY) {
if (type->meta.array_info.element_type == nullptr)
return false;
// A zero-sized element never advances the offset, so iterating every
// element is pointless and, for chains like `a[127][127][...][0]`,
// explodes exponentially without ever producing a leaf (which would
// otherwise trip the leaf-table capacity guard). Flatten the element
// type just once at the starting offset.
if (type->meta.array_info.element_type->size == 0) {
if (type->meta.array_info.num_elements > 0)
return rv64_flatten_all_recursive(type->meta.array_info.element_type, base_offset, out, depth + 1);
return true; // An empty array has no effect on the leaf list.
}
for (size_t i = 0; i < type->meta.array_info.num_elements; ++i) {
// Check the leaf-table capacity before each recursive call.
if (out->count >= RV_MAX_FLATTENED_FIELDS)
return false;
if (!rv64_flatten_all_recursive(type->meta.array_info.element_type,
base_offset + i * type->meta.array_info.element_type->size,
out,
depth + 1))
return false;
}
return true;
}
if (type->category == INFIX_TYPE_COMPLEX) {
// `_Complex double` is the two FP scalars {real, imag}.
size_t comp_size = type->meta.complex_info.base_type ? type->meta.complex_info.base_type->size : type->size / 2;
if (out->count + 2 > RV_MAX_FLATTENED_FIELDS)
return false;
out->leaves[out->count++] = (rv64_all_leaf){base_offset, comp_size, true, false};
out->leaves[out->count++] = (rv64_all_leaf){base_offset + comp_size, comp_size, true, false};
return true;
}
if (out->count >= RV_MAX_FLATTENED_FIELDS)
return false;
bool is_signed = type->category == INFIX_TYPE_PRIMITIVE && type->size < 8 &&
(type->meta.primitive_id == INFIX_PRIMITIVE_SINT8 || type->meta.primitive_id == INFIX_PRIMITIVE_SINT16 ||
type->meta.primitive_id == INFIX_PRIMITIVE_SINT32);
out->leaves[out->count++] = (rv64_all_leaf){base_offset, type->size, is_float(type) || is_double(type), is_signed};
return true;
}
infix/src/arch/riscv/abi_riscv64.c view on Meta::CPAN
size_t num_args,
size_t num_fixed_args,
void * target_fn) {
if (out_layout == nullptr)
return INFIX_ERROR_INVALID_ARGUMENT;
infix_call_frame_layout * layout =
infix_arena_calloc(arena, 1, sizeof(infix_call_frame_layout), _Alignof(infix_call_frame_layout));
if (layout == nullptr) {
*out_layout = nullptr;
return INFIX_ERROR_ALLOCATION_FAILED;
}
layout->arg_locations =
infix_arena_calloc(arena, num_args, sizeof(infix_arg_location), _Alignof(infix_arg_location));
if (layout->arg_locations == nullptr && num_args > 0) {
*out_layout = nullptr;
return INFIX_ERROR_ALLOCATION_FAILED;
}
layout->is_variadic = (num_fixed_args < num_args);
layout->target_fn = target_fn;
layout->num_args = num_args;
layout->num_stack_args = 0;
layout->return_value_in_memory = rv64_return_in_memory(ret_type);
// When the target returns by memory, a0 holds the hidden sret pointer.
size_t gpr_count = layout->return_value_in_memory ? 1 : 0;
size_t vpr_count = 0;
uint32_t stack_offset = 0;
bool variadic_stack_mode = false;
for (size_t i = 0; i < num_args; ++i) {
infix_type * type = arg_types[i];
if (type->size > INFIX_MAX_ARG_SIZE) {
*out_layout = nullptr;
return INFIX_ERROR_LAYOUT_FAILED;
}
bool is_variadic_arg = (i >= num_fixed_args);
rv64_arg_class cls =
rv64_classify_arg(type, is_variadic_arg, &gpr_count, &vpr_count, &stack_offset, &variadic_stack_mode);
layout->arg_locations[i].type = cls.type;
layout->arg_locations[i].reg_index = cls.reg_index;
layout->arg_locations[i].reg_index2 = cls.reg_index2;
layout->arg_locations[i].num_regs = cls.num_regs;
layout->arg_locations[i].stack_offset = cls.stack_offset;
if (cls.type == ARG_LOCATION_STACK)
layout->num_stack_args++;
}
layout->total_stack_alloc = (stack_offset + 15) & ~15;
layout->num_gpr_args = (uint8_t)gpr_count;
layout->num_vpr_args = (uint8_t)vpr_count;
if (layout->total_stack_alloc > INFIX_MAX_STACK_ALLOC) {
*out_layout = nullptr;
return INFIX_ERROR_LAYOUT_FAILED;
}
*out_layout = layout;
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 2 (Forward): Generates the function prologue.
* @details Saves the callee-saved context registers (s1 = target, s2 = return
* buffer, s3 = args array), moves the trampoline's own arguments into
* them, and allocates space for the outgoing stack arguments.
*/
static infix_status generate_forward_prologue_riscv64(code_buffer * buf, infix_call_frame_layout * layout) {
infix_riscv64_emit_addi(buf, X_SP_REG, X_SP_REG, -RV_FWD_SAVED_SIZE);
rv64_mem_sd(buf, X_SP_REG, RV_CTX_TARGET_REG, 0);
rv64_mem_sd(buf, X_SP_REG, RV_CTX_RET_REG, 8);
rv64_mem_sd(buf, X_SP_REG, RV_CTX_ARGS_REG, 16);
rv64_mem_sd(buf, X_SP_REG, X_RA_REG, 24);
layout->prologue_size = (uint32_t)buf->size;
if (layout->target_fn == nullptr) { // Unbound trampoline args: (target_fn, ret_ptr, args_ptr) in a0, a1, a2.
infix_riscv64_emit_addi(buf, RV_CTX_TARGET_REG, X_A0_REG, 0);
infix_riscv64_emit_addi(buf, RV_CTX_RET_REG, X_A1_REG, 0);
infix_riscv64_emit_addi(buf, RV_CTX_ARGS_REG, X_A2_REG, 0);
}
else { // Bound trampoline args: (ret_ptr, args_ptr) in a0, a1.
infix_riscv64_emit_addi(buf, RV_CTX_RET_REG, X_A0_REG, 0);
infix_riscv64_emit_addi(buf, RV_CTX_ARGS_REG, X_A1_REG, 0);
}
rv64_emit_stack_sub(buf, (uint32_t)layout->total_stack_alloc);
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 3 (Forward): Generates code to move arguments into their native locations.
*/
static infix_status generate_forward_argument_moves_riscv64(code_buffer * buf,
infix_call_frame_layout * layout,
infix_type ** arg_types,
size_t num_args,
c23_maybe_unused size_t num_fixed_args) {
// Hidden pointer for large struct returns is passed in a0.
if (layout->return_value_in_memory)
infix_riscv64_emit_addi(buf, X_A0_REG, RV_CTX_RET_REG, 0);
for (size_t i = 0; i < num_args; ++i) {
infix_arg_location * loc = &layout->arg_locations[i];
infix_type * type = arg_types[i];
// t0 = args_array[i]
rv64_mem_ld(buf, RV_SCRATCH0_REG, RV_CTX_ARGS_REG, (int32_t)(i * sizeof(void *)));
switch (loc->type) {
case ARG_LOCATION_GPR:
if (type->category == INFIX_TYPE_ARRAY) {
infix_riscv64_emit_addi(buf, GPR_ARGS[loc->reg_index], RV_SCRATCH0_REG, 0);
break;
}
rv64_emit_load_gpr_value(buf, GPR_ARGS[loc->reg_index], RV_SCRATCH0_REG, type);
break;
case ARG_LOCATION_GPR_PAIR:
rv64_mem_ld(buf, GPR_ARGS[loc->reg_index], RV_SCRATCH0_REG, 0);
rv64_mem_ld(buf, GPR_ARGS[loc->reg_index + 1], RV_SCRATCH0_REG, 8);
break;
case ARG_LOCATION_GPR_STACK_SPLIT:
// 2xXLEN value with exactly one register left: low half in the
// register, high half in the outgoing stack slot.
rv64_mem_ld(buf, GPR_ARGS[loc->reg_index], RV_SCRATCH0_REG, 0);
rv64_mem_ld(buf, RV_SCRATCH1_REG, RV_SCRATCH0_REG, 8);
rv64_mem_sd(buf, X_SP_REG, RV_SCRATCH1_REG, (int32_t)loc->stack_offset);
break;
case ARG_LOCATION_GPR_REFERENCE:
infix_riscv64_emit_addi(buf, GPR_ARGS[loc->reg_index], RV_SCRATCH0_REG, 0);
break;
case ARG_LOCATION_VPR:
if (type->category == INFIX_TYPE_STRUCT || type->category == INFIX_TYPE_COMPLEX)
rv64_emit_load_fp_aggregate(buf, RV_SCRATCH0_REG, FPR_ARGS[loc->reg_index], type);
else
rv64_emit_load_fp_value(buf, RV_SCRATCH0_REG, FPR_ARGS[loc->reg_index], 0, type->size);
break;
case ARG_LOCATION_MIXED:
rv64_emit_load_mixed(buf, RV_SCRATCH0_REG, GPR_ARGS[loc->reg_index], FPR_ARGS[loc->reg_index2], type);
break;
case ARG_LOCATION_STACK:
if (type->size > 16 || type->category == INFIX_TYPE_ARRAY) {
// By-reference / array argument: the pointer itself is the value.
rv64_mem_sd(buf, X_SP_REG, RV_SCRATCH0_REG, (int32_t)loc->stack_offset);
break;
}
if ((type->category == INFIX_TYPE_PRIMITIVE || type->category == INFIX_TYPE_POINTER ||
type->category == INFIX_TYPE_ENUM) &&
type->size <= 8) {
// Narrow scalars are extended into a full 8-byte stack slot.
rv64_emit_load_gpr_value(buf, RV_SCRATCH1_REG, RV_SCRATCH0_REG, type);
rv64_mem_sd(buf, X_SP_REG, RV_SCRATCH1_REG, (int32_t)loc->stack_offset);
break;
}
// Aggregates up to 16 bytes and 2xXLEN scalars copy their raw bytes.
rv64_emit_copy_memory(buf, X_SP_REG, (int32_t)loc->stack_offset, RV_SCRATCH0_REG, 0, type->size);
break;
default:
break;
}
}
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 3.5 (Forward): Generates the call instruction.
* @details Null-checks the target pointer (crashing via `ebreak` if null) and
* emits `jalr ra, target, 0`.
*/
static infix_status generate_forward_call_instruction_riscv64(code_buffer * buf,
c23_maybe_unused infix_call_frame_layout * layout) {
uint8_t target_reg = RV_SCRATCH0_REG;
if (layout->target_fn)
infix_riscv64_emit_load_u64_immediate(buf, target_reg, (uint64_t)layout->target_fn);
else
infix_riscv64_emit_addi(buf, target_reg, RV_CTX_TARGET_REG, 0);
// A non-null target skips the ebreak and calls the target; a null target falls
// through into the ebreak and traps. The target returns to the epilogue.
infix_riscv64_emit_bne(buf, target_reg, X_ZERO_REG, 8);
infix_riscv64_emit_ebreak(buf);
infix_riscv64_emit_jalr(buf, X_RA_REG, target_reg, 0);
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 4 (Forward): Generates the function epilogue.
* @details Copies the return value from a0/a1/fa0/fa1 into the user's return
* buffer, deallocates the frame, restores the context registers, and
* returns to the caller.
*/
static infix_status generate_forward_epilogue_riscv64(code_buffer * buf,
infix_call_frame_layout * layout,
infix_type * ret_type) {
layout->epilogue_offset = (uint32_t)buf->size;
if (ret_type->category != INFIX_TYPE_VOID && !layout->return_value_in_memory) {
rv64_return_class rc = rv64_classify_return(ret_type);
if (rc.mixed) {
// One integer leaf in a0 and one FP leaf in fa0.
rv64_emit_store_fp_value(buf, RV_CTX_RET_REG, F_FA0_REG, rc.fp_offsets[0], rc.fp_sizes[0]);
rv64_emit_store_gpr_low_bytes(buf, RV_CTX_RET_REG, X_A0_REG, rc.int_offset, rc.int_size);
}
else if (rc.fp_count == 2) {
rv64_emit_store_fp_value(buf, RV_CTX_RET_REG, F_FA0_REG, rc.fp_offsets[0], rc.fp_sizes[0]);
rv64_emit_store_fp_value(buf, RV_CTX_RET_REG, F_FA1_REG, rc.fp_offsets[1], rc.fp_sizes[1]);
}
else if (rc.fp_count == 1) {
rv64_emit_store_fp_value(buf, RV_CTX_RET_REG, F_FA0_REG, rc.fp_offsets[0], rc.fp_sizes[0]);
}
else {
rv64_emit_store_gpr_return(buf, RV_CTX_RET_REG, ret_type->size);
}
}
rv64_emit_stack_add(buf, (uint32_t)layout->total_stack_alloc);
rv64_mem_ld(buf, RV_CTX_TARGET_REG, X_SP_REG, 0);
rv64_mem_ld(buf, RV_CTX_RET_REG, X_SP_REG, 8);
rv64_mem_ld(buf, RV_CTX_ARGS_REG, X_SP_REG, 16);
rv64_mem_ld(buf, X_RA_REG, X_SP_REG, 24);
infix_riscv64_emit_addi(buf, X_SP_REG, X_SP_REG, RV_FWD_SAVED_SIZE);
infix_riscv64_emit_jalr(buf, X_ZERO_REG, X_RA_REG, 0);
return INFIX_SUCCESS;
}
//
// Reverse trampolines
//
/**
* @internal
* @brief Stage 1 (Reverse): Calculates the stack layout for a reverse trampoline stub.
*/
static infix_status prepare_reverse_call_frame_riscv64(infix_arena_t * arena,
infix_reverse_call_frame_layout ** out_layout,
infix_reverse_t * context) {
infix_reverse_call_frame_layout * layout = infix_arena_calloc(
arena, 1, sizeof(infix_reverse_call_frame_layout), _Alignof(infix_reverse_call_frame_layout));
if (layout == nullptr)
return INFIX_ERROR_ALLOCATION_FAILED;
if (context->return_type->size > INFIX_MAX_ARG_SIZE) {
*out_layout = nullptr;
return INFIX_ERROR_LAYOUT_FAILED;
}
size_t return_size = (context->return_type->size + 15) & ~15;
size_t args_array_size = (context->num_args * sizeof(void *) + 15) & ~15;
size_t saved_args_data_size = 0;
for (size_t i = 0; i < context->num_args; ++i) {
if (context->arg_types[i]->size > INFIX_MAX_ARG_SIZE) {
*out_layout = nullptr;
return INFIX_ERROR_LAYOUT_FAILED;
}
saved_args_data_size += (context->arg_types[i]->size + 15) & ~15;
}
if (saved_args_data_size > INFIX_MAX_ARG_SIZE) {
*out_layout = nullptr;
return INFIX_ERROR_LAYOUT_FAILED;
}
size_t total_local_space = return_size + args_array_size + saved_args_data_size;
if (total_local_space > INFIX_MAX_STACK_ALLOC) {
*out_layout = nullptr;
return INFIX_ERROR_LAYOUT_FAILED;
}
layout->total_stack_alloc = (total_local_space + 15) & ~15;
layout->return_buffer_offset = 0;
layout->args_array_offset = layout->return_buffer_offset + (int32_t)return_size;
layout->saved_args_offset = layout->args_array_offset + (int32_t)args_array_size;
*out_layout = layout;
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 2 (Reverse): Generates the prologue for the reverse trampoline stub.
*/
static infix_status generate_reverse_prologue_riscv64(code_buffer * buf, infix_reverse_call_frame_layout * layout) {
infix_riscv64_emit_addi(buf, X_SP_REG, X_SP_REG, -RV_REV_SAVED_SIZE);
rv64_mem_sd(buf, X_SP_REG, X_RA_REG, 0);
layout->prologue_size = (uint32_t)buf->size;
rv64_emit_stack_sub(buf, (uint32_t)layout->total_stack_alloc);
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 3 (Reverse): Generates code to marshal arguments into the `void**` array.
* @details Copies each incoming argument (from GPRs, FPRs, or the caller's stack)
* into a saved-data area on the stub's local stack and populates
* `args_array` with pointers to it.
*/
static infix_status generate_reverse_argument_marshalling_riscv64(code_buffer * buf,
infix_reverse_call_frame_layout * layout,
infix_reverse_t * context) {
bool return_in_memory = rv64_return_in_memory(context->return_type);
// a0 holds the hidden sret pointer when the return value is in memory; the
// dispatcher needs it again when it writes the return value back out, so
// stash it in the return-buffer slot before anything clobbers a0.
if (return_in_memory)
rv64_mem_sd(buf, X_SP_REG, X_A0_REG, layout->return_buffer_offset);
// a0 holds the hidden sret pointer when the return value is in memory.
size_t gpr_idx = return_in_memory ? 1 : 0;
size_t vpr_idx = 0;
uint32_t stack_offset = 0;
bool variadic_stack_mode = false;
size_t current_saved_data_offset = 0;
// The caller's stack arguments sit directly above our saved return address.
const int32_t caller_stack_base = RV_REV_SAVED_SIZE + (int32_t)layout->total_stack_alloc;
for (size_t i = 0; i < context->num_args; ++i) {
infix_type * type = context->arg_types[i];
bool is_variadic_arg = (i >= context->num_fixed_args);
rv64_arg_class cls =
rv64_classify_arg(type, is_variadic_arg, &gpr_idx, &vpr_idx, &stack_offset, &variadic_stack_mode);
int32_t arg_save_loc = (int32_t)(layout->saved_args_offset + current_saved_data_offset);
bool raw_pointer = false;
switch (cls.type) {
case ARG_LOCATION_GPR:
rv64_mem_sd(buf, X_SP_REG, GPR_ARGS[cls.reg_index], arg_save_loc);
break;
case ARG_LOCATION_GPR_PAIR:
rv64_mem_sd(buf, X_SP_REG, GPR_ARGS[cls.reg_index], arg_save_loc);
rv64_mem_sd(buf, X_SP_REG, GPR_ARGS[cls.reg_index + 1], arg_save_loc + 8);
break;
case ARG_LOCATION_GPR_STACK_SPLIT:
// 2xXLEN value split across a register and the caller's stack.
rv64_mem_sd(buf, X_SP_REG, GPR_ARGS[cls.reg_index], arg_save_loc);
rv64_mem_ld(buf, RV_SCRATCH0_REG, X_SP_REG, caller_stack_base + (int32_t)cls.stack_offset);
rv64_mem_sd(buf, X_SP_REG, RV_SCRATCH0_REG, arg_save_loc + 8);
break;
case ARG_LOCATION_GPR_REFERENCE:
// Large aggregate passed by reference: the argument is the pointer
// itself, which the dispatcher dereferences. Store the raw pointer
// straight into args_array[i].
raw_pointer = true;
break;
case ARG_LOCATION_VPR:
if (cls.num_regs > 1) {
infix/src/arch/riscv/abi_riscv64.c view on Meta::CPAN
rv64_emit_store_fp_value(buf, X_SP_REG, FPR_ARGS[cls.reg_index], arg_save_loc, type->size);
}
break;
case ARG_LOCATION_MIXED:
rv64_emit_compute_addr(buf, RV_SCRATCH0_REG, X_SP_REG, arg_save_loc);
rv64_emit_store_mixed(buf, RV_SCRATCH0_REG, GPR_ARGS[cls.reg_index], FPR_ARGS[cls.reg_index2], type);
break;
case ARG_LOCATION_STACK:
if (type->size > 16) {
// By-reference pointer passed on the caller's stack.
rv64_mem_ld(buf, RV_SCRATCH0_REG, X_SP_REG, caller_stack_base + (int32_t)cls.stack_offset);
rv64_mem_sd(buf, X_SP_REG, RV_SCRATCH0_REG, arg_save_loc);
}
else {
rv64_emit_copy_memory(
buf, X_SP_REG, arg_save_loc, X_SP_REG, caller_stack_base + (int32_t)cls.stack_offset, type->size);
}
break;
default:
break;
}
if (raw_pointer) {
// args_array[i] = the raw pointer value from the argument register.
rv64_mem_sd(
buf, X_SP_REG, GPR_ARGS[cls.reg_index], layout->args_array_offset + (int32_t)(i * sizeof(void *)));
}
else {
// args_array[i] = sp + arg_save_loc
rv64_emit_compute_addr(buf, RV_SCRATCH0_REG, X_SP_REG, arg_save_loc);
rv64_mem_sd(buf, X_SP_REG, RV_SCRATCH0_REG, layout->args_array_offset + (int32_t)(i * sizeof(void *)));
}
current_saved_data_offset += (type->size + 15) & ~15;
}
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 4 (Reverse): Generates the call to the C dispatcher.
* @details Loads `(context, return_buffer_ptr, args_array_ptr)` into a0/a1/a2 and
* calls the dispatcher via `jalr`.
*/
static infix_status generate_reverse_dispatcher_call_riscv64(code_buffer * buf,
infix_reverse_call_frame_layout * layout,
infix_reverse_t * context) {
infix_riscv64_emit_load_u64_immediate(buf, X_A0_REG, (uint64_t)context);
if (rv64_return_in_memory(context->return_type))
rv64_mem_ld(buf, X_A1_REG, X_SP_REG, layout->return_buffer_offset);
else
rv64_emit_compute_addr(buf, X_A1_REG, X_SP_REG, layout->return_buffer_offset);
rv64_emit_compute_addr(buf, X_A2_REG, X_SP_REG, layout->args_array_offset);
infix_riscv64_emit_load_u64_immediate(buf, RV_SCRATCH0_REG, (uint64_t)context->internal_dispatcher);
infix_riscv64_emit_jalr(buf, X_RA_REG, RV_SCRATCH0_REG, 0);
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 5 (Reverse): Generates the epilogue for the reverse trampoline stub.
*/
static infix_status generate_reverse_epilogue_riscv64(code_buffer * buf,
infix_reverse_call_frame_layout * layout,
infix_reverse_t * context) {
bool return_in_memory = rv64_return_in_memory(context->return_type);
if (context->return_type->category != INFIX_TYPE_VOID && !return_in_memory) {
rv64_return_class rc = rv64_classify_return(context->return_type);
if (rc.mixed) {
// One integer leaf in a0 and one FP leaf in fa0.
rv64_emit_load_fp_value(
buf, X_SP_REG, F_FA0_REG, layout->return_buffer_offset + rc.fp_offsets[0], rc.fp_sizes[0]);
rv64_emit_compute_addr(buf, RV_SCRATCH0_REG, X_SP_REG, layout->return_buffer_offset + rc.int_offset);
rv64_emit_load_gpr_value_sized(buf, X_A0_REG, RV_SCRATCH0_REG, rc.int_size, rc.int_signed);
}
else if (rc.fp_count == 2) {
rv64_emit_load_fp_value(
buf, X_SP_REG, F_FA0_REG, layout->return_buffer_offset + rc.fp_offsets[0], rc.fp_sizes[0]);
rv64_emit_load_fp_value(
buf, X_SP_REG, F_FA1_REG, layout->return_buffer_offset + rc.fp_offsets[1], rc.fp_sizes[1]);
}
else if (rc.fp_count == 1) {
rv64_emit_load_fp_value(
buf, X_SP_REG, F_FA0_REG, layout->return_buffer_offset + rc.fp_offsets[0], rc.fp_sizes[0]);
}
else {
rv64_emit_compute_addr(buf, RV_SCRATCH0_REG, X_SP_REG, layout->return_buffer_offset);
rv64_emit_load_gpr_value(buf, X_A0_REG, RV_SCRATCH0_REG, context->return_type);
if (context->return_type->size > 8)
rv64_mem_ld(buf, X_A1_REG, RV_SCRATCH0_REG, 8);
}
}
rv64_emit_stack_add(buf, (uint32_t)layout->total_stack_alloc);
rv64_mem_ld(buf, X_RA_REG, X_SP_REG, 0);
infix_riscv64_emit_addi(buf, X_SP_REG, X_SP_REG, RV_REV_SAVED_SIZE);
infix_riscv64_emit_jalr(buf, X_ZERO_REG, X_RA_REG, 0);
return INFIX_SUCCESS;
}
//
// Direct marshalling trampolines
//
/** @internal Scratch-space requirements for a single direct argument. */
typedef struct {
bool needs_scratch;
size_t size;
size_t align;
} rv64_direct_scratch_info;
/** @internal Compute the scratch-space needs for a direct argument. */
static rv64_direct_scratch_info rv64_direct_scratch(const infix_direct_arg_layout * arg) {
rv64_direct_scratch_info info = {false, 0, 0};
if (arg->handler->aggregate_marshaller) {
info.needs_scratch = true;
info.size = arg->type->size;
info.align = arg->type->alignment;
}
else if (arg->handler->scalar_marshaller) {
info.needs_scratch = true;
info.size = 16;
info.align = 16;
}
infix/src/arch/riscv/abi_riscv64.c view on Meta::CPAN
// The high half of a split 2xXLEN value occupies an 8-byte stack slot.
size_t end = loc->stack_offset + 8;
if (end > stack_offset)
stack_offset = end;
}
}
return (stack_offset + 15) & ~15;
}
/**
* @internal
* @brief Stage 1 (Direct): Analyzes a signature and creates a direct call frame layout.
*/
static infix_status prepare_direct_forward_call_frame_riscv64(infix_arena_t * arena,
infix_direct_call_frame_layout ** out_layout,
infix_type * ret_type,
infix_type ** arg_types,
size_t num_args,
infix_direct_arg_handler_t * handlers,
void * target_fn) {
infix_call_frame_layout * standard_layout = nullptr;
infix_status status =
prepare_forward_call_frame_riscv64(arena, &standard_layout, ret_type, arg_types, num_args, num_args, target_fn);
if (status != INFIX_SUCCESS)
return status;
infix_direct_call_frame_layout * layout =
infix_arena_calloc(arena, 1, sizeof(infix_direct_call_frame_layout), _Alignof(infix_direct_call_frame_layout));
if (layout == nullptr)
return INFIX_ERROR_ALLOCATION_FAILED;
layout->args =
infix_arena_calloc(arena, num_args, sizeof(infix_direct_arg_layout), _Alignof(infix_direct_arg_layout));
if (layout->args == nullptr && num_args > 0)
return INFIX_ERROR_ALLOCATION_FAILED;
layout->num_args = num_args;
layout->target_fn = target_fn;
layout->return_value_in_memory = standard_layout->return_value_in_memory;
size_t scratch_space_needed = 0;
for (size_t i = 0; i < num_args; ++i) {
layout->args[i].location = standard_layout->arg_locations[i];
layout->args[i].type = arg_types[i];
layout->args[i].handler = &handlers[i];
rv64_direct_scratch_info info = rv64_direct_scratch(&layout->args[i]);
if (info.needs_scratch) {
scratch_space_needed = _infix_align_up(scratch_space_needed, info.align);
scratch_space_needed += info.size;
}
}
size_t total_needed = standard_layout->total_stack_alloc + scratch_space_needed;
layout->total_stack_alloc = (total_needed + 15) & ~15;
*out_layout = layout;
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 2 (Direct): Generates the direct trampoline prologue.
*/
static infix_status generate_direct_forward_prologue_riscv64(code_buffer * buf,
infix_direct_call_frame_layout * layout) {
infix_riscv64_emit_addi(buf, X_SP_REG, X_SP_REG, -RV_FWD_SAVED_SIZE);
rv64_mem_sd(buf, X_SP_REG, RV_CTX_TARGET_REG, 0);
rv64_mem_sd(buf, X_SP_REG, RV_CTX_RET_REG, 8);
rv64_mem_sd(buf, X_SP_REG, RV_CTX_ARGS_REG, 16);
rv64_mem_sd(buf, X_SP_REG, X_RA_REG, 24);
layout->prologue_size = (uint32_t)buf->size;
// The direct CIF is called with (ret_ptr, lang_args) in a0, a1.
infix_riscv64_emit_addi(buf, RV_CTX_RET_REG, X_A0_REG, 0);
infix_riscv64_emit_addi(buf, RV_CTX_ARGS_REG, X_A1_REG, 0);
rv64_emit_stack_sub(buf, (uint32_t)layout->total_stack_alloc);
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 3 (Direct): Generates code to call marshallers and place arguments.
*/
static infix_status generate_direct_forward_argument_moves_riscv64(code_buffer * buf,
infix_direct_call_frame_layout * layout) {
if (layout->return_value_in_memory)
infix_riscv64_emit_addi(buf, X_A0_REG, RV_CTX_RET_REG, 0);
const size_t scratch_base_from_sp = rv64_direct_standard_alloc(layout);
size_t current_scratch_offset = 0;
// PHASE 1: MARSHALL & SAVE TO STACK
for (size_t i = 0; i < layout->num_args; ++i) {
const infix_direct_arg_layout * arg = &layout->args[i];
rv64_direct_scratch_info info = rv64_direct_scratch(arg);
int32_t my_scratch_offset = -1;
if (info.needs_scratch) {
current_scratch_offset = _infix_align_up(current_scratch_offset, info.align);
my_scratch_offset = (int32_t)(scratch_base_from_sp + current_scratch_offset);
current_scratch_offset += info.size;
}
if (!info.needs_scratch || (!arg->handler->aggregate_marshaller && !arg->handler->scalar_marshaller))
continue;
// a0 = language object
rv64_mem_ld(buf, X_A0_REG, RV_CTX_ARGS_REG, (int32_t)(i * sizeof(void *)));
if (arg->handler->aggregate_marshaller) {
rv64_emit_compute_addr(buf, X_A1_REG, X_SP_REG, my_scratch_offset);
infix_riscv64_emit_load_u64_immediate(buf, X_A2_REG, (uint64_t)arg->type);
infix_riscv64_emit_load_u64_immediate(buf, RV_SCRATCH0_REG, (uint64_t)arg->handler->aggregate_marshaller);
infix_riscv64_emit_jalr(buf, X_RA_REG, RV_SCRATCH0_REG, 0);
}
else {
infix_riscv64_emit_load_u64_immediate(buf, RV_SCRATCH0_REG, (uint64_t)arg->handler->scalar_marshaller);
infix_riscv64_emit_jalr(buf, X_RA_REG, RV_SCRATCH0_REG, 0);
rv64_mem_sd(buf, X_SP_REG, X_A0_REG, my_scratch_offset);
}
}
// PHASE 2: PLACE (Stack -> Registers)
current_scratch_offset = 0;
for (size_t i = 0; i < layout->num_args; ++i) {
const infix_direct_arg_layout * arg = &layout->args[i];
rv64_direct_scratch_info info = rv64_direct_scratch(arg);
int32_t my_scratch_offset = -1;
if (info.needs_scratch) {
current_scratch_offset = _infix_align_up(current_scratch_offset, info.align);
my_scratch_offset = (int32_t)(scratch_base_from_sp + current_scratch_offset);
infix/src/arch/riscv/abi_riscv64.c view on Meta::CPAN
rv64_emit_load_mixed(buf,
RV_SCRATCH0_REG,
GPR_ARGS[arg->location.reg_index],
FPR_ARGS[arg->location.reg_index2],
arg->type);
break;
case ARG_LOCATION_STACK:
if (pass_address) {
rv64_emit_compute_addr(buf, RV_SCRATCH1_REG, X_SP_REG, my_scratch_offset);
rv64_mem_sd(buf, X_SP_REG, RV_SCRATCH1_REG, (int32_t)arg->location.stack_offset);
}
else {
rv64_emit_copy_memory(buf,
X_SP_REG,
(int32_t)arg->location.stack_offset,
X_SP_REG,
my_scratch_offset,
arg->type->size);
}
break;
default:
break;
}
}
else if (arg->handler->scalar_marshaller) {
switch (arg->location.type) {
case ARG_LOCATION_GPR:
rv64_mem_ld(buf, GPR_ARGS[arg->location.reg_index], X_SP_REG, my_scratch_offset);
break;
case ARG_LOCATION_VPR:
if (is_float(arg->type)) {
// The scalar marshaller returns the value as a double; narrow it.
rv64_mem_fld(buf, FPR_ARGS[arg->location.reg_index], X_SP_REG, my_scratch_offset);
infix_riscv64_emit_fcvt_s_d(
buf, FPR_ARGS[arg->location.reg_index], FPR_ARGS[arg->location.reg_index]);
}
else {
rv64_mem_fld(buf, FPR_ARGS[arg->location.reg_index], X_SP_REG, my_scratch_offset);
}
break;
case ARG_LOCATION_STACK:
rv64_mem_ld(buf, RV_SCRATCH1_REG, X_SP_REG, my_scratch_offset);
rv64_mem_sd(buf, X_SP_REG, RV_SCRATCH1_REG, (int32_t)arg->location.stack_offset);
break;
default:
break;
}
}
}
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 3.5 (Direct): Generates the call instruction.
*/
static infix_status generate_direct_forward_call_instruction_riscv64(
code_buffer * buf, c23_maybe_unused infix_direct_call_frame_layout * layout) {
infix_riscv64_emit_load_u64_immediate(buf, RV_SCRATCH0_REG, (uint64_t)layout->target_fn);
// A non-null target skips the ebreak and calls the target; a null target falls
// through into the ebreak and traps. The target returns to the epilogue.
infix_riscv64_emit_bne(buf, RV_SCRATCH0_REG, X_ZERO_REG, 8);
infix_riscv64_emit_ebreak(buf);
infix_riscv64_emit_jalr(buf, X_RA_REG, RV_SCRATCH0_REG, 0);
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 4 (Direct): Generates the epilogue, including write-back calls.
*/
static infix_status generate_direct_forward_epilogue_riscv64(code_buffer * buf,
infix_direct_call_frame_layout * layout,
infix_type * ret_type) {
layout->epilogue_offset = (uint32_t)buf->size;
if (ret_type->category != INFIX_TYPE_VOID && !layout->return_value_in_memory) {
rv64_return_class rc = rv64_classify_return(ret_type);
if (rc.mixed) {
rv64_emit_store_fp_value(buf, RV_CTX_RET_REG, F_FA0_REG, rc.fp_offsets[0], rc.fp_sizes[0]);
rv64_emit_store_gpr_low_bytes(buf, RV_CTX_RET_REG, X_A0_REG, rc.int_offset, rc.int_size);
}
else if (rc.fp_count == 2) {
rv64_emit_store_fp_value(buf, RV_CTX_RET_REG, F_FA0_REG, rc.fp_offsets[0], rc.fp_sizes[0]);
rv64_emit_store_fp_value(buf, RV_CTX_RET_REG, F_FA1_REG, rc.fp_offsets[1], rc.fp_sizes[1]);
}
else if (rc.fp_count == 1) {
rv64_emit_store_fp_value(buf, RV_CTX_RET_REG, F_FA0_REG, rc.fp_offsets[0], rc.fp_sizes[0]);
}
else {
rv64_emit_store_gpr_return(buf, RV_CTX_RET_REG, ret_type->size);
}
}
const size_t scratch_base_from_sp = rv64_direct_standard_alloc(layout);
size_t epilogue_scratch_offset = 0;
for (size_t i = 0; i < layout->num_args; ++i) {
const infix_direct_arg_layout * arg = &layout->args[i];
rv64_direct_scratch_info info = rv64_direct_scratch(arg);
int32_t my_scratch_offset = -1;
if (info.needs_scratch) {
epilogue_scratch_offset = _infix_align_up(epilogue_scratch_offset, info.align);
my_scratch_offset = (int32_t)(scratch_base_from_sp + epilogue_scratch_offset);
epilogue_scratch_offset += info.size;
}
if (arg->handler->writeback_handler) {
// Save the C return value before calling out.
infix_riscv64_emit_addi(buf, X_SP_REG, X_SP_REG, -32);
rv64_mem_sd(buf, X_SP_REG, X_A0_REG, 0);
rv64_mem_sd(buf, X_SP_REG, X_A1_REG, 8);
rv64_mem_fsd(buf, X_SP_REG, F_FA0_REG, 16);
// a0 = language object
rv64_mem_ld(buf, X_A0_REG, RV_CTX_ARGS_REG, (int32_t)(i * sizeof(void *)));
// a1 = c_data_ptr (the scratch slot, 32 bytes above the current SP)
rv64_emit_compute_addr(buf, X_A1_REG, X_SP_REG, 32 + my_scratch_offset);
// a2 = type
infix_riscv64_emit_load_u64_immediate(buf, X_A2_REG, (uint64_t)arg->type);
// Call the handler.
infix_riscv64_emit_load_u64_immediate(buf, RV_SCRATCH0_REG, (uint64_t)arg->handler->writeback_handler);
infix_riscv64_emit_jalr(buf, X_RA_REG, RV_SCRATCH0_REG, 0);
// Restore the C return value.
rv64_mem_fld(buf, F_FA0_REG, X_SP_REG, 16);
rv64_mem_ld(buf, X_A1_REG, X_SP_REG, 8);
rv64_mem_ld(buf, X_A0_REG, X_SP_REG, 0);
infix_riscv64_emit_addi(buf, X_SP_REG, X_SP_REG, 32);
}
}
rv64_emit_stack_add(buf, (uint32_t)layout->total_stack_alloc);
rv64_mem_ld(buf, RV_CTX_TARGET_REG, X_SP_REG, 0);
rv64_mem_ld(buf, RV_CTX_RET_REG, X_SP_REG, 8);
rv64_mem_ld(buf, RV_CTX_ARGS_REG, X_SP_REG, 16);
rv64_mem_ld(buf, X_RA_REG, X_SP_REG, 24);
infix_riscv64_emit_addi(buf, X_SP_REG, X_SP_REG, RV_FWD_SAVED_SIZE);
infix_riscv64_emit_jalr(buf, X_ZERO_REG, X_RA_REG, 0);
return INFIX_SUCCESS;
}
( run in 1.472 second using v1.01-cache-2.11-cpan-4ac696b4eb4 )