ruka_codegen_rust/
emit_stmt_instr.rs

1use super::*;
2
3pub(crate) fn emit_stmt_tokens(
4    function: &MirFunction,
5    stmt: &MirStmt,
6    source_file: &str,
7    source_line: usize,
8    mut_locals: &HashSet<MirLocalId>,
9    read_locals: &HashSet<MirLocalId>,
10    ref_ro_locals: &HashSet<MirLocalId>,
11    ref_mut_locals: &HashSet<MirLocalId>,
12    slice_locals: &HashSet<MirLocalId>,
13    function_names: &FunctionNames,
14    program_names: &ProgramNames,
15) -> rust::Tokens {
16    let mut tokens = rust::Tokens::new();
17    match stmt {
18        MirStmt::Instr(instr) => {
19            let instr_tokens = emit_instr_tokens(
20                function,
21                instr,
22                source_file,
23                source_line,
24                mut_locals,
25                read_locals,
26                ref_ro_locals,
27                ref_mut_locals,
28                slice_locals,
29                function_names,
30                program_names,
31            );
32            quote_in!(tokens => $instr_tokens);
33        }
34        MirStmt::If {
35            cond,
36            then_body,
37            else_body,
38        } => {
39            let cond = function_names.local_ident(*cond);
40            let then_tokens = emit_stmt_list_tokens(
41                function,
42                then_body,
43                source_file,
44                source_line,
45                mut_locals,
46                read_locals,
47                ref_ro_locals,
48                ref_mut_locals,
49                slice_locals,
50                function_names,
51                program_names,
52            );
53            let else_tokens = emit_stmt_list_tokens(
54                function,
55                else_body,
56                source_file,
57                source_line,
58                mut_locals,
59                read_locals,
60                ref_ro_locals,
61                ref_mut_locals,
62                slice_locals,
63                function_names,
64                program_names,
65            );
66            quote_in!(tokens =>
67                if $cond {
68                    $then_tokens
69                } else {
70                    $else_tokens
71                }
72            );
73        }
74        MirStmt::While {
75            loop_params,
76            init_args,
77            cond_body,
78            cond,
79            body,
80            step_args,
81        } => {
82            let mut init_tokens = rust::Tokens::new();
83            for (loop_param, init_arg) in loop_params.iter().zip(init_args.iter()) {
84                let loop_param_name = function_names.local_ident(*loop_param);
85                let init_arg_name = function_names.local_ident(*init_arg);
86                if read_locals.contains(loop_param) {
87                    quote_in!(init_tokens => let mut $loop_param_name = $init_arg_name;);
88                } else {
89                    quote_in!(init_tokens => let _ = $init_arg_name;);
90                }
91            }
92
93            let cond_tokens = emit_stmt_list_tokens(
94                function,
95                cond_body,
96                source_file,
97                source_line,
98                mut_locals,
99                read_locals,
100                ref_ro_locals,
101                ref_mut_locals,
102                slice_locals,
103                function_names,
104                program_names,
105            );
106            let cond = function_names.local_ident(*cond);
107            let body_tokens = emit_stmt_list_tokens(
108                function,
109                body,
110                source_file,
111                source_line,
112                mut_locals,
113                read_locals,
114                ref_ro_locals,
115                ref_mut_locals,
116                slice_locals,
117                function_names,
118                program_names,
119            );
120
121            let mut step_tokens = rust::Tokens::new();
122            if !loop_params.is_empty() {
123                if loop_params.len() == 1 {
124                    let loop_param_name =
125                        function_names.local_ident(*loop_params.first().expect("loop param"));
126                    let step_arg_name =
127                        function_names.local_ident(*step_args.first().expect("step arg"));
128                    if read_locals.contains(loop_params.first().expect("loop param")) {
129                        quote_in!(step_tokens => $loop_param_name = $step_arg_name;);
130                    } else {
131                        quote_in!(step_tokens => let _ = $step_arg_name;);
132                    }
133                } else {
134                    let used_loop_param_indexes = loop_params
135                        .iter()
136                        .enumerate()
137                        .filter_map(|(index, local)| {
138                            if read_locals.contains(local) {
139                                Some(index)
140                            } else {
141                                None
142                            }
143                        })
144                        .collect::<Vec<_>>();
145
146                    if used_loop_param_indexes.is_empty() {
147                        for step_arg in step_args {
148                            let step_arg_name = function_names.local_ident(*step_arg);
149                            quote_in!(step_tokens => let _ = $step_arg_name;);
150                        }
151                    } else {
152                        let tuple_name =
153                            synthetic_temp_ident(&format!("loop_next_{}", loop_params[0].index()));
154                        let mut tuple_items = rust::Tokens::new();
155                        for (index, step_arg) in step_args.iter().enumerate() {
156                            if index > 0 {
157                                quote_in!(tuple_items => ,);
158                            }
159                            let step_arg_name = function_names.local_ident(*step_arg);
160                            quote_in!(tuple_items => $step_arg_name);
161                        }
162                        let tuple_name_decl = tuple_name.clone();
163                        quote_in!(step_tokens => let $tuple_name_decl = ($tuple_items););
164                        for index in used_loop_param_indexes {
165                            let loop_param_name = function_names.local_ident(loop_params[index]);
166                            let idx = index;
167                            let tuple_name_ref = tuple_name.clone();
168                            quote_in!(step_tokens => $loop_param_name = $tuple_name_ref.$idx;);
169                        }
170                    }
171                }
172            }
173
174            quote_in!(tokens =>
175                $init_tokens
176                loop {
177                    $cond_tokens
178                    if $cond {
179                        $body_tokens
180                        $step_tokens
181                    } else {
182                        break;
183                    }
184                }
185            );
186        }
187        MirStmt::Return { value } => {
188            let value = function_names.local_ident(*value);
189            quote_in!(tokens => return $value;);
190        }
191    }
192    tokens
193}
194
195pub(crate) fn emit_instr_tokens(
196    function: &MirFunction,
197    instr: &MirInstr,
198    source_file: &str,
199    source_line: usize,
200    mut_locals: &HashSet<MirLocalId>,
201    read_locals: &HashSet<MirLocalId>,
202    ref_ro_locals: &HashSet<MirLocalId>,
203    ref_mut_locals: &HashSet<MirLocalId>,
204    slice_locals: &HashSet<MirLocalId>,
205    function_names: &FunctionNames,
206    program_names: &ProgramNames,
207) -> rust::Tokens {
208    if let Some(tokens) = emit_pointer_instr_tokens(
209        function,
210        instr,
211        source_file,
212        source_line,
213        mut_locals,
214        read_locals,
215        function_names,
216    ) {
217        return tokens;
218    }
219    if let Some(tokens) = emit_call_like_instr_tokens(
220        function,
221        instr,
222        mut_locals,
223        read_locals,
224        function_names,
225        program_names,
226    ) {
227        return tokens;
228    }
229    if let Some(tokens) =
230        emit_int_binary_instr_tokens(instr, mut_locals, read_locals, function_names)
231    {
232        return tokens;
233    }
234    if let Some(tokens) =
235        emit_collection_borrow_instr_tokens(instr, mut_locals, read_locals, function_names)
236    {
237        return tokens;
238    }
239
240    let mut tokens = rust::Tokens::new();
241
242    match instr {
243        MirInstr::ConstUnit { dst } => {
244            emit_local_binding(
245                &mut tokens,
246                *dst,
247                quote!(()),
248                quote!(()),
249                true,
250                mut_locals,
251                read_locals,
252                function_names,
253            );
254        }
255        MirInstr::ConstInt { dst, value } => {
256            let int_value = *value;
257            let value = match function.locals[*dst].ty {
258                Ty::U8 => quote!($int_value as u8),
259                Ty::U16 => quote!($int_value as u16),
260                Ty::U32 => quote!($int_value as u32),
261                Ty::U64 => quote!($int_value as u64),
262                Ty::I8 => quote!($int_value as i8),
263                Ty::I16 => quote!($int_value as i16),
264                Ty::I32 => quote!($int_value as i32),
265                Ty::I64 => quote!($int_value),
266                Ty::F32 => quote!($int_value as f32),
267                Ty::F64 => quote!($int_value as f64),
268                _ => quote!($int_value),
269            };
270            emit_local_binding(
271                &mut tokens,
272                *dst,
273                value.clone(),
274                value,
275                true,
276                mut_locals,
277                read_locals,
278                function_names,
279            );
280        }
281        MirInstr::ConstFloat { dst, value } => {
282            let float_value = *value;
283            let value = match function.locals[*dst].ty {
284                Ty::F32 => {
285                    let bits = (float_value as f32).to_bits();
286                    quote!(f32::from_bits($bits))
287                }
288                _ => {
289                    let bits = float_value.to_bits();
290                    quote!(f64::from_bits($bits))
291                }
292            };
293            emit_local_binding(
294                &mut tokens,
295                *dst,
296                value.clone(),
297                value,
298                true,
299                mut_locals,
300                read_locals,
301                function_names,
302            );
303        }
304        MirInstr::ConstString { dst, value } => {
305            let value = value.clone();
306            let string_value = quote!(String::from($(quoted(value.as_str()))));
307            emit_local_binding(
308                &mut tokens,
309                *dst,
310                string_value.clone(),
311                string_value,
312                true,
313                mut_locals,
314                read_locals,
315                function_names,
316            );
317        }
318        MirInstr::ConstBool { dst, value } => {
319            let value = if *value { quote!(true) } else { quote!(false) };
320            emit_local_binding(
321                &mut tokens,
322                *dst,
323                value.clone(),
324                value,
325                true,
326                mut_locals,
327                read_locals,
328                function_names,
329            );
330        }
331        MirInstr::ConstNull { dst } => {
332            emit_local_binding(
333                &mut tokens,
334                *dst,
335                quote!(None),
336                quote!(None::<()>),
337                true,
338                mut_locals,
339                read_locals,
340                function_names,
341            );
342        }
343        MirInstr::MakeArray { items, dst, .. } => {
344            let dst = *dst;
345            let array_items = emit_array_items(items, function_names);
346            let value_tokens = if slice_locals.contains(&dst) {
347                emit_boxed_slice_items(items, function_names)
348            } else {
349                array_items
350            };
351            emit_local_binding(
352                &mut tokens,
353                dst,
354                value_tokens.clone(),
355                value_tokens,
356                true,
357                mut_locals,
358                read_locals,
359                function_names,
360            );
361        }
362        MirInstr::MakeTuple { items, dst, .. } => {
363            let dst = *dst;
364            let mut tuple_items = rust::Tokens::new();
365            for (index, item) in items.iter().enumerate() {
366                if index > 0 {
367                    quote_in!(tuple_items => ,);
368                }
369                let local = function_names.local_ident(item.local);
370                quote_in!(tuple_items => $local);
371            }
372            if items.len() == 1 {
373                quote_in!(tuple_items => ,);
374            }
375            let value = quote!(($tuple_items));
376            emit_local_binding(
377                &mut tokens,
378                dst,
379                value.clone(),
380                value,
381                true,
382                mut_locals,
383                read_locals,
384                function_names,
385            );
386        }
387        MirInstr::MakeStruct {
388            name, fields, dst, ..
389        } => {
390            let dst = *dst;
391            let struct_name = mangle_struct_ident(name);
392            let mut field_tokens = rust::Tokens::new();
393            for (index, (field_name, local)) in fields.iter().enumerate() {
394                if index > 0 {
395                    quote_in!(field_tokens => ,);
396                }
397                let field_name = field_name.clone();
398                let local = function_names.local_ident(*local);
399                quote_in!(field_tokens => $field_name: $local);
400            }
401            let value = quote!($struct_name { $field_tokens });
402            emit_local_binding(
403                &mut tokens,
404                dst,
405                value.clone(),
406                value,
407                true,
408                mut_locals,
409                read_locals,
410                function_names,
411            );
412        }
413        MirInstr::MakeEnum {
414            enum_name,
415            variant,
416            payload,
417            dst,
418            ..
419        } => {
420            let enum_name = if enum_name == "Option" {
421                "Option".to_owned()
422            } else {
423                mangle_struct_ident(enum_name)
424            };
425            let variant = variant.clone();
426            let dst = *dst;
427            let payload_tokens = emit_enum_payload_items(payload, function_names);
428            let value = if payload.is_empty() {
429                quote!($enum_name::$variant)
430            } else {
431                quote!($enum_name::$variant($payload_tokens))
432            };
433            emit_local_binding(
434                &mut tokens,
435                dst,
436                value.clone(),
437                value,
438                true,
439                mut_locals,
440                read_locals,
441                function_names,
442            );
443        }
444        MirInstr::ReadField { base, field, dst } => {
445            let base_local = *base;
446            let base = function_names.local_ident(base_local);
447            let field = field.clone();
448            let dst = *dst;
449            let value_expr = if let Ok(index) = field.parse::<usize>() {
450                if ref_ro_locals.contains(&base_local) || ref_mut_locals.contains(&base_local) {
451                    quote!((*$base).$index.clone())
452                } else {
453                    quote!($base.$index.clone())
454                }
455            } else if ref_ro_locals.contains(&base_local) || ref_mut_locals.contains(&base_local) {
456                quote!((*$base).$field.clone())
457            } else {
458                quote!($base.$field.clone())
459            };
460            emit_local_binding(
461                &mut tokens,
462                dst,
463                value_expr.clone(),
464                value_expr,
465                true,
466                mut_locals,
467                read_locals,
468                function_names,
469            );
470        }
471        MirInstr::EnumIsVariant {
472            value,
473            enum_name,
474            variant,
475            payload_len,
476            dst,
477        } => {
478            let value = function_names.local_ident(*value);
479            let enum_name = if enum_name == "Option" {
480                "Option".to_owned()
481            } else {
482                mangle_struct_ident(enum_name)
483            };
484            let variant = variant.clone();
485            let pattern = emit_enum_variant_wildcard_pattern(&enum_name, &variant, *payload_len);
486            let check_expr = quote!(matches!(&$value, $pattern));
487            emit_local_binding(
488                &mut tokens,
489                *dst,
490                check_expr.clone(),
491                check_expr,
492                true,
493                mut_locals,
494                read_locals,
495                function_names,
496            );
497        }
498        MirInstr::EnumGetField {
499            value,
500            enum_name,
501            variant,
502            index,
503            dst,
504        } => {
505            let value = function_names.local_ident(*value);
506            let enum_name = if enum_name == "Option" {
507                "Option".to_owned()
508            } else {
509                mangle_struct_ident(enum_name)
510            };
511            let variant = variant.clone();
512            let extract_expr = emit_enum_get_field_expr(&value, &enum_name, &variant, *index);
513            emit_local_binding(
514                &mut tokens,
515                *dst,
516                extract_expr.clone(),
517                extract_expr,
518                true,
519                mut_locals,
520                read_locals,
521                function_names,
522            );
523        }
524        MirInstr::Copy { src, dst } => {
525            let src_local_id = *src;
526            let src = function_names.local_ident(src_local_id);
527            let value = quote!($src.clone());
528            emit_local_binding(
529                &mut tokens,
530                *dst,
531                value.clone(),
532                value,
533                true,
534                mut_locals,
535                read_locals,
536                function_names,
537            );
538        }
539        MirInstr::Move { src, dst } => {
540            let src = function_names.local_ident(*src);
541            let value = quote!($src);
542            emit_local_binding(
543                &mut tokens,
544                *dst,
545                value.clone(),
546                value,
547                true,
548                mut_locals,
549                read_locals,
550                function_names,
551            );
552        }
553        MirInstr::AssignLocal { src, dst } => {
554            let src = function_names.local_ident(*src);
555            let dst = function_names.local_ident(*dst);
556            quote_in!(tokens => $dst = $src;);
557        }
558        MirInstr::StoreRef { src, dst_ref } => {
559            let src = function_names.local_ident(*src);
560            let dst_ref = function_names.local_ident(*dst_ref);
561            quote_in!(tokens => *$dst_ref = $src;);
562        }
563        MirInstr::NumCast { src, dst } => {
564            let src = function_names.local_ident(*src);
565            let cast_ty = emit_ty_tokens(&function.locals[*dst].ty);
566            let value = quote!($src as $cast_ty);
567            emit_local_binding(
568                &mut tokens,
569                *dst,
570                value.clone(),
571                value,
572                true,
573                mut_locals,
574                read_locals,
575                function_names,
576            );
577        }
578        MirInstr::CheckedIntCast { src, dst } => {
579            let src_name = function_names.local_ident(*src);
580            let cast_ty = emit_ty_tokens(&function.locals[*dst].ty);
581            let (target_min, target_max) = int_ty_range_i128(&function.locals[*dst].ty)
582                .expect("checked int cast destination should be integer");
583            let value = quote!({
584                let source_value_i128 = $src_name as i128;
585                if source_value_i128 < $target_min || source_value_i128 > $target_max {
586                    panic!("checked integer cast overflow");
587                }
588                $src_name as $cast_ty
589            });
590            emit_local_binding(
591                &mut tokens,
592                *dst,
593                value.clone(),
594                value,
595                true,
596                mut_locals,
597                read_locals,
598                function_names,
599            );
600        }
601        MirInstr::AssignFieldPath { base, fields, src } => {
602            let base_name = function_names.local_ident(*base);
603            let src = function_names.local_ident(*src);
604            let mut path = rust::Tokens::new();
605            for field in fields {
606                if let Ok(index) = field.parse::<usize>() {
607                    quote_in!(path => .$index);
608                } else {
609                    let field = field.clone();
610                    quote_in!(path => .$field);
611                }
612            }
613            if ref_mut_locals.contains(base) {
614                quote_in!(tokens => (*$base_name)$path = $src;);
615            } else {
616                quote_in!(tokens => $base_name$path = $src;);
617            }
618        }
619        MirInstr::ReleaseHeap { local } => {
620            let local = function_names.local_ident(*local);
621            quote_in!(tokens => let _ = &$local;);
622        }
623        MirInstr::PointerNew { .. }
624        | MirInstr::CollectionLen { .. }
625        | MirInstr::IndexBorrowRo { .. }
626        | MirInstr::IndexBorrowMut { .. }
627        | MirInstr::SliceBorrowRo { .. }
628        | MirInstr::SliceBorrowMut { .. }
629        | MirInstr::FieldBorrowRo { .. }
630        | MirInstr::FieldBorrowMut { .. }
631        | MirInstr::PointerIsSome { .. }
632        | MirInstr::PointerBorrowRo { .. }
633        | MirInstr::PointerBorrowMut { .. }
634        | MirInstr::DerefCopy { .. }
635        | MirInstr::Call { .. }
636        | MirInstr::CallExtern { .. }
637        | MirInstr::CallIntrinsic { .. }
638        | MirInstr::IntLt { .. }
639        | MirInstr::IntGt { .. }
640        | MirInstr::IntLtEq { .. }
641        | MirInstr::IntGtEq { .. }
642        | MirInstr::IntEq { .. }
643        | MirInstr::IntNeq { .. }
644        | MirInstr::IntAdd { .. }
645        | MirInstr::IntSub { .. }
646        | MirInstr::IntMul { .. }
647        | MirInstr::IntDiv { .. }
648        | MirInstr::IntMod { .. } => {
649            unreachable!("instruction should be emitted by helper before match")
650        }
651    }
652
653    tokens
654}
655
656pub(crate) fn int_ty_range_i128(ty: &Ty) -> Option<(rust::Tokens, rust::Tokens)> {
657    match ty {
658        Ty::U8 => Some((quote!(u8::MIN as i128), quote!(u8::MAX as i128))),
659        Ty::U16 => Some((quote!(u16::MIN as i128), quote!(u16::MAX as i128))),
660        Ty::U32 => Some((quote!(u32::MIN as i128), quote!(u32::MAX as i128))),
661        Ty::U64 => Some((quote!(u64::MIN as i128), quote!(u64::MAX as i128))),
662        Ty::I8 => Some((quote!(i8::MIN as i128), quote!(i8::MAX as i128))),
663        Ty::I16 => Some((quote!(i16::MIN as i128), quote!(i16::MAX as i128))),
664        Ty::I32 => Some((quote!(i32::MIN as i128), quote!(i32::MAX as i128))),
665        Ty::I64 => Some((quote!(i64::MIN as i128), quote!(i64::MAX as i128))),
666        _ => None,
667    }
668}
669
670pub(crate) fn emit_call_like_instr_tokens(
671    function: &MirFunction,
672    instr: &MirInstr,
673    mut_locals: &HashSet<MirLocalId>,
674    read_locals: &HashSet<MirLocalId>,
675    function_names: &FunctionNames,
676    program_names: &ProgramNames,
677) -> Option<rust::Tokens> {
678    let mut tokens = rust::Tokens::new();
679    match instr {
680        MirInstr::Call { callee, args, dst } => {
681            let callee = program_names.function_ident(*callee);
682            let args = emit_internal_call_args(function, args, function_names);
683            let value = quote!($callee($args));
684            emit_local_binding(
685                &mut tokens,
686                *dst,
687                value.clone(),
688                value,
689                true,
690                mut_locals,
691                read_locals,
692                function_names,
693            );
694            Some(tokens)
695        }
696        MirInstr::CallExtern { symbol, args, dst } => {
697            let expr = emit_extern_call_expr(function, symbol, args, function_names);
698            emit_local_binding(
699                &mut tokens,
700                *dst,
701                expr.clone(),
702                expr,
703                true,
704                mut_locals,
705                read_locals,
706                function_names,
707            );
708            Some(tokens)
709        }
710        MirInstr::CallIntrinsic {
711            intrinsic,
712            args,
713            dst,
714            ..
715        } => {
716            let expr = emit_intrinsic_call_expr(function, *intrinsic, args, function_names);
717            emit_local_binding(
718                &mut tokens,
719                *dst,
720                expr.clone(),
721                expr,
722                true,
723                mut_locals,
724                read_locals,
725                function_names,
726            );
727            Some(tokens)
728        }
729        _ => None,
730    }
731}
732
733pub(crate) fn emit_pointer_instr_tokens(
734    function: &MirFunction,
735    instr: &MirInstr,
736    source_file: &str,
737    source_line: usize,
738    mut_locals: &HashSet<MirLocalId>,
739    read_locals: &HashSet<MirLocalId>,
740    function_names: &FunctionNames,
741) -> Option<rust::Tokens> {
742    let mut tokens = rust::Tokens::new();
743    match instr {
744        MirInstr::PointerNew { src, dst } => {
745            let src = function_names.local_ident(*src);
746            let site = format!(
747                "{}:{} fn {} pointer_new local {}",
748                source_file,
749                source_line,
750                function.name,
751                dst.index()
752            );
753            let value = quote!(ruka_runtime::ptr::Ptr::new_tracked($src, $(quoted(site.as_str()))));
754            emit_local_binding(
755                &mut tokens,
756                *dst,
757                value.clone(),
758                value,
759                true,
760                mut_locals,
761                read_locals,
762                function_names,
763            );
764            Some(tokens)
765        }
766        MirInstr::PointerIsSome { pointer, dst } => {
767            let pointer = function_names.local_ident(*pointer);
768            let value = quote!($pointer.is_some());
769            emit_local_binding(
770                &mut tokens,
771                *dst,
772                value.clone(),
773                value,
774                true,
775                mut_locals,
776                read_locals,
777                function_names,
778            );
779            Some(tokens)
780        }
781        MirInstr::PointerBorrowRo { pointer, dst } => {
782            let pointer = function_names.local_ident(*pointer);
783            let value = quote!($pointer.borrow());
784            emit_local_binding(
785                &mut tokens,
786                *dst,
787                value.clone(),
788                value,
789                false,
790                mut_locals,
791                read_locals,
792                function_names,
793            );
794            Some(tokens)
795        }
796        MirInstr::PointerBorrowMut { pointer, dst } => {
797            let pointer = function_names.local_ident(*pointer);
798            let value = quote!($pointer.borrow_mut());
799            emit_local_binding(
800                &mut tokens,
801                *dst,
802                value.clone(),
803                value,
804                false,
805                mut_locals,
806                read_locals,
807                function_names,
808            );
809            Some(tokens)
810        }
811        MirInstr::DerefCopy { src, dst } => {
812            let src = function_names.local_ident(*src);
813            let value = quote!((*$src).clone());
814            emit_local_binding(
815                &mut tokens,
816                *dst,
817                value.clone(),
818                value,
819                true,
820                mut_locals,
821                read_locals,
822                function_names,
823            );
824            Some(tokens)
825        }
826        _ => None,
827    }
828}