Skip to content

Commit 305c782

Browse files
committed
[assembler] Distinguish single and multiple RC-word reservations.
The pipe construct can reserve only one RC-word, and this change uses type information to enforce this.
1 parent 4bb5a87 commit 305c782

File tree

5 files changed

+291
-128
lines changed

5 files changed

+291
-128
lines changed

assembler/src/asmlib/ast.rs

Lines changed: 91 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -335,14 +335,100 @@ impl ConfigValue {
335335
}
336336
}
337337

338+
#[derive(Debug, Clone, PartialEq, Eq)]
339+
pub(crate) struct RegistersContaining(Vec<RegisterContaining>);
340+
341+
impl RegistersContaining {
342+
pub(crate) fn from_words(words: Vec<RegisterContaining>) -> RegistersContaining {
343+
Self(words)
344+
}
345+
346+
pub(crate) fn words(&self) -> &[RegisterContaining] {
347+
&self.0
348+
}
349+
350+
fn symbol_uses(
351+
&self,
352+
block_id: BlockIdentifier,
353+
block_offset: Unsigned18Bit,
354+
) -> impl Iterator<Item = (SymbolName, Span, SymbolUse)> + use<'_> {
355+
self.0
356+
.iter()
357+
.flat_map(move |rc| rc.symbol_uses(block_id, block_offset))
358+
}
359+
}
360+
361+
#[derive(Debug, Clone, PartialEq, Eq)]
362+
pub(crate) struct RegisterContaining(Box<TaggedProgramInstruction>);
363+
364+
impl From<TaggedProgramInstruction> for RegisterContaining {
365+
fn from(inst: TaggedProgramInstruction) -> Self {
366+
Self(Box::new(inst))
367+
}
368+
}
369+
370+
impl RegisterContaining {
371+
pub(crate) fn instruction(&self) -> &TaggedProgramInstruction {
372+
&self.0
373+
}
374+
375+
fn symbol_uses(
376+
&self,
377+
block_id: BlockIdentifier,
378+
block_offset: Unsigned18Bit,
379+
) -> impl Iterator<Item = (SymbolName, Span, SymbolUse)> + use<'_> {
380+
// Tags defined inside the RC-word are not counted as
381+
// defined outside it. But if we refer to a symbol
382+
// inside the RC-word it counts as a reference for the
383+
// purpose of determining which contexts it has been
384+
// used in.
385+
let mut result = Vec::new();
386+
for symbol_use in self.0.symbol_uses(block_id, block_offset) {
387+
let (name, span, symbol_definition) = symbol_use;
388+
match symbol_definition {
389+
def @ SymbolUse::Reference(_) => {
390+
result.push((name, span, def));
391+
}
392+
SymbolUse::Definition(tagdef @ SymbolDefinition::Tag { .. }) => {
393+
result.push((name, span, SymbolUse::LocalDefinition(tagdef)));
394+
}
395+
SymbolUse::LocalDefinition(_) => {
396+
panic!("found local definition inside RC-word, don't know how to handle this.");
397+
}
398+
SymbolUse::Origin(_name, _block) => {
399+
unreachable!("Found origin {name} inside an RC-word; the parser should have rejected this.");
400+
}
401+
SymbolUse::Definition(_) => {
402+
// e.g. we have an input like
403+
//
404+
// { X = 2 }
405+
//
406+
//
407+
// Ideally we would issue an error for
408+
// this, but since this function cannot
409+
// fail, it's better to do that at the
410+
// time we parse the RC-word reference
411+
// (thus eliminating this case).
412+
//
413+
// When working on this case we should
414+
// figure out if an equality is allowed
415+
// inside a macro expansion.
416+
panic!("Found unexpected definition of {name} inside RC-word reference at {span:?}");
417+
}
418+
}
419+
}
420+
result.into_iter()
421+
}
422+
}
423+
338424
/// Eventually we will support real expressions, but for now we only
339425
/// suport literals and references to symbols ("equalities" in the
340426
/// User Handbook).
341427
#[derive(Debug, Clone, PartialEq, Eq)]
342428
pub(crate) enum Atom {
343429
SymbolOrLiteral(SymbolOrLiteral),
344430
Parens(Span, Script, Box<ArithmeticExpression>),
345-
RcRef(Span, Vec<TaggedProgramInstruction>),
431+
RcRef(Span, RegistersContaining),
346432
}
347433

