Skip to main content

yulang_native/
abi_eval.rs

1use std::collections::BTreeMap;
2use std::fmt;
3
4use yulang_runtime as runtime;
5use yulang_typed_ir as typed_ir;
6
7use crate::abi::{NativeAbiBlock, NativeAbiFunction, NativeAbiModule, NativeAbiStmt};
8use crate::control_ir::{BlockId, NativeLiteral, NativeTerminator, ValueId};
9use crate::eval::NativeEvalError;
10
11#[derive(Debug, Clone, PartialEq)]
12pub enum NativeAbiEvalError {
13    EmptyFunction {
14        name: String,
15    },
16    MissingFunction {
17        name: String,
18    },
19    MissingBlock {
20        id: BlockId,
21    },
22    BlockArgumentMismatch {
23        id: BlockId,
24        expected: usize,
25        actual: usize,
26    },
27    FunctionArgumentMismatch {
28        function: String,
29        expected: usize,
30        actual: usize,
31    },
32    EnvArgumentMismatch {
33        function: String,
34        expected: usize,
35        actual: usize,
36    },
37    MissingValue {
38        id: ValueId,
39    },
40    MissingEnvSlot {
41        function: String,
42        slot: usize,
43    },
44    ExpectedPlainValue {
45        id: ValueId,
46    },
47    ExpectedClosure {
48        id: ValueId,
49    },
50    ExpectedRecord {
51        value: runtime::VmValue,
52    },
53    ExpectedTuple {
54        value: runtime::VmValue,
55    },
56    ExpectedVariant {
57        value: runtime::VmValue,
58    },
59    NativeEval(NativeEvalError),
60}
61
62impl fmt::Display for NativeAbiEvalError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            NativeAbiEvalError::EmptyFunction { name } => {
66                write!(f, "native ABI function {name} has no entry block")
67            }
68            NativeAbiEvalError::MissingFunction { name } => {
69                write!(f, "native ABI function {name} is missing")
70            }
71            NativeAbiEvalError::MissingBlock { id } => {
72                write!(f, "native ABI block {id:?} is missing")
73            }
74            NativeAbiEvalError::BlockArgumentMismatch {
75                id,
76                expected,
77                actual,
78            } => write!(
79                f,
80                "native ABI block {id:?} expected {expected} arguments, got {actual}"
81            ),
82            NativeAbiEvalError::FunctionArgumentMismatch {
83                function,
84                expected,
85                actual,
86            } => write!(
87                f,
88                "native ABI function {function} expected {expected} arguments, got {actual}"
89            ),
90            NativeAbiEvalError::EnvArgumentMismatch {
91                function,
92                expected,
93                actual,
94            } => write!(
95                f,
96                "native ABI function {function} expected {expected} environment slots, got {actual}"
97            ),
98            NativeAbiEvalError::MissingValue { id } => {
99                write!(f, "native ABI value {id:?} is missing")
100            }
101            NativeAbiEvalError::MissingEnvSlot { function, slot } => {
102                write!(f, "native ABI env slot {slot} is missing in `{function}`")
103            }
104            NativeAbiEvalError::ExpectedPlainValue { id } => {
105                write!(f, "native ABI expected plain value {id:?}")
106            }
107            NativeAbiEvalError::ExpectedClosure { id } => {
108                write!(f, "native ABI expected closure value {id:?}")
109            }
110            NativeAbiEvalError::ExpectedRecord { value } => {
111                write!(f, "native ABI expected record, got {value:?}")
112            }
113            NativeAbiEvalError::ExpectedTuple { value } => {
114                write!(f, "native ABI expected tuple, got {value:?}")
115            }
116            NativeAbiEvalError::ExpectedVariant { value } => {
117                write!(f, "native ABI expected variant, got {value:?}")
118            }
119            NativeAbiEvalError::NativeEval(error) => write!(f, "{error}"),
120        }
121    }
122}
123
124impl std::error::Error for NativeAbiEvalError {}
125
126impl From<NativeEvalError> for NativeAbiEvalError {
127    fn from(error: NativeEvalError) -> Self {
128        NativeAbiEvalError::NativeEval(error)
129    }
130}
131
132pub type NativeAbiEvalResult<T> = Result<T, NativeAbiEvalError>;
133
134pub fn eval_abi_module(module: &NativeAbiModule) -> NativeAbiEvalResult<Vec<runtime::VmValue>> {
135    module
136        .roots
137        .iter()
138        .map(|root| eval_function(module, root, Vec::new(), Vec::new()))
139        .collect()
140}
141
142fn eval_function(
143    module: &NativeAbiModule,
144    function: &NativeAbiFunction,
145    env: Vec<NativeAbiRuntimeValue>,
146    args: Vec<NativeAbiRuntimeValue>,
147) -> NativeAbiEvalResult<runtime::VmValue> {
148    into_plain_value(
149        ValueId(usize::MAX),
150        eval_function_value(module, function, env, args)?,
151    )
152}
153
154fn eval_function_value(
155    module: &NativeAbiModule,
156    function: &NativeAbiFunction,
157    env: Vec<NativeAbiRuntimeValue>,
158    args: Vec<NativeAbiRuntimeValue>,
159) -> NativeAbiEvalResult<NativeAbiRuntimeValue> {
160    if function.environment_slots != env.len() {
161        return Err(NativeAbiEvalError::EnvArgumentMismatch {
162            function: function.name.clone(),
163            expected: function.environment_slots,
164            actual: env.len(),
165        });
166    }
167    if function.params.len() != args.len() {
168        return Err(NativeAbiEvalError::FunctionArgumentMismatch {
169            function: function.name.clone(),
170            expected: function.params.len(),
171            actual: args.len(),
172        });
173    }
174    let entry = function
175        .blocks
176        .first()
177        .ok_or_else(|| NativeAbiEvalError::EmptyFunction {
178            name: function.name.clone(),
179        })?;
180    eval_blocks(module, function, &env, entry.id, args)
181}
182
183fn eval_blocks(
184    module: &NativeAbiModule,
185    function: &NativeAbiFunction,
186    env: &[NativeAbiRuntimeValue],
187    entry: BlockId,
188    initial_args: Vec<NativeAbiRuntimeValue>,
189) -> NativeAbiEvalResult<NativeAbiRuntimeValue> {
190    let mut values = Vec::<Option<NativeAbiRuntimeValue>>::new();
191    let mut current = entry;
192    let mut args = initial_args;
193    loop {
194        let block = block_by_id(function, current)?;
195        assign_block_args(&mut values, block, args)?;
196        args = Vec::new();
197
198        for stmt in &block.stmts {
199            match stmt {
200                NativeAbiStmt::Literal { dest, literal } => {
201                    write_value(&mut values, *dest, plain(eval_literal(literal)));
202                }
203                NativeAbiStmt::Primitive { dest, op, args } => {
204                    let args = args
205                        .iter()
206                        .map(|id| read_plain_value(&values, *id))
207                        .collect::<NativeAbiEvalResult<Vec<_>>>()?;
208                    write_value(
209                        &mut values,
210                        *dest,
211                        plain(crate::eval::eval_primitive_for_abi(*op, &args)?),
212                    );
213                }
214                NativeAbiStmt::DirectCall { dest, target, args } => {
215                    let function = function_by_name(module, target)?;
216                    let args = args
217                        .iter()
218                        .map(|id| read_value(&values, *id))
219                        .collect::<NativeAbiEvalResult<Vec<_>>>()?;
220                    write_value(
221                        &mut values,
222                        *dest,
223                        eval_function_value(module, function, Vec::new(), args)?,
224                    );
225                }
226                NativeAbiStmt::Tuple { dest, items } => {
227                    let items = items
228                        .iter()
229                        .map(|id| read_plain_value(&values, *id))
230                        .collect::<NativeAbiEvalResult<Vec<_>>>()?;
231                    write_value(&mut values, *dest, plain(runtime::VmValue::Tuple(items)));
232                }
233                NativeAbiStmt::Record { dest, base, fields } => {
234                    let mut record = match base {
235                        Some(base) => match read_plain_value(&values, *base)? {
236                            runtime::VmValue::Record(fields) => fields,
237                            value => return Err(NativeAbiEvalError::ExpectedRecord { value }),
238                        },
239                        None => BTreeMap::new(),
240                    };
241                    for field in fields {
242                        record.insert(field.name.clone(), read_plain_value(&values, field.value)?);
243                    }
244                    write_value(&mut values, *dest, plain(runtime::VmValue::Record(record)));
245                }
246                NativeAbiStmt::RecordWithoutFields { dest, base, fields } => {
247                    let mut record = match read_plain_value(&values, *base)? {
248                        runtime::VmValue::Record(fields) => fields,
249                        value => return Err(NativeAbiEvalError::ExpectedRecord { value }),
250                    };
251                    for field in fields {
252                        record.remove(field);
253                    }
254                    write_value(&mut values, *dest, plain(runtime::VmValue::Record(record)));
255                }
256                NativeAbiStmt::Variant { dest, tag, value } => {
257                    let value = value
258                        .map(|value| read_plain_value(&values, value).map(Box::new))
259                        .transpose()?;
260                    write_value(
261                        &mut values,
262                        *dest,
263                        plain(runtime::VmValue::Variant {
264                            tag: tag.clone(),
265                            value,
266                        }),
267                    );
268                }
269                NativeAbiStmt::Select { dest, base, field } => {
270                    let value = match read_plain_value(&values, *base)? {
271                        runtime::VmValue::Record(fields) => fields.get(field).cloned(),
272                        _ => None,
273                    }
274                    .ok_or(NativeAbiEvalError::ExpectedPlainValue { id: *base })?;
275                    write_value(&mut values, *dest, plain(value));
276                }
277                NativeAbiStmt::TupleGet { dest, tuple, index } => {
278                    let value = read_plain_value(&values, *tuple)?;
279                    let runtime::VmValue::Tuple(items) = value else {
280                        return Err(NativeAbiEvalError::ExpectedTuple { value });
281                    };
282                    let value = items.get(*index).cloned().ok_or_else(|| {
283                        NativeAbiEvalError::ExpectedTuple {
284                            value: runtime::VmValue::Tuple(items.clone()),
285                        }
286                    })?;
287                    write_value(&mut values, *dest, plain(value));
288                }
289                NativeAbiStmt::VariantTagEq { dest, variant, tag } => {
290                    let value = read_plain_value(&values, *variant)?;
291                    let runtime::VmValue::Variant {
292                        tag: actual_tag, ..
293                    } = value
294                    else {
295                        return Err(NativeAbiEvalError::ExpectedVariant { value });
296                    };
297                    write_value(
298                        &mut values,
299                        *dest,
300                        plain(runtime::VmValue::Bool(actual_tag == *tag)),
301                    );
302                }
303                NativeAbiStmt::VariantPayload { dest, variant } => {
304                    let value = read_plain_value(&values, *variant)?;
305                    let runtime::VmValue::Variant {
306                        value: Some(payload),
307                        ..
308                    } = value
309                    else {
310                        return Err(NativeAbiEvalError::ExpectedVariant { value });
311                    };
312                    write_value(&mut values, *dest, plain(*payload));
313                }
314                NativeAbiStmt::ValueEq { dest, left, right } => {
315                    let left = read_plain_value(&values, *left)?;
316                    let right = read_plain_value(&values, *right)?;
317                    write_value(
318                        &mut values,
319                        *dest,
320                        plain(runtime::VmValue::Bool(left == right)),
321                    );
322                }
323                NativeAbiStmt::BoolAnd { dest, left, right } => {
324                    let left = read_plain_value(&values, *left)?;
325                    let right = read_plain_value(&values, *right)?;
326                    write_value(
327                        &mut values,
328                        *dest,
329                        plain(runtime::VmValue::Bool(
330                            matches!(left, runtime::VmValue::Bool(true))
331                                && matches!(right, runtime::VmValue::Bool(true)),
332                        )),
333                    );
334                }
335                NativeAbiStmt::LoadEnv { dest, slot } => {
336                    let value = env.get(*slot).cloned().ok_or_else(|| {
337                        NativeAbiEvalError::MissingEnvSlot {
338                            function: function.name.clone(),
339                            slot: *slot,
340                        }
341                    })?;
342                    write_value(&mut values, *dest, value);
343                }
344                NativeAbiStmt::AllocateClosure {
345                    dest,
346                    target,
347                    environment,
348                } => {
349                    let environment = environment
350                        .iter()
351                        .map(|id| read_value(&values, *id))
352                        .collect::<NativeAbiEvalResult<Vec<_>>>()?;
353                    write_value(
354                        &mut values,
355                        *dest,
356                        NativeAbiRuntimeValue::Closure(NativeAbiClosureValue {
357                            target: target.clone(),
358                            environment,
359                        }),
360                    );
361                }
362                NativeAbiStmt::IndirectClosureCall { dest, callee, args } => {
363                    let closure = read_closure(&values, *callee)?;
364                    let function = function_by_name(module, &closure.target)?;
365                    let args = args
366                        .iter()
367                        .map(|id| read_value(&values, *id))
368                        .collect::<NativeAbiEvalResult<Vec<_>>>()?;
369                    write_value(
370                        &mut values,
371                        *dest,
372                        eval_function_value(module, function, closure.environment, args)?,
373                    );
374                }
375            }
376        }
377
378        match &block.terminator {
379            NativeTerminator::Return(value) => return read_value(&values, *value),
380            NativeTerminator::Jump {
381                target,
382                args: jump_args,
383            } => {
384                args = jump_args
385                    .iter()
386                    .map(|id| read_value(&values, *id))
387                    .collect::<NativeAbiEvalResult<Vec<_>>>()?;
388                current = *target;
389            }
390            NativeTerminator::Branch {
391                cond,
392                then_block,
393                else_block,
394            } => {
395                let cond = read_plain_value(&values, *cond)?;
396                current = if bool_value(&cond)? {
397                    *then_block
398                } else {
399                    *else_block
400                };
401            }
402        }
403    }
404}
405
406fn block_by_id(function: &NativeAbiFunction, id: BlockId) -> NativeAbiEvalResult<&NativeAbiBlock> {
407    function
408        .blocks
409        .iter()
410        .find(|block| block.id == id)
411        .ok_or(NativeAbiEvalError::MissingBlock { id })
412}
413
414fn function_by_name<'a>(
415    module: &'a NativeAbiModule,
416    name: &str,
417) -> NativeAbiEvalResult<&'a NativeAbiFunction> {
418    module
419        .functions
420        .iter()
421        .chain(&module.roots)
422        .find(|function| function.name == name)
423        .ok_or_else(|| NativeAbiEvalError::MissingFunction {
424            name: name.to_string(),
425        })
426}
427
428fn assign_block_args(
429    values: &mut Vec<Option<NativeAbiRuntimeValue>>,
430    block: &NativeAbiBlock,
431    args: Vec<NativeAbiRuntimeValue>,
432) -> NativeAbiEvalResult<()> {
433    if block.params.len() != args.len() {
434        return Err(NativeAbiEvalError::BlockArgumentMismatch {
435            id: block.id,
436            expected: block.params.len(),
437            actual: args.len(),
438        });
439    }
440    for (param, value) in block.params.iter().copied().zip(args) {
441        write_value(values, param, value);
442    }
443    Ok(())
444}
445
446fn write_value(
447    values: &mut Vec<Option<NativeAbiRuntimeValue>>,
448    id: ValueId,
449    value: NativeAbiRuntimeValue,
450) {
451    if values.len() <= id.0 {
452        values.resize_with(id.0 + 1, || None);
453    }
454    values[id.0] = Some(value);
455}
456
457fn read_value(
458    values: &[Option<NativeAbiRuntimeValue>],
459    id: ValueId,
460) -> NativeAbiEvalResult<NativeAbiRuntimeValue> {
461    values
462        .get(id.0)
463        .and_then(Clone::clone)
464        .ok_or(NativeAbiEvalError::MissingValue { id })
465}
466
467fn read_plain_value(
468    values: &[Option<NativeAbiRuntimeValue>],
469    id: ValueId,
470) -> NativeAbiEvalResult<runtime::VmValue> {
471    into_plain_value(id, read_value(values, id)?)
472}
473
474fn read_closure(
475    values: &[Option<NativeAbiRuntimeValue>],
476    id: ValueId,
477) -> NativeAbiEvalResult<NativeAbiClosureValue> {
478    match read_value(values, id)? {
479        NativeAbiRuntimeValue::Closure(value) => Ok(value),
480        NativeAbiRuntimeValue::Plain(_) => Err(NativeAbiEvalError::ExpectedClosure { id }),
481    }
482}
483
484fn into_plain_value(
485    id: ValueId,
486    value: NativeAbiRuntimeValue,
487) -> NativeAbiEvalResult<runtime::VmValue> {
488    match value {
489        NativeAbiRuntimeValue::Plain(value) => Ok(value),
490        NativeAbiRuntimeValue::Closure(_) => Err(NativeAbiEvalError::ExpectedPlainValue { id }),
491    }
492}
493
494fn plain(value: runtime::VmValue) -> NativeAbiRuntimeValue {
495    NativeAbiRuntimeValue::Plain(value)
496}
497
498#[derive(Debug, Clone, PartialEq)]
499enum NativeAbiRuntimeValue {
500    Plain(runtime::VmValue),
501    Closure(NativeAbiClosureValue),
502}
503
504#[derive(Debug, Clone, PartialEq)]
505struct NativeAbiClosureValue {
506    target: String,
507    environment: Vec<NativeAbiRuntimeValue>,
508}
509
510fn eval_literal(lit: &NativeLiteral) -> runtime::VmValue {
511    match lit {
512        NativeLiteral::Int(value) => runtime::VmValue::Int(value.clone()),
513        NativeLiteral::Float(value) => runtime::VmValue::Float(value.clone()),
514        NativeLiteral::String(value) => {
515            runtime::VmValue::String(runtime::runtime::string_tree::StringTree::from_str(value))
516        }
517        NativeLiteral::Bool(value) => runtime::VmValue::Bool(*value),
518        NativeLiteral::Unit => runtime::VmValue::Unit,
519    }
520}
521
522fn bool_value(value: &runtime::VmValue) -> NativeAbiEvalResult<bool> {
523    match value {
524        runtime::VmValue::Bool(value) => Ok(*value),
525        value => Err(NativeAbiEvalError::NativeEval(
526            NativeEvalError::PrimitiveTypeMismatch {
527                op: typed_ir::PrimitiveOp::BoolNot,
528                value: value.clone(),
529            },
530        )),
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use yulang_typed_ir as typed_ir;
537
538    use crate::abi::{
539        NativeAbiBlock, NativeAbiFunction, NativeAbiModule, NativeAbiStmt,
540        lower_closure_module_to_abi,
541    };
542    use crate::closure::closure_convert_module;
543    use crate::control_ir::{
544        BlockId, NativeBlock, NativeFunction, NativeLiteral, NativeModule, NativeStmt,
545        NativeTerminator, ValueId,
546    };
547
548    use super::*;
549
550    #[test]
551    fn evaluates_direct_call() {
552        let module = NativeAbiModule {
553            functions: vec![add_function()],
554            roots: vec![NativeAbiFunction {
555                name: "root".to_string(),
556                params: Vec::new(),
557                environment_slots: 0,
558                blocks: vec![NativeAbiBlock {
559                    id: BlockId(0),
560                    params: Vec::new(),
561                    stmts: vec![
562                        NativeAbiStmt::Literal {
563                            dest: ValueId(0),
564                            literal: NativeLiteral::Int("20".to_string()),
565                        },
566                        NativeAbiStmt::Literal {
567                            dest: ValueId(1),
568                            literal: NativeLiteral::Int("22".to_string()),
569                        },
570                        NativeAbiStmt::DirectCall {
571                            dest: ValueId(2),
572                            target: "add".to_string(),
573                            args: vec![ValueId(0), ValueId(1)],
574                        },
575                    ],
576                    terminator: NativeTerminator::Return(ValueId(2)),
577                }],
578            }],
579        };
580
581        assert_eq!(
582            eval_abi_module(&module).expect("evaluated"),
583            vec![runtime::VmValue::Int("42".to_string())]
584        );
585    }
586
587    #[test]
588    fn evaluates_closure_environment_and_indirect_call() {
589        let module = NativeAbiModule {
590            functions: vec![add_capture_function()],
591            roots: vec![NativeAbiFunction {
592                name: "root".to_string(),
593                params: Vec::new(),
594                environment_slots: 0,
595                blocks: vec![NativeAbiBlock {
596                    id: BlockId(0),
597                    params: Vec::new(),
598                    stmts: vec![
599                        NativeAbiStmt::Literal {
600                            dest: ValueId(0),
601                            literal: NativeLiteral::Int("10".to_string()),
602                        },
603                        NativeAbiStmt::Literal {
604                            dest: ValueId(1),
605                            literal: NativeLiteral::Int("32".to_string()),
606                        },
607                        NativeAbiStmt::AllocateClosure {
608                            dest: ValueId(2),
609                            target: "add_capture".to_string(),
610                            environment: vec![ValueId(0)],
611                        },
612                        NativeAbiStmt::IndirectClosureCall {
613                            dest: ValueId(3),
614                            callee: ValueId(2),
615                            args: vec![ValueId(1)],
616                        },
617                    ],
618                    terminator: NativeTerminator::Return(ValueId(3)),
619                }],
620            }],
621        };
622
623        assert_eq!(
624            eval_abi_module(&module).expect("evaluated"),
625            vec![runtime::VmValue::Int("42".to_string())]
626        );
627    }
628
629    #[test]
630    fn evaluates_lowered_closure_call_abi() {
631        let native = NativeModule {
632            functions: vec![NativeFunction {
633                name: "add_capture".to_string(),
634                captures: vec![ValueId(0)],
635                params: vec![ValueId(0), ValueId(1)],
636                blocks: vec![NativeBlock {
637                    id: BlockId(0),
638                    params: vec![ValueId(0), ValueId(1)],
639                    stmts: vec![NativeStmt::Primitive {
640                        dest: ValueId(2),
641                        op: typed_ir::PrimitiveOp::IntAdd,
642                        args: vec![ValueId(0), ValueId(1)],
643                    }],
644                    terminator: NativeTerminator::Return(ValueId(2)),
645                }],
646            }],
647            roots: vec![NativeFunction {
648                name: "root".to_string(),
649                captures: Vec::new(),
650                params: Vec::new(),
651                blocks: vec![NativeBlock {
652                    id: BlockId(0),
653                    params: Vec::new(),
654                    stmts: vec![
655                        NativeStmt::Literal {
656                            dest: ValueId(0),
657                            literal: NativeLiteral::Int("10".to_string()),
658                        },
659                        NativeStmt::Literal {
660                            dest: ValueId(1),
661                            literal: NativeLiteral::Int("32".to_string()),
662                        },
663                        NativeStmt::MakeClosure {
664                            dest: ValueId(2),
665                            target: "add_capture".to_string(),
666                            captures: vec![ValueId(0)],
667                        },
668                        NativeStmt::ClosureCall {
669                            dest: ValueId(3),
670                            callee: ValueId(2),
671                            args: vec![ValueId(1)],
672                        },
673                    ],
674                    terminator: NativeTerminator::Return(ValueId(3)),
675                }],
676            }],
677        };
678        let abi = lower_closure_module_to_abi(&closure_convert_module(&native));
679
680        assert_eq!(
681            eval_abi_module(&abi).expect("evaluated"),
682            vec![runtime::VmValue::Int("42".to_string())]
683        );
684    }
685
686    fn add_function() -> NativeAbiFunction {
687        NativeAbiFunction {
688            name: "add".to_string(),
689            params: vec![ValueId(0), ValueId(1)],
690            environment_slots: 0,
691            blocks: vec![NativeAbiBlock {
692                id: BlockId(0),
693                params: vec![ValueId(0), ValueId(1)],
694                stmts: vec![NativeAbiStmt::Primitive {
695                    dest: ValueId(2),
696                    op: typed_ir::PrimitiveOp::IntAdd,
697                    args: vec![ValueId(0), ValueId(1)],
698                }],
699                terminator: NativeTerminator::Return(ValueId(2)),
700            }],
701        }
702    }
703
704    fn add_capture_function() -> NativeAbiFunction {
705        NativeAbiFunction {
706            name: "add_capture".to_string(),
707            params: vec![ValueId(1)],
708            environment_slots: 1,
709            blocks: vec![NativeAbiBlock {
710                id: BlockId(0),
711                params: vec![ValueId(1)],
712                stmts: vec![
713                    NativeAbiStmt::LoadEnv {
714                        dest: ValueId(0),
715                        slot: 0,
716                    },
717                    NativeAbiStmt::Primitive {
718                        dest: ValueId(2),
719                        op: typed_ir::PrimitiveOp::IntAdd,
720                        args: vec![ValueId(0), ValueId(1)],
721                    },
722                ],
723                terminator: NativeTerminator::Return(ValueId(2)),
724            }],
725        }
726    }
727}