Making "Good" Assembly Code

You can talk about almost anything that you want to on this board.

Moderator: Moderators

User avatar
nicklausw
Posts: 376
Joined: Sat Jan 03, 2015 5:58 pm
Location: ...
Contact:

Making "Good" Assembly Code

Post by nicklausw » Mon Jul 20, 2020 1:23 pm

(The code in this post is for SNES, but it pertains to any assembly language).

I'm alive, despite the one post in the last 2 years. Time flies. Why the time away from here? Life, and also because of an issue I've had anytime I try and come back to assembly coding: it's so ugly.

For instance, see my last attempt at a game for SNES. I was so proud of myself for finding an assembler (bass) that leaves behind all the syntactical nastiness that we've come to see as normal. The lack of a linker was also a huge thing. Why make the assembler throw away all these details and give the bare minimum to a linker, then bother to fix errors from there? Support for multiple modules might make sense if you were assembling the code on a SNES, but who would torture themselves like that?

My point being, the code is still ugly, even with a more "modern" syntax and all the curly braces that come with it. For example:

Code: Select all

function sync {
    php
  a8()
  -;bit >VBLSTATUS  // wait for leaving previous vblank
    bmi -
  -;bit >VBLSTATUS  // wait for start of this vblank
    bpl -
    plp
    rtl
}
At first glance, this is fine. But then while in the process of trying to write more code, I'll look back on this function and say, "this could be condensed more. Let's do that." Then the two lines with bit >VBLSTATUS become a bit longer, because why should a short loop take up more than a line of code?

Code: Select all

-;bit >VBLSTATUS; bmi -  // wait for leaving previous vblank
-;bit >VBLSTATUS; bpl -  // wait for start of this vblank
It's a small example, but looking through the repository, there's a million potential cosmetic changes to be made, and from experience, I will spend countless hours making them. 20 git commits later, the binary may come out exactly the same, but it's all about making everything pretty to look at, right? Just ignore that the game itself is still a portion of Sektor's sprite moving around a blank screen, and absolutely nothing else.

This happens anytime I try to return to assembly coding; the beautification takes over the urge to actually write any new code. Why program a scrolling background when your routine to sync the VBlank is looking so nasty? Why get a sprite to move around the screen when the code to display it still isn't properly indented? The assembler may throw away the white space, but that doesn't mean you shouldn't lose sleep over it...sarcasm fully intended.

So the question: do you guys also experience this issue when writing assembly, and if so, what do you do to work past it? Does anyone else never have this issue with newer languages like C++? Is it possible that I made the problem worse trying to find a "modern" assembler?
Last edited by nicklausw on Fri Jul 24, 2020 9:07 pm, edited 1 time in total.

Garth
Posts: 187
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Making "Beautiful" Assembly Code

Post by Garth » Tue Jul 21, 2020 12:41 am

Macros to the rescue. Really. You can raise the level of the language a lot, reducing source-code length, improving readability and maintainability, reducing bugs, and improving development time. In most cases, there will be zero penalty in run speed or memory taken, because the macros assemble exactly the same thing you would do by hand, except now you don't have to look at the ugly internal details anymore, and you'll produce fewer bugs because you can see what you're doing better. Most local labels become unnecessary, because the structure macros take care of branch addresses and targets. You can nest program structures of the same or different types, many levels deep, and all the branches will go to the correct places. I've been doing programming this way for about ten years. I wish I had thought of it in the mid-1980's! It would have saved a lot of work.

I have an article on using macros to form nestable program flow control structures, at http://wilsonminesco.com/StructureMacros/ . I much prefer the natural-language characteristic over curly braces, and it should be implementable on any macro assembler with very few modifications needed to make it work. There are a few extended examples of the macro usage in the last 40% of the page at http://wilsonminesco.com/multitask/, and further explanation of the macros' inner workings in the relevant chapter of the 6502 stacks treatise, at http://wilsonminesco.com/stacks/pgmstruc.html .

Andrew Jacobs (6502.org forum name "BitWise") has published his free AS65 assembler that has a similar thing built in, at http://www.obelisk.me.uk/dev65/as65.html . Also, Anton Treuenfels (6502.org forum name "teamtempest") has his HXA assembler he wrote taking my program structures and incorporating them (perhaps with slight modification to a few—I don't remember), at https://web.archive.org/web/20190208204 ... .net/~hxa/ .
http://WilsonMinesCo.com/ lots of 6502 resources

tepples
Posts: 22044
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Making "Beautiful" Assembly Code

Post by tepples » Tue Jul 21, 2020 6:41 pm

nicklausw wrote:
Mon Jul 20, 2020 1:23 pm
Why make the assembler throw away all these details and give the bare minimum to a linker, then bother to fix errors from there?
Q: Why are C programs broken into multiple translation units even now that we have multi-gigabyte RAM in even entry-level laptop PCs?

A: To create a concept of file scope or internal linkage as an approximation to the modular programming concept of module scope. The static keyword of the C programming language provides a way to declare a variable or function within a translation unit that no other code outside that translation unit can see. This gives the author of the module a chance to change the use of that variable or function without risking breaking other modules that depend on its behavior. It also lets the author of the module assign names that don't clash with similar names assigned in other modules.

Without module scope or some other namespace mechanism, you end up with prefix hell. Consider the following melody pattern with one line per measure:

Code: Select all

PPDAT_bf98_melodyA:
.byte REST|D_D8,N_D|D_8,REST,N_F|D_8,REST
.byte N_DS|D_8,N_F,N_DS|D_8,REST,N_C|D_8,REST
.byte N_D|D_8,N_DS,N_F|D_8,REST,N_AS|D_8,REST
.byte GRACE,5,N_GS,N_AS|D_D8,N_GS|D_4,N_TIE,REST
.byte PATEND
becomes this, which extends past the right side of the source code editor window:

Code: Select all

PPDAT_bf98_melodyA:
.byte PENTLY_REST|PENTLY_D_D8,PENTLY_N_D|PENTLY_D_8,PENTLY_REST,PENTLY_N_F|PENTLY_D_8,PENTLY_REST
.byte PENTLY_N_DS|PENTLY_D_8,PENTLY_N_F,PENTLY_N_DS|PENTLY_D_8,PENTLY_REST,PENTLY_N_C|PENTLY_D_8,PENTLY_REST
.byte PENTLY_N_D|PENTLY_D_8,PENTLY_N_DS,PENTLY_N_F|PENTLY_D_8,PENTLY_REST,PENTLY_N_AS|PENTLY_D_8,PENTLY_REST
.byte PENTLY_GRACE,5,PENTLY_N_GS,PENTLY_N_AS|PENTLY_D_D8,PENTLY_N_GS|PENTLY_D_4,PENTLY_N_TIE,PENTLY_REST
.byte PENTLY_PATEND
or this with one command per line and a blank line after each measure, which risks extending past the bottom of the window:

Code: Select all

PPDAT_bf98_melodyA:
.byte PENTLY_REST|PENTLY_D_D8
.byte PENTLY_N_D|PENTLY_D_8
.byte PENTLY_REST
.byte PENTLY_N_F|PENTLY_D_8
.byte PENTLY_REST

.byte PENTLY_N_DS|PENTLY_D_8
.byte PENTLY_N_F
.byte PENTLY_N_DS|PENTLY_D_8
.byte PENTLY_REST
.byte PENTLY_N_C|PENTLY_D_8
.byte PENTLY_REST

.byte PENTLY_N_D|PENTLY_D_8
.byte PENTLY_N_DS
.byte PENTLY_N_F|PENTLY_D_8
.byte PENTLY_REST
.byte PENTLY_N_AS|PENTLY_D_8
.byte PENTLY_REST

.byte PENTLY_GRACE,5,PENTLY_N_GS
.byte PENTLY_N_AS|PENTLY_D_D8
.byte PENTLY_N_GS|PENTLY_D_4
.byte PENTLY_N_TIE
.byte PENTLY_REST
.byte PENTLY_PATEND
In the preview of this post on my screen, the first of these code examples has no scroll bar, the second has a horizontal scroll bar, and the third has a vertical scroll bar.

User avatar
nicklausw
Posts: 376
Joined: Sat Jan 03, 2015 5:58 pm
Location: ...
Contact:

Re: Making "Beautiful" Assembly Code

Post by nicklausw » Tue Jul 21, 2020 9:51 pm

Garth wrote:
Tue Jul 21, 2020 12:41 am
Macros to the rescue. Really. You can raise the level of the language a lot, reducing source-code length, improving readability and maintainability, reducing bugs, and improving development time. In most cases, there will be zero penalty in run speed or memory taken, because the macros assemble exactly the same thing you would do by hand, except now you don't have to look at the ugly internal details anymore, and you'll produce fewer bugs because you can see what you're doing better.
I agree that macros are a godsend for countless reasons, and am an avid user of them now. I avoided them for a long time as an over-correction for an old habit of putting entire subroutines inside macros. In other words, trying to minimize the use of jsr for no reason. Once you find the middle ground, the code becomes infinitely nicer to look at regardless of the assembler.

It could be argued that macros make the problem mentioned in the OP of "countless hours revising what's already there" even worse, but I think it's a worthy trade-off. Especially when your project would have no memory management without them.
Garth wrote:
Tue Jul 21, 2020 12:41 am
I wish I had thought of it in the mid-1980's! It would have saved a lot of work.
Would it have been possible back then? I'm unfamiliar with how good 6502 assemblers were in their heyday. Not sure how old the Cross-32 assembler from your article is, but the site it's hosted on and its $99 price tag tells me it certainly isn't new. And you also imply that it used to be even more expensive. Sheesh.
tepples wrote:
Tue Jul 21, 2020 6:41 pm
Without module scope, you end up with prefix hell
Namespaces to the rescue!

Code: Select all

namespace a {
  variable a = 2; // a.a = 2
  lda #a // 2
}
namespace b {
  variable a = 3; // b.a = 3
  lda #a // 3
}
It's unfortunate that this option isn't common among assemblers. I do love asm6 in all its single-C-file glory, but as your linked topic shows, trying to avoid "namespace pollution" when namespaces and modules don't exist is an ugly issue. My hat is off to anyone who can get asm6 to support namespaces. It's a nice program, but only because it avoids complicated features like that. It'd even be easier to write a Python script that adds the name prefixes to Pently on your behalf, though I guess that reduces portability.

Oziphantom
Posts: 913
Joined: Tue Feb 07, 2017 2:03 am

Re: Making "Beautiful" Assembly Code

Post by Oziphantom » Wed Jul 22, 2020 12:31 am

Try 64Tass it has a lot of things to nicen code and it also has a built in linker to get you all nice things.

Asm is pure neatness itself

LABEL OOO PARAM ; COMMENT
all the way down for example

Code: Select all

NMIA	STA NMIHOLD
	LDA CIA2+13
	STA NMIHOLD+1
	AND #2
	BNE NMIB	
	LDA NMIHOLD+1
	AND #1
	BEQ HOLT

	LDA VICMCR
	AND #253
	STA VICMCR
	LDA #%11111111
	STA MULTICOL
	LDA #8	; 5
	STA SPC0
	STA SPC1
NMSB	LDA #0
	STA MSB
NX6	LDA #0
	STA X0
NY6	LDA #0
	STA Y0
NX7	LDA #0
	STA X1
NY7	LDA #0
	STA Y1
NP6	LDA #BL
	STA SPRITE0A
	STA SPRITE0B
NP7	LDA #BL
	STA SPRITE1A
	STA SPRITE1B
	LDA #0
	STA CIA2+14
	LDA CIA2+13
	LDA CIA2+14
HOLT	LDA NMIHOLD
RESET	RTI
However I don't code in this style any more. That is a relic of the TMP/PDS era.

But doing
- bit VBLSTATUS : bmi - is something I can not condone. Single op per line is the ASM way. Although in days long past were we had to program on a 40x25 screen and macros were not an option or would triple assembly time, sure you do it. But in the modern 80+x50+ era with I can have a million macros and it assembles in seconds no.
Use #WaitVBlank making a LD or MOV instruction so your

Code: Select all

lda #1
sta $0400
lda #5
sta $0401
becomes

Code: Select all

MOV #1,$0400
MOV #5,$0401
does help a bit as well. I made MLA so I just do things like this

