diff options
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 76 | ||||
| -rw-r--r-- | lib/xixanta/src/object.rs | 12 | ||||
| -rwxr-xr-x | scripts/test-e2e.sh | 5 | ||||
| -rw-r--r-- | tests/bad_jal.s | 18 | ||||
| -rw-r--r-- | tests/expected/bad_jal.txt | 2 | ||||
| -rw-r--r-- | tests/jal.s | 3 |
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 |
