Flexible templated function class and related utilities that avoid dynamic memory allocation without limiting functionality

Dependents:   SimpleHTTPExample

FuncPtr provides a flexible templated function class and related utilities while avoiding dynamic memory allocation and avoiding limited functionality.

FuncPtr provides an intuitive template interface:

FuncPtr<void(const char *)> func(puts);
func("hello!\n"); // prints hello!


Several function types are supported by FuncPtr:

// Simple C functions
void func();
FuncPtr<void()> fp(func);

// C++ Methods
struct Thing { void func(); };
Thing thing;
FuncPtr<void()> fp(&thing, &Thing::func);

// C functions with context
void func(Thing *);
FuncPtr<void()> fp(&thing, func);

// Function objects
struct Thing { void operator()(); };
Thing thing;
FuncPtr<void()> fp(&thing);


There is no dynamic memory allocation, managing memory is placed entirely on the user. More advanced function manipulation can be accomplished with statically allocated classes:

// Function binding
Binder<void(const char *), const char *> bind(putc, "hi!");
bind(); // prints hi!

// Function composition
Composer<int(const char *), const char *(int)> comp(puts, itoa);
comp(10); // prints 10

// Function chaining
Chainer<void(const char *), 2> chain;
chain.attach(puts);
chain.attach(puts);
chain("hi!\n"); // prints hi! twice


FuncPtr allows easy support of a large range of function types in C++ APIs with very few lines of code:

class Thing {
public:
    // The following two methods are sufficient for supporting 
    // every supported function type
    void attach(FuncPtr<void()> func) {
        _func.attach(func);
    }

    template<typename T, typename M>
    void attach(T *obj, M method) {
        attach(FuncPtr<void()>(obj, method));
    }

private:
    FuncPtr<void()> _func;
}


Additionally, FuncPtrs have several utilities for easy integration with C APIs:

// C style callbacks
void register_callback(void (*callback)(void *), void *data);

register_callback(&FuncPtr<void()>::thunk, &func);

// C style functions without context
void register_callback(void (*callback)());

Thunker thunk(func);
register_callback(thunk);

// mbed style callbacks
void register_callback(T *obj, void (T::*M)());

register_callback(&func, &FuncPtr<void()>::call);

trampoline.h

Committer:
Christopher Haster
Date:
2016-04-17
Revision:
18:a0fde14b6c39
Parent:
17:079c5ad807fb

File content as of revision 18:a0fde14b6c39:

/*  Platform specific trampoline generation
 */
#ifndef TRAMPOLINE_H
#define TRAMPOLINE_H


/*  Type of function available from trampolines
 */
typedef void entry_t();

/*  Trampoline storage
 */
typedef struct trampoline trampoline_t;

/*  Trampoline setup
 *  @param trampoline   Storage for trampoline
 *  @param callback     Function to call with context
 *  @param context      Context to pass to function
 *  @return             Entry point into trampoline
 */
static inline entry_t *trampoline_init(trampoline_t *trampoline,
        void (*callback)(void *), void *context);


/*  Platform specific trampoline generation
 */
#if defined(__CORTEX_M3) || defined(__CORTEX_M4) || defined(__thumb2__)
struct trampoline {
    volatile uint32_t code[1];
    volatile uint32_t context;
    volatile uint32_t callback;
};

static inline entry_t *trampoline_init(trampoline_t *trampoline,
         void (*callback)(void *), void *context) {

    // Loads context into r0 and branches to callback
    // Only touches scratch registers saved by caller
    trampoline->code[0] = 0x8001e89f; // ldm pc, {r0, pc}
    trampoline->context = (uint32_t)context;
    trampoline->callback = (uint32_t)callback;

    // Flush caches
#if defined(__GNUC__)
    __asm__ volatile ("isb 0xf":::"memory");
    __asm__ volatile ("dsb 0xf":::"memory");
#else
    __ISB()
    __DSB()
#endif

    // Set thumb bit
    return (entry_t *)((uint32_t)trampoline->code + 1);
}

#elif defined(__CORTEX_M0PLUS) || defined(__CORTEX_M0) || defined(__thumb__)
struct trampoline {
    volatile uint16_t code[4];
    volatile uint32_t context;
    volatile uint32_t callback;
};

