{"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":3,"created_at":"2017-02-21T15:10:18Z","updated_at":"2017-02-28T07:36:47Z","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/282965050","html_url":"https://github.com/micropython/micropython/issues/2894#issuecomment-282965050","issue_url":"https://api.github.com/repos/micropython/micropython/issues/2894","id":282965050,"user":{"login":"dpgeorge","id":6187689,"avatar_url":"https://avatars.githubusercontent.com/u/6187689?v=3","gravatar_id":"","url":"https://api.github.com/users/dpgeorge","html_url":"https://github.com/dpgeorge","followers_url":"https://api.github.com/users/dpgeorge/followers","following_url":"https://api.github.com/users/dpgeorge/following{/other_user}","gists_url":"https://api.github.com/users/dpgeorge/gists{/gist_id}","starred_url":"https://api.github.com/users/dpgeorge/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dpgeorge/subscriptions","organizations_url":"https://api.github.com/users/dpgeorge/orgs","repos_url":"https://api.github.com/users/dpgeorge/repos","events_url":"https://api.github.com/users/dpgeorge/events{/privacy}","received_events_url":"https://api.github.com/users/dpgeorge/received_events","type":"User","site_admin":false},"created_at":"2017-02-28T07:36:47Z","updated_at":"2017-02-28T07:36:47Z","body":"There has already been quite a bit of discussion and work done on implementing dynamic loading of C-based modules: #1627."}}
{"actor":{"display_login":"dpgeorge"}}