Code: Select all

	!!if PlayerY >= #kSprites.PlayerMiddleY then _FacingUp
		!!kVectors.sprPtr6 = #kSprites.PlayerDownFrame+1
		!!kVectors.sprPtr7 = #kSprites.PlayerDownFrame
		.cerror kSprites.PlayerDownFrame == 0, "need to change branch" 
		bne _CheckYEnd
	_FacingUp
		!!kVectors.sprPtr6 = #kSprites.PlayerUpFrame+1
		!!kVectors.sprPtr7 = #kSprites.PlayerUpFrame
	_CheckYEnd
	rts
not "pretty" but makes the code a lot easier to read. Only my tabs are 3 not 8

In 64Tass you can turn this

Code: Select all

PPDAT_bf98_melodyA:
.byte PENTLY_REST|PENTLY_D_D8,PENTLY_N_D|PENTLY_D_8,PENTLY_REST,PENTLY_N_F|PENTLY_D_8,PENTLY_REST
.byte PENTLY_N_DS|PENTLY_D_8,PENTLY_N_F,PENTLY_N_DS|PENTLY_D_8,PENTLY_REST,PENTLY_N_C|PENTLY_D_8,PENTLY_REST
.byte PENTLY_N_D|PENTLY_D_8,PENTLY_N_DS,PENTLY_N_F|PENTLY_D_8,PENTLY_REST,PENTLY_N_AS|PENTLY_D_8,PENTLY_REST
.byte PENTLY_GRACE,5,PENTLY_N_GS,PENTLY_N_AS|PENTLY_D_D8,PENTLY_N_GS|PENTLY_D_4,PENTLY_N_TIE,PENTLY_REST
.byte PENTLY_PATEND
into

Code: Select all

PPDAT_bf98_melodyA:
.byte PENTLY.(REST|D_D8,N_D|D_8,REST,N_F|D_8,_REST)
.byte PENTLY.(N_DS|D_8,N_F,N_DS|D_8,REST,N_C|D_8,REST)
.byte PENTLY.(N_D|D_8,N_DS,N_F|D_8,REST,N_AS|D_8,REST)
.byte PENTLY.(GRACE,5,N_GS,N_AS|D_D8,N_GS|D_4,N_TIE,REST)
.byte PENTLY_PATEND
but you could also use the namespace directive

Garth
Posts: 187
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Making "Beautiful" Assembly Code

Post by Garth » Wed Jul 22, 2020 1:45 am

nicklausw wrote:
Tue Jul 21, 2020 9:51 pm
Garth wrote:
Tue Jul 21, 2020 12:41 am
I wish I had thought of it in the mid-1980's! It would have saved a lot of work.
Would it have been possible back then? I'm unfamiliar with how good 6502 assemblers were in their heyday. Not sure how old the Cross-32 assembler from your article is, but the site it's hosted on and its $99 price tag tells me it certainly isn't new. And you also imply that it used to be even more expensive. Sheesh.
Yes; I was using the 2500AD starting in 1986 or 7, which is also an excellent macro assembler and linker that ran under DOS. I can't believe how much my employer spent on that one! I seem to recall it was $750. We ran it on a "portable" 8088 PC, and soon after, a PC-XT. (I put "portable" in quotes because this was back when you could almost put a handle on a refrigerator and call it portable. The PC had maybe a 7" or 9" green monochrome monitor built in, two floppy-disc drives beside it, the keyboard stowed in the cover over the front, and the whole thing probably weighed 40 pounds.) The assembler definitely could have done the job if I had thought of it. I don't know how long C32 had been out when I bought my copy in 1995. I think I spent $200 or $250 back then (probably reimbursed by my employer which was a different one by then). At the time, the choices for the medium were 5" or 3½" floppy disc. A nice thing about C32 is that you only buy it once and you can assemble for dozens of processors, and they give you the information you need to adapt it even to a new processor of your own design if you want to!
http://WilsonMinesCo.com/ lots of 6502 resources

Oziphantom
Posts: 913
Joined: Tue Feb 07, 2017 2:03 am

Re: Making "Beautiful" Assembly Code

Post by Oziphantom » Wed Jul 22, 2020 4:32 am

It existed back in 1984 https://www.pagetable.com/?p=848 the Lucas Arts Macros Assembler does it an more.

As for Macro assemblers, we had them on actually Commodore 64s and 128s. Turbo Macro Pro being the defacto, but Merlin etc added them. Once you get the Cross Dev, PDS supports Macros as well.

User avatar
pubby
Posts: 557
Joined: Thu Mar 31, 2016 11:15 am

Re: Making "Beautiful" Assembly Code

Post by pubby » Wed Jul 22, 2020 4:16 pm

Stick to the convention of 1 thing per line, whether it be an instruction, a label, or a macro invocation. Don't even consider condensing code.

User avatar
aa-dav
Posts: 102
Joined: Tue Apr 14, 2020 9:45 pm
Location: Russia

Re: Making "Beautiful" Assembly Code

Post by aa-dav » Wed Jul 22, 2020 9:05 pm

Oziphantom wrote:
Wed Jul 22, 2020 12:31 am
...
MOV #1,$0400
...
Ah... first macro I wrote in NES programming experience and such a relief! :) store, store_addr, store_word, store_ppu_addr made 70% of 6502 assembler simplifications for me. :)

Garth
Posts: 187
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Making "Beautiful" Assembly Code

Post by Garth » Wed Jul 22, 2020 11:59 pm

aa-dav wrote:
Wed Jul 22, 2020 9:05 pm
Oziphantom wrote:
Wed Jul 22, 2020 12:31 am
...
MOV #1,$0400
...
Ah... first macro I wrote in NES programming experience and such a relief! :) store, store_addr, store_word, store_ppu_addr made 70% of 6502 assembler simplifications for me. :)
I made mine a little more English-like:

Code: Select all

        PUT  1, in, $0400
PUT is for literals. If I want to copy one variable's contents to another's, I use COPY:

Code: Select all

        COPY  FOO, to, BAR
