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, 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;
18
19/// Where CALL finds a program on the VM (load-module.md ยง8.2): among the programs already read,
20/// those of the run's first source or module first; then `NAME.iwm` in the program libraries; then
21/// source there, lowered as CALL loads it.
22pub struct VmLibrary {
23    pub source: Library,
24    pub modules: Modules,
25    /// The run's first program came from a module, so its programs come before any source's.
26    modules_first: bool,
27}
28
29type Found = LoadedProgram<Rc<Code>>;
30type FoundCode = FoundClass<Rc<ClassCode<Rc<Code>>>>;
31
32impl VmLibrary {
33    /// The library's directories are where `NAME.iwm` is looked for, in their order.
34    pub fn new(source: Library) -> Self {
35        let modules = Modules::new(source.dirs.clone(), lower::verify);
36        Self { source, modules, modules_first: false }
37    }
38
39    /// A program of a source already read, lowered; None when no source read holds one.
40    fn read_source(&mut self, name: &str) -> Option<Result<Found, LoadError>> {
41        self.source.programs.iter().any(|p| loads_as(p, name)).then(|| self.lowered_source(name))
42    }
43
44    fn lowered_source(&mut self, name: &str) -> Result<Found, LoadError> {
45        let found = <Library as Loader<Rc<Compiled>>>::program(&mut self.source, name)?;
46        Ok(LoadedProgram { compiled: Rc::new(code(&found.compiled)), name: found.name, files: found.files, size: found.size, source: found.source })
47    }
48
49    /// The class the interpreter's library finds and compiles, with its FACTORY and OBJECT data and
50    /// each method lowered as a program of its own, as lowering a class definition lowers them.
51    fn source_class(&mut self, external: &str) -> Result<Option<FoundCode>, String> {
52        let Some(found) = <Library as Loader<Rc<Compiled>>>::class(&mut self.source, external)? else { return Ok(None) };
53        let class = &found.code;
54        let lowered = |c: &Rc<Compiled>| Rc::new(code(c));
55        let part = |p: &oo::Part| Part { data: lowered(&p.data), records: p.records.clone() };
56        let methods = class
57            .methods
58            .iter()
59            .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 })
60            .collect();
61        let code = ClassCode { parent: class.parent.clone(), factory: class.factory.as_ref().map(part), object: class.object.as_ref().map(part), methods };
62        Ok(Some(FoundClass { code: Rc::new(code), sources: found.sources }))
63    }
64}
65
66impl Loader<Rc<Code>> for VmLibrary {
67    fn program(&mut self, name: &str) -> Result<Found, LoadError> {
68        if !self.modules_first
69            && let Some(found) = self.read_source(name)
70        {
71            return found;
72        }
73        if let Some(found) = self.modules.loaded(name) {
74            return found;
75        }
76        if let Some(found) = self.read_source(name) {
77            return found;
78        }
79        match self.modules.search(name) {
80            Err(LoadError::NotFound) => self.lowered_source(name),
81            found => found,
82        }
83    }
84
85    fn holder(&self, entry: &str) -> Option<String> {
86        let source = || <Library as Loader<Rc<Compiled>>>::holder(&self.source, entry);
87        if self.modules_first { self.modules.holder(entry).or_else(source) } else { source().or_else(|| self.modules.holder(entry)) }
88    }
89
90    fn entry(program: &Rc<Code>, name: &str) -> Option<usize> {
91        program.entry(name)
92    }
93
94    fn shape(program: &Rc<Code>) -> (usize, usize) {
95        program.shape()
96    }
97
98    fn nested(program: &Rc<Code>) -> &[String] {
99        program.nested()
100    }
101
102    fn source(program: &Rc<Code>, file: usize) -> Option<String> {
103        let p = program.program()?;
104        p.debug.sources.get(file).map(|&s| p.symbols[s as usize].clone())
105    }
106
107    fn class(&mut self, external: &str) -> Result<Option<FoundCode>, String> {
108        let read_source = self.source.programs.iter().any(|p| oo::defined_class(p).as_deref() == Some(external));
109        if (!read_source || self.modules_first)
110            && let Some(found) = self.modules.loaded_class(external)?
111        {
112            return Ok(Some(found));
113        }
114        if !read_source && let Some(found) = self.modules.search_class(external)? {
115            return Ok(Some(found));
116        }
117        self.source_class(external)
118    }
119
120    /// A mapset a module already read holds, else one from the copy libraries.
121    fn mapset(&mut self, name: &str) -> Option<Result<rt::bms::Mapset, String>> {
122        self.modules.mapset(name).map(Ok).or_else(|| self.source.mapset(name))
123    }
124}
125
126/// A compiled program as the VM holds it: lowered, or with the construct lowering refused.
127pub fn code(compiled: &Compiled) -> Code {
128    let lowered = lower::lower(compiled).map_err(|e| match e {
129        LowerError::Unsupported(what, _) => what.to_owned(),
130        other => other.to_string(),
131    });
132    held(compiled, lowered)
133}
134
135/// A compiled program lowered for the VM, or why it does not lower, with where.
136pub fn lowered(compiled: &Compiled) -> Result<Code, LowerError> {
137    Ok(held(compiled, Ok(lower::lower(compiled)?)))
138}
139
140fn held(compiled: &Compiled, lowered: Result<rt::lir::Program, String>) -> Code {
141    let entries = compiled.entries.iter().map(|e| e.name.clone()).collect();
142    let method = compiled.program.oo.as_deref().and_then(|o| o.method()).map(|m| format!("{}.{}", m.class, m.name));
143    Code::new(lowered, entries, compiled.program.files.len(), compiled.layout.size as usize, compiled.program.nested.clone(), method)
144}
145
146/// A run unit on the VM over `library`, tracing and limited as the library says.
147#[allow(clippy::too_many_arguments)]
148fn run_unit<'w>(
149    library: VmLibrary,
150    dds: files::Dds,
151    sysin: Option<Box<dyn BufRead + 'w>>,
152    clock: Clock,
153    database: Option<&'w mut (dyn sql::Database + '_)>,
154    out: &'w mut dyn Write,
155    err: &'w mut dyn Write,
156    observer: Option<Observer<'w>>,
157) -> RunUnit<'w, Rc<Code>, VmLibrary> {
158    let (statements, taint) = (library.source.trace_statements.clone(), library.source.trace_input.then(rt::taint::Taint::default));
159    let limit = library.source.statement_limit;
160    let mut run_unit = RunUnit::new(library, dds, sysin, clock, out, err);
161    run_unit.observer = observer;
162    run_unit.statements = statements;
163    run_unit.taint = taint;
164    run_unit.statement_limit = limit;
165    run_unit.sql = database.map(sql::Session::new);
166    run_unit
167}
168
169/// Runs the first program of `run_unit`, `me`, as `code`, with a job step's `parm`, then settles
170/// the database and closes every file, unless an abend the PARM's TRAP(OFF) keeps from Language
171/// Environment ended it.
172fn run_main(code: &Code, id: &str, me: usize, run_unit: &mut RunUnit<'_, Rc<Code>, VmLibrary>, parm: Option<(usize, bool)>) -> Result<(Ending, i16), Halt> {
173    let (parm, trap_off) = parm.unzip();
174    let ending = rt::vm::run(code, me, run_unit, &parm.map_or_else(Vec::new, |p| vec![Some(p)]));
175    let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.settle(id, ending.is_ok()).map(drop));
176    let closed = run_unit.close_all(trap_off == Some(true) && matches!(&ending, Err(Halt::Abend(a)) if a.code.bypasses_trap_off()));
177    let ending = ending?;
178    settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
179    closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
180    Ok((ending, run_unit.return_code()))
181}
182
183/// Runs `compiled`, lowered as `code`, as the first program of a run unit on the VM, with a job
184/// step's `parm` as `Execute::execute_main` takes it; `kept` takes what the run left in its run
185/// unit.
186#[allow(clippy::too_many_arguments)]
187pub fn execute<'w>(
188    compiled: &Compiled,
189    code: &Code,
190    library: Library,
191    dds: files::Dds,
192    sysin: Option<Box<dyn BufRead + 'w>>,
193    clock: Clock,
194    database: Option<&'w mut (dyn sql::Database + '_)>,
195    out: &'w mut dyn Write,
196    err: &'w mut dyn Write,
197    observer: Option<Observer<'w>>,
198    parm: Option<&str>,
199    kept: &mut Option<Remains>,
200) -> Result<(Ending, i16), Halt> {
201    oo::refuse_to_run(&compiled.program)?;
202    let mut run_unit = run_unit(VmLibrary::new(library), dds, sysin, clock, database, out, err, observer);
203    let me = run_unit.add_named(None, compiled.program.id.to_ascii_uppercase(), compiled.program.files.len(), compiled.layout.size as usize);
204    let parm = parm.map(|p| (crate::push_parm(&mut run_unit, compiled.options.code_page(), p), rt::le::parm::trap_off(p)));
205    let ran = run_main(code, &compiled.program.id, me, &mut run_unit, parm);
206    *kept = Some(Remains::of(&run_unit));
207    ran
208}
209
210/// Runs program 0 of `module`, read from `path`, as the first program of a run unit on the VM,
211/// with a job step's `parm`. CALL finds the module's other programs before any other, then as
212/// [`VmLibrary`] finds them in `library`'s directories.
213#[allow(clippy::too_many_arguments)]
214pub fn execute_module<'w>(
215    module: LoadedModule,
216    path: &Path,
217    library: Library,
218    dds: files::Dds,
219    sysin: Option<Box<dyn BufRead + 'w>>,
220    clock: Clock,
221    database: Option<&'w mut (dyn sql::Database + '_)>,
222    out: &'w mut dyn Write,
223    err: &'w mut dyn Write,
224    parm: Option<&str>,
225) -> Result<(Ending, i16), Halt> {
226    let refused = |message: String| Halt::Abend(Abend::ironwork(message, Pos::default()));
227    let mut library = VmLibrary::new(library);
228    library.modules_first = true;
229    let first = library.modules.add(path.to_owned(), module);
230    let main = match library.modules.take(first, 0) {
231        Some(found) => found.map_err(refused)?,
232        None => return Err(refused(format!("{}: the module holds no program", path.display()))),
233    };
234    let code = main.compiled;
235    let Some(program) = code.program() else { return Err(refused(format!("{}: program 0 is not lowered", path.display()))) };
236    let id = program.symbols.get(program.id as usize).cloned().unwrap_or_default();
237    if program.services.class.is_some() {
238        return Err(refused(format!("{id} is a class definition: run a program that uses it")));
239    }
240    let page = program.options.options.code_page();
241    let mut run_unit = run_unit(library, dds, sysin, clock, database, out, err, None);
242    let me = run_unit.add_named(None, main.name, main.files, main.size);
243    let parm = parm.map(|p| (crate::push_parm(&mut run_unit, page, p), rt::le::parm::trap_off(p)));
244    run_main(&code, &id, me, &mut run_unit, parm)
245}
246
247/// Runs `compiled`, lowered as `code`, on the VM as the first program of a CICS task, as
248/// `Execute::execute_cics_observed` runs it on the interpreter; `kept` takes what the run left in
249/// its run unit, and the task comes back however the run ended.
250#[allow(clippy::too_many_arguments)]
251pub fn execute_cics<'w>(
252    compiled: &Compiled,
253    code: &Code,
254    library: Library,
255    dds: files::Dds,
256    task: cics::Task,
257    clock: Clock,
258    database: Option<&'w mut (dyn sql::Database + '_)>,
259    out: &'w mut dyn Write,
260    err: &'w mut dyn Write,
261    observer: Option<Observer<'w>>,
262    kept: &mut Option<Remains>,
263) -> (Result<Ending, Halt>, cics::Task) {
264    if let Err(abend) = oo::refuse_to_run(&compiled.program) {
265        return (Err(abend.into()), task);
266    }
267    let run_unit = run_unit(VmLibrary::new(library), dds, None, clock, database, out, err, observer);
268    let (ending, ended, task) = crate::run_task(compiled, run_unit, task, kept, |unit, me, commarea, length| rt::vm::run_task(code, me, unit, commarea, length));
269    let ending = match ending {
270        Err(Halt::Abend(abend)) => Err(Halt::Abend(crate::asra(abend))),
271        other => other,
272    };
273    (ending.and_then(|e| ended.map(|()| e).map_err(Halt::Abend)), task)
274}