348434
impl From<(Span, Script, SymbolName)> for Atom {
@@ -385,49 +471,8 @@ impl Atom {
385471
Atom::Parens(_span, _script, expr) => {
386472
result.extend(expr.symbol_uses(block_id, block_offset));
387473
}
388-
Atom::RcRef(_span, tagged_instructions) => {
389-
// Tags defined inside the RC-word are not counted as
390-
// defined outside it. But if we refer to a symbol
391-
// inside the RC-word it counts as a reference for the
392-
// purpose of determining which contexts it has been
393-
// used in.
394-
for symbol_use in tagged_instructions
395-
.iter()
396-
.flat_map(|instr| instr.symbol_uses(block_id, block_offset))
397-
{
398-
let (name, span, symbol_definition) = symbol_use;
399-
match symbol_definition {
400-
def @ SymbolUse::Reference(_) => {
401-
result.push((name, span, def));
402-
}
403-
SymbolUse::Definition(tagdef @ SymbolDefinition::Tag { .. }) => {
404-
result.push((name, span, SymbolUse::LocalDefinition(tagdef)));
405-
}
406-
SymbolUse::LocalDefinition(_) => {
407-
panic!("found local definition inside RC-word, don't know how to handle this.");
408-
}
409-
SymbolUse::Origin(_name, _block) => {
410-
unreachable!("Found origin {name} inside an RC-word; the parser should have rejected this.");
411-
}
412-
SymbolUse::Definition(_) => {
413-
// e.g. we have an input like
414-
//
415-
// { X = 2 }
416-
//
417-
//
418-
// Ideally we would issue an error for
419-
// this, but since this function cannot
420-
// fail, it's better to do that at the
421-
// time we parse the RC-word reference
422-
// (thus eliminating this case).
423-
//
424-
// When working on this case we should
425-
// figure out if an equality is allowed
426-
// inside a macro expansion.
427-
panic!("Found unexpected definition of {name} inside RC-word reference at {span:?}");
428-
}
429-
}
430-
}
474+
Atom::RcRef(_span, rc_words) => {
475+
result.extend(rc_words.symbol_uses(block_id, block_offset));
431476
}
432477
}
433478
result.into_iter()
@@ -544,7 +589,7 @@ pub(crate) enum InstructionFragment {
544589
PipeConstruct {
545590
index: SpannedSymbolOrLiteral,
546591
rc_word_span: Span,
547-
rc_word_value: Box<(InstructionFragment, Atom)>,
592+
rc_word_value: RegisterContaining,
548593
},
549594
Null,
550595
// TODO: subscript/superscript atom (if the `Arithmetic` variant
@@ -584,9 +629,7 @@ impl InstructionFragment {
584629
(name, span, symbol_use)
585630
}),
586631
);
587-
let (base, index) = rc_word_value.as_ref();
588-
result.extend(base.symbol_uses(block_id, block_offset));
589-
result.extend(index.symbol_uses(block_id, block_offset));
632+
result.extend(rc_word_value.symbol_uses(block_id, block_offset));
590633
}
591634
}
592635
result.into_iter()

