Skip to main content

ironwork_rt/
unit.rs

1//! The run unit: every program a run calls, sharing one memory as they share an address space on
2//! z/OS. A called program keeps its WORKING-STORAGE and open files from one CALL to the next until
3//! it is cancelled. A reference or pointer can reach anywhere in this memory, but never outside it.
4//!
5//! `H` is the executor's handle to a loaded program and `L` the loader CALL goes through; the run
6//! unit holds both without looking inside, and asks `L` what it needs to know about an `H`.
7
8use crate::abend::Abend;
9use crate::files::{Dds, Open};
10use crate::vocab::{OpenMode, Pos};
11use std::collections::HashMap;
12use std::io::{BufRead, Write};
13use std::path::{Path, PathBuf};
14
15/// A pointer's value is its offset into run-unit memory plus this, so that no item's address is
16/// NULL.
17pub const ADDRESS_BASE: u32 = 0x0001_0000;
18/// RETURN-CODE, a halfword shared by every program in the run unit.
19pub const RETURN_CODE: usize = 0;
20const RESERVED: usize = 8;
21const ALIGNMENT: usize = 8;
22
23pub struct Loaded<H> {
24    /// None for the first program, which the caller of the run unit owns.
25    pub compiled: Option<H>,
26    pub name: String,
27    pub base: usize,
28    pub files: Vec<Option<Open>>,
29    /// The files CLOSE WITH LOCK has closed, which OPEN refuses with status 38.
30    pub locked: Vec<bool>,
31    pub initialized: bool,
32    pub active: bool,
33    /// For a copy a dynamic CALL of an ENTRY name loaded, that entry (numbered as
34    /// [`Loader::entry`] numbers them); None for the program loaded by its PROGRAM-ID.
35    pub entry: Option<usize>,
36    /// Where each paragraph's GO TO goes since an ALTER, by paragraph; empty until one runs.
37    pub altered: Vec<Option<usize>>,
38    /// The library file CALL loaded the program from; None for the programs of the first source.
39    pub source: Option<PathBuf>,
40}
41
42pub enum LoadError {
43    NotFound,
44    Compile(String),
45}
46
47/// A program a loader found and compiled.
48pub struct LoadedProgram<H> {
49    pub compiled: H,
50    /// The PROGRAM-ID, in upper case.
51    pub name: String,
52    pub files: usize,
53    pub size: usize,
54    /// The file it was read from, when a program library supplied it.
55    pub source: Option<PathBuf>,
56}
57
58/// A class definition a loader found and compiled, and its source table, its own source first by
59/// path when a program library supplied it.
60pub struct FoundClass<C> {
61    pub code: C,
62    pub sources: Vec<String>,
63}
64
65/// Where CALL finds programs, and what the run unit needs to know about one it loaded.
66pub trait Loader<H> {
67    /// The executor's handle to a loaded class definition.
68    type Class;
69
70    /// The program whose PROGRAM-ID CALL names, compiled.
71    fn program(&mut self, name: &str) -> Result<LoadedProgram<H>, LoadError>;
72
73    /// The PROGRAM-ID of a program not yet loaded that has an ENTRY of this name.
74    fn holder(&self, entry: &str) -> Option<String>;
75
76    /// Which of a loaded program's ENTRY statements has this name.
77    fn entry(program: &H, name: &str) -> Option<usize>;
78
79    /// A loaded program's file count and storage size.
80    fn shape(program: &H) -> (usize, usize);
81
82    /// The COBOL class definition of this external name, compiled; None for a Java class.
83    fn class(&mut self, external: &str) -> Result<Option<FoundClass<Self::Class>>, String>;
84}
85
86#[derive(Clone, Copy, Debug)]
87pub enum Clock {
88    System,
89    /// Seconds since 1970-01-01T00:00:00Z and hundredths, for runs that must repeat exactly.
90    Fixed(i64, u32),
91}
92
93/// What a run did that its evidence journal records: each file as it is opened and closed, each
94/// program CALL loads, with the source it was read from when a library supplied it, and each
95/// operation an input could steer, with its operand as the program's code page reads it.
96pub enum Event<'a> {
97    Open { dd: &'a str, mode: OpenMode, path: &'a Path },
98    Close { dd: &'a str, path: &'a Path },
99    Load { program: &'a str, source: Option<&'a Path> },
100    /// Control entering paragraph (or section header) `index` of `program` at its start.
101    Paragraph { program: &'a str, name: &'a str, index: usize },
102    /// `kind` is cobolwork's name for the sink (`dynamic-program-load`, `log`, ...); `file` is the
103    /// library file or COPY member the operation is in, empty for the first program's own source.
104    Sink { kind: &'static str, file: &'a str, line: u32, operand: &'a str },
105}
106
107pub type Observer<'w> = Box<dyn FnMut(Event<'_>) + 'w>;
108
109/// How deep PERFORMs and CALLs may nest before the run abends, rather than exhaust the stack.
110pub const MAX_DEPTH: usize = 100;
111
112pub struct RunUnit<'w, H, L: Loader<H>> {
113    pub mem: Vec<u8>,
114    /// PERFORMs and CALLs in progress, across every program.
115    pub depth: usize,
116    pub programs: Vec<Loaded<H>>,
117    names: HashMap<String, usize>,
118    pub library: L,
119    pub dds: Dds,
120    pub sysin: Option<Box<dyn BufRead + 'w>>,
121    pub clock: Clock,
122    pub out: &'w mut dyn Write,
123    pub err: &'w mut dyn Write,
124    /// The CICS task a harness run stands in for, with its EXEC interface block and open files.
125    pub cics: Option<crate::cics::Task>,
126    pub eib: usize,
127    pub cics_files: HashMap<String, Open>,
128    /// The database EXEC SQL statements reach, when the run has one.
129    pub sql: Option<crate::sql::Session<'w>>,
130    /// Language Environment's heap storage and message files.
131    pub le: crate::le::State,
132    /// Classes, objects and the JNI environment of the run unit's object-oriented programs.
133    pub oo: crate::oo::Objects<L::Class>,
134    /// Told what the run opens, closes and loads, when a caller keeps evidence of it.
135    pub observer: Option<Observer<'w>>,
136    /// FUNCTION RANDOM's generator, one for the run unit, from the first reference on.
137    pub random: Option<u32>,
138}
139
140impl<'w, H: Clone, L: Loader<H>> RunUnit<'w, H, L> {
141    pub fn new(library: L, dds: Dds, sysin: Option<Box<dyn BufRead + 'w>>, clock: Clock, out: &'w mut dyn Write, err: &'w mut dyn Write) -> Self {
142        Self {
143            mem: vec![0; RESERVED],
144            depth: 0,
145            programs: Vec::new(),
146            names: HashMap::new(),
147            library,
148            dds,
149            sysin,
150            clock,
151            out,
152            err,
153            cics: None,
154            eib: 0,
155            cics_files: HashMap::new(),
156            sql: None,
157            le: crate::le::State::default(),
158            oo: Default::default(),
159            observer: None,
160            random: None,
161        }
162    }
163
164    fn allocate(&mut self, size: usize) -> usize {
165        let base = self.mem.len().div_ceil(ALIGNMENT) * ALIGNMENT;
166        self.mem.resize(base + size, 0);
167        base
168    }
169
170    /// Adds a program to the run unit under `name` and gives it its storage.
171    pub fn add_named(&mut self, compiled: Option<H>, name: String, files: usize, size: usize) -> usize {
172        let base = self.allocate(size);
173        let index = self.programs.len();
174        self.names.insert(name.clone(), index);
175        self.programs.push(Loaded { compiled, name, base, files: (0..files).map(|_| None).collect(), locked: vec![false; files], initialized: false, active: false, entry: None, altered: Vec::new(), source: None });
176        index
177    }
178
179    /// The program a CALL of `name` enters, and which of its ENTRY statements when `name` is not
180    /// its PROGRAM-ID. A static CALL of an entry name enters the one copy of the program; a dynamic
181    /// CALL gets a copy of its own for each entry name (assumption C51).
182    pub fn load_entry(&mut self, name: &str, dynamic: bool) -> Result<(usize, Option<usize>), LoadError> {
183        let name = name.to_ascii_uppercase();
184        if let Some(i) = self.find(&name) {
185            return Ok((i, self.programs[i].entry));
186        }
187        let index = match self.programs.iter().position(|p| p.compiled.as_ref().is_some_and(|c| L::entry(c, &name).is_some())) {
188            Some(i) => i,
189            None => {
190                let holder = self.library.holder(&name);
191                self.load(holder.as_deref().unwrap_or(&name))?
192            }
193        };
194        let Some(compiled) = self.programs[index].compiled.clone() else { return Ok((index, None)) };
195        let Some(entry) = L::entry(&compiled, &name) else { return Ok((index, None)) };
196        if !dynamic {
197            return Ok((index, Some(entry)));
198        }
199        let (files, size) = L::shape(&compiled);
200        let copy = self.add_named(Some(compiled), name, files, size);
201        self.programs[copy].entry = Some(entry);
202        self.programs[copy].source = self.programs[index].source.clone();
203        Ok((copy, Some(entry)))
204    }
205
206    /// Storage for a BY CONTENT or BY VALUE argument, at the end of memory.
207    pub fn push_temporary(&mut self, bytes: &[u8]) -> usize {
208        let at = self.allocate(bytes.len());
209        self.mem[at..at + bytes.len()].copy_from_slice(bytes);
210        at
211    }
212
213    /// Releases arguments pushed since `mark`, unless a program or heap storage was placed behind them.
214    pub fn release_temporaries(&mut self, mark: usize) {
215        if self.programs.iter().all(|p| p.base < mark) && self.le.heap_end() <= mark {
216            self.mem.truncate(mark.max(RESERVED));
217        }
218    }
219
220    /// One more PERFORM or CALL in progress, refused past `MAX_DEPTH` rather than exhaust the stack.
221    pub fn enter(&mut self, pos: Pos) -> Result<(), Abend> {
222        if self.depth >= MAX_DEPTH {
223            return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {MAX_DEPTH}"), pos));
224        }
225        self.depth += 1;
226        Ok(())
227    }
228
229    /// Marks program `me` active: where its storage starts, and whether the activation starts from
230    /// fresh storage, as its first does, the first after a CANCEL, and every one of an INITIAL
231    /// program.
232    pub fn activate(&mut self, me: usize, initial: bool) -> (usize, bool) {
233        let program = &mut self.programs[me];
234        program.active = true;
235        (program.base, !program.initialized || initial)
236    }
237
238    /// Program `me`'s storage holds its initial values, and its GO TOs go where they are written.
239    pub fn initialized(&mut self, me: usize) {
240        self.programs[me].initialized = true;
241        self.programs[me].altered.clear();
242    }
243
244    pub fn find(&self, name: &str) -> Option<usize> {
245        self.names.get(&name.to_ascii_uppercase()).copied()
246    }
247
248    /// The program CALL names, compiling and loading it the first time.
249    pub fn load(&mut self, name: &str) -> Result<usize, LoadError> {
250        let name = name.to_ascii_uppercase();
251        if let Some(i) = self.find(&name) {
252            return Ok(i);
253        }
254        let loaded = self.library.program(&name)?;
255        self.notify(Event::Load { program: &name, source: loaded.source.as_deref() });
256        let index = self.add_named(Some(loaded.compiled), loaded.name, loaded.files, loaded.size);
257        self.programs[index].source = loaded.source;
258        Ok(index)
259    }
260
261    pub const fn observed(&self) -> bool {
262        self.observer.is_some()
263    }
264
265    pub fn notify(&mut self, event: Event<'_>) {
266        if let Some(observer) = self.observer.as_mut() {
267            observer(event);
268        }
269    }
270
271    /// Closes every file any program left open, as the runtime does when the run unit ends.
272    pub fn close_all(&mut self) -> Result<(), String> {
273        for program in &mut self.programs {
274            for f in program.files.iter_mut().filter_map(Option::take) {
275                f.close().map_err(|e| format!("closing a file of {}: {e}", program.name))?;
276            }
277        }
278        Ok(())
279    }
280
281    pub fn return_code(&self) -> i16 {
282        i16::from_be_bytes([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]])
283    }
284
285    /// The current time: seconds since the epoch, and hundredths.
286    pub fn now(&self) -> (i64, u32) {
287        match self.clock {
288            Clock::Fixed(s, h) => (s, h),
289            Clock::System => {
290                let d = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
291                (d.as_secs() as i64, d.subsec_millis() / 10)
292            }
293        }
294    }
295}