Skip to main content

ironwork_exec/
vm.rs

1//! Running on the VM (`rt::vm`): CALL's loader over load modules and the interpreter's program
2//! library, lowering each program it compiles from source, and the run itself, of a compiled
3//! program as `Execute::execute_observed` makes the interpreter's, or of a load module's.
4
5use crate::loader::{Library, loads_as};
6use crate::lower::{self, LowerError};
7use crate::unit::{Clock, Observer, Remains};
8use crate::{Compiled, Passed, cics, files, oo, sql};
9use rt::abend::{Abend, AbendCode, Ending};
10use rt::module::{LoadedModule, Modules};
11use rt::oo::{ClassCode, MethodCode, Part};
12use rt::unit::{FoundClass, LoadError, LoadedProgram, Loader, RunUnit};
13pub use rt::vm::{Code, Halt};
14use std::io::{BufRead, Write};
15use std::path::Path;
16use std::rc::Rc;
17use syntax::Pos;
18use zarch::ebcdic::CodePage;
19
20/// Where CALL finds a program on the VM (load-module.md ยง8.2): among the programs already read,
21/// those of the run's first source or module first; then `NAME.iwm` in the program libraries; then
22/// source there, lowered as CALL loads it.
23pub struct VmLibrary {
24    pub source: Library,
25    pub modules: Modules,
26    /// The run's first program came from a module, so its programs come before any source's.
27    modules_first: bool,
28}
29
30type Found = LoadedProgram<Rc<Code>>;
31type FoundCode = FoundClass<Rc<ClassCode<Rc<Code>>>>;
32
33impl VmLibrary {
34    /// The library's directories are where `NAME.iwm` is looked for, in their order.
35    pub fn new(source: Library) -> Self {
36        let modules = Modules::new(source.dirs.clone(), lower::verify);
37        Self { source, modules, modules_first: false }
38    }
39
40    /// A library for a run that begins with `module`, read from `path`, whose programs come before
41    /// any other's.
42    pub fn for_module(source: Library, path: &Path, module: LoadedModule) -> Self {
43        let mut library = Self::new(source);
44        library.modules_first = true;
45        library.modules.add_first(path.to_owned(), module);
46        library
47    }
48
49    /// Program 0 of the module the run began with, or the program `name` as a CALL of it finds
50    /// one; Err says why there is none to run.
51    fn first_program(&mut self, path: &Path, name: Option<&str>) -> Result<Found, String> {
52        let found = match name {
53            None => self.modules.take(0, 0).unwrap_or_else(|| Err(format!("{}: the module holds no program", path.display()))),
54            Some(name) => self.program(name).map_err(|e| match e {
55                LoadError::NotFound => format!("program {name} not found"),
56                LoadError::Compile(message) => message,
57            }),
58        }?;
59        match found.compiled.program() {
60            Some(p) if p.services.class.is_some() => Err(format!("{} is a class definition: run a program that uses it", p.symbols.get(p.id as usize).cloned().unwrap_or_default())),
61            _ => Ok(found),
62        }
63    }
64
65    /// A program of a source already read, lowered; None when no source read holds one.
66    fn read_source(&mut self, name: &str) -> Option<Result<Found, LoadError>> {
67        self.source.programs.iter().any(|p| loads_as(p, name)).then(|| self.lowered_source(name))
68    }
69
70    fn lowered_source(&mut self, name: &str) -> Result<Found, LoadError> {
71        let found = <Library as Loader<Rc<Compiled>>>::program(&mut self.source, name)?;
72        Ok(LoadedProgram { compiled: Rc::new(code(&found.compiled)), name: found.name, files: found.files, size: found.size, source: found.source, recorded: Vec::new() })
73    }
74
75    /// The class the interpreter's library finds and compiles, with its FACTORY and OBJECT data and
76    /// each method lowered as a program of its own, as lowering a class definition lowers them.
77    fn source_class(&mut self, external: &str) -> Result<Option<FoundCode>, String> {
78        let Some(found) = <Library as Loader<Rc<Compiled>>>::class(&mut self.source, external)? else { return Ok(None) };
79        let class = &found.code;
80        let lowered = |c: &Rc<Compiled>| Rc::new(code(c));
81        let part = |p: &oo::Part| Part { data: lowered(&p.data), records: p.records.clone() };
82        let methods = class
83            .methods
84            .iter()
85            .map(|m| MethodCode { name: m.name.clone(), factory: m.factory, params: m.params.clone(), returns: m.returns.clone(), code: lowered(&m.code), own_records: m.own_records })
86            .collect();
87        let code = ClassCode { parent: class.parent.clone(), factory: class.factory.as_ref().map(part), object: class.object.as_ref().map(part), methods };
88        Ok(Some(FoundClass { code: Rc::new(code), sources: found.sources }))
89    }
90}
91
92impl Loader<Rc<Code>> for VmLibrary {
93    fn program(&mut self, name: &str) -> Result<Found, LoadError> {
94        if !self.modules_first
95            && let Some(found) = self.read_source(name)
96        {
97            return found;
98        }
99        if let Some(found) = self.modules.loaded(name) {
100            return found;
101        }
102        if let Some(found) = self.read_source(name) {
103            return found;
104        }
105        match self.modules.search(name) {
106            Err(LoadError::NotFound) => self.lowered_source(name),
107            found => found,
108        }
109    }
110
111    fn holder(&self, entry: &str) -> Option<String> {
112        let source = || <Library as Loader<Rc<Compiled>>>::holder(&self.source, entry);
113        if self.modules_first { self.modules.holder(entry).or_else(source) } else { source().or_else(|| self.modules.holder(entry)) }
114    }
115
116    fn entry(program: &Rc<Code>, name: &str) -> Option<usize> {
117        program.entry(name)
118    }
119
120    fn shape(program: &Rc<Code>) -> (usize, usize) {
121        program.shape()
122    }
123
124    fn nested(program: &Rc<Code>) -> &[String] {
125        program.nested()
126    }
127
128    fn facts(program: &Rc<Code>) -> numeric::governs::Facts {
129        program.facts()
130    }
131
132    fn source(program: &Rc<Code>, file: usize) -> Option<String> {
133        let p = program.program()?;
134        p.debug.sources.get(file).map(|&s| p.symbols[s as usize].clone())
135    }
136
137    fn class(&mut self, external: &str) -> Result<Option<FoundCode>, String> {
138        let read_source = self.source.programs.iter().any(|p| oo::defined_class(p).as_deref() == Some(external));
139        if (!read_source || self.modules_first)
140            && let Some(found) = self.modules.loaded_class(external)?
141        {
142            return Ok(Some(found));
143        }
144        if !read_source && let Some(found) = self.modules.search_class(external)? {
145            return Ok(Some(found));
146        }
147        self.source_class(external)
148    }
149
150    /// A mapset a module already read holds, else one from the copy libraries.
151    fn mapset(&mut self, name: &str) -> Option<Result<rt::bms::Mapset, String>> {
152        self.modules.mapset(name).map(Ok).or_else(|| self.source.mapset(name))
153    }
154}
155
156/// A compiled program as the VM holds it: lowered, or with the construct lowering refused.
157pub fn code(compiled: &Compiled) -> Code {
158    let lowered = lower::lower(compiled).map_err(|e| match e {
159        LowerError::Unsupported(what, _) => what.to_owned(),
160        other => other.to_string(),
161    });
162    held(compiled, lowered)
163}
164
165/// A compiled program lowered for the VM, or why it does not lower, with where.
166pub fn lowered(compiled: &Compiled) -> Result<Code, LowerError> {
167    Ok(held(compiled, Ok(lower::lower(compiled)?)))
168}
169
170fn held(compiled: &Compiled, lowered: Result<rt::lir::Program, String>) -> Code {
171    let entries = compiled.entries.iter().map(|e| e.name.clone()).collect();
172    let method = compiled.program.oo.as_deref().and_then(|o| o.method()).map(|m| format!("{}.{}", m.class, m.name));
173    Code::new(lowered, entries, compiled.program.files.len(), compiled.layout.size as usize, compiled.program.nested.clone(), method, compile::constructs::of(compiled))
174}
175
176/// A run unit on the VM over `library`, tracing and limited as the library says.
177#[allow(clippy::too_many_arguments)]
178fn run_unit<'w>(
179    library: VmLibrary,
180    dds: files::Dds,
181    sysin: Option<Box<dyn BufRead + 'w>>,
182    clock: Clock,
183    database: Option<&'w mut (dyn sql::Database + '_)>,
184    out: &'w mut dyn Write,
185    err: &'w mut dyn Write,
186    observer: Option<Observer<'w>>,
187) -> RunUnit<'w, Rc<Code>, VmLibrary> {
188    let (statements, taint) = (library.source.trace_statements.clone(), library.source.trace_input.then(rt::taint::Taint::default));
189    let (limits, screen, environment) = ((library.source.statement_limit, library.source.time_limit, library.source.storage_limit), library.source.screen.clone(), library.source.environment.clone());
190    let mut run_unit = RunUnit::new(library, dds, sysin, clock, out, err);
191    run_unit.crt = screen;
192    run_unit.environment = rt::environment::Environment::of(environment);
193    run_unit.observer = observer;
194    run_unit.statements = statements;
195    run_unit.taint = taint;
196    run_unit.limit(limits.0, limits.1, limits.2);
197    run_unit.sql = database.map(sql::Session::new);
198    run_unit
199}
200
201/// Runs the first program of `run_unit`, `me`, as `code`, given `passed`, then settles the
202/// database and closes every file, unless an abend the PARM's TRAP(OFF) keeps from Language
203/// Environment ended it. `addresses` takes where each argument passed is.
204fn run_main(code: &Code, id: &str, me: usize, run_unit: &mut RunUnit<'_, Rc<Code>, VmLibrary>, passed: Passed<'_>, page: &CodePage, addresses: &mut Vec<Option<usize>>) -> Result<(Ending, i16), Halt> {
205    let trap_off = matches!(passed, Passed::Parm(p) if rt::le::parm::trap_off(p));
206    passed.apply_parm(run_unit);
207    *addresses = passed.addresses(run_unit, page);
208    let last_paragraph = code.program().and_then(|p| p.paragraphs.last().and_then(|para| p.debug.positions.get(para.at as usize).copied()));
209    let ending = rt::vm::run(code, me, run_unit, addresses, passed.main()).and_then(|e| crate::past_the_end(e, passed.main(), id, last_paragraph).map_err(Halt::Abend));
210    let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.settle(id, ending.is_ok()).map(drop));
211    let closed = run_unit.close_all(matches!(&ending, Err(Halt::Abend(a)) if a.code.skips_termination(trap_off)));
212    let ending = ending?;
213    settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
214    closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
215    Ok((ending, run_unit.return_code()))
216}
217
218/// Runs `compiled`, lowered as `code`, as the first program of a run unit on the VM, given
219/// `passed` as `Execute::execute_main` and `Execute::execute_with_arguments` take it; `kept`
220/// takes what the run left in its run unit.
221#[allow(clippy::too_many_arguments)]
222pub fn execute<'w>(
223    compiled: &Compiled,
224    code: &Code,
225    library: Library,
226    dds: files::Dds,
227    sysin: Option<Box<dyn BufRead + 'w>>,
228    clock: Clock,
229    database: Option<&'w mut (dyn sql::Database + '_)>,
230    out: &'w mut dyn Write,
231    err: &'w mut dyn Write,
232    observer: Option<Observer<'w>>,
233    passed: Passed<'_>,
234    kept: &mut Option<Remains>,
235) -> Result<(Ending, i16), Halt> {
236    oo::refuse_to_run(&compiled.program)?;
237    let mut run_unit = run_unit(VmLibrary::new(library), dds, sysin, clock, database, out, err, observer);
238    let me = run_unit.add_named(None, compiled.program.id.to_ascii_uppercase(), compiled.program.files.len(), compiled.layout.size as usize);
239    let mut addresses = Vec::new();
240    let ran = run_main(code, &compiled.program.id, me, &mut run_unit, passed, compiled.options.code_page(), &mut addresses);
241    *kept = Some(Remains { arguments: passed.returned(&addresses, &run_unit.mem), ..Remains::of(&run_unit) });
242    ran
243}
244
245fn refused(message: String) -> Halt {
246    Halt::Abend(Abend::ironwork(message, Pos::default()))
247}
248
249/// The source names of a program's debug table, which an abend's position indexes.
250fn sources(program: &rt::lir::Program) -> Vec<String> {
251    program.debug.sources.iter().map(|&s| program.symbols.get(s as usize).cloned().unwrap_or_default()).collect()
252}
253
254/// Runs program 0 of `module`, read from `path`, as the first program of a run unit on the VM,
255/// with a job step's `parm`, telling `observer` what the run does. CALL finds the module's other
256/// programs before any other, then as [`VmLibrary`] finds them in `library`'s directories.
257#[allow(clippy::too_many_arguments)]
258pub fn execute_module<'w>(
259    module: LoadedModule,
260    path: &Path,
261    library: Library,
262    dds: files::Dds,
263    sysin: Option<Box<dyn BufRead + 'w>>,
264    clock: Clock,
265    database: Option<&'w mut (dyn sql::Database + '_)>,
266    out: &'w mut dyn Write,
267    err: &'w mut dyn Write,
268    observer: Option<Observer<'w>>,
269    parm: Option<&str>,
270) -> Result<(Ending, i16), Halt> {
271    let mut library = VmLibrary::for_module(library, path, module);
272    let main = library.first_program(path, None).map_err(refused)?;
273    let code = main.compiled;
274    let Some(program) = code.program() else { return Err(refused(format!("{}: program 0 is not lowered", path.display()))) };
275    let id = program.symbols.get(program.id as usize).cloned().unwrap_or_default();
276    let page = program.options.options.code_page();
277    let mut run_unit = run_unit(library, dds, sysin, clock, database, out, err, observer);
278    let me = run_unit.add_named(None, main.name, main.files, main.size);
279    run_main(&code, &id, me, &mut run_unit, parm.map_or(Passed::Nothing, Passed::Parm), page, &mut Vec::new())
280}
281
282/// Whether a CICS task of a run that began with `module` can begin with the program `name`, as a
283/// CALL of it finds one, and if so the files a module records for it ([`LoadedProgram::recorded`]);
284/// Err says why not.
285pub fn module_program(module: &LoadedModule, path: &Path, library: Library, name: &str) -> Result<Vec<(String, Option<rt::module::SourceFile>)>, String> {
286    VmLibrary::for_module(library, path, module.clone()).first_program(path, Some(name)).map(|found| found.recorded)
287}
288
289/// Runs a CICS task on the VM whose first program is program 0 of `module`, read from `path`, or
290/// with `name` the program a CALL of that name finds, as [`execute_cics`] runs a compiled one.
291/// Returns the task however the run ended, and the source names of its first program's debug
292/// table, its own source by path when a program library supplied it.
293#[allow(clippy::too_many_arguments)]
294pub fn execute_module_cics<'w>(
295    module: &LoadedModule,
296    path: &Path,
297    name: Option<&str>,
298    library: Library,
299    dds: files::Dds,
300    task: cics::Task,
301    clock: Clock,
302    database: Option<&'w mut (dyn sql::Database + '_)>,
303    out: &'w mut dyn Write,
304    err: &'w mut dyn Write,
305    observer: Option<Observer<'w>>,
306) -> (Result<Ending, Halt>, cics::Task, Vec<String>) {
307    let mut library = VmLibrary::for_module(library, path, module.clone());
308    let found = match library.first_program(path, name) {
309        Ok(found) => found,
310        Err(message) => return (Err(refused(message)), task, Vec::new()),
311    };
312    let code = found.compiled;
313    let program = code.program();
314    let mut names = program.map(sources).unwrap_or_default();
315    if let (Some(own), Some(path)) = (names.first_mut(), &found.source) {
316        *own = path.display().to_string();
317    }
318    let id = program.and_then(|p| p.symbols.get(p.id as usize).cloned()).unwrap_or_default();
319    let page = program.map_or_else(|| numeric::Options::default().code_page(), |p| p.options.options.code_page());
320    let first = crate::First { id: &id, name: found.name, files: found.files, size: found.size, page, source: found.source };
321    let run_unit = run_unit(library, dds, None, clock, database, out, err, observer);
322    let (ending, ended, task) = crate::run_task(first, run_unit, task, &mut None, |unit, me, commarea, length| rt::vm::run_task(&code, me, unit, commarea, length));
323    let ending = match ending {
324        Err(Halt::Abend(abend)) => Err(Halt::Abend(crate::asra(abend))),
325        other => other,
326    };
327    (ending.and_then(|e| ended.map(|()| e).map_err(Halt::Abend)), task, names)
328}
329
330/// Runs `compiled`, lowered as `code`, on the VM as the first program of a CICS task, as
331/// `Execute::execute_cics_observed` runs it on the interpreter; `kept` takes what the run left in
332/// its run unit, and the task comes back however the run ended.
333#[allow(clippy::too_many_arguments)]
334pub fn execute_cics<'w>(
335    compiled: &Compiled,
336    code: &Code,
337    library: Library,
338    dds: files::Dds,
339    task: cics::Task,
340    clock: Clock,
341    database: Option<&'w mut (dyn sql::Database + '_)>,
342    out: &'w mut dyn Write,
343    err: &'w mut dyn Write,
344    observer: Option<Observer<'w>>,
345    kept: &mut Option<Remains>,
346) -> (Result<Ending, Halt>, cics::Task) {
347    if let Err(abend) = oo::refuse_to_run(&compiled.program) {
348        return (Err(abend.into()), task);
349    }
350    let run_unit = run_unit(VmLibrary::new(library), dds, None, clock, database, out, err, observer);
351    let (ending, ended, task) = crate::run_task(crate::First::of(compiled), run_unit, task, kept, |unit, me, commarea, length| rt::vm::run_task(code, me, unit, commarea, length));
352    let ending = match ending {
353        Err(Halt::Abend(abend)) => Err(Halt::Abend(crate::asra(abend))),
354        other => other,
355    };
356    (ending.and_then(|e| ended.map(|()| e).map_err(Halt::Abend)), task)
357}