gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

C/C++ > GAH! A really evil bug if you think about it...

#26863 - LOst? - Mon Sep 27, 2004 10:35 pm

Code:

   if (ObjectOffset != 0)
      DMA3(OAMMem, sprites, (ObjectOffset << 1), DMA_32NOW);

   ObjectOffset = 0;


This screws up my VBlank, and I found out why. It looks so correct until you realize DMA3 is a macro that looks like this:


Code:

#define   DMA3(dst, src, cnt, mode)      \
   REG_DMA3SAD = (u32) src;         \
   REG_DMA3DAD = (u32) dst;         \
   REG_DMA3CNT = (cnt | mode);         \


So I fixed it by putting { } around the macro call:
Code:

   if (ObjectOffset != 0)
   {
      DMA3(OAMMem, sprites, (ObjectOffset << 1), DMA_32NOW);
   }


I have never used macros before so I have never come across this problem before.

#26868 - DekuTree64 - Mon Sep 27, 2004 11:33 pm

Yup, that's a sneaky one. To prevent future errors of that sort, I'd suggest putting the {}'s into the macro itself. They're perfectly legal without an if or anything before them.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#26869 - tepples - Mon Sep 27, 2004 11:40 pm

I think you need to convert DMA3() into an inline function to prevent this from happening in the future. Toss this in your header to replace the macro:
Code:
static inline void DMA3(void *dst, const void *src, u32 cnt, u32 mode)
{
  REG_DMA3SAD = (u32) src;
  REG_DMA3DAD = (u32) dst;
  REG_DMA3CNT = (cnt | mode);
}

_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#26870 - LOst? - Tue Sep 28, 2004 12:53 am

tepples wrote:
I think you need to convert DMA3() into an inline function to prevent this from happening in the future. Toss this in your header to replace the macro:
Code:
static inline void DMA3(void *dst, const void *src, u32 cnt, u32 mode)
{
  REG_DMA3SAD = (u32) src;
  REG_DMA3DAD = (u32) dst;
  REG_DMA3CNT = (cnt | mode);
}


Great! Thanks!

#26877 - SmileyDude - Tue Sep 28, 2004 1:46 am

Deku's suggestion is also valid -- especially when you are coding in C. inline functions technically aren't a part of C until you get to C99.
_________________
dennis

#26879 - sajiimori - Tue Sep 28, 2004 2:53 am

...which GCC supports quite well.

#26880 - poslundc - Tue Sep 28, 2004 2:56 am

GCC supports inline functions without using the C99 extensions. They are probably a GNU-specific extension to the ANSI standard or something, but they are there.

Dan.

#26887 - getch - Tue Sep 28, 2004 11:18 am

I dont know exactly what the problem with the macro is?
Could it be fixed by removing the last backslash?

#26888 - DialogPimp - Tue Sep 28, 2004 11:26 am

It's probably irrelevant to the thread, but it's precisely because of things like this that our coding standard at work requires us to block any "if" statement regardless of whether it's one line or not.

#26892 - SmileyDude - Tue Sep 28, 2004 12:20 pm

well, in all honesty, gcc lets you do a lot of things that may or may not be allowed in the standard. But, the nice thing about coding to the standard is that it will work even if the compiler doesn't support things like inline functions in C code.

Personally, if the code is for yourself, use whatever method you are comfortable with. But, if you don't have control over where the code is going to be compiled/used, it's best to stick to the standard.

:)
_________________
dennis

#26893 - SmileyDude - Tue Sep 28, 2004 12:24 pm

getch wrote:
I dont know exactly what the problem with the macro is?
Could it be fixed by removing the last backslash?


The problem with the macro is that it expands out to 3 statements. As long as you are not in something like an if/while/do, it's okay. It's also okay if you block out the if/while/do in {}s. But, in the case where you don't block out the {}s, the if will only do the first statement inside the if, and the other 2 outside the if.

And, this is one of the bigger (biggest?) drawbacks to the C preprocessor. It's pretty dumb about what it does. Since the preprocessor simply replaces the macro with whatever was defined, it has no idea that since the code was in an if, that it needs to block it out.

But, the simplest solution is to put the {}s in the macro definition -- that way, it always works.
_________________
dennis

#26896 - poslundc - Tue Sep 28, 2004 2:00 pm

