aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/xixanta/src/assembler.rs76
-rw-r--r--lib/xixanta/src/object.rs12
-rwxr-xr-xscripts/test-e2e.sh5
-rw-r--r--tests/bad_jal.s18
-rw-r--r--tests/expected/bad_jal.txt2
-rw-r--r--tests/jal.s3
6 files changed, 116 insertions, 0 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 0d4e132..d7e036f 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -889,11 +889,50 @@ impl<'a> Assembler<'a> {
let current = &self.mappings[pn.mapping].segments[pn.segment];
bundle.address = current.bundles[pn.bundle_index].address;
+ // If we are trying to 'jmp'/'jsr' right into the next
+ // instruction, then warn the programmer about it. This
+ // looks silly but in practice it might happen inside of a
+ // .proc where the code layout might not make this as
+ // obvious as it sounds.
+ //
+ // NOTE: this is only done for absolute addressing as the
+ // indirect case for 'jmp' is harder to follow and just not
+ // worth it. If programmers do fancy indirect jumps, let
+ // them shoot themselves in the foot if that's what they
+ // want.
+ if (bundle.bytes[0] == 0x4C || bundle.bytes[0] == 0x20)
+ && bundle.arg() as usize == bundle.next_address()
+ {
+ self.warnings.push(Error {
+ line: pn.node.value.line,
+ message: String::from(
+ "unconditional jump that points to the next instruction",
+ ),
+ source: self.source_for(&pn.node),
+ global: false,
+ expanded_from: pn.macro_context.clone(),
+ });
+ }
+
if pn.node.is_branch() {
bundle.resolved = true;
if let Err(e) = self.to_relative_address(&pn.node, &mut bundle) {
errors.push(e);
}
+
+ // Silly mistake coming from using a label directly
+ // after the current branch instruction.
+ if bundle.bytes[1] == 0 {
+ self.warnings.push(Error {
+ line: pn.node.value.line,
+ message: String::from(
+ "conditional jump that points to the next instruction",
+ ),
+ source: self.source_for(&pn.node),
+ expanded_from: pn.macro_context.clone(),
+ global: false,
+ });
+ }
}
let current_mut = &mut self.mappings[pn.mapping].segments[pn.segment];
@@ -4066,6 +4105,43 @@ JAL procedure
assert_eq!(res[1].bytes[0], 0x60);
}
+ #[test]
+ fn jump_to_next() {
+ let res = just_assemble(
+ r#"
+.proc foo
+ jmp bar
+.endproc
+
+.proc bar
+ jsr another
+.endproc
+
+.proc another
+ lda #0
+ beq @next
+@next:
+ rts
+.endproc
+"#,
+ );
+
+ assert_eq!(res.warnings.len(), 3);
+ // NOTE: +3 lines for the implicit header.
+ assert_eq!(
+ res.warnings[0].to_string(),
+ "unconditional jump that points to the next instruction (line 6)"
+ );
+ assert_eq!(
+ res.warnings[1].to_string(),
+ "unconditional jump that points to the next instruction (line 10)"
+ );
+ assert_eq!(
+ res.warnings[2].to_string(),
+ "conditional jump that points to the next instruction (line 15)"
+ );
+ }
+
// Control statements
#[test]
diff --git a/lib/xixanta/src/object.rs b/lib/xixanta/src/object.rs
index 5904b56..170d872 100644
--- a/lib/xixanta/src/object.rs
+++ b/lib/xixanta/src/object.rs
@@ -98,6 +98,18 @@ impl Bundle {
])
}
}
+
+ /// Considering that the first element of the 'bytes' property is the
+ /// instruction identifier, returns the two last bytes as if they were a
+ /// 16-bit value.
+ pub fn arg(&self) -> u16 {
+ self.bytes[1] as u16 + ((self.bytes[2] as u16) << 8)
+ }
+
+ /// Returns the address to the next instruction after this bundle.
+ pub fn next_address(&self) -> usize {
+ self.address + self.size as usize
+ }
}
/// The type of object being referenced, which is either a value as-is, or an
diff --git a/scripts/test-e2e.sh b/scripts/test-e2e.sh
index ca4dbbb..2f7ff5f 100755
--- a/scripts/test-e2e.sh
+++ b/scripts/test-e2e.sh
@@ -71,6 +71,11 @@ echo "test: custom => asan_reserve_constant.nes"
diff tests/out/asan_reserve_constant.txt tests/expected/asan_reserve_constant.txt
exit_code=$((exit_code + $?))
+echo "test: custom => bad_jal.nes"
+./target/debug/nasm -c empty --asan tests/bad_jal.s -o /dev/null 2>tests/out/bad_jal.txt
+diff tests/out/bad_jal.txt tests/expected/bad_jal.txt
+exit_code=$((exit_code + $?))
+
##
# code.nes
diff --git a/tests/bad_jal.s b/tests/bad_jal.s
new file mode 100644
index 0000000..693fa7d
--- /dev/null
+++ b/tests/bad_jal.s
@@ -0,0 +1,18 @@
+.segment "HEADER"
+ .byte 'N', 'E', 'S', $1A
+ .byte $02
+ .byte $01
+ .byte $00
+ .byte $00
+
+.segment "CODE"
+
+.include "jal.s"
+
+.proc foo
+ JAL bar
+.endproc
+
+.proc bar
+ rts
+.endproc
diff --git a/tests/expected/bad_jal.txt b/tests/expected/bad_jal.txt
new file mode 100644
index 0000000..1b3f315
--- /dev/null
+++ b/tests/expected/bad_jal.txt
@@ -0,0 +1,2 @@
+warning: unconditional jump that points to the next instruction (jal.s: line 2)
+ >> expanded from bad_jal.s: line 13.
diff --git a/tests/jal.s b/tests/jal.s
new file mode 100644
index 0000000..90f8a15
--- /dev/null
+++ b/tests/jal.s
@@ -0,0 +1,3 @@
+.macro JAL ADDR
+ jmp ADDR
+.endmacro