Connect with us

Question about function call in a microprocessor?

Discussion in 'Electronic Design' started by [email protected], Feb 11, 2005.

Scroll to continue with content
  1. Guest

    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.
  2. DaveC

    DaveC Guest

    wrote in
    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

    Movlw a,w
    Addwf b,w
    Addwf c,w

    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

  3. 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(;;) {
    ; }

    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.

    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
    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
    17: void main(void) {
    000094 0D012 BRA 0xba
    18: int x;
    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
    24: }
    0000B6 0EF0C GOTO 0x18

    Best regards,
    Spehro Pefhany
  4. Ken Smith

    Ken Smith Guest

    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.
  5. Guy Macon

    Guy Macon Guest


    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.
  6. Guy Macon

    Guy Macon Guest

    Good point. I was only thinking of the PC case.
  7. If you're programming a small micro in C, you'd better understand

    Best regards,
    Spehro Pefhany
  8. Andrew Holme

    Andrew Holme Guest

    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!
  9. This is the sort of question you can best answer by investigation. Google
    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 :

    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
    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: {

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

    [00000003] 5D pop ebp
    [00000004] C3 ret

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

    [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
    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

    [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
    a function.

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

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

  10. keith

    keith Guest

    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.
Ask a Question
Want to reply to this thread or ask your own question?
You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.
Electronics Point Logo
Continue to site
Quote of the day