use super::{Code, Halt, Lowered, R, Vm, not_yet};
use crate::abend::{Abend, AbendCode, Ending};
use crate::callee::{self, Arguments, Bindings, By, Callee};
use crate::cics;
use crate::le::{self, LeHost};
use crate::lir::{Base, CallArg, CallPlan, CallTarget, Chars, LeService, Operand, PlaceId, SenderCheck, Step};
use crate::parmcheck;
use crate::set;
use crate::storage::{Kind, Loc, Val};
use crate::store;
use crate::unit::{Event, LoadError, Loader, OS_COMMAND_ROUTINES, RETURN_CODE, RunUnit, UnitHost};
use crate::virtual_printer::{self, Job};
use crate::vocab::Pos;
use numeric::precision::{Fixed, Places};
use std::rc::Rc;
use zarch::ebcdic::CodePage;
impl<'p, L: Loader<Rc<Code>>> Vm<'p, '_, '_, L> {
pub(super) fn call(&mut self, plan: &'p CallPlan, pos: Pos) -> R<Step> {
let dynam = self.p.options.options.dynam;
let (name, variable, dynamic) = match &plan.target {
CallTarget::Pointer(pointer) => {
if self.entry_in(*pointer)?.is_some() {
return Err(not_yet("a CALL through a JNI function-pointer SET TO ENTRY, whose arguments the LIR keeps by value"));
}
return self.call_through_pointer(plan, *pointer, pos);
}
CallTarget::Entry(pointer) => match self.entry_in(*pointer)? {
Some(entry) => (entry.name, false, entry.dynamic),
None if plan.args.iter().all(|a| matches!(a, CallArg::Value(_) | CallArg::Omitted)) => return self.call_through_pointer(plan, *pointer, pos),
None => return Err(not_yet("a CALL BY REFERENCE or BY CONTENT through a pointer that holds no entry, which the walker calls as a JNI service")),
},
CallTarget::Named { name, .. } => (self.sym(*name).to_owned(), false, dynam),
CallTarget::Dynamic(o) => (self.program_name(*o, pos)?, true, true),
};
if self.unit.observed() {
if variable {
self.sink("dynamic-program-load", pos, &name);
}
if OS_COMMAND_ROUTINES.contains(&name.as_str()) {
let text = callee::arguments_text(self, &plan.args, pos);
match self.settle(text) {
Ok(text) => self.sink("os-command", pos, &text),
Err(Halt::Unimplemented(what)) => return Err(Halt::Unimplemented(what)),
Err(Halt::Abend(_)) => {}
}
}
}
let (index, entry) = match self.unit.load_entry(&name, dynamic) {
Ok(found) => found,
Err(LoadError::NotFound) => {
if let Some(service) = le::service(&name) {
return self.le_call(plan, service, &name, pos);
}
if let Some(step) = self.virtual_print(plan, &name, pos)? {
return Ok(step);
}
if plan.on_exception {
return Ok(Step::Arm(1));
}
return Err(Abend { code: AbendCode::ModuleNotFound, message: le::missing(&name), pos, file: None }.into());
}
Err(LoadError::Compile(message)) => return Err(Abend::ironwork(format!("CALL {name}: {message}"), pos).into()),
};
let held = self.unit.programs[index].compiled.clone();
let lowered = match held.as_deref() {
Some(code) => code.lowered.as_ref().map_err(|why| not_yet(format!("CALL of a program that does not lower ({why})")))?,
None => self.first.ok_or_else(|| Abend::ironwork(format!("CALL {name}: the run unit's first program cannot be CALLed from a function or a method"), pos))?,
};
self.unit.programs[index].dynamic |= dynamic;
let p = &lowered.program;
if self.unit.programs[index].active && !p.recursive {
let unit = p.services.scope.containers.last().unwrap_or(&p.id);
return Err(callee::recursive_call(&p.symbols[p.id as usize], &p.symbols[*unit as usize], pos).into());
}
self.unit.enter(pos)?;
let suspends = dynamic && lowered.program.services.scope.containers.is_empty();
let result = self.call_nested(plan, index, entry, lowered, suspends, pos);
self.unit.depth = self.unit.depth.saturating_sub(1);
result
}
fn call_nested(&mut self, plan: &CallPlan, index: usize, entry: Option<usize>, lowered: &Lowered, suspends: bool, pos: Pos) -> R<Step> {
let mark = self.unit.mem.len();
let addresses = callee::addresses(self, &plan.args, pos);
let addresses = self.settle(addresses)?;
self.parmcheck_set();
let program = &lowered.program;
let containers = self.containers_of(program);
let by = By::Call { initial: program.initial };
let (ending, returned) = callee::run(self, &Callee { index, by, mark: Some(mark), pos }, |caller| {
let mut vm = Vm::activation_within(lowered, index, &mut *caller.unit, false, containers)?;
let entry = entry.and_then(|k| program.services.entries.get(k));
let using = entry.map_or(&program.storage.using, |e| &e.using).iter().map(|&o| Some(usize::from(o))).collect();
let returning = program.storage.returning.map(|o| (usize::from(o), program.storage.linkage[usize::from(o)] as usize));
Bindings { records: &[], using, addresses: &addresses, returning }.bind(vm.unit, &mut vm.linkage);
(vm.cics_handlers, vm.first) = (caller.cics_handlers.lend(suspends), caller.first);
let ending = match vm.run_called(entry.map(|e| (e.paragraph, e.block))) {
Err(Halt::Unimplemented(what)) => return Err(Halt::Unimplemented(what)),
Err(Halt::Abend(a)) => Err(a),
Ok(e) => Ok(e),
};
caller.cics_handlers.take_back(&mut vm.cics_handlers, suspends && ending.is_ok());
let returned = match (program.storage.returning, &ending) {
(Some(ordinal), Ok(_)) => Some(vm.returned(ordinal, pos)?),
_ => None,
};
Ok((ending, returned))
})?;
if ending? == Ending::StopRun {
return Ok(Step::End(Ending::StopRun));
}
if cics::level_ended(self.unit) {
return Ok(Step::End(Ending::Goback));
}
self.parmcheck_test(plan, &addresses, |unit| unit.programs[index].name.clone(), pos)?;
if let (Some(target), Some(val)) = (plan.returning, returned) {
let dest = self.loc_written(target)?;
store::assign(&self.facts(), self.unit, dest, val, None, pos)?;
}
Ok(if plan.on_exception || plan.not_on_exception { Step::Arm(0) } else { Step::Next })
}
fn le_call(&mut self, plan: &CallPlan, service: LeService, name: &str, pos: Pos) -> R<Step> {
let mark = self.unit.mem.len();
let addresses = callee::addresses(self, &plan.args, pos);
let ran = match self.settle(addresses) {
Ok(args) => {
self.parmcheck_set();
le::call(self, service, &args, pos).map(|()| args).map_err(Halt::Abend)
}
Err(halt) => Err(halt),
};
self.unit.release_temporaries(mark);
self.parmcheck_test(plan, &ran?, |_| name.to_owned(), pos)?;
Ok(if plan.on_exception || plan.not_on_exception { Step::Arm(0) } else { Step::Next })
}
pub(super) fn parmcheck_set(&mut self) {
parmcheck::set(self.unit, self.base, self.p.storage.parmcheck);
}
pub(super) fn parmcheck_test(&mut self, plan: &CallPlan, addresses: &[Option<usize>], called: impl FnOnce(&RunUnit<'_, Rc<Code>, L>) -> String, pos: Pos) -> R<()> {
let p = self.p;
let arguments = plan.args.iter().zip(addresses).filter_map(|(arg, &address)| match (arg, address) {
(CallArg::Reference(q) | CallArg::Content(Chars::Place(q)) | CallArg::Value(Operand::Load(q)), Some(a)) => Some((a, p.symbols[p.places[*q as usize].name as usize].as_str())),
_ => None,
});
let abd = p.options.options.parmcheck.is_some_and(|c| c.abd);
Ok(parmcheck::test(self.unit, self.base, p.storage.parmcheck, arguments, called, &p.symbols[p.id as usize], abd, pos)?)
}
fn virtual_print(&mut self, plan: &CallPlan, name: &str, pos: Pos) -> R<Option<Step>> {
if !virtual_printer::ROUTINES.contains(&name) {
return Ok(None);
}
let Some(printer) = self.unit.dds.get(virtual_printer::DD) else { return Ok(None) };
let text = callee::arguments_text(self, &plan.args, pos);
let job = match self.settle(text) {
Ok(text) => Job::parse(&text),
Err(Halt::Abend(_)) => None,
Err(halt) => return Err(halt),
};
let Some(job) = job else { return Ok(None) };
let dds = self.unit.dds.clone();
let status: i16 = match virtual_printer::print(&dds, &printer, &job, &mut |event| self.unit.notify(event)) {
Ok(()) => 0,
Err(why) => {
let _ = writeln!(self.unit.err, "ironwork: {pos}: CALL {name}: the virtual printer printed nothing: {why}");
1
}
};
match plan.returning {
Some(target) => {
let dest = self.loc(target)?;
store::assign(&self.facts(), self.unit, dest, Val::Num(Fixed::new(i128::from(status), Places::new(9, 0))), None, pos)?;
}
None => self.unit.write(RETURN_CODE, &status.to_be_bytes()),
}
Ok(Some(if plan.on_exception || plan.not_on_exception { Step::Arm(0) } else { Step::Next }))
}
pub(super) fn returned(&mut self, ordinal: u16, pos: Pos) -> R<Val> {
let p = self.p;
let Some(offset) = self.linkage[usize::from(ordinal)] else { return Err(not_yet("a RETURNING item with no storage")) };
let len = p.storage.linkage[usize::from(ordinal)];
let odo = p.items.iter().any(|i| i.linkage == Some(ordinal) && i.depending_on.is_some());
let whole = |q: &crate::lir::Place| {
q.base == Base::Linkage(ordinal) && q.offset == 0 && q.len == len && q.subscripts.is_empty() && q.refmod.is_none() && (!q.odo.is_empty() || !odo)
};
let loc = match p.places.iter().position(whole) {
Some(q) => self.loc(q as u32)?,
None if odo => return Err(not_yet("a RETURNING record holding an OCCURS DEPENDING ON table it never names as a sender")),
None => {
let kind = p.items.iter().find(|i| i.linkage == Some(ordinal) && i.parent.is_none()).map_or(Kind::Group, |i| i.kind);
Loc { offset, len: len as usize, kind, item: usize::MAX }
}
};
self.unit.taint_read(loc);
self.read(loc, pos)
}
fn entry_in(&mut self, pointer: PlaceId) -> R<Option<set::Entry>> {
let loc = self.loc(pointer)?;
let Ok(value) = <[u8; 4]>::try_from(store::bytes(&self.unit.mem, loc)).map(u32::from_be_bytes) else { return Ok(None) };
Ok(set::entry_of(&self.unit.entries, value).cloned())
}
pub(super) fn set_entry(&mut self, entry: Operand, targets: &[PlaceId], pos: Pos) -> R<()> {
let name = self.program_name(entry, pos)?;
let variable = !matches!(entry, Operand::Const(_));
if variable && self.unit.observed() {
self.sink("dynamic-program-load", pos, &name);
}
let dynamic = self.p.options.options.dynam || variable;
match self.unit.load_entry(&name, dynamic) {
Ok(_) => {}
Err(LoadError::NotFound) if le::provides(&name) => {}
Err(LoadError::NotFound) => return Err(Abend { code: AbendCode::ModuleNotFound, message: le::missing(&name), pos, file: None }.into()),
Err(LoadError::Compile(message)) => return Err(Abend::ironwork(format!("SET TO ENTRY {name}: {message}"), pos).into()),
}
let value = set::entry(&mut self.unit.entries, &name, dynamic, pos)?;
for &target in targets {
let dest = self.loc_written(target)?;
store::assign(&self.facts(), self.unit, dest, Val::Address(value), None, pos)?;
}
Ok(())
}
pub(super) fn cancel(&mut self, name: Operand, pos: Pos) -> R<()> {
let name = self.program_name(name, pos)?;
Ok(callee::cancel(self.unit, &name, pos)?)
}
pub(super) fn sink(&mut self, kind: &'static str, pos: Pos, operand: &str) {
let file = self.event_file(pos);
let input = self.unit.input_at_sink();
self.unit.notify(Event::Sink { kind, file: &file, line: pos.line, operand, input });
}
pub(super) fn event_file(&self, pos: Pos) -> String {
match (pos.file, &self.unit.programs[self.me].source) {
(0, Some(path)) => path.to_str().unwrap_or_default().to_owned(),
(i, _) => self.p.debug.sources.get(usize::from(i)).map_or_else(String::new, |&s| self.sym(s).to_owned()),
}
}
}
impl<'w, L: Loader<Rc<Code>>> UnitHost<'w> for Vm<'_, '_, 'w, L> {
type Program = Rc<Code>;
type Loader = L;
fn unit(&mut self) -> &mut RunUnit<'w, Rc<Code>, L> {
self.unit
}
}
impl<'w, L: Loader<Rc<Code>>> LeHost<'w> for Vm<'_, '_, 'w, L> {
fn page(&self) -> &'static CodePage {
self.p.options.options.code_page()
}
fn method_name(program: &Rc<Code>) -> Option<String> {
program.method.clone()
}
}
impl<'w, L: Loader<Rc<Code>>> Arguments<'w, PlaceId, Operand> for Vm<'_, '_, 'w, L> {
fn item(&self, operand: &Operand) -> Option<PlaceId> {
match *operand {
Operand::Load(place) => Some(place),
_ => None,
}
}
fn length_of(&self, operand: &Operand) -> bool {
matches!(operand, Operand::LengthOf(_))
}
fn content_item(&mut self, place: PlaceId) -> Result<Loc, Abend> {
let pos = self.pos(self.p.places[place as usize].at);
let tested = self.loc(place).and_then(|loc| self.numcheck(loc, SenderCheck::Item, pos).map(|()| loc));
self.lift(tested, pos)
}
}