SmileyDude wrote:
And, this is one of the bigger (biggest?) drawbacks to the C preprocessor. It's pretty dumb about what it does.


I blame the programmer. :P

Dan.

#26903 - sajiimori - Tue Sep 28, 2004 6:24 pm

It still wouldn't work as an expression, and expressions should be the preferred form of macro expansion. I recommend using an inline function or a parenthesized comma-seperated expression.

Depending on how you look at it, there are no drawbacks to the C preprocessor. It does exactly what it does, and what it does is plain text substitution. It's only a disappointment if you are expecting something more. If you want real macros, use Lisp.

#26904 - jma - Tue Sep 28, 2004 6:48 pm

sajiimori wrote:
..., use Lisp.

/cheer sajiimori

Jeff
_________________
massung@gmail.com
http://www.retrobyte.org

#26905 - poslundc - Tue Sep 28, 2004 7:30 pm

sajiimori wrote:
Depending on how you look at it, there are no drawbacks to the C preprocessor. It does exactly what it does, and what it does is plain text substitution. It's only a disappointment if you are expecting something more.


Yes, yes, thank you. This is precisely what I think whenever I hear programmers upset at this kind of behaviour. Multiple inclusion, linker errors due to multiple symbol generation, problematic macro expansion... everyone is so quick to blame the frailty of the language for these problems when the only real problem is ignorance of what the directives they're using actually do.

Dan.

#26906 - tepples - Tue Sep 28, 2004 7:52 pm

Which free Lisp compiler emits decent Thumb code?

None?

That's why most of us use C instead.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#26907 - sajiimori - Tue Sep 28, 2004 8:05 pm

That's the thing about a Lisp recommendation. It's often packaged with an implicit lament that the recommendation probably shouldn't be taken. =)

#26909 - jma - Tue Sep 28, 2004 9:58 pm

tepples wrote:
Which free Lisp compiler emits decent Thumb code?

None?

That's why most of us use C instead.

Hardly. Here, with my compliments. If you use this, please, give me credit where due:

Code:
;;; Thumb assember for GBA
;;;
;;; Created 9/13/04 by Jeff Massung
;;;
;;; THUMB.LISP
;;;

;(load "gba.lisp")

(defparameter *mnemonics* (make-hash-table))

;; Assembles a body of code
(defun assemble-form (&rest body)
  (let ((locals (collect-labels body))
        (here 0)
        (asm (make-array 0
                         :element-type 'unsigned-byte
                         :adjustable t
                         :fill-pointer t)))
    (loop for form in body do
          (unless (keywordp form)
            (let ((operands (expand-labels (cdr form) locals here)))
              (incf here (assemble-instruction (car form) operands asm)))))
    asm))

;; Collect all the labels in a form
(defun collect-labels (body)
  (do ((i body (cdr i)) (here 0) (locals (make-hash-table)))
      ((null i) locals)
    (let ((form (car i)))
      (if (keywordp form)
          (setf (gethash form locals) here)
        (let ((mnemonic (gethash (car form) *mnemonics*)))
          (if (null mnemonic)
              (error "Unknown instruction ~A" (car form))
            (incf here (mnemonic-size mnemonic))))))))

;; Replace all labels in an instruction call with their address
(defun expand-labels (operands labels here)
  (loop for form in operands collect
        (if (keywordp form)
            (let ((label (gethash form labels)))
              (if (null label)
                  (error "Undefined label ~A" form)
                (- label (+ here 4))))
          form)))

