Closed
Description
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
Labels
No labels