Porównaj commity

...

22 Commity

Autor SHA1 Wiadomość Data
Jared Hancock 2ccc77c285
Merge ed7ef9e48c into e60e8079a7 2024-04-26 17:46:30 -05:00
Damien George e60e8079a7 nrf/mpconfigport: Enable MICROPY_NLR_THUMB_USE_LONG_JUMP on nRF51xx.
Signed-off-by: Damien George <damien@micropython.org>
2024-04-26 11:15:59 +10:00
J. Neuschäfer 7b050b366b py/nlrthumb: Make non-Thumb2 long-jump workaround opt-in.
Although the original motivation given for the workaround[1] is correct,
nlr.o and nlrthumb.o are linked with a small enough distance that the
problem does not occur, and the workaround isn't necessary. The distance
between the b instruction and its target (nlr_push_tail) is just 64
bytes[2], well within the ±2046 byte range addressable by an
unconditional branch instruction in Thumb mode.

The workaround induces a relocation in the text section (textrel), which
isn't supported everywhere, notably not on musl-libc[3], where it causes
a crash on start-up. With the workaround removed, micropython works on an
ARMv5T Linux system built with musl-libc.

This commit changes nlrthumb.c to use a direct jump by default, but
leaves the long jump workaround as an option for those cases where it's
actually needed.

[1]: commit dd376a239d

Author: Damien George <damien.p.george@gmail.com>
Date:   Fri Sep 1 15:25:29 2017 +1000

    py/nlrthumb: Get working again on standard Thumb arch (ie not Thumb2).

    "b" on Thumb might not be long enough for the jump to nlr_push_tail so
    it must be done indirectly.

[2]: Excerpt from objdump -d micropython:

000095c4 <nlr_push_tail>:
    95c4:       b510            push    {r4, lr}
    95c6:       0004            movs    r4, r0
    95c8:       f02d fd42       bl      37050 <mp_thread_get_state>
    95cc:       6943            ldr     r3, [r0, #20]
    95ce:       6023            str     r3, [r4, #0]
    95d0:       6144            str     r4, [r0, #20]
    95d2:       2000            movs    r0, #0
    95d4:       bd10            pop     {r4, pc}

000095d6 <nlr_pop>:
    95d6:       b510            push    {r4, lr}
    95d8:       f02d fd3a       bl      37050 <mp_thread_get_state>
    95dc:       6943            ldr     r3, [r0, #20]
    95de:       681b            ldr     r3, [r3, #0]
    95e0:       6143            str     r3, [r0, #20]
    95e2:       bd10            pop     {r4, pc}

000095e4 <nlr_push>:
    95e4:       60c4            str     r4, [r0, #12]
    95e6:       6105            str     r5, [r0, #16]
    95e8:       6146            str     r6, [r0, #20]
    95ea:       6187            str     r7, [r0, #24]
    95ec:       4641            mov     r1, r8
    95ee:       61c1            str     r1, [r0, #28]
    95f0:       4649            mov     r1, r9
    95f2:       6201            str     r1, [r0, #32]
    95f4:       4651            mov     r1, sl
    95f6:       6241            str     r1, [r0, #36]   @ 0x24
    95f8:       4659            mov     r1, fp
    95fa:       6281            str     r1, [r0, #40]   @ 0x28
    95fc:       4669            mov     r1, sp
    95fe:       62c1            str     r1, [r0, #44]   @ 0x2c
    9600:       4671            mov     r1, lr
    9602:       6081            str     r1, [r0, #8]
    9604:       e7de            b.n     95c4 <nlr_push_tail>

[3]: https://www.openwall.com/lists/musl/2020/09/25/4

Signed-off-by: J. Neuschäfer <j.ne@posteo.net>
2024-04-25 16:06:28 +10:00
Damien George 49af8cad49 webassembly/api: Inject asyncio.run if needed by the script.
This allows a simple way to run the existing asyncio tests under the
webassembly port, which doesn't support `asyncio.run()`.

Signed-off-by: Damien George <damien@micropython.org>
2024-04-24 16:24:00 +10:00
Damien George 8a3546b3bd webassembly: Add JavaScript-based asyncio support.
This commit adds a significant portion of the existing MicroPython asyncio
module to the webassembly port, using parts of the existing asyncio code
and some custom JavaScript parts.

The key difference to the standard asyncio is that this version uses the
JavaScript runtime to do the actual scheduling and waiting on events, eg
Promise fulfillment, timeouts, fetching URLs.

This implementation does not include asyncio.run(). Instead one just uses
asyncio.create_task(..) to start tasks and then returns to the JavaScript.
Then JavaScript will run the tasks.

The implementation here tries to reuse as much existing asyncio code as
possible, and gets all the semantics correct for things like cancellation
and asyncio.wait_for.  An alternative approach would reimplement Task,
Event, etc using JavaScript Promise's.  That approach is very difficult to
get right when trying to implement cancellation (because it's not possible
to cancel a JavaScript Promise).

Signed-off-by: Damien George <damien@micropython.org>
2024-04-24 16:24:00 +10:00
Damien George 84d6f8e8cb webassembly/modjsffi: Add jsffi.async_timeout_ms.
This function exposes `setTimeout()` as an async function.

Signed-off-by: Damien George <damien@micropython.org>
2024-04-24 16:24:00 +10:00
Damien George 967ad38ac7 extmod/modasyncio: Make mp_asyncio_context variable public.
So it can be accessed by a port if needed, for example to see if asyncio
has been imported.

Signed-off-by: Damien George <damien@micropython.org>
2024-04-24 16:23:59 +10:00
Damien George d998ca78c8 webassembly/proxy_c: Fix then-continue to convert reason to throw value.
When a Promise is rejected on the JavaScript side, the reject reason should
be thrown into the encapsulating generator on the Python side.

Signed-off-by: Damien George <damien@micropython.org>
2024-04-24 16:23:42 +10:00
Damien George 92b3b69648 webassembly/proxy_c: Fix proxy then reject handling.
An exception on the Python side should be passed to the Promise reject
callback on the JavaScript side.

Signed-off-by: Damien George <damien@micropython.org>
2024-04-24 16:14:17 +10:00
Damien George 4c3f5f552b webassembly/objjsproxy: Fix handling of thrown value into JS generator.
Signed-off-by: Damien George <damien@micropython.org>
2024-04-24 16:07:00 +10:00
Damien George 9c7f0659e2 webassembly/api: Allocate code data on C heap when running Python code.
Otherwise Emscripten allocates it on the Emscripten C stack, which will
overflow for large amounts of code.

Fixes issue #14307.

Signed-off-by: Damien George <damien@micropython.org>
2024-04-24 13:15:54 +10:00
Damien George 45848f77ca webassembly/api: Fix waiting for Emscripten module to be loaded.
In modularize mode, the `_createMicroPythonModule()` constructor must be
await'ed on, before `Module` is ready to use.

Signed-off-by: Damien George <damien@micropython.org>
2024-04-24 13:15:54 +10:00
Jared Hancock ed7ef9e48c wiznet5k: Fix reported formatting issues.
Signed-off-by: Jared Hancock <jared@greezybacon.me>
2024-04-15 21:57:10 -05:00
Jared Hancock 70190b0c75 wiznet5k: Technically ::send and ::recv is valid in CLOSE_WAIT state.
Signed-off-by: Jared Hancock <jared@greezybacon.me>
2024-04-15 21:42:45 -05:00
Jared Hancock fc03d95493 wiznet5k: dns: Support using dynamic socket number.
Signed-off-by: Jared Hancock <jared@greezybacon.me>
2024-04-15 21:42:36 -05:00
Jared Hancock b96f75cb95 wiznet5k: Support running DHCP in background and renewing address.
Signed-off-by: Jared Hancock <jared@greezybacon.me>
2024-04-15 21:42:32 -05:00
Jared Hancock 35763b3970 wiznet5k: Fix ambiguity with SOCK_BUSY return value.
Signed-off-by: Jared Hancock <jared@greezybacon.me>
2024-04-07 18:03:39 -05:00
Jared Hancock e9de433ecf wiznet5k: Add support for using interrupt without lwIP.
Signed-off-by: Jared Hancock <jared@greezybacon.me>
2024-04-07 18:03:33 -05:00
Jared Hancock c1b0c7eca3 wiznet5k: stm32: Add support for nonblocking and settimeout.
Signed-off-by: Jared Hancock <jared@greezybacon.me>
2024-04-07 18:03:25 -05:00
Jared Hancock 4364ad74ae wiznet5k: Add support for ::send on UDP sockets.
Signed-off-by: Jared Hancock <jared@greezybacon.me>
2024-04-07 18:03:17 -05:00
Jared Hancock 26d0ea4006 wiznet5k: Add support for read timeouts.
Signed-off-by: Jared Hancock <jared@greezybacon.me>
2024-04-07 18:03:09 -05:00
Jared Hancock 2e371503d6 wiznet5k: Add support for nonblocking and settimeout.
Signed-off-by: Jared Hancock <jared@greezybacon.me>
2024-04-07 18:02:38 -05:00
22 zmienionych plików z 826 dodań i 90 usunięć

Wyświetl plik

@ -156,7 +156,7 @@ static MP_DEFINE_CONST_OBJ_TYPE(
// Task class
// This is the core asyncio context with cur_task, _task_queue and CancelledError.
static mp_obj_t asyncio_context = MP_OBJ_NULL;
mp_obj_t mp_asyncio_context = MP_OBJ_NULL;
static mp_obj_t task_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 1, 2, false);
@ -168,7 +168,7 @@ static mp_obj_t task_make_new(const mp_obj_type_t *type, size_t n_args, size_t n
self->state = TASK_STATE_RUNNING_NOT_WAITED_ON;
self->ph_key = MP_OBJ_NEW_SMALL_INT(0);
if (n_args == 2) {
asyncio_context = args[1];
mp_asyncio_context = args[1];
}
return MP_OBJ_FROM_PTR(self);
}
@ -186,7 +186,7 @@ static mp_obj_t task_cancel(mp_obj_t self_in) {
return mp_const_false;
}
// Can't cancel self (not supported yet).
mp_obj_t cur_task = mp_obj_dict_get(asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
mp_obj_t cur_task = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
if (self_in == cur_task) {
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("can't cancel self"));
}
@ -195,7 +195,7 @@ static mp_obj_t task_cancel(mp_obj_t self_in) {
self = MP_OBJ_TO_PTR(self->data);
}
mp_obj_t _task_queue = mp_obj_dict_get(asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR__task_queue));
mp_obj_t _task_queue = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR__task_queue));
// Reschedule Task as a cancelled task.
mp_obj_t dest[3];
@ -218,7 +218,7 @@ static mp_obj_t task_cancel(mp_obj_t self_in) {
task_queue_push(2, dest);
}
self->data = mp_obj_dict_get(asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_CancelledError));
self->data = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_CancelledError));
return mp_const_true;
}
@ -278,7 +278,7 @@ static mp_obj_t task_iternext(mp_obj_t self_in) {
nlr_raise(self->data);
} else {
// Put calling task on waiting queue.
mp_obj_t cur_task = mp_obj_dict_get(asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
mp_obj_t cur_task = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
mp_obj_t args[2] = { self->state, cur_task };
task_queue_push(2, args);
// Set calling task's data to this task that it waits on, to double-link it.

Wyświetl plik

@ -37,6 +37,7 @@
#if MICROPY_PY_NETWORK_WIZNET5K
#include "shared/netutils/netutils.h"
#include "shared/runtime/softtimer.h"
#include "extmod/modnetwork.h"
#include "extmod/modmachine.h"
#include "extmod/virtpin.h"
@ -74,6 +75,14 @@
#include "lib/wiznet5k/Internet/DNS/dns.h"
#include "lib/wiznet5k/Internet/DHCP/dhcp.h"
// Poll WIZnet every 64ms by default (if not using interrupt pin)
#define WIZNET5K_TICK_RATE_MS 64
// In DHCP.c, RIP_MSG max size is defined as 312 + 256 (sadly, not in the header)
#define MAX_DHCP_BUF_SIZE 568
// Soft timer for polling and running DHCP in the background.
static soft_timer_entry_t mp_network_soft_timer;
#endif
#ifndef printf
@ -99,9 +108,9 @@ typedef struct _wiznet5k_obj_t {
void (*spi_transfer)(mp_obj_base_t *obj, size_t len, const uint8_t *src, uint8_t *dest);
mp_hal_pin_obj_t cs;
mp_hal_pin_obj_t rst;
#if WIZNET5K_WITH_LWIP_STACK
mp_hal_pin_obj_t pin_intn;
bool use_interrupt;
#if WIZNET5K_WITH_LWIP_STACK
uint8_t eth_frame[1514];
uint32_t trace_flags;
struct netif netif;
@ -110,9 +119,20 @@ typedef struct _wiznet5k_obj_t {
wiz_NetInfo netinfo;
uint8_t socket_used;
bool active;
uint8_t *dhcp_buf;
uint8_t dhcp_state;
mp_int_t dhcp_socket;
uint32_t dhcp_renew;
#endif
} wiznet5k_obj_t;
#if WIZNET5K_PROVIDED_STACK
typedef struct _wiznet5k_socket_extra_t {
byte remote_ip[4];
mp_uint_t remote_port;
} wiznet5k_socket_extra_t;
#endif
#if WIZNET5K_WITH_LWIP_STACK
#define IS_ACTIVE(self) (self->netif.flags & NETIF_FLAG_UP)
#else // WIZNET5K_PROVIDED_STACK
@ -170,11 +190,7 @@ static void wiznet5k_get_mac_address(wiznet5k_obj_t *self, uint8_t mac[6]) {
getSHAR(mac);
}
#if WIZNET5K_WITH_LWIP_STACK
void wiznet5k_try_poll(void);
static void wiznet5k_lwip_init(wiznet5k_obj_t *self);
static mp_obj_t mpy_wiznet_read_int(mp_obj_t none_in) {
(void)none_in;
// Handle incoming data, unless the SPI bus is busy
@ -197,6 +213,9 @@ static void wiznet5k_config_interrupt(bool enabled) {
);
}
#if WIZNET5K_WITH_LWIP_STACK
static void wiznet5k_lwip_init(wiznet5k_obj_t *self);
void wiznet5k_deinit(void) {
for (struct netif *netif = netif_list; netif != NULL; netif = netif->next) {
if (netif == &wiznet5k_obj.netif) {
@ -350,6 +369,59 @@ void wiznet5k_poll(void) {
#if WIZNET5K_PROVIDED_STACK
static mp_int_t wiznet5k_allocate_socket(void) {
// get first unused socket number
for (mp_uint_t sn = 0; sn < _WIZCHIP_SOCK_NUM_; sn++) {
if ((wiznet5k_obj.socket_used & (1 << sn)) == 0) {
wiznet5k_obj.socket_used |= (1 << sn);
return sn;
}
}
return -1;
}
extern uint8_t DHCP_SOCKET;
static void wiznet5k_dhcp_poll(void) {
if (wiznet5k_obj.dhcp_socket != -1) {
wiznet5k_obj.dhcp_state = DHCP_run();
}
if (wiznet5k_obj.dhcp_state == DHCP_IP_LEASED) {
// Every few seconds, check in about renewing the IP
if (wiznet5k_obj.dhcp_socket != -1) {
// Release socket and reset timeout
wiznet5k_obj.socket_used &= ~(1 << wiznet5k_obj.dhcp_socket);
wiznet5k_obj.dhcp_socket = -1;
// Run about once per minute
wiznet5k_obj.dhcp_renew = mp_hal_ticks_ms() + 60000;
} else if (mp_hal_ticks_ms() > wiznet5k_obj.dhcp_renew) {
mp_uint_t sn = wiznet5k_allocate_socket();
if (sn != -1) {
DHCP_SOCKET = sn;
wiznet5k_obj.dhcp_socket = sn;
wiznet5k_obj.dhcp_state = DHCP_run();
}
}
} else if (wiznet5k_obj.dhcp_state == DHCP_FAILED || wiznet5k_obj.dhcp_state == DHCP_STOPPED) {
if (wiznet5k_obj.dhcp_socket != -1) {
wiznet5k_obj.socket_used &= ~(1 << wiznet5k_obj.dhcp_socket);
wiznet5k_obj.dhcp_socket = -1;
}
}
}
void wiznet5k_try_poll(void) {
// If using DHCP for the interface, periodically renew/update the address
wiznet5k_dhcp_poll();
// There's really nothing to do here. The interrupt that triggered this will
// release a WFE() wait and will trigger a poll() loop which will
// wiznet5k_socket_ioctl will detect the readable or writeable state of the
// respective socket(s).
}
static void wiz_dhcp_assign(void) {
getIPfromDHCP(wiznet5k_obj.netinfo.ip);
getGWfromDHCP(wiznet5k_obj.netinfo.gw);
@ -359,7 +431,7 @@ static void wiz_dhcp_assign(void) {
}
static void wiz_dhcp_update(void) {
;
wiz_dhcp_assign();
}
@ -367,6 +439,22 @@ static void wiz_dhcp_conflict(void) {
;
}
// This is called by soft_timer and executes at PendSV level.
static void mp_network_soft_timer_callback(soft_timer_entry_t *self) {
wiznet5k_try_poll();
}
static void mod_network_wiznet5k_poll_init(void) {
soft_timer_static_init(
&mp_network_soft_timer,
SOFT_TIMER_MODE_PERIODIC,
WIZNET5K_TICK_RATE_MS,
mp_network_soft_timer_callback
);
soft_timer_reinsert(&mp_network_soft_timer, WIZNET5K_TICK_RATE_MS);
}
static void wiznet5k_init(void) {
// Configure wiznet provided TCP / socket interface
@ -388,21 +476,46 @@ static void wiznet5k_init(void) {
};
wiznet5k_obj.netinfo = netinfo;
if (wiznet5k_obj.use_interrupt) {
mp_hal_pin_input(wiznet5k_obj.pin_intn);
wiznet5k_config_interrupt(true);
wizchip_setinterruptmask(IK_SOCK_ALL);
#if _WIZCHIP_ == W5100S
// Enable interrupt pin
setMR2(getMR2() | MR2_G_IEN);
#endif
}
// register with network module
mod_network_register_nic(&wiznet5k_obj);
mod_network_wiznet5k_poll_init();
wiznet5k_obj.active = true;
wiznet5k_obj.dhcp_socket = -1;
wiznet5k_obj.dhcp_state = DHCP_STOPPED;
}
static int wiznet5k_gethostbyname(mp_obj_t nic, const char *name, mp_uint_t len, uint8_t *out_ip) {
uint8_t dns_ip[MOD_NETWORK_IPADDR_BUF_SIZE] = {8, 8, 8, 8};
uint8_t *buf = m_new(uint8_t, MAX_DNS_BUF_SIZE);
DNS_init(2, buf);
mp_int_t sn = wiznet5k_allocate_socket();
if (sn == -1) {
return -2;
}
DNS_init(sn, buf);
if (wiznet5k_obj.netinfo.dns[0]) {
memcpy(dns_ip, wiznet5k_obj.netinfo.dns, MOD_NETWORK_IPADDR_BUF_SIZE);
}
mp_int_t ret = DNS_run(dns_ip, (uint8_t *)name, out_ip);
m_del(uint8_t, buf, MAX_DNS_BUF_SIZE);
// NOTE: DNS_run will close the socket, so it just needs to be marked as
// unused here and clear any interrupts.
wiznet5k_obj.socket_used &= ~(1 << sn);
wizchip_clrinterrupt(IK_SOCK_0 << sn);
if (ret == 1) {
// success
return 0;
@ -432,18 +545,19 @@ static int wiznet5k_socket_socket(mod_network_socket_obj_t *socket, int *_errno)
if (socket->fileno == -1) {
// get first unused socket number
for (mp_uint_t sn = 0; sn < _WIZCHIP_SOCK_NUM_; sn++) {
if ((wiznet5k_obj.socket_used & (1 << sn)) == 0) {
wiznet5k_obj.socket_used |= (1 << sn);
socket->fileno = sn;
break;
}
}
socket->fileno = wiznet5k_allocate_socket();
if (socket->fileno == -1) {
// too many open sockets
*_errno = MP_EMFILE;
return -1;
}
socket->_private = NULL;
// Enable data receive interrupt
if (wiznet5k_obj.use_interrupt) {
setSn_IMR(socket->fileno, (Sn_IR_RECV | Sn_IR_CON | Sn_IR_DISCON));
}
}
// WIZNET does not have a concept of pure "open socket". You need to know
@ -461,12 +575,27 @@ static void wiznet5k_socket_close(mod_network_socket_obj_t *socket) {
if (sn < _WIZCHIP_SOCK_NUM_) {
wiznet5k_obj.socket_used &= ~(1 << sn);
WIZCHIP_EXPORT(close)(sn);
// Disable receive interrupts
if (wiznet5k_obj.use_interrupt) {
setSn_IMR(sn, 0);
}
}
m_del(wiznet5k_socket_extra_t, socket->_private, 1);
socket->_private = NULL;
}
static int wiznet5k_socket_bind(mod_network_socket_obj_t *socket, byte *ip, mp_uint_t port, int *_errno) {
uint8_t flag = 0;
if (socket->timeout == 0) {
// Setup non-blocking mode
flag |= SOCK_IO_NONBLOCK;
}
// open the socket in server mode (if port != 0)
mp_int_t ret = WIZCHIP_EXPORT(socket)(socket->fileno, socket->type, port, 0);
mp_int_t ret = WIZCHIP_EXPORT(socket)(socket->fileno, socket->type, port, flag);
if (ret < 0) {
wiznet5k_socket_close(socket);
*_errno = -ret;
@ -531,22 +660,49 @@ static int wiznet5k_socket_connect(mod_network_socket_obj_t *socket, byte *ip, m
return -1;
}
// now connect
MP_THREAD_GIL_EXIT();
mp_int_t ret = WIZCHIP_EXPORT(connect)(socket->fileno, ip, port);
MP_THREAD_GIL_ENTER();
// WIZnet doesn't support connect on UDP sockets. Stash the remote
// information for use with the ::send method, if used on this UDP socket.
if (socket->type == Sn_MR_TCP) {
// now connect
MP_THREAD_GIL_EXIT();
mp_int_t ret = WIZCHIP_EXPORT(connect)(socket->fileno, ip, port);
MP_THREAD_GIL_ENTER();
if (ret < 0) {
wiznet5k_socket_close(socket);
*_errno = -ret;
return -1;
if (ret < 0) {
wiznet5k_socket_close(socket);
*_errno = -ret;
return -1;
} else if (ret == SOCK_BUSY) {
*_errno = MP_EAGAIN;
return -1;
}
} else if (socket->type == Sn_MR_UDP) {
// For POSIX usage of ::send later, stash the remote IP and port
wiznet5k_socket_extra_t *extra = m_new_maybe(wiznet5k_socket_extra_t, 1);
if (extra == NULL) {
*_errno = MP_ENOMEM;
return -1;
}
memcpy(extra->remote_ip, ip, 4);
extra->remote_port = port;
socket->_private = extra;
}
// success
return 0;
}
static mp_uint_t wiznet5k_socket_sendto(mod_network_socket_obj_t *socket, const byte *buf, mp_uint_t len, byte *ip, mp_uint_t port, int *_errno);
static mp_uint_t wiznet5k_socket_send(mod_network_socket_obj_t *socket, const byte *buf, mp_uint_t len, int *_errno) {
if (socket->type == Sn_MR_UDP) {
if (socket->_private != NULL) {
wiznet5k_socket_extra_t *extra = (wiznet5k_socket_extra_t *)socket->_private;
return wiznet5k_socket_sendto(socket, buf, len, extra->remote_ip, extra->remote_port, _errno);
}
*_errno = MP_ENOTCONN;
return -1;
}
MP_THREAD_GIL_EXIT();
mp_int_t ret = WIZCHIP_EXPORT(send)(socket->fileno, (byte *)buf, len);
MP_THREAD_GIL_ENTER();
@ -556,11 +712,28 @@ static mp_uint_t wiznet5k_socket_send(mod_network_socket_obj_t *socket, const by
wiznet5k_socket_close(socket);
*_errno = -ret;
return -1;
} else if (ret == SOCK_BUSY) {
uint8_t status = getSn_SR(socket->fileno);
if (status == SOCK_ESTABLISHED || status == SOCK_CLOSE_WAIT) {
*_errno = MP_EAGAIN;
return -1;
}
}
return ret;
}
static mp_uint_t wiznet5k_socket_recv(mod_network_socket_obj_t *socket, byte *buf, mp_uint_t len, int *_errno) {
if (socket->timeout > 0) {
mp_uint_t start = mp_hal_ticks_ms();
while (getSn_SR(socket->fileno) == SOCK_ESTABLISHED && getSn_RX_RSR(socket->fileno) == 0) {
if (mp_hal_ticks_ms() - start > socket->timeout) {
*_errno = MP_ETIMEDOUT;
return -1;
}
mp_event_wait_ms(1);
}
}
MP_THREAD_GIL_EXIT();
mp_int_t ret = WIZCHIP_EXPORT(recv)(socket->fileno, buf, len);
MP_THREAD_GIL_ENTER();
@ -570,6 +743,14 @@ static mp_uint_t wiznet5k_socket_recv(mod_network_socket_obj_t *socket, byte *bu
wiznet5k_socket_close(socket);
*_errno = -ret;
return -1;
} else if (ret == SOCK_BUSY) {
// NOTE: SOCK_BUSY is zero (0) which is confusing if the socket is closed
// and at EOF
uint8_t status = getSn_SR(socket->fileno);
if (status == SOCK_ESTABLISHED || status == SOCK_CLOSE_WAIT) {
*_errno = MP_EAGAIN;
return -1;
}
}
return ret;
}
@ -590,12 +771,27 @@ static mp_uint_t wiznet5k_socket_sendto(mod_network_socket_obj_t *socket, const
wiznet5k_socket_close(socket);
*_errno = -ret;
return -1;
} else if (ret == SOCK_BUSY) {
*_errno = MP_EAGAIN;
return -1;
}
return ret;
}
static mp_uint_t wiznet5k_socket_recvfrom(mod_network_socket_obj_t *socket, byte *buf, mp_uint_t len, byte *ip, mp_uint_t *port, int *_errno) {
uint16_t port2;
if (socket->timeout > 0) {
mp_uint_t start = mp_hal_ticks_ms();
while (getSn_SR(socket->fileno) != SOCK_CLOSED && getSn_RX_RSR(socket->fileno) == 0) {
if (mp_hal_ticks_ms() - start > socket->timeout) {
*_errno = MP_ETIMEDOUT;
return -1;
}
mp_event_wait_ms(1);
}
}
MP_THREAD_GIL_EXIT();
mp_int_t ret = WIZCHIP_EXPORT(recvfrom)(socket->fileno, buf, len, ip, &port2);
MP_THREAD_GIL_ENTER();
@ -604,39 +800,78 @@ static mp_uint_t wiznet5k_socket_recvfrom(mod_network_socket_obj_t *socket, byte
wiznet5k_socket_close(socket);
*_errno = -ret;
return -1;
} else if (ret == SOCK_BUSY) {
*_errno = MP_EAGAIN;
return -1;
}
return ret;
}
static int wiznet5k_socket_setsockopt(mod_network_socket_obj_t *socket, mp_uint_t level, mp_uint_t opt, const void *optval, mp_uint_t optlen, int *_errno) {
// TODO
*_errno = MP_EINVAL;
return -1;
switch (opt) {
// level: SOL_SOCKET
case MOD_NETWORK_SO_REUSEADDR:
case MOD_NETWORK_SO_BROADCAST:
// Implied/not-required in Wiznet sockets
break;
default:
*_errno = MP_EINVAL;
return -1;
}
return 0;
}
static int wiznet5k_socket_settimeout(mod_network_socket_obj_t *socket, mp_uint_t timeout_ms, int *_errno) {
// TODO
*_errno = MP_EINVAL;
return -1;
/*
uint8_t arg;
if (timeout_ms == 0) {
// set non-blocking mode
uint8_t arg = SOCK_IO_NONBLOCK;
WIZCHIP_EXPORT(ctlsocket)(socket->fileno, CS_SET_IOMODE, &arg);
arg = SOCK_IO_NONBLOCK;
} else {
arg = SOCK_IO_BLOCK;
}
*/
mp_int_t ret = WIZCHIP_EXPORT(ctlsocket)(socket->fileno, CS_SET_IOMODE, &arg);
if (ret < 0) {
*_errno = -ret;
return -1;
}
socket->timeout = timeout_ms;
return 0;
}
static int wiznet5k_socket_ioctl(mod_network_socket_obj_t *socket, mp_uint_t request, mp_uint_t arg, int *_errno) {
uint8_t sn = (uint8_t)socket->fileno;
if (request == MP_STREAM_POLL) {
int ret = 0;
if (arg & MP_STREAM_POLL_RD && getSn_RX_RSR(socket->fileno) != 0) {
ret |= MP_STREAM_POLL_RD;
if (sn < _WIZCHIP_SOCK_NUM_) {
if (arg & MP_STREAM_POLL_RD && getSn_RX_RSR(sn) != 0) {
ret |= MP_STREAM_POLL_RD;
}
if (arg & MP_STREAM_POLL_WR && getSn_TX_FSR(sn) != 0) {
ret |= MP_STREAM_POLL_WR;
}
uint8_t status = getSn_SR(sn);
if (status == SOCK_CLOSE_WAIT || getSn_IR(sn) & Sn_IR_DISCON) {
// Peer-closed socket is both readable and writable: read will
// return EOF, write - error. Without this poll will hang on a
// socket which was closed by peer.
ret |= arg & (MP_STREAM_POLL_RD | MP_STREAM_POLL_WR);
} else if (status == SOCK_CLOSED) {
ret |= MP_STREAM_POLL_ERR;
}
} else {
ret |= MP_STREAM_POLL_NVAL;
}
if (arg & MP_STREAM_POLL_WR && getSn_TX_FSR(socket->fileno) != 0) {
ret |= MP_STREAM_POLL_WR;
if (wiznet5k_obj.use_interrupt) {
setSn_IR(sn, (Sn_IR_RECV | Sn_IR_CON | Sn_IR_DISCON));
}
return ret;
} else {
*_errno = MP_EINVAL;
@ -645,23 +880,31 @@ static int wiznet5k_socket_ioctl(mod_network_socket_obj_t *socket, mp_uint_t req
}
static void wiznet5k_dhcp_init(wiznet5k_obj_t *self) {
uint8_t test_buf[2048];
uint8_t ret = 0;
uint8_t dhcp_retry = 0;
self->dhcp_state = DHCP_STOPPED;
self->dhcp_buf = m_new_maybe(uint8_t, MAX_DHCP_BUF_SIZE);
if (self->dhcp_buf == NULL) {
return;
}
while (ret != DHCP_IP_LEASED) {
self->dhcp_socket = wiznet5k_allocate_socket();
if (self->dhcp_socket == -1) {
return;
}
while (self->dhcp_state != DHCP_IP_LEASED) {
mp_uint_t timeout = mp_hal_ticks_ms() + 3000;
DHCP_init(1, test_buf);
DHCP_init(self->dhcp_socket, self->dhcp_buf);
while (1) {
ret = DHCP_run();
if (ret == DHCP_IP_LEASED) {
mpy_wiznet_yield();
if (self->dhcp_state == DHCP_IP_LEASED) {
break;
} else if (ret == DHCP_FAILED || mp_hal_ticks_ms() > timeout) {
} else if (self->dhcp_state == DHCP_FAILED || mp_hal_ticks_ms() > timeout) {
dhcp_retry++;
break;
}
mpy_wiznet_yield();
}
if (dhcp_retry > 3) {
@ -670,9 +913,11 @@ static void wiznet5k_dhcp_init(wiznet5k_obj_t *self) {
}
}
if (ret == DHCP_IP_LEASED) {
if (self->dhcp_state == DHCP_IP_LEASED) {
ctlnetwork(CN_GET_NETINFO, &self->netinfo);
}
wizchip_clrinterrupt(IK_SOCK_0 << self->dhcp_socket);
}
#endif // WIZNET5K_PROVIDED_STACK
@ -686,11 +931,10 @@ static mp_obj_t wiznet5k_make_new(const mp_obj_type_t *type, size_t n_args, size
mp_obj_base_t *spi;
mp_hal_pin_obj_t cs;
mp_hal_pin_obj_t rst;
#if WIZNET5K_WITH_LWIP_STACK
mp_hal_pin_obj_t pin_intn = (mp_hal_pin_obj_t)NULL;
bool use_interrupt = false;
#if WIZNET5K_WITH_LWIP_STACK
// Bring down interface while configuring
wiznet5k_obj.netif.flags = 0;
#endif
@ -713,7 +957,7 @@ static mp_obj_t wiznet5k_make_new(const mp_obj_type_t *type, size_t n_args, size
cs = mp_hal_get_pin_obj(mp_pin_make_new(NULL, 1, 0, (mp_obj_t[]) {MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIZNET_PIN_CS)}));
rst = mp_hal_get_pin_obj(mp_pin_make_new(NULL, 1, 0, (mp_obj_t[]) {MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIZNET_PIN_RST)}));
#if WIZNET5K_WITH_LWIP_STACK && defined(MICROPY_HW_WIZNET_PIN_INTN)
#ifdef MICROPY_HW_WIZNET_PIN_INTN
pin_intn = mp_hal_get_pin_obj(mp_pin_make_new(NULL, 1, 0, (mp_obj_t[]) {MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIZNET_PIN_INTN)}));
use_interrupt = true;
#endif
@ -722,20 +966,14 @@ static mp_obj_t wiznet5k_make_new(const mp_obj_type_t *type, size_t n_args, size
#endif
{
// If passing in args, must supply spi, pin_cs, pin_rst and optionally pin_intn
#if WIZNET5K_WITH_LWIP_STACK
mp_arg_check_num(n_args, n_kw, 3, 4, false);
#else
mp_arg_check_num(n_args, n_kw, 3, 3, false);
#endif
spi = mp_hal_get_spi_obj(args[0]);
cs = mp_hal_get_pin_obj(args[1]);
rst = mp_hal_get_pin_obj(args[2]);
#if WIZNET5K_WITH_LWIP_STACK
if (n_args > 3) {
pin_intn = mp_hal_get_pin_obj(args[3]);
use_interrupt = true;
}
#endif
}
mp_hal_pin_output(cs);
@ -748,9 +986,9 @@ static mp_obj_t wiznet5k_make_new(const mp_obj_type_t *type, size_t n_args, size
wiznet5k_obj.spi_transfer = ((mp_machine_spi_p_t *)MP_OBJ_TYPE_GET_SLOT(spi->type, protocol))->transfer;
wiznet5k_obj.cs = cs;
wiznet5k_obj.rst = rst;
#if WIZNET5K_WITH_LWIP_STACK
wiznet5k_obj.pin_intn = pin_intn;
wiznet5k_obj.use_interrupt = use_interrupt;
#if WIZNET5K_WITH_LWIP_STACK
wiznet5k_obj.trace_flags = 0;
#else // WIZNET5K_PROVIDED_STACK
wiznet5k_obj.active = false;
@ -894,6 +1132,11 @@ static mp_obj_t wiznet5k_ifconfig(size_t n_args, const mp_obj_t *args) {
} else {
// Set static IP addresses
self->netinfo.dhcp = NETINFO_STATIC;
self->dhcp_state = DHCP_STOPPED;
if (self->dhcp_buf) {
m_del(uint8_t, self->dhcp_buf, MAX_DHCP_BUF_SIZE);
self->dhcp_buf = NULL;
}
mp_obj_t *items;
mp_obj_get_array_fixed_n(args[1], 4, &items);
netutils_parse_ipv4_addr(items[0], netinfo.ip, NETUTILS_BIG);

Wyświetl plik

@ -59,6 +59,12 @@
// options to control how MicroPython is built
// Due to the use of LTO and the unknown distance between nlr.o and nlrthumb.o code,
// MCUs using the Thumb 1 instruction set must enable this NLR long jump feature.
#if defined(NRF51822)
#define MICROPY_NLR_THUMB_USE_LONG_JUMP (1)
#endif
#ifndef MICROPY_VFS
#define MICROPY_VFS (CORE_FEAT)
#endif

Wyświetl plik

@ -218,6 +218,10 @@ extern const struct _mp_obj_type_t mod_network_nic_type_nina;
#if MICROPY_PY_NETWORK_WIZNET5K
extern const struct _mp_obj_type_t mod_network_nic_type_wiznet5k;
#define MICROPY_HW_NIC_WIZNET5K { MP_ROM_QSTR(MP_QSTR_WIZNET5K), MP_ROM_PTR(&mod_network_nic_type_wiznet5k) },
// This Network interface requires the extended socket state for timeouts and non-blocking
#ifndef MICROPY_PY_SOCKET_EXTENDED_STATE
#define MICROPY_PY_SOCKET_EXTENDED_STATE (1)
#endif
#else
#define MICROPY_HW_NIC_WIZNET5K
#endif

Wyświetl plik

@ -202,6 +202,10 @@ extern const struct _mp_obj_type_t mp_network_cyw43_type;
#if MICROPY_PY_NETWORK_WIZNET5K
extern const struct _mp_obj_type_t mod_network_nic_type_wiznet5k;
#define MICROPY_HW_NIC_WIZNET5K { MP_ROM_QSTR(MP_QSTR_WIZNET5K), MP_ROM_PTR(&mod_network_nic_type_wiznet5k) },
// This Network interface requires the extended socket state for timeouts and non-blocking
#ifndef MICROPY_PY_SOCKET_EXTENDED_STATE
#define MICROPY_PY_SOCKET_EXTENDED_STATE (1)
#endif
#else
#define MICROPY_HW_NIC_WIZNET5K
#endif

Wyświetl plik

@ -22,6 +22,9 @@ BUILD ?= build-$(VARIANT)
include ../../py/mkenv.mk
include $(VARIANT_DIR)/mpconfigvariant.mk
# Use the default frozen manifest, variants may override this.
FROZEN_MANIFEST ?= variants/manifest.py
# Qstr definitions (must come before including py.mk).
QSTR_DEFS = qstrdefsport.h

Wyświetl plik

@ -38,7 +38,7 @@ export async function loadMicroPython(options) {
{ heapsize: 1024 * 1024, linebuffer: true },
options,
);
const Module = {};
let Module = {};
Module.locateFile = (path, scriptDirectory) =>
url || scriptDirectory + path;
Module._textDecoder = new TextDecoder();
@ -83,11 +83,7 @@ export async function loadMicroPython(options) {
Module.stderr = (c) => stderr(new Uint8Array([c]));
}
}
const moduleLoaded = new Promise((r) => {
Module.postRun = r;
});
_createMicroPythonModule(Module);
await moduleLoaded;
Module = await _createMicroPythonModule(Module);
globalThis.Module = Module;
proxy_js_init();
const pyimport = (name) => {
@ -131,23 +127,31 @@ export async function loadMicroPython(options) {
},
pyimport: pyimport,
runPython(code) {
const len = Module.lengthBytesUTF8(code);
const buf = Module._malloc(len + 1);
Module.stringToUTF8(code, buf, len + 1);
const value = Module._malloc(3 * 4);
Module.ccall(
"mp_js_do_exec",
"number",
["string", "pointer"],
[code, value],
["pointer", "number", "pointer"],
[buf, len, value],
);
Module._free(buf);
return proxy_convert_mp_to_js_obj_jsside_with_free(value);
},
runPythonAsync(code) {
const len = Module.lengthBytesUTF8(code);
const buf = Module._malloc(len + 1);
Module.stringToUTF8(code, buf, len + 1);
const value = Module._malloc(3 * 4);
Module.ccall(
"mp_js_do_exec_async",
"number",
["string", "pointer"],
[code, value],
["pointer", "number", "pointer"],
[buf, len, value],
);
Module._free(buf);
return proxy_convert_mp_to_js_obj_jsside_with_free(value);
},
replInit() {
@ -224,6 +228,16 @@ async function runCLI() {
}
});
} else {
// If the script to run ends with a running of the asyncio main loop, then inject
// a simple `asyncio.run` hook that starts the main task. This is primarily to
// support running the standard asyncio tests.
if (contents.endsWith("asyncio.run(main())\n")) {
const asyncio = mp.pyimport("asyncio");
asyncio.run = async (task) => {
await asyncio.create_task(task);
};
}
try {
mp.runPython(contents);
} catch (error) {

Wyświetl plik

@ -0,0 +1,9 @@
# MicroPython asyncio module, for use with webassembly port
# MIT license; Copyright (c) 2024 Damien P. George
from .core import *
from .funcs import wait_for, wait_for_ms, gather
from .event import Event
from .lock import Lock
__version__ = (3, 0, 0)

Wyświetl plik

@ -0,0 +1,249 @@
# MicroPython asyncio module, for use with webassembly port
# MIT license; Copyright (c) 2019-2024 Damien P. George
from time import ticks_ms as ticks, ticks_diff, ticks_add
import sys, js, jsffi
# Import TaskQueue and Task from built-in C code.
from _asyncio import TaskQueue, Task
################################################################################
# Exceptions
class CancelledError(BaseException):
pass
class TimeoutError(Exception):
pass
# Used when calling Loop.call_exception_handler.
_exc_context = {"message": "Task exception wasn't retrieved", "exception": None, "future": None}
################################################################################
# Sleep functions
# "Yield" once, then raise StopIteration
class SingletonGenerator:
def __init__(self):
self.state = None
self.exc = StopIteration()
def __iter__(self):
return self
def __next__(self):
if self.state is not None:
_task_queue.push(cur_task, self.state)
self.state = None
return None
else:
self.exc.__traceback__ = None
raise self.exc
# Pause task execution for the given time (integer in milliseconds, uPy extension)
# Use a SingletonGenerator to do it without allocating on the heap
def sleep_ms(t, sgen=SingletonGenerator()):
if cur_task is None:
# Support top-level asyncio.sleep, via a JavaScript Promise.
return jsffi.async_timeout_ms(t)
assert sgen.state is None
sgen.state = ticks_add(ticks(), max(0, t))
return sgen
# Pause task execution for the given time (in seconds)
def sleep(t):
return sleep_ms(int(t * 1000))
################################################################################
# Main run loop
asyncio_timer = None
class ThenableEvent:
def __init__(self, thenable):
self.result = None # Result of the thenable
self.waiting = None # Task waiting on completion of this thenable
thenable.then(self.set)
def set(self, value):
# Thenable/Promise is fulfilled, set result and schedule any waiting task.
self.result = value
if self.waiting:
_task_queue.push(self.waiting)
self.waiting = None
_schedule_run_iter(0)
def remove(self, task):
self.waiting = None
# async
def wait(self):
# Set the calling task as the task waiting on this thenable.
self.waiting = cur_task
# Set calling task's data to this object so it can be removed if needed.
cur_task.data = self
# Wait for the thenable to fulfill.
yield
# Return the result of the thenable.
return self.result
# Ensure the awaitable is a task
def _promote_to_task(aw):
return aw if isinstance(aw, Task) else create_task(aw)
def _schedule_run_iter(dt):
global asyncio_timer
if asyncio_timer is not None:
js.clearTimeout(asyncio_timer)
asyncio_timer = js.setTimeout(_run_iter, dt)
def _run_iter():
global cur_task
excs_all = (CancelledError, Exception) # To prevent heap allocation in loop
excs_stop = (CancelledError, StopIteration) # To prevent heap allocation in loop
while True:
# Wait until the head of _task_queue is ready to run
t = _task_queue.peek()
if t:
# A task waiting on _task_queue; "ph_key" is time to schedule task at
dt = max(0, ticks_diff(t.ph_key, ticks()))
else:
# No tasks can be woken so finished running
cur_task = None
return
if dt > 0:
# schedule to call again later
cur_task = None
_schedule_run_iter(dt)
return
# Get next task to run and continue it
t = _task_queue.pop()
cur_task = t
try:
# Continue running the coroutine, it's responsible for rescheduling itself
exc = t.data
if not exc:
t.coro.send(None)
else:
# If the task is finished and on the run queue and gets here, then it
# had an exception and was not await'ed on. Throwing into it now will
# raise StopIteration and the code below will catch this and run the
# call_exception_handler function.
t.data = None
t.coro.throw(exc)
except excs_all as er:
# Check the task is not on any event queue
assert t.data is None
# This task is done.
if t.state:
# Task was running but is now finished.
waiting = False
if t.state is True:
# "None" indicates that the task is complete and not await'ed on (yet).
t.state = None
elif callable(t.state):
# The task has a callback registered to be called on completion.
t.state(t, er)
t.state = False
waiting = True
else:
# Schedule any other tasks waiting on the completion of this task.
while t.state.peek():
_task_queue.push(t.state.pop())
waiting = True
# "False" indicates that the task is complete and has been await'ed on.
t.state = False
if not waiting and not isinstance(er, excs_stop):
# An exception ended this detached task, so queue it for later
# execution to handle the uncaught exception if no other task retrieves
# the exception in the meantime (this is handled by Task.throw).
_task_queue.push(t)
# Save return value of coro to pass up to caller.
t.data = er
elif t.state is None:
# Task is already finished and nothing await'ed on the task,
# so call the exception handler.
# Save exception raised by the coro for later use.
t.data = exc
# Create exception context and call the exception handler.
_exc_context["exception"] = exc
_exc_context["future"] = t
Loop.call_exception_handler(_exc_context)
# Create and schedule a new task from a coroutine.
def create_task(coro):
if not hasattr(coro, "send"):
raise TypeError("coroutine expected")
t = Task(coro, globals())
_task_queue.push(t)
_schedule_run_iter(0)
return t
################################################################################
# Event loop wrapper
cur_task = None
class Loop:
_exc_handler = None
def create_task(coro):
return create_task(coro)
def close():
pass
def set_exception_handler(handler):
Loop._exc_handler = handler
def get_exception_handler():
return Loop._exc_handler
def default_exception_handler(loop, context):
print(context["message"], file=sys.stderr)
print("future:", context["future"], "coro=", context["future"].coro, file=sys.stderr)
sys.print_exception(context["exception"], sys.stderr)
def call_exception_handler(context):
(Loop._exc_handler or Loop.default_exception_handler)(Loop, context)
def get_event_loop():
return Loop
def current_task():
if cur_task is None:
raise RuntimeError("no running event loop")
return cur_task
def new_event_loop():
global _task_queue
_task_queue = TaskQueue() # TaskQueue of Task instances.
return Loop
# Initialise default event loop.
new_event_loop()

Wyświetl plik

@ -104,7 +104,7 @@ void mp_js_do_import(const char *name, uint32_t *out) {
}
}
void mp_js_do_exec(const char *src, uint32_t *out) {
void mp_js_do_exec(const char *src, size_t len, uint32_t *out) {
// Collect at the top-level, where there are no root pointers from stack/registers.
gc_collect_start();
gc_collect_end();
@ -112,7 +112,7 @@ void mp_js_do_exec(const char *src, uint32_t *out) {
mp_parse_input_kind_t input_kind = MP_PARSE_FILE_INPUT;
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
mp_lexer_t *lex = mp_lexer_new_from_str_len_dedent(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
mp_lexer_t *lex = mp_lexer_new_from_str_len_dedent(MP_QSTR__lt_stdin_gt_, src, len, 0);
qstr source_name = lex->source_name;
mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false);
@ -125,9 +125,9 @@ void mp_js_do_exec(const char *src, uint32_t *out) {
}
}
void mp_js_do_exec_async(const char *src, uint32_t *out) {
void mp_js_do_exec_async(const char *src, size_t len, uint32_t *out) {
mp_compile_allow_top_level_await = true;
mp_js_do_exec(src, out);
mp_js_do_exec(src, len, out);
mp_compile_allow_top_level_await = false;
}

Wyświetl plik

@ -61,12 +61,27 @@ static mp_obj_t mp_jsffi_to_js(mp_obj_t arg) {
}
static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_to_js_obj, mp_jsffi_to_js);
// *FORMAT-OFF*
EM_JS(void, promise_with_timeout_ms, (double ms, uint32_t * out), {
const ret = new Promise((resolve) => setTimeout(resolve, ms));
proxy_convert_js_to_mp_obj_jsside(ret, out);
});
// *FORMAT-ON*
static mp_obj_t mp_jsffi_async_timeout_ms(mp_obj_t arg) {
uint32_t out[PVN];
promise_with_timeout_ms(mp_obj_get_float_to_d(arg), out);
return proxy_convert_js_to_mp_obj_cside(out);
}
static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_async_timeout_ms_obj, mp_jsffi_async_timeout_ms);
static const mp_rom_map_elem_t mp_module_jsffi_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_jsffi) },
{ MP_ROM_QSTR(MP_QSTR_JsProxy), MP_ROM_PTR(&mp_type_jsproxy) },
{ MP_ROM_QSTR(MP_QSTR_create_proxy), MP_ROM_PTR(&mp_jsffi_create_proxy_obj) },
{ MP_ROM_QSTR(MP_QSTR_to_js), MP_ROM_PTR(&mp_jsffi_to_js_obj) },
{ MP_ROM_QSTR(MP_QSTR_async_timeout_ms), MP_ROM_PTR(&mp_jsffi_async_timeout_ms_obj) },
};
static MP_DEFINE_CONST_DICT(mp_module_jsffi_globals, mp_module_jsffi_globals_table);

Wyświetl plik

@ -346,6 +346,12 @@ typedef struct _jsproxy_gen_t {
mp_vm_return_kind_t jsproxy_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) {
jsproxy_gen_t *self = MP_OBJ_TO_PTR(self_in);
if (throw_value) {
*ret_val = throw_value;
return MP_VM_RETURN_EXCEPTION;
}
switch (self->state) {
case JSOBJ_GEN_STATE_WAITING:
self->state = JSOBJ_GEN_STATE_COMPLETED;
@ -468,9 +474,29 @@ static mp_obj_t jsproxy_new_gen(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) {
/******************************************************************************/
#if MICROPY_PY_ASYNCIO
extern mp_obj_t mp_asyncio_context;
#endif
static mp_obj_t jsproxy_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) {
mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
if (has_attr(self->ref, "then")) {
#if MICROPY_PY_ASYNCIO
// When asyncio is running and the caller here is a task, wrap the JavaScript
// thenable in a ThenableEvent, and get the task to wait on that event. This
// decouples the task from the thenable and allows cancelling the task.
if (mp_asyncio_context != MP_OBJ_NULL) {
mp_obj_t cur_task = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
if (cur_task != mp_const_none) {
mp_obj_t thenable_event_class = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_ThenableEvent));
mp_obj_t thenable_event = mp_call_function_1(thenable_event_class, self_in);
mp_obj_t dest[2];
mp_load_method(thenable_event, MP_QSTR_wait, dest);
mp_obj_t wait_gen = mp_call_method_n_kw(0, 0, dest);
return mp_getiter(wait_gen, iter_buf);
}
}
#endif
return jsproxy_new_gen(self_in, iter_buf);
} else {
return jsproxy_new_it(self_in, iter_buf);

Wyświetl plik

@ -296,10 +296,11 @@ EM_JS(void, js_then_resolve, (uint32_t * ret_value, uint32_t * resolve, uint32_t
resolve_js(ret_value_js);
});
EM_JS(void, js_then_reject, (uint32_t * resolve, uint32_t * reject), {
EM_JS(void, js_then_reject, (uint32_t * ret_value, uint32_t * resolve, uint32_t * reject), {
const ret_value_js = proxy_convert_mp_to_js_obj_jsside(ret_value);
const resolve_js = proxy_convert_mp_to_js_obj_jsside(resolve);
const reject_js = proxy_convert_mp_to_js_obj_jsside(reject);
reject_js(null);
reject_js(ret_value_js);
});
// *FORMAT-OFF*
@ -307,14 +308,34 @@ EM_JS(void, js_then_continue, (int jsref, uint32_t * py_resume, uint32_t * resol
const py_resume_js = proxy_convert_mp_to_js_obj_jsside(py_resume);
const resolve_js = proxy_convert_mp_to_js_obj_jsside(resolve);
const reject_js = proxy_convert_mp_to_js_obj_jsside(reject);
const ret = proxy_js_ref[jsref].then((x) => {py_resume_js(x, resolve_js, reject_js);}, reject_js);
const ret = proxy_js_ref[jsref].then(
(result) => {
// The Promise is fulfilled on the JavaScript side. Take the result and
// send it to the encapsulating generator on the Python side, so it
// becomes the result of the "yield from" that deferred to this Promise.
py_resume_js(result, null, resolve_js, reject_js);
},
(reason) => {
// The Promise is rejected on the JavaScript side. Take the reason and
// throw it into the encapsulating generator on the Python side.
py_resume_js(null, reason, resolve_js, reject_js);
},
);
proxy_convert_js_to_mp_obj_jsside(ret, out);
});
// *FORMAT-ON*
static mp_obj_t proxy_resume_execute(mp_obj_t self_in, mp_obj_t value, mp_obj_t resolve, mp_obj_t reject) {
static mp_obj_t proxy_resume_execute(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t resolve, mp_obj_t reject) {
if (throw_value != MP_OBJ_NULL && throw_value != mp_const_none) {
if (send_value == mp_const_none) {
send_value = MP_OBJ_NULL;
}
} else {
throw_value = MP_OBJ_NULL;
}
mp_obj_t ret_value;
mp_vm_return_kind_t ret_kind = mp_resume(self_in, value, MP_OBJ_NULL, &ret_value);
mp_vm_return_kind_t ret_kind = mp_resume(self_in, send_value, throw_value, &ret_value);
uint32_t out_resolve[PVN];
uint32_t out_reject[PVN];
@ -335,17 +356,19 @@ static mp_obj_t proxy_resume_execute(mp_obj_t self_in, mp_obj_t value, mp_obj_t
uint32_t out[PVN];
js_then_continue(ref, out_py_resume, out_resolve, out_reject, out);
return proxy_convert_js_to_mp_obj_cside(out);
} else {
// MP_VM_RETURN_EXCEPTION;
js_then_reject(out_resolve, out_reject);
nlr_raise(ret_value);
} else { // ret_kind == MP_VM_RETURN_EXCEPTION;
// Pass the exception through as an object to reject the promise (don't raise/throw it).
uint32_t out_ret_value[PVN];
proxy_convert_mp_to_js_obj_cside(ret_value, out_ret_value);
js_then_reject(out_ret_value, out_resolve, out_reject);
return mp_const_none;
}
}
static mp_obj_t resume_fun(size_t n_args, const mp_obj_t *args) {
return proxy_resume_execute(args[0], args[1], args[2], args[3]);
return proxy_resume_execute(args[0], args[1], args[2], args[3], args[4]);
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(resume_obj, 4, 4, resume_fun);
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(resume_obj, 5, 5, resume_fun);
void proxy_c_to_js_resume(uint32_t c_ref, uint32_t *args) {
nlr_buf_t nlr;
@ -353,7 +376,7 @@ void proxy_c_to_js_resume(uint32_t c_ref, uint32_t *args) {
mp_obj_t obj = proxy_c_get_obj(c_ref);
mp_obj_t resolve = proxy_convert_js_to_mp_obj_cside(args + 1 * 3);
mp_obj_t reject = proxy_convert_js_to_mp_obj_cside(args + 2 * 3);
mp_obj_t ret = proxy_resume_execute(obj, mp_const_none, resolve, reject);
mp_obj_t ret = proxy_resume_execute(obj, mp_const_none, mp_const_none, resolve, reject);
nlr_pop();
return proxy_convert_mp_to_js_obj_cside(ret, args);
} else {

Wyświetl plik

@ -0,0 +1,24 @@
# The asyncio package is built from the standard implementation but with the
# core scheduler replaced with a custom scheduler that uses the JavaScript
# runtime (with setTimeout an Promise's) to contrtol the scheduling.
package(
"asyncio",
(
"event.py",
"funcs.py",
"lock.py",
),
base_path="$(MPY_DIR)/extmod",
opt=3,
)
package(
"asyncio",
(
"__init__.py",
"core.py",
),
base_path="$(PORT_DIR)",
opt=3,
)

Wyświetl plik

@ -1,3 +1,5 @@
include("$(PORT_DIR)/variants/manifest.py")
require("abc")
require("base64")
require("collections")

Wyświetl plik

@ -587,6 +587,12 @@
/*****************************************************************************/
/* Python internal features */
// Use a special long jump in nlrthumb.c, which may be necessary if nlr.o and
// nlrthumb.o are linked far apart from each other.
#ifndef MICROPY_NLR_THUMB_USE_LONG_JUMP
#define MICROPY_NLR_THUMB_USE_LONG_JUMP (0)
#endif
// Whether to enable import of external modules
// When disabled, only importing of built-in modules is supported
// When enabled, a port must implement mp_import_stat (among other things)

Wyświetl plik

@ -38,6 +38,14 @@
__attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) {
// If you get a linker error here, indicating that a relocation doesn't
// fit, try the following (in that order):
//
// 1. Ensure that nlr.o nlrthumb.o are linked closely together, i.e.
// there aren't too many other files between them in the linker list
// (PY_CORE_O_BASENAME in py/py.mk)
// 2. Set -DMICROPY_NLR_THUMB_USE_LONG_JUMP=1 during the build
//
__asm volatile (
"str r4, [r0, #12] \n" // store r4 into nlr_buf
"str r5, [r0, #16] \n" // store r5 into nlr_buf
@ -71,7 +79,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) {
"str lr, [r0, #8] \n" // store lr into nlr_buf
#endif
#if !defined(__thumb2__)
#if MICROPY_NLR_THUMB_USE_LONG_JUMP
"ldr r1, nlr_push_tail_var \n"
"bx r1 \n" // do the rest in C
".align 2 \n"

Wyświetl plik

@ -0,0 +1,44 @@
// Test asyncio.create_task(), and tasks waiting on a Promise.
const mp = await (await import(process.argv[2])).loadMicroPython();
globalThis.p0 = new Promise((resolve, reject) => {
resolve(123);
});
globalThis.p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("setTimeout resolved");
resolve(456);
}, 200);
});
mp.runPython(`
import js
import asyncio
async def task(id, promise):
print("task start", id)
print("task await", id, await promise)
print("task await", id, await promise)
print("task end", id)
print("start")
t1 = asyncio.create_task(task(1, js.p0))
t2 = asyncio.create_task(task(2, js.p1))
print("t1", t1.done(), t2.done())
print("end")
`);
// Wait for p1 to fulfill so t2 can continue.
await globalThis.p1;
// Wait a little longer so t2 can complete.
await new Promise((resolve, reject) => {
setTimeout(resolve, 10);
});
mp.runPython(`
print("restart")
print("t1", t1.done(), t2.done())
`);

Wyświetl plik

@ -0,0 +1,14 @@
start
t1 False False
end
task start 1
task start 2
task await 1 123
task await 1 123
task end 1
setTimeout resolved
task await 2 456
task await 2 456
task end 2
restart
t1 True True

Wyświetl plik

@ -0,0 +1,25 @@
// Test asyncio.sleep(), both at the top level and within a task.
const mp = await (await import(process.argv[2])).loadMicroPython();
await mp.runPythonAsync(`
import time
import asyncio
print("main start")
t0 = time.time()
await asyncio.sleep(0.25)
dt = time.time() - t0
print(0.2 <= dt <= 0.3)
async def task():
print("task start")
t0 = time.time()
await asyncio.sleep(0.25)
dt = time.time() - t0
print(0.2 <= dt <= 0.3)
print("task end")
asyncio.create_task(task())
print("main end")
`);

Wyświetl plik

@ -0,0 +1,6 @@
main start
True
main end
task start
True
task end

Wyświetl plik

@ -681,6 +681,17 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
elif args.target == "webassembly":
skip_tests.add("basics/string_format_modulo.py") # can't print nulls to stdout
skip_tests.add("basics/string_strip.py") # can't print nulls to stdout
skip_tests.add("extmod/asyncio_basic2.py")
skip_tests.add("extmod/asyncio_cancel_self.py")
skip_tests.add("extmod/asyncio_current_task.py")
skip_tests.add("extmod/asyncio_exception.py")
skip_tests.add("extmod/asyncio_gather_finished_early.py")
skip_tests.add("extmod/asyncio_get_event_loop.py")
skip_tests.add("extmod/asyncio_heaplock.py")
skip_tests.add("extmod/asyncio_loop_stop.py")
skip_tests.add("extmod/asyncio_new_event_loop.py")
skip_tests.add("extmod/asyncio_threadsafeflag.py")
skip_tests.add("extmod/asyncio_wait_for_fwd.py")
skip_tests.add("extmod/binascii_a2b_base64.py")
skip_tests.add("extmod/re_stack_overflow.py")
skip_tests.add("extmod/time_res.py")