Skip to main content

vihaco_module/
program_metadata.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4//! Standard program-header schema and section-local metadata validation.
5//!
6//! Every table is supplied by the header, even when empty. Array order defines
7//! table indices; names and files are explicit string-table indices. Addresses
8//! count decoded instructions, with an exclusive function end. Loading never
9//! interns strings, invents signatures, or infers an entrypoint.
10//!
11//! Constants use their type's ordinary `Parse` implementation. Standard `Value`
12//! constants use typed SST literals such as `u64 42` or `string 0`; `f32_bits`
13//! and `f64_bits` followed by unsigned integers preserve all IEEE-754 encodings.
14//! Custom extra records use the same delimited metadata derive as the header.
15
16use crate::module::{FunctionInfo, LabelInfo, LocalModule, NoInfo, SourceSymbolInfo};
17use vihaco_abi::{Type, Value};
18
19/// All non-code fields of a `LocalModule`, supplied explicitly by a section header.
20/// The ordinary metadata derive owns object parsing, including nested records.
21#[derive(Debug, Clone, PartialEq, vihaco_parser_derive::Parse)]
22#[syntax_class(metadata, head = "metadata", open = "{", close = "}")]
23pub struct ProgramMetadata<V = Value, Ty = Type, Info = NoInfo> {
24    pub functions: Vec<FunctionInfo<Ty>>,
25    pub labels: Vec<LabelInfo>,
26    pub constants: Vec<V>,
27    pub strings: Vec<String>,
28    pub main_function: Option<u32>,
29    pub source_symbols: Vec<SourceSymbolInfo>,
30    pub extra: Info,
31}
32
33fn check_index(index: u32, len: usize, what: &str) -> eyre::Result<()> {
34    eyre::ensure!(
35        (index as usize) < len,
36        "{what}: index {index} is outside table of length {len}"
37    );
38    Ok(())
39}
40
41impl<V, Ty, Info> ProgramMetadata<V, Ty, Info> {
42    /// Transform consumer-specific extra metadata while preserving every table.
43    pub fn map_extra<NewInfo>(
44        self,
45        map: impl FnOnce(Info) -> NewInfo,
46    ) -> ProgramMetadata<V, Ty, NewInfo> {
47        ProgramMetadata {
48            functions: self.functions,
49            labels: self.labels,
50            constants: self.constants,
51            strings: self.strings,
52            main_function: self.main_function,
53            source_symbols: self.source_symbols,
54            extra: map(self.extra),
55        }
56    }
57
58    /// Validate references and instruction ranges without altering supplied metadata.
59    pub fn validate(&self, instruction_count: usize) -> eyre::Result<()> {
60        eyre::ensure!(
61            u32::try_from(instruction_count).is_ok(),
62            "instruction stream exceeds u32 address space"
63        );
64        if let Some(main) = self.main_function {
65            check_index(main, self.functions.len(), "metadata.main_function")?;
66        }
67        for (index, function) in self.functions.iter().enumerate() {
68            let path = format!("metadata.functions[{index}]");
69            check_index(function.name, self.strings.len(), &format!("{path}.name"))?;
70            check_index(function.file, self.strings.len(), &format!("{path}.file"))?;
71            eyre::ensure!(
72                function.start_address <= function.end_address
73                    && function.end_address as usize <= instruction_count,
74                "{path}: invalid instruction range {}..{} for {instruction_count} instructions",
75                function.start_address,
76                function.end_address
77            );
78            eyre::ensure!(
79                function.local_count as usize >= function.signature.params.len(),
80                "{path}.local_count: fewer slots than parameters"
81            );
82            for parameter in &function.signature.params {
83                check_index(
84                    parameter.name,
85                    self.strings.len(),
86                    &format!("{path}.signature.params.name"),
87                )?;
88            }
89        }
90        for (index, label) in self.labels.iter().enumerate() {
91            check_index(
92                label.name,
93                self.strings.len(),
94                &format!("metadata.labels[{index}].name"),
95            )?;
96            eyre::ensure!(
97                label.address as usize <= instruction_count,
98                "metadata.labels[{index}].address: outside instruction stream"
99            );
100        }
101        let mut symbols = std::collections::BTreeSet::new();
102        for symbol in &self.source_symbols {
103            eyre::ensure!(
104                symbols.insert(symbol.index),
105                "metadata.source_symbols: duplicate index {}",
106                symbol.index
107            );
108        }
109        Ok(())
110    }
111}
112
113impl<Ty, Info> ProgramMetadata<Value, Ty, Info> {
114    /// Assemble the standard value model, checking the supplied tables once.
115    /// Custom value models can assemble `LocalModule` directly after `validate`.
116    pub fn into_module<I>(self, code: Vec<I>) -> eyre::Result<LocalModule<I, Value, Ty, Info>> {
117        self.validate(code.len())?;
118        for (index, constant) in self.constants.iter().enumerate() {
119            match constant {
120                Value::String(id) => check_index(
121                    *id,
122                    self.strings.len(),
123                    &format!("metadata.constants[{index}] string"),
124                )?,
125                Value::FunctionRef(id) => check_index(
126                    *id,
127                    self.functions.len(),
128                    &format!("metadata.constants[{index}] function"),
129                )?,
130                _ => {}
131            }
132        }
133        Ok(LocalModule {
134            code,
135            functions: self.functions,
136            labels: self.labels,
137            constants: self.constants,
138            strings: self.strings,
139            main_function: self.main_function,
140            source_symbols: self.source_symbols,
141            extra: self.extra,
142        })
143    }
144}