assembler/src/asmlib/driver/tests.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -752,8 +752,12 @@ fn test_kleinrock_200016() {
752752
let program = assemble_source(input, Default::default()).expect("program is valid");
753753
dbg!(&program);
754754
assert_eq!(program.chunks.len(), 3);
755-
assert_eq!(program.chunks[0].words.len(), 1); // program length
756-
assert_eq!(program.chunks[2].words.len(), 1); // RC block length
755+
assert_eq!(
756+
program.chunks[0].words.len(),
757+
1,
758+
"Program code has wrong length"
759+
);
760+
assert_eq!(program.chunks[2].words.len(), 1, "RC block length is wrong");
757761

758762
assert_eq!(program.chunks[0].address, Address::from(u18!(0o200_000))); // Location of program code
759763
assert_eq!(program.chunks[0].words[0], u36!(0o031_712_606_336));

assembler/src/asmlib/eval.rs

Lines changed: 95 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use base::{
1010
use super::ast::{
1111
ArithmeticExpression, Atom, CommaDelimitedInstruction, Commas, ConfigValue, EqualityValue,
1212
HoldBit, InstructionFragment, LiteralValue, LocatedBlock, Operator, RcAllocator, RcWordSource,
13-
SignedAtom, Statement, SymbolOrLiteral, Tag, TaggedProgramInstruction,
14-
UntaggedProgramInstruction,
13+
RegisterContaining, RegistersContaining, SignedAtom, Statement, SymbolOrLiteral, Tag,
14+
TaggedProgramInstruction, UntaggedProgramInstruction,
1515
};
1616
use super::listing::{Listing, ListingLine};
1717
use super::span::*;
@@ -394,19 +394,20 @@ impl Evaluate for InstructionFragment {
394394
// of Q and ₜ were combined into the two parts of the
395395
// rc_word_value tuple. We evaluate Qₜ as
396396
// rc_word_val.
397-
let (base, index) = rc_word_value.as_ref();
398-
let base_value =
399-
base.evaluate(*rc_word_span, target_address, symtab, rc_allocator, op)?;
400-
let index_value =
401-
index.evaluate(*rc_word_span, target_address, symtab, rc_allocator, op)?;
402-
let rc_word_val: Unsigned36Bit = combine_fragment_values(base_value, index_value);
403397
let p_value: Unsigned36Bit =
404398
p.item
405399
.evaluate(p.span, target_address, symtab, rc_allocator, op)?;
406-
let rc_source = RcWordSource::PipeConstruct(*rc_word_span);
407-
let addr: Address = rc_allocator.allocate(rc_source, rc_word_val);
400+
let source_and_val: (&RcWordSource, &RegisterContaining) =
401+
(&RcWordSource::PipeConstruct(*rc_word_span), rc_word_value);
402+
let rc_word_addr: Unsigned36Bit = source_and_val.evaluate(
403+
*rc_word_span,
404+
target_address,
405+
symtab,
406+
rc_allocator,
407+
op,
408+
)?;
408409
Ok(combine_fragment_values(
409-
combine_fragment_values(Unsigned36Bit::from(addr), p_value),
410+
combine_fragment_values(p_value, rc_word_addr),
410411
DEFER_BIT,
411412
))
412413
}
@@ -467,44 +468,10 @@ impl Evaluate for Atom {
467468
Atom::Parens(span, _script, expr) => {
468469
expr.evaluate(*span, target_address, symtab, rc_allocator, op)
469470
}
470-
Atom::RcRef(span, tagged_program_instructions) => {
471-
let mut first_addr: Option<Address> = None;
472-
for inst in tagged_program_instructions.iter() {
473-
let rc_source = RcWordSource::Braces(*span);
474-
let rc_word_addr: Address =
475-
rc_allocator.allocate(rc_source, Unsigned36Bit::ZERO);
476-
if first_addr.is_none() {
477-
first_addr = Some(rc_word_addr);
478-
}
479-
480-
// Within the RC-word, # ("here") resolves to the
481-
// address of the RC-word itself. So before we
482-
// evaluate the value to be placed in the RC-word,
483-
// we need to know the value that # will take
484-
// during the evaluation process.
485-
let here = HereValue::Address(rc_word_addr);
486-
487-
// If inst has a tag, we temporarily override any
488-
// global value for that tag with the address of
489-
// this instruction.
490-
let tag_override: Option<(&Tag, Address)> =
491-
inst.tag.as_ref().map(|t| (t, rc_word_addr));
492-
let value: Unsigned36Bit = symtab.evaluate_with_temporary_tag_override(
493-
tag_override,
494-
inst,
495-
inst.span(),
496-
&here,
497-
rc_allocator,
498-
op,
499-
)?;
500-
rc_allocator.update(rc_word_addr, value);
501-
}
502-
match first_addr {
503-
Some(addr) => Ok(addr.into()),
504-
None => {
505-
unreachable!("RC-references should not occupy zero words of storage");
506-
}
507-
}
471+
Atom::RcRef(span, registers_containing) => {
472+
let source_and_val: (&RcWordSource, &RegistersContaining) =
473+
(&RcWordSource::Braces(*span), registers_containing);
474+
source_and_val.evaluate(*span, target_address, symtab, rc_allocator, op)
508475
}
509476
}
510477
}
@@ -1184,3 +1151,82 @@ impl LocatedBlock {
11841151
Ok(words)
11851152
}
11861153
}
1154+
1155+
impl Evaluate for (&RcWordSource, &RegistersContaining) {
1156+
fn evaluate<R: RcAllocator>(
1157+
&self,
1158+
span: Span,
1159+
_target_address: &HereValue,
1160+
symtab: &mut SymbolTable,
1161+
rc_allocator: &mut R,
1162+
op: &mut LookupOperation,
1163+
) -> Result<Unsigned36Bit, SymbolLookupFailure> {
1164+
let rc_source: &RcWordSource = self.0;
1165+
let registers_containing: &RegistersContaining = self.1;
1166+
let mut first_addr: Option<Unsigned36Bit> = None;
1167+
for rc_word in registers_containing.words() {
1168+
// Evaluation of the RegisterContaining value will compute
1169+
// a correct here-value, we don't need to pass it in. But
1170+
// we can't pass None, and so instead we pass NotAllowed
1171+
// so that if a bug is introduced we will see a failure
1172+
// rather than an incorrect result.
1173+
let must_recompute_here_address = HereValue::NotAllowed;
1174+
let addr: Unsigned36Bit = (rc_source, rc_word).evaluate(
1175+
span,
1176+
&must_recompute_here_address,
1177+
symtab,
1178+
rc_allocator,
1179+
op,
1180+
)?;
1181+
if first_addr.is_none() {
1182+
first_addr = Some(addr);
1183+
}
1184+
}
1185+
match first_addr {
1186+
Some(addr) => Ok(addr),
1187+
None => {
1188+
unreachable!("RC-references should not occupy zero words of storage");
1189+
}
1190+
}
1191+
}
1192+
}
1193+
1194+
impl Evaluate for (&RcWordSource, &RegisterContaining) {
1195+
fn evaluate<R: RcAllocator>(
1196+
&self,
1197+
_span: Span,
1198+
_target_address: &HereValue,
1199+
symtab: &mut SymbolTable,
1200+
rc_allocator: &mut R,
1201+
op: &mut LookupOperation,
1202+
) -> Result<Unsigned36Bit, SymbolLookupFailure> {
1203+
let rc_source: &RcWordSource = self.0;
1204+
let register_containing: &RegisterContaining = self.1;
1205+
1206+
let rc_word_addr: Address = rc_allocator.allocate(rc_source.clone(), Unsigned36Bit::ZERO);
1207+
dbg!(&(rc_source, rc_word_addr));
1208+
1209+
// Within the RC-word, # ("here") resolves to the
1210+
// address of the RC-word itself. So before we
1211+
// evaluate the value to be placed in the RC-word,
1212+
// we need to know the value that # will take
1213+
// during the evaluation process.
1214+
let here = HereValue::Address(rc_word_addr);
1215+
1216+
// If inst has a tag, we temporarily override any
1217+
// global value for that tag with the address of
1218+
// this instruction.
1219+
let inst: &TaggedProgramInstruction = register_containing.instruction();
1220+
let tag_override: Option<(&Tag, Address)> = inst.tag.as_ref().map(|t| (t, rc_word_addr));
1221+
let value: Unsigned36Bit = symtab.evaluate_with_temporary_tag_override(
1222+
tag_override,
1223+
inst,
1224+
inst.span(),
1225+
&here,
1226+
rc_allocator,
1227+
op,
1228+
)?;
1229+
rc_allocator.update(rc_word_addr, value);
1230+
Ok(Unsigned36Bit::from(rc_word_addr))
1231+
}
1232+
}

0 commit comments

Comments
 (0)