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, 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, and for a method the
95/// `Class.method` a dump lists it by.
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}
104
105/// A lowered program with what the VM works out from it once: its collating sequence as the
106/// semantics library takes it, the paragraph each entry block begins, the name a TRUNC(OPT)
107/// report gives each binary receiver, and under NUMCHECK the name its message gives each
108/// conditional variable (`cond::conditional_variables`).
109struct Lowered {
110    program: Program,
111    collation: Collation,
112    ordinals: Vec<u8>,
113    high_value: u8,
114    low_value: u8,
115    entry_of: Vec<Option<u32>>,
116    receivers: HashMap<PlaceId, SymId>,
117    variables: HashMap<PlaceId, Option<String>>,
118    pure: Vec<bool>,
119}
120
121impl Code {
122    pub fn new(program: Result<Program, String>, entries: Vec<String>, files: usize, size: usize, nested: Vec<String>, method: Option<String>) -> Self {
123        Self { lowered: program.map(Lowered::new), entries, files, size, nested, method }
124    }
125
126    pub fn program(&self) -> Option<&Program> {
127        self.lowered.as_ref().ok().map(|l| &l.program)
128    }
129
130    /// Which ENTRY statement, in source order, has this name.
131    pub fn entry(&self, name: &str) -> Option<usize> {
132        self.entries.iter().position(|e| e == name)
133    }
134
135    pub fn shape(&self) -> (usize, usize) {
136        (self.files, self.size)
137    }
138
139    pub fn nested(&self) -> &[String] {
140        &self.nested
141    }
142}
143
144impl Lowered {
145    fn new(program: Program) -> Self {
146        let (collation, ordinals, high_value, low_value) = match &program.options.collating {
147            Collating::Native => (Collation::Native, (0..=255).collect(), ebcdic::HIGH_VALUE, ebcdic::LOW_VALUE),
148            Collating::Sequence(s) => {
149                let weights = Box::new(std::array::from_fn(|b| u16::from(s.positions[b])));
150                (Collation::Weights(weights), s.characters.clone(), s.high_value, s.low_value)
151            }
152        };
153        let mut entry_of = vec![None; program.blocks.len()];
154        for (i, paragraph) in program.paragraphs.iter().enumerate() {
155            if let Some(slot) = entry_of.get_mut(paragraph.entry as usize) {
156                *slot = Some(i as u32);
157            }
158        }
159        let receivers = receivers(&program);
160        let variables = if program.options.options.numcheck.is_some() { cond::conditional_variables(&program) } else { HashMap::new() };
161        let pure = place::pure_places(&program);
162        Self { program, collation, ordinals, high_value, low_value, entry_of, receivers, variables, pure }
163    }
164}
165
166/// Each place a binary store plan names with the item the TRUNC(OPT) report gives it, which differs
167/// from the place's own name where a condition-name reaches its conditional variable.
168fn receivers(p: &Program) -> HashMap<PlaceId, SymId> {
169    let mut names = HashMap::new();
170    let mut store = |place: PlaceId, plan: &StorePlan| {
171        if let StorePlan::Binary { name, .. } = plan {
172            names.entry(place).or_insert(*name);
173        }
174    };
175    let moved = |plan: &MovePlan| match plan {
176        MovePlan::Numeric { store, .. } => Some(*store),
177        _ => None,
178    };
179    for a in &p.plans.arith {
180        a.steps.iter().for_each(|s| store(s.target, &s.store));
181        a.remainder.iter().for_each(|r| store(r.target, &r.store));
182    }
183    for Block { ops, .. } in &p.blocks {
184        for op in ops {
185            match op {
186                Op::Move { to, plan, .. } | Op::Set { to, plan, .. } | Op::Accept { target: to, plan, .. } => moved(plan).iter().for_each(|s| store(*to, s)),
187                Op::Step { var, plan, .. } => store(*var, &plan.store),
188                Op::SetUpDown { targets, .. } => targets.iter().for_each(|(place, how)| {
189                    if let UpDown::Number(plan) = how {
190                        store(*place, &plan.store);
191                    }
192                }),
193                _ => {}
194            }
195        }
196    }
197    for s in &p.plans.string {
198        s.pointer.iter().for_each(|(place, plan)| store(*place, plan));
199    }
200    for u in &p.plans.unstring {
201        u.pointer.iter().for_each(|(place, plan)| store(*place, plan));
202        u.tallying.iter().for_each(|(place, plan)| store(*place, &plan.store));
203        for into in &u.into {
204            moved(&into.plan).iter().for_each(|s| store(into.target, s));
205            into.count.iter().for_each(|(place, plan)| store(*place, plan));
206        }
207    }
208    for i in &p.plans.inspect {
209        i.tallying.iter().filter_map(|t| t.counter.as_ref()).for_each(|(place, plan)| store(*place, &plan.store));
210    }
211    for s in &p.plans.search_all {
212        store(s.index, &s.store);
213    }
214    names
215}
216
217/// Runs program `me` of the run unit, `code`, as its first program, its PROCEDURE DIVISION USING
218/// items given `arguments`' addresses; `main` as a run unit's main program, where EXIT PROGRAM
219/// does nothing, or else as a subprogram a caller passed them to.
220pub fn run<L: Loader<Rc<Code>>>(code: &Code, me: usize, unit: &mut RunUnit<'_, Rc<Code>, L>, arguments: &[Option<usize>], main: bool) -> Result<Ending, Halt> {
221    let lowered = code.lowered.as_ref().map_err(|why| not_yet(format!("a program that does not lower ({why})")))?;
222    let mut vm = Vm::activation(lowered, me, unit, main)?;
223    for (&record, &address) in lowered.program.storage.using.iter().zip(arguments) {
224        vm.linkage[usize::from(record)] = address;
225    }
226    Ok(vm.run_from(None)?)
227}
228
229/// What the semantics library reads of the running program, answered from its LIR. A `Loc`'s
230/// item is the place it was evaluated from.
231#[derive(Clone, Copy)]
232struct Facts<'p> {
233    code: &'p Lowered,
234}
235
236impl ProgramFacts for Facts<'_> {
237    fn options(&self) -> Options {
238        self.code.program.options.options
239    }
240
241    fn page(&self) -> &'static CodePage {
242        self.code.program.options.options.code_page()
243    }
244
245    fn figurative(&self, f: Figurative) -> u8 {
246        match f {
247            Figurative::HighValue => self.code.high_value,
248            Figurative::LowValue => self.code.low_value,
249            Figurative::Space => ebcdic::SPACE,
250            Figurative::Zero => ebcdic::ZERO,
251            Figurative::Quote => self.options().quote.byte(),
252            Figurative::Null => 0,
253        }
254    }
255
256    fn collation(&self) -> &Collation {
257        &self.code.collation
258    }
259
260    fn ordinal(&self, byte: u8) -> u16 {
261        self.code.collation.weight(byte) + 1
262    }
263
264    fn character(&self, ordinal: i64) -> Option<u8> {
265        usize::try_from(ordinal).ok().and_then(|n| n.checked_sub(1)).and_then(|i| self.code.ordinals.get(i)).copied()
266    }
267
268    fn characters(&self) -> usize {
269        self.code.ordinals.len()
270    }
271
272    fn decimal_point(&self) -> char {
273        if self.code.program.options.decimal_point_comma { ',' } else { '.' }
274    }
275
276    fn edit(&self, edit: u32) -> (&[Sym], &str) {
277        let e = &self.code.program.edits[edit as usize];
278        (&e.syms, &e.currency)
279    }
280
281    fn scaling(&self, item: usize) -> u32 {
282        self.code.program.places.get(item).map_or(0, |p| p.scaling)
283    }
284
285    /// The walker names an item by its `Loc`'s item, which an XML register's has none of.
286    fn item_name(&self, item: usize) -> String {
287        let p = &self.code.program;
288        let name = match (u32::try_from(item).ok().and_then(|i| self.code.receivers.get(&i)), p.places.get(item)) {
289            (Some(&name), _) => name,
290            (None, Some(Place { base: Base::Xml(_), .. }) | None) => return "RETURN-CODE".into(),
291            (None, Some(place)) => match self.code.variables.get(&(item as u32)) {
292                Some(Some(name)) => return name.clone(),
293                _ => place.name,
294            },
295        };
296        p.symbols[name as usize].clone()
297    }
298
299    fn lax_redefinition(&self, item: usize) -> Option<LaxRedefinition> {
300        self.code.program.places.get(item).and_then(|p| p.numcheck.lax)
301    }
302
303    fn numcheck_removed(&self, item: usize, _pos: Pos) -> bool {
304        self.code.program.places.get(item).is_some_and(|p| p.numcheck.removed)
305    }
306}
307
308/// One activation of one program: its storage, its LINKAGE addresses, its return points and
309/// frames, and the registers of the DEBUG option (lir.md §8.4, §9.10).
310struct Vm<'p, 'u, 'w, L: Loader<Rc<Code>>> {
311    code: &'p Lowered,
312    p: &'p Program,
313    me: usize,
314    base: usize,
315    local_base: usize,
316    linkage: Vec<Option<usize>>,
317    /// The run unit's first program, where EXIT PROGRAM does nothing.
318    main: bool,
319    returns: Returns,
320    segment: u8,
321    /// The line register, which DEBUG-LINE shows.
322    line: u32,
323    arrival: Arrival,
324    /// A debugging section is running.
325    debugging: bool,
326    /// Places being evaluated, inside which FUNCTION RANDOM is not run.
327    locating: u32,
328    /// What a callback of the semantics library stopped for, its error being an `Abend`.
329    pending: Option<String>,
330    /// The JSON walk's subscripts and XML PARSE's fragment registers.
331    markup: markup::State,
332    io: files::State,
333    /// The logical level's HANDLE CONDITION, IGNORE CONDITION and HANDLE ABEND, which the
334    /// activation running holds (C234).
335    cics_handlers: Handlers,
336    /// This activation's number in the CICS task, which owns the HANDLE labels it sets.
337    serial: u64,
338    /// `Machine::first`: the run unit's first program, for a CALL, LINK or XCTL of it.
339    first: Option<&'p Lowered>,
340    /// SQLCODE and SQLWARN0 of the last EXEC SQL statement, which WHENEVER tests.
341    whenever: Option<Ran>,
342    /// The method this activation runs, if it is one: its class and SELF.
343    method: Option<Running>,
344    /// The programs containing this one, innermost first, as they are running.
345    containers: Vec<scope::Container<'p>>,
346    memo: Option<place::Memo>,
347    /// What the activations this one CALLs leave, for the next.
348    spare: Spare,
349    unit: &'u mut RunUnit<'w, Rc<Code>, L>,
350}
351
352/// The tables an activation the VM CALLs takes, a CALL's argument lists and a FUNCTION's arguments,
353/// kept from one to the next.
354#[derive(Default)]
355struct Spare {
356    linkage: Vec<Option<usize>>,
357    armed: Vec<Option<ReturnPoint>>,
358    frames: Vec<Frame>,
359    using: Vec<Option<usize>>,
360    addresses: Vec<Option<usize>>,
361    args: Vec<Val>,
362}
363
364impl<'p, 'u, 'w, L: Loader<Rc<Code>>> Vm<'p, 'u, 'w, L> {
365    /// An activation of loaded program `me`, its storage as `Machine::activation` leaves it: fresh
366    /// on its first activation, after a CANCEL, and on every activation of an INITIAL program.
367    fn activation(code: &'p Lowered, me: usize, unit: &'u mut RunUnit<'w, Rc<Code>, L>, main: bool) -> R<Self> {
368        Self::activation_within(code, me, unit, main, Vec::new(), Spare::default())
369    }
370
371    /// An activation of a contained program, called with the programs containing it running, its
372    /// tables taken from `spare`.
373    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> {
374        let p = &code.program;
375        let storage = &p.storage;
376        if !storage.local_image.is_empty() && (storage.init_abend.is_some() || !storage.init_reports.is_empty()) {
377            return Err(not_yet("VALUE initialization that reports or abends in a program with LOCAL-STORAGE"));
378        }
379        let (base, fresh) = unit.activate(me, p.initial);
380        let mut vm = Self::over_reusing(code, me, base, unit, main, containers, spare);
381        vm.bind_shared()?;
382        if !storage.local_image.is_empty() {
383            vm.local_base = vm.unit.push_temporary(&storage.local_image);
384            vm.unit.mark_input(vm.local_base, storage.local_image.len(), false);
385        }
386        if fresh {
387            vm.unit.mem[base..base + storage.image.len()].copy_from_slice(&storage.image);
388            vm.unit.mark_input(base, storage.image.len(), false);
389            for &report in &storage.init_reports {
390                let _ = writeln!(vm.unit.err, "{}", vm.sym(report));
391            }
392            if let Some(abend) = storage.init_abend {
393                return Err(vm.abend(abend, None).into());
394            }
395            vm.unit.initialized(me);
396        }
397        Ok(vm)
398    }
399
400    /// Program `me` over its storage at `base`, with nothing bound or initialized.
401    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 {
402        Self::over_reusing(code, me, base, unit, main, containers, Spare::default())
403    }
404
405    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 {
406        let p = &code.program;
407        let serial = unit.cics.as_mut().map_or(0, crate::cics::Task::next_activation);
408        let first = unit.programs[me].compiled.is_none().then_some(code);
409        let main_frame = Frame { id: 0, kind: FrameKind::Main, displaced: None, segment: 0, depth: unit.depth as u32, temps: Vec::new() };
410        let Spare { mut linkage, mut armed, mut frames, .. } = spare;
411        linkage.clear();
412        linkage.resize(p.storage.linkage.len(), None);
413        armed.clear();
414        armed.resize(p.paragraphs.len(), None);
415        frames.clear();
416        frames.push(main_frame);
417        Self {
418            code,
419            p,
420            me,
421            base,
422            local_base: 0,
423            linkage,
424            main,
425            returns: Returns { armed, saved: BTreeMap::new(), frames, next_frame: 1 },
426            segment: 0,
427            line: 0,
428            arrival: Arrival::Start,
429            debugging: false,
430            locating: 0,
431            pending: None,
432            markup: markup::State::default(),
433            io: files::State::default(),
434            cics_handlers: Handlers::default(),
435            serial,
436            first,
437            whenever: None,
438            method: None,
439            containers,
440            memo: None,
441            spare: Spare::default(),
442            unit,
443        }
444    }
445
446    fn facts(&self) -> Facts<'p> {
447        Facts { code: self.code }
448    }
449
450    fn sym(&self, id: SymId) -> &'p str {
451        &self.p.symbols[id as usize]
452    }
453
454    fn pos(&self, at: DebugId) -> Pos {
455        self.p.debug.positions[at as usize]
456    }
457
458    /// Abend `id` of the program's table, at its own position or else `at`.
459    fn abend(&self, id: AbendId, at: Option<DebugId>) -> Abend {
460        let text = &self.p.abends[id as usize];
461        let pos = text.at.or(at).map(|d| self.pos(d)).unwrap_or_default();
462        Abend { code: text.code.clone(), message: self.sym(text.message).to_owned(), pos, file: None }
463    }
464
465    /// A callback's result as the semantics library takes it: an `Unimplemented` is held in
466    /// `pending` behind an abend that [`Vm::settle`] turns back into it.
467    fn lift<T>(&mut self, result: R<T>, pos: Pos) -> Result<T, Abend> {
468        result.map_err(|stop| match stop.halt() {
469            Halt::Abend(a) => a,
470            Halt::Unimplemented(what) => {
471                self.pending.get_or_insert(what);
472                Abend::ironwork("the VM stopped inside a library call", pos)
473            }
474        })
475    }
476
477    /// A library call's result, with what a callback stopped for in place of its abend.
478    fn settle<T>(&mut self, result: Result<T, Abend>) -> R<T> {
479        match (result, self.pending.take()) {
480            (_, Some(what)) => Err(Halt::Unimplemented(what).into()),
481            (result, None) => result.map_err(Stop::from),
482        }
483    }
484}