{"action":"created","issue":{"url":"https://api.github.com/repos/micropython/micropython/issues/2894","repository_url":"https://api.github.com/repos/micropython/micropython","labels_url":"https://api.github.com/repos/micropython/micropython/issues/2894/labels{/name}","comments_url":"https://api.github.com/repos/micropython/micropython/issues/2894/comments","events_url":"https://api.github.com/repos/micropython/micropython/issues/2894/events","html_url":"https://github.com/micropython/micropython/issues/2894","id":209173783,"number":2894,"title":"Add method/function for calling non-python functions by function pointer","user":{"login":"ARF1","id":5834577,"avatar_url":"https://avatars.githubusercontent.com/u/5834577?v=3","gravatar_id":"","url":"https://api.github.com/users/ARF1","html_url":"https://github.com/ARF1","followers_url":"https://api.github.com/users/ARF1/followers","following_url":"https://api.github.com/users/ARF1/following{/other_user}","gists_url":"https://api.github.com/users/ARF1/gists{/gist_id}","starred_url":"https://api.github.com/users/ARF1/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ARF1/subscriptions","organizations_url":"https://api.github.com/users/ARF1/orgs","repos_url":"https://api.github.com/users/ARF1/repos","events_url":"https://api.github.com/users/ARF1/events{/privacy}","received_events_url":"https://api.github.com/users/ARF1/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2017-02-21T15:10:18Z","updated_at":"2017-02-24T16:01:25Z","closed_at":null,"body":"Sometimes it would be useful to be able to call existing non-python functions without having create a new module and recompile from source.\r\n\r\nAt present, because `asm_xtensa` functions only accept a limited number of arguments, many c-functions requiring more arguments cannot be called using `callx0` directly.\r\n\r\nA 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:\r\n```\r\nimport uctypes\r\nimport machine\r\n\r\n\r\n@micropython.asm_xtensa\r\ndef fn_ptr_callx0(a2, a3, a4):\r\n    \"\"\"\r\n    Implements the xtensa CALL0 ABI\r\n    arguments: fn_ptr, n_args, arg_stack_addr\r\n    \"\"\"\r\n    \r\n    # CALL0 ABI:\r\n    # Section 8.1.2 on page 589 of the \r\n    # Xtensa Instruction Set Architecture Reference Manual\r\n    #\r\n    # TODO: additional arguments passing on the stack\r\n\r\n    # free the registers needed for function calling convention\r\n    mov(a8, a2)    # a8 = fn_ptr\r\n    mov(a9, a3)    # a9 = n_args\r\n    mov(a10, a4)   # a10 = arg_stack_addr\r\n\r\n    # move arguments from argument stack into register\r\n    # corresponding to function calling convention\r\n    movi(a12, 6)\r\n    blt(a9, a12, LT6)         # skip if less than 6 arguments\r\n    l32i(a7, a10, 4*(6-1))\r\n    label(LT6)    \r\n    movi(a12, 5)\r\n    blt(a9, a12, LT5)         # skip if less than 5 arguments\r\n    l32i(a6, a10, 4*(5-1))\r\n    label(LT5)    \r\n    movi(a12, 4)\r\n    blt(a9, a12, LT4)         # skip if less than 4 arguments\r\n    l32i(a5, a10, 4*(4-1))\r\n    label(LT4) \r\n    movi(a12, 3)   \r\n    blt(a9, a12, LT3)         # skip if less than 3 arguments\r\n    l32i(a4, a10, 4*(3-1))\r\n    label(LT3)    \r\n    movi(a12, 2)\r\n    blt(a9, a12, LT2)         # skip if less than 2 arguments\r\n    l32i(a3, a10, 4*(2-1))\r\n    label(LT2)    \r\n    movi(a12, 1)\r\n    blt(a9, a12, LT1)         # skip if less than 1 arguments\r\n    l32i(a2, a10, 4*(1-1))\r\n    label(LT1)    \r\n\r\n    # call the function\r\n    callx0(a8)\r\n\r\ndef call_fn_ptr(fn_ptr, *args, has_return_value=False):\r\n    \"\"\"Calls a function defined using a function pointer\r\n\r\n    Arguments:\r\n     - fn_ptr\r\n     - *args: arguments to pass to the called function\r\n     - has_return_value: True/False - indicates whether called function returns a value\r\n    \"\"\"\r\n    if len(args) > 6:\r\n        raise NotImplementedError('Only up to 6 arguments are currently supported')\r\n\r\n    # store the arguments in a continuous memory region\r\n    arg_stack = bytes(4 * len(args))\r\n    for i, arg in enumerate(args):\r\n        if type(arg) == str:\r\n            # strings, pass as char *\r\n            # for pass as char, supply input as ord(character)\r\n            machine.mem32[uctypes.addressof(arg_stack) + 4 * i] = uctypes.addressof(arg)\r\n            continue\r\n        machine.mem32[uctypes.addressof(arg_stack) + 4 * i] = arg\r\n    ret = fn_ptr_callx0(fn_ptr, len(args), uctypes.addressof(arg_stack))\r\n    if has_return_value:\r\n        return ret\r\n```\r\n\r\nSome toy-examples using rom-functions on the esp8266:\r\n```\r\n>>> fn_ptr_rand = 0x40000600                                                    \r\n>>> fn_ptr_srand = 0x400005f0                                                   \r\n>>> call_fn_ptr(fn_ptr_srand,  1)                                               \r\n>>> call_fn_ptr(fn_ptr_rand, has_return_value=True)                             \r\n1481765933                                                                      \r\n>>> call_fn_ptr(fn_ptr_rand, has_return_value=True)                             \r\n1085377743                                                                      \r\n>>> call_fn_ptr(fn_ptr_srand,  1)                                               \r\n>>> call_fn_ptr(fn_ptr_rand, has_return_value=True)                             \r\n1481765933                                                                      \r\n>>> call_fn_ptr(fn_ptr_rand, has_return_value=True)                             \r\n1085377743                                                                      \r\n>>>                                                                             \r\n>>> # single argument call                                                      \r\n>>> fn_ptr_strlen = 0x4000bf4c                                                  \r\n>>> call_fn_ptr(fn_ptr_strlen, '12345', has_return_value=True)                  \r\n5                                                                               \r\n>>>                                                                             \r\n>>> # two argument call                                                         \r\n>>> fn_ptr_strcmp = 0x40002aa8                                                  \r\n>>> call_fn_ptr(fn_ptr_strcmp, '12345', '0', has_return_value=True)             \r\n1                                                                               \r\n>>> call_fn_ptr(fn_ptr_strcmp, '12345', '12345', has_return_value=True)         \r\n0                                                                               \r\n>>> call_fn_ptr(fn_ptr_strcmp, '12345', '2', has_return_value=True)             \r\n-1                                                                              \r\n>>>                                                                             \r\n>>> # three argument call                                                       \r\n>>> fn_ptr_strncmp = 0x4000bfa8                                                 \r\n>>> call_fn_ptr(fn_ptr_strncmp, '12345', '12333', 3, has_return_value=True)     \r\n0                                                                               \r\n>>> call_fn_ptr(fn_ptr_strncmp, '12345', '12333', 4, has_return_value=True)     \r\n1                                                                               \r\n>>>                                                                             \r\n>>> # six argument call                                                         \r\n>>> fn_ptr_ets_uart_printf = 0x40002544                                         \r\n>>> call_fn_ptr(fn_ptr_ets_uart_printf, '1: %d, 0xffffffff: 0x%x, 2**32-1: 0x%x,\r\n 4294967295: 0x%x, -1: 0x%x\\n', 1, 0xffffffff, 2**32-1, 4294967295, -1)         \r\n1: 1, 0xffffffff: 0xffffffff, 2**32-1: 0xffffffff, 4294967295: 0xffffffff, -1: 0\r\nxffffffff                                                                       \r\n>>>                                                                             \r\n>>> # six argument call                                                         \r\n>>> fn_ptr_ets_uart_printf = 0x40002544                                         \r\n>>> call_fn_ptr(fn_ptr_ets_uart_printf, '1: %d, 0xffffffff: 0x%x, 2**32-1: 0x%x,\r\n 4294967295: 0x%x, -1: 0x%x\\n', 1, 0xffffffff, 2**32-1, 4294967295, -1)         \r\n1: 1, 0xffffffff: 0xffffffff, 2**32-1: 0xffffffff, 4294967295: 0xffffffff, -1: 0\r\nxffffffff                                                                       \r\n>>> \r\n```\r\n\r\nOf course one could also wrap this to create a c-language equivalent function signature:\r\n```\r\ndef make_c_function(fn_ptr, has_return_value=False):\r\n    def wrapper(*args):\r\n        ret = call_fn_ptr(fn_ptr, *args, has_return_value=has_return_value)\r\n        if has_return_value:\r\n            return ret\r\n    return wrapper\r\n```\r\n\r\nExample use:\r\n```\r\n>>> ets_uart_printf = make_c_function(fn_ptr_ets_uart_printf)                       \r\n>>> ets_uart_printf('Hello %s\\n', 'World')                                      \r\nHello World                                                                     \r\n>>>\r\n```"},"comment":{"url":"https://api.github.com/repos/micropython/micropython/issues/comments/282328336","html_url":"https://github.com/micropython/micropython/issues/2894#issuecomment-282328336","issue_url":"https://api.github.com/repos/micropython/micropython/issues/2894","id":282328336,"user":{"login":"ARF1","id":5834577,"avatar_url":"https://avatars.githubusercontent.com/u/5834577?v=3","gravatar_id":"","url":"https://api.github.com/users/ARF1","html_url":"https://github.com/ARF1","followers_url":"https://api.github.com/users/ARF1/followers","following_url":"https://api.github.com/users/ARF1/following{/other_user}","gists_url":"https://api.github.com/users/ARF1/gists{/gist_id}","starred_url":"https://api.github.com/users/ARF1/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ARF1/subscriptions","organizations_url":"https://api.github.com/users/ARF1/orgs","repos_url":"https://api.github.com/users/ARF1/repos","events_url":"https://api.github.com/users/ARF1/events{/privacy}","received_events_url":"https://api.github.com/users/ARF1/received_events","type":"User","site_admin":false},"created_at":"2017-02-24T16:01:24Z","updated_at":"2017-02-24T16:01:24Z","body":"@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?"}}
{"actor":{"display_login":"ARF1"}}