Skip to content

Add method/function for calling non-python functions by function pointer #2894

Closed
@ARF1

Description

@ARF1

Sometimes it would be useful to be able to call existing non-python functions without having create a new module and recompile from source.

At present, because asm_xtensa functions only accept a limited number of arguments, many c-functions requiring more arguments cannot be called using callx0 directly.

A crude implementation of what I am looking for is the following (for esp8266). The implementation is limited to 6 arguments but easily extensible to an arbitrary number of arguments:

import uctypes
import machine


@micropython.asm_xtensa
def fn_ptr_callx0(a2, a3, a4):
    """
    Implements the xtensa CALL0 ABI
    arguments: fn_ptr, n_args, arg_stack_addr
    """
    
    # CALL0 ABI:
    # Section 8.1.2 on page 589 of the 
    # Xtensa Instruction Set Architecture Reference Manual
    #
    # TODO: additional arguments passing on the stack

    # free the registers needed for function calling convention
    mov(a8, a2)    # a8 = fn_ptr
    mov(a9, a3)    # a9 = n_args
    mov(a10, a4)   # a10 = arg_stack_addr

    # move arguments from argument stack into register
    # corresponding to function calling convention
    movi(a12, 6)
    blt(a9, a12, LT6)         # skip if less than 6 arguments
    l32i(a7, a10, 4*(6-1))
    label(LT6)    
    movi(a12, 5)
    blt(a9, a12, LT5)         # skip if less than 5 arguments
    l32i(a6, a10, 4*(5-1))
    label(LT5)    
    movi(a12, 4)
    blt(a9, a12, LT4)         # skip if less than 4 arguments
    l32i(a5, a10, 4*(4-1))
    label(LT4) 
    movi(a12, 3)   
    blt(a9, a12, LT3)         # skip if less than 3 arguments
    l32i(a4, a10, 4*(3-1))
    label(LT3)    
    movi(a12, 2)
    blt(a9, a12, LT2)         # skip if less than 2 arguments
    l32i(a3, a10, 4*(2-1))
    label(LT2)    
    movi(a12, 1)
    blt(a9, a12, LT1)         # skip if less than 1 arguments
    l32i(a2, a10, 4*(1-1))
    label(LT1)    

    # call the function
    callx0(a8)

def call_fn_ptr(fn_ptr, *args, has_return_value=False):
    """Calls a function defined using a function pointer

    Arguments:
     - fn_ptr
     - *args: arguments to pass to the called function
     - has_return_value: True/False - indicates whether called function returns a value
    """
    if len(args) > 6:
        raise NotImplementedError('Only up to 6 arguments are currently supported')

    # store the arguments in a continuous memory region
    arg_stack = bytes(4 * len(args))
    for i, arg in enumerate(args):
        if type(arg) == str:
            # strings, pass as char *
            # for pass as char, supply input as ord(character)
            machine.mem32[uctypes.addressof(arg_stack) + 4 * i] = uctypes.addressof(arg)
            continue
        machine.mem32[uctypes.addressof(arg_stack) + 4 * i] = arg
    ret = fn_ptr_callx0(fn_ptr, len(args), uctypes.addressof(arg_stack))
    if has_return_value:
        return ret

Some toy-examples using rom-functions on the esp8266:

>>> fn_ptr_rand = 0x40000600                                                    
>>> fn_ptr_srand = 0x400005f0                                                   
>>> call_fn_ptr(fn_ptr_srand,  1)                                               
>>> call_fn_ptr(fn_ptr_rand, has_return_value=True)                             
1481765933                                                                      
>>> call_fn_ptr(fn_ptr_rand, has_return_value=True)                             
1085377743                                                                      
>>> call_fn_ptr(fn_ptr_srand,  1)                                               
>>> call_fn_ptr(fn_ptr_rand, has_return_value=True)                             
1481765933                                                                      
>>> call_fn_ptr(fn_ptr_rand, has_return_value=True)                             
1085377743                                                                      
>>>                                                                             
>>> # single argument call                                                      
>>> fn_ptr_strlen = 0x4000bf4c                                                  
>>> call_fn_ptr(fn_ptr_strlen, '12345', has_return_value=True)                  
5                                                                               
>>>                                                                             
>>> # two argument call                                                         
>>> fn_ptr_strcmp = 0x40002aa8                                                  
>>> call_fn_ptr(fn_ptr_strcmp, '12345', '0', has_return_value=True)             
1                                                                               
>>> call_fn_ptr(fn_ptr_strcmp, '12345', '12345', has_return_value=True)         
0                                                                               
>>> call_fn_ptr(fn_ptr_strcmp, '12345', '2', has_return_value=True)             
-1                                                                              
>>>                                                                             
>>> # three argument call                                                       
>>> fn_ptr_strncmp = 0x4000bfa8                                                 
>>> call_fn_ptr(fn_ptr_strncmp, '12345', '12333', 3, has_return_value=True)     
0                                                                               
>>> call_fn_ptr(fn_ptr_strncmp, '12345', '12333', 4, has_return_value=True)     
1                                                                               
>>>                                                                             
>>> # six argument call                                                         
>>> fn_ptr_ets_uart_printf = 0x40002544                                         
>>> call_fn_ptr(fn_ptr_ets_uart_printf, '1: %d, 0xffffffff: 0x%x, 2**32-1: 0x%x,
 4294967295: 0x%x, -1: 0x%x\n', 1, 0xffffffff, 2**32-1, 4294967295, -1)         
1: 1, 0xffffffff: 0xffffffff, 2**32-1: 0xffffffff, 4294967295: 0xffffffff, -1: 0
xffffffff                                                                       
>>>                                                                             
>>> # six argument call                                                         
>>> fn_ptr_ets_uart_printf = 0x40002544                                         
>>> call_fn_ptr(fn_ptr_ets_uart_printf, '1: %d, 0xffffffff: 0x%x, 2**32-1: 0x%x,
 4294967295: 0x%x, -1: 0x%x\n', 1, 0xffffffff, 2**32-1, 4294967295, -1)         
1: 1, 0xffffffff: 0xffffffff, 2**32-1: 0xffffffff, 4294967295: 0xffffffff, -1: 0
xffffffff                                                                       
>>> 

Of course one could also wrap this to create a c-language equivalent function signature:

def make_c_function(fn_ptr, has_return_value=False):
    def wrapper(*args):
        ret = call_fn_ptr(fn_ptr, *args, has_return_value=has_return_value)
        if has_return_value:
            return ret
    return wrapper

Example use:

>>> ets_uart_printf = make_c_function(fn_ptr_ets_uart_printf)                       
>>> ets_uart_printf('Hello %s\n', 'World')                                      
Hello World                                                                     
>>>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions