swamp_code_gen/
func.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::ctx::Context;
7use crate::layout::layout_variables;
8use crate::reg_pool::HwmTempRegisterPool;
9use crate::state::GenOptions;
10use crate::top_state::TopLevelGenState;
11use crate::{
12    FunctionInData, FunctionIp, FunctionIpKind, GenFunctionInfo, MAX_REGISTER_INDEX_FOR_PARAMETERS,
13    RepresentationOfRegisters, SpilledRegisterRegion,
14};
15use source_map_cache::SourceMapWrapper;
16use source_map_node::Node;
17use std::collections::HashSet;
18use swamp_semantic::{InternalFunctionDefinitionRef, InternalMainExpression, formal_function_name};
19use swamp_vm_debug_info::FunctionDebugInfo;
20use swamp_vm_instr_build::InstructionBuilder;
21use swamp_vm_types::types::{
22    Destination, FunctionInfo, FunctionInfoKind, TypedRegister, VariableRegister, VmType,
23    VmTypeOrigin,
24};
25use swamp_vm_types::{
26    InstructionPosition, InstructionPositionOffset, InstructionRange, MemoryLocation, MemoryOffset,
27    PatchPosition,
28};
29
30impl TopLevelGenState {
31    /// # Panics
32    ///
33    pub fn emit_function_def(
34        &mut self,
35        internal_fn_def: &InternalFunctionDefinitionRef,
36        source_map_wrapper: &SourceMapWrapper,
37        should_ignore_host_call: bool,
38    ) {
39        assert_ne!(internal_fn_def.program_unique_id, 0);
40
41        let complete_function_name = formal_function_name(internal_fn_def);
42        //info!(complete_function_name, "code generating function def");
43
44        let in_data = FunctionInData {
45            function_name_node: internal_fn_def.name.0.clone(),
46            kind: FunctionInfoKind::Normal(internal_fn_def.program_unique_id as usize),
47            assigned_name: complete_function_name,
48            function_variables: internal_fn_def.function_variables.clone(),
49            return_type: internal_fn_def.signature.return_type.clone(),
50            expression: internal_fn_def.body.clone(),
51        };
52
53        let attrs = &internal_fn_def.attributes;
54
55        let should_insert_halt =
56            Self::is_test_call(attrs) || (!should_ignore_host_call && Self::is_host_call(attrs));
57
58        let (start_ip, end_ip, function_info) =
59            self.emit_function_preamble(&in_data, source_map_wrapper, should_insert_halt);
60
61        let count_ip = end_ip.0 - start_ip.0;
62
63        let range = InstructionRange {
64            start: start_ip,
65            count: InstructionPositionOffset(count_ip),
66        };
67
68        self.codegen_state
69            .function_infos
70            .insert(
71                internal_fn_def.program_unique_id,
72                GenFunctionInfo {
73                    ip_range: range.clone(),
74                    return_type: self.codegen_state.layout_cache.layout(&in_data.return_type),
75                    internal_function_definition: internal_fn_def.clone(),
76                },
77            )
78            .unwrap();
79
80        self.codegen_state.function_ips.ranges.push(FunctionIp {
81            ip_range: range.clone(),
82            kind: FunctionIpKind::Normal(internal_fn_def.program_unique_id),
83        });
84
85        self.codegen_state
86            .debug_info
87            .function_table
88            .entries
89            .push(FunctionDebugInfo {
90                start_pc: range.start.0,
91                function_id: internal_fn_def.program_unique_id,
92            });
93
94        self.codegen_state
95            .debug_info
96            .function_infos
97            .insert(internal_fn_def.program_unique_id, function_info)
98            .unwrap();
99    }
100
101    /// # Errors
102    ///
103    pub fn emit_main_function(
104        &mut self,
105        main: &InternalMainExpression,
106        options: &GenOptions,
107        source_map_lookup: &SourceMapWrapper,
108    ) {
109        let variable_and_frame_memory = layout_variables(
110            &mut self.codegen_state.layout_cache,
111            &main.expression.node,
112            &main.scopes,
113            &main.expression.ty,
114        );
115
116        let in_data = FunctionInData {
117            function_name_node: main.expression.node.clone(),
118            kind: FunctionInfoKind::Normal(main.program_unique_id as usize),
119            assigned_name: "main_expr".to_string(),
120            function_variables: main.scopes.clone(),
121            return_type: main.expression.ty.clone(),
122            expression: main.expression.clone(),
123        };
124
125        let (start_ip, end_ip, function_info) =
126            self.emit_function_preamble(&in_data, source_map_lookup, true);
127
128        let function_info = FunctionInfo {
129            kind: FunctionInfoKind::Normal(main.program_unique_id as usize),
130            frame_memory: variable_and_frame_memory.frame_memory,
131            return_type: variable_and_frame_memory.return_type,
132            name: "main".to_string(),
133            ip_range: InstructionRange {
134                start: start_ip,
135                count: InstructionPositionOffset(end_ip.0 - start_ip.0),
136            },
137        };
138    }
139
140    #[must_use]
141    pub const fn is_callee_save(reg_index: u8) -> bool {
142        reg_index > MAX_REGISTER_INDEX_FOR_PARAMETERS
143    }
144
145    pub fn spill_callee_save_registers(
146        code_builder: &mut CodeBuilder,
147        function_info: &FunctionInfo,
148        node: &Node,
149    ) -> Option<SpilledRegisterRegion> {
150        // Collect the actual register indices that need to be saved
151        let mut registers_to_save: Vec<u8> = Vec::new();
152
153        for variable_register in &function_info.frame_memory.variable_registers {
154            if Self::is_callee_save(variable_register.register.index)
155                && !variable_register.register.ty.is_mutable_primitive()
156            {
157                registers_to_save.push(variable_register.register.index);
158            }
159        }
160
161        if registers_to_save.is_empty() {
162            return None;
163        }
164
165        // Remove duplicates and sort
166        registers_to_save.sort_unstable();
167        registers_to_save.dedup();
168
169        let count = registers_to_save.len() as u8;
170
171        let abi_parameter_frame_memory_region = code_builder.temp_frame_space_for_register(
172            count,
173            "temporary space for callee_save (and not mutable primitives)",
174        );
175
176        // Always use contiguous range approach since callee-save registers are typically >= r6
177        // and the mask approach only works for registers 0-7
178        let start_reg = registers_to_save[0];
179
180        code_builder.builder.add_st_contiguous_regs_to_frame(
181            abi_parameter_frame_memory_region,
182            start_reg,
183            count,
184            node,
185            "prologue, spill contiguous range of callee-save registers to stack frame memory",
186        );
187
188        Some(SpilledRegisterRegion {
189            registers: RepresentationOfRegisters::Range { start_reg, count },
190            frame_memory_region: abi_parameter_frame_memory_region,
191        })
192    }
193    pub fn initialize_and_clear_variables_that_are_on_the_frame(
194        instruction_builder: &mut InstructionBuilder,
195        variable_registers: &[VariableRegister],
196        node: &Node,
197    ) {
198        for variable_reg in variable_registers {
199            if let VmTypeOrigin::Frame(frame_region) = variable_reg.register.ty.origin {
200                instruction_builder.add_lea_from_frame_region(
201                    &variable_reg.register,
202                    frame_region,
203                    node,
204                    &format!("define frame placed register {variable_reg}"),
205                );
206
207                instruction_builder.add_frame_memory_clear(
208                    frame_region,
209                    node,
210                    &format!("clear memory for indirect variable {variable_reg}"),
211                );
212            }
213        }
214    }
215
216    pub fn function_prologue(
217        code_builder: &mut CodeBuilder,
218        function_info: &FunctionInfo,
219        node: &Node,
220    ) -> (Option<SpilledRegisterRegion>, PatchPosition) {
221        let enter_patch_position = code_builder
222            .builder
223            .add_enter_placeholder(&Node::default(), "prologue");
224
225        let maybe_spilled = Self::spill_callee_save_registers(code_builder, function_info, node);
226
227        // Note: Variable initialization (LEA instructions) are now generated
228        // at the point of variable definition, not in the function prologue
229
230        (maybe_spilled, enter_patch_position)
231    }
232
233    fn function_epilogue(
234        instruction_builder: &mut CodeBuilder,
235        maybe_spilled_registers: Option<SpilledRegisterRegion>,
236        node: &Node,
237        comment: &str,
238    ) {
239        if let Some(spilled_register_region) = maybe_spilled_registers {
240            instruction_builder.emit_restore_region(
241                spilled_register_region,
242                &HashSet::new(),
243                node,
244                &format!("function epilogue: {comment}"),
245            );
246        }
247    }
248
249    pub fn emit_function_preamble(
250        &mut self,
251        in_data: &FunctionInData,
252        source_map_wrapper: &SourceMapWrapper,
253        is_called_by_host: bool,
254    ) -> (InstructionPosition, InstructionPosition, FunctionInfo) {
255        let start_ip = self.ip();
256
257        //info!(in_data.assigned_name, "emit_function");
258
259        let frame_and_variable_info = layout_variables(
260            &mut self.codegen_state.layout_cache,
261            &in_data.function_name_node,
262            &in_data.function_variables,
263            &in_data.return_type,
264        );
265
266        // Get the return type layout before borrowing codegen_state mutably
267        let return_basic_type = self.codegen_state.layout_cache.layout(&in_data.return_type);
268
269        let mut function_info = FunctionInfo {
270            kind: in_data.kind.clone(),
271            frame_memory: frame_and_variable_info.frame_memory,
272            return_type: frame_and_variable_info.return_type,
273            name: in_data.assigned_name.clone(),
274            ip_range: InstructionRange {
275                start: start_ip,
276                count: InstructionPositionOffset(0),
277            },
278        };
279
280        // debug!(name=?in_data.assigned_name, "code generating function");
281
282        let _complete_function_info = self.codegen_state.add_function(
283            function_info.clone(),
284            &in_data.function_name_node,
285            "function",
286        );
287
288        let mut instruction_builder = InstructionBuilder::new(&mut self.builder_state);
289
290        let temp_pool = HwmTempRegisterPool::new(128, 32);
291
292        let ctx = Context::new();
293
294        let mut function_code_builder = CodeBuilder::new(
295            &mut self.codegen_state,
296            &mut instruction_builder,
297            frame_and_variable_info.parameter_and_variable_offsets,
298            //frame_and_variable_info.frame_registers,
299            temp_pool,
300            frame_and_variable_info.local_frame_allocator,
301            self.code_builder_options,
302            source_map_wrapper,
303        );
304
305        let (maybe_spilled_registers, enter_patch_position) = Self::function_prologue(
306            &mut function_code_builder,
307            &function_info,
308            &in_data.function_name_node,
309        );
310        let return_register =
311            TypedRegister::new_vm_type(0, VmType::new_unknown_placement(return_basic_type));
312
313        let destination = if return_register.ty.basic_type.is_scalar() {
314            Destination::Register(return_register)
315        } else {
316            let memory_location = MemoryLocation {
317                ty: VmType::new_unknown_placement(return_register.ty().clone()),
318                base_ptr_reg: return_register,
319                offset: MemoryOffset(0),
320            };
321            if let FunctionInfoKind::Constant(found_constant) = &in_data.kind {
322                function_code_builder.emit_initialize_memory_for_any_type(
323                    &memory_location,
324                    &in_data.expression.node,
325                    "prepare r0 memory for constant",
326                );
327            }
328
329            Destination::Memory(memory_location)
330        };
331
332        function_code_builder.emit_expression(&destination, &in_data.expression, &ctx);
333
334        function_code_builder.patch_enter(enter_patch_position);
335
336        Self::function_epilogue(
337            &mut function_code_builder,
338            maybe_spilled_registers,
339            &in_data.expression.node,
340            "epilogue",
341        );
342
343        self.finalize_function(&GenOptions {
344            is_halt_function: is_called_by_host,
345        });
346
347        let end_ip = self.ip();
348
349        function_info.ip_range.count = InstructionPositionOffset(end_ip.0 - start_ip.0);
350
351        (start_ip, end_ip, function_info)
352    }
353
354    pub fn finalize_function(&mut self, options: &GenOptions) {
355        if options.is_halt_function {
356            self.builder_state.add_hlt(&Node::default(), "");
357        } else {
358            self.builder_state.add_ret(&Node::default(), "");
359        }
360    }
361}