swamp_code_gen/
call.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5//! `CodeBuilder` helper functions for function calls and arguments.
6
7use crate::code_bld::CodeBuilder;
8use crate::ctx::Context;
9use crate::err::Error;
10use crate::reg_pool::RegisterPool;
11use crate::state::FunctionFixup;
12use crate::{err, ArgumentAndTempScope, RepresentationOfRegisters, SpilledRegisterRegion, MAX_REGISTER_INDEX_FOR_PARAMETERS};
13use source_map_node::Node;
14use std::collections::HashSet;
15use swamp_semantic::{pretty_module_name, ArgumentExpression, InternalFunctionDefinitionRef};
16use swamp_types::prelude::Signature;
17use swamp_types::TypeKind;
18use swamp_vm_types::types::{BasicTypeRef, Destination, TypedRegister, VmType};
19use swamp_vm_types::{FrameMemoryRegion, REG_ON_FRAME_SIZE};
20
21pub struct CopyArgument {
22    pub canonical_target: TypedRegister,
23    pub source_temporary: TypedRegister,
24}
25pub struct EmitArgumentInfo {
26    pub argument_and_temp_scope: ArgumentAndTempScope,
27    pub copy_back_of_registers_mutated_by_callee: Vec<MutableReturnReg>,
28}
29
30pub struct MutableReturnReg {
31    pub target_location_after_call: Destination,
32    pub parameter_reg: TypedRegister,
33}
34
35impl CodeBuilder<'_> {
36    // TODO: for mutable arguments we want to leave output part of the spilling
37    // - store abi registers (do not save r0 if that is the destination for the call itself, since we want to clobber it in that case)
38    // - store temp registers (including the newly created replacement base regs)
39    // - call (callee is allowed to clobber r0, r1, r2, r128)
40    // - restore temp registers (so our r128 is valid again)
41    // - issue copy backs of the mutables scalars. (replacement) base reg is valid, and the r1 and r2 are clobbered, which is good.
42    // - restore abi registers
43    // - copy back of r0 for immutable scalars to target register,  unless the target was r0 itself, in that case it was left out of store abi register mask
44    pub fn spill_required_registers(&mut self, node: &Node, comment: &str) -> ArgumentAndTempScope {
45        const ABI_ARGUMENT_RETURN_AND_ARGUMENT_REGISTERS: usize =
46            MAX_REGISTER_INDEX_FOR_PARAMETERS as usize + 1; // r0-r6
47        const ABI_ARGUMENT_MASK: u8 =
48            ((1u16 << ABI_ARGUMENT_RETURN_AND_ARGUMENT_REGISTERS) - 1) as u8;
49
50        let abi_parameter_frame_memory_region = self.temp_frame_space_for_register(
51            ABI_ARGUMENT_RETURN_AND_ARGUMENT_REGISTERS as u8,
52            &format!("emit abi arguments r0-r6 {comment}"),
53        );
54
55        self.builder.add_st_masked_regs_to_frame(
56            abi_parameter_frame_memory_region.addr,
57            ABI_ARGUMENT_MASK,
58            node,
59            "spill masked registers to stack frame memory.",
60        );
61
62        let abi_parameter_region = SpilledRegisterRegion {
63            registers: RepresentationOfRegisters::Mask(ABI_ARGUMENT_MASK),
64            frame_memory_region: abi_parameter_frame_memory_region,
65        };
66
67        let (first_temp_register_index, temp_register_probable_live_count) =
68            self.temp_registers.start_index_and_number_of_allocated();
69        debug_assert_eq!(first_temp_register_index, 128);
70
71        let temp_register_region = if temp_register_probable_live_count > 0 {
72            let temp_register_frame_memory_region = self.temp_frame_space_for_register(temp_register_probable_live_count, &format!("emit temp arguments from r{first_temp_register_index} count:{temp_register_probable_live_count} {comment}"));
73            let temp_register_region = SpilledRegisterRegion {
74                registers: RepresentationOfRegisters::Range {
75                    start_reg: first_temp_register_index,
76                    count: temp_register_probable_live_count,
77                },
78                frame_memory_region: temp_register_frame_memory_region,
79            };
80
81            self.builder.add_st_contiguous_regs_to_frame(
82                temp_register_frame_memory_region,
83                first_temp_register_index,
84                temp_register_probable_live_count,
85                node,
86                "spill contiguous range of registers to stack frame memory",
87            );
88            Some(temp_register_region)
89        } else {
90            None
91        };
92
93        ArgumentAndTempScope {
94            argument_registers: abi_parameter_region,
95            scratch_registers: temp_register_region,
96        }
97    }
98
99    pub fn setup_return_pointer_reg(
100        &mut self,
101        output_destination: &Destination,
102        return_basic_type: BasicTypeRef,
103        node: &Node,
104    ) {
105        let r0 = TypedRegister::new_vm_type(0, VmType::new_unknown_placement(return_basic_type));
106
107        let return_pointer_reg = self.emit_compute_effective_address_to_register(
108            output_destination,
109            node,
110            "r0: create an absolute pointer to r0 if needed",
111        );
112
113        self.builder.add_mov_reg(
114            &r0,
115            &return_pointer_reg,
116            node,
117            "r0: copy the return pointer into r0",
118        );
119    }
120
121    fn emit_single_argument(
122        &mut self,
123        argument_expr: &ArgumentExpression,
124        argument_to_use: &TypedRegister,
125        target_canonical_argument_register: &TypedRegister,
126        parameter_basic_type: &BasicTypeRef,
127        copy_back_phase_one: &mut Vec<MutableReturnReg>,
128        node: &Node,
129        ctx: &Context,
130    ) {
131        match argument_expr {
132            ArgumentExpression::BorrowMutableReference(lvalue) => {
133                let original_destination = self.emit_lvalue_address(lvalue, ctx);
134
135                if parameter_basic_type.should_be_copied_back_when_mutable_arg_or_return() {
136                    // Load the primitive from memory
137                    self.emit_transfer_value_to_register(
138                        argument_to_use,
139                        &original_destination,
140                        node,
141                        "must get primitive from lvalue and pass as copy back (by value)",
142                    );
143
144                    // Add a copy back to the original location (base register will be restored by spill/restore)
145                    copy_back_phase_one.push(MutableReturnReg {
146                        target_location_after_call: original_destination,
147                        parameter_reg: target_canonical_argument_register.clone(),
148                    });
149                } else {
150                    let flattened_source_pointer_reg = self
151                        .emit_compute_effective_address_to_register(
152                            &original_destination,
153                            node,
154                            "flattened into absolute pointer",
155                        );
156                    self.builder.add_mov_reg(
157                        argument_to_use,
158                        &flattened_source_pointer_reg,
159                        node,
160                        "copy absolute address",
161                    );
162                }
163            }
164            ArgumentExpression::MaterializedExpression(expr) => {
165                if Self::rvalue_needs_memory_location_to_materialize_in(&mut self.state.layout_cache, expr) {
166                    // Use the helper function to get a pointer to the temporary storage
167                    let temp_ptr = self.emit_scalar_rvalue_or_pointer_to_temporary(expr, ctx, true);
168
169                    self.builder.add_mov_reg(
170                        argument_to_use,
171                        &temp_ptr,
172                        node,
173                        "copy temporary storage address to argument register",
174                    );
175                } else {
176                    self.emit_expression_into_register(
177                        argument_to_use,
178                        expr,
179                        "argument expression into specific argument register",
180                        ctx,
181                    );
182                }
183            }
184
185            ArgumentExpression::Expression(expr) => {
186                // Normal case: expression can be materialized directly into register
187                self.emit_expression_into_register(
188                    argument_to_use,
189                    expr,
190                    "argument expression into specific argument register",
191                    ctx,
192                );
193            }
194        }
195    }
196
197
198    pub(crate) fn emit_arguments(
199        &mut self,
200        output_destination: &Destination,
201        node: &Node,
202        signature: &Signature,
203        self_variable: Option<&TypedRegister>,
204        arguments: &[ArgumentExpression],
205        is_host_call: bool,
206        ctx: &Context,
207    ) -> EmitArgumentInfo {
208        let mut copy_back_operations: Vec<MutableReturnReg> = Vec::new();
209        let has_return_value = !matches!(&*signature.return_type.kind, TypeKind::Unit);
210
211        // Step 1: Spill live registers before we start using ABI registers
212        let spill_scope = self.spill_required_registers(node, "spill before emit arguments");
213
214        // Step 2: Handle return value setup
215        if has_return_value {
216            let return_basic_type = self.state.layout_cache.layout(&signature.return_type);
217
218            if return_basic_type.is_aggregate() {
219                // For aggregates: initialize the destination space first, then set up r0 as pointer to destination
220                self.setup_return_pointer_reg(output_destination, return_basic_type, node);
221            } else {
222                // For primitives: add r0 to copy-back list (function writes to r0, we copy to destination)
223                let r0 =
224                    TypedRegister::new_vm_type(0, VmType::new_unknown_placement(return_basic_type));
225                copy_back_operations.push(MutableReturnReg {
226                    target_location_after_call: output_destination.clone(),
227                    parameter_reg: r0,
228                });
229            }
230        }
231
232        assert!(
233            signature.parameters.len() <= MAX_REGISTER_INDEX_FOR_PARAMETERS.into(),
234            "signature is wrong {signature:?}"
235        );
236
237        // Step 3: Prepare argument registers and handle temporary register conflicts
238        let mut temp_to_abi_copies = Vec::new();
239        let mut argument_registers = RegisterPool::new(1, 6); // r1-r6 for arguments
240
241        for (index_in_signature, type_for_parameter) in signature.parameters.iter().enumerate() {
242            let parameter_basic_type = self
243                .state
244                .layout_cache
245                .layout(&type_for_parameter.resolved_type);
246            let target_canonical_argument_register = argument_registers.alloc_register(
247                VmType::new_unknown_placement(parameter_basic_type.clone()),
248                &format!("{index_in_signature}:{}", type_for_parameter.name),
249            );
250
251            let argument_to_use = if self.argument_needs_to_be_in_a_temporary_register_first(
252                &target_canonical_argument_register,
253            ) {
254                let temp_reg = self.temp_registers.allocate(
255                    target_canonical_argument_register.ty.clone(),
256                    &format!(
257                        "temporary argument for '{}'",
258                        target_canonical_argument_register.comment
259                    ),
260                );
261                let copy_argument = CopyArgument {
262                    canonical_target: target_canonical_argument_register.clone(),
263                    source_temporary: temp_reg.register.clone(),
264                };
265                temp_to_abi_copies.push(copy_argument);
266                temp_reg.register
267            } else {
268                target_canonical_argument_register.clone()
269            };
270
271            // Handle self variable (first parameter) vs regular arguments
272            if index_in_signature == 0 && self_variable.is_some() {
273                let self_reg = self_variable.as_ref().unwrap();
274                if self_reg.index != argument_to_use.index {
275                    self.builder.add_mov_reg(
276                        &argument_to_use,
277                        self_reg,
278                        node,
279                        &format!(
280                            "move self_variable ({}) to first argument register",
281                            self_reg.ty
282                        ),
283                    );
284                }
285            } else {
286                // Regular argument - get from arguments array
287                let argument_vector_index = if self_variable.is_some() {
288                    index_in_signature - 1
289                } else {
290                    index_in_signature
291                };
292                let argument_expr_or_location = &arguments[argument_vector_index];
293
294                self.emit_single_argument(
295                    argument_expr_or_location,
296                    &argument_to_use,
297                    &target_canonical_argument_register,
298                    &parameter_basic_type,
299                    &mut copy_back_operations,
300                    node,
301                    ctx,
302                );
303            }
304        }
305
306        // Step 4: Copy from temporary registers to final ABI argument registers
307        for (index, copy_argument) in temp_to_abi_copies.iter().enumerate() {
308            let parameter_in_signature = &signature.parameters[index];
309            self.builder.add_mov_reg(
310                &copy_argument.canonical_target,
311                &copy_argument.source_temporary,
312                node,
313                &format!(
314                    "copy argument {index} ({}) in place from temporary '{}'",
315                    parameter_in_signature.name, copy_argument.source_temporary.comment
316                ),
317            );
318        }
319
320        EmitArgumentInfo {
321            argument_and_temp_scope: spill_scope,
322            copy_back_of_registers_mutated_by_callee: copy_back_operations,
323        }
324    }
325
326    pub(crate) fn emit_post_call(
327        &mut self,
328        spilled_arguments: EmitArgumentInfo,
329        node: &Node,
330        comment: &str,
331    ) {
332        // Phase 1: Save current mutable parameter values to temporary safe space before registers get clobbered
333        let mut temp_saved_values = Vec::new();
334        for copy_back in &spilled_arguments.copy_back_of_registers_mutated_by_callee {
335            let temp_reg = self.temp_registers.allocate(
336                copy_back.parameter_reg.ty.clone(),
337                &format!(
338                    "temp save for copy-back of {}",
339                    copy_back.parameter_reg.comment
340                ),
341            );
342
343            self.builder.add_mov_reg(
344                temp_reg.register(),
345                &copy_back.parameter_reg,
346                node,
347                &format!(
348                    "save {} to temp before register restoration",
349                    copy_back.parameter_reg
350                ),
351            );
352
353            temp_saved_values.push((temp_reg, copy_back));
354        }
355
356        // Phase 2: Restore all spilled registers (temp registers first, then variables, then arguments)
357        if let Some(scratch_region) = spilled_arguments.argument_and_temp_scope.scratch_registers {
358            self.emit_restore_region(scratch_region, &HashSet::new(), node, comment);
359        }
360
361        // Restore argument registers - cool thing with this approach is that we don't have to bother with restoring some of them
362        self.emit_restore_region(
363            spilled_arguments.argument_and_temp_scope.argument_registers,
364            &HashSet::new(),
365            node,
366            comment,
367        );
368
369        // Phase 3: Copy from temporary safe registers to the final destinations
370        for (temp_reg, copy_back) in temp_saved_values {
371            let temp_source = Destination::Register(temp_reg.register().clone());
372            self.emit_copy_value_between_destinations(
373                &copy_back.target_location_after_call,
374                &temp_source,
375                node,
376                "copy-back from temp to final destination",
377            );
378        }
379    }
380
381    #[allow(clippy::too_many_lines)]
382    pub fn emit_restore_region(
383        &mut self,
384        region: SpilledRegisterRegion,
385        output_destination_registers: &HashSet<u8>, // TODO: Remove this
386        node: &Node,
387        comment: &str,
388    ) {
389        match region.registers {
390            RepresentationOfRegisters::Individual(spilled_registers_list) => {
391                if !spilled_registers_list.is_empty() {
392                    let mut sorted_regs = spilled_registers_list;
393                    sorted_regs.sort_by_key(|reg| reg.index);
394
395                    // Filter out registers that are in output_destination_registers
396                    let filtered_regs: Vec<_> = sorted_regs
397                        .into_iter()
398                        .filter(|reg| !output_destination_registers.contains(&reg.index))
399                        .collect();
400
401                    if !filtered_regs.is_empty() {
402                        let mut i = 0;
403                        while i < filtered_regs.len() {
404                            let seq_start_idx = i;
405                            let start_reg = filtered_regs[i].index;
406                            let mut seq_length = 1;
407
408                            while i + 1 < filtered_regs.len()
409                                && filtered_regs[i + 1].index == filtered_regs[i].index + 1
410                            {
411                                seq_length += 1;
412                                i += 1;
413                            }
414
415                            let memory_offset = if seq_start_idx > 0 {
416                                (filtered_regs[seq_start_idx].index - filtered_regs[0].index)
417                                    as usize
418                                    * REG_ON_FRAME_SIZE.0 as usize
419                            } else {
420                                0
421                            };
422
423                            let specific_mem_location = FrameMemoryRegion {
424                                addr: region.frame_memory_region.addr
425                                    + swamp_vm_types::MemoryOffset(memory_offset as u32),
426                                size: REG_ON_FRAME_SIZE,
427                            };
428
429                            self.builder.add_ld_contiguous_regs_from_frame(
430                                start_reg,
431                                specific_mem_location,
432                                seq_length,
433                                node,
434                                &format!(
435                                    "restoring r{}-r{} (sequence) {comment}",
436                                    start_reg,
437                                    start_reg + seq_length - 1
438                                ),
439                            );
440
441                            i += 1;
442                        }
443                    }
444                }
445            }
446
447            RepresentationOfRegisters::Mask(original_spill_mask) => {
448                let mut mask_to_actually_restore = original_spill_mask;
449
450                for i in 0..8 {
451                    let reg_idx = i as u8;
452                    if (original_spill_mask >> i) & 1 != 0
453                        && output_destination_registers.contains(&reg_idx)
454                    {
455                        mask_to_actually_restore &= !(1 << i); // Clear the bit: don't restore this one
456                    }
457                }
458
459                if mask_to_actually_restore != 0 {
460                    self.builder.add_ld_masked_regs_from_frame(
461                        mask_to_actually_restore,
462                        region.frame_memory_region,
463                        node,
464                        &format!("restore registers using mask {comment}"),
465                    );
466                }
467            }
468            RepresentationOfRegisters::Range { start_reg, count } => {
469                let base_mem_addr_of_spilled_range = region.frame_memory_region.addr;
470
471                // Find contiguous sequences of registers that need to be restored
472                let mut i = 0;
473                while i < count {
474                    while i < count && output_destination_registers.contains(&(start_reg + i)) {
475                        i += 1;
476                    }
477
478                    if i < count {
479                        let seq_start_reg = start_reg + i;
480                        let seq_start_offset = (i as usize) * REG_ON_FRAME_SIZE.0 as usize;
481                        let mut seq_length = 1;
482
483                        while i + seq_length < count
484                            && !output_destination_registers.contains(&(start_reg + i + seq_length))
485                        {
486                            seq_length += 1;
487                        }
488
489                        let specific_mem_location = FrameMemoryRegion {
490                            addr: base_mem_addr_of_spilled_range
491                                + swamp_vm_types::MemoryOffset(seq_start_offset as u32),
492                            size: REG_ON_FRAME_SIZE,
493                        };
494
495                        self.builder.add_ld_contiguous_regs_from_frame(
496                            seq_start_reg,
497                            specific_mem_location,
498                            seq_length,
499                            node,
500                            &format!(
501                                "restoring spilled contiguous range of registers from stack frame r{}-r{} {comment}",
502                                seq_start_reg,
503                                seq_start_reg + seq_length - 1
504                            ),
505                        );
506
507                        i += seq_length;
508                    }
509                }
510            }
511        }
512    }
513
514    pub(crate) fn emit_call(
515        &mut self,
516        node: &Node,
517        internal_fn: &InternalFunctionDefinitionRef,
518        comment: &str,
519    ) {
520        let function_name = internal_fn.associated_with_type.as_ref().map_or_else(
521            || {
522                format!(
523                    "{}::{}",
524                    pretty_module_name(&internal_fn.defined_in_module_path),
525                    internal_fn.assigned_name
526                )
527            },
528            |associated_with_type| {
529                format!(
530                    "{}::{}:{}",
531                    pretty_module_name(&internal_fn.defined_in_module_path),
532                    associated_with_type,
533                    internal_fn.assigned_name
534                )
535            },
536        );
537        let call_comment = &format!("calling `{function_name}` ({comment})", );
538
539        let patch_position = self.builder.add_call_placeholder(node, call_comment);
540        self.state.function_fixups.push(FunctionFixup {
541            patch_position,
542            fn_id: internal_fn.program_unique_id,
543            internal_function_definition: internal_fn.clone(),
544        });
545        //}
546    }
547    pub(crate) fn emit_internal_call(
548        &mut self,
549        target_reg: &Destination,
550        node: &Node,
551        internal_fn: &InternalFunctionDefinitionRef,
552        arguments: &Vec<ArgumentExpression>,
553        ctx: &Context,
554    ) {
555        let argument_info = self.emit_arguments(
556            target_reg,
557            node,
558            &internal_fn.signature,
559            None,
560            arguments,
561            false,
562            ctx,
563        );
564
565        self.emit_call(node, internal_fn, "call"); // will be fixed up later
566
567        if !matches!(&*internal_fn.signature.return_type.kind, TypeKind::Never) {
568            self.emit_post_call(argument_info, node, "restore spilled after call");
569        }
570    }
571
572    /// If you're not on the last argument for "Outer Function", you have to put
573    /// that value away somewhere safe for a bit. Otherwise, when you're figuring out the next arguments,
574    /// you might accidentally overwrite it.
575    /// But if you are on the last argument, you can just drop it right where it needs to go.
576    const fn argument_needs_to_be_in_a_temporary_register_first(
577        &self,
578        reg: &TypedRegister,
579    ) -> bool {
580        // TODO: for now just assume it is
581        true
582    }
583
584    fn add_error(&mut self, error_kind: err::ErrorKind, node: &Node) {
585        self.errors.push(Error {
586            node: node.clone(),
587            kind: error_kind,
588        });
589    }
590}