Skip to main content

ironwork_exec/lower/
mod.rs

1//! Lowering (docs/lir.md): a compiled program with every name resolved, every category decided and
2//! every transfer of control made explicit, as the LIR a VM runs and a load module holds.
3//!
4//! Lowered so far: storage, places, expressions and conditions, the arithmetic verbs, MOVE, IF,
5//! EVALUATE, DISPLAY, INITIALIZE, PERFORM, GO TO, GO TO DEPENDING ON, ALTER, EXIT, STOP RUN, GOBACK,
6//! CALL, CANCEL, ENTRY, INVOKE, SET, STRING, UNSTRING, INSPECT, SEARCH, ACCEPT, the file
7//! statements, intrinsic functions, independent segments, class definitions, USE AFTER
8//! EXCEPTION/ERROR, USE FOR DEBUGGING, JSON and XML GENERATE and PARSE, the EXEC blocks, SORT,
9//! MERGE, RELEASE and RETURN, the Report Writer, user-defined functions, and EXTERNAL and GLOBAL
10//! records, files and declaratives.
11//! Anything else is [`LowerError::Unsupported`], naming the construct.
12
13mod call;
14mod check;
15mod cics;
16mod class;
17mod cond;
18mod data;
19mod file;
20mod flow;
21mod function;
22mod markup;
23mod plans;
24mod report;
25mod scope;
26mod search;
27mod set;
28mod sort;
29mod sql;
30mod text;
31mod user_function;
32mod verify;
33
34#[cfg(test)]
35mod tests;
36
37pub use verify::verify;
38
39use crate::Compiled;
40use crate::layout::{Layout, Resolved};
41use crate::machine::Machine;
42use crate::unit::{AddProgram, Clock, Library, RunUnit};
43use rt::abend::AbendCode;
44use rt::lir::{self, AbendId, BlockId, ConstId, DebugId, PlaceId, RangeId, SymId};
45use std::collections::{BTreeSet, HashMap};
46use std::fmt;
47use syntax::Pos;
48use syntax::ast;
49use zarch::ebcdic::CodePage;
50
51/// Why a program does not lower.
52#[derive(Clone, Debug, PartialEq, Eq)]
53pub enum LowerError {
54    /// A construct this slice does not lower yet, and where the program uses it.
55    Unsupported(&'static str, Pos),
56    /// A table larger than the LIR's ids can index.
57    Exceeds(&'static str, Pos),
58    /// The lowered program fails [`verify`], which is a fault in lowering.
59    Invalid(String),
60}
61
62impl LowerError {
63    pub fn pos(&self) -> Pos {
64        match self {
65            Self::Unsupported(_, pos) | Self::Exceeds(_, pos) => *pos,
66            Self::Invalid(_) => Pos::default(),
67        }
68    }
69}
70
71impl fmt::Display for LowerError {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            Self::Unsupported(what, _) => write!(f, "lowering: {what} is not lowered yet"),
75            Self::Exceeds(what, _) => write!(f, "lowering: {what} exceeds the LIR's limit"),
76            Self::Invalid(why) => write!(f, "lowering: the lowered program is invalid: {why}"),
77        }
78    }
79}
80
81impl From<LowerError> for syntax::Error {
82    fn from(e: LowerError) -> Self {
83        let message = match e {
84            LowerError::Unsupported(..) => syntax::messages::IWR0052,
85            LowerError::Exceeds(..) => syntax::messages::IWL0001,
86            LowerError::Invalid(_) => syntax::messages::IWL0002,
87        };
88        message.at(e.pos(), e.to_string())
89    }
90}
91
92type R<T> = Result<T, LowerError>;
93
94fn unsupported<T>(what: &'static str, pos: Pos) -> R<T> {
95    Err(LowerError::Unsupported(what, pos))
96}
97
98/// The next index of a table, refused past `u32`.
99fn next_id<T>(table: &[T], what: &'static str) -> R<u32> {
100    u32::try_from(table.len()).map_err(|_| LowerError::Exceeds(what, Pos::default()))
101}
102
103fn push<T>(table: &mut Vec<T>, value: T, what: &'static str) -> R<u32> {
104    let id = next_id(table, what)?;
105    table.push(value);
106    Ok(id)
107}
108
109/// Lowers one compiled program, or a class definition with its data and methods. One that passes
110/// Check and uses only the constructs lowered so far lowers.
111pub fn lower(compiled: &Compiled) -> Result<lir::Program, LowerError> {
112    let mut l = Lower::new(compiled);
113    let id = l.sym(&compiled.program.id);
114    let sources = compiled.program.sources.iter().map(|s| l.sym(s)).collect();
115    let storage = l.storage()?;
116    let items = l.items()?;
117    l.services.files = l.files()?;
118    l.services.declaratives = l.declaratives()?;
119    l.services.scope = l.scope()?;
120    l.services.report = l.report_writer()?;
121    (l.sql, l.services.sqlca) = l.sql_table()?;
122    let paragraphs = l.procedure()?;
123    l.unresolved_statuses()?;
124    l.services.entries = l.entry_points()?;
125    l.services.class = l.class_definition()?;
126    l.services.function = l.function_definition()?;
127    let procedure_start = compiled.program.report_writer.procedure_start.min(compiled.program.paragraphs.len());
128    let (blocks, debug) = l.blocks.finish()?;
129    let program = lir::Program {
130        id,
131        options: lir::ProgramOptions {
132            options: compiled.options,
133            ssrange: compiled.ssrange,
134            cards: compiled.program.options.clone(),
135            collating: collating(&compiled.collating),
136            decimal_point_comma: compiled.program.environment.decimal_point_comma,
137            numval_currency: crate::machine::numval_currency(&compiled.program.environment.currency),
138            when_compiled: l.plans.function.iter().any(|f| f.func == lir::Func::WhenCompiled).then_some(compiled.when_compiled),
139        },
140        initial: compiled.program.initial,
141        recursive: compiled.program.recursive,
142        storage,
143        items,
144        paragraphs,
145        procedure_start: procedure_start as u32,
146        ranges: l.ranges,
147        blocks,
148        places: l.places,
149        exprs: l.exprs,
150        conds: l.conds,
151        consts: l.consts,
152        plans: l.plans,
153        services: l.services,
154        sql: l.sql,
155        abends: l.abends,
156        edits: edits(&compiled.layout)?,
157        symbols: l.symbols,
158        debug: lir::Debug { sources, positions: l.positions, ops: debug.ops, statements: debug.statements },
159    };
160    if cfg!(debug_assertions) {
161        verify(&program).map_err(LowerError::Invalid)?;
162    }
163    Ok(program)
164}
165
166/// The tables a program lowers into, each interned where equal entries may be shared.
167struct Lower<'c> {
168    c: &'c Compiled,
169    layout: &'c Layout,
170    program: &'c ast::Program,
171    page: &'static CodePage,
172    symbols: Vec<String>,
173    symbol_ids: HashMap<String, SymId>,
174    positions: Vec<Pos>,
175    position_ids: HashMap<(u16, u32, u32), DebugId>,
176    places: Vec<lir::Place>,
177    /// The layout item each place names, None for RETURN-CODE the program does not declare.
178    place_items: Vec<Option<usize>>,
179    place_ids: HashMap<String, PlaceId>,
180    exprs: Vec<lir::Expr>,
181    conds: Vec<lir::Cond>,
182    consts: Vec<lir::Const>,
183    const_ids: HashMap<String, ConstId>,
184    abends: Vec<lir::AbendText>,
185    abend_ids: HashMap<String, AbendId>,
186    plans: lir::Plans,
187    services: lir::Services,
188    /// Every EXEC SQL block's entry, by ordinal.
189    sql: Vec<lir::SqlEntry>,
190    ranges: Vec<lir::Range>,
191    range_ids: HashMap<(u32, u32, lir::RangeKind), RangeId>,
192    blocks: flow::Blocks,
193    temps: u16,
194    /// Each paragraph's entry block.
195    entries: Vec<BlockId>,
196    /// The block that starts statement k of paragraph p, after a separator period.
197    sentences: HashMap<(usize, usize), BlockId>,
198    /// The block that starts statement k of paragraph p, after an ENTRY statement.
199    entry_blocks: HashMap<(usize, usize), BlockId>,
200    /// The paragraphs an ALTER names.
201    altered: BTreeSet<usize>,
202    /// Whether an ALTER names a paragraph of an independent segment, which makes the segment
203    /// control reaches observable.
204    segments: bool,
205    /// Under the DEBUG option, whether a debugging section serves a paragraph.
206    debugging: bool,
207    /// The arithmetic of the expression being lowered, which its functions' argument expressions
208    /// take part in.
209    within: data::Within,
210}
211
212impl<'c> Lower<'c> {
213    fn new(c: &'c Compiled) -> Self {
214        Self {
215            c,
216            layout: &c.layout,
217            program: &c.program,
218            page: c.options.code_page(),
219            symbols: Vec::new(),
220            symbol_ids: HashMap::new(),
221            positions: Vec::new(),
222            position_ids: HashMap::new(),
223            places: Vec::new(),
224            place_items: Vec::new(),
225            place_ids: HashMap::new(),
226            exprs: Vec::new(),
227            conds: Vec::new(),
228            consts: Vec::new(),
229            const_ids: HashMap::new(),
230            abends: Vec::new(),
231            abend_ids: HashMap::new(),
232            plans: lir::Plans::default(),
233            services: lir::Services::default(),
234            sql: Vec::new(),
235            ranges: Vec::new(),
236            range_ids: HashMap::new(),
237            blocks: flow::Blocks::default(),
238            temps: 0,
239            entries: Vec::new(),
240            sentences: HashMap::new(),
241            entry_blocks: HashMap::new(),
242            altered: BTreeSet::new(),
243            segments: false,
244            debugging: !c.declaratives.triggers.is_empty(),
245            within: data::Within::Own,
246        }
247    }
248
249    fn sym(&mut self, text: &str) -> SymId {
250        if let Some(&id) = self.symbol_ids.get(text) {
251            return id;
252        }
253        let id = self.symbols.len() as SymId;
254        self.symbols.push(text.to_owned());
255        self.symbol_ids.insert(text.to_owned(), id);
256        id
257    }
258
259    fn at(&mut self, pos: Pos) -> DebugId {
260        let key = (pos.file, pos.line, pos.col);
261        if let Some(&id) = self.position_ids.get(&key) {
262            return id;
263        }
264        let id = self.positions.len() as DebugId;
265        self.positions.push(pos);
266        self.position_ids.insert(key, id);
267        id
268    }
269
270    /// An abend's text, with a position of its own only where no op gives one.
271    fn abend(&mut self, code: AbendCode, message: &str, pos: Option<Pos>) -> R<AbendId> {
272        let at = pos.map(|p| self.at(p));
273        let key = format!("{code:?}{at:?}{message}");
274        if let Some(&id) = self.abend_ids.get(&key) {
275            return Ok(id);
276        }
277        let text = lir::AbendText { code, message: self.sym(message), at };
278        let id = push(&mut self.abends, text, "abend messages")?;
279        self.abend_ids.insert(key, id);
280        Ok(id)
281    }
282
283    fn ironwork(&mut self, message: &str) -> R<AbendId> {
284        self.abend(AbendCode::Ironwork, message, None)
285    }
286
287    /// The slab and LOCAL-STORAGE as the walker's own VALUE initialization leaves them, run once
288    /// in a run unit of their own, with what it reported and any abend.
289    fn storage(&mut self) -> R<lir::Storage> {
290        let layout = self.layout;
291        let (size, local) = (layout.size as usize, layout.local_size as usize);
292        let (mut out, mut err) = (Vec::new(), Vec::new());
293        let (image, local_image, abend) = {
294            let mut unit = RunUnit::new(Library::default(), crate::files::Dds::default(), None, Clock::Fixed(0, 0), &mut out, &mut err);
295            let me = unit.add(None, self.program, size);
296            let abend = Machine::unbound(self.c, me, &mut unit).err();
297            let base = unit.programs[me].base;
298            let image = unit.mem[base..base + size].to_vec();
299            let local_image = if local > 0 { unit.mem[unit.mem.len() - local..].to_vec() } else { Vec::new() };
300            (image, local_image, abend)
301        };
302        let init_reports = String::from_utf8_lossy(&err).lines().map(|l| self.sym(l)).collect();
303        let init_abend = match abend {
304            Some(a) => Some(self.abend(a.code.clone(), &a.message, Some(a.pos))?),
305            None => None,
306        };
307        let root = |name: &str| layout.linkage_roots.iter().position(|&i| layout.items[i].name.as_deref() == Some(name));
308        let mut using = Vec::new();
309        // BY VALUE or BY REFERENCE is the caller's to honour, for the walker as for the VM.
310        for ast::Param { by_value: _, name } in &self.program.using {
311            match root(name).map(u16::try_from) {
312                Some(Ok(ordinal)) => using.push(ordinal),
313                Some(Err(_)) => return Err(LowerError::Exceeds("LINKAGE records", Pos::default())),
314                None => return unsupported("PROCEDURE DIVISION USING an item that is not a LINKAGE record", Pos::default()),
315            }
316        }
317        let returning = match &self.program.returning {
318            None => None,
319            Some(name) => match root(name).map(u16::try_from) {
320                Some(Ok(ordinal)) => Some(ordinal),
321                Some(Err(_)) => return Err(LowerError::Exceeds("LINKAGE records", Pos::default())),
322                None => return unsupported("RETURNING an item that is not a LINKAGE record", Pos::default()),
323            },
324        };
325        Ok(lir::Storage {
326            size: layout.size,
327            image,
328            local_image,
329            init_reports,
330            init_abend,
331            linkage: layout.linkage_roots.iter().map(|&i| layout.items[i].size).collect(),
332            using,
333            returning,
334            file_areas: layout.file_areas.clone(),
335            parmcheck: layout.parmcheck,
336        })
337    }
338
339    /// The layout's items for `dump`, with DEPENDING ON objects and keys as item indices.
340    fn items(&mut self) -> R<Vec<lir::Item>> {
341        let layout = self.layout;
342        let mut items = Vec::with_capacity(layout.items.len());
343        for item in &layout.items {
344            let item_of = |r: &ast::Ref| match layout.resolve(&r.name, &r.qualifiers, r.pos) {
345                Ok(Resolved::Item(i)) => Ok(i as u32),
346                _ => unsupported("an OCCURS DEPENDING ON or KEY that names no data item", r.pos),
347            };
348            let depending_on = item.depending_on.as_ref().map(item_of).transpose()?;
349            let keys = item.keys.iter().map(|(ascending, r)| item_of(r).map(|i| (*ascending, i))).collect::<R<_>>()?;
350            items.push(lir::Item {
351                name: item.name.as_deref().map(|n| self.sym(n)),
352                level: item.level,
353                parent: item.parent.map(|p| p as u32),
354                offset: item.offset,
355                size: item.size,
356                occurs: item.occurs,
357                dims: item.dims.clone(),
358                kind: item.kind,
359                local: item.local,
360                linkage: item.linkage,
361                redefines: item.redefines.as_deref().map(|n| self.sym(n)),
362                depending_on,
363                keys,
364                at: self.at(item.pos),
365            });
366        }
367        Ok(items)
368    }
369}
370
371/// The program's sequence, from what `collating::Sequence` exposes: each byte's position, the
372/// character FUNCTION CHAR gives for each position, and HIGH-VALUE and LOW-VALUE.
373fn collating(sequence: &crate::collating::Sequence) -> lir::Collating {
374    if sequence.is_native() {
375        return lir::Collating::Native;
376    }
377    let characters = (1..=sequence.count() as i64).filter_map(|k| sequence.character(k)).collect();
378    lir::Collating::Sequence(lir::Sequence {
379        positions: Box::new(sequence.positions()),
380        characters,
381        high_value: sequence.high_value,
382        low_value: sequence.low_value,
383    })
384}
385
386/// Each edited PICTURE with the currency sign value it shows.
387fn edits(layout: &Layout) -> R<Vec<lir::Edit>> {
388    if layout.edits.len() != layout.currencies.len() {
389        return Err(LowerError::Invalid(format!("{} edited PICTUREs with {} currency values", layout.edits.len(), layout.currencies.len())));
390    }
391    Ok(layout.edits.iter().zip(&layout.currencies).map(|(syms, currency)| lir::Edit { syms: syms.clone(), currency: currency.clone() }).collect())
392}
393
394/// Whether the place cannot abend when evaluated: a slab, LOCAL-STORAGE or RETURN-CODE base and a
395/// constant offset and length.
396fn is_static(place: &lir::Place) -> bool {
397    matches!(place.base, lir::Base::Program | lir::Base::Local | lir::Base::ReturnCode) && place.moved.is_empty() && place.subscripts.is_empty() && place.odo.is_empty() && place.refmod.is_none()
398}