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::code_bld::CodeBuilder;
6use crate::DetailedLocationResolved;
7use source_map_node::Node;
8use swamp_vm_types::types::{u16_type, BasicTypeKind, Destination, TypedRegister, VmType};
9use swamp_vm_types::{MemoryLocation, COLLECTION_CAPACITY_OFFSET};
10
11impl CodeBuilder<'_> {
12    // Load -------------------------------------------------------
13
14    /// Transfers a **value** from a given `Destination` (either a register or a memory location)
15    /// into a specified `target_reg`.
16    ///
17    /// This function acts as a primary entry point for ensuring a value resides in a register.
18    /// If the `source` is a `Register`, it generates a register-to-register copy if the
19    /// source and target are not already the same. If the `source` is `Memory`, it delegates
20    /// the load operation to `emit_load_value_from_memory_source`.
21    pub(crate) fn emit_transfer_value_to_register(
22        &mut self,
23        target_reg: &TypedRegister,
24        source: &Destination,
25        node: &Node,
26        comment: &str,
27    ) {
28        match source {
29            Destination::Register(source_reg) => {
30                if target_reg.index != source_reg.index {
31                    self.emit_copy_register(target_reg, source_reg, node, comment);
32                }
33            }
34            Destination::Memory(memory_location) => {
35                self.emit_load_value_from_memory_source(target_reg, memory_location, node, comment);
36            }
37            Destination::Unit => panic!("Cannot load from Unit destination"),
38        }
39    }
40
41    /// Loads a **value** from a `MemoryLocation` into a `target_reg`.
42    ///
43    /// This function serves as a helper for `emit_transfer_value_to_register` when the
44    /// source is memory. It distinguishes between loading scalar values and
45    /// loading pointer-like values (e.g., references) that are themselves stored within
46    /// registers. For scalar loads, it utilizes `emit_load_scalar_from_memory_offset_instruction`.
47    pub(crate) fn emit_load_value_from_memory_source(
48        &mut self,
49        target_reg: &TypedRegister,
50        source_memory_location: &MemoryLocation,
51        node: &Node,
52        comment: &str,
53    ) {
54        let source_type = source_memory_location.vm_type();
55        if source_type.is_aggregate() {
56            // We want the pointer since it is an aggregate. So either just copy the register
57            // or flatten the pointer
58            let source_loc = Destination::Memory(source_memory_location.clone());
59
60            self.emit_compute_effective_address_to_target_register(
61                target_reg,
62                &source_loc,
63                node,
64                "copy aggregate pointer to target register",
65            );
66        } else {
67            self.emit_load_scalar_from_memory_offset_instruction(
68                target_reg,
69                source_memory_location,
70                node,
71                &format!("emit primitive value. ptr to primitive reg {comment}"),
72            );
73        }
74    }
75
76    /// Loads a scalar value or computes the absolute pointer for an aggregate type.
77    ///
78    /// This function handles the two main cases for intrinsic function arguments:
79    ///
80    /// - **Scalar values**: Ensures the actual primitive value is loaded into a register
81    /// - **Aggregate types**: Computes and returns the absolute memory address pointer
82    ///
83    /// This is specifically designed for intrinsic calls where:
84    /// - Scalar intrinsics need the actual value (not a pointer to the value)
85    /// - Aggregate intrinsics need a flattened absolute pointer for operations
86    ///
87    /// # Arguments
88    /// * `destination` - The source location (register, memory, or unit)
89    /// * `node` - Source location for debugging
90    /// * `comment` - Description for generated assembly comments
91    ///
92    /// # Returns
93    /// * `Some(TypedRegister)` - Register containing the scalar value or aggregate pointer
94    /// * `None` - If the destination is Unit
95    pub(crate) fn emit_load_scalar_or_absolute_aggregate_pointer(
96        &mut self,
97        destination: &Destination,
98        node: &Node,
99        comment: &str,
100    ) -> Option<TypedRegister> {
101        if matches!(destination, Destination::Unit) {
102            None
103        } else {
104            let vm_type = destination.vm_type().unwrap();
105
106            if vm_type.is_scalar() {
107                // Scalar case
108                match destination {
109                    Destination::Register(reg) => {
110                        // a) if it is a register, we are done: use that register
111                        Some(reg.clone())
112                    }
113                    Destination::Memory(memory_location) => {
114                        // b) if it is a memory location: load_scalar_from_memory_location
115                        let scalar_temp = self.temp_registers.allocate(
116                            memory_location.ty.clone(),
117                            &format!("load scalar from memory for intrinsic: {comment}"),
118                        );
119
120                        self.emit_load_scalar_from_memory_offset_instruction(
121                            scalar_temp.register(),
122                            memory_location,
123                            node,
124                            &format!("load scalar value from memory for intrinsic: {comment}"),
125                        );
126
127                        Some(scalar_temp.register)
128                    }
129                    Destination::Unit => unreachable!(), // Already handled above
130                }
131            } else {
132                // Aggregate case: we only need to flatten it if needed
133                Some(self.emit_compute_effective_address_to_register(
134                    destination,
135                    node,
136                    &format!("flatten aggregate for intrinsic: {comment}"),
137                ))
138            }
139        }
140    }
141
142    /// Loads a scalar value or calculates the effective address for an aggregate type.
143    ///
144    /// It is basically a "bind" of a register to a location, and is used (exclusively?)
145    /// in "pattern matching", like `match` arms.
146    ///
147    /// For scalar types: Loads the actual value into the target register
148    /// For aggregate types: Calculates and stores the effective address in the target register
149    pub(crate) fn emit_load_or_calculate_address_from_memory(
150        &mut self,
151        target_reg: &TypedRegister,
152        source_memory_location: &MemoryLocation,
153        node: &Node,
154        comment: &str,
155    ) {
156        let source_type = source_memory_location.vm_type();
157        if source_type.is_aggregate() {
158            self.emit_compute_effective_address_to_target_register(
159                target_reg,
160                &Destination::Memory(source_memory_location.clone()),
161                node,
162                comment,
163            );
164        } else {
165            // For scalars, load the actual value
166            self.emit_load_scalar_from_memory_offset_instruction(
167                target_reg,
168                source_memory_location,
169                node,
170                &format!("load scalar value {comment}"),
171            );
172        }
173    }
174
175    /// Ensures that a value from a given `Destination` is available in a register.
176    ///
177    /// In compiler design, "materialization" refers to the process of bringing a value from an
178    /// abstract or indirect representation (like a variable name, or a value stored in memory)
179    /// into a concrete, directly usable form, which is typically a CPU register.
180    ///
181    /// If the `location` is already a `Register`, it's returned directly. If the `location`
182    /// is `Memory`, a temporary register is allocated, and the primitive value is loaded
183    /// into it using `emit_load_value_from_memory_source`, ensuring it's ready for
184    /// register-based operations.
185    pub(crate) fn emit_materialize_value_to_register(
186        &mut self,
187        location: &Destination,
188        node: &Node,
189        comment: &str,
190    ) -> DetailedLocationResolved {
191        match location {
192            Destination::Register(reg) => DetailedLocationResolved::Register(reg.clone()),
193            Destination::Memory(memory_location) => {
194                let temp_reg_target = self.temp_registers.allocate(
195                    memory_location.ty.clone(),
196                    "emit load primitive from location",
197                );
198                self.emit_load_value_from_memory_source(
199                    temp_reg_target.register(),
200                    memory_location,
201                    node,
202                    &format!("load primitive from detailed location {comment}"),
203                );
204                DetailedLocationResolved::TempRegister(temp_reg_target)
205            }
206            Destination::Unit => {
207                panic!("")
208            }
209        }
210    }
211
212    // Store -------------------------------------------------------
213
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        // Special case: StringView to StringStorage should perform vec-like copy
331        if matches!(
332            source_memory_location.ty.basic_type.kind,
333            BasicTypeKind::StringView { byte: _, char: _ }
334        ) && matches!(
335            destination_memory_location.ty.basic_type.kind,
336            BasicTypeKind::StringStorage {
337                element_type: _,
338                char: _,
339                capacity: _
340            }
341        ) {
342            // Perform vec-like copy for StringView to StringStorage
343            self.emit_copy_vec_like_value_helper(
344                destination_memory_location,
345                source_memory_location,
346                node,
347                &format!("copy StringView to StringStorage {comment}"),
348            );
349            return;
350        }
351
352        let ty = &source_memory_location.ty;
353        if ty.is_collection_like() {
354            if ty.basic_type.is_vec_like() {
355                if let (Some(_element_size), Some(_header_size)) = (
356                    source_memory_location
357                        .ty
358                        .basic_type
359                        .bucket_size_for_vec_like(),
360                    source_memory_location
361                        .ty
362                        .basic_type
363                        .header_size_for_vec_like(),
364                ) {
365                    self.emit_copy_vec_like_value_helper(
366                        destination_memory_location,
367                        source_memory_location,
368                        node,
369                        comment,
370                    );
371                } else {
372                    // Fallback to block copy for vec-like types that don't have proper size methods
373                    self.emit_block_copy_with_size_from_location(
374                        destination_memory_location,
375                        source_memory_location,
376                        node,
377                        &format!("block copy {comment} (vec-like fallback) to memory pointed by register {destination_memory_location} <- {source_memory_location}"),
378                    );
379                }
380            } else {
381                self.emit_compute_effective_address_from_location_to_register(
382                    destination_memory_location,
383                    node,
384                    comment,
385                );
386                self.emit_copy_map_like_value_helper(
387                    destination_memory_location,
388                    source_memory_location,
389                    node,
390                    comment,
391                );
392            }
393        } else {
394            self.emit_block_copy_with_size_from_location(
395                destination_memory_location,
396                source_memory_location,
397                node,
398                &format!("block copy {comment} to memory pointed by register {destination_memory_location} <- {source_memory_location}"),
399            );
400        }
401    }
402
403    pub fn emit_copy_value_between_destinations(
404        &mut self,
405        output_destination: &Destination,
406        value_source: &Destination,
407        node: &Node,
408        comment: &str,
409    ) {
410        match output_destination {
411            Destination::Register(reg) => {
412                self.emit_transfer_value_to_register(reg, value_source, node, comment);
413            }
414            Destination::Memory(_) => {
415                self.emit_store_value_to_memory_destination(
416                    output_destination,
417                    value_source,
418                    node,
419                    comment,
420                );
421            }
422            Destination::Unit => {
423                panic!("Cannot copy to Unit destination")
424            }
425        }
426    }
427
428    pub(crate) fn emit_copy_value_from_memory_location(
429        &mut self,
430        destination: &Destination,
431        source_memory_location: &MemoryLocation,
432        node: &Node,
433        comment: &str,
434    ) {
435        if let Some(_mem_loc) = destination.memory_location() {
436            let source_loc = Destination::Memory(source_memory_location.clone());
437            self.emit_store_value_to_memory_destination(destination, &source_loc, node, comment);
438        } else if let Some(output_target_reg) = destination.register() {
439            self.emit_load_value_from_memory_source(
440                output_target_reg,
441                source_memory_location,
442                node,
443                comment,
444            );
445        } else {
446            panic!("it was unit, not supported");
447        }
448    }
449
450    /// Stores a **value** from a `value_source` (either a register or memory) to a
451    /// target memory location specified by `output_destination`.
452    ///
453    /// This function handles storing both **scalar** and **aggregate** types. For scalars,
454    /// it delegates to `emit_store_scalar_to_memory_offset_instruction`. For larger
455    /// aggregate types (like structs, tagged unions or arrays), it performs a block copy.
456    /// If the `value_source` is also memory, it first loads the value into a temporary
457    /// register using `emit_load_value_from_memory_source` before storing.
458    pub(crate) fn emit_store_value_to_memory_destination(
459        &mut self,
460        output_destination: &Destination,
461        value_source: &Destination,
462        node: &Node,
463        comment: &str,
464    ) {
465        let output_mem_loc = output_destination.grab_memory_location(); // Assuming this is always a MemoryLocation
466
467        match value_source {
468            Destination::Register(value_reg) => {
469                // Special case: StringView to StringStorage should perform vec-like copy
470                if matches!(
471                    value_reg.ty.basic_type.kind,
472                    BasicTypeKind::StringView { byte: _, char: _ }
473                ) && matches!(
474                    output_mem_loc.ty.basic_type.kind,
475                    BasicTypeKind::StringStorage {
476                        element_type: _,
477                        char: _,
478                        capacity: _
479                    }
480                ) {
481                    // Create a memory location for the StringView source
482                    let source_memory_location =
483                        MemoryLocation::new_copy_over_whole_type_with_zero_offset(
484                            value_reg.clone(),
485                        );
486                    // Perform vec-like copy instead of scalar copy
487                    self.emit_copy_aggregate_value_helper(
488                        output_destination.grab_memory_location(),
489                        &source_memory_location,
490                        node,
491                        &format!("copy StringView to StringStorage {comment}"),
492                    );
493                } else if value_reg.ty.is_scalar() {
494                    self.emit_store_scalar_to_memory_offset_instruction(
495                        output_mem_loc,
496                        value_reg,
497                        node,
498                        &format!("store {comment} to memory pointed by register {output_destination} <- {value_reg}"),
499                    );
500                } else {
501                    let source_memory_location =
502                        MemoryLocation::new_copy_over_whole_type_with_zero_offset(
503                            value_reg.clone(),
504                        );
505                    self.emit_copy_aggregate_value_helper(
506                        output_destination.grab_memory_location(),
507                        &source_memory_location,
508                        node,
509                        "copy aggregate",
510                    );
511                }
512            }
513            Destination::Memory(source_mem_loc) => {
514                let temp_reg = self
515                    .temp_registers
516                    .allocate(source_mem_loc.ty.clone(), "temp_for_memory_to_memory_store");
517
518                self.emit_load_value_from_memory_source(
519                    temp_reg.register(),
520                    source_mem_loc,
521                    node,
522                    &format!("load {comment} from memory for store"),
523                );
524
525                if source_mem_loc.ty.is_scalar() {
526                    self.emit_store_scalar_to_memory_offset_instruction(
527                        output_mem_loc,
528                        temp_reg.register(),
529                        node,
530                        &format!("store {comment} from temp to memory pointed by register"),
531                    );
532                } else {
533                    self.emit_copy_aggregate_value_helper(
534                        output_destination.grab_memory_location(),
535                        source_mem_loc,
536                        node,
537                        "copy aggregate",
538                    );
539                }
540            }
541            Destination::Unit => panic!("Cannot store from Unit source"),
542        }
543    }
544}