8000 windows/thread: Add support for win32 micropython _thread. · micropython/micropython@694b941 · GitHub
[go: up one dir, main page]

Skip to content

Commit 694b941

Browse files
andrewleechpi-anl
authored andcommitted
windows/thread: Add support for win32 micropython _thread.
1 parent 6f7d6c5 commit 694b941

10 files changed

+325
-20
lines changed

ports/unix/mphalport.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ static inline int mp_hal_readline(vstr_t *vstr, const char *p) {
6464

6565
#endif
6666

67+
#ifndef _WIN32
6768
static inline void mp_hal_delay_us(mp_uint_t us) {
6869
usleep(us);
6970
}
71+
#endif
7072
#define mp_hal_ticks_cpu() 0
7173

7274
// This macro is used to implement PEP 475 to retry specified syscalls on EINTR

ports/unix/unix_mphal.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,12 @@ void mp_hal_delay_ms(mp_uint_t ms) {
225225
#ifdef MICROPY_EVENT_POLL_HOOK
226226
mp_uint_t start = mp_hal_ticks_ms();
227227
while (mp_hal_ticks_ms() - start < ms) {
228-
// MICROPY_EVENT_POLL_HOOK does mp_hal_delay_us(500) (i.e. usleep(500)).
228+
// MICROPY_EVENT_POLL_HOOK does mp_hal_delay_us(500).
229229
MICROPY_EVENT_POLL_HOOK
230230
}
231231
#else
232232
// TODO: POSIX et al. define usleep() as guaranteedly capable only of 1s sleep:
233233
// "The useconds argument shall be less than one million."
234-
usleep(ms * 1000);
234+
mp_hal_delay_us(ms * 1000);
235235
#endif
236236
}

ports/windows/.appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ test_script:
5353
& $env:MICROPY_CPYTHON3 run-tests.py --print-failures
5454
throw "Test failure"
5555
}
56-
& $env:MICROPY_CPYTHON3 run-tests.py --via-mpy -d basics float micropython
56+
& $env:MICROPY_CPYTHON3 run-tests.py --via-mpy -d basics float micropython thread
5757
if ($LASTEXITCODE -ne 0) {
5858
& $env:MICROPY_CPYTHON3 run-tests.py --print-failures
5959
throw "Test failure"

ports/windows/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ SRC_C = \
5454
ports/unix/modtime.c \
5555
ports/unix/gccollect.c \
5656
windows_mphal.c \
57+
mpthreadport.c \
5758
realpath.c \
5859
init.c \
5960
sleep.c \

ports/windows/mpconfigport.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@
139139
#define MICROPY_PY_URANDOM (1)
140140
#define MICROPY_PY_MACHINE (1)
141141
#define MICROPY_PY_MACHINE_PULSE (1)
142+
#ifndef MICROPY_PY_THREAD
143+
#define MICROPY_PY_THREAD (1)
144+
#endif
142145
#define MICROPY_MACHINE_MEM_GET_READ_ADDR mod_machine_mem_get_addr
143146
#define MICROPY_MACHINE_MEM_GET_WRITE_ADDR mod_machine_mem_get_addr
144147

@@ -216,6 +219,11 @@ extern const struct _mp_obj_module_t mp_module_time;
216219

217220
#define MP_STATE_PORT MP_STATE_VM
218221

222+
#if MICROPY_PY_THREAD
223+
#define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_windows_begin_atomic_section(), 0xffffffff)
224+
#define MICROPY_END_ATOMIC_SECTION(x) (void)x; mp_thread_windows_end_atomic_section()
225+
#endif
226+
219227
#define MICROPY_MPHALPORT_H "windows_mphal.h"
220228

221229
#if MICROPY_ENABLE_SCHEDULER

ports/windows/mpthreadport.c

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2021 Andrew Leech
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include <stdio.h>
28+
#include <stdlib.h>
29+
#include <errno.h>
30+
31+
#include "py/runtime.h"
32+
#include "py/mpthread.h"
33+
#include "py/gc.h"
34+
35+
#if MICROPY_PY_THREAD
36+
37+
#include <windows.h>
38+
#include "shared/runtime/gchelper.h"
39+
40+
// This value seems to be about right for both 32-bit and 64-bit builds.
41+
#define THREAD_STACK_OVERFLOW_MARGIN (8192)
42+
43+
// this structure forms a linked list, one node per active thread.
44+
typedef struct _thread_t {
45+
DWORD id; // system id of thread
46+
HANDLE handle; // handle of thread
47+
int ready; // whether the thread is ready and running
48+
void *(*entry)(void *); // entrypoint, to be passed into thread wrapper
49+
void *arg; // thread Python args, a GC root pointer
50+
struct _thread_t *next;
51+
} thread_t;
52+
53+
STATIC DWORD tls_key;
54+
STATIC thread_t *thread;
55+
STATIC HANDLE criticalSection = INVALID_HANDLE_VALUE;
56+
57+
STATIC DWORD wait_for_event(HANDLE event, bool wait) {
58+
// WaitForSingleObjectEx will exit with WAIT_IO_COMPLETION when the thread
59+
// runs an APC function (mp_thread_gc below) so we ignore this and wait
60+
// for a normal event retult.
61+
DWORD ret = WAIT_IO_COMPLETION;
62+
while (WAIT_IO_COMPLETION == ret) {
63+
ret = WaitForSingleObjectEx(event, (wait)?INFINITE:0, TRUE);
64+
}
65+
return ret;
66+
}
67+
68+
void mp_thread_windows_begin_atomic_section(void) {
69+
if (criticalSection != INVALID_HANDLE_VALUE) {
70+
wait_for_event(criticalSection, TRUE);
71+
}
72+
}
73+
74+
void mp_thread_windows_end_atomic_section(void) {
75+
if (criticalSection != INVALID_HANDLE_VALUE) {
76+
ReleaseMutex(criticalSection);
77+
}
78+
}
79+
80+
void mp_thread_init(void) {
81+
tls_key = TlsAlloc();
82+
TlsSetValue(tls_key, (LPVOID)&mp_state_ctx.thread);
83+
84+
// Needs to be a recursive mutex to emulate the behavior of
85+
// BEGIN_ATOMIC_SECTION on bare metal. This is the default
86+
// behavior of win32 Mutex and CriticalSection objects.
87+
criticalSection = CreateMutex(NULL, FALSE, NULL);
88+
89+
// create first entry in linked list of all threads.
90+
thread = malloc(sizeof(thread_t));
91+
thread->id = GetCurrentThreadId();
92+
thread->ready = 1;
93+
thread->arg = NULL;
94+
thread->next = NULL;
95+
96+
// The handle is used for running gc via QueueUserAPC below.
97+
thread->handle = OpenThread(THREAD_SET_CONTEXT, FALSE, thread->id);
98+
}
99+
100+
void mp_thread_deinit(void) {
101+
mp_thread_windows_begin_atomic_section();
102+
while (thread->next != NULL) {
103+
thread_t *th = thread;
104+
thread = thread->next;
105+
TerminateThread(th->handle, -1);
106+
free(th);
107+
}
108+
mp_thread_windows_end_atomic_section();
109+
assert(thread->id == GetCurrentThreadId());
110+
free(thread);
111+
}
112+
113+
// this signal handler is used to scan the regs and stack of a thread.
114+
STATIC void WINAPI mp_thread_gc(ULONG_PTR parameter) {
115+
HANDLE thread_signal_done = (HANDLE)parameter;
116+
gc_helper_collect_regs_and_stack();
117+
#if MICROPY_ENABLE_PYSTACK
118+
void **ptrs = (void **)(void *)MP_STATE_THREAD(pystack_start);
119+
gc_collect_root(ptrs, (MP_STATE_THREAD(pystack_cur) - MP_STATE_THREAD(pystack_start)) / sizeof(void *));
120+
#endif
121+
SetEvent(thread_signal_done);
122+
}
123+
124+
// This function scans all pointers that are external to the current thread.
125+
// It does this by signalling all other threads and getting them to scan their
126+
// own registers and stack. Note that there may still be some edge cases left
127+
// with race conditions and root-pointer scanning: a given thread may manipulate
128+
// the global root pointers (in mp_state_ctx) while another thread is doing a
129+
// garbage collection and tracing these pointers.
130+
void mp_thread_gc_others(void) {
131+
mp_thread_windows_begin_atomic_section();
132+
for (thread_t *th = thread; th != NULL; th = th->next) {
133+
gc_collect_root(&th->arg, 1);
134+
if (th->id == GetCurrentThreadId()) {
135+
continue;
136+
}
137+
if (!th->ready) {
138+
continue;
139+
}
140+
141+
// Used to signal when targetted thread gc is complete.
142+
HANDLE thread_signal_done = CreateEvent(NULL, FALSE, FALSE, NULL);
143+
144+
// Run mp_thread_gc function on target thread (soon).
145+
QueueUserAPC(mp_thread_gc, th->handle, (ULONG_PTR)thread_signal_done);
146+
147+
// wait for it to finish running.
148+
wait_for_event(thread_signal_done, TRUE);
149+
}
150+
mp_thread_windows_end_atomic_section();
151+
}
152+
153+
mp_state_thread_t *mp_thread_get_state(void) {
154+
return (mp_state_thread_t *)TlsGetValue(tls_key);
155+
}
156+
157+
void mp_thread_set_state(mp_state_thread_t *state) {
158+
TlsSetValue(tls_key, (LPVOID)state);
159+
}
160+
161+
void mp_thread_start(void) {
162+
mp_thread_windows_begin_atomic_section();
163+
for (thread_t *th = thread; th != NULL; th = th->next) {
164+
if (th->id == GetCurrentThreadId()) {
165+
th->ready = 1;
166+
break;
167+
}
168+
}
169+
mp_thread_windows_end_atomic_section();
170+
}
171+
172+
STATIC DWORD WINAPI win32_entry(PVOID args) {
173+
// Win32 threads require different function form to posix.
174+
thread_t *th = (thread_t *)args;
175+
th->entry(th->arg);
176+
return 0;
177+
}
178+
179+
void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) {
180+
// default stack size is 8k machine-words.
181+
if (*stack_size == 0) {
182+
*stack_size = 8192 * sizeof(void *);
183+
}
184+
185+
// ensure there is enough stack to include a stack-overflow margin.
186+
if (*stack_size < 2 * THREAD_STACK_OVERFLOW_MARGIN) {
187+
*stack_size = 2 * THREAD_STACK_OVERFLOW_MARGIN;
188+
}
189+
190+
mp_thread_windows_begin_atomic_section();
191+
192+
// create thread
193+
DWORD id;
194+
195+
thread_t *th = malloc(sizeof(thread_t));
196+
th->entry = entry;
197+
th->arg = arg;
198+
199+
HANDLE thread_handle = CreateThread(NULL, *stack_size, win32_entry, th, 0, &id);
200+
201+
if (thread_handle == NULL) {
202+
free(th);
203+
mp_thread_windows_end_atomic_section();
204+
goto er;
205+
}
206+
207+
// adjust stack_size to provide room to recover from hitting the limit.
208+
*stack_size -= THREAD_STACK_OVERFLOW_MARGIN;
209+
210+
// add thread to linked list of all threads.
211+
th->id = id;
212+
th->handle = thread_handle;
213+
th->ready = 0;
214+
th->next = thread;
215+
thread = th;
216+
217+
mp_thread_windows_end_atomic_section();
218+
219+
return;
220+
221+
er:
222+
mp_raise_OSError(-1);
223+
}
224+
225+
void mp_thread_finish(void) {
226+
mp_thread_windows_begin_atomic_section();
227+
thread_t *prev = NULL;
228+
for (thread_t *th = thread; th != NULL; th = th->next) {
229+
if (th->id == GetCurrentThreadId()) {
230+
if (prev == NULL) {
231+
thread = th->next;
232+
} else {
233+
prev->next = th->next;
234+
}
235+
free(th);
236+
break;
237+
}
238+
prev = th;
239+
}
240+
mp_thread_windows_end_atomic_section();
241+
}
242+
243+
void mp_thread_mutex_init(mp_thread_mutex_t *mutex) {
244+
// The win32 Mutex is re-entrant, unlike the posix equivalent.
245+
// To avoid this a semaphore with size of 1 is used.
246+
*mutex = CreateSemaphore(NULL, 1, 1, NULL);
247+
}
248+
249+
int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) {
250+
if (wait_for_event(*mutex, wait) == 0) {
251+
return 1;
252+
} else {
253+
return 0;
254+
}
255+
}
256+
257+
void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) {
258+
ReleaseSemaphore(*mutex, 1, NULL);
259+
}
260+
261+
#endif // MICROPY_PY_THREAD

ports/windows/mpthreadport.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2021 Andrew Leech
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
typedef void *mp_thread_mutex_t; // win32 HANDLE
28+
29+
void mp_thread_init(void);
30+
void mp_thread_deinit(void);
31+
void mp_thread_gc_others(void);
32+
33+
// Windows version of "enable/disable IRQs".
34+
// Functions as a port-global lock for any code that must be serialised.
35+
void mp_thread_windows_begin_atomic_section(void);
36+
void mp_thread_windows_end_atomic_section(void);

0 commit comments

Comments
 (0)
0