(I much prefer "copy" to "move," because "move" incorrectly implies that the original place won't have the data anymore.)

To copy the two bytes of a 2-byte variable, it's COPY2:

Code: Select all

        COPY2  FOO, to, BAR
Or, if a different emphasis is appropriate for the situation, it could be "from" instead of "to," so you can reverse the order:

Code: Select all

        COPY  BAR, from, FOO
The "to" and "from" prepositions are EQUates (ie, constants) which the macro tests at assembly time to determine which way to do the resulting code. You can have a ton of conditional assembly in macros. I've had macros that may have 50 lines and yet only assemble two instructions, based on the conditions.

Unfortunately most assemblers require macro parameters to be separated by commas, making the code slightly less "beautiful."
http://WilsonMinesCo.com/ lots of 6502 resources

calima
Posts: 1186
Joined: Tue Oct 06, 2015 10:16 am

Re: Making "Beautiful" Assembly Code

Post by calima » Thu Jul 23, 2020 12:06 am

(I much prefer "copy" to "move," because "move" incorrectly implies that the original place won't have the data anymore.)
That bit me too when first learning 6502 asm. "TXA" - transfer X to A - implies the contents of X aren't in X after the instruction. Crappy naming by whoever designed it.

Oziphantom
Posts: 913
Joined: Tue Feb 07, 2017 2:03 am

Re: Making "Beautiful" Assembly Code

Post by Oziphantom » Thu Jul 23, 2020 12:13 am

What do you expect to be in the original place?

Mov is an Intel thing, they are horribly American and did some really silly American things. See also XOR. Then Push and Pop which then inspired other people to come up with "Pop and Top" rather than Push, Peek, Pull :roll:

User avatar
nicklausw
Posts: 376
Joined: Sat Jan 03, 2015 5:58 pm
Location: ...
Contact:

Re: Making "Beautiful" Assembly Code

Post by nicklausw » Thu Jul 23, 2020 11:00 am

Oziphantom wrote:
Thu Jul 23, 2020 12:13 am
What do you expect to be in the original place?
I'd expect $00. Not sure why, just sounds right.
pubby wrote:
Wed Jul 22, 2020 4:16 pm
Stick to the convention of 1 thing per line, whether it be an instruction, a label, or a macro invocation. Don't even consider condensing code.
This seems to be the consensus, and I usually stick to that convention. Some sets of instructions just bother me when they take up multiple lines. Especially "compare this and branch based on its value". See this Gameboy RAM clearing routine.

Code: Select all

ram_l:
  xor a // ld a,0
  ldi [hl],a // load ram location with 0
  // ldi means "load and increment hl"
  
  // now check for $dfff
  ld a,h; cp $df; jr nz,ram_l
  ld a,l; cp $ff; jr nz,ram_l
It might make some of you throw up, but I get the same way with C++. See these random excerpts from a project.

Code: Select all

switch (c & 4) {
  case 1: { Student s(Freshman); Students.push_back(s); break; }
  case 2: { Student s(Sophomore); Students.push_back(s); break; }
  case 3: { Student s(Junior); Students.push_back(s); break; }
  case 4: { Student s(Senior); Students.push_back(s); break; }
}

Code: Select all

string name = "";
name.append(first); name.append(" "); name.append(last);
I suppose in terms of assembly, this is another case where things can be hidden inside macros instead, something like branchNotEqual(h, $df, ram_l). You'd just have to be aware that the accumulator will get eaten if it isn't the register being compared.

Pokun
Posts: 1511
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Making "Beautiful" Assembly Code

Post by Pokun » Thu Jul 23, 2020 3:42 pm

Semantic carnage time!
Yeah that about MOV have always puzzled me since even in computer terms, a MOVE implies a COPY followed by a DELETE of the original data. On the other hand I guess LOAD and STORE along with TRANSFER also means moving something.

It's weird but PUSH and POP seems to be the standard terms for stacks nowadays, despite PULL being the clear opposite of PUSH. But when I visualize a stack I would rather PUT or PLACE something on top of it and then TAKE or LIFT it off when needed. Pushing and pulling (and "popping" whatever that means) sounds like you are trying to push in or out something from the bottom or the middle of the stack, which is illegal according to the LIFO stack rules, and counter-intuitive to the whole stack metaphor.

As for XOR, how do you even pronounce that? Like [zor]? XFER is another weird spelling of transfer.

zzo38
Posts: 1075
Joined: Mon Feb 07, 2011 12:46 pm

Re: Making "Beautiful" Assembly Code

Post by zzo38 » Thu Jul 23, 2020 7:04 pm

Pokun wrote:
Thu Jul 23, 2020 3:42 pm
Semantic carnage time!
Yeah that about MOV have always puzzled me since even in computer terms, a MOVE implies a COPY followed by a DELETE of the original data. On the other hand I guess LOAD and STORE along with TRANSFER also means moving something.
In the "Checkout" esolang, the "move" command does get rid of the original data. According to the specification, "it's conceptually equivalent to a copy followed by a discard, but is often more efficient".
It's weird but PUSH and POP seems to be the standard terms for stacks nowadays, despite PULL being the clear opposite of PUSH.
A common macro in Glulx assemblers uses "pull" instead of "pop". This is also supported in Inform, both for Glulx and for Z-code.
Pushing and pulling (and "popping" whatever that means) sounds like you are trying to push in or out something from the bottom or the middle of the stack, which is illegal according to the LIFO stack rules, and counter-intuitive to the whole stack metaphor.
Some programming languages do allow taking from the middle of the stack, including Forth and PostScript, and Glulx. Also in Magic: the Gathering, objects can be taken from the middle of the stack if they are countered. Of course, in all of these cases, normally you only deal with the top of stack, in that order, although sometimes there are dealings with stuff in the middle too.
As for XOR, how do you even pronounce that?
I read somewhere they punch themself and then "or", but perhaps that is a joke.

In terms of making nice assembly code, there are some things:
  • You can use macros for many purposes (although I tend to avoid it when it makes the code inefficient).
  • Comments are helpful in assembly language code.
  • MIXAL and MMIXAL have relative labels which use a number and H, and then you can refer to it by the number followed by B or F (for backward or forward). I find this helpful, and also implemented it in Glasm (my own assembler for targeting Glulx).
  • Allow macros to jump to different parts of the code, and/or to allow a "come from" command. My own variant of MIXAL has a "come from" command for making jump tables.
[url=gopher://zzo38computer.org/].[/url]

Post Reply