Affix
view release on metacpan or search on metacpan
infix/src/arch/x64/abi_win_x64.c view on Meta::CPAN
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->return_value_in_memory = return_value_is_by_reference(ret_type);
size_t arg_position = 0;
if (layout->return_value_in_memory)
arg_position++; // The hidden return pointer consumes the first slot (RCX).
size_t current_stack_offset = SHADOW_SPACE;
size_t max_align = 16;
layout->num_stack_args = 0;
for (size_t i = 0; i < num_args; ++i) {
infix_type * current_type = arg_types[i];
if (current_type->alignment > max_align)
max_align = current_type->alignment;
// Detect vectors as FP so they get XMM slots if passed by value (<=16 bytes).
bool is_fp = is_float16(current_type) || is_float(current_type) || is_double(current_type) ||
(current_type->category == INFIX_TYPE_VECTOR);
// as FP register args in the slot assignment logic
// (they go to GPR slots as pointers).
bool is_ref = is_passed_by_reference(current_type);
bool is_variadic_arg = (i >= num_fixed_args);
if (arg_position < 4) {
if (is_fp && !is_ref && !is_variadic_arg) {
layout->arg_locations[i].type = ARG_LOCATION_XMM;
layout->arg_locations[i].reg_index = (uint8_t)arg_position++;
}
else {
layout->arg_locations[i].type = ARG_LOCATION_GPR;
layout->arg_locations[i].reg_index = (uint8_t)arg_position++;
}
}
else {
layout->arg_locations[i].type = ARG_LOCATION_STACK;
current_stack_offset = _infix_align_up(current_stack_offset, current_type->alignment);
layout->arg_locations[i].stack_offset = (uint32_t)current_stack_offset;
layout->num_stack_args++;
// Calculate space needed on the stack for this argument.
// By-reference types (including arrays/vectors) are just a pointer (8 bytes).
size_t arg_stack_space = is_ref ? 8 : ((current_type->size + 7) & ~7);
current_stack_offset += arg_stack_space;
// Step 0: Make sure we aren't blowing ourselves up
if (current_stack_offset > INFIX_MAX_ARG_SIZE) {
*out_layout = nullptr;
return INFIX_ERROR_LAYOUT_FAILED;
}
}
}
if (ret_type->alignment > max_align)
max_align = ret_type->alignment;
size_t total_stack_arg_size = current_stack_offset;
// Total allocation must include shadow space and be aligned to max_align.
layout->total_stack_alloc = (uint32_t)_infix_align_up(total_stack_arg_size, max_align);
layout->max_align = (uint32_t)max_align;
// Prevent integer overflow and excessive stack allocation.
if (layout->total_stack_alloc > INFIX_MAX_STACK_ALLOC) {
fprintf(stderr, "Error: Calculated stack allocation exceeds safe limit of %d bytes.\n", 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 for the Windows x64 trampoline.
* @details This function emits the standard machine code required at the beginning of a function.
* The generated assembly performs these steps:
* 1. `push rbp` / `mov rbp, rsp`: Creates a standard stack frame.
* 2. `push r12-r15`: Saves all callee-saved registers that the trampoline will
* use to hold its context.
* 3. `and rsp, -16`: **Forces 16-byte stack alignment**. This is critical because
* SIMD instructions in the target function may segfault if the stack is misaligned.
* 4. `mov r12, rcx`, etc.: Moves the trampoline's own arguments into preserved registers.
* 5. `sub rsp, imm32`: Allocates the required space on the stack.
*
* @param buf The code buffer to write the assembly into.
* @param layout The call frame layout containing total stack allocation information.
* @return `INFIX_SUCCESS` on successful code generation.
*/
static infix_status generate_forward_prologue_win_x64(code_buffer * buf, infix_call_frame_layout * layout) {
emit_push_reg(buf, RBP_REG); // push rbp
// Save callee-saved registers we will use to hold our context.
emit_push_reg(buf, R12_REG); // push r12 (will hold target function address)
emit_push_reg(buf, R13_REG); // push r13 (will hold return value pointer)
emit_push_reg(buf, R14_REG); // push r14 (will hold argument pointers array)
emit_push_reg(buf, R15_REG); // push r15 (will be a scratch register for data moves)
emit_mov_reg_reg(buf, RBP_REG, RSP_REG); // mov rbp, rsp
layout->prologue_size = (uint32_t)buf->size;
// FORCE 16-BYTE ALIGNMENT.
// AND RSP, -16
emit_and_reg_imm8(buf, RSP_REG, -16);
// Move incoming trampoline arguments to non-volatile registers.
if (layout->target_fn == nullptr) { // Unbound: (target_fn, ret_ptr, args_ptr) in RCX, RDX, R8
emit_mov_reg_reg(buf, R12_REG, RCX_REG); // R12 = target function
emit_mov_reg_reg(buf, R13_REG, RDX_REG); // R13 = return value buffer
emit_mov_reg_reg(buf, R14_REG, R8_REG); // R14 = argument values array
}
else { // Bound: (ret_ptr, args_ptr) in RCX, RDX
emit_mov_reg_reg(buf, R13_REG, RCX_REG); // R13 = return value buffer
emit_mov_reg_reg(buf, R14_REG, RDX_REG); // R14 = argument values array
}
// Allocate stack space for arguments and shadow space.
if (layout->total_stack_alloc > 0)
emit_sub_reg_imm32(buf, RSP_REG, (int32_t)layout->total_stack_alloc);
return INFIX_SUCCESS;
}
/**
* @internal
* @brief Stage 3 (Forward): Generates code to move arguments into their native locations.
* @details This function iterates through the layout blueprint and emits `mov` instructions
* to place each argument into its assigned register or stack slot.
*
( run in 1.918 second using v1.01-cache-2.11-cpan-9169edd2b0e )