Skip to main content

runmat_vm/bytecode/
region.rs

1use runmat_types::{ProgramPointId, RegionContract, RegionId};
2use serde::{Deserialize, Serialize};
3
4use super::program::Bytecode;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(deny_unknown_fields)]
8pub struct BytecodeRegionBoundary {
9    pub point: ProgramPointId,
10    pub pc: usize,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(deny_unknown_fields)]
15pub struct BytecodeRegion {
16    pub id: RegionId,
17    pub entry: BytecodeRegionBoundary,
18    pub exits: Vec<BytecodeRegionBoundary>,
19}
20
21impl Bytecode {
22    /// Transactionally install every analyzed region that resolves to exact
23    /// empty-stack VM boundaries in this bytecode product. Regions are
24    /// optional optimization candidates; a composed or transformed product
25    /// that cannot represent one must execute normally without it.
26    pub fn install_regions(&mut self, contracts: &[RegionContract]) -> Result<(), String> {
27        let mut contracts = contracts.iter().collect::<Vec<_>>();
28        contracts.sort_by_key(|contract| contract.id);
29        if contracts.windows(2).any(|pair| pair[0].id == pair[1].id) {
30            return Err("region inventory contains duplicate identities".to_string());
31        }
32        let mut mapped = Vec::with_capacity(contracts.len());
33        for contract in contracts {
34            contract
35                .validate()
36                .map_err(|error| format!("{}: {}", error.path, error.message))?;
37            let function = runmat_hir::FunctionId(
38                usize::try_from(contract.id.function.0)
39                    .map_err(|_| "region function identity exceeds this target".to_string())?,
40            );
41            // Region contracts describe this executable's MIR assembly. Prefer
42            // its immutable layout over the composed session registry: a
43            // persisted function may legitimately reuse a unit-local function
44            // identity while carrying resume points for a different body.
45            let layout_points = self
46                .layout
47                .as_ref()
48                .and_then(|layout| layout.functions.get(&function))
49                .map(|layout| &layout.resume_points);
50            let registry_points = self
51                .function_registry
52                .functions
53                .get(&function)
54                .map(|function| &function.resume_points);
55            let Some(points) = layout_points
56                .filter(|points| contains_region_boundaries(contract, points))
57                .or_else(|| {
58                    registry_points.filter(|points| contains_region_boundaries(contract, points))
59                })
60            else {
61                continue;
62            };
63            let region = map_bytecode_region(contract, points)?;
64            mapped.push((function, region));
65        }
66
67        self.regions = mapped.iter().map(|(_, region)| region.clone()).collect();
68        for function in self.function_registry.functions.values_mut() {
69            function.regions.clear();
70        }
71        for function in self.bound_functions.values_mut() {
72            function.regions.clear();
73        }
74        for (function, region) in mapped {
75            if let Some(bytecode) = self.function_registry.functions.get_mut(&function) {
76                bytecode.regions.push(region.clone());
77            }
78            if let Some(bytecode) = self.bound_functions.get_mut(&function) {
79                bytecode.regions.push(region);
80            }
81        }
82        Ok(())
83    }
84}
85
86fn contains_region_boundaries(
87    contract: &RegionContract,
88    points: &std::collections::BTreeMap<ProgramPointId, usize>,
89) -> bool {
90    points.contains_key(&contract.entry)
91        && contract.exits.iter().all(|exit| points.contains_key(exit))
92}
93
94fn map_bytecode_region(
95    contract: &RegionContract,
96    points: &std::collections::BTreeMap<ProgramPointId, usize>,
97) -> Result<BytecodeRegion, String> {
98    let entry = BytecodeRegionBoundary {
99        point: contract.entry,
100        pc: points.get(&contract.entry).copied().ok_or_else(|| {
101            format!(
102                "region {:?} entry {:?} has no bytecode boundary",
103                contract.id, contract.entry
104            )
105        })?,
106    };
107    let exits = contract
108        .exits
109        .iter()
110        .map(|point| {
111            Ok(BytecodeRegionBoundary {
112                point: *point,
113                pc: points.get(point).copied().ok_or_else(|| {
114                    format!(
115                        "region {:?} exit {:?} has no bytecode boundary",
116                        contract.id, point
117                    )
118                })?,
119            })
120        })
121        .collect::<Result<Vec<_>, String>>()?;
122    Ok(BytecodeRegion {
123        id: contract.id,
124        entry,
125        exits,
126    })
127}