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