@arcana @sun alright, i did something:
i first asked: “armv4t does not have the blx instructin found in later architectures. build a gnu assembler compatible macro to emulate the behaviour as close as possible, ignore the “exchange” aspect if necessary.” the last “ignore the exchange aspect” is very important because it leads to a very easy solution.
it replied “The BLX instruction (Branch with Link and Exchange) in later ARM architectures performs a branch while storing the return address in LR (link register), with optional instruction set switching. Since ARMv4T lacks BLX, we can approximate its behavior using available instructions.
In ARMv4T, we can:
Use BL (Branch with Link) if the target address is within range.
Use BX (Branch and Exchange) for indirect jumps.
Manually save the return address in LR and jump to a computed address.”
alright, now what did it do with that info:
“Here’s a macro to emulate BLX as closely as possible:”
.macro BLX target
mov lr, pc @ Store return address in LR
bx \target @ Branch to target
.endm
and further:
“This works for indirect jumps where target is a register. If target is an immediate, BX won’t work directly. Instead, we use BL for relative jumps:”
.macro BLX target
adr lr, 1f @ Store return address in LR
bx \target @ Branch to target
1:
.endm
which is bullshit. why do you suddenly use “adr lr, 1f” instead of the correct “mov lr, pc” and using r3 here while being correct in the sense that it fullfills the branch, exchange and link aspects is really terrible. it’ll overload r3, which is commonly used for arguments in function calls or as an intermediate register to process data. so i gave it some more help:
“extend the macro so that it works with registers as well as labels/addresses. keep in mind that the target might be outside the range of a short-range jump.”
and it said:
“To properly handle both register-based and label/address-based targets while ensuring compatibility with ARMv4T, we need to account for the following cases:
Register Targets (bx reg): If the target is a register, we can use BX directly after storing the return address in LR.
Label or Immediate Targets (Far Jumps): If the target is a label or an absolute address, we may need an indirect jump to handle long-range jumps. This means loading the address into a register first before branching.
Here’s a GNU assembler macro that works in both cases:”
.macro BLX target
.ifc \target, r0 @ Check if target is a register (r0 to r15)
mov lr, pc @ Store return address in LR
bx \target @ Branch to register
.elseifc \target, r1
mov lr, pc
bx \target
.elseifc \target, r2
mov lr, pc
bx \target
.elseifc \target, r3
mov lr, pc
bx \target
.elseifc \target, r4
mov lr, pc
bx \target
.elseifc \target, r5
mov lr, pc
bx \target
.elseifc \target, r6
mov lr, pc
bx \target
.elseifc \target, r7
mov lr, pc
bx \target
.elseifc \target, r8
mov lr, pc
bx \target
.elseifc \target, r9
mov lr, pc
bx \target
.elseifc \target, r10
mov lr, pc
bx \target
.elseifc \target, r11
mov lr, pc
bx \target
.elseifc \target, r12
mov lr, pc
bx \target
.elseifc \target, r13
mov lr, pc
bx \target
.elseifc \target, r14
mov lr, pc
bx \target
.elseifc \target, r15
mov lr, pc
bx \target
.else @ Otherwise, assume a label or address
adr lr, 1f @ Store return address in LR
ldr r3, =\target @ Load target address into a register (use r3 as scratch)
bx r3 @ Branch to target
1:
.endif
.endm
this won’t assemble at all because .elseifc does not exist. rookie mistake ig, so i complained “.elseifc is not a valid pseudo-op” to which it came up with this:
.macro BLX target
.ifnc \target, r0 && \target, r1 && \target, r2 && \target, r3 && \
\target, r4 && \target, r5 && \target, r6 && \target, r7 && \
\target, r8 && \target, r9 && \target, r10 && \target, r11 && \
\target, r12 && \target, r13 && \target, r14 && \target, r15
@ Case for label or absolute address
adr lr, 1f @ Store return address in LR
ldr r3, =\target @ Load the target address into r3
bx r3 @ Branch to target
1:
.else
@ Case for register target
mov lr, pc @ Store return address in LR
bx \target @ Branch to register
.endif
.endm
which again is nonesense.
the easy solution is to implement a macro that implements
mov lr, pc
bx \target
for all cases where target is a valid register, and
mov lr, pc
ldr pc, =\target
for all the others which your assembler will translate to
mov lr, pc
ldr pc, [pc + ???]
using intermediates to load the full long distance address. from the tip of my tongue this would look like so (but won’t exchange for when target is not a register):
.macro blx target
.ifnc \target, r0
.ifnc \target, r1
.ifnc \target, r2
.ifnc \target, r3
.ifnc \target, r4
.ifnc \target, r5
.ifnc \target, r6
.ifnc \target, r7
.ifnc \target, r8
.ifnc \target, r9
.ifnc \target, r10
.ifnc \target, r11
.ifnc \target, r12
mov lr, pc
ldr pc, =\target
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.else
mov lr, pc
bx \target
.endif
.endm
