1use crate::abend::Abend;
10use crate::files::{Dds, Open};
11use crate::oo::ClassCode;
12use crate::storage::Loc;
13use crate::taint::Taint;
14use crate::vocab::{OpenMode, Pos};
15use std::collections::{HashMap, HashSet};
16use std::io::{BufRead, Write};
17use std::path::{Path, PathBuf};
18use std::rc::Rc;
19
20pub const ADDRESS_BASE: u32 = 0x0001_0000;
23pub const RETURN_CODE: usize = 0;
25const RESERVED: usize = 8;
26const ALIGNMENT: usize = 8;
27
28pub struct Loaded<H> {
29 pub compiled: Option<H>,
31 pub name: String,
32 pub base: usize,
33 pub size: usize,
34 pub placed: bool,
37 pub files: Vec<Option<Open>>,
38 pub locked: Vec<bool>,
40 pub initialized: bool,
41 pub active: bool,
42 pub dynamic: bool,
44 pub entry: Option<usize>,
47 pub altered: Vec<Option<usize>>,
49 pub source: Option<PathBuf>,
51}
52
53impl<H> Loaded<H> {
54 pub fn new(compiled: Option<H>, name: String, base: usize, size: usize, files: usize) -> Self {
56 let (locked, files) = (vec![false; files], (0..files).map(|_| None).collect());
57 Self { compiled, name, base, size, placed: true, files, locked, initialized: false, active: false, dynamic: false, entry: None, altered: Vec::new(), source: None }
58 }
59
60 fn set_aside(&mut self) -> Held {
63 let files = self.files.iter_mut().map(Option::take).collect();
64 let locked = std::mem::replace(&mut self.locked, vec![false; self.files.len()]);
65 let held = Held { base: self.base, placed: self.placed, files, locked, initialized: self.initialized, active: self.active, dynamic: self.dynamic, altered: std::mem::take(&mut self.altered) };
66 (self.placed, self.initialized, self.active, self.dynamic) = (false, false, false, false);
67 held
68 }
69
70 fn restore(&mut self, held: Held) {
71 (self.base, self.placed, self.files, self.locked, self.altered) = (held.base, held.placed, held.files, held.locked, held.altered);
72 (self.initialized, self.active, self.dynamic) = (held.initialized, held.active, held.dynamic);
73 }
74}
75
76struct Enclave {
81 programs: Vec<Held>,
82 externals: Externals,
83 connectors: HashMap<(usize, usize), Connector>,
84 heap: Vec<(usize, usize, bool)>,
85 random: Option<u32>,
86 return_code: ([u8; 2], bool),
88}
89
90struct Held {
92 base: usize,
93 placed: bool,
94 files: Vec<Option<Open>>,
95 locked: Vec<bool>,
96 initialized: bool,
97 active: bool,
98 dynamic: bool,
99 altered: Vec<Option<usize>>,
100}
101
102pub enum LoadError {
103 NotFound,
104 Compile(String),
106}
107
108pub struct LoadedProgram<H> {
110 pub compiled: H,
111 pub name: String,
113 pub files: usize,
114 pub size: usize,
115 pub source: Option<PathBuf>,
117}
118
119pub struct FoundClass<C> {
122 pub code: C,
123 pub sources: Vec<String>,
124}
125
126pub trait Loader<H> {
128 fn program(&mut self, name: &str) -> Result<LoadedProgram<H>, LoadError>;
130
131 fn holder(&self, entry: &str) -> Option<String>;
133
134 fn entry(program: &H, name: &str) -> Option<usize>;
136
137 fn shape(program: &H) -> (usize, usize);
139
140 fn nested(program: &H) -> &[String];
142
143 fn source(program: &H, file: usize) -> Option<String>;
145
146 fn class(&mut self, external: &str) -> Result<Option<FoundClass<Rc<ClassCode<H>>>>, String>;
149
150 fn mapset(&mut self, name: &str) -> Option<Result<crate::bms::Mapset, String>>;
153}
154
155pub trait UnitHost<'w> {
158 type Program: Clone;
159 type Loader: Loader<Self::Program>;
160 fn unit(&mut self) -> &mut RunUnit<'w, Self::Program, Self::Loader>;
161}
162
163#[derive(Clone, Copy, Debug)]
164pub enum Clock {
165 System,
166 Fixed(i64, u32),
168}
169
170pub enum Event<'a> {
174 Open { dd: &'a str, mode: OpenMode, path: &'a Path },
175 Close { dd: &'a str, path: &'a Path },
176 Load { program: &'a str, source: Option<&'a Path> },
177 Paragraph { program: &'a str, name: &'a str, index: usize },
179 Sink { kind: &'static str, file: &'a str, line: u32, operand: &'a str, input: Option<bool> },
184 Statement { file: &'a str, line: u32 },
186}
187
188#[derive(Clone, Debug, PartialEq, Eq)]
191pub enum StatementFilter {
192 All,
193 Lines(HashSet<u32>),
194}
195
196pub type Observer<'w> = Box<dyn FnMut(Event<'_>) + 'w>;
197
198pub const MAX_DEPTH: usize = 100;
200
201#[derive(Default)]
205pub struct Externals {
206 storage: HashMap<(bool, String), (usize, usize)>,
209 files: Vec<Option<Open>>,
210 locked: Vec<bool>,
212 file_names: HashMap<String, usize>,
213}
214
215#[derive(Clone, Copy, Debug, PartialEq, Eq)]
217pub enum Connector {
218 External(usize),
220 Program(usize, usize),
222}
223
224pub const OS_COMMAND_ROUTINES: &[&str] = &["SYSTEM", "C$SYSTEM", "CBL_EXEC_RUN_UNIT", "CBL_GC_HOSTED", "BXPSYSTM"];
227
228pub struct RunUnit<'w, H, L: Loader<H>> {
229 pub mem: Vec<u8>,
230 pub depth: usize,
232 pub programs: Vec<Loaded<H>>,
233 names: HashMap<String, usize>,
234 pub library: L,
235 pub dds: Dds,
236 pub sysin: Option<Box<dyn BufRead + 'w>>,
237 pub clock: Clock,
238 pub out: &'w mut dyn Write,
239 pub err: &'w mut dyn Write,
240 pub cics: Option<crate::cics::Task>,
242 pub eib: usize,
243 pub cics_files: HashMap<String, Open>,
244 pub sql: Option<crate::sql::Session<'w>>,
246 pub le: crate::le::State,
248 pub oo: crate::oo::Objects<Rc<ClassCode<H>>>,
250 pub observer: Option<Observer<'w>>,
252 pub random: Option<u32>,
254 externals: Externals,
255 connectors: HashMap<(usize, usize), Connector>,
257 pub entries: Vec<crate::set::Entry>,
259 pub statements: Option<StatementFilter>,
261 pub taint: Option<Taint>,
263 pub statement_limit: Option<u64>,
265 set_aside: Vec<Enclave>,
267}
268
269fn end_file(f: Open, unclosed: bool) -> std::io::Result<()> {
270 if unclosed { f.abandon() } else { f.close() }
271}
272
273impl<H, L: Loader<H>> RunUnit<'_, H, L> {
274 pub fn write(&mut self, offset: usize, bytes: &[u8]) {
278 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
279 self.mark(offset, bytes.len());
280 }
281
282 pub fn write_input(&mut self, offset: usize, bytes: &[u8]) {
284 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
285 self.mark_input(offset, bytes.len(), true);
286 }
287
288 pub fn mark(&mut self, offset: usize, len: usize) {
290 if let Some(t) = self.taint.as_mut() {
291 let pending = t.pending();
292 t.set(offset, len, pending);
293 }
294 }
295
296 pub fn mark_input(&mut self, offset: usize, len: usize, input: bool) {
298 if let Some(t) = self.taint.as_mut() {
299 t.set(offset, len, input);
300 }
301 }
302
303 pub fn taint_read(&mut self, loc: Loc) {
305 if let Some(t) = self.taint.as_mut() {
306 t.read(loc.offset, loc.len);
307 }
308 }
309
310 pub fn writing(&mut self, on: bool) -> bool {
312 self.taint.as_mut().is_some_and(|t| t.writing(on))
313 }
314
315 pub fn statement_starts(&mut self) {
317 if let Some(t) = self.taint.as_mut() {
318 t.start_statement();
319 }
320 }
321
322 pub fn take_input(&mut self) {
324 if let Some(t) = self.taint.as_mut() {
325 t.take_input();
326 }
327 }
328
329 pub fn holds_input(&self, offset: usize, len: usize) -> bool {
331 self.taint.as_ref().is_some_and(|t| t.any(offset, len))
332 }
333
334 pub fn pending(&self) -> bool {
336 self.taint.as_ref().is_some_and(Taint::pending)
337 }
338
339 pub fn resume_statement(&mut self, read_before: bool) {
341 if let Some(t) = self.taint.as_mut() {
342 t.resume_statement(read_before);
343 }
344 }
345
346 pub fn unfollowed(&mut self, what: &'static str) {
348 if let Some(t) = self.taint.as_mut() {
349 t.unfollowed(what);
350 }
351 }
352
353 pub fn input_at_sink(&self) -> Option<bool> {
355 self.taint.as_ref().and_then(Taint::at_sink)
356 }
357}
358
359impl<'w, H: Clone, L: Loader<H>> RunUnit<'w, H, L> {
360 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 {
361 Self {
362 mem: vec![0; RESERVED],
363 depth: 0,
364 programs: Vec::new(),
365 names: HashMap::new(),
366 library,
367 dds,
368 sysin,
369 clock,
370 out,
371 err,
372 cics: None,
373 eib: 0,
374 cics_files: HashMap::new(),
375 sql: None,
376 le: crate::le::State::default(),
377 oo: Default::default(),
378 observer: None,
379 random: None,
380 externals: Externals::default(),
381 connectors: HashMap::new(),
382 entries: Vec::new(),
383 statements: None,
384 taint: None,
385 statement_limit: None,
386 set_aside: Vec::new(),
387 }
388 }
389
390 fn allocate(&mut self, size: usize) -> usize {
391 let base = self.mem.len().div_ceil(ALIGNMENT) * ALIGNMENT;
392 self.mem.resize(base + size, 0);
393 base
394 }
395
396 pub fn add_named(&mut self, compiled: Option<H>, name: String, files: usize, size: usize) -> usize {
398 let base = self.allocate(size);
399 let index = self.programs.len();
400 self.names.insert(name.clone(), index);
401 self.programs.push(Loaded::new(compiled, name, base, size, files));
402 index
403 }
404
405 pub fn begin_cics_run_unit(&mut self) {
410 let programs = self.programs.iter_mut().map(Loaded::set_aside).collect();
411 let (externals, connectors) = (std::mem::take(&mut self.externals), std::mem::take(&mut self.connectors));
412 let return_code = ([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]], self.holds_input(RETURN_CODE, 2));
413 self.mem[RETURN_CODE..RETURN_CODE + 2].fill(0);
414 self.mark_input(RETURN_CODE, 2, false);
415 let (heap, random) = (std::mem::take(&mut self.le.heap), self.random.take());
416 self.set_aside.push(Enclave { programs, externals, connectors, heap, random, return_code });
417 }
418
419 pub fn end_cics_run_unit(&mut self, xctl: bool) -> Result<(), String> {
425 let mut closed = Ok(());
426 for program in &mut self.programs {
427 for f in program.files.iter_mut().filter_map(Option::take) {
428 if let Err(e) = f.close() {
429 closed = closed.and(Err(format!("closing a file of {}: {e}", program.name)));
430 }
431 }
432 drop(program.set_aside());
433 }
434 closed = closed.and(self.close_external_files(false));
435 let Some(enclave) = self.set_aside.pop() else { return closed };
436 for (program, held) in self.programs.iter_mut().zip(enclave.programs) {
437 program.restore(held);
438 }
439 (self.externals, self.connectors, self.le.heap, self.random) = (enclave.externals, enclave.connectors, enclave.heap, enclave.random);
440 if !xctl {
441 let (bytes, input) = enclave.return_code;
442 self.mem[RETURN_CODE..RETURN_CODE + 2].copy_from_slice(&bytes);
443 self.mark_input(RETURN_CODE, 2, input);
444 }
445 closed
446 }
447
448 pub fn load_entry(&mut self, name: &str, dynamic: bool) -> Result<(usize, Option<usize>), LoadError> {
452 let name = name.to_ascii_uppercase();
453 if let Some(i) = self.find(&name) {
454 return Ok((i, self.programs[i].entry));
455 }
456 let index = match self.programs.iter().position(|p| p.compiled.as_ref().is_some_and(|c| L::entry(c, &name).is_some())) {
457 Some(i) => i,
458 None => {
459 let holder = self.library.holder(&name);
460 self.load(holder.as_deref().unwrap_or(&name))?
461 }
462 };
463 let Some(compiled) = self.programs[index].compiled.clone() else { return Ok((index, None)) };
464 let Some(entry) = L::entry(&compiled, &name) else { return Ok((index, None)) };
465 if !dynamic {
466 return Ok((index, Some(entry)));
467 }
468 let (files, size) = L::shape(&compiled);
469 let copy = self.add_named(Some(compiled), name, files, size);
470 self.programs[copy].entry = Some(entry);
471 self.programs[copy].source = self.programs[index].source.clone();
472 Ok((copy, Some(entry)))
473 }
474
475 pub fn push_temporary(&mut self, bytes: &[u8]) -> usize {
478 let at = self.allocate(bytes.len());
479 self.write(at, bytes);
480 at
481 }
482
483 pub fn release_temporaries(&mut self, mark: usize) {
486 let external = self.externals.storage.values().all(|&(at, _)| at < mark);
487 if self.programs.iter().all(|p| !p.placed || p.base + p.size <= mark) && self.le.heap_end() <= mark && external {
488 self.mem.truncate(mark.max(RESERVED));
489 if let Some(t) = self.taint.as_mut() {
490 t.truncate(self.mem.len());
491 }
492 }
493 }
494
495 pub fn enter(&mut self, pos: Pos) -> Result<(), Abend> {
497 if self.depth >= MAX_DEPTH {
498 return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {MAX_DEPTH}"), pos));
499 }
500 self.depth += 1;
501 Ok(())
502 }
503
504 pub fn activate(&mut self, me: usize, initial: bool) -> (usize, bool) {
508 if !self.programs[me].placed {
509 let base = self.allocate(self.programs[me].size);
510 (self.programs[me].base, self.programs[me].placed) = (base, true);
511 }
512 let program = &mut self.programs[me];
513 program.active = true;
514 (program.base, !program.initialized || initial)
515 }
516
517 pub fn initialized(&mut self, me: usize) {
519 self.programs[me].initialized = true;
520 self.programs[me].altered.clear();
521 }
522
523 pub fn find(&self, name: &str) -> Option<usize> {
524 self.names.get(&name.to_ascii_uppercase()).copied()
525 }
526
527 pub fn load(&mut self, name: &str) -> Result<usize, LoadError> {
529 let name = name.to_ascii_uppercase();
530 if let Some(i) = self.find(&name) {
531 return Ok(i);
532 }
533 let loaded = self.library.program(&name)?;
534 self.notify(Event::Load { program: &name, source: loaded.source.as_deref() });
535 let index = self.add_named(Some(loaded.compiled), loaded.name, loaded.files, loaded.size);
536 self.programs[index].source = loaded.source;
537 Ok(index)
538 }
539
540 pub const fn observed(&self) -> bool {
541 self.observer.is_some()
542 }
543
544 pub fn start_statement(&mut self, pos: Pos) -> Result<(), Abend> {
548 match self.statement_limit.as_mut() {
549 Some(0) => Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: "the run reached its statement limit, as a step past its TIME= ends".into(), pos, file: None }),
550 Some(left) => {
551 *left -= 1;
552 Ok(())
553 }
554 None => Ok(()),
555 }
556 }
557
558 pub fn traces(&self, line: u32) -> bool {
559 match &self.statements {
560 None => false,
561 Some(StatementFilter::All) => self.observer.is_some(),
562 Some(StatementFilter::Lines(lines)) => self.observer.is_some() && lines.contains(&line),
563 }
564 }
565
566 pub fn notify(&mut self, event: Event<'_>) {
567 if let Some(observer) = self.observer.as_mut() {
568 observer(event);
569 }
570 }
571
572 pub fn close_all(&mut self, unclosed: bool) -> Result<(), String> {
577 for program in &mut self.programs {
578 for f in program.files.iter_mut().filter_map(Option::take) {
579 end_file(f, unclosed).map_err(|e| format!("closing a file of {}: {e}", program.name))?;
580 }
581 }
582 self.close_external_files(unclosed)
583 }
584
585 fn close_external_files(&mut self, unclosed: bool) -> Result<(), String> {
587 let mut closed = Ok(());
588 for (name, &k) in &self.externals.file_names {
589 if let Some(f) = self.externals.files[k].take()
590 && let Err(e) = end_file(f, unclosed)
591 {
592 closed = closed.and(Err(format!("closing EXTERNAL file {name}: {e}")));
593 }
594 }
595 closed
596 }
597
598 pub fn external(&mut self, name: &str, file: bool, size: usize) -> Result<usize, String> {
602 let key = (file, name.to_owned());
603 if let Some(&(at, had)) = self.externals.storage.get(&key) {
604 return if had == size {
605 Ok(at)
606 } else {
607 let what = if file { "the record area of EXTERNAL file" } else { "EXTERNAL record" };
608 Err(format!("{what} {name} has {had} bytes in the run unit, and this program describes {size}"))
609 };
610 }
611 let at = self.allocate(size);
612 self.externals.storage.insert(key, (at, size));
613 Ok(at)
614 }
615
616 pub fn external_file(&mut self, name: &str) -> Connector {
618 let (files, locked) = (&mut self.externals.files, &mut self.externals.locked);
619 let k = *self.externals.file_names.entry(name.to_owned()).or_insert_with(|| {
620 files.push(None);
621 locked.push(false);
622 files.len() - 1
623 });
624 Connector::External(k)
625 }
626
627 pub fn connect(&mut self, me: usize, k: usize, to: Connector) {
629 self.connectors.insert((me, k), to);
630 }
631
632 fn connector(&self, mut me: usize, mut k: usize) -> Option<Connector> {
633 let mut to = None;
634 while let Some(&c) = self.connectors.get(&(me, k)) {
635 to = Some(c);
636 match c {
637 Connector::External(_) => break,
638 Connector::Program(p, j) => (me, k) = (p, j),
639 }
640 }
641 to
642 }
643
644 pub fn file(&mut self, me: usize, k: usize) -> &mut Option<Open> {
646 match self.connector(me, k) {
647 None => &mut self.programs[me].files[k],
648 Some(Connector::External(e)) => &mut self.externals.files[e],
649 Some(Connector::Program(p, j)) => &mut self.programs[p].files[j],
650 }
651 }
652
653 pub fn locked(&mut self, me: usize, k: usize) -> &mut bool {
655 match self.connector(me, k) {
656 None => &mut self.programs[me].locked[k],
657 Some(Connector::External(e)) => &mut self.externals.locked[e],
658 Some(Connector::Program(p, j)) => &mut self.programs[p].locked[j],
659 }
660 }
661
662 pub fn file_ref(&self, me: usize, k: usize) -> &Option<Open> {
663 match self.connector(me, k) {
664 None => &self.programs[me].files[k],
665 Some(Connector::External(e)) => &self.externals.files[e],
666 Some(Connector::Program(p, j)) => &self.programs[p].files[j],
667 }
668 }
669
670 pub fn return_code(&self) -> i16 {
671 i16::from_be_bytes([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]])
672 }
673
674 pub fn now(&self) -> (i64, u32) {
676 match self.clock {
677 Clock::Fixed(s, h) => (s, h),
678 Clock::System => {
679 let d = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
680 (d.as_secs() as i64, d.subsec_millis() / 10)
681 }
682 }
683 }
684}