ruka_codegen_wasm/memory_and_release/
clone_ops.rs

1use super::*;
2
3pub(crate) fn emit_clone_for_type(
4    body: &mut walrus::InstrSeqBuilder,
5    runtime: &RuntimeFunctions,
6    memory_id: MemoryId,
7    source_pos: Option<ruka_mir::MirSourcePos>,
8    fallback_line: i32,
9    source_local: LocalId,
10    ty: &Ty,
11    structs: &[ruka_mir::MirStructDecl],
12    enums: &[ruka_mir::MirEnumDecl],
13    scratch_i32_local: LocalId,
14    scratch_i32_local_b: LocalId,
15    scratch_i32_local_c: LocalId,
16    scratch_i32_local_d: LocalId,
17    scratch_i32_local_e: LocalId,
18    scratch_i64_local: LocalId,
19    dst_local: LocalId,
20) -> Result<(), LowerError> {
21    let plan = build_release_plan(ty, structs, enums)?;
22    emit_clone_plan_value(
23        body,
24        runtime,
25        memory_id,
26        source_pos,
27        fallback_line,
28        source_local,
29        &plan,
30        scratch_i32_local,
31        scratch_i32_local_b,
32        scratch_i32_local_c,
33        scratch_i32_local_d,
34        scratch_i32_local_e,
35        scratch_i64_local,
36        dst_local,
37    )
38}
39
40pub(crate) fn emit_clone_plan_value(
41    body: &mut walrus::InstrSeqBuilder,
42    runtime: &RuntimeFunctions,
43    memory_id: MemoryId,
44    source_pos: Option<ruka_mir::MirSourcePos>,
45    fallback_line: i32,
46    source_local: LocalId,
47    plan: &ReleasePlan,
48    scratch_i32_local: LocalId,
49    scratch_i32_local_b: LocalId,
50    scratch_i32_local_c: LocalId,
51    scratch_i32_local_d: LocalId,
52    scratch_i32_local_e: LocalId,
53    scratch_i64_local: LocalId,
54    dst_local: LocalId,
55) -> Result<(), LowerError> {
56    match plan {
57        ReleasePlan::None => {
58            body.local_get(source_local).local_set(dst_local);
59            Ok(())
60        }
61        ReleasePlan::Pointer { pointee, .. } => emit_clone_pointer(
62            body,
63            runtime,
64            memory_id,
65            source_pos,
66            fallback_line,
67            source_local,
68            pointee.as_ref(),
69            scratch_i32_local,
70            scratch_i32_local_b,
71            scratch_i32_local_c,
72            scratch_i32_local_d,
73            scratch_i32_local_e,
74            scratch_i64_local,
75            dst_local,
76        ),
77        ReleasePlan::String => emit_clone_string(
78            body,
79            runtime,
80            memory_id,
81            source_pos,
82            fallback_line,
83            source_local,
84            scratch_i32_local,
85            scratch_i32_local_b,
86            scratch_i32_local_c,
87            dst_local,
88        ),
89        ReleasePlan::Array(item_plan) => emit_clone_array(
90            body,
91            runtime,
92            memory_id,
93            source_pos,
94            fallback_line,
95            source_local,
96            item_plan.as_ref(),
97            scratch_i32_local,
98            scratch_i32_local_b,
99            scratch_i32_local_c,
100            scratch_i32_local_d,
101            scratch_i32_local_e,
102            scratch_i64_local,
103            dst_local,
104        ),
105        ReleasePlan::Aggregate {
106            payload_bytes,
107            fields,
108        } => emit_clone_aggregate(
109            body,
110            runtime,
111            memory_id,
112            source_pos,
113            fallback_line,
114            source_local,
115            *payload_bytes,
116            fields,
117            scratch_i32_local,
118            scratch_i32_local_b,
119            scratch_i32_local_c,
120            scratch_i32_local_d,
121            scratch_i32_local_e,
122            scratch_i64_local,
123            dst_local,
124        ),
125        ReleasePlan::Enum {
126            payload_bytes,
127            variants,
128        } => emit_clone_enum(
129            body,
130            runtime,
131            memory_id,
132            source_pos,
133            fallback_line,
134            source_local,
135            *payload_bytes,
136            variants,
137            scratch_i32_local,
138            scratch_i32_local_b,
139            scratch_i32_local_c,
140            scratch_i32_local_d,
141            scratch_i32_local_e,
142            scratch_i64_local,
143            dst_local,
144        ),
145    }
146}
147
148fn pick_clone_temp_local(
149    first: LocalId,
150    second: LocalId,
151    third: LocalId,
152    avoid_a: LocalId,
153    avoid_b: LocalId,
154    avoid_c: LocalId,
155) -> LocalId {
156    if first != avoid_a && first != avoid_b && first != avoid_c {
157        return first;
158    }
159    if second != avoid_a && second != avoid_b && second != avoid_c {
160        return second;
161    }
162    if third != avoid_a && third != avoid_b && third != avoid_c {
163        return third;
164    }
165    first
166}
167
168fn pick_clone_temp_local_wide(
169    first: LocalId,
170    second: LocalId,
171    third: LocalId,
172    fourth: LocalId,
173    fifth: LocalId,
174    avoid_a: LocalId,
175    avoid_b: LocalId,
176    avoid_c: LocalId,
177    avoid_d: LocalId,
178) -> LocalId {
179    for candidate in [first, second, third, fourth, fifth] {
180        if candidate != avoid_a
181            && candidate != avoid_b
182            && candidate != avoid_c
183            && candidate != avoid_d
184        {
185            return candidate;
186        }
187    }
188    first
189}
190
191fn emit_clone_pointer(
192    body: &mut walrus::InstrSeqBuilder,
193    runtime: &RuntimeFunctions,
194    memory_id: MemoryId,
195    source_pos: Option<ruka_mir::MirSourcePos>,
196    fallback_line: i32,
197    source_local: LocalId,
198    pointee_plan: &ReleasePlan,
199    scratch_i32_local: LocalId,
200    scratch_i32_local_b: LocalId,
201    scratch_i32_local_c: LocalId,
202    scratch_i32_local_d: LocalId,
203    scratch_i32_local_e: LocalId,
204    scratch_i64_local: LocalId,
205    dst_local: LocalId,
206) -> Result<(), LowerError> {
207    let alloc = runtime_tracked_alloc_function(runtime)?;
208    let (kind_id, file_id, line, column) =
209        alloc_site_parts(ALLOC_SITE_POINTER_CLONE, source_pos, fallback_line);
210    let nested_result_local = pick_clone_temp_local(
211        scratch_i32_local_d,
212        scratch_i32_local_c,
213        scratch_i32_local_e,
214        scratch_i32_local_b,
215        dst_local,
216        source_local,
217    );
218    let mut nested_error = None;
219    body.local_get(source_local)
220        .local_set(scratch_i32_local)
221        .local_get(scratch_i32_local)
222        .i32_const(0)
223        .binop(BinaryOp::I32Eq)
224        .if_else(
225            None,
226            |null_src| {
227                null_src.i32_const(0).local_set(dst_local);
228            },
229            |non_null| {
230                non_null
231                    .i32_const(POINTER_CELL_BYTES)
232                    .i32_const(kind_id)
233                    .i32_const(file_id)
234                    .i32_const(line)
235                    .i32_const(column)
236                    .call(alloc.function_id)
237                    .local_set(dst_local)
238                    .local_get(dst_local)
239                    .i32_const(1)
240                    .instr(Store {
241                        memory: memory_id,
242                        kind: StoreKind::I32 { atomic: false },
243                        arg: MemArg {
244                            align: 4,
245                            offset: ARRAY_HEADER_OFFSET,
246                        },
247                    })
248                    .local_get(scratch_i32_local)
249                    .instr(Load {
250                        memory: memory_id,
251                        kind: LoadKind::I64 { atomic: false },
252                        arg: MemArg {
253                            align: 8,
254                            offset: POINTER_VALUE_OFFSET,
255                        },
256                    })
257                    .local_set(scratch_i64_local);
258                if matches!(pointee_plan, ReleasePlan::None) {
259                    non_null
260                        .local_get(dst_local)
261                        .local_get(scratch_i64_local)
262                        .instr(Store {
263                            memory: memory_id,
264                            kind: StoreKind::I64 { atomic: false },
265                            arg: MemArg {
266                                align: 8,
267                                offset: POINTER_VALUE_OFFSET,
268                            },
269                        });
270                    return;
271                }
272                non_null
273                    .local_get(scratch_i64_local)
274                    .unop(UnaryOp::I32WrapI64)
275                    .local_set(scratch_i32_local_b);
276                if let Err(err) = emit_clone_plan_value(
277                    non_null,
278                    runtime,
279                    memory_id,
280                    source_pos,
281                    fallback_line,
282                    scratch_i32_local_b,
283                    pointee_plan,
284                    scratch_i32_local,
285                    scratch_i32_local_c,
286                    scratch_i32_local_d,
287                    scratch_i32_local_e,
288                    scratch_i32_local_b,
289                    scratch_i64_local,
290                    nested_result_local,
291                ) {
292                    nested_error = Some(err);
293                    return;
294                }
295                non_null
296                    .local_get(dst_local)
297                    .local_get(nested_result_local)
298                    .unop(UnaryOp::I64ExtendUI32)
299                    .instr(Store {
300                        memory: memory_id,
301                        kind: StoreKind::I64 { atomic: false },
302                        arg: MemArg {
303                            align: 8,
304                            offset: POINTER_VALUE_OFFSET,
305                        },
306                    });
307            },
308        );
309    if let Some(err) = nested_error {
310        return Err(err);
311    }
312    Ok(())
313}
314
315fn emit_clone_string(
316    body: &mut walrus::InstrSeqBuilder,
317    runtime: &RuntimeFunctions,
318    memory_id: MemoryId,
319    source_pos: Option<ruka_mir::MirSourcePos>,
320    fallback_line: i32,
321    source_local: LocalId,
322    scratch_i32_local: LocalId,
323    scratch_i32_local_b: LocalId,
324    scratch_i32_local_c: LocalId,
325    dst_local: LocalId,
326) -> Result<(), LowerError> {
327    let alloc = runtime_tracked_alloc_function(runtime)?;
328    let (kind_id, file_id, line, column) =
329        alloc_site_parts(ALLOC_SITE_AGGREGATE_NEW, source_pos, fallback_line);
330    body.local_get(source_local)
331        .local_set(scratch_i32_local)
332        .local_get(scratch_i32_local)
333        .i32_const(0)
334        .binop(BinaryOp::I32Eq)
335        .if_else(
336            None,
337            |null_src| {
338                null_src.i32_const(0).local_set(dst_local);
339            },
340            |non_null| {
341                non_null
342                    .local_get(scratch_i32_local)
343                    .instr(Load {
344                        memory: memory_id,
345                        kind: LoadKind::I32 { atomic: false },
346                        arg: MemArg {
347                            align: 4,
348                            offset: STRING_HEADER_OFFSET,
349                        },
350                    })
351                    .local_set(scratch_i32_local_b)
352                    .local_get(scratch_i32_local_b)
353                    .i32_const(0)
354                    .binop(BinaryOp::I32Eq)
355                    .if_else(
356                        None,
357                        |static_literal| {
358                            static_literal
359                                .local_get(scratch_i32_local)
360                                .local_set(dst_local);
361                        },
362                        |heap_string| {
363                            heap_string
364                                .local_get(scratch_i32_local)
365                                .instr(Load {
366                                    memory: memory_id,
367                                    kind: LoadKind::I32 { atomic: false },
368                                    arg: MemArg {
369                                        align: 4,
370                                        offset: STRING_LEN_OFFSET,
371                                    },
372                                })
373                                .local_set(scratch_i32_local_b)
374                                .local_get(scratch_i32_local_b)
375                                .i32_const(STRING_DATA_OFFSET as i32)
376                                .binop(BinaryOp::I32Add)
377                                .i32_const(kind_id)
378                                .i32_const(file_id)
379                                .i32_const(line)
380                                .i32_const(column)
381                                .call(alloc.function_id)
382                                .local_set(dst_local)
383                                .local_get(dst_local)
384                                .i32_const(1)
385                                .instr(Store {
386                                    memory: memory_id,
387                                    kind: StoreKind::I32 { atomic: false },
388                                    arg: MemArg {
389                                        align: 4,
390                                        offset: STRING_HEADER_OFFSET,
391                                    },
392                                })
393                                .local_get(dst_local)
394                                .local_get(scratch_i32_local_b)
395                                .instr(Store {
396                                    memory: memory_id,
397                                    kind: StoreKind::I32 { atomic: false },
398                                    arg: MemArg {
399                                        align: 4,
400                                        offset: STRING_LEN_OFFSET,
401                                    },
402                                });
403                            heap_string
404                                .i32_const(0)
405                                .local_set(scratch_i32_local_c)
406                                .block(None, |done| {
407                                    let done_id = done.id();
408                                    done.loop_(None, |loop_| {
409                                        let loop_id = loop_.id();
410                                        loop_
411                                            .local_get(scratch_i32_local_c)
412                                            .local_get(scratch_i32_local)
413                                            .instr(Load {
414                                                memory: memory_id,
415                                                kind: LoadKind::I32 { atomic: false },
416                                                arg: MemArg {
417                                                    align: 4,
418                                                    offset: STRING_LEN_OFFSET,
419                                                },
420                                            })
421                                            .binop(BinaryOp::I32GeU)
422                                            .br_if(done_id)
423                                            .local_get(scratch_i32_local)
424                                            .i32_const(STRING_DATA_OFFSET as i32)
425                                            .binop(BinaryOp::I32Add)
426                                            .local_get(scratch_i32_local_c)
427                                            .binop(BinaryOp::I32Add)
428                                            .instr(Load {
429                                                memory: memory_id,
430                                                kind: LoadKind::I32_8 {
431                                                    kind: walrus::ir::ExtendedLoad::ZeroExtend,
432                                                },
433                                                arg: MemArg {
434                                                    align: 1,
435                                                    offset: 0,
436                                                },
437                                            })
438                                            .local_set(scratch_i32_local_b)
439                                            .local_get(dst_local)
440                                            .i32_const(STRING_DATA_OFFSET as i32)
441                                            .binop(BinaryOp::I32Add)
442                                            .local_get(scratch_i32_local_c)
443                                            .binop(BinaryOp::I32Add)
444                                            .local_get(scratch_i32_local_b)
445                                            .instr(Store {
446                                                memory: memory_id,
447                                                kind: StoreKind::I32_8 { atomic: false },
448                                                arg: MemArg {
449                                                    align: 1,
450                                                    offset: 0,
451                                                },
452                                            })
453                                            .local_get(scratch_i32_local_c)
454                                            .i32_const(1)
455                                            .binop(BinaryOp::I32Add)
456                                            .local_set(scratch_i32_local_c)
457                                            .br(loop_id);
458                                    });
459                                });
460                        },
461                    );
462            },
463        );
464    Ok(())
465}
466
467fn emit_clone_array(
468    body: &mut walrus::InstrSeqBuilder,
469    runtime: &RuntimeFunctions,
470    memory_id: MemoryId,
471    source_pos: Option<ruka_mir::MirSourcePos>,
472    fallback_line: i32,
473    source_local: LocalId,
474    item_plan: &ReleasePlan,
475    scratch_i32_local: LocalId,
476    scratch_i32_local_b: LocalId,
477    scratch_i32_local_c: LocalId,
478    scratch_i32_local_d: LocalId,
479    scratch_i32_local_e: LocalId,
480    scratch_i64_local: LocalId,
481    dst_local: LocalId,
482) -> Result<(), LowerError> {
483    let alloc = runtime_tracked_alloc_function(runtime)?;
484    let (kind_id, file_id, line, column) =
485        alloc_site_parts(ALLOC_SITE_ARRAY_NEW, source_pos, fallback_line);
486    let mut nested_error = None;
487    let nested_result_local = pick_clone_temp_local(
488        scratch_i32_local_d,
489        scratch_i32_local_c,
490        scratch_i32_local_e,
491        scratch_i32_local_d,
492        dst_local,
493        source_local,
494    );
495    body.local_get(source_local)
496        .local_set(scratch_i32_local)
497        .local_get(scratch_i32_local)
498        .i32_const(0)
499        .binop(BinaryOp::I32Eq)
500        .if_else(
501            None,
502            |null_src| {
503                null_src.i32_const(0).local_set(dst_local);
504            },
505            |non_null| {
506                non_null
507                    .local_get(scratch_i32_local)
508                    .instr(Load {
509                        memory: memory_id,
510                        kind: LoadKind::I32 { atomic: false },
511                        arg: MemArg {
512                            align: 4,
513                            offset: ARRAY_CAP_OFFSET,
514                        },
515                    })
516                    .i32_const(ARRAY_SLOT_BYTES)
517                    .binop(BinaryOp::I32Mul)
518                    .i32_const(ARRAY_DATA_OFFSET as i32)
519                    .binop(BinaryOp::I32Add)
520                    .i32_const(kind_id)
521                    .i32_const(file_id)
522                    .i32_const(line)
523                    .i32_const(column)
524                    .call(alloc.function_id)
525                    .local_set(dst_local)
526                    .local_get(dst_local)
527                    .i32_const(1)
528                    .instr(Store {
529                        memory: memory_id,
530                        kind: StoreKind::I32 { atomic: false },
531                        arg: MemArg {
532                            align: 4,
533                            offset: ARRAY_HEADER_OFFSET,
534                        },
535                    })
536                    .local_get(dst_local)
537                    .local_get(scratch_i32_local)
538                    .instr(Load {
539                        memory: memory_id,
540                        kind: LoadKind::I32 { atomic: false },
541                        arg: MemArg {
542                            align: 4,
543                            offset: ARRAY_LEN_OFFSET,
544                        },
545                    })
546                    .instr(Store {
547                        memory: memory_id,
548                        kind: StoreKind::I32 { atomic: false },
549                        arg: MemArg {
550                            align: 4,
551                            offset: ARRAY_LEN_OFFSET,
552                        },
553                    })
554                    .local_get(dst_local)
555                    .local_get(scratch_i32_local)
556                    .instr(Load {
557                        memory: memory_id,
558                        kind: LoadKind::I32 { atomic: false },
559                        arg: MemArg {
560                            align: 4,
561                            offset: ARRAY_CAP_OFFSET,
562                        },
563                    })
564                    .instr(Store {
565                        memory: memory_id,
566                        kind: StoreKind::I32 { atomic: false },
567                        arg: MemArg {
568                            align: 4,
569                            offset: ARRAY_CAP_OFFSET,
570                        },
571                    })
572                    .local_get(dst_local)
573                    .local_get(scratch_i32_local)
574                    .instr(Load {
575                        memory: memory_id,
576                        kind: LoadKind::I32 { atomic: false },
577                        arg: MemArg {
578                            align: 4,
579                            offset: ARRAY_LEN_OFFSET,
580                        },
581                    })
582                    .instr(Store {
583                        memory: memory_id,
584                        kind: StoreKind::I32 { atomic: false },
585                        arg: MemArg {
586                            align: 4,
587                            offset: ARRAY_HEADER_OFFSET,
588                        },
589                    })
590                    .block(None, |done| {
591                        let done_id = done.id();
592                        done.loop_(None, |loop_| {
593                            let loop_id = loop_.id();
594                            loop_
595                                .local_get(dst_local)
596                                .instr(Load {
597                                    memory: memory_id,
598                                    kind: LoadKind::I32 { atomic: false },
599                                    arg: MemArg {
600                                        align: 4,
601                                        offset: ARRAY_HEADER_OFFSET,
602                                    },
603                                })
604                                .local_set(scratch_i32_local_b)
605                                .local_get(scratch_i32_local_b)
606                                .i32_const(0)
607                                .binop(BinaryOp::I32Eq)
608                                .br_if(done_id)
609                                .local_get(scratch_i32_local_b)
610                                .i32_const(1)
611                                .binop(BinaryOp::I32Sub)
612                                .local_set(scratch_i32_local_b)
613                                .local_get(dst_local)
614                                .local_get(scratch_i32_local_b)
615                                .instr(Store {
616                                    memory: memory_id,
617                                    kind: StoreKind::I32 { atomic: false },
618                                    arg: MemArg {
619                                        align: 4,
620                                        offset: ARRAY_HEADER_OFFSET,
621                                    },
622                                })
623                                .local_get(scratch_i32_local)
624                                .i32_const(ARRAY_DATA_OFFSET as i32)
625                                .binop(BinaryOp::I32Add)
626                                .local_get(scratch_i32_local_b)
627                                .i32_const(ARRAY_SLOT_BYTES)
628                                .binop(BinaryOp::I32Mul)
629                                .binop(BinaryOp::I32Add)
630                                .instr(Load {
631                                    memory: memory_id,
632                                    kind: LoadKind::I64 { atomic: false },
633                                    arg: MemArg {
634                                        align: 8,
635                                        offset: 0,
636                                    },
637                                })
638                                .local_set(scratch_i64_local);
639                            if matches!(item_plan, ReleasePlan::None) {
640                                loop_
641                                    .local_get(dst_local)
642                                    .i32_const(ARRAY_DATA_OFFSET as i32)
643                                    .binop(BinaryOp::I32Add)
644                                    .local_get(scratch_i32_local_b)
645                                    .i32_const(ARRAY_SLOT_BYTES)
646                                    .binop(BinaryOp::I32Mul)
647                                    .binop(BinaryOp::I32Add)
648                                    .local_get(scratch_i64_local)
649                                    .instr(Store {
650                                        memory: memory_id,
651                                        kind: StoreKind::I64 { atomic: false },
652                                        arg: MemArg {
653                                            align: 8,
654                                            offset: 0,
655                                        },
656                                    })
657                                    .br(loop_id);
658                                return;
659                            }
660                            loop_
661                                .local_get(scratch_i64_local)
662                                .unop(UnaryOp::I32WrapI64)
663                                .local_set(scratch_i32_local_c)
664                                .local_get(scratch_i32_local_c)
665                                .local_set(scratch_i32_local_d);
666                            if let Err(err) = emit_clone_plan_value(
667                                loop_,
668                                runtime,
669                                memory_id,
670                                source_pos,
671                                fallback_line,
672                                scratch_i32_local_d,
673                                item_plan,
674                                scratch_i32_local,
675                                scratch_i32_local_b,
676                                scratch_i32_local_c,
677                                scratch_i32_local_d,
678                                scratch_i32_local_e,
679                                scratch_i64_local,
680                                nested_result_local,
681                            ) {
682                                nested_error = Some(err);
683                                return;
684                            }
685                            loop_
686                                .local_get(dst_local)
687                                .i32_const(ARRAY_DATA_OFFSET as i32)
688                                .binop(BinaryOp::I32Add)
689                                .local_get(scratch_i32_local_b)
690                                .i32_const(ARRAY_SLOT_BYTES)
691                                .binop(BinaryOp::I32Mul)
692                                .binop(BinaryOp::I32Add)
693                                .local_get(nested_result_local)
694                                .unop(UnaryOp::I64ExtendUI32)
695                                .instr(Store {
696                                    memory: memory_id,
697                                    kind: StoreKind::I64 { atomic: false },
698                                    arg: MemArg {
699                                        align: 8,
700                                        offset: 0,
701                                    },
702                                })
703                                .br(loop_id);
704                        });
705                    })
706                    .local_get(dst_local)
707                    .i32_const(1)
708                    .instr(Store {
709                        memory: memory_id,
710                        kind: StoreKind::I32 { atomic: false },
711                        arg: MemArg {
712                            align: 4,
713                            offset: ARRAY_HEADER_OFFSET,
714                        },
715                    });
716            },
717        );
718    if let Some(err) = nested_error {
719        return Err(err);
720    }
721    Ok(())
722}
723
724fn emit_clone_aggregate(
725    body: &mut walrus::InstrSeqBuilder,
726    runtime: &RuntimeFunctions,
727    memory_id: MemoryId,
728    source_pos: Option<ruka_mir::MirSourcePos>,
729    fallback_line: i32,
730    source_local: LocalId,
731    payload_bytes: u32,
732    fields: &[(u32, bool, ReleasePlan)],
733    scratch_i32_local: LocalId,
734    scratch_i32_local_b: LocalId,
735    scratch_i32_local_c: LocalId,
736    scratch_i32_local_d: LocalId,
737    scratch_i32_local_e: LocalId,
738    scratch_i64_local: LocalId,
739    dst_local: LocalId,
740) -> Result<(), LowerError> {
741    let total_bytes = ARRAY_DATA_OFFSET.saturating_add(payload_bytes);
742    emit_clone_blob(
743        body,
744        runtime,
745        memory_id,
746        source_pos,
747        fallback_line,
748        source_local,
749        total_bytes,
750        ALLOC_SITE_AGGREGATE_NEW,
751        scratch_i32_local,
752        scratch_i32_local_b,
753        dst_local,
754    )?;
755    emit_clone_aggregate_inline_fields(
756        body,
757        runtime,
758        memory_id,
759        source_pos,
760        fallback_line,
761        source_local,
762        dst_local,
763        fields,
764        scratch_i32_local,
765        scratch_i32_local_b,
766        scratch_i32_local_c,
767        scratch_i32_local_d,
768        scratch_i32_local_e,
769        scratch_i64_local,
770    )
771}
772
773fn emit_clone_enum(
774    body: &mut walrus::InstrSeqBuilder,
775    runtime: &RuntimeFunctions,
776    memory_id: MemoryId,
777    source_pos: Option<ruka_mir::MirSourcePos>,
778    fallback_line: i32,
779    source_local: LocalId,
780    payload_bytes: u32,
781    variants: &[Vec<(u32, bool, ReleasePlan)>],
782    scratch_i32_local: LocalId,
783    scratch_i32_local_b: LocalId,
784    scratch_i32_local_c: LocalId,
785    scratch_i32_local_d: LocalId,
786    scratch_i32_local_e: LocalId,
787    scratch_i64_local: LocalId,
788    dst_local: LocalId,
789) -> Result<(), LowerError> {
790    let total_bytes = ARRAY_DATA_OFFSET.saturating_add(payload_bytes);
791    emit_clone_blob(
792        body,
793        runtime,
794        memory_id,
795        source_pos,
796        fallback_line,
797        source_local,
798        total_bytes,
799        ALLOC_SITE_ENUM_NEW,
800        scratch_i32_local,
801        scratch_i32_local_b,
802        dst_local,
803    )?;
804    let mut nested_error = None;
805    body.local_get(source_local)
806        .i32_const(0)
807        .binop(BinaryOp::I32Ne)
808        .if_else(
809            None,
810            |non_null| {
811                non_null
812                    .local_get(source_local)
813                    .i32_const(ARRAY_DATA_OFFSET as i32)
814                    .binop(BinaryOp::I32Add)
815                    .instr(Load {
816                        memory: memory_id,
817                        kind: LoadKind::I32 { atomic: false },
818                        arg: MemArg {
819                            align: 4,
820                            offset: ENUM_TAG_OFFSET,
821                        },
822                    })
823                    .local_set(scratch_i32_local);
824                for (variant_index, field_plans) in variants.iter().enumerate() {
825                    non_null
826                        .local_get(scratch_i32_local)
827                        .i32_const(variant_index as i32)
828                        .binop(BinaryOp::I32Eq)
829                        .if_else(
830                            None,
831                            |variant_block| {
832                                if let Err(err) = emit_clone_aggregate_inline_fields(
833                                    variant_block,
834                                    runtime,
835                                    memory_id,
836                                    source_pos,
837                                    fallback_line,
838                                    source_local,
839                                    dst_local,
840                                    field_plans,
841                                    scratch_i32_local,
842                                    scratch_i32_local_b,
843                                    scratch_i32_local_c,
844                                    scratch_i32_local_d,
845                                    scratch_i32_local_e,
846                                    scratch_i64_local,
847                                ) {
848                                    nested_error = Some(err);
849                                }
850                            },
851                            |_else_| {},
852                        );
853                }
854            },
855            |_null| {},
856        );
857    if let Some(err) = nested_error {
858        return Err(err);
859    }
860    Ok(())
861}
862
863fn emit_clone_aggregate_inline_fields(
864    body: &mut walrus::InstrSeqBuilder,
865    runtime: &RuntimeFunctions,
866    memory_id: MemoryId,
867    source_pos: Option<ruka_mir::MirSourcePos>,
868    fallback_line: i32,
869    src_aggregate_local: LocalId,
870    dst_aggregate_local: LocalId,
871    fields: &[(u32, bool, ReleasePlan)],
872    scratch_i32_local: LocalId,
873    scratch_i32_local_b: LocalId,
874    scratch_i32_local_c: LocalId,
875    scratch_i32_local_d: LocalId,
876    scratch_i32_local_e: LocalId,
877    scratch_i64_local: LocalId,
878) -> Result<(), LowerError> {
879    let mut nested_error = None;
880    for (offset, inline, plan) in fields {
881        if *inline {
882            if let ReleasePlan::Aggregate { fields, .. } = plan {
883                body.local_get(src_aggregate_local)
884                    .i32_const(ARRAY_DATA_OFFSET as i32)
885                    .binop(BinaryOp::I32Add)
886                    .i32_const(*offset as i32)
887                    .binop(BinaryOp::I32Add)
888                    .local_set(scratch_i32_local_d)
889                    .local_get(dst_aggregate_local)
890                    .i32_const(ARRAY_DATA_OFFSET as i32)
891                    .binop(BinaryOp::I32Add)
892                    .i32_const(*offset as i32)
893                    .binop(BinaryOp::I32Add)
894                    .local_set(scratch_i32_local_e);
895                if let Err(err) = emit_clone_aggregate_inline_fields(
896                    body,
897                    runtime,
898                    memory_id,
899                    source_pos,
900                    fallback_line,
901                    scratch_i32_local_d,
902                    scratch_i32_local_e,
903                    fields,
904                    scratch_i32_local,
905                    scratch_i32_local_b,
906                    scratch_i32_local_c,
907                    scratch_i32_local_d,
908                    scratch_i32_local_e,
909                    scratch_i64_local,
910                ) {
911                    nested_error = Some(err);
912                    break;
913                }
914            }
915            continue;
916        }
917        if matches!(plan, ReleasePlan::None) {
918            continue;
919        }
920        body.local_get(src_aggregate_local)
921            .i32_const(ARRAY_DATA_OFFSET as i32)
922            .binop(BinaryOp::I32Add)
923            .i32_const(*offset as i32)
924            .binop(BinaryOp::I32Add)
925            .instr(Load {
926                memory: memory_id,
927                kind: LoadKind::I32 { atomic: false },
928                arg: MemArg {
929                    align: 4,
930                    offset: 0,
931                },
932            })
933            .local_set(scratch_i32_local);
934        let nested_source_local = pick_clone_temp_local_wide(
935            scratch_i32_local_d,
936            scratch_i32_local_c,
937            scratch_i32_local_e,
938            scratch_i32_local_b,
939            scratch_i32_local,
940            src_aggregate_local,
941            dst_aggregate_local,
942            scratch_i32_local,
943            scratch_i64_local,
944        );
945        body.local_get(scratch_i32_local)
946            .local_set(nested_source_local);
947        let nested_result_local = pick_clone_temp_local_wide(
948            scratch_i32_local_e,
949            scratch_i32_local_c,
950            scratch_i32_local_d,
951            scratch_i32_local_b,
952            scratch_i32_local,
953            src_aggregate_local,
954            dst_aggregate_local,
955            nested_source_local,
956            scratch_i64_local,
957        );
958        if let Err(err) = emit_clone_plan_value(
959            body,
960            runtime,
961            memory_id,
962            source_pos,
963            fallback_line,
964            nested_source_local,
965            plan,
966            scratch_i32_local,
967            scratch_i32_local_b,
968            scratch_i32_local_c,
969            scratch_i32_local_d,
970            scratch_i32_local_e,
971            scratch_i64_local,
972            nested_result_local,
973        ) {
974            nested_error = Some(err);
975            break;
976        }
977        body.local_get(dst_aggregate_local)
978            .i32_const(ARRAY_DATA_OFFSET as i32)
979            .binop(BinaryOp::I32Add)
980            .i32_const(*offset as i32)
981            .binop(BinaryOp::I32Add)
982            .local_get(nested_result_local)
983            .instr(Store {
984                memory: memory_id,
985                kind: StoreKind::I32 { atomic: false },
986                arg: MemArg {
987                    align: 4,
988                    offset: 0,
989                },
990            });
991    }
992    if let Some(err) = nested_error {
993        return Err(err);
994    }
995    Ok(())
996}
997
998fn emit_clone_blob(
999    body: &mut walrus::InstrSeqBuilder,
1000    runtime: &RuntimeFunctions,
1001    memory_id: MemoryId,
1002    source_pos: Option<ruka_mir::MirSourcePos>,
1003    fallback_line: i32,
1004    source_local: LocalId,
1005    total_bytes: u32,
1006    alloc_site: i32,
1007    scratch_i32_local: LocalId,
1008    scratch_i32_local_b: LocalId,
1009    dst_local: LocalId,
1010) -> Result<(), LowerError> {
1011    let total_i32 = int32_from_u32(total_bytes, "clone bytes")?;
1012    let mut nested_error = None;
1013    body.local_get(source_local)
1014        .i32_const(0)
1015        .binop(BinaryOp::I32Eq)
1016        .if_else(
1017            None,
1018            |null_src| {
1019                null_src.i32_const(0).local_set(dst_local);
1020            },
1021            |non_null| {
1022                if let Err(err) = emit_heap_alloc(
1023                    non_null,
1024                    runtime,
1025                    total_i32,
1026                    alloc_site,
1027                    source_pos,
1028                    fallback_line,
1029                    dst_local,
1030                ) {
1031                    nested_error = Some(err);
1032                    return;
1033                }
1034                if let Err(err) = emit_copy_bytes(
1035                    non_null,
1036                    memory_id,
1037                    source_local,
1038                    dst_local,
1039                    total_bytes,
1040                    scratch_i32_local,
1041                    scratch_i32_local_b,
1042                ) {
1043                    nested_error = Some(err);
1044                    return;
1045                }
1046                non_null.local_get(dst_local).i32_const(1).instr(Store {
1047                    memory: memory_id,
1048                    kind: StoreKind::I32 { atomic: false },
1049                    arg: MemArg {
1050                        align: 4,
1051                        offset: ARRAY_HEADER_OFFSET,
1052                    },
1053                });
1054            },
1055        );
1056    if let Some(err) = nested_error {
1057        return Err(err);
1058    }
1059    Ok(())
1060}