8000 Add method/function for calling non-python functions by function pointer · Issue #2894 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ARF1 opened this issue Feb 21, 2017 · 6 comments
Closed

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

ARF1 opened this issue Feb 21, 2017 · 6 comments

Comments

@ARF1
Copy link
ARF1 commented Feb 21, 2017

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                                                                     
>>>
@pfalcon
Copy link
Contributor
pfalcon commented Feb 24, 2017

The way MicroPython does it is by "ffi" module: https://github.com/micropython/micropython/blob/master/examples/unix/ffi_example.py .

@ARF1
Copy link
Author
ARF1 commented Feb 24, 2017

@pfalcon Thanks. I was looking for something like ffi but could not find it. Is there any intention to include the ffi module in the esp8266 port down the road? Or is it too large to be considered?

@pfalcon
Copy link
Contributor
pfalcon commented Feb 25, 2017

I definitely thought about being able to use "ffi" module on bare-metal (well, non-unix) ports. Actually getting this done would depend on answering the usual question of why it's needed and then prioritizing the work wrt to thousands of other things. (So I personally don't think this would be done any time soon, interested parties are however welcome to make steps in the collinear direction, e.g. #2191)

@dpgeorge
Copy link
Member< 8000 /span>

There has already been quite a bit of discussion and work done on implementing dynamic loading of C-based modules: #1627.

@jonnor
Copy link
Contributor
jonnor commented Jul 2, 2024

Dynamic loading of native C modules is now implemented and in use for some years. Does this mean that this issue is no longer relevant, and can be closed?

@dpgeorge
Copy link
Member
dpgeorge commented Jul 3, 2024

Calling C from Python is a pretty complex and deep topic, with ffi and dynamic-native-modules just two ways to do it. The original post on this issues shows another way using pure Python and @micropython.asm_xtensa.

But, yes, let's close this issue, it hasn't had any further progress or interest.

@dpgeorge dpgeorge closed this as completed Jul 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants
0