aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta/src/assembler.rs
Commit message (Collapse)AuthorAgeFilesLines
* Add the .fallthrough control statementMiquel Sabaté Solà2026-02-021-1/+86
| | | | | | | | | | | | | | | | This is exclusive to 'nasm' and it allows the developer to explicitly tell the assembler than a "fall through" condition is actually desired: it's not a mistake. This comes in two flavors. The first, without arguments, just makes this explicit without much enforcement. The second allows you to pass an argument which is the name of the function or label you are expecting to fall through. The assembler will error out if the fall through address is not the expected one, hence telling the programmer whenever the fall through condition they thought in the past is no longer true (e.g. the function has moved somewhere else in the code). Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
* Warn on pointless (un)conditional branchingMiquel Sabaté Solà2026-02-021-0/+76
| | | | | | | | | | | | | | | | | | | | | | | | | | Sometimes performing some 'jmp'/'jsr' can be quite pointless, and the programmer might not be fully aware of this because of the layout of the code. Imagine: .proc foo ;; code jmp bar .endproc ;; Documentation, comments, extra space, etc. .proc bar ;; whatever .endproc The 'jmp' in the code above tries to perform a call stack optimization, but it's actually not needed because the next instruction after 'jmp' is the one inside of 'bar', but that's obfuscated because of the layout. In these sort of cases (and also for 'jsr' and branches) warn the programmer about it so it can remove that instruction. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
* xixanta: add context from macro expansionsMiquel Sabaté Solà2026-02-021-1/+118
| | | | | | | | | | | | | The way macro expansions work is that the evaluated bundles replace the current node, but this throws away the context of the line, the source, etc. from the original line of code. Add a stack that is pushed/popped when expanding macros, and are then cloned for each pending node and error. This way, errors have full context of the original code and can display backtraces for macro expansion. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
* asan: move the conflict check into its own loopMiquel Sabaté Solà2025-12-151-18/+30
| | | | | | | | | | | | | There was a condition race in which some conflicts would be caught but sometimes wouldn't depending on how/when `memory.memory_ranges` was being filled. Instead of this, just fill this vector with otherwise valid candidates (other checks like "is it used?" still apply in this context), and then perform the conflict check upon the already filled vector. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
* Allow constants in asan:reserve statementsMiquel Sabaté Solà2025-12-151-0/+16
| | | | | | | | | | | | | | | | This way, if you define a constant like: MY_BUFFER_LEN_IN_BYTES = $10 You can then declare your buffer like so: zp_buffer = $00 ; asan:reserve MY_BUFFER_LEN_IN_BYTES And then further in the code you can rely on just using the constant for bound checking, and then the address sanitizer will check on bound checks via static analysis as well. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
* asan: addresses >= 0x2000 cannot be reserved spaceMiquel Sabaté Solà2025-12-151-10/+15
| | | | | | | These addresses are merely references to real APU/PPU addresses which can be used for ease of use. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
* Report on multi-byte values on arithmetic evaluationMiquel Sabaté Solà2025-12-151-0/+32
| | | | | | | | | | | | | | | When evaluating an arithmetic operation, check on whether it theoretically spans more than one byte, and set it on the resulting Bundle.size. It's up to the caller to decide on whether that makes sense or not. This fixes situations in which: sta $200 + 1 was marked as having 2 bytes for the size as only the "1" was being considered for the size on this operation. This would then signal the "sta" evaluation that it was zeropage instead of absolute addressing. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
* Allow asan:reserve to receive an usizeMiquel Sabaté Solà2025-12-141-8/+8
| | | | | | | This allows for reserving memory regions which go outside of the page boundary. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
* xixanta: Don't allow negative variables on asanMiquel Sabaté Solà2025-09-121-4/+41
| | | | | | | | | | | | | | Negative variables make no sense when it comes to do stuff like: variable = -1 lda variable That being said, as an assembler you never know the hacks and nonsense programmers are willing to endure. But we do know that if the address sanitizer is enabled, since if it's an "asan-friendly", then an explicit negative variable can be barred. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* asan: Add fixes on absolute/indirect addressingMiquel Sabaté Solà2025-09-031-1/+6
| | | | | | The check was not being applied on certain conditions. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Apply asan:ignore also when bundlingMiquel Sabaté Solà2025-09-031-1/+13
| | | | | | | This way instructions which might make use of bare memory numbers can freely ignore the address sanitizer when it actually makes sense. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Add a check for variable namesMiquel Sabaté Solà2025-09-031-0/+67
| | | | | | | This check ensures that asan-friendly names actually match their expected scope. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Validate that memory access is done via variablesMiquel Sabaté Solà2025-09-031-9/+127
| | | | | | | | | | | | | The address sanitizer is now able to detect whenever in an instruction a memory access is done without using variables. This is now detected for all instructions except for branching, which falls outside of this scope. Moreover, simple arithmetics is allowed and bounds are checked for simple cases. That being said, more involved bound checks should be done with other tools (e.g. emulators). Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Add a warning for each unused variableMiquel Sabaté Solà2025-09-021-21/+45
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* assembler: Preserve the original working directoryMiquel Sabaté Solà2025-09-021-0/+10
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* assembler: Add support for asan:reserve,ignoreMiquel Sabaté Solà2025-09-021-12/+213
| | | | | | | | This is the initial support for both directives for the address sanitizer. Note that asan:weak has been moved into asan:ignore, which is not exactly the same but for now it should suffice. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* nasm: Error out when using WRAM when not availableMiquel Sabaté Solà2025-08-281-0/+26
| | | | | | | | | | This commit introduces the ability to inspect the temptative header before producing the actual output, and with that it checks whether the Working RAM is being advertised or not. If it is not being advertised but the assembler detected memory accesses to that region, then we are in trouble and we should error out. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* xixanta: Be more mindful on what's being exportedMiquel Sabaté Solà2025-08-271-6/+21
| | | | | | | This means removing a lot of `pub` structs or enums, as well as adding documentation on `pub` structs. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* nasm: Implement the -s/--stats optionMiquel Sabaté Solà2025-08-271-0/+9
| | | | | | | | This option prints further information on how segments are laid out. In particular, for now it prints the amount of space being filled for each segment. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Don't get into all blocks when evaluating the contextMiquel Sabaté Solà2025-08-181-4/+51
| | | | | | | | For some control statements like .if/.ifdef/.ifndef this is only desired when the condition is true; otherwise getting into the inner block should be prevented. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Provide a node to the Object structMiquel Sabaté Solà2025-08-181-1/+59
| | | | | | | | | This allows the `evaluate_variable` to pull from it in the crunching stage so to evaluate the original node in cases like macro expansion, where the connection between the macro argument and the original caller might have been lost. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Reset literal mode before left arm of an operationMiquel Sabaté Solà2025-08-181-0/+5
| | | | | | | | On an arithmetical/logical operation, the literal mode needed to be reset before evaluating the left arm since the previous evaluation of the node could have altered it. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Fix shift overflow on certain expressionsMiquel Sabaté Solà2025-08-181-2/+2
| | | | | | | | | I got a report from libfuzz that some cryptic input could make the assembler panic on shifts. It turns out that the check on whether the operator was too big or not had to be explicitely casted to `usize` to avoid signedness issues. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Use a buffered reader for incbinMiquel Sabaté Solà2025-08-171-2/+3
| | | | | | | | | Iterating via `bytes()` on a file is inefficient as the default implementation calls `read` on each byte, which can be costly on bytes which are not in memory like files. This is extra important for statements like `incbin` as included files can be rather big. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Apply suggested style fixes from clippyMiquel Sabaté Solà2025-08-171-25/+17
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Implement echo control statementsMiquel Sabaté Solà2025-01-221-1/+30
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* nasm: Implement the -D flagMiquel Sabaté Solà2025-01-221-4/+64
| | | | | | | This allows users to define variables directly from the command line, which is useful for testing purposes. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Introduce looking ahead when parsing literalsMiquel Sabaté Solà2025-01-211-1/+1
| | | | | | | | | | | | | | | | | | | | | | There were certain situations in which syntax ambiguity could arise. For example, the parser as it stood could treat a valid expression such as 'lda #$80 >> 2' in an unexpected 'lda #$(80 >> 2)'. This is of course bad, and it came from the fact that literals don't have enclosing characters, and white spaces are not enough to provide disambiguation in some cases. Because of this fact, the parser now has a "look ahead" capability similar to many other parsers, and it's applied for now only to literals. This looking ahead actually honors parenthesis, so these can be added if the programmer wants to explicitely disambiguate an expression. This involved quite the heavy lifting, and some functions like 'parse_expression_with_identifier' had to be removed with the rewrite. This had the side effect of having (hopefully) more sane functions all around, and the parser also has a better capability to differentiate between regular Values and Calls. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Whitelist valid identifier charactersMiquel Sabaté Solà2025-01-161-5/+14
| | | | | | | | Instead of making up a list of characters that end an identifier, do the other way around since it's far less cumbersome and it prevents from silly bugs such as "var+1" being considered a single identifier. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Permit 16-bit decimal valuesMiquel Sabaté Solà2025-01-161-9/+18
| | | | | | | | | | As a remnant of old code, the 'parse_decimal' function was not allowing for decimal values larger than 8-bits. This was not the case in other areas such as 'parse_hexadecimal', and in the rest of the code we already cover that immediates are not too big in instructions. Hence, this restriction can be lift up and allow up to 16-bit decimal literals. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Implement .ifdef/.ifndef statementsMiquel Sabaté Solà2025-01-161-0/+49
| | | | | | | They are just synonyms for ".if .defined" and ".if !.defined" respectively. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Implement .def/.defined expressionsMiquel Sabaté Solà2025-01-161-0/+53
| | | | | | | This can be combined with .if/.elsif statements just like any other expression. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Implement .if/.elsif/.else statementsMiquel Sabaté Solà2025-01-161-11/+133
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Add boolean operatorsMiquel Sabaté Solà2025-01-161-7/+60
| | | | | | | | | | | Allow for expressions that evaluate to a boolean expression. This in turn mean that the value is just set to 0 or 1 depending on the given condition. As with other assemblers, only a value of 0 evaluates to 0, and others go to 1. So, something like "1 && 2" evaluates to 1 even if it doesn't make much sense at first glance (as an assembler we just assume that the programmer knows what it's doing). Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Move check for ASCII-only strings into the parserMiquel Sabaté Solà2025-01-151-9/+2
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Implement .asciiz control statementMiquel Sabaté Solà2025-01-141-10/+69
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Implement the .res control statementMiquel Sabaté Solà2025-01-141-0/+97
| | | | | | | | | | | | | Similarly to other assemblers, this allows the programmer to write a definite amount of bytes with the same values. Compared to other assemblers there are two things to notice. First, there is a limit to it (i.e. whatever can fit in 2 bytes). Second, if the fill value is not provided, then it will default to the current mapping's fill value, or just 0x00 if the current mapping doesn't define one of its own. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* tests: Update code.nes so -Werror can be passedMiquel Sabaté Solà2025-01-131-20/+2
| | | | | | | | | Update the underlying code.nes test data so it uses a tighter configuration for NROM which in turn frees nasm from complaining about empty segments. This makes room to allo for -Werror so we catch warnings that might appear in the future. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Improve the message on bad file includesMiquel Sabaté Solà2025-01-101-3/+2
| | | | | | Also remove some pending TODOs. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Properly fill the context stack when forcing itMiquel Sabaté Solà2025-01-101-2/+48
| | | | | | | | | | | | | When calling `force_context_switch` the stack was mindlessly pushing the given name without taking into consideration how scopes are to be laid out. This made some variables/addresses that were previously preserved no longer reachable when crunching pending nodes. This patch also makes `force_context_switch` reset the stack before doing anything at all, which means that `force_context_pop` was no longer relevant. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Improve error message on unknown variable/scopeMiquel Sabaté Solà2025-01-091-10/+8
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Prevent assignments on relative referencesMiquel Sabaté Solà2025-01-091-1/+17
| | | | | | | | | | | | It is a really weird thing to do, but on a twisted way I can see how someone could think of such a monstrosity. Panicking on such a case is not valid because that's not the fault from the assembler but from the programmer, and so a proper message should be displayed instead. This was detected via the fuzzer, but it was a side effect from e7c5e63d04f6 ("Evaluate bare numbers as decimal values"). Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Reset the literal mode on each macro argumentMiquel Sabaté Solà2025-01-091-0/+20
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Evaluate bare numbers as decimal valuesMiquel Sabaté Solà2025-01-091-6/+11
| | | | | | | | | | In places like macro calls, the programmer might actually prefer to pass a numeric argument as is, without any prefixes. In these cases, just evaluate it as a decimal. In fact, this was already covered when evaluating the context because the same thing happens to assignments. Hence, I just needed to expand the scope of this. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Rename SourceInfo::working_directory to directoryMiquel Sabaté Solà2025-01-091-2/+2
| | | | | | Also documented the struct as it is exported. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Merge all error types into a single oneMiquel Sabaté Solà2025-01-091-132/+134
| | | | | | | | They started with good intentions, but in the end they were all pretty much alike. Hence, it makes sense to simplify everything and provide a single struct. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Remove ContextErrorReason as it was unusedMiquel Sabaté Solà2025-01-091-10/+1
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Implement the .include statementMiquel Sabaté Solà2025-01-091-81/+228
| | | | | | | | | | | | | | This needed some heavy lifting when it comes to how files were located. This means that statements like .include/.incbin now take into consideration a new list made out of SourceInfo, which holds enough information to translate from which file a node comes from. This has also been added into errors, so they are more informative on what went wrong. In order to tests this, besides all the regular unit tests, a new e2e test has been added. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Reset the literal mode on assignmentsMiquel Sabaté Solà2025-01-081-0/+15
| | | | Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
* Try absolute addressing on bad zeropage addressingMiquel Sabaté Solà2025-01-081-0/+31
| | | | | | | | | | | | | | | | | | | | | | | | Sometimes, out of clarity, the programmer may have written something along the lines of: lda $40, y This is invalid because the `lda` instruction does not allow zeropage y-indexing addressing mode. That being said, it does allow for absolute y-indexing addressing mode. This commit allows this syntax by transforming code like the previous one into: lda $0040, y This cannot always be done, but the assembler should at least try if it's possible and not trouble the programmer. That being said, this is otherwise a bit shady since the programmer might think that it's a 2-byte instruction when it's a 3-byte one. Hence, maybe a future linter can pick up code like this and suggest a more explicit writing. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>