[pypy-commit] pypy stacklet: Import stacklets into the main repository.

arigo noreply at buildbot.pypy.org
Sat Aug 6 16:02:04 CEST 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: stacklet
Changeset: r46321:61945d36b794
Date: 2011-08-06 14:11 +0200
http://bitbucket.org/pypy/pypy/changeset/61945d36b794/

Log:	Import stacklets into the main repository.

diff --git a/pypy/rlib/rstacklet.py b/pypy/rlib/rstacklet.py
--- a/pypy/rlib/rstacklet.py
+++ b/pypy/rlib/rstacklet.py
@@ -1,3 +1,5 @@
+import py
+from pypy.tool.autopath import pypydir
 from pypy.rpython.lltypesystem import lltype, rffi
 from pypy.translator.tool.cbuild import ExternalCompilationInfo
 
@@ -10,14 +12,13 @@
 ###
 
 
-#cdir = py.path.local(pypydir) / 'translator' / 'c'
-cdir = '/home/arigo/hg/arigo/hack/pypy-hack/stacklet'    # XXX FIXME
+cdir = py.path.local(pypydir) / 'translator' / 'c'
 
 
 eci = ExternalCompilationInfo(
     include_dirs = [cdir],
-    includes = ['stacklet.h'],
-    separate_module_sources = ['#include "stacklet.c"\n'],
+    includes = ['src/stacklet/stacklet.h'],
+    separate_module_sources = ['#include "src/stacklet/stacklet.c"\n'],
 )
 
 def llexternal(name, args, result):
