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}