Getting Started With Asm?


r0-r12 are always usable, r13 and r14 can be used in special cases but I'm not sure when.

R0 - R3 are free, R4 - R12 & R14 can be used but you should store there contents somewhere before overwriting them. R13 is your stack if your using GCC. You can overwrite it if you want, but it's normally best to leave it alone and shove the other regs on it :)

I don't think that unrolling loops will make any difference, as contents on the code passed are still loaded into cache

That's true, but the ARM also has a prefetch - any branches will invalidate this prefetch and cause it to be refilled from the new location, so if you unroll sensibly, you can maximise use of the prefetch *and* the cache. I don't think it'll give much benefit in this case however simply because the code in the loop is so large. Always try it though - maybe get a nice surprise?
 
A_SN posted on Dec 18 2006 at 12:47 PM said:
Not sure to understand how to do that, but I'll have to look for explanations on Google anyways, meanwhile if you know some good documentation for that, let me know

If you look for a good optimization (that the compiler cannot do) : write several words in a single instruction (strm). This reduces the number of cycle the write buffer has to wait for memory (and, IIRC, help fit more data in the write buffer).

This is of course easier to write words N by N if you know that you have a multiple of N to write.

Oh yeah, I talked with Ukko (RapidRacoon ASM dev) and he told me that he was working on a fast memory function that would use ldmia to load 9 words in 9 registers and write them all at once with stmia. So, unlike what I've done so far which consisted in using all the registers I could (pretty much) to treat my two words in parallel (is there any gain to doing that by the way or is it the same as doing one after the other?) I guess I should try to use as few registers as I can for each word so I can have as many registers I can to load and store results in?

Put all your code in a file with the .S extention, and add it to your source files. GCC will know what to do with it. There are plenty
exemple availables out there.

As to using ldm/stm, yes this help is you save some registers from your computations. The more you save, the more you can use to
buffer the results before writing them all.

As an example, here is the 'memset' function used in gpu940 :

Code:
my_memset_words:  // r0 = dst, r1 = value, r2 = count
   cmp r2, #11
   bls 1f
   stmfd sp!, {r4-r12}
   mov r3, r1
   mov r4, r1
   mov r5, r1
   mov r6, r1
   mov r7, r1
   mov r8, r1
   mov r9, r1
   mov r10, r1
   mov r11, r1
   mov r12, r1
0:
   subs r2, r2, #11
   stmhsia r0!, {r1,r3-r12}
   bhi 0b
   add r2, r2, #11
   ldmfd sp!, {r4-r12}
1:
   subs r2, r2, #1
   strhs r1, [r0], #4
   bhi 1b
   mov pc, lr
 
Last edited by a moderator:
generating assembly output from C code: see gcc -s (or was it -S?)
to use separate asm files: write a .s file (you can take a gcc-generated file to start with a working sample) and name every exported function as a .globl, in C you name it extern, so: extern helloworld(int dummy); shall point to a function called "_helloworld:" declared ".globl _helloworld" first 3 (or was it 4?) arguments are passed thru r0~r3 and return is passed on r0, the funcion must preserve r4~r8 at least (not sure about r9+) and also I don't recall exactly if you need to start with a underscore when exporting something to C or not, so try both

other suggestion is to try to process 2 pixels at once, just like a SIMD with only 2 packed pixels inside (or perhaps a SIMD with 4 reds in a register, 4 greens in other and 4 blues in last, whatever fits best), if it's possible to do that I think you'll gain much more performance in 16bit that you haven't got yet, then you can try SIMD with multiple registers (for a later multiple store) which should give the best performance ever, altough maybe a large algorithm change is required

hammy_lite: sorry, I don't recall where I'd found that (I hate to say things just by myself, but it happens more then often), most of the things I've learned about arm assembly were on sites googled about arm assembly or any other keyword that I thought that time.... anyway I'm going today to buy a new CPU then I'll be able to boot up my desktop computer and see if eventually I've bookmarked that page that had many arm instruction's timings, but anyway, if you search in "arm assembly language programming" (I think it's found on gp2x's wiki, if not google for "03 - ch3.pdf") at chapter 3.7, you'll find that it says something different: all constant shifts are free, but shift by a register costs 1 cycle more

