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 and functions
2//! it calls, over the run unit, calling the semantics library the interpreter calls with the `Loc`s
3//! and values the interpreter would pass, so the two agree by construction. Control follows lir.md
4//! §8: frames and return points per activation (assumption C99), a dispatch loop over blocks, and
5//! Rust recursion for CALL, a user-defined function and a procedure a statement runs, bounded by
6//! `MAX_DEPTH` as the interpreter is.
7//! What this slice does not run stops the run as [`Halt::Unimplemented`], never as an abend.
8
9mod arith;
10mod call;
11mod cics;
12mod cond;
13mod files;
14mod flow;
15mod function;
16mod markup;
17mod oo;
18mod ops;
19mod place;
20mod report;
21mod scope;
22mod sort;
23mod sql;
24mod value;
25
26use crate::abend::{Abend, Ending};
27use crate::cics::Handlers;
28use crate::lir::{AbendId, Base, Block, CallPlan, Collating, DebugId, Frame, FrameKind, MovePlan, Op, Place, PlaceId, Program, ReturnPoint, Returns, StorePlan, SymId, UpDown};
29use crate::oo::Running;
30use crate::picture::Sym;
31use crate::sql::Ran;
32use crate::storage::Val;
33use crate::store::{LaxRedefinition, ProgramFacts};
34use crate::unit::{Loader, RunUnit};
35use crate::vocab::{Figurative, Pos};
36use numeric::Options;
37use std::collections::{BTreeMap, HashMap};
38use std::rc::Rc;
39use zarch::ebcdic::{self, CodePage, Collation};
40
41pub use cics::run_task;
42pub(crate) use flow::Arrival;
43
44type R<T> = Result<T, Stop>;
45
46/// Why a VM run stopped other than by ending.
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub enum Halt {
49    Abend(Abend),
50    /// A construct this slice of the VM does not run yet, named.
51    Unimplemented(String),
52}
53
54impl From<Abend> for Halt {
55    fn from(a: Abend) -> Self {
56        Self::Abend(a)
57    }
58}
59
60/// A `Halt` in a box, as the VM's own results carry it: an `R` of a small value is then small.
61#[derive(Debug)]
62struct Stop(Box<Halt>);
63
64impl Stop {
65    fn halt(self) -> Halt {
66        *self.0
67    }
68}
69
70impl From<Halt> for Stop {
71    fn from(halt: Halt) -> Self {
72        Self(Box::new(halt))
73    }
74}
75
76impl From<Abend> for Stop {
77    fn from(a: Abend) -> Self {
78        Self(Box::new(Halt::Abend(a)))
79    }
80}
81
82impl From<Stop> for Halt {
83    fn from(stop: Stop) -> Self {
84        stop.halt()
85    }
86}
87
88fn not_yet(what: impl Into<String>) -> Stop {
89    Halt::Unimplemented(what.into()).into()
90}
91
92/// A program as the VM's run unit holds it: its LIR, or why it did not lower, with its ENTRY names,
93/// the file count and storage size the run unit gives it whether or not it lowered, the
94/// PROGRAM-IDs of the programs it contains, which a CANCEL of it reaches, for a method the
95/// `Class.method` a dump lists it by, and the statement kinds, usages and options it holds.
96pub struct Code {
97    lowered: Result<Lowered, String>,
98    entries: Vec<String>,
99    files: usize,
100    size: usize,
101    nested: Vec<String>,
102    method: Option<String>,
103    facts: numeric::governs::Facts,
104}
105
106/// A lowered program with what the VM works out from it once: its collating sequence as the
107/// semantics library takes it, the paragraph each entry block begins, the name a TRUNC(OPT)
108/// report gives each binary receiver, and under NUMCHECK the name its message gives each
109/// conditional variable (`cond::conditional_variables`).
110struct Lowered {
111    program: Program,
112    collation: Collation,
113    ordinals: Vec<u8>,
114    high_value: u8,
115    low_value: u8,
116    entry_of: Vec<Option<u32>>,
117    receivers: HashMap<PlaceId, SymId>,
118    variables: HashMap<PlaceId, Option<String>>,
119    pure: Vec<bool>,
120    quick: Vec<Option<place::Quick>>,
121    /// Each place `loc_with` takes straight from its base (`place::direct`).
122    direct: Vec<bool>,
123    /// Each place `static_number` reads: a static one of a numeric kind.
124    numbers: Vec<bool>,
125    /// Each constant's value as `operand_number` takes it where it is a number that fits an `i64`.
126    literals: Vec<Option<(i64, numeric::precision::Places)>>,
127}
128
129impl Code {
130    pub fn new(program: Result<Program, String>, entries: Vec<String>, files: usize, size: usize, nested: Vec<String>, method: Option<String>, facts: numeric::governs::Facts) -> Self {
131        Self { lowered: program.map(Lowered::new), entries, files, size, nested, method, facts }
132    }
133
134    pub fn facts(&self) -> numeric::governs::Facts {
135        self.facts
136    }
137
138    pub fn program(&self) -> Option<&Program> {
139        self.lowered.as_ref().ok().map(|l| &l.program)
140    }
141
142    /// Which ENTRY statement, in source order, has this name.
143    pub fn entry(&self, name: &str) -> Option<usize> {
144        self.entries.iter().position(|e| e == name)
145    }
146
147    pub fn shape(&self) -> (usize, usize) {
148        (self.files, self.size)
149    }
150
151    pub fn nested(&self) -> &[String] {
152        &self.nested
153    }
154}
155
156impl Lowered {
157    fn new(program: Program) -> Self {
158        let (collation, ordinals, high_value, low_value) = match &program.options.collating {
159            Collating::Native => (Collation::Native, (0..=255).collect(), ebcdic::HIGH_VALUE, ebcdic::LOW_VALUE),
160            Collating::Sequence(s) => {
161                let weights = Box::new(std::array::from_fn(|b| u16::from(s.positions[b])));
162                (Collation::Weights(weights), s.characters.clone(), s.high_value, s.low_value)
163            }
164        };
165        let mut entry_of = vec![None; program.blocks.len()];
166        for (i, paragraph) in program.paragraphs.iter().enumerate() {
167            if let Some(slot) = entry_of.get_mut(paragraph.entry as usize) {
168                *slot = Some(i as u32);
169            }
170        }
171        let receivers = receivers(&program);
172        let variables = if program.options.options.numcheck.is_some() { cond::conditional_variables(&program) } else { HashMap::new() };
173        let pure = place::pure_places(&program);
174        let quick = place::quick_places(&program);
175        let literals = program.consts.iter().map(value::const_number).collect();
176        let numbers = program.places.iter().map(place::number_item).collect();
177        let direct = program.places.iter().map(place::direct).collect();
178        Self { program, collation, ordinals, high_value, low_value, entry_of, receivers, variables, pure, quick, direct, numbers, literals }
179    }
180}
181
182/// Each place a binary store plan names with the item the TRUNC(OPT) report gives it, which differs
183/// from the place's own name where a condition-name reaches its conditional variable.
184fn receivers(p: &Program) -> HashMap<PlaceId, SymId> {
185    let mut names = HashMap::new();
186    let mut store = |place: PlaceId, plan: &StorePlan| {
187        if let StorePlan::Binary { name, .. } = plan {
188            names.entry(place).or_insert(*name);
189        }
190    };
191    let moved = |plan: &MovePlan| match plan {
192        MovePlan::Numeric { store, .. } => Some(*store),
193        _ => None,
194    };
195    for a in &p.plans.arith {
196        a.steps.iter().for_each(|s| store(s.target, &s.store));
197        a.remainder.iter().for_each(|r| store(r.target, &r.store));
198    }
199    for Block { ops, .. } in &p.blocks {
200        for op in ops {
201            match op {
202                Op::Move { to, plan, .. } | Op::Set { to, plan, .. } | Op::Accept { target: to, plan, .. } => moved(plan).iter().for_each(|s| store(*to, s)),
203                Op::Step { var, plan, .. } => store(*var, &plan.store),
204                Op::SetUpDown { targets, .. } => targets.iter().for_each(|(place, how)| {
205                    if let UpDown::Number(plan) = how {
206                        store(*place, &plan.store);
207                    }
208                }),
209                _ => {}
210            }
211        }
212    }
213    for s in &p.plans.string {
214        s.pointer.iter().for_each(|(place, plan)| store(*place, plan));
215    }
216    for u in &p.plans.unstring {
217        u.pointer.iter().for_each(|(place, plan)| store(*place, plan));
218        u.tallying.iter().for_each(|(place, plan)| store(*place, &plan.store));
219        for into in &u.into {
220            moved(&into.plan).iter().for_each(|s| store(into.target, s));
221            into.count.iter().for_each(|(place, plan)| store(*place, plan));
222        }
223    }
224    for i in &p.plans.inspect {
225        i.tallying.iter().filter_map(|t| t.counter.as_ref()).for_each(|(place, plan)| store(*place, &plan.store));
226    }
227    for s in &p.plans.search_all {
228        store(s.index, &s.store);
229    }
230    names
231}
232
233/// Runs program `me` of the run unit, `code`, as its first program, its PROCEDURE DIVISION USING
234/// items given `arguments`' addresses; `main` as a run unit's main program, where EXIT PROGRAM
235/// does nothing, or else as a subprogram a caller passed them to.
236pub fn run<L: Loader<Rc<Code>>>(code: &Code, me: usize, unit: &mut RunUnit<'_, Rc<Code>, L>, arguments: &[Option<usize>], main: bool) -> Result<Ending, Halt> {
237    let lowered = code.lowered.as_ref().map_err(|why| not_yet(format!("a program that does not lower ({why})")))?;
238    let mut vm = Vm::activation(lowered, me, unit, main)?;
239    for (&record, &address) in lowered.program.storage.using.iter().zip(arguments) {
240        vm.linkage[usize::from(record)] = address;
241    }
242    Ok(vm.run_from(None)?)
243}
244
245/// What the semantics library reads of the running program, answered from its LIR. A `Loc`'s
246/// item is the place it was evaluated from.
247#[derive(Clone, Copy)]
248struct Facts<'p> {
249    code: &'p Lowered,
250}
251
252impl ProgramFacts for Facts<'_> {
253    fn options(&self) -> Options {
254        self.code.program.options.options
255    }
256
257    fn page(&self) -> &'static CodePage {
258        self.code.program.options.options.code_page()
259    }
260
261    fn figurative(&self, f: Figurative) -> u8 {
262        match f {
263            Figurative::HighValue => self.code.high_value,
264            Figurative::LowValue => self.code.low_value,
265            Figurative::Space => ebcdic::SPACE,
266            Figurative::Zero => ebcdic::ZERO,
267            Figurative::Quote => self.options().quote.byte(),
268            Figurative::Null => 0,
269        }
270    }
271
272    fn collation(&self) -> &Collation {
273        &self.code.collation
274    }
275
276    fn ordinal(&self, byte: u8) -> u16 {
277        self.code.collation.weight(byte) + 1
278    }
279
280    fn character(&self, ordinal: i64) -> Option<u8> {
281        usize::try_from(ordinal).ok().and_then(|n| n.checked_sub(1)).and_then(|i| self.code.ordinals.get(i)).copied()
282    }
283
284    fn characters(&self) -> usize {
285        self.code.ordinals.len()
286    }
287
288    fn decimal_point(&self) -> char {
289        if self.code.program.options.decimal_point_comma { ',' } else { '.' }
290    }
291
292    fn edit(&self, edit: u32) -> (&[Sym], &str) {
293        let e = &self.code.program.edits[edit as usize];
294        (&e.syms, &e.currency)
295    }
296
297    fn scaling(&self, item: usize) -> u32 {
298        self.code.program.places.get(item).map_or(0, |p| p.scaling)
299    }
300
301    /// The walker names an item by its `Loc`'s item, which an XML register's has none of.
302    fn item_name(&self, item: usize) -> String {
303        let p = &self.code.program;
304        let name = match (u32::try_from(item).ok().and_then(|i| self.code.receivers.get(&i)), p.places.get(item)) {
305            (Some(&name), _) => name,
306            (None, Some(Place { base: Base::Xml(_), .. }) | None) => return "RETURN-CODE".into(),
307            (None, Some(place)) => match self.code.variables.get(&(item as u32)) {
308                Some(Some(name)) => return name.clone(),
309                _ => place.name,
310            },
311        };
312        p.symbols[name as usize].clone()
313    }
314
315    fn lax_redefinition(&self, item: usize) -> Option<LaxRedefinition> {
316        self.code.program.places.get(item).and_then(|p| p.numcheck.lax)
317    }
318
319    fn numcheck_removed(&self, item: usize, _pos: Pos) -> bool {
320        self.code.program.places.get(item).is_some_and(|p| p.numcheck.removed)
321    }
322}
323
324/// One activation of one program: its storage, its LINKAGE addresses, its return points and
325/// frames, and the registers of the DEBUG option (lir.md §8.4, §9.10).
326struct Vm<'p, 'u, 'w, L: Loader<Rc<Code>>> {
327    code: &'p Lowered,
328    p: &'p Program,
329    me: usize,
330    base: usize,
331    local_base: usize,
332    linkage: Vec<Option<usize>>,
333    /// The run unit's first program, where EXIT PROGRAM does nothing.
334    main: bool,
335    returns: Returns,
336    segment: u8,
337    /// The line register, which DEBUG-LINE shows.
338    line: u32,
339    arrival: Arrival,
340    /// A debugging section is running.
341    debugging: bool,
342    /// Places being evaluated, inside which FUNCTION RANDOM is not run.
343    locating: u32,
344    /// What a callback of the semantics library stopped for, its error being an `Abend`.
345    pending: Option<String>,
346    /// The JSON walk's subscripts and XML PARSE's fragment registers.
347    markup: markup::State,
348    io: files::State,
349    /// The logical level's HANDLE CONDITION, IGNORE CONDITION and HANDLE ABEND, which the
350    /// activation running holds (C234).
351    cics_handlers: Handlers,
352    /// This activation's number in the CICS task, which owns the HANDLE labels it sets.
353    serial: u64,
354    /// `Machine::first`: the run unit's first program, for a CALL, LINK or XCTL of it.
355    first: Option<&'p Lowered>,
356    /// SQLCODE and SQLWARN0 of the last EXEC SQL statement, which WHENEVER tests.
357    whenever: Option<Ran>,
358    /// The method this activation runs, if it is one: its class and SELF.
359    method: Option<Running>,
360    /// The programs containing this one, innermost first, as they are running.
361    containers: Vec<scope::Container<'p>>,
362    memo: Option<place::Memo>,
363    /// The last static CALL this activation ran and the program and entry it reached, which a
364    /// static CALL's name always reaches once found.
365    called: Option<(&'p CallPlan, usize, Option<usize>)>,
366    /// What the activations this one CALLs leave, for the next.
367    spare: Spare,
368    unit: &'u mut RunUnit<'w, Rc<Code>, L>,
369}
370
371/// What an activation cannot lay out yet, refused before the program is activated.
372fn check_storage(code: &Lowered) -> R<()> {
373    let storage = &code.program.storage;
374    if !storage.local_image.is_empty() && (storage.init_abend.is_some() || !storage.init_reports.is_empty()) {
375        return Err(not_yet("VALUE initialization that reports or abends in a program with LOCAL-STORAGE"));
376    }
377    Ok(())
378}
379
380/// The tables an activation the VM CALLs takes, a CALL's argument lists and a FUNCTION's arguments,
381/// kept from one to the next.
382#[derive(Default)]
383struct Spare {
384    linkage: Vec<Option<usize>>,
385    armed: Vec<Option<ReturnPoint>>,
386    frames: Vec<Frame>,
387    using: Vec<Option<usize>>,
388    addresses: Vec<Option<usize>>,
389    lengths: Vec<usize>,
390    args: Vec<Val>,
391}
392
393impl<'p, 'u, 'w, L: Loader<Rc<Code>>> Vm<'p, 'u, 'w, L> {
394    /// An activation of loaded program `me`, its storage as `Machine::activation` leaves it: fresh
395    /// on its first activation, after a CANCEL, and on every activation of an INITIAL program.
396    fn activation(code: &'p Lowered, me: usize, unit: &'u mut RunUnit<'w, Rc<Code>, L>, main: bool) -> R<Self> {
397        Self::activation_within(code, me, unit, main, Vec::new(), Spare::default())
398    }
399
400    /// An activation of a contained program, called with the programs containing it running, its
401    /// tables taken from `spare`.
402    fn activation_within(code: &'p Lowered, me: usize, unit: &'u mut RunUnit<'w, Rc<Code>, L>, main: bool, containers: Vec<scope::Container<'p>>, spare: Spare) -> R<Self> {
403        check_storage(code)?;
404        let (base, fresh) = unit.activate(me, code.program.initial);
405        let mut vm = Self::over_reusing(code, me, base, unit, main, containers, spare);
406        vm.start_storage(fresh)?;
407        Ok(vm)
408    }
409
410    /// Binds the shared records and lays out storage as an activation starts: LOCAL-STORAGE each
411    /// time, and on a `fresh` activation the program's initial image, with what it reported.
412    fn start_storage(&mut self, fresh: bool) -> R<()> {
413        let storage = &self.p.storage;
414        self.bind_shared()?;
415        if !storage.local_image.is_empty() {
416            self.local_base = self.unit.push_temporary(&storage.local_image);
417            self.unit.mark_input(self.local_base, storage.local_image.len(), false);
418        }
419        if fresh {
420            let base = self.base;
421            self.unit.mem[base..base + storage.image.len()].copy_from_slice(&storage.image);
422            self.unit.mark_input(base, storage.image.len(), false);
423            for &report in &storage.init_reports {
424                let _ = writeln!(self.unit.err, "{}", self.sym(report));
425            }
426            if let Some(abend) = storage.init_abend {
427                return Err(self.abend(abend, None).into());
428            }
429            self.unit.initialized(self.me);
430        }
431        Ok(())
432    }
433
434    /// Program `me` over its storage at `base`, with nothing bound or initialized.
435    fn over(code: &'p Lowered, me: usize, base: usize, unit: &'u mut RunUnit<'w, Rc<Code>, L>, main: bool, containers: Vec<scope::Container<'p>>) -> Self {
436        Self::over_reusing(code, me, base, unit, main, containers, Spare::default())
437    }
438
439    fn over_reusing(code: &'p Lowered, me: usize, base: usize, unit: &'u mut RunUnit<'w, Rc<Code>, L>, main: bool, containers: Vec<scope::Container<'p>>, spare: Spare) -> Self {
440        let p = &code.program;
441        let serial = unit.cics.as_mut().map_or(0, crate::cics::Task::next_activation);
442        let first = unit.programs[me].compiled.is_none().then_some(code);
443        let main_frame = Frame { id: 0, kind: FrameKind::Main, displaced: None, segment: 0, depth: unit.depth as u32, temps: Vec::new() };
444        let Spare { mut linkage, mut armed, mut frames, .. } = spare;
445        linkage.clear();
446        linkage.resize(p.storage.linkage.len(), None);
447        armed.clear();
448        armed.resize(p.paragraphs.len(), None);
449        frames.clear();
450        frames.push(main_frame);
451        Self {
452            code,
453            p,
454            me,
455            base,
456            local_base: 0,
457            linkage,
458            main,
459            returns: Returns { armed, saved: BTreeMap::new(), frames, next_frame: 1 },
460            segment: 0,
461            line: 0,
462            arrival: Arrival::Start,
463            debugging: false,
464            locating: 0,
465            pending: None,
466            markup: markup::State::default(),
467            io: files::State::default(),
468            cics_handlers: Handlers::default(),
469            serial,
470            first,
471            whenever: None,
472            method: None,
473            containers,
474            memo: None,
475            called: None,
476            spare: Spare::default(),
477            unit,
478        }
479    }
480
481    fn facts(&self) -> Facts<'p> {
482        Facts { code: self.code }
483    }
484
485    fn sym(&self, id: SymId) -> &'p str {
486        &self.p.symbols[id as usize]
487    }
488
489    fn pos(&self, at: DebugId) -> Pos {
490        self.p.debug.positions[at as usize]
491    }
492
493    /// Abend `id` of the program's table, at its own position or else `at`.
494    fn abend(&self, id: AbendId, at: Option<DebugId>) -> Abend {
495        let text = &self.p.abends[id as usize];
496        let pos = text.at.or(at).map(|d| self.pos(d)).unwrap_or_default();
497        Abend { code: text.code.clone(), message: self.sym(text.message).to_owned(), pos, file: None }
498    }
499
500    /// A callback's result as the semantics library takes it: an `Unimplemented` is held in
501    /// `pending` behind an abend that [`Vm::settle`] turns back into it.
502    fn lift<T>(&mut self, result: R<T>, pos: Pos) -> Result<T, Abend> {
503        result.map_err(|stop| match stop.halt() {
504            Halt::Abend(a) => a,
505            Halt::Unimplemented(what) => {
506                self.pending.get_or_insert(what);
507                Abend::ironwork("the VM stopped inside a library call", pos)
508            }
509        })
510    }
511
512    /// A library call's result, with what a callback stopped for in place of its abend.
513    fn settle<T>(&mut self, result: Result<T, Abend>) -> R<T> {
514        match (result, self.pending.take()) {
515            (_, Some(what)) => Err(Halt::Unimplemented(what).into()),
516            (result, None) => result.map_err(Stop::from),
517        }
518    }
519}
520