Question about function call in a microprocessor?

Hello all the experts here, I would like to know how a typical porcessor executes a function call or subroutine call in a assembly code.

I know it uses stack for doing it and I do understand the mechanism about the stack here. I would want to go one step further and ask you how it exaclty saves the internal variable, returned variable and call in functions and all those things. If you see I am asking something like a compiler design, where in it maps the high-level language into architecture specific assembly code. I would really appreciate if you could do with an example.

I propose something like this,

main {

int k = foo (int a, int b, int c); foo uses lets says 5 local varaibles.

where in foo calls another function int l = foo1 (int d, int e); foo1 uses 2 local variables.

Please shed some thought on this.

Thanks in advance Hariharan K Srinivasan.

Reply to
harisrini
Loading thread data ...

snipped-for-privacy@gmail.com wrote in news:1108112842.485983.209510 @o13g2000cwo.googlegroups.com:

Well, to give Microchip's PIC micro-controller as an example. It only has one working register (W). I have not used any other chips, but I know Intel chips in your home PC have many more.

I'd use the W register for the return value. But Id need to tell my assembler in advance that I wanted to use a,b and c. Because I have not OS that can dynamically allocate memory for me.

So for int k = foo (int a, int b, int c);

I would globally define a, b, c Set some values in a,b,c Call the foo function

Foo: Movlw a,w Addwf b,w Addwf c,w Return

And end up with the result in the W register.

The stack is used by the processor to remember program jumps, because the location of Foo in program memory is way off in some distant place. But I never worry about this.

The only time the Stack is of concern for me is in implementing recursive functions, because the stack is only so deep and could overflow.

It's good that you know C but you will quickly answer any questions you have about the low level stuff, if you looked at a datasheet of any micro-controller or processor.

Here is a simple one

formatting link

DaveC

----== Posted via Newsfeeds.Com - Unlimited-Uncensored-Secure Usenet News==----

formatting link
The #1 Newsgroup Service in the World! 120,000+ Newsgroups

----= East and West-Coast Server Farms - Total Privacy via Encryption =----

Reply to
DaveC

This is not necessarily true. Using the stack is theoretically what you'd like to do because it makes the function calls re-entrant. However the crippled architecture of small micros (and possibly issues with caching with larger ones) make it sometimes necessary to just use part of memory of some kind for the required storage.

Yes, it's pretty much that.

Okay, it's a little more complex than that because there will be registers that have to be saved in addition to the automatic variables in foo/foo1. At least the accumulator(s), but maybe some floating point registers etc. The compiler will hopefully figure out the minimum that is required, rather than just saving everything every time (safer from compiler bugs, but potentially very inefficient).

One way is just to push the accumulator(s), index registers etc. on the stack, and assign automatic variables within a stack frame for the function. There might be more than one stack, BTW. Some small processors have a hardware stack for function calls. That creates re-entrant code to the extent you don't run out of stack space.

But in brain-dead small micros with Harvard architecture, often non-re-entrant functions are used- some general purpose (on-chip or off) RAM is allocated for the storage of automatic variables etc. . This is not as elegant.

A von Neuman 8-bit micro such the HC908 might have a compiler that would do something like (generated by Metrowerks CodeWarrior C/C++ cross compiler):

;int foo1(int d, int e) { ; int v,w; ; v = d; w = e; ; return d + e; ;}

0000 87 PSHA 0001 89 PSHX 0002 95 TSX 0003 e601 LDA 1,X 0005 eb05 ADD 5,X 0007 87 PSHA 0008 e604 LDA 4,X 000a f9 ADC ,X 000b 97 TAX 000c 86 PULA 000d a702 AIS #2 000f 81 RTS ;int foo(int a, int b, int c) { ; int q,r,s,t,u; ; q=r=s=a; t=b; u=c; ; return a + foo1(b, c); ; } 0000 87 PSHA 0001 89 PSHX 0002 a7fe AIS #-2 0004 9ee608 LDA 8,SP 0007 87 PSHA 0008 9ee608 LDA 8,SP 000b 87 PSHA 000c 9ee60c LDA 12,SP 000f 9ee703 STA 3,SP 0012 9ee606 LDA 6,SP 0015 ad00 BSR foo1 0017 a702 AIS #2 0019 9ee702 STA 2,SP 001c 9ee609 LDA 9,SP 001f 87 PSHA 0020 9ee602 LDA 2,SP 0023 9eeb03 ADD 3,SP 0026 87 PSHA 0027 9f TXA 0028 95 TSX 0029 e901 ADC 1,X 002b 97 TAX 002c 86 PULA 002d 8a PULH 002e a704 AIS #4 0030 81 RTS

;void main(void) { ; int x; ; x = foo(1,2,3); ; for(;;) { ; __RESET_WATCHDOG(); ; }

0000 a7fe AIS #-2 0002 ae01 LDX #1 0004 8c CLRH 0005 89 PSHX 0006 8b PSHH 0007 5c INCX 0008 89 PSHX 0009 8b PSHH 000a a603 LDA #3 000c 5f CLRX 000d ad00 BSR foo 000f a704 AIS #4 0011 9ee702 STA 2,SP 0014 9eef01 STX 1,SP

Now, here's a similar dissembly listing generated by PIC18 cross-compiler for the PIC18 core-- Quite a difference! This one uses fixed memory locations rather than Push/pull from the stack.

2: 3: int foo1(int d, int e) { 00001E 0D013 BRA 0x46 4: int v,w; 5: v = d; w = e; 000020 0C0F8 MOVFF 0xf8, 0xfc 000022 0F0FC NOP 000024 0C0F9 MOVFF 0xf9, 0xfd 000026 0F0FD NOP 000028 0C0FA MOVFF 0xfa, 0xfe 00002A 0F0FE NOP 00002C 0C0FB MOVFF 0xfb, 0xff 00002E 0F0FF NOP 6: return d + e; 000030 0C0F8 MOVFF 0xf8, 0 000032 0F000 NOP 000034 0C0F9 MOVFF 0xf9, 0x1 000036 0F001 NOP 000038 00100 MOVLB 0 00003A 051FA MOVF 0xfa, W, BANKED 00003C 02600 ADDWF 0, F, ACCESS 00003E 051FB MOVF 0xfb, W, BANKED 000040 02201 ADDWFC 0x1, F, ACCESS 000042 0D000 BRA 0x44 7: } 000044 00012 RETURN 0 8: 9: 10: int foo(int a, int b, int c) { 00004A 0D023 BRA 0x92 11: int q,r,s,t,u; 12: q=r=s=a; t=b; u=c; 00004C 0C0E8 MOVFF 0xe8, 0xf2 00004E 0F0F2 NOP 000050 0C0E9 MOVFF 0xe9, 0xf3 000052 0F0F3 NOP 000054 0C0F2 MOVFF 0xf2, 0xf0 000056 0F0F0 NOP 000058 0C0F3 MOVFF 0xf3, 0xf1 00005A 0F0F1 NOP 00005C 0C0F0 MOVFF 0xf0, 0xee 00005E 0F0EE NOP 000060 0C0F1 MOVFF 0xf1, 0xef 000062 0F0EF NOP 000064 0C0EA MOVFF 0xea, 0xf4 000066 0F0F4 NOP 000068 0C0EB MOVFF 0xeb, 0xf5 00006A 0F0F5 NOP 00006C 0C0EC MOVFF 0xec, 0xf6 00006E 0F0F6 NOP 000070 0C0ED MOVFF 0xed, 0xf7 000072 0F0F7 NOP 13: return a + foo1(b, c); 000074 0C0EA MOVFF 0xea, 0xf8 000076 0F0F8 NOP 000078 0C0EB MOVFF 0xeb, 0xf9 00007A 0F0F9 NOP 00007C 0C0EC MOVFF 0xec, 0xfa 00007E 0F0FA NOP 000080 0C0ED MOVFF 0xed, 0xfb 000082 0F0FB NOP 000084 0DFCC RCALL 0x1e 000086 051E8 MOVF 0xe8, W, BANKED 000088 02600 ADDWF 0, F, ACCESS 00008A 051E9 MOVF 0xe9, W, BANKED 00008C 02201 ADDWFC 0x1, F, ACCESS 00008E 0D000 BRA 0x90 14: } 000090 00012 RETURN 0 15: 16: 17: void main(void) { 000094 0D012 BRA 0xba 18: int x; 19: 20: x = foo(1,2,3); 000096 00E01 MOVLW 0x1 000098 00100 MOVLB 0 00009A 06FE8 MOVWF 0xe8, BANKED 00009C 06BE9 CLRF 0xe9, BANKED 00009E 00E02 MOVLW 0x2 0000A0 06FEA MOVWF 0xea, BANKED 0000A2 06BEB CLRF 0xeb, BANKED 0000A4 00E03 MOVLW 0x3 0000A6 06FEC MOVWF 0xec, BANKED 0000A8 06BED CLRF 0xed, BANKED 0000AA 0DFCF RCALL 0x4a 0000AC 0C000 MOVFF 0, 0xe6 0000AE 0F0E6 NOP 0000B0 0C001 MOVFF 0x1, 0xe7 0000B2 0F0E7 NOP 21: for(;;); 0000B4 0D7FF BRA 0xb4 22: 23: 24: } 0000B6 0EF0C GOTO 0x18

Best regards, Spehro Pefhany

--
"it's the network..."                          "The Journey is the reward"
speff@interlog.com             Info for manufacturers: http://www.trexon.com
Embedded software/hardware/analog  Info for designers:  http://www.speff.com
Reply to
Spehro Pefhany

If you're programming a small micro in C, you'd better understand both.

Best regards, Spehro Pefhany

--
"it's the network..."                          "The Journey is the reward"
speff@interlog.com             Info for manufacturers: http://www.trexon.com
Embedded software/hardware/analog  Info for designers:  http://www.speff.com
Reply to
Spehro Pefhany

In the 8051 it usually depends a great deal on whether the function involved must be able to be re-entered. If not, the linker allocates the variables in memory such that routines that can't be running at the same time reuse locations. Effectively the variable spaces get merged. This is part fo the reason that a good C compiler can make 8051 code that is only 3 times slower than assembly.

Returned values are usually put into ACC or (B,ACC) if they are 16 bit.

--
--
kensmith@rahul.net   forging knowledge
Reply to
Ken Smith

[...]

[snip]

Decide whether you want to write assembly language or C.

If you are programming in C, your compiler does all of this for you.

If you are programming in assembly, you will know how to do this as part of knowing how to program in assembly language.

Reply to
Guy Macon

Good point. I was only thinking of the PC case.

Reply to
Guy Macon

80x86 compilers store not only the subroutine return address, but also function parameters and local variables on the stack. Together, they comprise the stack frame. Often, a special register called the base pointer keeps track of where they are. The parameters and variables are accessed using base pointer relative addressing. Here's a (16-bit) stack frame:

BP+6 => Param1 BP+4 => Param2 BP+2 => Return adddress BP+0 => Previous value of BP saved here BP-2 => Local1 BP-4 => Local2

You can read about this in books and on the web, or you can just inspect the code your compiler produces.

If your processor doesn't have base pointer support, then the compiler has to do a lot more work!

Reply to
Andrew Holme

This is the sort of question you can best answer by investigation. Google will help you more than the busy people on newsgroups can. You need to get a C compiler and look at the output listing. I recommend Pelles C from :

formatting link

If you target the 80x86 family, you get a beautiful clear assembly you can work through. Unlike many small and RISC processors, the 80x86 family compilers tend to give consistent output for high level languages no matter how many levels deep are your function calls.

Pelles C gives you a nice editor to write and compile from. To see the output listing run the PODUMP utility with the /DISASM switch on the .OBJ files .

Here is what I get :

15: void foo1( int d, int e ) 16: {

_foo1: [00000000] 55 push ebp [00000001] 89E5 mov ebp,esp

@1: [00000003] 5D pop ebp [00000004] C3 ret

19: void foo( int a, int b, int c ) 20: {

_foo: [00000010] 55 push ebp [00000011] 89E5 mov ebp,esp [00000013] 83EC10 sub esp,+10 [00000016] 53 push ebx

21: int v1, v2, v3, v4, v5; 22: v1 = 0; [00000017] 31DB xor ebx,ebx 23: 24: foo1( v1, a ); [00000019] 8B4508 mov eax,dword ptr [ebp+8] [0000001C] 50 push eax [0000001D] 53 push ebx [0000001E] E800000000 call _foo1 [00000023] 83C408 add esp,+8

@2: [00000026] 5B pop ebx [00000027] 89EC mov esp,ebp [00000029] 5D pop ebp [0000002A] C3 ret

The function call to foo1() pushes on the stack the parameters from in eax and ebx. The ebp is an indexing register used to access the stack variables belonging to a function.

sub esp,+10 makes room for local variables while add esp+8 releases that stack space at the end of the function.

If you start with simple function calls, you can work out what is going on.

Roger

Reply to
Roger Lascelles

Thre is no one answer to this. Different processors and compilers do it differently. Note that many processors don't have a "stack", per se.

--
  Keith
Reply to
keith

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.