7

I need to call a function at address 0xDD2:

// foo.h
void foo(void) __at(0xDD2);

// foo.c
#include "foo.h"
void foo(void)
{
    // some code
}

This code works:

#include "foo.h"
void main(void)
{
    void (*a)(void) = &foo;
    a();
}

However, this one doesn't:

#include "foo.h"
void main(void)
{
    void (*a)(void) = (void (*)(void))(0x0DD2);
    a();
}

The compiler (XC8) says: main.c:5:: warning: (759) expression generates no code and debugger passes these lines while debugging.

I need second one (call function just by its address). Why compiler optimizes it out? Is there any mistake in pointer assignment? Changing optimization level of compiler didn't help.

AmirSina Mashayekh
  • 498
  • 1
  • 5
  • 21
  • 2
    If the function is never called, you probably have to force it to get linked with some trick in the linker script. Why should the function never get called though, is it a bootloader or something? – Lundin Jun 09 '20 at 11:59
  • 3
    Can you provide a minimal context where it can be reproduced? Where exactly these lines are placed? – Alex Lop. Jun 09 '20 at 11:59
  • @Lundin The function itself is OK (not optimized out at all). I want to use USB peripheral in both bootloader and application. However, if I write USB codes for both, it will take a lot of memory. I'm trying to create something like a **USB partition**! (put USB codes at a specific area and use it for both bootloader and main application) – AmirSina Mashayekh Jun 09 '20 at 12:07
  • @AlexLop. These lines could be everywhere. For now, they are in main(). – AmirSina Mashayekh Jun 09 '20 at 12:09
  • Can you try `void (* volatile a)(void) = (void (*)(void))(0x0DD2);` ? Does it change anything? – Alex Lop. Jun 09 '20 at 12:10
  • @AlexLop. Good news: Debugger runs this line and assigns address to `a`. Bad news: `a()` still not working! – AmirSina Mashayekh Jun 09 '20 at 12:16
  • By "not working" you mean no call/jump/branch is generated by the compiler? – Alex Lop. Jun 09 '20 at 12:18
  • What is actually stored as this location `0x0DD2` in memory? Is it a plain function created the same way as a function inside your own code? – Some programmer dude Jun 09 '20 at 12:20
  • @AlexLop. Yes. Compiler says `expression generates no code` and debugger passes `a();` line. – AmirSina Mashayekh Jun 09 '20 at 12:20
  • @Someprogrammerdude Look at first code: `void foo(void) __at(0xDD2);`. I need to call `foo()` just by its address (0xDD2). – AmirSina Mashayekh Jun 09 '20 at 12:22
  • Now the big question: Does `foo.c` include `foo.h`? Does the compiler *know* about the `__at(0xDD2)` part when it's compiling the `foo` function definition (implementation)? There's a reason we usually ask for a [mcve] much earlier than has now been done. And you should really ask about this latest issue in a new question, since this has really been answered. – Some programmer dude Jun 09 '20 at 12:33
  • `main()` must return `int` – underscore_d Jun 09 '20 at 12:39
  • @Someprogrammerdude I edited my question codes; and compiler puts `foo()` in correct location as I found while debugging. – AmirSina Mashayekh Jun 09 '20 at 12:40
  • Can you try to: `typedef void (*fn_p)(void); ... extern fn_p get_fn_p(void); ... fn_p a = get_fn_p(); a();`. Will you see the same warning? – Alex Lop. Jun 09 '20 at 12:40
  • 1
    @underscore_d: This particular compiler uses a (non-standard) dialect of C in which `main` is supposed to return `void`. This is documented at D.3.1 in [the manual](http://ww1.microchip.com/downloads/en/devicedoc/50002053g.pdf). So that's not the issue here. – Nate Eldredge Jun 09 '20 at 12:43
  • @AlexLop. Where should I put the address (0xDD2) in your code? Also compiler makes error. – AmirSina Mashayekh Jun 09 '20 at 12:46
  • @AmirSinaMashayekh Do not put it. Since there is no definition for `get_fn_p()` it will fail linkage anyway but I am curious to see if the compiler generates the assembly for `a();` – Alex Lop. Jun 09 '20 at 12:48
  • 1
    Section 5.4.5.4 of [the compiler manual](http://ww1.microchip.com/downloads/en/devicedoc/50002053g.pdf), page 161, seems to be saying that such casts are simply not allowed. It may be that the `__at` construct is the only way to call an absolute address. Can you explain, perhaps with an example, why this won't work in the application you have in mind? Maybe there will be a different approach that would work. – Nate Eldredge Jun 09 '20 at 12:50
  • I don't know a lot about PIC, but a quick glance at some instruction set listings suggest that many or all PIC machines don't have an indirect jump instruction. That would make it impossible to use function pointers in a completely general way, and might explain the restrictions imposed by the compiler. Of course in this case there is only one possible target for the jump, and the compiler could optimize it into an unconditional jump, but it may not be smart enough for that. – Nate Eldredge Jun 09 '20 at 12:56
  • @NateEldredge Read the third comment. If I create separate projects for bootloader, USB and main app, I can't use `&foo` because foo is in USB project. – AmirSina Mashayekh Jun 09 '20 at 13:00
  • There are some other notes in the compiler manual linked above that suggest that function pointers are only supported insofar as the compiler can keep track of all the functions to which the pointer might point. That would fit with the lack of an indirect jump instruction. And it might be that this tracking is only supported when those functions have names, even if there is only one of them. – Nate Eldredge Jun 09 '20 at 13:02
  • 1
    Based on what @NateEldredge wrote, you need to reuse the same `foo.h` and `foo.c` for both projects. Create a 'shared' directory which will be used by both projects and place those files in it. – Alex Lop. Jun 09 '20 at 13:03
  • @AmirSinaMashayekh: I read that comment but I'm afraid I still don't understand. Can't you define `foo` at an absolute address in the USB project, then use the declaration (without the definition) to call it from the main project? – Nate Eldredge Jun 09 '20 at 13:04
  • @NateEldredge You mean I include `foo.h` in all projects? Good idea. I'll try it soon. – AmirSina Mashayekh Jun 09 '20 at 13:09
  • Maybe try to assign first `0x0DD2` to a `long` like `long var = 0x0DD2;`, then replace the 0x0DD2 with `var`? `void (*a)(void) = (void (*)(void))var;` That could look less strange to the compiler. – Déjà vu Jun 09 '20 at 13:28

3 Answers3

1

This seems to do what you asked for:

/*
 * File:   main.c
 * Author: dan1138
 * Target: PIC18F47Q10
 * Compiler: XC8 v2.20
 * 
 * See: https://stackoverflow.com/questions/62282036/compiler-optimizes-out-pointer-to-function-when-address-is-assigned-manually/62297967#62297967
 *
 * Created on June 9, 2020, 11:42 PM
 */
#pragma config FEXTOSC = OFF, RSTOSC = HFINTOSC_64MHZ, CLKOUTEN = OFF
#pragma config CSWEN = ON, FCMEN = OFF, MCLRE = EXTMCLR, PWRTE = OFF
#pragma config LPBOREN = OFF, BOREN = SBORDIS, BORV = VBOR_190
#pragma config ZCD = OFF, PPS1WAY = OFF, STVREN = ON, XINST = OFF
#pragma config WDTCPS = WDTCPS_31, WDTE = OFF, WDTCWS = WDTCWS_7, WDTCCS = SC
#pragma config WRT0 = OFF, WRT1 = OFF, WRT2 = OFF, WRT3 = OFF
#pragma config WRT4 = OFF, WRT5 = OFF, WRT6 = OFF, WRT7 = OFF
#pragma config WRTC = OFF, WRTB = OFF, WRTD = OFF
#pragma config SCANE = ON, LVP = OFF, CP = OFF, CPD = OFF
#pragma config EBTR0 = OFF, EBTR1 = OFF, EBTR2 = OFF, EBTR3 = OFF
#pragma config EBTR4 = OFF, EBTR5 = OFF, EBTR6 = OFF, EBTR7 = OFF
#pragma config EBTRB = OFF

#include <xc.h>

void main(void)
{
    extern void foo(void) __at(0xDD2);
    void (* volatile a)(void) = foo;

    a();
}    

Disassembly:

!void main(void)
!{
!    extern void foo(void) __at(0xDD2);
!    void (* volatile a)(void) = foo;
0x1FFD4: MOVLW 0xD2
0x1FFD6: MOVWF __pcstackCOMRAM, ACCESS
0x1FFD8: MOVLW 0xD
0x1FFDA: MOVWF 0x2, ACCESS
0x1FFDC: MOVLW 0x0
0x1FFDE: MOVWF 0x3, ACCESS
!    a();
0x1FFE0: RCALL 0xFFE6
0x1FFE2: GOTO 0x1FFFA
0x1FFE4: NOP
0x1FFE6: PUSH
0x1FFE8: MOVWF PCLATH, ACCESS
0x1FFEA: MOVF __pcstackCOMRAM, W, ACCESS
0x1FFEC: MOVWF TOS, ACCESS
0x1FFEE: MOVF 0x2, W, ACCESS
0x1FFF0: MOVWF TOSH, ACCESS
0x1FFF2: MOVF 0x3, W, ACCESS
0x1FFF4: MOVWF TOSU, ACCESS
0x1FFF6: MOVF PCLATH, W, ACCESS
0x1FFF8: RETURN 0
!}
0x1FFFA: BSF _ccovbit2_1, 0, ACCESS
0x1FFFC: GOTO 0x0
0x1FFFE: NOP
Dan1138
  • 1,150
  • 8
  • 11
0

Should really be a comment, but it contains code and reference from standard.

I have no XC8 compiler at hand so I tried to mimic something with CLang. This compiles with no warning and correctly display In foo:

#include <stdio.h>
#include <stdint.h>

void foo(void) {
    printf("In foo\n");
}

int main() {
    intptr_t f = (intptr_t) &foo;
    printf("%lx\n", f);
    void (*a)(void) = (void (*)(void))f;
    (*a)();        // Same behaviour with a();
    return 0;
}

I controlled in C11 reference manual (draft n1570) at 6.3.2.3 Language/Conversions/Other operands/Pointers §5-6:

5 An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined...

6 Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined....

My understanding is that converting from a plain integer to a function pointer is legal but implementation defined. What is the compatibility level of your compiler, and are there any flags that could control that behaviour?

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
0

Based on what I read in XC8 manual and some discussions at another forum, it's not a good idea to put objects at a fixed address to use them in another project because of compiled stack and some other reasons.
However, if you just need to prevent compiler from optimizing out pointer to function when address is assigned manually, Use code in this comment or this answer.

AmirSina Mashayekh
  • 498
  • 1
  • 5
  • 21