static inline entry_t *trampoline_init(trampoline_t *trampoline,
         void (*callback)(void *), void *context) {

    // Loads context into r0, callback to r1, and branches
    // Only touches scratch registers saved by caller
    trampoline->code[0] = 0x2202; // movs r2, #2
    trampoline->code[1] = 0x447a; // add r2, pc
    trampoline->code[2] = 0xca03; // ldm r2!, {r0, r1}
    trampoline->code[3] = 0x4708; // bx r1
    trampoline->context = (uint32_t)context;
    trampoline->callback = (uint32_t)callback;

    // Flush caches
#if defined(__GNUC__)
    __asm__ volatile ("isb 0xf":::"memory");
    __asm__ volatile ("dsb 0xf":::"memory");
#else
    __ISB()
    __DSB()
#endif

    // Set thumb bit
    return (entry_t *)((uint32_t)trampoline->code + 1);
}

#elif defined(__i386__)
#include <sys/mman.h>
#include <unistd.h>

struct trampoline {
    volatile uint8_t code[24];
    volatile uint32_t context;
    volatile uint32_t callback;
};

static inline entry_t *trampoline_init(trampoline_t *trampoline,
        void (*callback)(void *), void *context) {
    
    // Pushes context and calls callback
    // Relative call is used to access pc-relative addresses
    // Only touches scratch registers saved by caller
    volatile uint8_t *c = trampoline->code;
    c[ 0]=0x55;                                 // push %ebp
    c[ 1]=0x89; c[2]=0xe5;                      // mov %esp, %ebp
    c[ 3]=0xe8; c[4]=0; c[5]=0; c[6]=0; c[7]=0; // call 0
    c[ 8]=0x5a;                                 // pop %edx
    c[ 9]=0x8b; c[10]=0x42; c[11]=16;           // mov 16(%edx), %eax
    c[12]=0x50;                                 // push %eax
    c[13]=0x8b; c[14]=0x42; c[15]=20;           // mov 20(%edx), %eax
    c[16]=0xff; c[17]=0xd0;                     // call *%eax
    c[18]=0x89; c[19]=0xec;                     // mov %ebp, %esp
    c[20]=0x5d;                                 // pop %ebp
    c[21]=0xc3;                                 // ret
    trampoline->context = (uint32_t)context;
    trampoline->callback = (uint32_t)callback;

    // Mark region as executable
    uintptr_t pagesize = sysconf(_SC_PAGESIZE);
    uintptr_t start = (uintptr_t)trampoline & ~(pagesize-1);
    uintptr_t size  = ((uintptr_t)trampoline - start) + sizeof(trampoline_t);
    int err = mprotect((void*)start, size, PROT_READ | PROT_WRITE | PROT_EXEC);
    if (err) {
        return 0;
    }

    // Return entry point
    return (entry_t *)trampoline->code;
}

#elif defined(__amd64__)
#include <sys/mman.h>
#include <unistd.h>

struct trampoline {
    volatile uint8_t code[24];
    volatile uint64_t context;
    volatile uint64_t callback;
};

static inline entry_t *trampoline_init(trampoline_t *trampoline,
        void (*callback)(void *), void *context) {
    
    // Pushes context and calls callback
    // Relative call is used to access pc-relative addresses
    // Only touches scratch registers saved by caller
    volatile uint8_t *c = trampoline->code;
    c[ 0]=0x55;                         // push %rbp
    c[ 1]=0x48; c[ 2]=0x89; c[ 3]=0xe5; // mov %rsp, %rbp
    // mov 13(%rip), %rdi
    c[ 4]=0x48; c[ 5]=0x8b; c[ 6]=0x3d; c[ 7]=13; c[ 8]=0; c[ 9]=0; c[10]=0;
    // mov 14(%rip), %rax
    c[11]=0x48; c[12]=0x8b; c[13]=0x05; c[14]=14; c[15]=0; c[16]=0; c[17]=0;
    c[18]=0xff; c[19]=0xd0;             // callq *%rax
    c[20]=0x5d;                         // pop %rbp
    c[21]=0xc3;                         // retq
    trampoline->context = (uint64_t)context;
    trampoline->callback = (uint64_t)callback;

    // Mark region as executable
    uintptr_t pagesize = sysconf(_SC_PAGESIZE);
    uintptr_t start = (uintptr_t)trampoline & ~(pagesize-1);
    uintptr_t size  = ((uintptr_t)trampoline - start) + sizeof(trampoline_t);
    int err = mprotect((void*)start, size, PROT_READ | PROT_WRITE | PROT_EXEC);
    if (err) {
        return 0;
    }

    // Return entry point
    return (entry_t *)trampoline->code;
}

#else
#error "Target is currently not supported"
#endif


#endif