Skip to main content

ironwork_rt/vm/
mod.rs

1//! The VM (codegen-runtime.md §14, step 3): runs a lowered program, and the programs it CALLs, over
2//! the run unit, calling the semantics library the interpreter calls with the `Loc`s and values the
3//! interpreter would pass, so the two agree by construction. Control follows lir.md §8: frames and
4//! return points per activation (assumption C99), a dispatch loop over blocks, and Rust recursion
5//! for CALL and for a procedure a statement runs, bounded by `MAX_DEPTH` as the interpreter is.
6//! What this slice does not run stops the run as [`Halt::Unimplemented`], never as an abend.
7
8mod arith;
9mod call;
10mod cond;
11mod flow;
12mod ops;
13mod place;
14mod value;
15
16use crate::abend::{Abend, Ending};
17use crate::lir::{AbendId, Block, Collating, DebugId, Frame, FrameKind, MovePlan, Op, PlaceId, Program, Returns, StorePlan, SymId, UpDown};
18use crate::picture::Sym;
19use crate::store::ProgramFacts;
20use crate::unit::{Loader, RunUnit};
21use crate::vocab::{Figurative, Pos};
22use numeric::Options;
23use std::collections::{BTreeMap, HashMap};
24use std::rc::Rc;
25use zarch::ebcdic::{self, CodePage, Collation};
26
27pub(crate) use flow::Arrival;
28
29type R<T> = Result<T, Halt>;
30
31/// Why a VM run stopped other than by ending.
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub enum Halt {
34    Abend(Abend),
35    /// A construct this slice of the VM does not run yet, named.
36    Unimplemented(String),
37}
38
39impl From<Abend> for Halt {
40    fn from(a: Abend) -> Self {
41        Self::Abend(a)
42    }
43}
44
45fn not_yet(what: impl Into<String>) -> Halt {
46    Halt::Unimplemented(what.into())
47}
48
49/// A program as the VM's run unit holds it: its LIR, or why it did not lower, with its ENTRY names,
50/// the file count and storage size the run unit gives it whether or not it lowered, and the
51/// PROGRAM-IDs of the programs it contains, which a CANCEL of it reaches.
52pub struct Code {
53    lowered: Result<Lowered, String>,
54    entries: Vec<String>,
55    files: usize,
56    size: usize,
57    nested: Vec<String>,
58}
59
60/// A lowered program with what the VM works out from it once: its collating sequence as the
61/// semantics library takes it, the paragraph each entry block begins, and the name a TRUNC(OPT)
62/// report gives each binary receiver.
63struct Lowered {
64    program: Program,
65    collation: Collation,
66    ordinals: Vec<u8>,
67    high_value: u8,
68    low_value: u8,
69    entry_of: Vec<Option<u32>>,
70    receivers: HashMap<PlaceId, SymId>,
71}
72
73impl Code {
74    pub fn new(program: Result<Program, String>, entries: Vec<String>, files: usize, size: usize, nested: Vec<String>) -> Self {
75        Self { lowered: program.map(Lowered::new), entries, files, size, nested }
76    }
77
78    pub fn program(&self) -> Option<&Program> {
79        self.lowered.as_ref().ok().map(|l| &l.program)
80    }
81
82    /// Which ENTRY statement, in source order, has this name.
83    pub fn entry(&self, name: &str) -> Option<usize> {
84        self.entries.iter().position(|e| e == name)
85    }
86
87    pub fn shape(&self) -> (usize, usize) {
88        (self.files, self.size)
89    }
90}
91
92impl Lowered {
93    fn new(program: Program) -> Self {
94        let (collation, ordinals, high_value, low_value) = match &program.options.collating {
95            Collating::Native => (Collation::Native, (0..=255).collect(), ebcdic::HIGH_VALUE, ebcdic::LOW_VALUE),
96            Collating::Sequence(s) => {
97                let weights = Box::new(std::array::from_fn(|b| u16::from(s.positions[b])));
98                (Collation::Weights(weights), s.characters.clone(), s.high_value, s.low_value)
99            }
100        };
101        let mut entry_of = vec![None; program.blocks.len()];
102        for (i, paragraph) in program.paragraphs.iter().enumerate() {
103            if let Some(slot) = entry_of.get_mut(paragraph.entry as usize) {
104                *slot = Some(i as u32);
105            }
106        }
107        let receivers = receivers(&program);
108        Self { program, collation, ordinals, high_value, low_value, entry_of, receivers }
109    }
110}
111
112/// Each place a binary store plan names with the item the TRUNC(OPT) report gives it, which differs
113/// from the place's own name where a condition-name reaches its conditional variable.
114fn receivers(p: &Program) -> HashMap<PlaceId, SymId> {
115    let mut names = HashMap::new();
116    let mut store = |place: PlaceId, plan: &StorePlan| {
117        if let StorePlan::Binary { name, .. } = plan {
118            names.entry(place).or_insert(*name);
119        }
120    };
121    let moved = |plan: &MovePlan| match plan {
122        MovePlan::Numeric { store, .. } => Some(*store),
123        _ => None,
124    };
125    for a in &p.plans.arith {
126        a.steps.iter().for_each(|s| store(s.target, &s.store));
127        a.remainder.iter().for_each(|r| store(r.target, &r.store));
128    }
129    for Block { ops, .. } in &p.blocks {
130        for op in ops {
131            match op {
132                Op::Move { to, plan, .. } | Op::Set { to, plan, .. } | Op::Accept { target: to, plan, .. } => moved(plan).iter().for_each(|s| store(*to, s)),
133                Op::Step { var, plan, .. } => store(*var, &plan.store),
134                Op::SetUpDown { targets, .. } => targets.iter().for_each(|(place, how)| {
135                    if let UpDown::Number(plan) = how {
136                        store(*place, &plan.store);
137                    }
138                }),
139                _ => {}
140            }
141        }
142    }
143    for s in &p.plans.string {
144        s.pointer.iter().for_each(|(place, plan)| store(*place, plan));
145    }
146    for u in &p.plans.unstring {
147        u.pointer.iter().for_each(|(place, plan)| store(*place, plan));
148        u.tallying.iter().for_each(|(place, plan)| store(*place, &plan.store));
149        for into in &u.into {
150            moved(&into.plan).iter().for_each(|s| store(into.target, s));
151            into.count.iter().for_each(|(place, plan)| store(*place, plan));
152        }
153    }
154    for i in &p.plans.inspect {
155        i.tallying.iter().filter_map(|t| t.counter.as_ref()).for_each(|(place, plan)| store(*place, &plan.store));
156    }
157    for s in &p.plans.search_all {
158        store(s.index, &s.store);
159    }
160    names
161}
162
163/// Runs program `me` of the run unit, `code`, as its first program, its PROCEDURE DIVISION USING
164/// items given `arguments`' addresses.
165pub fn run<L: Loader<Rc<Code>>>(code: &Code, me: usize, unit: &mut RunUnit<'_, Rc<Code>, L>, arguments: &[Option<usize>]) -> Result<Ending, Halt> {
166    let lowered = code.lowered.as_ref().map_err(|why| not_yet(format!("a program that does not lower ({why})")))?;
167    let mut vm = Vm::activation(lowered, me, unit, true)?;
168    for (&record, &address) in lowered.program.storage.using.iter().zip(arguments) {
169        vm.linkage[usize::from(record)] = address;
170    }
171    vm.run_from(None)
172}
173
174/// What the semantics library reads of the running program, answered from its LIR. A `Loc`'s
175/// item is the place it was evaluated from.
176#[derive(Clone, Copy)]
177struct Facts<'p> {
178    code: &'p Lowered,
179}
180
181impl ProgramFacts for Facts<'_> {
182    fn options(&self) -> Options {
183        self.code.program.options.options
184    }
185
186    fn page(&self) -> &'static CodePage {
187        self.code.program.options.options.code_page()
188    }
189
190    fn figurative(&self, f: Figurative) -> u8 {
191        match f {
192            Figurative::HighValue => self.code.high_value,
193            Figurative::LowValue => self.code.low_value,
194            Figurative::Space => ebcdic::SPACE,
195            Figurative::Zero => ebcdic::ZERO,
196            Figurative::Quote => self.options().quote.byte(),
197            Figurative::Null => 0,
198        }
199    }
200
201    fn collation(&self) -> &Collation {
202        &self.code.collation
203    }
204
205    fn ordinal(&self, byte: u8) -> u16 {
206        self.code.collation.weight(byte) + 1
207    }
208
209    fn character(&self, ordinal: i64) -> Option<u8> {
210        usize::try_from(ordinal).ok().and_then(|n| n.checked_sub(1)).and_then(|i| self.code.ordinals.get(i)).copied()
211    }
212
213    fn characters(&self) -> usize {
214        self.code.ordinals.len()
215    }
216
217    fn decimal_point(&self) -> char {
218        if self.code.program.options.decimal_point_comma { ',' } else { '.' }
219    }
220
221    fn edit(&self, edit: u32) -> (&[Sym], &str) {
222        let e = &self.code.program.edits[edit as usize];
223        (&e.syms, &e.currency)
224    }
225
226    fn scaling(&self, item: usize) -> u32 {
227        self.code.program.places.get(item).map_or(0, |p| p.scaling)
228    }
229
230    fn item_name(&self, item: usize) -> String {
231        let p = &self.code.program;
232        let name = match (u32::try_from(item).ok().and_then(|i| self.code.receivers.get(&i)), p.places.get(item)) {
233            (Some(&name), _) => name,
234            (None, Some(place)) => place.name,
235            (None, None) => return "RETURN-CODE".into(),
236        };
237        p.symbols[name as usize].clone()
238    }
239}
240
241/// One activation of one program: its storage, its LINKAGE addresses, its return points and
242/// frames, and the registers of the DEBUG option (lir.md §8.4, §9.10).
243struct Vm<'p, 'u, 'w, L: Loader<Rc<Code>>> {
244    code: &'p Lowered,
245    p: &'p Program,
246    me: usize,
247    base: usize,
248    local_base: usize,
249    linkage: Vec<Option<usize>>,
250    /// The run unit's first program, where EXIT PROGRAM does nothing.
251    main: bool,
252    returns: Returns,
253    segment: u8,
254    /// The line register, which DEBUG-LINE shows.
255    line: u32,
256    arrival: Arrival,
257    /// A debugging section is running.
258    debugging: bool,
259    /// Places being evaluated, inside which FUNCTION RANDOM is not run.
260    locating: u32,
261    /// What a callback of the semantics library stopped for, its error being an `Abend`.
262    pending: Option<String>,
263    unit: &'u mut RunUnit<'w, Rc<Code>, L>,
264}
265
266impl<'p, 'u, 'w, L: Loader<Rc<Code>>> Vm<'p, 'u, 'w, L> {
267    /// An activation of loaded program `me`, its storage as `Machine::activation` leaves it: fresh
268    /// on its first activation, after a CANCEL, and on every activation of an INITIAL program.
269    fn activation(code: &'p Lowered, me: usize, unit: &'u mut RunUnit<'w, Rc<Code>, L>, main: bool) -> R<Self> {
270        let p = &code.program;
271        let storage = &p.storage;
272        if !storage.local_image.is_empty() && (storage.init_abend.is_some() || !storage.init_reports.is_empty()) {
273            return Err(not_yet("VALUE initialization that reports or abends in a program with LOCAL-STORAGE"));
274        }
275        let (base, fresh) = unit.activate(me, p.initial);
276        let main_frame = Frame { id: 0, kind: FrameKind::Main, displaced: None, segment: 0, depth: unit.depth as u32, temps: Vec::new() };
277        let mut vm = Self {
278            code,
279            p,
280            me,
281            base,
282            local_base: 0,
283            linkage: vec![None; storage.linkage.len()],
284            main,
285            returns: Returns { armed: vec![None; p.paragraphs.len()], saved: BTreeMap::new(), frames: vec![main_frame], next_frame: 1 },
286            segment: 0,
287            line: 0,
288            arrival: Arrival::Start,
289            debugging: false,
290            locating: 0,
291            pending: None,
292            unit,
293        };
294        if !storage.local_image.is_empty() {
295            vm.local_base = vm.unit.push_temporary(&storage.local_image);
296        }
297        if fresh {
298            vm.unit.mem[base..base + storage.image.len()].copy_from_slice(&storage.image);
299            for &report in &storage.init_reports {
300                let _ = writeln!(vm.unit.err, "{}", vm.sym(report));
301            }
302            if let Some(abend) = storage.init_abend {
303                return Err(vm.abend(abend, None).into());
304            }
305            vm.unit.initialized(me);
306        }
307        Ok(vm)
308    }
309
310    fn facts(&self) -> Facts<'p> {
311        Facts { code: self.code }
312    }
313
314    fn sym(&self, id: SymId) -> &'p str {
315        &self.p.symbols[id as usize]
316    }
317
318    fn pos(&self, at: DebugId) -> Pos {
319        self.p.debug.positions[at as usize]
320    }
321
322    /// Abend `id` of the program's table, at its own position or else `at`.
323    fn abend(&self, id: AbendId, at: Option<DebugId>) -> Abend {
324        let text = &self.p.abends[id as usize];
325        let pos = text.at.or(at).map(|d| self.pos(d)).unwrap_or_default();
326        Abend { code: text.code.clone(), message: self.sym(text.message).to_owned(), pos, file: None }
327    }
328
329    /// A callback's result as the semantics library takes it: an `Unimplemented` is held in
330    /// `pending` behind an abend that [`Vm::settle`] turns back into it.
331    fn lift<T>(&mut self, result: R<T>, pos: Pos) -> Result<T, Abend> {
332        result.map_err(|halt| match halt {
333            Halt::Abend(a) => a,
334            Halt::Unimplemented(what) => {
335                self.pending.get_or_insert(what);
336                Abend::ironwork("the VM stopped inside a library call", pos)
337            }
338        })
339    }
340
341    /// A library call's result, with what a callback stopped for in place of its abend.
342    fn settle<T>(&mut self, result: Result<T, Abend>) -> R<T> {
343        match (result, self.pending.take()) {
344            (_, Some(what)) => Err(Halt::Unimplemented(what)),
345            (result, None) => result.map_err(Halt::Abend),
346        }
347    }
348}