Skip to content

Commit

Permalink
spcasm: Implement startpos
Browse files Browse the repository at this point in the history
  • Loading branch information
kleinesfilmroellchen committed Sep 3, 2024
1 parent 8f267ad commit 20260d8
Show file tree
Hide file tree
Showing 18 changed files with 1,141 additions and 990 deletions.
32 changes: 29 additions & 3 deletions doc/src/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ spcasm::arch::valid
· ───────┬───────
· ╰── `arch` directive
2 │ org 0
3 │ start: ; @ 0
4 │ MOV A,#$10 ;= E8 10
3 │ startpos
4 │ start: ; @ 0
╰────
help: spcasm supports `arch` directives for compatibility with the Asar
multi-architecture assembler. This arch directive points to the
Expand Down Expand Up @@ -203,6 +203,32 @@ See [arch::valid](#spcasmarchvalid); when compiling files originally targeted at

This category contains directive-related errors.

#### spcasm::directive::duplicate_startpos

```trycmd
$ spcasm -w all tests/errors/duplicate-startpos.spcasmtest
? 1
spcasm::directive::duplicate_startpos
× Duplicate startpos directive
╭─[tests/errors/duplicate-startpos.spcasmtest:6:1]
3 │ nop
4 │
5 │ org $300
6 │ startpos
· ────┬───
· ╰── `startpos` directive
7 │ nop
╰────
help: the `startpos` directive defines the execution entry point of the
ROM after it was loaded. There can only be one entry point.
```

The [`startpos` directive](reference/directives.md#startpos) can only be specified once, since there can only be one program entry point.


#### spcasm::directive::invalid_directive_option

```trycmd
Expand Down Expand Up @@ -245,7 +271,7 @@ spcasm::directive::invalid_range

For range specifications, like when including binary files, the Asar style range syntax is a `start-end` format. Obviously, the start then needs to be before (or the same as) the end. Often you just accidentally swapped these limits.

#### spcasm::math_pri_unsupported
#### spcasm::directive::math_pri_unsupported

```trycmd
$ spcasm -w all tests/errors/math-pri.spcasmtest
Expand Down
16 changes: 16 additions & 0 deletions doc/src/reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ org $4000+30

Because segment start locations must be known early on, using references in the value is quite restricted.

### `startpos`

The `startpos` directive specifies that the program’s entry point should be at the current position (that is, at the next instruction). The entry point is what the program executes after being loaded by the ROM loader.

Note that this directive is not always required depending on the output format. If you are assembling a raw RAM image, the entry point will not stored anywhere anyways.

If you implement a custom fastloader, the code at the entry point is what should be uploaded with the bootrom uploader.

```asm
org $1000
startpos
; your entry point code, e.g. initialization...
main_function:
mov a, #2
```

### Segment stack

The segment stack can be controlled with the `pushpc` and `pullpc` instructions. `pushpc` pushes the current segment to the segment stack, and there will not be an active segment afterwards. `pullpc` pulls the last segment from the segment stack, reactivating it and allowing you to continue appending data to its end. This is currently the only way of continuing to put data into an already-existing segment that was "interrupted", so to speak.
Expand Down
91 changes: 59 additions & 32 deletions src/assembler/directive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use flexstr::{shared_str, IntoSharedStr, SharedStr, ToSharedStr};
use miette::SourceSpan;
use num_traits::{FromPrimitive, ToPrimitive};

use super::{resolve_file, AssembledData};
use super::{resolve_file, AssembledData, ClearLabels};
use crate::brr::wav;
use crate::directive::{symbolic_directives, DirectiveValue, FillOperation};
use crate::sema::instruction::MemoryAddress;
Expand All @@ -18,47 +18,46 @@ use crate::sema::AssemblyTimeValue;
use crate::{brr, AssemblyError, Directive};

impl AssembledData {
/// Assemble a single assembler directive into this assembly data.
/// Assemble a single assembler directive into this assembly data. Returns whether the label list needs to be
/// cleared or (if the directive is transparent to labels) not.
///
/// # Errors
/// Any error caused by the directive assembly process is returned.
///
/// # Panics
/// All panics are programming bugs.
#[allow(clippy::unnecessary_wraps)]
pub fn assemble_directive(
pub(super) fn assemble_directive(
&mut self,
directive: &mut Directive,
current_labels: &Vec<Reference>,
) -> Result<(), Box<AssemblyError>> {
) -> Result<ClearLabels, Box<AssemblyError>> {
match directive.value {
// Symbolic directives should not be around anymore.
symbolic_directives!() => unreachable!(),
DirectiveValue::Table { ref values } =>
try {
let mut is_first = true;
for value in values {
self.append_sized_unresolved(
value.clone(),
if is_first { current_labels } else { Self::DEFAULT_VEC },
directive.span,
)?;
is_first = false;
}
},
DirectiveValue::Brr { ref file, range, auto_trim, .. } =>
self.assemble_brr(directive, file, range, auto_trim, current_labels),
DirectiveValue::String { ref text, has_null_terminator } =>
try {
self.append_bytes(text.clone(), current_labels, directive.span)?;
if has_null_terminator {
self.append(
0,
if text.is_empty() { current_labels } else { Self::DEFAULT_VEC },
directive.span,
)?;
}
},
DirectiveValue::Table { ref values } => {
let mut is_first = true;
for value in values {
self.append_sized_unresolved(
value.clone(),
if is_first { current_labels } else { Self::DEFAULT_VEC },
directive.span,
)?;
is_first = false;
}
Ok(ClearLabels::Yes)
},
DirectiveValue::Brr { ref file, range, auto_trim, .. } => {
self.assemble_brr(directive, file, range, auto_trim, current_labels)?;
Ok(ClearLabels::Yes)
},
DirectiveValue::String { ref text, has_null_terminator } => {
self.append_bytes(text.clone(), current_labels, directive.span)?;
if has_null_terminator {
self.append(0, if text.is_empty() { current_labels } else { Self::DEFAULT_VEC }, directive.span)?;
}
Ok(ClearLabels::Yes)
},
DirectiveValue::Include { ref file, range } => {
let binary_file = resolve_file(&self.source_code, file);
let mut binary_data = std::fs::read(binary_file).map_err(|os_error| AssemblyError::FileNotFound {
Expand All @@ -69,7 +68,8 @@ impl AssembledData {
})?;

binary_data = self.slice_data_if_necessary(file, directive.span, binary_data, range)?;
self.append_bytes(binary_data, current_labels, directive.span)
self.append_bytes(binary_data, current_labels, directive.span)?;
Ok(ClearLabels::Yes)
},
DirectiveValue::SampleTable { auto_align } => {
let current_address = self.segments.current_location().unwrap();
Expand All @@ -88,18 +88,45 @@ impl AssembledData {
}
.into());
}
self.assemble_sample_table(current_labels, directive.span)
self.assemble_sample_table(current_labels, directive.span)?;
Ok(ClearLabels::Yes)
},
DirectiveValue::Fill { ref operation, ref parameter, ref value } => {
let current_address = self.segments.current_location().unwrap();
self.assemble_fill(operation, parameter, value.clone(), current_address, current_labels, directive.span)
self.assemble_fill(
operation,
parameter,
value.clone(),
current_address,
current_labels,
directive.span,
)?;
Ok(ClearLabels::Yes)
},
DirectiveValue::Conditional { ref mut condition, ref mut true_block, ref mut false_block } => {
let condition = condition.try_value(directive.span, &self.source_code)?;
if condition == 0 {
self.assemble_all_from_list(false_block)
} else {
self.assemble_all_from_list(true_block)
}?;
Ok(ClearLabels::Yes)
},
DirectiveValue::Startpos => {
let current = self.segments.current_location().map_err(|()| AssemblyError::MissingSegment {
location: directive.span,
src: self.source_code.clone(),
})?;

if self.entry_point.is_some() {
Err(AssemblyError::DuplicateStartpos {
src: self.source_code.clone(),
location: directive.span,
}
.into())
} else {
self.entry_point = Some(current);
Ok(ClearLabels::No)
}
},
}
Expand Down
33 changes: 28 additions & 5 deletions src/assembler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ use table::{EntryOrFirstOperandTable, EntryOrSecondOperandTable, TwoOperandEntry

use self::memory::{LabeledMemoryValue, MemoryValue};

#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
enum ClearLabels {
#[default]
Yes,
No,
}

/// Assembles the instructions into a byte sequence. This function receives already-separated sections as input, so it
/// does not do section splitting itself. It might modify the input segments as well during optimization.
///
Expand All @@ -52,8 +59,9 @@ pub fn assemble_inside_segments(
segments: &mut Segments<ProgramElement>,
source_code: &Arc<AssemblyCode>,
options: Arc<dyn Frontend>,
) -> Result<Segments<u8>, Box<AssemblyError>> {
assemble_to_data(segments, source_code, options)?.resolve_segments()
) -> Result<(Segments<u8>, EntryPoint), Box<AssemblyError>> {
let data = assemble_to_data(segments, source_code, options)?;
Ok((data.resolve_segments()?, data.entry_point))
}

/// Assembles a [`ProgramElement`] inside a loop.
Expand All @@ -68,8 +76,12 @@ macro_rules! assemble_element {
},
$crate::sema::ProgramElement::Instruction(instruction) =>
$data.assemble_instruction(instruction, &$current_labels)?,
$crate::sema::ProgramElement::Directive(directive) =>
$data.assemble_directive(directive, &$current_labels)?,
$crate::sema::ProgramElement::Directive(directive) => {
let clear_labels = $data.assemble_directive(directive, &$current_labels)?;
if clear_labels == ClearLabels::No {
continue;
}
},
$crate::sema::ProgramElement::IncludeSource { .. } =>
unreachable!("there should not be any remaining unincluded source code at assembly time"),
$crate::sema::ProgramElement::UserDefinedMacroCall { .. } =>
Expand Down Expand Up @@ -119,6 +131,9 @@ pub(crate) fn resolve_file(source_code: &Arc<AssemblyCode>, target_file: &str) -
.expect("file path was root, this makes no sense")
}

/// Entry point specification.
pub type EntryPoint = Option<MemoryAddress>;

/// The assembled data, which consists of multiple sections.
#[derive(Debug)]
pub struct AssembledData {
Expand All @@ -128,6 +143,8 @@ pub struct AssembledData {
pub source_code: Arc<AssemblyCode>,
/// Assembler subroutines use this as a flag to signal an end of assembly as soon as possible.
should_stop: bool,
/// Execution entry point of the code after being loaded.
pub entry_point: EntryPoint,
/// Options that command line received; used for determining what to do with warnings.
options: Arc<dyn Frontend>,
}
Expand Down Expand Up @@ -193,7 +210,13 @@ impl AssembledData {
#[must_use]
#[inline]
pub fn new(source_code: Arc<AssemblyCode>) -> Self {
Self { segments: Segments::default(), source_code, should_stop: false, options: default_backend_options() }
Self {
segments: Segments::default(),
source_code,
should_stop: false,
entry_point: None,
options: default_backend_options(),
}
}

/// Change the error options for assembler warning and error reporting.
Expand Down
10 changes: 6 additions & 4 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use parking_lot::RwLock;
pub use super::directive::Directive;
pub use super::error::AssemblyError;
pub use super::sema::Environment;
use crate::assembler::EntryPoint;
use crate::cli::{default_backend_options, Frontend};
use crate::sema::reference::Label;
use crate::sema::ProgramElement;
Expand Down Expand Up @@ -167,11 +168,12 @@ pub fn run_assembler_into_symbolic_segments(
pub fn run_assembler_into_segments(
source_code: &Arc<AssemblyCode>,
options: Arc<dyn Frontend>,
) -> Result<(Segments<ProgramElement>, Segments<u8>), Box<AssemblyError>> {
) -> Result<(Segments<ProgramElement>, Segments<u8>, EntryPoint), Box<AssemblyError>> {
let (_, mut segmented_program) = run_assembler_into_symbolic_segments(source_code, options.clone())?;
let assembled = crate::assembler::assemble_inside_segments(&mut segmented_program, source_code, options)
.map_err(AssemblyError::from)?;
Ok((segmented_program, assembled))
let (assembled, entry_point) =
crate::assembler::assemble_inside_segments(&mut segmented_program, source_code, options)
.map_err(AssemblyError::from)?;
Ok((segmented_program, assembled, entry_point))
}

/// Provides a name for enum variants.
Expand Down
10 changes: 10 additions & 0 deletions src/directive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ pub enum DirectiveSymbol {
PadWord,
PadLong,
PadDWord,
Startpos,
Namespace,
}

Expand Down Expand Up @@ -226,6 +227,7 @@ impl Display for DirectiveSymbol {
Self::PadWord => "padword",
Self::PadLong => "padlong",
Self::PadDWord => "paddword",
Self::Startpos => "startpos",
Self::Namespace => "namespace",
})
}
Expand Down Expand Up @@ -294,6 +296,8 @@ pub enum DirectiveValue {
/// The block that is assembled if the condition is falsy.
false_block: Vec<ProgramElement>,
},
/// `startpos`
Startpos,
/// `namespace`
StartNamespace { name: SharedStr },
/// `namespace off`
Expand Down Expand Up @@ -339,6 +343,7 @@ impl DirectiveValue {
| Self::AssignReference { .. }
| Self::Placeholder
| Self::SetDirectiveParameters { .. }
| Self::Startpos
| Self::StartNamespace { .. }
| Self::EndNamespace
| Self::Org(..) => 0,
Expand Down Expand Up @@ -403,6 +408,7 @@ impl Display for DirectiveValue {
Self::End => "endasm".to_string(),
Self::PushSection => "push".to_string(),
Self::PopSection => "pop".to_string(),
Self::Startpos => "startpos".to_string(),
Self::EndNamespace => "namespace off".to_string(),
Self::StartNamespace { name } => format!("namespace {name}"),
Self::UserDefinedMacro { name, arguments, body } => format!(
Expand Down Expand Up @@ -485,6 +491,7 @@ impl ReferenceResolvable for DirectiveValue {
| Self::SetDirectiveParameters { .. }
| Self::Fill { .. }
| Self::PopSection
| Self::Startpos
| Self::StartNamespace { .. }
| Self::EndNamespace
| Self::Org(_) => Ok(()),
Expand Down Expand Up @@ -531,6 +538,7 @@ impl ReferenceResolvable for DirectiveValue {
| Self::SampleTable { .. }
| Self::SetDirectiveParameters { .. }
| Self::PopSection
| Self::Startpos
| Self::StartNamespace { .. }
| Self::EndNamespace
| Self::Org(_)
Expand Down Expand Up @@ -564,6 +572,7 @@ impl ReferenceResolvable for DirectiveValue {
| Self::SampleTable { .. }
| Self::SetDirectiveParameters { .. }
| Self::PopSection
| Self::Startpos
| Self::StartNamespace { .. }
| Self::EndNamespace
| Self::Org(_)
Expand Down Expand Up @@ -615,6 +624,7 @@ impl ReferenceResolvable for DirectiveValue {
| Self::End
| Self::PushSection
| Self::PopSection
| Self::Startpos
| Self::StartNamespace { .. }
| Self::EndNamespace
| Self::Org(_) => Ok(()),
Expand Down
Loading

0 comments on commit 20260d8

Please sign in to comment.