aboutsummaryrefslogtreecommitdiff
path: root/crates/nasm/README.md
blob: 0eaf85462535b79332895060c3e4b5c18fc7f692 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
`nasm` is an assembler specifically tailored for the Famicom/NES. Because of
this, it does not try to replace other assemblers available out there which
cover all 6502 platforms and derivatives. The only goal is to have an assembler
that is suitable for NES/Famicom development.

As such, only features available for this platform have been implemented. All in
all, `nasm` is an assembler that makes a lot of assumptions on the target
platform, its memory layout, and how developers have to write their code. You
can read more about this down below.

Last but not least, `nasm` makes an effort at being flexible and as informative
to programmers as possible. Hence, error messages try to be concise, clear, and
pointing exactly where each error happened.

## Usage

The most basic way to use this assembler is by running:

```
$ nasm awesome.s
```

This will produce an `out.nes` file placed under the same working directory. You
can change the name of the file with the `-o/--output` flag. Hence, you can call
it like so:

```
$ nasm -o awesome.nes awesome.s
```

Moreover, you can actually tell `nasm` to redirect the output to stdout instead
with the `--stdout` flag. This is useful when debugging the binary format with
another CLI tool. For example:

```
$ nasm --stdout awesome.s | hexdump -C
```

The syntax for this assembler is virtually the same as the one for
[ca65](https://cc65.github.io/doc/ca65.html), even if some functions might be
missing. One difference that you will find in contrast with `ca65` is that
`nasm` is a bit more pedantic. Let's consider the following example:

```asm
.scope Scope
  .macro MACRO
    lda #0
  .endmacro
.endscope
```

Here `ca65` will place `MACRO` on the global scope. `nasm` will do the same but
it will also print a warning telling the programmer about this, as this might be
unexpected at first. Hence, as a general rule `nasm` will be more noisy than
`ca65`, in the hope that the programmer is more aware about the end result.

That being said, `nasm` is at the same time more flexible than `ca65`. For
exemple, the order of declaration for variables, proc's, etc. is not
important. Hence, the following code is valid in `nasm`, but not in `ca65`:

```asm
.proc foo
  ;; 'ca65' will complain that 'Variable' is not known.
  lda #Variable
  rts
.endproc

Variable = $00
```

### Exit code

The exit status code from `nasm` is the amount of errors that were
detected. Hence, if the exit status code is 0, then everything is fine,
otherwise you will get a count of errors.

Note that warnings that were turned into errors via the `-Werror` flag will also
be accounted.

### Interfaces specific to `nasm`

In contrast to `ca65`, there are some nasm-specific features. First of all,
`nasm` defines the `__NASM__` variable by default, with an integer value of
`1`. This way, if you plan on using something nasm-specific, you can always do
something like:

```asm
.ifdef __NASM__
  ;; whatever
.endif
```

The main design of this assembler from the interface's perspective was to be as
close to `ca65` as possible. Thus, using `__NASM__` shouldn't happen, as it's
expected from code written targetting `ca65` to "just work" on `nasm`. That
being said, if you use `nasm` there might be some specific features which are
missing in `ca65`. In this case, `nasm` has the `--prelude` flag, which will
print some code that can fill the gaps when running `ca65` with some
nasm-specific code. See below.

#### Defining global labels

For optimization reasons, sometimes it's necessary to write a label that can be
accessed globally, regardless of the current scope. The
[jetpac.nes](https://git.mssola.com/nes/jetpac.nes/) game has a good example of
this need on its `enemies.s` file. In there, we define a function pointer that
is set to the current function handler for the enemies' algorithm. Then enemy
handling can go along like this:

```asm
   ;; previous code

   lda #.hibyte(@return_from_movement_handler - 1)
   pha
   lda #.lobyte(@return_from_movement_handler - 1)
   pha
   jmp (zp_movement_fn)

@return_from_movement_handler:
   ;; Rest of the code after enemy movement has been handled.
```

That is, we push onto the stack the address of `@return_from_movement_handler`,
and then `jmp` to the function handler. Then, the function handler can simply
call `rts` and everything will be fine. As explained in the source code, other
techniques like trampolines or the "rts trick" were not desirable on this
context.

Moreover, note that a simple `jmp` was not possible from the context of enemy
handlers, as `@return_from_movement_handler` is inside of the scope of the
`Enemies::update` proc. For this game the performance penalty with this setup
was acceptable, but note that it's inside of a loop, and these extra cycles are
given for each enemy.

But this is potentially bad if your game is more tight on the cycle count and
every cycle you can squeeze matters. For this reason, `nasm` has the possibility
to define labels globally. That is, the above example could be rewritten like
this:

```asm
    jmp (zp_movement_fn)

#@return_from_movement_handler:
   ;; Rest of the code after enemy movement has been handled.
```

Notice the leading '#' symbol. This tells `nasm` that
`@return_from_movement_handler` should be defined at the global scope,
regardless of the current one (hence, it's no longer hidden inside of the
`Enemies::update` scope). With this, then each handler can switch from an `rts`
to:

```asm
    jmp @return_from_movement_handler   ; or hide it on an 'RTS_FROM_ENEMY_HANDLER' macro.
```

In total, for each iteration loop, the setup would save 10 cycles (2 cycles x
lda, 3 cycles x pha), and replacing each handler's `rts` with a `jmp` would save
3 cycles (6 cycles 'rts' - 3 cycles 'jmp'). That is, given that this game
allowed 4 enemies at once, then each call to `Enemies::update` could save 13
cycles x 4 enemies = 52 cycles. Not a crazy amount, yes, but this optimization
is now so easy to pull that it's well worth it.

#### The `__fallthrough__` pseudo-instruction

It's a [well-known
optimization](https://www.nesdev.org/wiki/6502_assembly_optimisations) to avoid
`jsr` + `rts` chains, and that's why programmers usually change `jsr` with `jmp`
in cases such as this:

```asm
.proc foo
  ;; code
  jmp bar

  ;; instead of:
  ;; jsr bar
  ;; rts
.endproc

;; comments, documentation, blank lines, whatever

.proc bar
  ;; code
  rts
.endproc
```

But if you try to assemble this code by using `nasm` you will get the following
warning:

```asm
warning: unconditional jump that points to the next instruction (bad_fallthrough.s: line 11)
```

This is because `foo` and `bar` happen to be contiguous and `nasm` is telling
you that a further optimization can be done by removing the `jmp` instruction
altogether. This is "falling through" the code as it's done in high-level
programming languages such as C. Hence:

```asm
.proc foo
  ;; code
  ;; fall through 'bar'
.endproc

;; comments, documentation, blank lines, whatever

.proc bar
  ;; code
  rts
.endproc
```

In these cases, when reading the code, a programmer might not be totally sure on
whether the code is missing an `rts` or a `jmp` instruction, or whether this
falling through was done on purpose. Hence, as it's done above, one idea is to
leave a comment for future reference.

But `nasm` also provides the `__fallthrough__` pseudo-instruction, which allows
developers to explicitly specify that the code flow is expected to fall through
to the next instruction, even if the code layout might not make this obvious, or
it might seem an accident. Hence, the above could have been written like so:

```asm
.proc foo
  ;; code
  __fallthrough__
.endproc

;; comments, documentation, blank lines, whatever

.proc bar
  ;; code
  rts
.endproc
```

This is not that much different than writing a comment. But this
pseudo-instruction also accepts an argument, which is the label that you expect
the code to fall through. Hence, the previous code can be better written like
this:

```asm
.proc foo
  ;; code
  __fallthrough__ bar
.endproc

;; comments, documentation, blank lines, whatever

.proc bar
  ;; code
  rts
.endproc
```

This is semantically similar, but we are also stating that we expect to fall to
the first instruction of `bar`. That is, if now the programmer moves `bar`
somewhere else, or puts some code in between both functions, then the assembler
will issue the following error:

```
error: statement expects to progress on 'bar' ($8004), but the next address is $8002 (bad_fallthrough.s: line 11)
```

This way, programmers can perform this optimization while also making sure that
the assembler will catch the error whenever code moves around.

Last but not least, the `--prelude` flag provides a default implementation so
`ca65` doesn't break on an otherwise unknown `__fallthrough__` identifier. The
implementation looks like this:

```asm
.ifndef __NASM__
  .macro __fallthrough__ arg
  .endmacro
.endif
```

## Mappers

By default `nasm` will assume the configuration for an `NROM` mapper, but this
can be changed with the `-c/--configuration` flag, with which you can pass the
path for the linker configuration file you'd like to use. This file can either
follow the same [ld65 syntax](https://www.cc65.org/doc/ld65-5.html), or a
simplified one (check out some samples for the "simplified" syntax
[here](../../lib/xixanta/src/mappings)).

All of that being said, and out of convenience, this flag also accepts this set
of **values**: `empty`, `nrom`, `nrom65`, `unrom`, `uxrom` and `mmc1`. These
values correspond to the configurations [already
bundled](../../lib/xixanta/src/mappings) on this application.

## Defining global values from the command line

You can define global values with the `-D` flag which follows a `NAME=VALUE`
syntax. Note that the value is expected to be in decimal format and it has to
fit in a byte. Hence, you could have a code like follows:

```asm
.ifdef PAL
  lda #1
.else
  lda #0
.endif
```

If you compile the code with `-D PAL=1`, then the first branch will be taken
instead of the second one.

## The .nasm/ directory

When you enable the `--write-info` flag, some files will be written into a
hidden `.nasm/` directory. This directory will contain debug information that
can be later used by other tools. That being said, some of these files can also
be useful to programmers. They are as follows:

- `segments.txt`: summary of the segments being used and how much they have been
  filled. Note that you need to also pass `--stats` to get this file.
- `memory.txt`: list of variables being used, expressed as ranges to account for
  reservation via the `asan:reserve` special comment. Note that you need to also
  pass `--asan` to get this file.
- `addresses.txt`: list of addresses known by the assembler, expressed as ranges
  to account for blocks of code like proc's. You don't need any other flag for
  this file.

## Unused code

This assembler will also issue a warning whenever it finds unreferenced
variables or labels. Hence, if you have something like:

```asm
foo:
    rts

;; code that never references 'foo'.
```

Then you will get:

```
warning: label 'foo' is unused (unused.s)
```

In the case for subroutines defined via `.proc`, this warning will actually be
an error, as `nasm` can rightly identify that this is dead code.

This check can be skipped by providing the `--allow-unused` flag on nasm.

## Cross-mapping references

This assembler will issue a warning whenever you are referencing an object which
is defined into a segment from another mapping. Some segments, like the
'vectors' one, will reference code that is outside of its mapping. But in some
other configurations, segments cannot make these cross-mapping references so
happily. Imagine that we have an UNROM chip configuration, where "SWAPPABLE" is
a segment that can be swapped according to the specification of this mapper
chip. Then, you could have code like this:

```asm
.segment "SWAPPABLE"

.proc foo
    rts
.endproc

.segment "FIXED"

jsr foo
```

Here the assembler will properly detect the address of 'foo' in the context of
the 'SWAPPABLE' segment. But what this assembler doesn't know is that this
segment is swappable. Hence, if the bank being mapped right now is not the one
containing the 'SWAPPABLE' segment, then the address computed for 'foo' and used
in that 'jsr' instruction will point to something else entirely. This would be
similar to a use-after-free bug.

This is something that can only be inspected at runtime, and so the assembler
cannot be of much help here. Hence, `nasm` adds a warning so the programmer can
understand the potentially dangerous operation.

All of that being said, this assembler also adds support for "asan:safe" or
"check:safe", which is a magic comment that the programmer can write to
re-assure the assembler that this operation is fine (e.g. there is a guarantee
that the mapped bank is that one we are expecting). Hence, the code above could
now be written like so:

```asm
jsr foo      ; check:safe
```

Moreover, you can define whole segments as "fixed" ones with the
`asan:fixed-segments` (or `check:fixed-segments`) comments. This way, you
express to the assembler that references to addresses of these segments are
guaranteed to always be valid. Consider the following example:

```asm
;;; asan:fixed-segments ONE, OTHER

.segment "ONE"

.proc foo
    rts
.endproc

.segment "OTHER"

.proc bar
    rts
.endproc

.segment "FIXED"

jsr foo
jsr bar
```

In the code above, we state that both 'ONE' and 'OTHER' are guaranteed to have a
stable address space (they are fixed, never to be re-mapped). Hence, the
assembler won't spit any warning at the final two 'jsr' instructions. Also note
that you can define this comment multiple times. So the code below achieves the
same thing:

```asm
;;; asan:fixed-segments ONE
.segment "ONE"
;; bla bla

;;; asan:fixed-segments OTHER
.segment "OTHER"
;; rest
```

## Address sanitizer

This assembler comes with a set of tools that builds up an "address
sanitizer". Some of its functionality is already included, while some other is
opt-in via a special command line option.

### Reserved memory

This assembler can detect the memory regions being used and make decisions out
of it. This comes with a few gotchas that the programmer has to be aware in
order for the assembler to be useful. Because of this, the tooling around this
detection is behind the `-a/--asan` flag.

The address sanitizer will blindly follow the naming conventions from
[style.nes](https://git.mssola.com/nes/style.nes), and assume at first that each
variable takes 1 byte exactly. Hence, in order to reserve one byte, you can
simply:

```asm
zp_variable = $20
```

Then the address sanitizer will assume that you are reserving a byte at address
`$20` which will be used throughout the code. If you want to reserve more than a
byte, then:

```asm
zp_buffer = $20   ; asan:reserve $0F
```

Then the address sanitizer will assume that `zp_buffer = $20..$2F
(included)`. Any access to this reserved range that is not strictly via
`zp_buffer` will be considered a conflict. For example:

```asm
zp_buffer = $20   ; asan:reserve $0F
zp_bad    = $22   ; NOTE: The address sanitizer will mark it as a *conflict*.
```

If you want to ignore this (e.g. you are using a variable that shadows other
ones), then you can explicitely tell the address sanitizer to ignore a given
assignment or instruction:

```asm
zp_buffer = $20   ; asan:reserve $0F
zp_good   = $22   ; asan:ignore
lda $200          ; asan:ignore
```

That is, in order for the address sanitizer to be successful, it will also warn
programmers whenever an access to memory is being done without using
variables. This allows for the address sanitizer to be more thorough, even if
there is never the guarantee that memory accesses will be safe. For example:

```asm
zp_variable = $20

ldx #20
lda zp_variable, x
```

This will access memory far beyond to `zp_variable` which was only reserving a
single byte. This is beyond the scope of this tool and other tools should be
used instead (e.g. an emulator with breakpoints on accesses to unexpected memory
regions, or `vnf` from this project).

On another note, the address sanitizer is also able to do some basic bound
checks. For example:

```asm
zp_variable = $20     ; asan:reserve $02

lda zp_variable       ; good
lda zp_variable + 1   ; good: arithmetics within the reserved limits.
lda zp_variable + 2   ; bad: arithmetics that would point to out of bounds.
lda zp_variable - 1   ; bad: same as before but wrapping around.
```

Last but not least, what if we want to reserve some space which is not exactly
assigned to a variable? This happens basically with the stack, and for this you
can use the following statement:

```asm
;; asan:stack $0100-$01FF
```

Only one of these statements is allowed for a project. You can explicitely
indicate that we are on the `$0100` page as above, or you can especify just a
byte:

```asm
;; asan:stack $00-$FF
```

The above will implicitely assume we are on the `$0100` page. But, for this
simple case, a shorthand is given when you want to use the whole page:

```asm
;; asan:stack full
```

With all of this, `nasm` will be able to tell how much memory you've been using
so far, and if you add the `--write-info` option, you will also know how this
memory is laid out by reading the `.nasm/memory.txt` file. Couple that with the
`--stats` option, and for a given project you will be able to know:

1. How much do you have left in memory and in your ROM segments.
2. You have no conflicting variables or variables that shadow others in
   unexpected ways.
3. Where exactly you are reserving RAM and ROM space.
4. Basic arithmetics don't make you fall out of bounds.

### Working RAM

In NES/Famicom programs you have to advertise on the header whether Working RAM
is being used or not (see [byte 6 on the iNES
format](https://www.nesdev.org/wiki/INES)). The programmer is expected to set
this flag on if PRG RAM is available. If there are memory accesses to PRG RAM
but the programmer did not advertise that on the header flag, then `nasm` will
error out as it cannot assume that these accesses are valid. This check will
happen regardless of the `-a/--asan` flag.