swamp_code_gen/
transfer.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 */
5use crate::DetailedLocationResolved;
6use crate::code_bld::CodeBuilder;
7use source_map_node::Node;
8use swamp_vm_isa::COLLECTION_CAPACITY_OFFSET;
9use swamp_vm_types::MemoryLocation;
10use swamp_vm_types::types::{Place, TypedRegister, VmType, u16_type};
11
12impl CodeBuilder<'_> {
13    // Load -------------------------------------------------------
14
15    /// Transfers a **value** from a given `Destination` (either a register or a memory location)
16    /// into a specified `target_reg`.
17    ///
18    /// This function acts as a primary entry point for ensuring a value resides in a register.
19    /// If the `source` is a `Register`, it generates a register-to-register copy if the
20    /// source and target are not already the same. If the `source` is `Memory`, it delegates
21    /// the load operation to `emit_load_value_from_memory_source`.
22    pub(crate) fn emit_transfer_value_to_register(
23        &mut self,
24        target_reg: &TypedRegister,
25        source: &Place,
26        node: &Node,
27        comment: &str,
28    ) {
29        match source {
30            Place::Register(source_reg) => {
31                if target_reg.index != source_reg.index {
32                    self.emit_copy_register(target_reg, source_reg, node, comment);
33                }
34            }
35            Place::Memory(memory_location) => {
36                self.emit_load_value_from_memory_source(target_reg, memory_location, node, comment);
37            }
38            Place::Discard => panic!("Cannot load from Unit destination"),
39        }
40    }
41
42    /// Loads a **value** from a `MemoryLocation` into a `target_reg`.
43    ///
44    /// This function serves as a helper for `emit_transfer_value_to_register` when the
45    /// source is memory. It distinguishes between loading scalar values and
46    /// loading pointer-like values (e.g., references) that are themselves stored within
47    /// registers. For scalar loads, it utilizes `emit_load_scalar_from_memory_offset_instruction`.
48    pub(crate) fn emit_load_value_from_memory_source(
49        &mut self,
50        target_reg: &TypedRegister,
51        source_memory_location: &MemoryLocation,
52        node: &Node,
53        comment: &str,
54    ) {
55        let source_type = source_memory_location.vm_type();
56        if source_type.is_reg_copy() {
57            self.emit_load_scalar_from_memory_offset_instruction(
58                target_reg,
59                source_memory_location,
60                node,
61                &format!("emit primitive value. ptr to primitive reg {comment}"),
62            );
63        } else {
64            // We want the pointer since it is an aggregate. So either just copy the register
65            // or flatten the pointer
66            let source_loc = Place::Memory(source_memory_location.clone());
67
68            self.emit_compute_effective_address_to_target_register(
69                target_reg,
70                &source_loc,
71                node,
72                "copy aggregate pointer to target register",
73            );
74        }
75    }
76
77    /// Loads a scalar value or computes the absolute pointer for an aggregate type.
78    ///
79    /// This function handles the two main cases for intrinsic function arguments:
80    ///
81    /// - **Scalar values**: Ensures the actual primitive value is loaded into a register
82    /// - **Aggregate types**: Computes and returns the absolute memory address pointer
83    ///
84    /// This is specifically designed for intrinsic calls where:
85    /// - Scalar intrinsics need the actual value (not a pointer to the value)
86    /// - Aggregate intrinsics need a flattened absolute pointer for operations
87    ///
88    /// # Arguments
89    /// * `destination` - The source location (register, memory, or unit)
90    /// * `node` - Source location for debugging
91    /// * `comment` - Description for generated assembly comments
92    ///
93    /// # Returns
94    /// * `Some(TypedRegister)` - Register containing the scalar value or aggregate pointer
95    /// * `None` - If the destination is Unit
96    pub(crate) fn emit_load_scalar_or_absolute_aggregate_pointer(
97        &mut self,
98        destination: &Place,
99        node: &Node,
100        comment: &str,
101    ) -> Option<TypedRegister> {
102        if matches!(destination, Place::Discard) {
103            None
104        } else {
105            let vm_type = destination.vm_type().unwrap();
106
107            if vm_type.is_scalar() {
108                // Scalar case
109                match destination {
110                    Place::Register(reg) => {
111                        // a) if it is a register, we are done: use that register
112                        Some(reg.clone())
113                    }
114                    Place::Memory(memory_location) => {
115                        // b) if it is a memory location: load_scalar_from_memory_location
116                        let scalar_temp = self.temp_registers.allocate(
117                            memory_location.ty.clone(),
118                            &format!("load scalar from memory for intrinsic: {comment}"),
119                        );
120
121                        self.emit_load_scalar_from_memory_offset_instruction(
122                            scalar_temp.register(),
123                            memory_location,
124                            node,
125                            &format!("load scalar value from memory for intrinsic: {comment}"),
126                        );
127
128                        Some(scalar_temp.register)
129                    }
130                    Place::Discard => unreachable!(), // Already handled above
131                }
132            } else {
133                // Aggregate case: we only need to flatten it if needed
134                Some(self.emit_compute_effective_address_to_register(
135                    destination,
136                    node,
137                    &format!("flatten aggregate for intrinsic: {comment}"),
138                ))
139            }
140        }
141    }
142
143    /// Loads a scalar value or calculates the effective address for an aggregate type.
144    ///
145    /// It is basically a "bind" of a register to a location, and is used (exclusively?)
146    /// in "pattern matching", like `match` arms.
147    ///
148    /// For scalar types: Loads the actual value into the target register
149    /// For aggregate types: Calculates and stores the effective address in the target register
150    pub(crate) fn emit_load_or_calculate_address_from_memory(
151        &mut self,
152        target_reg: &TypedRegister,
153        source_memory_location: &MemoryLocation,
154        node: &Node,
155        comment: &str,
156    ) {
157        let source_type = source_memory_location.vm_type();
158        if source_type.is_aggregate() {
159            self.emit_compute_effective_address_to_target_register(
160                target_reg,
161                &Place::Memory(source_memory_location.clone()),
162                node,
163                comment,
164            );
165        } else {
166            // For scalars, load the actual value
167            self.emit_load_scalar_from_memory_offset_instruction(
168                target_reg,
169                source_memory_location,
170                node,
171                &format!("load scalar value {comment}"),
172            );
173        }
174    }
175
176    /// Ensures that a value from a given `Destination` is available in a register.
177    ///
178    /// In compiler design, "materialization" refers to the process of bringing a value from an
179    /// abstract or indirect representation (like a variable name, or a value stored in memory)
180    /// into a concrete, directly usable form, which is typically a CPU register.
181    ///
182    /// If the `location` is already a `Register`, it's returned directly. If the `location`
183    /// is `Memory`, a temporary register is allocated, and the primitive value is loaded
184    /// into it using `emit_load_value_from_memory_source`, ensuring it's ready for
185    /// register-based operations.
186    pub(crate) fn emit_materialize_value_to_register(
187        &mut self,
188        location: &Place,
189        node: &Node,
190        comment: &str,
191    ) -> DetailedLocationResolved {
192        match location {
193            Place::Register(reg) => DetailedLocationResolved::Register(reg.clone()),
194            Place::Memory(memory_location) => {
195                let temp_reg_target = self.temp_registers.allocate(
196                    memory_location.ty.clone(),
197                    "emit load primitive from location",
198                );
199                self.emit_load_value_from_memory_source(
200                    temp_reg_target.register(),
201                    memory_location,
202                    node,
203                    &format!("load primitive from detailed location {comment}"),
204                );
205                DetailedLocationResolved::TempRegister(temp_reg_target)
206            }
207            Place::Discard => {
208                panic!("")
209            }
210        }
211    }
212
213    // Store -------------------------------------------------------
214
215    pub fn emit_check_that_known_len_is_less_or_equal_to_capacity(
216        &mut self,
217        destination_memory_location: &MemoryLocation,
218        len: usize,
219        node: &Node,
220        comment: &str,
221    ) -> TypedRegister {
222        // First check if the destination capacity is greater or equal to the source location so we don't overwrite too much
223        // Then always preserve the capacity. We should fill in the elements count and other things, but never overwrite capacity.
224
225        let destination_capacity_reg = self.temp_registers.allocate(
226            VmType::new_contained_in_register(u16_type()),
227            "destination capacity",
228        );
229        self.builder.add_ld16_from_pointer_from_memory_location(
230            destination_capacity_reg.register(),
231            &destination_memory_location.unsafe_add_offset(COLLECTION_CAPACITY_OFFSET),
232            node,
233            &format!("{comment} - load capacity for destination"),
234        );
235
236        let source_length_reg = self.temp_registers.allocate(
237            VmType::new_contained_in_register(u16_type()),
238            "source capacity",
239        );
240
241        self.builder.add_mov_16_immediate_value(
242            source_length_reg.register(),
243            len as u16,
244            node,
245            "known length size",
246        );
247
248        self.builder.add_trap_if_lt(
249            destination_capacity_reg.register(),
250            source_length_reg.register(),
251            node,
252            &format!("{comment} - verify that we are within bounds"),
253        );
254
255        source_length_reg.register
256    }
257    pub(crate) fn emit_copy_vec_like_value_helper(
258        &mut self,
259        destination_memory_location: &MemoryLocation,
260        source_memory_location: &MemoryLocation,
261        node: &Node,
262        comment: &str,
263    ) {
264        let destination_pointer = self.emit_compute_effective_address_from_location_to_register(
265            destination_memory_location,
266            node,
267            "get the destination vec",
268        );
269        let source_pointer = self.emit_compute_effective_address_from_location_to_register(
270            source_memory_location,
271            node,
272            "get vector source address",
273        );
274
275        self.builder.add_vec_copy(
276            &destination_pointer,
277            &source_pointer,
278            node,
279            "copy over, but leave the capacity on the destination",
280        );
281    }
282
283    /// Copies the data for a map-like collection using open addressing.
284    ///
285    /// This implementation safely handles maps with different capacities by:
286    /// 1. Verifying that the source `element_count` doesn't exceed target capacity
287    /// 2. Re-inserting each element into the target map individually
288    ///
289    /// Rather than performing a direct bucket array copy (which would be incorrect when
290    /// capacities differ), this approach ensures each element is inserted at the correct
291    /// index in the target map. Since element positions are determined by `hash % capacity`,
292    /// this recalculation is essential when source and target capacities differ.
293    ///
294    /// Performance note: When source and target have identical capacities, a more
295    /// efficient direct copy could be used, but this implementation prioritizes
296    /// correctness across all scenarios.
297    pub(crate) fn emit_copy_map_like_value_helper(
298        &mut self,
299        destination_memory_location: &MemoryLocation,
300        source_memory_location: &MemoryLocation,
301        node: &Node,
302        comment: &str,
303    ) {
304        let destination_ptr_location = self
305            .emit_compute_effective_address_from_location_to_register(
306                destination_memory_location,
307                node,
308                comment,
309            );
310        let source_ptr_location = self.emit_compute_effective_address_from_location_to_register(
311            source_memory_location,
312            node,
313            comment,
314        );
315        self.builder.add_map_overwrite(
316            &destination_ptr_location,
317            &source_ptr_location,
318            node,
319            comment,
320        );
321    }
322
323    pub(crate) fn emit_copy_aggregate_value_helper(
324        &mut self,
325        destination_memory_location: &MemoryLocation,
326        source_memory_location: &MemoryLocation,
327        node: &Node,
328        comment: &str,
329    ) {
330        let ty = &source_memory_location.ty;
331        if ty.is_collection_like() {
332            if ty.basic_type.is_vec_like() {
333                if let (Some(_element_size), Some(_header_size)) = (
334                    source_memory_location
335                        .ty
336                        .basic_type
337                        .bucket_size_for_vec_like(),
338                    source_memory_location
339                        .ty
340                        .basic_type
341                        .header_size_for_vec_like(),
342                ) {
343                    self.emit_copy_vec_like_value_helper(
344                        destination_memory_location,
345                        source_memory_location,
346                        node,
347                        comment,
348                    );
349                } else {
350                    // Fallback to block copy for vec-like types that don't have proper size methods
351                    self.emit_block_copy_with_size_from_location(
352                        destination_memory_location,
353                        source_memory_location,
354                        node,
355                        &format!("block copy {comment} (vec-like fallback) to memory pointed by register {destination_memory_location} <- {source_memory_location}"),
356                    );
357                }
358            } else {
359                self.emit_compute_effective_address_from_location_to_register(
360                    destination_memory_location,
361                    node,
362                    comment,
363                );
364                self.emit_copy_map_like_value_helper(
365                    destination_memory_location,
366                    source_memory_location,
367                    node,
368                    comment,
369                );
370            }
371        } else {
372            self.emit_block_copy_with_size_from_location(
373                destination_memory_location,
374                source_memory_location,
375                node,
376                &format!("block copy {comment} to memory pointed by register {destination_memory_location} <- {source_memory_location}"),
377            );
378        }
379    }
380
381    pub fn emit_copy_value_between_places(
382        &mut self,
383        output_place: &Place,
384        value_source: &Place,
385        node: &Node,
386        comment: &str,
387    ) {
388        match output_place {
389            Place::Register(reg) => {
390                self.emit_transfer_value_to_register(reg, value_source, node, comment);
391            }
392            Place::Memory(_) => {
393                self.emit_store_value_to_memory_place(output_place, value_source, node, comment);
394            }
395            Place::Discard => {
396                panic!("Cannot copy to Unit destination")
397            }
398        }
399    }
400
401    pub(crate) fn emit_copy_value_from_memory_location(
402        &mut self,
403        destination: &Place,
404        source_memory_location: &MemoryLocation,
405        node: &Node,
406        comment: &str,
407    ) {
408        if let Some(_mem_loc) = destination.memory_location() {
409            let source_loc = Place::Memory(source_memory_location.clone());
410            self.emit_store_value_to_memory_place(destination, &source_loc, node, comment);
411        } else if let Some(output_target_reg) = destination.register() {
412            self.emit_load_value_from_memory_source(
413                output_target_reg,
414                source_memory_location,
415                node,
416                comment,
417            );
418        } else {
419            panic!("it was unit, not supported");
420        }
421    }
422
423    /// Stores a **value** from a `value_source` (either a register or memory) to a
424    /// target memory location specified by `output_place`.
425    ///
426    /// This function handles storing both **scalar** and **aggregate** types. For scalars,
427    /// it delegates to `emit_store_scalar_to_memory_offset_instruction`. For larger
428    /// aggregate types (like structs, tagged unions or arrays), it performs a block copy.
429    /// If the `value_source` is also memory, it first loads the value into a temporary
430    /// register using `emit_load_value_from_memory_source` before storing.
431    pub(crate) fn emit_store_value_to_memory_place(
432        &mut self,
433        output_place: &Place,
434        value_source: &Place,
435        node: &Node,
436        comment: &str,
437    ) {
438        let output_mem_loc = output_place.grab_memory_location(); // Assuming this is always a MemoryLocation
439
440        match value_source {
441            Place::Register(value_reg) => {
442                if value_reg.ty.is_reg_copy() {
443                    self.emit_store_scalar_to_memory_offset_instruction(
444                        output_mem_loc,
445                        value_reg,
446                        node,
447                        &format!("store {comment} to memory pointed by register {output_place} <- {value_reg}"),
448                    );
449                } else {
450                    let source_memory_location =
451                        MemoryLocation::new_copy_over_whole_type_with_zero_offset(
452                            value_reg.clone(),
453                        );
454                    self.emit_copy_aggregate_value_helper(
455                        output_place.grab_memory_location(),
456                        &source_memory_location,
457                        node,
458                        "copy aggregate",
459                    );
460                }
461            }
462            Place::Memory(source_mem_loc) => {
463                let temp_reg = self
464                    .temp_registers
465                    .allocate(source_mem_loc.ty.clone(), "temp_for_memory_to_memory_store");
466
467                self.emit_load_value_from_memory_source(
468                    temp_reg.register(),
469                    source_mem_loc,
470                    node,
471                    &format!("load {comment} from memory for store"),
472                );
473
474                if source_mem_loc.ty.is_reg_copy() {
475                    self.emit_store_scalar_to_memory_offset_instruction(
476                        output_mem_loc,
477                        temp_reg.register(),
478                        node,
479                        &format!("store {comment} from temp to memory pointed by register"),
480                    );
481                } else {
482                    self.emit_copy_aggregate_value_helper(
483                        output_place.grab_memory_location(),
484                        source_mem_loc,
485                        node,
486                        "copy aggregate",
487                    );
488                }
489            }
490            Place::Discard => panic!("Cannot store from Unit source"),
491        }
492    }
493}