using r13 is very close to x86's (and other processors) push/pop stack codes, the difference itself is that arm are not required to have a stack, processor can be ran without it, but I think everybody uses r12 or r13 to implement a stack, that stack can be ascending/descending and full/empty, AFAIK linux uses a stack identical to x86, which is full descending

r14 is the link-register, the only special-purpose that that register have is when you use the instruction branch-with-link (bl) wich saves the contents of r15 into r14 and then jumps to the location specified, it's analog to a CALL in x86, but it doesn't need a stack, so 1 word less written/readed to/from memory, after the subroutine finishes, it just do a "b lr", if a extra register in a subroutine is needed to avoid many memory writes/reads you can push r14 when the called routine starts and use it as you wish, then, when leaving, pop r14 and branch to it to return

sorry for the enormous posting :lol:
 
Lint posted on Dec 18 2006 at 06:01 PM said:
extern helloworld(int dummy); shall point to a function called "_helloworld:" declared ".globl _helloworld" first 3 (or was it 4?) arguments are passed thru r0~r3 and return is passed on r0, the funcion must preserve r4~r8 at least (not sure about r9+) and also I don't recall exactly if you need to start with a underscore when exporting something to C or not, so try both

No need for the underscore.
 
Last edited by a moderator:
Lint posted on Dec 18 2006 at 06:01 PM said:
hammy_lite: sorry, I don't recall where I'd found that (I hate to say things just by myself, but it happens more then often), most of the things I've learned about arm assembly were on sites googled about arm assembly or any other keyword that I thought that time.... anyway I'm going today to buy a new CPU then I'll be able to boot up my desktop computer and see if eventually I've bookmarked that page that had many arm instruction's timings, but anyway, if you search in "arm assembly language programming" (I think it's found on gp2x's wiki, if not google for "03 - ch3.pdf") at chapter 3.7, you'll find that it says something different: all constant shifts are free, but shift by a register costs 1 cycle more

Well I found that register controlled shifts does take 1 more cycle, but I have not found that you can only shift max 3 in an imbedded one.

I'm starting to get pretty sure that you can make any imbedded shift for free. I was just hoping for some clarification :)
 
