1use 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 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; 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 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 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 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 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 let spill_scope = self.spill_required_registers(node, "spill before emit arguments");
213
214 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 self.setup_return_pointer_reg(output_destination, return_basic_type, node);
221 } else {
222 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 let mut temp_to_abi_copies = Vec::new();
239 let mut argument_registers = RegisterPool::new(1, 6); 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 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 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 ¶meter_basic_type,
299 &mut copy_back_operations,
300 node,
301 ctx,
302 );
303 }
304 }
305
306 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 ©_argument.canonical_target,
311 ©_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 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 ©_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 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 self.emit_restore_region(
363 spilled_arguments.argument_and_temp_scope.argument_registers,
364 &HashSet::new(),
365 node,
366 comment,
367 );
368
369 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 ©_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>, 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 let filtered_regs: Vec<_> = sorted_regs
397 .into_iter()
398 .filter(|reg| !output_destination_registers.contains(®.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(®_idx)
454 {
455 mask_to_actually_restore &= !(1 << i); }
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 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 }
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"); 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 const fn argument_needs_to_be_in_a_temporary_register_first(
577 &self,
578 reg: &TypedRegister,
579 ) -> bool {
580 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}