;; Assemble an instruction into a form and return number of bytes
(defun assemble-instruction (symbol operands form)
  (let* ((mnemonic (gethash symbol *mnemonics*))
         (bytes (apply (mnemonic-builder mnemonic) operands))
         (size (mnemonic-size mnemonic)))
    (loop for i from 0 to (1- size) do
          (vector-push-extend (logand (ash bytes (- (* i 8))) #xff) form))
    size))

;; Simple mnemonic function
(defstruct mnemonic builder size)

;; Define a new mnemonic function
(defmacro defmnemonic (symbol args size &body body)
  `(setf (gethash ',symbol *mnemonics*)
         (make-mnemonic :builder (lambda ,args ,@body)
                        :size ,size)))

;; Get the index of a lo register (r0-r7)
(defun lo-reg (r)
  (do ((lo '(r0 r1 r2 r3 r4 r5 r6 r7) (cdr lo))
       (reg-index 0 (1+ reg-index)))
      ((eq r (car lo)) reg-index)
    (unless lo
      (error "Not a lo register ~A" r))))

;; Get the index of a hi register (r8-r15)
(defun hi-reg (r)
  (do ((hi '(r8 r9 r10 r11 r12 r13 r14 r15) (cdr hi))
       (reg-index 8 (1+ reg-index)))
      ((eq r (car hi)) reg-index)
    (unless hi
      (error "Not a lo register ~A" r))))

;; Any register (with hi/lo return value)
(defun any-reg (r)
  (do ((any '(r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15) (cdr any))
       (reg-index 0 (1+ reg-index)))
      ((eq r (car any)) (if (>= reg-index 8)
                            (values (- reg-index 8) t)
                          (values reg-index nil)))
    (unless any
      (error "Not a register ~A" r))))

;; Create a register list bitfield
(defun make-reglist (list &key allow-pc allow-lr)
  (do ((rd list (cdr rd)) (acc 0))
      ((null rd) acc)
    (if (or (and allow-pc (eq (car rd) 'pc))
            (and allow-lr (eq (car rd) 'lr)))
        (incf acc #x100)
      (incf acc (ash 1 (lo-reg (car rd)))))))

;; Hi/lo register arguments
(defun hi-lo-regs (opcode rm rn &optional allow-both-lo)
  (multiple-value-bind (rm h1) (any-reg rm)
    (multiple-value-bind (rn h2) (any-reg rn)
      (cond
       ((and (null h1) h2) (logior opcode #x80 (ash rn 3) rm))
       ((and h1 (null h2)) (logior opcode #x40 (ash rn 3) rm))
       ((and h1 h2)        (logior opcode #xc0 (ash rn 3) rm))
       (allow-both-lo      (logior allow-both-lo (ash rn 3) rm))
       (t                  (error "Invalid operands"))))))

;; Make sure the mnemonics is clean before defining assembly instructions
(clrhash *mnemonics*)

;; Simple ALU operations
(defun simple-alu (rd rm opcode)
  (let ((rd (lo-reg rd)) (rm (lo-reg rm)))
    (logior opcode (ash rm 3) rd)))

(defmnemonic adc (rd rm) 2 (simple-alu rd rm #x4140))
(defmnemonic and (rd rm) 2 (simple-alu rd rm #x4000))
(defmnemonic bic (rd rm) 2 (simple-alu rd rm #x4380))
(defmnemonic cmn (rd rm) 2 (simple-alu rd rm #x42c0))
(defmnemonic eor (rd rm) 2 (simple-alu rd rm #x4040))
(defmnemonic mul (rd rm) 2 (simple-alu rd rm #x4340))
(defmnemonic mvn (rd rm) 2 (simple-alu rd rm #x43c0))
(defmnemonic neg (rd rm) 2 (simple-alu rd rm #x4240))
(defmnemonic orr (rd rm) 2 (simple-alu rd rm #x4300))
(defmnemonic ror (rd rm) 2 (simple-alu rd rm #x41c0))
(defmnemonic sbc (rd rm) 2 (simple-alu rd rm #x4180))
(defmnemonic tst (rd rm) 2 (simple-alu rd rm #x4200))

;; Unconditional branch
(defmnemonic b (offset) 2
  (if (not (zerop (logand offset 1)))
      (error "Invalid offset ~A" offset)
    (logior #xe000 (ash offset -1))))

;; Conditional branches
(defmacro cond-branch (offset cc)
  `(if (not (zerop (logand ,offset 1)))
       (error "Invalid offset ~A" ,offset)
     (logior #xd000 ,cc (ash ,offset -1))))

(defmnemonic beq (offset) 2 (cond-branch offset #x000))
(defmnemonic bne (offset) 2 (cond-branch offset #x100))
(defmnemonic bcs (offset) 2 (cond-branch offset #x200))
(defmnemonic bcc (offset) 2 (cond-branch offset #x300))
(defmnemonic bhs (offset) 2 (cond-branch offset #x200))
(defmnemonic blo (offset) 2 (cond-branch offset #x300))
(defmnemonic bmi (offset) 2 (cond-branch offset #x400))
(defmnemonic bpl (offset) 2 (cond-branch offset #x500))
(defmnemonic bvs (offset) 2 (cond-branch offset #x600))
(defmnemonic bvc (offset) 2 (cond-branch offset #x700))
(defmnemonic bhi (offset) 2 (cond-branch offset #x800))
(defmnemonic bls (offset) 2 (cond-branch offset #x900))
(defmnemonic bge (offset) 2 (cond-branch offset #xa00))
(defmnemonic blt (offset) 2 (cond-branch offset #xb00))
(defmnemonic bgt (offset) 2 (cond-branch offset #xc00))
(defmnemonic ble (offset) 2 (cond-branch offset #xd00))

;; Branch with link
(defmnemonic bl (offset) 4
  (if (logand offset 1)
      (error "Invalid branch with link offset ~A" offset)
    (let ((offset (ash offset -1)))
      (logior #xf800f000
              (ash (logand offset #x3ff) 16)
              (logand (ash offset -10) #x3ff)))))

;; Branch and exchange
(defmnemonic bx (rm) 2
  (multiple-value-bind (rm hi) (any-reg rm)
    (logior #x4700 (ash rm 3) (if hi #x40 #x00))))

;; Load multiple registers
(defmnemonic ldmia (rm &rest list) 2
  (let ((rm (lo-reg rm)))
    (logior #xc800 (ash rm 8) (make-reglist list))))

;; Store multiple registers
(defmnemonic stmia (rm &rest list) 2
  (let ((rm (lo-reg rm)))
    (logior #xc000 (ash rm 8) (make-reglist list))))

;; Push registers onto the stack
(defmnemonic push (&rest list) 2
  (logior #xb400 (make-reglist list :allow-lr t)))

;; Pop registers off the stack
(defmnemonic pop (&rest list) 2
  (logior #xbc00 (make-reglist list :allow-pc t)))

;; Arithmetic shift right
(defmnemonic asr (rd rm &optional n) 2
  (let ((operands (logior (ash (lo-reg rm) 3) (lo-reg rd))))
    (if (null n)
        (logior #x4100 operands)
      (logior #x1000 (ash n 6) operands))))

;; Logical shift left
(defmnemonic lsl (rd rm &optional n) 2
  (let ((operands (logior (ash (lo-reg rm) 3) (lo-reg rd))))
    (if (null n)
        (logior #x4080 operands)
      (logior #x0000 (ash n 6) operands))))

;; Logical shift right
(defmnemonic lsr (rd rm &optional n) 2

  (let ((operands (logior (ash (lo-reg rm) 3) (lo-reg rd))))
    (if (null n)
        (logior #x40c0 operands)
      (logior #x0800 (ash n 6) operands))))

;; Compare register
(defmnemonic cmp (rm n) 2
  (if (integerp n)
      (logior #x2800 (ash (lo-reg rm) 8) n)
    (hi-lo-regs #x4500 rm n #x4280)))

;; Move register
(defmnemonic mov (rm n) 2
  (if (integerp n)
      (logior #x2000 (ash (lo-reg rm) 8) n)
    (hi-lo-regs #x4600 rm n)))

;; Add
(defmnemonic add (rd rm &optional n) 2
  (if (null n)
      (if (integerp rm)
          (logior #x3000 (ash (lo-reg rd) 8) rm)
        (if (eq rd 'sp)
            (logior #xb000 (ash rm -2))
          (hi-lo-regs #x4400 rd rm)))
    (if (integerp n)
        (case rm
          (pc        (logior #xa800 (ash (lo-reg rd) 8) (ash n -2)))
          (sp        (logior #xa000 (ash (lo-reg rd) 8) (ash n -2)))
          (otherwise (logior #x1c00 (ash (lo-reg rm) 3) (ash n 6) (lo-reg rd))))
      (logior #x1800 (ash (lo-reg n) 6) (ash (lo-reg rm) 3) (lo-reg rd)))))

;; Subtract
(defmnemonic sub (rd rm &optional n) 2
  (if (null n)
      (if (eq rd 'sp)
          (logior #xb080 (ash rm -2))
        (logior #x3800 (ash (lo-reg rd) 8) rm))
    (if (integerp n)
        (logior #x1e00 (ash n 6) (ash (lo-reg rm) 3) (lo-reg rd))
      (logior #x1a00 (ash (lo-reg n) 6) (ash (lo-reg rm) 3) (lo-reg rd)))))

;; Load register with word
(defmnemonic ldr (rd rm &optional n) 2
  (let ((rd (lo-reg rd)))
    (if (or (integerp n) (null n))
        (let ((n (if (null n) 0 (ash n -2))))
          (case rm
            (pc        (logior #x4800 (ash rd 8) n))
            (sp        (logior #x9800 (ash rd 8) n))
            (otherwise (logior #x6800 (ash n 6) (ash (lo-reg rm) 3) rd))))
      (logior #x5800 (ash (lo-reg n) 6) (ash (lo-reg rm) 3) rd))))

;; Load register with halfword
(defmnemonic ldrh (rd rm &optional n) 2
  (let ((rd (lo-reg rd)) (rm (lo-reg rm)))
    (if (or (integerp n) (null n))
        (let ((n (if (null n) 0 (ash n -1))))
          (logior #x8800 (ash n 6) (ash rm 3) rd))
      (logior #x5a00 (ash (lo-reg n) 6) (ash rm 3) rd))))

;; Load register with byte
(defmnemonic ldrb (rd rm &optional n) 2
  (let ((rd (lo-reg rd)) (rm (lo-reg rm)))
    (if (or (integerp n) (null n))
        (let ((n (if (null n) 0 n)))
          (logior #x7800 (ash n 6) (ash rm 3) rd))
      (logior #x5c00 (ash (lo-reg n) 6) (ash rm 3) rd))))

;; Store register
(defmnemonic str (rd rm &optional n) 2
  (let ((rd (lo-reg rd)))
    (if (or (integerp n) (null n))
        (let ((n (if (null n) 0 (ash n -2))))
          (case rm
            (sp        (logior #x9000 (ash rd 8) n))
            (otherwise (logior #x6000 (ash n 6) (ash (lo-reg rm) 3) rd))))
      (logior #x5000 (ash (lo-reg n) 6) (ash (lo-reg rm) 3) rd))))
 
;; Store halfword register
(defmnemonic strh (rd rm &optional n) 2
  (let ((rd (lo-reg rd)) (rm (lo-reg rm)))
    (if (or (integerp n) (null n))
        (let ((n (if (null n) 0 (ash n -1))))
          (logior #x8000 (ash n 6) (ash rm 3) rd))
      (logior #x5200 (ash (lo-reg n) 6) (ash rm 3) rd))))

;; Load byte register
(defmnemonic strb (rd rm &optional n) 2
  (let ((rd (lo-reg rd)) (rm (lo-reg rm)))
    (if (or (integerp n) (null n))
        (let ((n (if (null n) 0 n)))
          (logior #x7000 (ash n 6) (ash rm 3) rd))
      (logior #x5400 (ash (lo-reg n) 6) (ash rm 3) rd))))

;; Software interrupt
(defmnemonic swi (n) 2
  (if (or (>= n #x100) (not (plusp n)))
      (error "Invalid software interrupt ~A" n)
    (logior #xdf n)))


And for usage (this is snipped from the source code to the Dragon BASIC compiler):

Code:
;;; Dragon BASIC API for the GBA
;;;
;;; Created 9/16/04 by Jeff Massung
;;;
;;; DBAPI.LISP
;;;

(load "thumb.lisp")

(defparameter *api* (make-hash-table))

;; Structure that holds the adress and arguments to an API function
(defstruct api address arglist bytes)

;; Define a new assembler api function
(defmacro defapi (symbol args &body body)
  (setf (gethash symbol *api*)
        (make-api :bytes (apply #'assemble-form body)
                  :arglist args)))

;; Compile all the API functions into a stream
(defun compile-api ()
  (maphash #'(lambda (key value)
               (declare (ignore key))
               (setf (api-address value) (file-position *bank*))
               (compile-asm (api-bytes value)))
           *api*))

;; Clear the API function table
(clrhash *api*)

;; Set the current graphics mode (0-5)
(defapi graphics (mode :integer)
  (mov r2 4)
  (cmp r0 3)
  (blt :t1)
  (lsl r1 r2 8)
  (add r0 r0 r1)
  :t1
  (lsl r1 r2 24)
  (strh r0 r1)
  (pop r0)
  (bx r14))

;; Wait for the next vertical blank
(defapi vblank ()
  (mov r2 4)
  (lsl r2 r2 24)
  :t1
  (ldrh r1 r2 6)
  (cmp r1 160)
  (bne :t1)
  (bx r14))

;; Get the current keypad state (1=pressed)
(defapi keys ()
  (mov r2 4)
  (lsl r2 r2 24)
  (push r0)
  (add r2 130)
  (ldrh r0 r2)
  (mvn r0 r0)
  (bx r14))

;; Check to see if a keypad button is pressed (1=pressed)
(defapi key (mask :integer)
  (mov r2 4)
  (lsl r2 r2 24)
  (add r2 130)
  (ldrh r1 r2)
  (mvn r1 r1)
  (and r0 r1)
  (bx r14))

;; Returns true if a button was just hit
(defapi keyhit (mask :integer prev-state :integer)
  (mov r2 4)
  (lsl r2 r2 24)
  (add r2 130)
  (ldrh r1 r2)
  (mvn r1 r1)
  (and r1 r0)
  (pop r2)
  (and r2 r0)
  (cmp r1 r2)
  (sbc r0 r0)
  (mvn r0 r0)
  (bx r14))


As you see.. Dragon BASIC is written in a combination of Lisp and Forth. So, next time, don't drag down the language -- meaning Lisp or Forth -- please ;)

Jeff
_________________
massung@gmail.com
http://www.retrobyte.org


Last edited by jma on Tue Sep 28, 2004 10:13 pm; edited 1 time in total

#26910 - SmileyDude - Tue Sep 28, 2004 10:04 pm

poslundc wrote:
Yes, yes, thank you. This is precisely what I think whenever I hear programmers upset at this kind of behaviour. Multiple inclusion, linker errors due to multiple symbol generation, problematic macro expansion... everyone is so quick to blame the frailty of the language for these problems when the only real problem is ignorance of what the directives they're using actually do.


Ahh, so I bet that you sneer at people who don't code in assembly language because they consider it too complex. When in reality, it's just their ignorance of what the instructions actually do.

Look, any good C programmer knows how to use the C preprocessor correctly. Just like a good assembly programmer knows how to use all of the instructions that are available, and a good Lisp programmer knows how to do that Lisp stuff (*GRIN*).

But, the C preprocessor is fragile. Even with quite a bit of C/C++ experience behind me, I still end up making the occasional screw-up that ends up going back to a #define someplace where I didn't surround one of the parameters in ()s or something similar to that.

Just because it does what it was originally intended to do, doesn't mean that it does what I need/want it to do.
_________________
dennis

#26912 - poslundc - Tue Sep 28, 2004 10:19 pm

SmileyDude wrote:
poslundc wrote:
Yes, yes, thank you. This is precisely what I think whenever I hear programmers upset at this kind of behaviour. Multiple inclusion, linker errors due to multiple symbol generation, problematic macro expansion... everyone is so quick to blame the frailty of the language for these problems when the only real problem is ignorance of what the directives they're using actually do.


Ahh, so I bet that you sneer at people who don't code in assembly language because they consider it too complex. When in reality, it's just their ignorance of what the instructions actually do.


No, I sneer at people who code in assembly language and use the add instruction to load data into a register. If you're going to attack my character at least get your analogies straight.

Quote:
Look, any good C programmer knows how to use the C preprocessor correctly. Just like a good assembly programmer knows how to use all of the instructions that are available, and a good Lisp programmer knows how to do that Lisp stuff (*GRIN*).

But, the C preprocessor is fragile. Even with quite a bit of C/C++ experience behind me, I still end up making the occasional screw-up that ends up going back to a #define someplace where I didn't surround one of the parameters in ()s or something similar to that.


And sometimes I screw up when performing a search and replace (which is what the preprocessor basically does with macros). I don't blame the search and replace function for it when I do, though.

Quote:
Just because it does what it was originally intended to do, doesn't mean that it does what I need/want it to do.


Then why are you using it for that purpose? I don't complain that the search/replace function in my text editor doesn't correct my spelling errors.

Dan.

#26915 - sajiimori - Tue Sep 28, 2004 10:35 pm

tepples meant a program that compiles Lisp to Thumb. I don't think he was dragging down the language, either. In fact, it says something positive about Lisp if people would use it on GBA given a good, free implementation.

Edit: Can we sum up the preprocessor debate by saying that we sometimes wish we didn't have to rely on a simple text replacement tool to do things that could use a bit more power? While you could simply avoid all problems that are hindered by the simplicity of the preprocessor, most of us try to twist to fit our problems from time to time.

Another alternative is to use a more powerful language from the start. At that point one might complain: "I wish C were more powerful so I wouldn't have to use something else, because C is fast and it has good, free implementations, and a wide user base."

#26922 - sgeos - Wed Sep 29, 2004 6:51 am

IMHO, enclosing a macro in do{}while(0) the "correct" way of handling multi line macros.

Code:
#define   DMA3(dst, src, cnt, mode)   \
   do {            \
      REG_DMA3SAD = (u32) src;   \
      REG_DMA3DAD = (u32) dst;   \
      REG_DMA3CNT = (cnt | mode);   \
   while (0)

When this is expanded, the trailing semicolon will finish off the while(0). (Redundant semicolons cause warnings on some compilers.) The whole block will be treated as a single statement.

Commas are less good way of setting up multiline macros:
Code:
#define   DMA3(dst, src, cnt, mode)   \
   REG_DMA3SAD = (u32) src,   \
   REG_DMA3DAD = (u32) dst,   \
   REG_DMA3CNT = (cnt | mode)

Note the lack of semicolons. The user supplies the last one. (The only one in this case.)

NOTE: The last line of your macro does not need or want a backslash followed by a newline. It is harmless if a blank line follows your macro. It is not good if the following line not blank.

-Brendan

#26926 - Cearn - Wed Sep 29, 2004 10:28 am

sgeos wrote:
IMHO, enclosing a macro in do{}while(0) the "correct" way of handling multi line macros.

Code:
#define   DMA3(dst, src, cnt, mode)   \
   do {            \
      REG_DMA3SAD = (u32) src;   \
      REG_DMA3DAD = (u32) dst;   \
      REG_DMA3CNT = (cnt | mode);   \
   while (0)


Except, of course, in this case you'd never get it compiled because you missed the closing curly bracket :P. And before anyone complains about me fussing about a minor typo, remember that because macros will only be 'compiled' when used, this kind of mistake can lay dormant for a good while. You're lucky to get an error message if you finally do use this macro, unlike the macro that started this thread which messes up at runtime.
Another problem might be extra spaces behind the backslashes. GCC gives a warning about this so you know there's something wrong, but I think I've seen such a thing seriously mess up the macro in the past.
Macros should probably come with "CAVEAT EMPTOR" stamped on them in big angry red letters.

#26929 - jma - Wed Sep 29, 2004 4:12 pm

sgeos wrote:
IMHO, enclosing a macro in do{}while(0) the "correct" way of handling multi line macros.

Yes, but for a different reason that trailing semi-colons. Most compilers won't complain about: ";;;;" in code somewhere -- they are just empty statements -- like multiple newlines. But consider the following code with and without the do { } while(0) in the macro:

Code:
if (x > y) DMA3(oam_copy, OAM, 0x100, 32);
else do_something_else();

Jeff
_________________
massung@gmail.com
http://www.retrobyte.org

#26941 - tepples - Wed Sep 29, 2004 9:55 pm

The (statement, statement, statement, value) trick given by sgeos works for simple statements in ANSI C, but 1. make sure you put parentheses around the whole thing, or it will take up multiple arguments to a function, and 2. don't try it with switch or while inside the parentheses.

Because almost all GBA programmers are using GCC, I'd suggest using GCC's extensions to the C language and documenting that your code uses such extensions. Favorite extensions include
  • static inline
  • inner functions
  • compound statement expressions
    ({ statement; statement; statement; value; })
    which is similar to what sgeos proposed but allows local variables and if, while, do, and switch blocks


And yes, jma, by "Lisp compiler" I did mean a free compiler that takes Lisp source code as input and outputs either Thumb assembly code or Thumb object code. Such a compiler would attract a lot of Lisp coders to ARM platforms. In fact, I'd like to see somebody get GCL working on the GBA if it's possible.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.