[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