"structured assembler" for gas...

Go To Last Post
28 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Has anyone managed to implement "structured assembler" macros for the gnu assembler? You know, like:

  cpi r17, 12
  _ife
    ;;do stuff.
  _else
    ;; do other stuff
  _endif

I've seen two separate ways to do this, one of which relies on macros being able to do stringification of symbol values, and one of which relies on being able to set ".org" (or equivalent) backward.
Apparently neither of these is possible in the gnu assembler :-(

OTOH, I've recently seen some assembler that hints that there might be some commonly used "brute-force" techniques for addressing stringification (at least for a limited range of values), so perhaps someone has already implemented these after all?

Have they? Are there "structured assembler" macros for gas?

(web searches seem pretty hopeless...)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Quote:
Are there "structured assembler" macros for gas?

I know you'll hate me for the answer, but yes there is. It's called "C". :wink:

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Rats, Johan beat me to it, I was going to say exactly the same thing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why not give a practical example ?

You can easily make for(), while(), if() constructions with macros.

I used to do exactly that before I discovered C.

Note that you generally end up with brute force, but it may be worth the convenience.

If you use a HLL, the compiler will remove unused code and impossible branches. With ASM you can revel in your ignorance.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

First, I agree with the others about c.
Just played a little. Since you can reuse numerical labels you could simply do something like this

.macro _ifeq1
        breq .+2                ; Skip next instruction if Z set
        rjmp 99f                ; Jump to _else1
.endm

.macro _else1
        rjmp 98f                ; Skip the else block
99:     
.endm

.macro _endif1
98:
.endm

but that will require that you always use an _else1. One solution to that could be

.macro _ifeq2
        else_used = 0           ; Reset 
        breq .+2                ; Skip next instruction if Z set
        rjmp 99f                ; Jump to _else2 or _endif2
.endm

.macro _else2
        else_used = 1           ; tell _endif2 that else have been used
        rjmp 98f                ; Skip the else block
99:     
.endm

.macro _endif2
.if else_used == 1
98:
.else
99:
.endif
.endm

Support for nesting would be a little harder I guess.

=======================================================================
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
You can easily make for(), while(), if() constructions with macros.

Such as? Allowing nesting and everything?
I've done this in several assemblers, pre-gnu. There were whole mainframe operating systems that made extensive use of similar macros. There are pre-processors that do it now (with relatively arbitrary target assemblers), touted for use in classes and such. So it's not like there is zero interest in such a thing. Sometimes you just need more determinism than C can provide, and there is no reason that using assembler should be any more painful than it has to be.

A "standardized" gas-based solution would immediately enable such structure in a whole gaggle of cpus, not just AVRs. Including some for which (AFAIK) gas is the only available assembler.

I *think* that it ought to be possible by using local labels. Some of the code I have looks a lot like it spends most of its effort generating the equivalent of "local labels." But if it's so easy, where is the "standard implementation" ??

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes. You can do a certain amount of nesting. After all, you are using standard constructions. e.g.

.macro FOR start, condition, after

You just pass the relevant macro/subroutine_name for start, etc.

Yes, it is fairly hairy to write complex constructions. Yes, you have to use local labels all over the place.

I used to do this for 6502 ASM some 30 years ago. Admittedly I wrote my own assembler and have forgotten the syntax. But the principle is exactly the same for GNU assemblers. just different syntax.

It was convenient for the 6502 to have 16-bit macros for most operations, and to have primitive loop constructions. However, nowadays I would simply use C. Then hand-optimise.

Seriously, most C haters tend to be so because they think that C has complex syntax and rules. I can assure you that complex ASM macros are far harder to get your head around.

They tend to be .MACRO haters too. They certainly would never consider #define or even M4 etc.

So most ASM programmers never get further than simple macros like LOAD and STORE. If they do, then their 'individuality' comes to prominence. ---- i.e. they don't want to use no standard anything.

If you were to quote an example problem, I suspect our Scandivanian colleagues would have some good suggestions.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
If you were to quote an example problem

What "problem" ? Clearly this sort of thing is just "nice to have", helping you get rid of all those silly labels. For instance, there's no reason that the hypothetical strlen function needs two extra labels (loop start and null branch destination):

strlen: clr r10
        _begin                          ;Start of loop
          ld r17, X+
          and r17, r17                  ;check for end of string
          _if ne
             inc r10                    ;  nope.  inc count and
             _loop                      ;  loop always...
          _endif
        ret
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Would you consider a preprocessor of some sort? Clearly you could just write something in your favourite language that reads the source you write and expands out your favoured macro syntax or you may be able to achieve it with an existing, powerful macro language like GNU m4.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Say that you wanted a macro called STRLEN that takes a literal address.

     .macro STRLEN ads
     ldi  XH,hi8(ads)
     ldi  XL,lo8(ads)
     ldi  R10, -1
1:   inc  R10
     ld   R17,X+
     tst  R17
     brne 1b
     .endm

You might find it handy to have a LDIX macro

     .macro LDIX value
     ldi  XH,hi8(value)
     ldi  XL,lo8(value)
     .endm

You might find it handy to have a TSTBRNE macro

     .macro TSTBRNE reg, dest
     tst  reg
     brne dest
     .endm

I am sure that gas has got sufficient rope for punters to hang themselves with. There comes a time when some 'aids' become more trouble than they are worth.

I can't see the point in your '_begin' and '_loop' macros. For a start, they don't seem to have any arguments. So you would need to use .set expressions in the definition which are inherently global. Hence you destroy all nestability.

Macros are sequences rather than subroutines. So you would actually do something like this:

strlen_func:
    PUSH16 X
    push   R17 
    STRLEN message_address
    pop    R17
    POP16  X
    ret

In practice, I can see sense in using 16-bit arithmetic macros. I can see sense in FOR, WHILE, UNTIL macros.

In all honesty, I would just write a strlen function straight off.

If you want to see some Atmel Assembler2 macros in action, just look at the CodeVision Evaluation LST files for a few tips.

Note that Assembler2 is completely crap at macros. avr-as is far more capable.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Would you consider a preprocessor of some sort?

I'd rather not. Parsing complexity becomes large. Besides, it's already been done...
http://www.plantation-production...

Here's a macro-based implementation and description that work for assemblers other than gas:
http://dkeenan.com/AddingStructu...

Quote:
Say that you wanted a macro

Yes, yes. I'm familiar with traditional macros.
Implementing the "structure" macros is a bit more complex. Typically "if" and "else" generate a single branch to a created target label (could be local), and "else" and "end" create the necessary labels. Add a counter or two to handle nesting/etc. The implementation(s) I'm looking at (Macro-10 and MASM) use two or three separately counted namespaces; otherwise you might not ever need more than gas's local labels...

I actually have some code that works, but it requires code like:

.macro _gtbeg2jmp ind, jmp
	.if (\ind == 0)
	\jmp __BEG0
	.elseif (\ind == 1)
	\jmp  __BEG1
	.elseif (\ind == 2)
	\jmp  __BEG2
	.elseif (\ind == 3)
	\jmp __BEG3
	.elseif (\ind == 4)
	\jmp __BEG4
;;;
;;; And etc for as many clauses as you might have
;;;
	.else
	.print "struct.asm macro begctr overflow"
	.err
	.endif
	.endm

Which would be OK except for the "and etc" part. The other counter does "nesting level" (I think. I guess part of the problem is I never figured out exactly how these work), so it decrements as well. Perhaps local labels could be used for one of the counters, and a more limited implementation for the other.

But like I said, it would be best if there was already a well-accepted version...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Aren't those labels global? Can you use them more than one time in one file? (Edit: I see, they are all going to be different, so there may be many of them).
By the way, I made a better solution for the optional else

.macro  _ifeq3
        breq    .+2             ;Skip next if Z set
        rjmp    99f             ;Jump to else or endif
.endm

.macro  _else3
        rjmp    98f             ;Jump to endif
99:     
.endm

.macro  _endif3
99:                             ;Label for if
98:                             ;Label for else
.endm

With nesting support (one level implemented). Labels are reused.

.macro  iflabel num
.if \num == 99
99:
.elseif \num == 98
98:
.elseif \num == 97
97:
.elseif \num == 96
96:
.else
        .error "Too deep nesting"
.endif
.endm


.macro rjmpfn num
.if \num == 99
        rjmp    99f
.elseif \num == 98
        rjmp    98f
.elseif \num == 97
        rjmp    97f
.elseif \num == 96
        rjmp    96f
.else
        .error "Too deep nesting"
.endif
.endm


iflevel = 101

.macro  _ifeq
        iflevel = iflevel - 2   ;Set next iflevel
        breq .+2                ;Skip next if Z set
        rjmpfn  iflevel         ;Jump to else or endif
.endm

.macro  _else
        rjmpfn  iflevel - 1     ;Jump to endif
        iflabel iflevel         ;Label for if
.endm

.macro  _endif
.if iflevel > 99
        .error "_endif without _ifXX (next two errors are wrong)"
.endif
        iflabel iflevel         ;Label for if
        iflabel iflevel - 1     ;Label for else
        iflevel = iflevel + 2   ;Go back one iflevel
.endm
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And instead of the test _ifeq one can use the more general

#define BRCAT(cond) br ## cond

;;; _if cond, cond can be XX in all brXX.
;;; That is: bc, bs, cc, cs, eq, ge, hc, hs, id, ie,
;;; lo, lt, mi, ne, pl, sh, tc, ts, vc or vs
.macro  _if cond
        iflevel = iflevel - 2   ;Set next iflevel
        BRCAT(\cond) .+2        ;Skip next if cond is true
        rjmpfn  iflevel         ;Jump to else or endif
.endm
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm pretty sure that last example combining cpp macros and asm macros won't work; if it did, things would be a lot easier. (cpp gets done once, before any ASM macro processing at all gets done.)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Actually it does work. But you're right, it expands to br\cond, so the cpp thing is not needed at all (brain fart, still learning gas .macro's). This is better

.macro  _if cond
        iflevel = iflevel - 2   ;Set next iflevel
        br\cond .+2             ;Skip next if cond is true
        rjmpfn  iflevel         ;Jump to else or endif
.endm
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

At least someone gets the idea!
Hmm. So gas will treat:

test: cpi r27, 13  /* if (r27 == 13) */
      brne 1f
        /* If clause */
        ldi r27, 10 ; blah blah
        rjmp 2f ; jump past else clause
1:    /* else clause */
      andi r27, ~('A' ^ 'a')
1: ;; would be end of if statement, if there was no else
2: ;; always end of else clause

correctly (the "if" jump jumping to the closest local lable of 1), and then I can reuse 1 and 2 in the next if statement as well?
That could be very useful; That would make the required range of the .if chain be based on only the nesting level, rather than total number of if statements. Just the sort of insight I was looking for! Thanks.

(The usual "handle this set of conditions" can be done with an additional set of macros):

.macro _br_not_e dest
   brne \dest
.end

.macro _if cond
  _br_not_\cond 1:
.endif
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:
So gas will treat:
...
correctly (the "if" jump jumping to the closest local lable of 1), and then I can reuse 1 and 2 in the next if statement as well?
Yes, those reusable numerical labels are very handy for short jumps (not very descriptive but you write in a comment what's going on anyway). From the manual
Quote:
Here is an example:

1: branch 1f
2: branch 1b
1: branch 2f
2: branch 1b

Which is the equivalent of:

label_1: branch label_3
label_2: branch label_1
label_3: branch label_4
label_4: branch label_3

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Things are coming along nicely.
Although, I also discovered that I've been looking at old gas documentation, and more recent versions have an "alternate macro mode" enabled by ".altmacro", which has some (most? All?) of the features I was complaining about not having. Sigh.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

which has some (most? All?) of the features I was complaining about not having

LOCAL by any chance? ;-) When you started this thread I had a look at the manual and some examples of using .altmacro but assumed (wrongly it turns out) that you would have also hit those already. My thought was that because gas is generic across many architectures and not just AVR that someone/somewhere must have already done the _if/_else/_endif thing even if the underlying opcodes were i386 or ARM or whatever. Unfortunately it's surprisingly difficult to Google for as things like if/else are very common words and don't help the search :-(

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

no. I don't think "local" is appropriate; it looks like it would be TOO local. "
EXPR" and "\()" look especially useful, along with the additional string delimiters. I suspect that I could get the original macro structure to work as originally implemented, though I'm finding the local label solution to be much nicer...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

(Hmm. "LOCAL" might turn out to be useful for "loop" structures, where you generally have a backward reference to a new label. It's less useful for IF/etc because you need a forward reference to the created unique symbol.)

The other side of the coin is that given the existence of .altmacro, it seems even more surprising that what I want to do hasn't already been done.
One of the few nice things to say about gas is that it provides a pretty consistent "infrastructure syntax" across a lot of different architectures...

The test code looks like this:

IFTEST:	cpi r16, 'I'
	_if e
	   call dummyfunc
	   mov r2, r24
	   sei
	_endif

IFELSETEST:	cpi r16, 'E'
	_if e
	   call dummyfunc
	   lpm
	   cli
	_else
	  ldi r17, 'X'
	_endif

NESTIFTEST:		
	cpi r27, 12
	_if e
	  ldi r16, 5
	  cpi r27, 13
	  _if ts
	    eor r16, r16
	  _else
	    add r16, r20
	  _endif
	  ldi r17, 123
	_endif
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

FWIW, I think I have this in a more-or-less publishable state. It does _if, _else, _elseif, _endif, _do, _while, _until, and _break

The use of local labels and .altmacro made this relatively easy, making me all the more surprised that it doesn't already exist.

https://github.com/WestfW/struct...

Comments welcome, of course.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Any new status?

I, too, am interested in structuring macros for use in AVR assembler coding. I used similar macros ("Concept-14") for over 15 years while coding mainframe assembler. At first, I couldn't see the point, but quickly grew to understand the benefits for maintenance and future development.

Having recently rediscovered MCUs, I have a lot to relearn across many aspects. I am not at a place yet where I can evaluate the OP's progress, but I know that this (structuring macros) is a piece that I will need soon.

And to those that feel that "C" is the answer to the OP's request, I add my thoughts:

"C" is a nasty language that never should have escaped from Bell Labs a half-century ago. It has the conflicting attributes of being powerful, readily available and requiring no discipline whatsoever to code in it.

It is the coding equivalent of giving knives and sticks to little boys. Assembler is more like C4: More powerful, but it takes some intelligence and knowledge to make it do anything.

Meh... opinions... everybody has one.

Cheers!
Pat

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

"C" is a nasty language that never should have escaped from Bell Labs a half-century ago. It has the conflicting attributes of being powerful, readily available and requiring no discipline whatsoever to code in it.
So C++ then? ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ding! Ding! Ding! Absolutely! Like putting lipstick on a pig AND trying to teach it to sing! :D

I know... "C doesn't make bad code... bad programmers make bad code". "C" just seems to be a bad-programmer magnet. (Though assembler gets its share of oxygen-wasters.)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I know this is 4.5 years late, but thanks to westfw, who posted a link to my method above, I have now updated it so it uses brute-force stringification to generate labels (instead of .ORGing backwards) and so can be adapted to any assembler that allows macros, including gas. See

http://dkeenan.com/AddingStructu...

Last Edited: Sat. Jan 13, 2018 - 02:21 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Neat...

 

I came up with the following recursive macro.

Do most assemblers support recursion of macros?  The initial implementation that I was familiar with used the ability of a macro to redefine other macros, which it turns out is pretty uncommon...

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:

Neat...

...

Do most assemblers support recursion of macros?  The initial implementation that I was familiar with used the ability of a macro to redefine other macros, which it turns out is pretty uncommon...

Thanks Bill. That's a really good question. Of course I'm only using the ability of a macro to invoke another macro (including itself), not define another macro. But regarding macro invocation recursion, I don't know enough assemblers to say. However I am emboldened in my renewed claim of "any assembler", by Garth Wilson's independent implementation, which I just learned about,

http://wilsonminesco.com/Structu...

for an assembler that doesn't even allow one macro to invoke another. He uses file inclusion to work around it.

 

Also, those brute-force-stringification macros could be written even more brute-forcedly without recursion, for say 999 labels, by using 999 string comparison macro IFs, although you'd probably want to write a program to write those macros. :-) And possibly it could be done using macro REPEATs if the assembler has them.

 

I'd be interested to know if you can do it for gas without using .altmacro.

Last Edited: Sat. Jan 13, 2018 - 04:23 AM