aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-07-15 07:58:08 +0200
committerMiquel Sabaté Solà <mssola@mssola.com>2026-07-15 08:10:45 +0200
commit61b9ce33d54bc643b0ece227101d8bf571fd86e5 (patch)
treee3fce800a82adac700997b02d4ab37a80f4fd97b /lib
parent10b2276829b31c117eb3276dca11503248801cdd (diff)
downloadtools.nes-61b9ce33d54bc643b0ece227101d8bf571fd86e5.tar.gz
tools.nes-61b9ce33d54bc643b0ece227101d8bf571fd86e5.zip
Save known addresses into .nasm/addresses.txt
This new file contains all the addresses that are known to the assembler, belonging to either proc's or plain labels. For the former we will further be able to tell the "end" of the proc. With that, the file follows a simple CSV format, with the name, start and end. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/xixanta/src/assembler.rs86
1 files changed, 81 insertions, 5 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 81ac9ee..959840d 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -187,6 +187,18 @@ struct Assembler<'a> {
// List of mapping/segment indeces which the programmer asked to be
// considered safe.
safe_segments: Vec<(usize, usize)>,
+
+ // Name of the last proc that was encountered while traversing the parsed
+ // nodes.
+ last_proc: String,
+
+ // Target address that was last saved in push_bundle().
+ last_address: Option<usize>,
+
+ // Map that identifies for each given string identifying a piece of code
+ // (e.g. a proc's name), the address which can be used as the 'end' for a
+ // Range object.
+ address_ends: HashMap<String, usize>,
}
/// The result to be given at the end of `assembler::assemble` and
@@ -232,6 +244,14 @@ pub struct AssemblerResult {
/// The resaulting memory analysis from the address sanitizer. Ignore this
/// if you did not assemble the source with the address sanitizer enabled.
pub memory: MemoryResult,
+
+ /// The addresses that have been evaluated. Note that some addresses will be
+ /// proper ranges (i.e. proc's), while others will only be a value (i.e.
+ /// labels). This is done in purpose as there's no reliable way to guess
+ /// whether a label was actually a subroutine or not. Hence, we don't even
+ /// try and encourage programmers to use .proc's when necessary (see
+ /// style.nes).
+ pub addresses: Vec<Range>,
}
/// Read the contents from the `reader` as a source file and produce a list of
@@ -264,6 +284,7 @@ pub fn assemble(
mappings: vec![],
accessing_working_ram: false,
memory: MemoryResult::default(),
+ addresses: vec![],
};
}
};
@@ -291,6 +312,7 @@ pub fn assemble_with_mapping(
let mut asm = Assembler::new(mapping);
let mut memory = MemoryResult::default();
+ let mut addresses = vec![];
asm.asan_enabled = asan;
asm.allow_unused = allow_unused;
@@ -305,6 +327,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
@@ -322,6 +345,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
}
@@ -336,6 +360,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
@@ -372,6 +397,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
@@ -385,12 +411,13 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
// Before filling in the holes with fill(), let's check what we have
// processed so far.
- if let Err(errors) = asm.check(&mut memory) {
+ if let Err(errors) = asm.check(&mut memory, &mut addresses) {
return AssemblerResult {
bundles: vec![],
errors,
@@ -398,6 +425,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
@@ -416,6 +444,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
},
Err(errors) => AssemblerResult {
bundles: vec![],
@@ -424,6 +453,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
},
}
}
@@ -457,6 +487,9 @@ impl<'a> Assembler<'a> {
pending_defines: vec![],
objects_visited: vec![],
safe_segments: vec![],
+ last_proc: String::new(),
+ last_address: None,
+ address_ends: HashMap::new(),
}
}
@@ -915,6 +948,8 @@ impl<'a> Assembler<'a> {
// then open up its inner context.
NodeType::Control(ControlType::StartProc) => {
let proc_name = &node.left.as_ref().unwrap();
+ self.last_proc = proc_name.value.value.clone();
+
if let Err(e) = self.apply_segment_offset_to_label(proc_name, ObjectType::Proc)
{
errors.push(e);
@@ -985,6 +1020,23 @@ impl<'a> Assembler<'a> {
ControlType::StartRepeat => {
self.evaluate_repeat_statement(node)?;
}
+ ControlType::EndProc => {
+ // Save the last visited proc as the index for the
+ // 'address_ends' map. Note that we need to have it
+ // here because after the previous
+ // evaluate_control_statement() call the context
+ // will be the right one.
+
+ let context_name = self.context.name();
+ let full_name = if context_name == GLOBAL_CONTEXT {
+ self.last_proc.clone()
+ } else {
+ format!("{context_name}::{}", self.last_proc)
+ };
+ if let Some(last_address) = self.last_address {
+ self.address_ends.insert(full_name, last_address);
+ }
+ }
ControlType::StartScope
// If this is the start of a .scope statement, then go
// inside of its body too if it exists (note that its
@@ -1275,8 +1327,12 @@ impl<'a> Assembler<'a> {
}
// Perform different checks on the bundles we have produced so far, and
- // update the 'memory' if possible (i.e. ASAN is enabled).
- fn check(&mut self, memory: &mut MemoryResult) -> Result<(), Vec<Error>> {
+ // update the 'memory' and the 'addresses' if possible.
+ fn check(
+ &mut self,
+ memory: &mut MemoryResult,
+ addresses: &mut Vec<Range>,
+ ) -> Result<(), Vec<Error>> {
let mut errors = vec![];
for (context_name, map) in self.context.map.iter() {
@@ -1370,8 +1426,21 @@ impl<'a> Assembler<'a> {
// addresses.
match bundle.object_type {
ObjectType::Proc | ObjectType::Address => {
- // TODO
- println!("{full_name}");
+ // Using unwrap() is safe as it only returns an error on
+ // badly formatted addresses. This will not happen at
+ // this point.
+ let resolved = self.context.resolve_label(&self.mappings, &bundle).unwrap();
+
+ let val = resolved.bundle.value() as usize;
+ let end = match self.address_ends.get(&full_name) {
+ Some(v) => v,
+ None => &val,
+ };
+ let range = Range {
+ range: (val..*end),
+ name: full_name,
+ };
+ addresses.push(range);
}
ObjectType::Value
// We have a value at hand. If the address sanitizer is
@@ -1696,6 +1765,13 @@ impl<'a> Assembler<'a> {
current.offset += bundle.size as usize;
current.segments[self.current_segment].offset += bundle.size as usize;
+ // Save this bundle as the last address being used. Note that the
+ // address being pointed at is _after_ consuming the bundle (i.e. by
+ // adding 'bundle.size'). This is because in the end this 'last_address'
+ // is going to be used by the Range struct as its 'end' member. Since
+ // this is going to be inclusive, we need to point at the "next" bundle.
+ self.last_address = Some(bundle.address + bundle.size as usize);
+
// Mark this bundle as reference-safe, so the checker afterwards doesn't
// complain on different checks.
bundle.safe = self.asan_next_safe;