Last edited by a moderator:
yep, that was not the source of my 3-shifts-1-cycle penalty affirmation (as I've said before)

I did not bought that cpu today, so hold on if you're still want arm timings page/spreadsheet, that core 2 duo is ultrageously expensive... :(

anyway I don't think much people will avoid shifting something just because it takes 1 more cycle or not... :)
 
I've read a lot about how to call ASM functions in a .s file from C, and what I heard was hardly consistent, so i tried the following and it failed, please tell me where I got it wrong :

I got rid of my original C function in my C program and put
Code:
extern void fb_write(uint64_t *screen, uint32_t *fb);
instead. In main I called it using
Code:
fb_write((uint64_t *)screen32, (uint32_t *)fb);
just as I used to do before.

My .s file named fb_write.s contains exactly the following and nothing else :

Code:
fb_write:
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	stmfd	sp!, {r4, r5, r6, r7, r8, lr}				@store registers
	ldr	lr, .L245		@lr = 0x0004AFF8		original array start adress (local)
	ldr	ip, .L245+4		@ip = 38399			init of the counter
	add	lr, r0, lr		@lr += r0			calculation of the full adress
	mov	r0, r1			@r0 = r1			change of base adress
	mov	r4, #248
	mov	r6, #252
.L240:
	ldmia	lr, {r7-r8}		@r7 = LSW	r8 = MSW		example 0x008f8f8f and 0x001133ff (grey and red-orange)
	and	r2, r8, r4, asl #16	@r2 = r8 & (0xF8<<16)		r2 = MSW & 0x00F80000 = 0x00100000	//MSW red
	and	r3, r8, r6, asl #8	@r3 = r8 & (0xFC<<8)		r3 = MSW & 0x0000FC00 = 0x00003000	//MSW green
	mov	r1, r2, lsr #3		@r1 = r2 >> 3			r1 = 0x00100000 >> 3 = 0x00020000
	orr	r1, r1, r3, asl #11	@r1 = r1 | (r3 << 11)		r1 = 0x00020000 | 0x01800000 = 0x01820000
	and	r3, r8, r4		@r3 = r8 & 0xF8			r3 = MSW & 0x000000F8 = 0x000000F8	//MSW blue
	orr	r1, r1, r3, asl #24	@r1 = r1 | (r3 << 24)		r1 = 0x01820000 | 0xF8000000 = 0xF9820000
	and	r3, r7, r4, asl #16	@r3 = r7 & (0xF8<<16)		r3 = LSW & 0x00F80000 = 0x00880000	//LSW red
	orr	r1, r1, r3, lsr #19	@r1 = r1 | (r3 >> 19)		r1 = r1 | r3 = 0xF9820000 | 0x00000011 = 0xF9820011
	and	r3, r7, r6, asl #8	@r3 = r7 & (0xFC<<8)		r3 = LSW & 0x0000FC00 = 0x0000F800	//LSW green
	orr	r1, r1, r3, lsr #5	@r1 = r1 | (r3>>5)		r1 = r1 | r3 = 0xF9820011 | 0x000007C0 = 0xF98207D1
	and	r7, r7, r4		@r7 = r7 & 0x000000F8		r7 = LSW & 0x000000F8 = 0x00000088
	orr	r1, r1, r7, asl #8	@r1 = r1 | (r7 << 8)		r1 = r1 | (r7 << 8) = 0xF98207D1 | 0x00008800 = 0xF9828FD1
	str	r1, [r0, ip, asl #2]	@[r0 + (ip * 4)] = r1		write of r1 into the array
	subs	ip, ip, #1		@ip--				decrementation of ip
	sub	lr, lr, #8		@lr -= 8			decrementation of the original array pointer
	bne	.L240			@if (ip!=0) go back to .L240	loop condition
	ldmfd	sp!, {r4, r5, r6, r7, r8, pc}				@restore registers
.L246:
	.align	2
.L245:
	.word	307192
	.word	38399
	.size	fb_write, .-fb_write
	.section	.rodata.str1.4
	.align	2

And compiling using pretty much the same command (no makefile) except that I added fb_write.s after main.c, like this : gcc main.c fb_write.s -o main.gpe -I"etc..."

Here's what it says :
C:\DOCUME~1\PRINCI~1\LOCALS~1\Temp/ccu2caaa.o: In function `main':
main.c:(.text+0x33d0): undefined reference to `fb_write'

Don't know what to do from there. By the way I wish not to mess with .o files, if possible I'd like to go to the solution the closet to where I am.
 
add a ".global" directive to allow the linker to "find" your function name

Code:
.global fb_write

.... your code

And for 32 bit data you should use ".align 4" not ".align 2". ARM processor require 32 bit data to be 4 byte aligned (low order 2 bits of address off) and for 16 bit data 2 byte aligned (low order bit of address off).
 
Gary Miller posted on Dec 20 2006 at 01:43 AM said:
add a ".global" directive to allow the linker to "find" your function name

Code:
.global fb_write

.... your code

And for 32 bit data you should use ".align 4" not ".align 2". ARM processor require 32 bit data to be 4 byte aligned (low order 2 bits of address off) and for 16 bit data 2 byte aligned (low order bit of address off).

I did what you said (and also removed the : at the end of line 1) and I still get the same error when compiling.
 
Last edited by a moderator:
try:

Code:
.type fb_write, %function

.align shall be 2, I think, in arm .align refers to powers of two, or, in doubt, .align 4 and don't bother (maybe) eventually wasting 15 bytes for a word...
there's also a directive called .p2align which always refers to one single signification, so .p2align 2 shall work all the time

and for that I even got a webpage! http://www.gnu.org/software/binutils/manua...as_7.html#SEC70

also try compiling each one with -c (to produce .o's) and then link using arm-linux-ld manually, so then arm-linux-objdump -x can help
 
Like this :


Code:
	 .text	@ following output is meant to be in text segment (text is for code)
	 .align  @ if its not the case, align to word boundary
	 .global fb_write   @ equivalent to the C language extern

fb_write:
	stmfd	sp!, {r4, r5, r6, r7, r8, lr}				@store registers
	(... your code here ... please use consistent names for labels ...)
	ldmfd	sp!, {r4, r5, r6, r7, r8, pc}				@restore registers
mywords:
	 .word 307192
	 .word 38399
.end

And that's enought.
 
The "gas" assembler takes a number of directives that are needed to make ARM assembly generate correctly and link correctly. As I said the ".global fb_write" is just to generate a symbol in the object file so the linker can find it.

ARM code is always 4 byte aligned and Thumb code is always 2 byte aligned so using ".align 2/4" for code appears to be a waste of time. It is important that your data be aligned correctly. Shorts aligned 2 and int's/longs aligned 4 byte. So in general the align directive might need to be in your code just prior to your data if it mixes 16 bit and 32 bit but not otherwise. I have looked at the addresses generated when using both ARM and Thumb and it does the correct thing regardless of the align directive for code but data is another story. If the low order nibble of the address is not 0/4/8/C and you try to load it as a 32 bit your data will be corrupted by the processor by padding it some way. The same is true for 16 bit data (low order nibble 0/2/4/6/8/A/C/E) but it is only the low order bit that concerns us. There is a tremendous amount of miss-information out there concerning ARM coding in assembly (Even the ARM book has plenty of errors). I have been trying to put together a document that tells the true story so I can add ARM assembly to my current class but I find that when you run the code through the tool chains the way it works does not match the documentation. Of course most of the documented examples use the ARM assembler not the GNU one so converting the examples and seeing how they convert (I have found numerous examples that are wrong even for the ARM tools) is a long task.
 
rixed posted on Dec 20 2006 at 08:51 AM said:
Like this :


Code:
	 .text	@ following output is meant to be in text segment (text is for code)
	 .align  @ if its not the case, align to word boundary
	 .global fb_write   @ equivalent to the C language extern

fb_write:
	stmfd	sp!, {r4, r5, r6, r7, r8, lr}				@store registers
	(... your code here ... please use consistent names for labels ...)
	ldmfd	sp!, {r4, r5, r6, r7, r8, pc}				@restore registers
mywords:
	 .word 307192
	 .word 38399
.end

And that's enought.

Thanks a lot, that's all I needed to do for it to work. So I compiled it as I said with gcc main.c fb_write.s -o main.gpe and it worked great from the first time. Now fb_write takes only 31.78% of the time, which is a great improvement, but I'll still have to consider doing everything in 16-bit color instead of 32-bit.

I stated earlier that in this case I wouldn't know how to do about the alpha layer if I switched to 16-bit color, but I think that I'll make it simple and keep it in 32-bit format. Although it would cost more memory, it hardly would affect the performance and I could have the two first bytes for the RGB565 data, the next byte for the alpha layer and the next byte for nothing or maybe another layer of something that I'd use with my custom blitter, so at last I might not even have that much code to re write.

Anyways, although in the end I probably won't use this ASM function I did, it was a great exercise and I'll need to code other stuff in ASM anyways, like a fast array zeroing function for example.

By the way, what's the use of this :
Code:
.L246:
	.align	2
if .L246 isn't even called anywhere? Oh and couldn't there be conflicts between my .LXXX names in my .s file and those generated from the .c file by the compiler?
 
Last edited by a moderator:
Labels can be branched to or used as a reference point for data depending on how your code is laid out. The label has a context of the the function so there is no need to worry about conflicts unless you make it's context global with the ".global" directive.

The align forces the next "thing" to be on an address that is multiple of two. Since code is fixed length either 2 bytes (for Thumb code) or 4 bytes for ARM code this appropriate if you want the next data to be on some sort of alignment boundary.
 
have you ever thought about using a 4-4-4-4 RBGA format?
I know you'd gonna be limited to 4096 colors (which in some cases are more than enough, some cases not) then you'll have 2 pixels per word AND a quite easy 1-nibble-per-attribute packing... definitely you're going to exchange graphical candyness for speed

and .global is not the same as extern in C, .extern is the same as extern, .global is what makes the label public to other objects, so every source/object can have a ".L246" routine as long as it's not global, if you declare .L246 global in more then one object a link-time error shall appear..
 
Lint posted on Dec 21 2006 at 03:50 AM said:
have you ever thought about using a 4-4-4-4 RBGA format?
I know you'd gonna be limited to 4096 colors (which in some cases are more than enough, some cases not) then you'll have 2 pixels per word AND a quite easy 1-nibble-per-attribute packing... definitely you're going to exchange graphical candyness for speed

Nice idea but no, no way I'm gonna use less colors than the hardware allows me to. In an arcadish game it could do it but the game I'm working on aim towards all the photorealism I can get on a GP2X.

Edit : just thought about it, that's actually not a good idea at all since you'd have to convert from RGB444 to RGB565 before writing to the framebuffer.

Oh and by the way, it takes between 9 and 10 ms for each run of my fb_write function on the whole screen.
 
Last edited by a moderator:
Lint posted on Dec 21 2006 at 03:50 AM said:
and .global is not the same as extern in C, .extern is the same as extern, .global is what makes the label public to other objects, so every source/object can have a ".L246" routine as long as it's not global, if you declare .L246 global in more then one object a link-time error shall appear..

From GAS doc, extern is ignored, while .global mean :

.global makes the symbol visible to . If you define symbol in your partial program, its value is made available to other partial programs that are linked with it. Otherwise, symbol will take its attributes from a symbol of the same name from another partial program it is linked with.

So I maintain that it's the equivalent of the C extern.

:p
 
Last edited by a moderator:
no, gas don't ignore .extern, it just treats all unknown labels as an extern, so you don't have to declare extern at all with gas, but still it's the opposite thing of .global

your label/variable don't have a public name when linking if it's not a global, so you can have many modules with a "mainloop:" label for example, as long as they're not globals, that's totally different, extern is used for importing and global used for exporting, see?
 
Lint posted on Dec 22 2006 at 02:53 PM said:
no, gas don't ignore .extern

http://www.gnu.org/software/binutils/manua...s_87.html#SEC89 :

".extern is accepted in the source program--for compatibility with other assemblers--but it is ignored."

it just treats all unknown labels as an extern, so you don't have to declare extern at all with gas, but still it's the opposite thing of .global

Yes, all undefined symbols are considered as externals, ie defined elsewhere with external linkeage (like in C, when a symbol is declared (without static qualifier or qualified extern) but have no definition).

your label/variable don't have a public name when linking if it's not a global, so you can have many modules with a "mainloop:" label for example, as long as they're not globals

In C language : defined objects have no external linkage until they are declared extern (which is the default, BTW).

that's totally different, extern is used for importing and global used for exporting, see?

GAS global is used to export, and thus is equivalent to C extern (except for the fact that GAS global is not the default while C extern is), while GAS extern is not used for anything, since it's ignored.

B)
 
Last edited by a moderator:
rixed said:
http://www.gnu.org/software/binutils/manua...s_87.html#SEC89 :

".extern is accepted in the source program--for compatibility with other assemblers--but it is ignored."
nevermind you, you're definitely not interested in learn by any means....

perhaps you'll see that sometime later, as I thought like you a long time ago too

I would have to tell you that for the assembler all it's needed is an absolute address (where you can change, call or do whatever your code wants) while in C you still need a declaration, but heck, then you'll came with some man page that I've already readed like a thousand times just to people think you're right (reading it literally), so, blow it to hell :p
 
Last edited by a moderator:
Back
Top