diff --git a/pypy/translator/c/src/stacklet/Makefile b/pypy/translator/c/src/stacklet/Makefile
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/Makefile
@@ -0,0 +1,49 @@
+
+all: stacklet.so
+
+stacklet.so: stacklet.c stacklet.h
+	gcc -fPIC -shared -O2 -o $@ stacklet.c
+
+stacklet_g.so: stacklet.c stacklet.h
+	gcc -fPIC -shared -g -o $@ stacklet.c -DDEBUG_DUMP
+
+clean:
+	rm -fr stacklet.so stacklet_g.so
+	rm -fr run_tests_*_[go]
+
+
+DEBUG = #-DDEBUG_DUMP
+
+tests: clean
+	make -j1 run-all-tests
+
+ALL_TESTS = tests-static-g \
+            tests-static-o \
+            tests-dynamic-g \
+            tests-dynamic-o
+
+run-all-tests: $(ALL_TESTS)
+	@echo "*** All test suites passed ***"
+
+tests-static-g: stacklet.c stacklet.h tests.c
+	gcc -Wall -g -o run_tests_static_g stacklet.c tests.c ${DEBUG}
+	run_tests_static_g
+
+tests-static-o: stacklet.c stacklet.h tests.c
+	gcc -Wall -g -O2 -o run_tests_static_o stacklet.c tests.c ${DEBUG}
+	run_tests_static_o
+
+tests-dynamic-g: stacklet_g.so tests.c
+	gcc -Wall -g -o run_tests_dynamic_g stacklet_g.so tests.c ${DEBUG}
+	LD_LIBRARY_PATH=. run_tests_dynamic_g
+
+tests-dynamic-o: stacklet.so tests.c
+	gcc -Wall -g -O2 -o run_tests_dynamic_o stacklet.so tests.c ${DEBUG}
+	LD_LIBRARY_PATH=. run_tests_dynamic_o
+
+tests-repeat: tests
+	python runtests.py run_tests_static_g > /dev/null
+	python runtests.py run_tests_static_o > /dev/null
+	LD_LIBRARY_PATH=. python runtests.py run_tests_dynamic_g > /dev/null
+	LD_LIBRARY_PATH=. python runtests.py run_tests_dynamic_o > /dev/null
+	@echo "*** All tests passed repeatedly ***"
diff --git a/pypy/translator/c/src/stacklet/runtests.py b/pypy/translator/c/src/stacklet/runtests.py
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/runtests.py
@@ -0,0 +1,8 @@
+import os, sys
+
+
+
+for i in range(2000):
+    err = os.system("%s %d" % (sys.argv[1], i))
+    if err != 0:
+        raise OSError("return code %r" % (err,))
diff --git a/pypy/translator/c/src/stacklet/slp_platformselect.h b/pypy/translator/c/src/stacklet/slp_platformselect.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/slp_platformselect.h
@@ -0,0 +1,12 @@
+
+#if   defined(_M_IX86)
+#include "switch_x86_msvc.h" /* MS Visual Studio on X86 */
+#elif defined(_M_X64)
+#include "switch_x64_msvc.h" /* MS Visual Studio on X64 */
+#elif defined(__GNUC__) && defined(__amd64__)
+#include "switch_x86_64_gcc.h" /* gcc on amd64 */
+#elif defined(__GNUC__) && defined(__i386__)
+#include "switch_x86_gcc.h" /* gcc on X86 */
+#else
+#error "Unsupported platform!"
+#endif
diff --git a/pypy/translator/c/src/stacklet/stacklet.c b/pypy/translator/c/src/stacklet/stacklet.c
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/stacklet.c
@@ -0,0 +1,301 @@
+/********** A really minimal coroutine package for C **********
+ * By Armin Rigo
+ */
+
+#include "stacklet.h"
+
+#include <stddef.h>
+#include <assert.h>
+#include <string.h>
+
+/************************************************************
+ * platform specific code
+ */
+
+/* The default stack direction is downwards, 0, but platforms
+ * can redefine it to upwards growing, 1.
+ */
+#define STACK_DIRECTION 0   
+
+#include "slp_platformselect.h"
+
+#if STACK_DIRECTION != 0
+#  error "review this whole code, which depends on STACK_DIRECTION==0 so far"
+#endif
+
+/************************************************************/
+
+/* #define DEBUG_DUMP */
+
+#ifdef DEBUG_DUMP
+#include <stdio.h>
+#endif
+
+/************************************************************/
+
+struct stacklet_s {
+    /* The portion of the real stack claimed by this paused tealet. */
+    char *stack_start;                /* the "near" end of the stack */
+    char *stack_stop;                 /* the "far" end of the stack */
+
+    /* The amount that has been saved away so far, just after this struct.
+     * There is enough allocated space for 'stack_stop - stack_start'
+     * bytes.
+     */
+    ptrdiff_t stack_saved;            /* the amount saved */
+
+    /* Internally, some stacklets are arranged in a list, to handle lazy
+     * saving of stacks: if the stacklet has a partially unsaved stack,
+     * this points to the next stacklet with a partially unsaved stack,
+     * creating a linked list with each stacklet's stack_stop higher
+     * than the previous one.  The last entry in the list is always the
+     * main stack.
+     */
+    struct stacklet_s *stack_prev;
+};
+
+void *(*_stacklet_switchstack)(void*(*)(void*, void*),
+                               void*(*)(void*, void*), void*) = NULL;
+void (*_stacklet_initialstub)(struct stacklet_thread_s *,
+                              stacklet_run_fn, void *) = NULL;
+
+struct stacklet_thread_s {
+    struct stacklet_s *g_stack_chain_head;  /* NULL <=> running main */
+    char *g_current_stack_stop;
+    char *g_current_stack_marker;
+    struct stacklet_s *g_source;
+    struct stacklet_s *g_target;
+};
+
+/***************************************************************/
+
+static void g_save(struct stacklet_s* g, char* stop)
+{
+    /* Save more of g's stack into the heap -- at least up to 'stop'
+
+       In the picture below, the C stack is on the left, growing down,
+       and the C heap on the right.  The area marked with xxx is the logical
+       stack of the stacklet 'g'.  It can be half in the C stack (its older
+       part), and half in the heap (its newer part).
+
+       g->stack_stop |________|
+                     |xxxxxxxx|
+                     |xxx __ stop       .........
+                     |xxxxxxxx|    ==>  :       :
+                     |________|         :_______:
+                     |        |         |xxxxxxx|
+                     |        |         |xxxxxxx|
+      g->stack_start |        |         |_______| g+1
+
+     */
+    ptrdiff_t sz1 = g->stack_saved;
+    ptrdiff_t sz2 = stop - g->stack_start;
+    assert(stop <= g->stack_stop);
+
+    if (sz2 > sz1) {
+        char *c = (char *)(g + 1);
+#if STACK_DIRECTION == 0
+        memcpy(c+sz1, g->stack_start+sz1, sz2-sz1);
+#else
+        xxx;
+#endif
+        g->stack_saved = sz2;
+    }
+}
+
+/* Allocate and store in 'g_source' a new stacklet, which has the C
+ * stack from 'old_stack_pointer' to 'g_current_stack_stop'.  It is
+ * initially completely unsaved, so it is attached to the head of the
+ * chained list of 'stack_prev'.
+ */
+static int g_allocate_source_stacklet(void *old_stack_pointer,
+                                      struct stacklet_thread_s *thrd)
+{
+    struct stacklet_s *stacklet;
+    ptrdiff_t stack_size = (thrd->g_current_stack_stop -
+                            (char *)old_stack_pointer);
+
+    thrd->g_source = malloc(sizeof(struct stacklet_s) + stack_size);
+    if (thrd->g_source == NULL)
+        return -1;
+
+    stacklet = thrd->g_source;
+    stacklet->stack_start = old_stack_pointer;
+    stacklet->stack_stop  = thrd->g_current_stack_stop;
+    stacklet->stack_saved = 0;
+    stacklet->stack_prev  = thrd->g_stack_chain_head;
+    thrd->g_stack_chain_head = stacklet;
+    return 0;
+}
+
+/* Save more of the C stack away, up to 'target_stop'.
+ */
+static void g_clear_stack(char *target_stop, struct stacklet_thread_s *thrd)
+{
+    struct stacklet_s *current = thrd->g_stack_chain_head;
+
+    /* save and unlink tealets that are completely within
+       the area to clear. */
+    while (current != NULL && current->stack_stop <= target_stop) {
+        struct stacklet_s *prev = current->stack_prev;
+        current->stack_prev = NULL;
+        g_save(current, current->stack_stop);
+        current = prev;
+    }
+
+    /* save a partial stack */
+    if (current != NULL && current->stack_start < target_stop)
+        g_save(current, target_stop);
+
+    thrd->g_stack_chain_head = current;
+}
+
+/* This saves the current state in a new stacklet that gets stored in
+ * 'g_source', and save away enough of the stack to allow a jump to
+ * 'g_target'.
+ */
+static void *g_save_state(void *old_stack_pointer, void *rawthrd)
+{
+    struct stacklet_thread_s *thrd = (struct stacklet_thread_s *)rawthrd;
+    if (g_allocate_source_stacklet(old_stack_pointer, thrd) < 0)
+        return NULL;
+    g_clear_stack(thrd->g_target->stack_stop, thrd);
+    return thrd->g_target->stack_start;
+}
+
+/* This saves the current state in a new stacklet that gets stored in
+ * 'g_source', but returns NULL, to not do any restoring yet.
+ */
+static void *g_initial_save_state(void *old_stack_pointer, void *rawthrd)
+{
+    struct stacklet_thread_s *thrd = (struct stacklet_thread_s *)rawthrd;
+    if (g_allocate_source_stacklet(old_stack_pointer, thrd) == 0)
+        g_save(thrd->g_source, thrd->g_current_stack_marker);
+    return NULL;
+}
+
+/* Save away enough of the stack to allow a jump to 'g_target'.
+ */
+static void *g_destroy_state(void *old_stack_pointer, void *rawthrd)
+{
+    struct stacklet_thread_s *thrd = (struct stacklet_thread_s *)rawthrd;
+    thrd->g_source = EMPTY_STACKLET_HANDLE;
+    g_clear_stack(thrd->g_target->stack_stop, thrd);
+    return thrd->g_target->stack_start;
+}
+
+/* Restore the C stack by copying back from the heap in 'g_target',
+ * and free 'g_target'.
+ */
+static void *g_restore_state(void *new_stack_pointer, void *rawthrd)
+{
+    /* Restore the heap copy back into the C stack */
+    struct stacklet_thread_s *thrd = (struct stacklet_thread_s *)rawthrd;
+    struct stacklet_s *g = thrd->g_target;
+    ptrdiff_t stack_saved = g->stack_saved;
+
+#if STACK_DIRECTION == 0
+    memcpy(g->stack_start, g+1, stack_saved);
+#else
+    memcpy(g->stack_start - stack_saved, g+1, stack_saved);
+#endif
+    free(g);
+    return EMPTY_STACKLET_HANDLE;
+}
+
+static void g_initialstub(struct stacklet_thread_s *thrd,
+                          stacklet_run_fn run, void *run_arg)
+{
+    struct stacklet_s *result;
+
+    /* The following call returns twice! */
+    result = (struct stacklet_s *) _stacklet_switchstack(g_initial_save_state,
+                                                         g_restore_state,
+                                                         thrd);
+    if (result == NULL && thrd->g_source != NULL) {
+        /* First time it returns.  Only g_initial_save_state() has run
+           and has created 'g_source'.  Call run(). */
+        result = run(thrd->g_source, run_arg);
+
+        /* Then switch to 'result'. */
+        thrd->g_target = result;
+        _stacklet_switchstack(g_destroy_state, g_restore_state, thrd);
+
+        assert(!"stacklet: we should not return here");
+        abort();
+    }
+    /* The second time it returns. */
+}
+
+/************************************************************/
+
+stacklet_thread_handle stacklet_newthread(void)
+{
+    struct stacklet_thread_s *thrd;
+
+    if (_stacklet_switchstack == NULL) {
+        /* set up the following global with an indirection, which is needed
+           to prevent any inlining */
+        _stacklet_initialstub = g_initialstub;
+        _stacklet_switchstack = slp_switch;
+    }
+
+    thrd = malloc(sizeof(struct stacklet_thread_s));
+    if (thrd != NULL)
+        memset(thrd, 0, sizeof(struct stacklet_thread_s));
+    return thrd;
+}
+
+void stacklet_deletethread(stacklet_thread_handle thrd)
+{
+    free(thrd);
+}
+
+stacklet_handle stacklet_new(stacklet_thread_handle thrd,
+                             stacklet_run_fn run, void *run_arg)
+{
+    long stackmarker;
+    assert((char *)NULL < (char *)&stackmarker);
+    if (thrd->g_current_stack_stop < (char *)&stackmarker)
+        thrd->g_current_stack_stop = (char *)&stackmarker;
+
+    thrd->g_current_stack_marker = (char *)&stackmarker;
+    _stacklet_initialstub(thrd, run, run_arg);
+    return thrd->g_source;
+}
+
+stacklet_handle stacklet_switch(stacklet_thread_handle thrd,
+                                stacklet_handle target)
+{
+    long stackmarker;
+    if (thrd->g_current_stack_stop < (char *)&stackmarker)
+        thrd->g_current_stack_stop = (char *)&stackmarker;
+
+    thrd->g_target = target;
+    _stacklet_switchstack(g_save_state, g_restore_state, thrd);
+    return thrd->g_source;
+}
+
+void stacklet_destroy(stacklet_thread_handle thrd, stacklet_handle target)
+{
+    /* remove 'target' from the chained list 'unsaved_stack', if it is there */
+    struct stacklet_s **pp = &thrd->g_stack_chain_head;
+    for (; *pp != NULL; pp = &(*pp)->stack_prev)
+        if (*pp == target) {
+            *pp = target->stack_prev;
+            break;
+        }
+    free(target);
+}
+
+char **_tealet_translate_pointer(stacklet_handle context, char **ptr)
+{
+  char *p = (char *)ptr;
+  long delta = p - context->stack_start;
+  if (((unsigned long)delta) < ((unsigned long)context->stack_saved)) {
+    /* a pointer to a saved away word */
+    char *c = (char *)(context + 1);
+    return (char **)(c + delta);
+  }
+  return ptr;
+}
diff --git a/pypy/translator/c/src/stacklet/stacklet.h b/pypy/translator/c/src/stacklet/stacklet.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/stacklet.h
@@ -0,0 +1,62 @@
+/********** A really minimal coroutine package for C **********/
+#ifndef _STACKLET_H_
+#define _STACKLET_H_
+
+#include <stdlib.h>
+
+
+/* A "stacklet handle" is an opaque pointer to a suspended stack.
+ * Whenever we suspend the current stack in order to switch elsewhere,
+ * stacklet.c passes to the target a 'stacklet_handle' argument that points
+ * to the original stack now suspended.  The handle must later be passed
+ * back to this API once, in order to resume the stack.  It is only
+ * valid once.
+ */
+typedef struct stacklet_s *stacklet_handle;
+
+#define EMPTY_STACKLET_HANDLE  ((stacklet_handle) -1)
+
+
+/* Multithread support.
+ */
+typedef struct stacklet_thread_s *stacklet_thread_handle;
+
+stacklet_thread_handle stacklet_newthread(void);
+void stacklet_deletethread(stacklet_thread_handle thrd);
+
+
+/* The "run" function of a stacklet.  The first argument is the handle
+ * of the stack from where we come.  When such a function returns, it
+ * must return a (non-empty) stacklet_handle that tells where to go next.
+ */
+typedef stacklet_handle (*stacklet_run_fn)(stacklet_handle, void *);
+
+/* Call 'run(source, run_arg)' in a new stack.  See stacklet_switch()
+ * for the return value.
+ */
+stacklet_handle stacklet_new(stacklet_thread_handle thrd,
+                             stacklet_run_fn run, void *run_arg);
+
+/* Switch to the target handle, resuming its stack.  This returns:
+ *  - if we come back from another call to stacklet_switch(), the source handle
+ *  - if we come back from a run() that finishes, EMPTY_STACKLET_HANDLE
+ *  - if we run out of memory, NULL
+ * Don't call this with an already-used target, with EMPTY_STACKLET_HANDLE,
+ * or with a stack handle from another thread (in multithreaded apps).
+ */
+stacklet_handle stacklet_switch(stacklet_thread_handle thrd,
+                                stacklet_handle target);
+
+/* Delete a stack handle without resuming it at all.
+ * (This works even if the stack handle is of a different thread)
+ */
+void stacklet_destroy(stacklet_thread_handle thrd, stacklet_handle target);
+
+/* stacklet_handle _stacklet_switch_to_copy(stacklet_handle) --- later */
+
+/* Hack: translate a pointer into the stack of a stacklet into a pointer
+ * to where it is really stored so far.  Only to access word-sized data.
+ */
+char **_stacklet_translate_pointer(stacklet_handle context, char **ptr);
+
+#endif /* _STACKLET_H_ */
diff --git a/pypy/translator/c/src/stacklet/switch_x64_msvc.asm b/pypy/translator/c/src/stacklet/switch_x64_msvc.asm
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x64_msvc.asm
@@ -0,0 +1,101 @@
+;
+; stack switching code for MASM on x64
+; Kristjan Valur Jonsson, apr 2011
+;
+
+include macamd64.inc
+
+pop_reg MACRO reg
+	pop reg
+ENDM
+
+load_xmm128 macro Reg, Offset
+	movdqa  Reg, Offset[rsp]
+endm
+
+.code
+
+;arguments save_state, restore_state, extra are passed in rcx, rdx, r8 respectively
+;slp_switch PROC FRAME
+NESTED_ENTRY slp_switch, _TEXT$00
+	; save all registers that the x64 ABI specifies as non-volatile.
+	; This includes some mmx registers.  May not always be necessary,
+	; unless our application is doing 3D, but better safe than sorry.
+	alloc_stack 168; 10 * 16 bytes, plus 8 bytes to make stack 16 byte aligned
+	save_xmm128 xmm15, 144
+	save_xmm128 xmm14, 128
+	save_xmm128 xmm13, 112
+	save_xmm128 xmm12, 96
+	save_xmm128 xmm11, 80
+	save_xmm128 xmm10, 64
+	save_xmm128 xmm9,  48
+	save_xmm128 xmm8,  32
+	save_xmm128 xmm7,  16
+	save_xmm128 xmm6,  0
+	
+	push_reg r15
+	push_reg r14
+	push_reg r13
+	push_reg r12
+	
+	push_reg rbp
+	push_reg rbx
+	push_reg rdi
+	push_reg rsi
+	
+	sub rsp, 20h ;allocate shadow stack space for the arguments (must be multiple of 16)
+	.allocstack 20h
+.endprolog
+
+	;save argments in nonvolatile registers
+	mov r12, rcx ;save_state
+	mov r13, rdx
+	mov r14, r8
+
+	; load stack base that we are saving minus the callee argument
+	; shadow stack.  We don't want that clobbered
+	lea rcx, [rsp+20h] 
+	mov rdx, r14
+	call r12 ;pass stackpointer, return new stack pointer in eax
+	
+	; an null value means that we don't restore.
+	test rax, rax
+	jz exit
+	
+	;actual stack switch (and re-allocating the shadow stack):
+	lea rsp, [rax-20h]
+	
+	mov rcx, rax ;pass new stack pointer
+	mov rdx, r14
+	call r13
+	;return the rax
+EXIT:
+	
+	add rsp, 20h
+	pop_reg rsi
+	pop_reg rdi
+	pop_reg rbx
+	pop_reg rbp
+	
+	pop_reg r12
+	pop_reg r13
+	pop_reg r14
+	pop_reg r15
+	
+	load_xmm128 xmm15, 144
+	load_xmm128 xmm14, 128
+	load_xmm128 xmm13, 112
+	load_xmm128 xmm12, 96
+	load_xmm128 xmm11, 80
+	load_xmm128 xmm10, 64
+	load_xmm128 xmm9,  48
+	load_xmm128 xmm8,  32
+	load_xmm128 xmm7,  16
+	load_xmm128 xmm6,  0
+	add rsp, 168
+	ret
+	
+NESTED_END slp_switch, _TEXT$00
+;slp_switch ENDP 
+	
+END
\ No newline at end of file
diff --git a/pypy/translator/c/src/stacklet/switch_x64_msvc.h b/pypy/translator/c/src/stacklet/switch_x64_msvc.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x64_msvc.h
@@ -0,0 +1,7 @@
+/* The actual stack saving function, which just stores the stack,
+ * this declared in an .asm file
+ */
+extern void *slp_switch(void *(*save_state)(void*, void*),
+                        void *(*restore_state)(void*, void*),
+                        void *extra);
+
diff --git a/pypy/translator/c/src/stacklet/switch_x86_64_gcc.h b/pypy/translator/c/src/stacklet/switch_x86_64_gcc.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x86_64_gcc.h
@@ -0,0 +1,55 @@
+
+static void *slp_switch(void *(*save_state)(void*, void*),
+                        void *(*restore_state)(void*, void*),
+                        void *extra)
+{
+  void *result, *garbage1, *garbage2;
+  __asm__ volatile (
+     "pushq %%rbp\n"
+     "pushq %%rbx\n"       /* push the registers specified as caller-save */
+     "pushq %%r12\n"
+     "pushq %%r13\n"
+     "pushq %%r14\n"
+     "pushq %%r15\n"
+
+     "movq %%rax, %%r12\n" /* save 'restore_state' for later */
+     "movq %%rsi, %%r13\n" /* save 'extra' for later         */
+
+                           /* arg 2: extra (already in rsi)      */
+     "movq %%rsp, %%rdi\n" /* arg 1: current (old) stack pointer */
+     "call *%%rcx\n"       /* call save_state()                  */
+
+     "testq %%rax, %%rax\n"    /* skip the rest if the return value is null */
+     "jz 0f\n"
+
+     "movq %%rax, %%rsp\n"     /* change the stack pointer */
+
+     /* From now on, the stack pointer is modified, but the content of the
+        stack is not restored yet.  It contains only garbage here. */
+
+     "movq %%r13, %%rsi\n" /* arg 2: extra                       */
+     "movq %%rax, %%rdi\n" /* arg 1: current (new) stack pointer */
+     "call *%%r12\n"       /* call restore_state()               */
+
+     /* The stack's content is now restored. */
+
+     "0:\n"
+     "popq %%r15\n"
+     "popq %%r14\n"
+     "popq %%r13\n"
+     "popq %%r12\n"
+     "popq %%rbx\n"
+     "popq %%rbp\n"
+
+     : "=a"(result),             /* output variables */
+       "=c"(garbage1),
+       "=S"(garbage2)
+     : "a"(restore_state),       /* input variables  */
+       "c"(save_state),
+       "S"(extra)
+     : "memory", "rdx", "rdi", "r8", "r9", "r10", "r11",
+       "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
+       "xmm8", "xmm9", "xmm10","xmm11","xmm12","xmm13","xmm14","xmm15"
+     );
+  return result;
+}
diff --git a/pypy/translator/c/src/stacklet/switch_x86_gcc.h b/pypy/translator/c/src/stacklet/switch_x86_gcc.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x86_gcc.h
@@ -0,0 +1,56 @@
+
+static void *slp_switch(void *(*save_state)(void*, void*),
+                        void *(*restore_state)(void*, void*),
+                        void *extra)
+{
+  void *result, *garbage1, *garbage2;
+  __asm__ volatile (
+     "pushl %%ebp\n"
+     "pushl %%ebx\n"       /* push some registers that may contain */
+     "pushl %%esi\n"       /* some value that is meant to be saved */
+     "pushl %%edi\n"
+
+     "movl %%eax, %%esi\n" /* save 'restore_state' for later */
+     "movl %%edx, %%edi\n" /* save 'extra' for later         */
+
+     "movl %%esp, %%eax\n"
+
+     "pushl %%edx\n"       /* arg 2: extra                       */
+     "pushl %%eax\n"       /* arg 1: current (old) stack pointer */
+     "call *%%ecx\n"       /* call save_state()                  */
+
+     "testl %%eax, %%eax\n"/* skip the rest if the return value is null */
+     "jz 0f\n"
+
+     "movl %%eax, %%esp\n"     /* change the stack pointer */
+
+     /* From now on, the stack pointer is modified, but the content of the
+        stack is not restored yet.  It contains only garbage here. */
+
+     "pushl %%edi\n"       /* arg 2: extra                       */
+     "pushl %%eax\n"       /* arg 1: current (new) stack pointer */
+     "call *%%esi\n"       /* call restore_state()               */
+
+     /* The stack's content is now restored. */
+
+     "0:\n"
+     "addl $8, %%esp\n"
+     "popl %%edi\n"
+     "popl %%esi\n"
+     "popl %%ebx\n"
+     "popl %%ebp\n"
+
+     : "=a"(result),             /* output variables */
+       "=c"(garbage1),
+       "=d"(garbage2)
+     : "a"(restore_state),       /* input variables  */
+       "c"(save_state),
+       "d"(extra)
+     : "memory"
+     );
+  /* Note: we should also list all fp/xmm registers, but is there a way
+     to list only the ones used by the current compilation target?
+     For now we will just ignore the issue and hope (reasonably) that
+     this function is never inlined all the way into 3rd-party user code. */
+  return result;
+}
diff --git a/pypy/translator/c/src/stacklet/switch_x86_msvc.asm b/pypy/translator/c/src/stacklet/switch_x86_msvc.asm
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x86_msvc.asm
@@ -0,0 +1,44 @@
+
+.386
+.model flat, c
+
+.code
+
+slp_switch_raw PROC save_state:DWORD, restore_state:DWORD, extra:DWORD
+  
+  ;save registers. EAX ECX and EDX are available for function use and thus
+  ;do not have to be stored.
+  push ebx
+  push esi
+  push edi
+  push ebp
+  
+  mov esi, restore_state ; /* save 'restore_state' for later */
+  mov edi, extra ;         /* save 'extra' for later         */
+
+  mov eax, esp
+
+  push edi ;               /* arg 2: extra                       */
+  push eax ;               /* arg 1: current (old) stack pointer */
+  mov  ecx, save_state
+  call ecx ;               /* call save_state()                  */
+
+  test eax, eax;           /* skip the restore if the return value is null */
+  jz exit
+
+  mov esp, eax;            /* change the stack pointer */
+
+  push edi ;               /* arg 2: extra                       */
+  push eax ;               /* arg 1: current (new) stack pointer */
+  call esi ;               /* call restore_state()               */
+
+exit:
+  add esp, 8
+  pop  ebp
+  pop  edi
+  pop  esi
+  pop  ebx
+  ret
+slp_switch_raw ENDP
+
+end
\ No newline at end of file
diff --git a/pypy/translator/c/src/stacklet/switch_x86_msvc.h b/pypy/translator/c/src/stacklet/switch_x86_msvc.h
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/switch_x86_msvc.h
@@ -0,0 +1,26 @@
+/* The actual stack saving function, which just stores the stack,
+ * this declared in an .asm file
+ */
+extern void *slp_switch_raw(void *(*save_state)(void*, void*),
+                        void *(*restore_state)(void*, void*),
+                        void *extra);
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+/* Store any other runtime information on the local stack */
+#pragma optimize("", off) /* so that autos are stored on the stack */
+#pragma warning(disable:4733) /* disable warning about modifying FS[0] */
+
+static void *slp_switch(void *(*save_state)(void*, void*),
+                        void *(*restore_state)(void*, void*),
+                        void *extra)
+{
+    /* store the structured exception state for this stack */
+    DWORD seh_state = __readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList));
+    void * result = slp_switch_raw(save_state, restore_state, extra);
+    __writefsdword(FIELD_OFFSET(NT_TIB, ExceptionList), seh_state);
+    return result;
+}
+#pragma warning(default:4733) /* disable warning about modifying FS[0] */
+#pragma optimize("", on)
diff --git a/pypy/translator/c/src/stacklet/tests.c b/pypy/translator/c/src/stacklet/tests.c
new file mode 100644
--- /dev/null
+++ b/pypy/translator/c/src/stacklet/tests.c
@@ -0,0 +1,647 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <assert.h>
+#include "stacklet.h"
+
+
+static stacklet_thread_handle thrd;
+
+/************************************************************/
+
+stacklet_handle empty_callback(stacklet_handle h, void *arg)
+{
+  assert(arg == (void *)123);
+  return h;
+}
+
+void test_new(void)
+{
+  stacklet_handle h = stacklet_new(thrd, empty_callback, (void *)123);
+  assert(h == EMPTY_STACKLET_HANDLE);
+}
+
+/************************************************************/
+
+static int status;
+
+stacklet_handle switchbackonce_callback(stacklet_handle h, void *arg)
+{
+  assert(arg == (void *)123);
+  assert(status == 0);
+  status = 1;
+  assert(h != EMPTY_STACKLET_HANDLE);
+  h = stacklet_switch(thrd, h);
+  assert(status == 2);
+  assert(h != EMPTY_STACKLET_HANDLE);
+  status = 3;
+  return h;
+}
+
+void test_simple_switch(void)
+{
+  status = 0;
+  stacklet_handle h = stacklet_new(thrd, switchbackonce_callback, (void *)123);
+  assert(h != EMPTY_STACKLET_HANDLE);
+  assert(status == 1);
+  status = 2;
+  h = stacklet_switch(thrd, h);
+  assert(status == 3);
+  assert(h == EMPTY_STACKLET_HANDLE);
+}
+
+/************************************************************/
+
+static stacklet_handle handles[10];
+static int nextstep, comefrom, gointo;
+static const int statusmax = 5000;
+
+int withdepth(int self, float d);
+
+stacklet_handle variousdepths_callback(stacklet_handle h, void *arg)
+{
+  int self, n;
+  assert(nextstep == status);
+  nextstep = -1;
+  self = (ptrdiff_t)arg;
+  assert(self == gointo);
+  assert(0 <= self && self < 10);
+  assert(handles[self] == NULL);
+  assert(0 <= comefrom && comefrom < 10);
+  assert(handles[comefrom] == NULL);
+  assert(h != NULL && h != EMPTY_STACKLET_HANDLE);
+  handles[comefrom] = h;
+  comefrom = -1;
+  gointo = -1;
+
+  while (withdepth(self, rand() % 20) == 0)
+    ;
+
+  assert(handles[self] == NULL);
+
+  do {
+    n = rand() % 10;
+  } while (handles[n] == NULL);
+
+  h = handles[n];
+  assert(h != EMPTY_STACKLET_HANDLE);
+  handles[n] = NULL;
+  comefrom = -42;
+  gointo = n;
+  assert(nextstep == -1);
+  nextstep = ++status;
+  //printf("LEAVING %d to go to %d\n", self, n);
+  return h;
+}
+
+int withdepth(int self, float d)
+{
+  int res = 0;
+  if (d > 0.0)
+    res = withdepth(self, d - 1.1);
+  else
+    {
+      stacklet_handle h;
+      int n = rand() % 10;
+      if (n == self || (status >= statusmax && handles[n] == NULL))
+        return 1;
+
+      //printf("status == %d, self = %d\n", status, self);
+      assert(handles[self] == NULL);
+      assert(nextstep == -1);
+      nextstep = ++status;
+      comefrom = self;
+      gointo = n;
+      if (handles[n] == NULL)
+        {
+          /* start a new stacklet */
+          //printf("new %d\n", n);
+          h = stacklet_new(thrd, variousdepths_callback, (void *)(ptrdiff_t)n);
+        }
+      else
+        {
+          /* switch to this stacklet */
+          //printf("switch to %d\n", n);
+          h = handles[n];
+          handles[n] = NULL;
+          h = stacklet_switch(thrd, h);
+        }
+      //printf("back in self = %d, coming from %d\n", self, comefrom);
+      assert(nextstep == status);
+      nextstep = -1;
+      assert(gointo == self);
+      assert(comefrom != self);
+      assert(handles[self] == NULL);
+      if (comefrom != -42)
+        {
+          assert(0 <= comefrom && comefrom < 10);
+          assert(handles[comefrom] == NULL);
+          handles[comefrom] = h;
+        }
+      else
+        assert(h == EMPTY_STACKLET_HANDLE);
+      comefrom = -1;
+      gointo = -1;
+    }
+  assert((res & (res-1)) == 0);   /* to prevent a tail-call to withdepth() */
+  return res;
+}
+
+int any_alive(void)
+{
+  int i;
+  for (i=0; i<10; i++)
+    if (handles[i] != NULL)
+      return 1;
+  return 0;
+}
+
+void test_various_depths(void)
+{
+  int i;
+  for (i=0; i<10; i++)
+    handles[i] = NULL;
+
+  nextstep = -1;
+  comefrom = -1;
+  status = 0;
+  while (status < statusmax || any_alive())
+    withdepth(0, rand() % 50);
+}
+
+/************************************************************/
+#if 0
+
+static tealet_t *runner1(tealet_t *cur)
+{
+  abort();
+}
+
+void test_new_pending(void)
+{
+  tealet_t *g1 = tealet_new();
+  tealet_t *g2 = tealet_new();
+  int r1 = tealet_fill(g1, runner1);
+  int r2 = tealet_fill(g2, runner1);
+  assert(r1 == TEALET_OK);
+  assert(r2 == TEALET_OK);
+  assert(g1->suspended == 1);
+  assert(g2->suspended == 1);
+  tealet_delete(g1);
+  tealet_delete(g2);
+}
+
+/************************************************************/
+
+void test_not_switched(void)
+{
+  tealet_t *g1 = tealet_new();
+  tealet_t *g2 = tealet_new();
+  tealet_t *g3 = tealet_switch(g2, g1);
+  assert(!TEALET_ERROR(g3));
+  assert(g3 == g1);
+  tealet_delete(g1);
+  tealet_delete(g2);
+}
+
+/************************************************************/
+
+static tealet_t *g_main;
+
+static void step(int newstatus)
+{
+  assert(status == newstatus - 1);
+  status = newstatus;
+}
+
+static tealet_t *simple_run(tealet_t *t1)
+{
+  assert(t1 != g_main);
+  step(2);
+  tealet_delete(t1);
+  return g_main;
+}
+
+void test_simple(void)
+{
+  tealet_t *t1, *tmain;
+  int res;
+
+  status = 0;
+  g_main = tealet_new();
+  t1 = tealet_new();
+  res = tealet_fill(t1, simple_run);
+  assert(res == TEALET_OK);
+  step(1);
+  tmain = tealet_switch(g_main, t1);
+  step(3);
+  assert(tmain == g_main);
+  tealet_delete(g_main);
+  step(4);
+}
+
+/************************************************************/
+
+static tealet_t *simple_exit(tealet_t *t1)
+{
+  int res;
+  assert(t1 != g_main);
+  step(2);
+  tealet_delete(t1);
+  res = tealet_exit_to(g_main);
+  assert(!"oups");
+}
+
+void test_exit(void)
+{
+  tealet_t *t1, *tmain;
+  int res;
+
+  status = 0;
+  g_main = tealet_new();
+  t1 = tealet_new();
+  res = tealet_fill(t1, simple_exit);
+  assert(res == TEALET_OK);
+  step(1);
+  tmain = tealet_switch(g_main, t1);
+  step(3);
+  assert(tmain == g_main);
+  tealet_delete(g_main);
+  step(4);
+}
+
+/************************************************************/
+
+static tealet_t *g_other;
+
+static tealet_t *three_run_1(tealet_t *t1)
+{
+  assert(t1 != g_main);
+  assert(t1 != g_other);
+  step(2);
+  tealet_delete(t1);
+  return g_other;
+}
+
+static tealet_t *three_run_2(tealet_t *t2)
+{
+  assert(t2 == g_other);
+  step(3);
+  tealet_delete(t2);
+  return g_main;
+}
+
+void test_three_tealets(void)
+{
+  tealet_t *t1, *t2, *tmain;
+  int res;
+
+  status = 0;
+  g_main = tealet_new();
+  t1 = tealet_new();
+  t2 = tealet_new();
+  res = tealet_fill(t1, three_run_1);
+  assert(res == TEALET_OK);
+  res = tealet_fill(t2, three_run_2);
+  assert(res == TEALET_OK);
+  step(1);
+  g_other = t2;
+  tmain = tealet_switch(g_main, t1);
+  step(4);
+  assert(tmain == g_main);
+  tealet_delete(g_main);
+  step(5);
+}
+
+/************************************************************/
+
+static tealet_t *glob_t1;
+static tealet_t *glob_t2;
+
+tealet_t *test_switch_2(tealet_t *t2)
+{
+  assert(t2 != g_main);
+  assert(t2 != glob_t1);
+  glob_t2 = t2;
+
+  step(2);
+  t2 = tealet_switch(glob_t2, glob_t1);
+  assert(t2 == glob_t2);
+
+  step(4);
+  assert(glob_t1->suspended == 1);
+  t2 = tealet_switch(glob_t2, glob_t1);
+  assert(t2 == glob_t2);
+
+  step(6);
+  assert(glob_t1->suspended == 0);
+  t2 = tealet_switch(glob_t2, glob_t1);
+  assert(t2 == glob_t1);
+  printf("ok!\n");
+
+  return g_main;
+}
+
+tealet_t *test_switch_1(tealet_t *t1)
+{
+  tealet_t *t2 = tealet_new();
+  assert(t1 != g_main);
+  tealet_fill(t2, test_switch_2);
+  glob_t1 = t1;
+
+  step(1);
+  t1 = tealet_switch(glob_t1, t2);
+  assert(t1 == glob_t1);
+  assert(t2 == glob_t2);
+
+  step(3);
+  t1 = tealet_switch(glob_t1, t2);
+  assert(t1 == glob_t1);
+  assert(t2 == glob_t2);
+
+  step(5);
+  return t2;
+}
+
+void test_switch(void)
+{
+  int res;
+  tealet_t *t, *t2;
+
+  g_main = tealet_new();
+  status = 0;
+  t = tealet_new();
+  res = tealet_fill(t, test_switch_1);
+  assert(res == TEALET_OK);
+  t2 = tealet_switch(g_main, t);
+  assert(!TEALET_ERROR(t2));
+
+  step(7);
+  tealet_delete(g_main);
+  tealet_delete(glob_t1);
+  tealet_delete(glob_t2);
+}
+
+/************************************************************/
+
+#define ARRAYSIZE  127
+#define MAX_STATUS 50000
+
+static tealet_t *tealetarray[ARRAYSIZE] = {NULL};
+static int got_index;
+
+tealet_t *random_new_tealet(tealet_t*);
+
+static void random_run(tealet_t* cur, int index)
+{
+  int i, prevstatus;
+  tealet_t *t, *tres;
+  assert(tealetarray[index] == cur);
+  do
+    {
+      i = rand() % (ARRAYSIZE + 1);
+      status += 1;
+      if (i == ARRAYSIZE)
+        break;
+      prevstatus = status;
+      got_index = i;
+      if (tealetarray[i] == NULL)
+        {
+          if (status >= MAX_STATUS)
+            break;
+          t = tealet_new();
+          tealet_fill(t, random_new_tealet);
+          t->data = (void*)(ptrdiff_t)i;
+        }
+      else
+        {
+          t = tealetarray[i];
+        }
+      tres = tealet_switch(cur, t);
+      assert(tres == cur);
+
+      assert(status >= prevstatus);
+      assert(tealetarray[index] == cur);
+      assert(got_index == index);
+    }
+  while (status < MAX_STATUS);
+}
+
+tealet_t *random_new_tealet(tealet_t* cur)
+{
+  int i = got_index;
+  assert(i == (ptrdiff_t)(cur->data));
+  assert(i > 0 && i < ARRAYSIZE);
+  assert(tealetarray[i] == NULL);
+  tealetarray[i] = cur;
+  random_run(cur, i);
+  tealetarray[i] = NULL;
+  tealet_delete(cur);
+
+  i = rand() % ARRAYSIZE;
+  if (tealetarray[i] == NULL)
+    {
+      assert(tealetarray[0] != NULL);
+      i = 0;
+    }
+  got_index = i;
+  return tealetarray[i];
+}
+
+void test_random(void)
+{
+  int i;
+  g_main = tealet_new();
+  for( i=0; i<ARRAYSIZE; i++)
+      tealetarray[i] = NULL;
+  tealetarray[0] = g_main;
+  status = 0;
+  while (status < MAX_STATUS)
+    random_run(g_main, 0);
+
+  assert(g_main == tealetarray[0]);
+  for (i=1; i<ARRAYSIZE; i++)
+    while (tealetarray[i] != NULL)
+      random_run(g_main, 0);
+
+  tealet_delete(g_main);
+}
+
+/************************************************************/
+
+tealet_t *test_double_run(tealet_t *current)
+{
+  double d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, *numbers;
+  numbers = (double *)current->data;
+  d0 = numbers[0] + 1 / 1.0;
+  d1 = numbers[1] + 1 / 2.0;
+  d2 = numbers[2] + 1 / 4.0;
+  d3 = numbers[3] + 1 / 8.0;
+  d4 = numbers[4] + 1 / 16.0;
+  d5 = numbers[5] + 1 / 32.0;
+  d6 = numbers[6] + 1 / 64.0;
+  d7 = numbers[7] + 1 / 128.0;
+  d8 = numbers[8] + 1 / 256.0;
+  d9 = numbers[9] + 1 / 512.0;
+  numbers[0] = d0;
+  numbers[1] = d1;
+  numbers[2] = d2;
+  numbers[3] = d3;
+  numbers[4] = d4;
+  numbers[5] = d5;
+  numbers[6] = d6;
+  numbers[7] = d7;
+  numbers[8] = d8;
+  numbers[9] = d9;
+  tealet_delete(current);
+  return g_main;
+}
+
+void test_double(void)
+{
+  int i;
+  double d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, numbers[10];
+  g_main = tealet_new();
+
+  d0 = d1 = d2 = d3 = d4 = d5 = d6 = d7 = d8 = d9 = 0.0;
+  for (i=0; i<10; i++)
+    numbers[i] = 0.0;
+
+  for (i=0; i<99; i++)
+    {
+      tealet_t *t = tealet_new();
+      tealet_t *tres;
+      tealet_fill(t, test_double_run);
+      t->data = numbers;
+      tres = tealet_switch(g_main, t);
+      assert(tres == g_main);
+      d0 += numbers[0];
+      d1 += numbers[1];
+      d2 += numbers[2];
+      d3 += numbers[3];
+      d4 += numbers[4];
+      d5 += numbers[5];
+      d6 += numbers[6];
+      d7 += numbers[7];
+      d8 += numbers[8];
+      d9 += numbers[9];
+    }
+
+  assert(d0 == 4950.0 / 1.0);
+  assert(d1 == 4950.0 / 2.0);
+  assert(d2 == 4950.0 / 4.0);
+  assert(d3 == 4950.0 / 8.0);
+  assert(d4 == 4950.0 / 16.0);
+  assert(d5 == 4950.0 / 32.0);
+  assert(d6 == 4950.0 / 64.0);
+  assert(d7 == 4950.0 / 128.0);
+  assert(d8 == 4950.0 / 256.0);
+  assert(d9 == 4950.0 / 512.0);
+  tealet_delete(g_main);
+}
+
+/************************************************************/
+
+static tealet_t *g_main2, *g_sub, *g_sub2;
+
+tealet_t *test_two_mains_green(tealet_t *current)
+{
+  tealet_t *tres;
+  assert(current == g_sub2);
+
+  step(3); printf("3 G: M1 [S1]  M2 [S2]\n");
+  tres = tealet_switch(g_sub, g_main);
+  assert(tres == g_sub);
+
+  step(6); printf("6 G: M1 [S1]  [M2] S2\n");
+  return g_sub2;
+}
+
+tealet_t *test_two_mains_red(tealet_t *current)
+{
+  tealet_t *tres;
+  assert(current == g_sub);
+
+  step(2); printf("2 R: M1 [S1]  [M2] S2\n");
+  tres = tealet_switch(g_main2, g_sub2);
+  assert(tres == g_main2);
+
+  step(5); printf("5 R: [M1] S1  [M2] S2\n");
+  return g_sub;
+}
+
+void test_two_mains(void)
+{
+  int res;
+  tealet_t *tres;
+
+  status = 0;
+  g_main = tealet_new();
+  g_main2 = tealet_new();
+  g_sub = tealet_new();
+  g_sub2 = tealet_new();
+  res = tealet_fill(g_sub, test_two_mains_red);
+  assert(res == TEALET_OK);
+  res = tealet_fill(g_sub2, test_two_mains_green);
+  assert(res == TEALET_OK);
+
+  step(1); printf("1 W: [M1] S1  [M2] S2\n");
+  tres = tealet_switch(g_main, g_sub);
+  assert(tres == g_main);
+
+  step(4); printf("4 W: [M1] S1  M2 [S2]\n");
+  tres = tealet_switch(g_sub2, g_main2);
+  assert(tres == g_sub2);
+
+  step(7); printf("7 W: M1 [S1]  M2 [S2]\n");
+
+  tealet_delete(g_main);
+  tealet_delete(g_main2);
+  tealet_delete(g_sub);
+  tealet_delete(g_sub2);
+}
+#endif
+/************************************************************/
+
+#define TEST(name)   { name, #name }
+
+typedef struct {
+  void (*runtest)(void);
+  const char *name;
+} test_t;
+
+static test_t test_list[] = {
+  TEST(test_new),
+  TEST(test_simple_switch),
+  TEST(test_various_depths),
+#if 0
+  TEST(test_new_pending),
+  TEST(test_not_switched),
+  TEST(test_simple),
+  TEST(test_exit),
+  TEST(test_three_tealets),
+  TEST(test_two_mains),
+  TEST(test_switch),
+  TEST(test_double),
+  TEST(test_random),
+#endif
+  { NULL, NULL }
+};
+
+
+int main(int argc, char **argv)
+{
+  test_t *tst;
+  if (argc > 1)
+    srand(atoi(argv[1]));
+
+  thrd = stacklet_newthread();
+  for (tst=test_list; tst->runtest; tst++)
+    {
+      printf("+++ Running %s... +++\n", tst->name);
+      tst->runtest();
+    }
+  stacklet_deletethread(thrd);
+  printf("+++ All ok. +++\n");
+  return 0;
+}


More information about the pypy-commit mailing list