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);

infix/src/arch/riscv/abi_riscv64.c  view on Meta::CPAN

            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;
}

/**
 * @internal
 * @brief Flatten an aggregate (or a scalar leaf list) into every scalar leaf.
 * @return `true` on success. A scalar type yields a single leaf.
 */
static bool rv64_flatten_all(const infix_type * type, rv64_all_leaf_list * out) {
    out->count = 0;
    return rv64_flatten_all_recursive(type, 0, out, 0);
}

/**
 * @internal
 * @brief Load an aggregate's floating-point leaves into consecutive FPRs.
 * @details A 16-byte FP leaf (long double) consumes two FPRs; smaller leaves one.
 */
static void rv64_emit_load_fp_aggregate(code_buffer * buf,
                                        uint8_t addr_reg,
                                        uint8_t base_fpr,
                                        const infix_type * type) {
    rv64_all_leaf_list leaves;
    if (!rv64_flatten_all(type, &leaves))
        return;
    uint8_t fpr = base_fpr;
    for (size_t j = 0; j < leaves.count; ++j) {
        const rv64_all_leaf * leaf = &leaves.leaves[j];
        if (leaf->is_fp) {
            if (leaf->size >= 16) {
                rv64_mem_fld(buf, fpr, addr_reg, (int32_t)leaf->offset);
                rv64_mem_fld(buf, fpr + 1, addr_reg, (int32_t)(leaf->offset + 8));
                fpr += 2;
            }
            else {
                rv64_emit_load_fp_value(buf, addr_reg, fpr++, (int32_t)leaf->offset, leaf->size);
            }
        }
    }
}

/**
 * @internal
 * @brief Store consecutive FPRs into an aggregate's floating-point leaves.
 */
static void rv64_emit_store_fp_aggregate(code_buffer * buf,
                                         uint8_t addr_reg,
                                         uint8_t base_fpr,
                                         const infix_type * type) {
    rv64_all_leaf_list leaves;
    if (!rv64_flatten_all(type, &leaves))
        return;
    uint8_t fpr = base_fpr;
    for (size_t j = 0; j < leaves.count; ++j) {
        const rv64_all_leaf * leaf = &leaves.leaves[j];
        if (leaf->is_fp) {
            if (leaf->size >= 16) {
                rv64_mem_fsd(buf, addr_reg, fpr, (int32_t)leaf->offset);
                rv64_mem_fsd(buf, addr_reg, fpr + 1, (int32_t)(leaf->offset + 8));
                fpr += 2;
            }
            else {
                rv64_emit_store_fp_value(buf, addr_reg, fpr++, (int32_t)leaf->offset, leaf->size);
            }
        }
    }
}

/**
 * @internal
 * @brief Load the integer (GPR) and FP (FPR) leaves of a mixed aggregate.
 * @details The integer leaf is extended into the GPR per its width; the FP leaf
 *          is loaded as-is. GPR leaf index is `reg_index`, FPR leaf `reg_index2`.
 */
static void rv64_emit_load_mixed(
    code_buffer * buf, uint8_t addr_reg, uint8_t gpr_reg, uint8_t fpr_reg, const infix_type * type) {
    rv64_all_leaf_list leaves;
    if (!rv64_flatten_all(type, &leaves))
        return;
    for (size_t j = 0; j < leaves.count; ++j) {
        const rv64_all_leaf * leaf = &leaves.leaves[j];
        if (leaf->is_fp)
            rv64_emit_load_fp_value(buf, addr_reg, fpr_reg, (int32_t)leaf->offset, leaf->size);
        else
            rv64_emit_load_gpr_value_sized(buf, gpr_reg, addr_reg, leaf->size, leaf->is_signed);
    }
}

/**
 * @internal
 * @brief Store the GPR and FPR leaves of a mixed aggregate back to memory.
 * @details The integer leaf is stored at its natural width so it never clobbers
 *          the FP leaf when the leaves share bytes of an 8-byte word.
 */
static void rv64_emit_store_mixed(
    code_buffer * buf, uint8_t addr_reg, uint8_t gpr_reg, uint8_t fpr_reg, const infix_type * type) {
    rv64_all_leaf_list leaves;
    if (!rv64_flatten_all(type, &leaves))
        return;
    for (size_t j = 0; j < leaves.count; ++j) {
        const rv64_all_leaf * leaf = &leaves.leaves[j];
        if (leaf->is_fp)
            rv64_emit_store_fp_value(buf, addr_reg, fpr_reg, (int32_t)leaf->offset, leaf->size);
        else



( run in 0.708 second using v1.01-cache-2.11-cpan-d01c6094234 )