Skip to main content

vihaco_stdlib/program/
lower.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4//! Instruction-owned program lowering, separate from the dialect definitions.
5
6use super::{
7    operand::{StringOperand, Target},
8    program,
9};
10
11use vihaco_abi::{Lower, Syntax};
12use vihaco_module::loader::MetadataLoweringContext;
13
14/// Resolve labels in the function currently being lowered.
15///
16/// The loader prepares label addresses before lowering so forward references
17/// work. Addresses count executable instructions, excluding label declarations.
18pub trait LabelLookup {
19    fn label_address(&self, name: &str) -> eyre::Result<u32>;
20}
21
22/// Resolve function names in the section currently being lowered.
23///
24/// Calls need an executable address; reference constants need a function-table
25/// index. These are separate values even when a program happens to make them equal.
26pub trait FunctionLookup {
27    fn function_address(&self, name: &str) -> eyre::Result<u32>;
28    fn function_index(&self, name: &str) -> eyre::Result<u32>;
29}
30
31/// Intern decoded string contents in the current section's string table.
32///
33/// Equal contents must return the same index, including contents already in the
34/// table. Implementations report an error if the table cannot represent an index.
35pub trait StringIntern {
36    fn intern_string(&mut self, value: &str) -> eyre::Result<u32>;
37}
38
39impl LabelLookup for MetadataLoweringContext {
40    fn label_address(&self, name: &str) -> eyre::Result<u32> {
41        MetadataLoweringContext::label_address(self, name)
42    }
43}
44
45impl FunctionLookup for MetadataLoweringContext {
46    fn function_address(&self, name: &str) -> eyre::Result<u32> {
47        MetadataLoweringContext::function_address(self, name)
48    }
49
50    fn function_index(&self, name: &str) -> eyre::Result<u32> {
51        MetadataLoweringContext::function_index(self, name)
52    }
53}
54
55impl StringIntern for MetadataLoweringContext {
56    fn intern_string(&mut self, value: &str) -> eyre::Result<u32> {
57        self.string_index(value)
58    }
59}
60
61fn label_target<C: LabelLookup + ?Sized>(target: Target, context: &C) -> eyre::Result<u32> {
62    match target {
63        Target::Resolved(address) => Ok(address),
64        Target::Symbol(name) => context.label_address(name.as_str()),
65    }
66}
67
68impl<C: LabelLookup + ?Sized> Lower<C> for program::Branch<Syntax> {
69    type Runtime = program::Branch;
70    fn lower(self, context: &mut C) -> eyre::Result<Self::Runtime> {
71        Ok(program::Branch(label_target(self.0, context)?))
72    }
73}
74
75impl<C: LabelLookup + ?Sized> Lower<C> for program::ConditionalBranch<Syntax> {
76    type Runtime = program::ConditionalBranch;
77    fn lower(self, context: &mut C) -> eyre::Result<Self::Runtime> {
78        Ok(program::ConditionalBranch(
79            label_target(self.0, context)?,
80            label_target(self.1, context)?,
81        ))
82    }
83}
84
85impl<C: FunctionLookup + ?Sized> Lower<C> for program::Call<Syntax> {
86    type Runtime = program::Call;
87    fn lower(self, context: &mut C) -> eyre::Result<Self::Runtime> {
88        let address = match self.1 {
89            Target::Resolved(address) => address,
90            Target::Symbol(name) => context.function_address(name.as_str())?,
91        };
92        Ok(program::Call(self.0, address))
93    }
94}
95
96impl<C: FunctionLookup + ?Sized> Lower<C> for program::ConstFunctionRef<Syntax> {
97    type Runtime = program::ConstFunctionRef;
98    fn lower(self, context: &mut C) -> eyre::Result<Self::Runtime> {
99        let index = match self.0 {
100            Target::Resolved(index) => index,
101            Target::Symbol(name) => context.function_index(name.as_str())?,
102        };
103        Ok(program::ConstFunctionRef(
104            vihaco_abi::word::encode_function_ref(index),
105        ))
106    }
107}
108
109impl<C: StringIntern + ?Sized> Lower<C> for program::ConstString<Syntax> {
110    type Runtime = program::ConstString;
111    fn lower(self, context: &mut C) -> eyre::Result<Self::Runtime> {
112        let index = match self.0 {
113            StringOperand::Resolved(index) => index,
114            StringOperand::Literal(value) => context.intern_string(value.as_str())?,
115        };
116        Ok(program::ConstString(vihaco_abi::word::encode_string_id(
117            index,
118        )))
119    }
120}
121
122// Literal encoding is instruction-owned lowering and needs no loader capabilities.
123macro_rules! lower_literal {
124    ($($instruction:ident => $convert:expr),* $(,)?) => {$(
125        impl<C: ?Sized> vihaco_abi::Lower<C> for program::$instruction<vihaco_abi::Syntax> {
126            type Runtime = program::$instruction;
127            fn lower(self, _: &mut C) -> eyre::Result<Self::Runtime> {
128                Ok(program::$instruction(($convert)(self.0)))
129            }
130        }
131    )*};
132}
133
134lower_literal! {
135    ConstI32 => vihaco_abi::word::Word::from,
136    ConstI64 => vihaco_abi::word::Word::from,
137    ConstF32 => vihaco_abi::word::Word::from,
138    ConstF64 => vihaco_abi::word::Word::from,
139    ConstU32 => vihaco_abi::word::encode_u32,
140    ConstBool => vihaco_abi::word::encode_bool,
141    ConstHeapRef => vihaco_abi::word::encode_heap_ref,
142}
143
144// String interning belongs to module construction, before immutable loading.
145impl<I, V, Ty, Info> StringIntern for vihaco_module::module::LocalModule<I, V, Ty, Info> {
146    fn intern_string(&mut self, value: &str) -> eyre::Result<u32> {
147        if let Some(index) = self.strings.iter().position(|existing| existing == value) {
148            return Ok(u32::try_from(index)?);
149        }
150        let index = u32::try_from(self.strings.len())?;
151        self.strings.push(value.to_owned());
152        Ok(index)
153    }
154}