From c540609e4dc05604421a2b7fa535f451736bec55 Mon Sep 17 00:00:00 2001 From: KirkMcDonald Date: Tue, 15 Aug 2006 01:41:17 +0000 Subject: [PATCH] opApply wrapping and various refactoring git-svn-id: http://svn.dsource.org/projects/pyd/trunk@37 1df65b71-e716-0410-9316-ac55df2b1602 --- dcompiler.py | 87 +- examples/testdll/test.py | 4 + examples/testdll/testdll.d | 8 + infrastructure/pyd/class_wrap.d | 109 +- infrastructure/pyd/ctor_wrap.d | 191 ++- infrastructure/pyd/exception.d | 64 +- infrastructure/pyd/func_wrap.d | 53 +- infrastructure/pyd/iteration.d | 113 ++ infrastructure/pyd/make_object.d | 39 +- infrastructure/pyd/op_wrap.d | 4 +- infrastructure/st/coroutine.d | 651 ++++++++ infrastructure/st/stackcontext.d | 2216 +++++++++++++++++++++++++++ infrastructure/st/stackthread.d | 2456 ++++++++++++++++++++++++++++++ 13 files changed, 5757 insertions(+), 238 deletions(-) create mode 100644 infrastructure/pyd/iteration.d create mode 100644 infrastructure/st/coroutine.d create mode 100644 infrastructure/st/stackcontext.d create mode 100644 infrastructure/st/stackthread.d diff --git a/dcompiler.py b/dcompiler.py index ba34f12..4dd4818 100644 --- a/dcompiler.py +++ b/dcompiler.py @@ -33,12 +33,19 @@ 'exception.d', 'ftype.d', 'func_wrap.d', + 'iteration.d', 'make_object.d', 'op_wrap.d', 'pyd.d', 'tuples.d', ] +_stFiles = [ + 'coroutine.d', + 'stackcontext.d', + 'stackthread.d', +] + _pyVerXDotY = '.'.join(str(v) for v in sys.version_info[:2]) # e.g., '2.4' _pyVerXY = _pyVerXDotY.replace('.', '') # e.g., '24' @@ -124,7 +131,7 @@ def compile(self, sources, binpath = _qp(self._binpath) compileOpts = self._compileOpts - #outputOpts = self._outputOpts + outputOpts = self._outputOpts includePathOpts = [] @@ -149,7 +156,15 @@ def compile(self, sources, " missing." % filePath ) sources.append(filePath) - # Add the pyd directory to the include path + # And StackThreads + for file in _stFiles: + filePath = os.path.join(_infraDir, 'st', file) + if not os.path.isfile(filePath): + raise DistutilsPlatformError("Required StackThreads source" + "file '%s' is missing." % filePath + ) + sources.append(filePath) + # Add the infraDir to the include path for Pyd and ST includePathOpts += self._includeOpts includePathOpts[-1] = includePathOpts[-1] % os.path.join(_infraDir) @@ -198,35 +213,47 @@ def compile(self, sources, else: optimizationOpts = self._defaultOptimizeOpts - #for source in sources: - #outOpts = outputOpts[:] - #objName = self.object_filenames([os.path.split(source)[1]], 0, output_dir)[0] - #outOpts[-1] = outOpts[-1] % _qp(objName) - #cmdElements = ( - # [binpath] + extra_preargs + compileOpts + - # [pythonVersionOpt, self._unicodeOpt] + optimizationOpts + - # includePathOpts + outOpts + userVersionAndDebugOpts + - # [_qp(source)] + extra_postargs - #) - # gdc/gcc doesn't support the idea of an output directory, so we - # compile from the destination - sources = [_qp(os.path.abspath(s)) for s in sources] - cwd = os.getcwd() - os.chdir(output_dir) - cmdElements = ( - [binpath] + extra_preargs + compileOpts + - [pythonVersionOpt, self._unicodeOpt] + optimizationOpts + - includePathOpts + userVersionAndDebugOpts + - sources + extra_postargs - ) - cmdElements = [el for el in cmdElements if el] - - try: - self.spawn(cmdElements) - except DistutilsExecError, msg: + print 'sources: ', [os.path.basename(s) for s in sources] + # Compiling one-by-one exhibits a strange bug in the D front-end, while + # compiling all at once works. This flags allows me to test each form + # easily. Supporting the one-by-one form is synonymous with GDC support. + ONE_BY_ONE = False + if ONE_BY_ONE: + for source in sources: + outOpts = outputOpts[:] + objName = self.object_filenames([os.path.split(source)[1]], 0, output_dir)[0] + outOpts[-1] = outOpts[-1] % _qp(objName) + cmdElements = ( + [binpath] + extra_preargs + compileOpts + + [pythonVersionOpt, self._unicodeOpt] + optimizationOpts + + includePathOpts + outOpts + userVersionAndDebugOpts + + [_qp(source)] + extra_postargs + ) + cmdElements = [el for el in cmdElements if el] + try: + self.spawn(cmdElements) + except DistutilsExecError, msg: + raise CompileError(msg) + else: + # gdc/gcc doesn't support the idea of an output directory, so we + # compile from the destination + sources = [_qp(os.path.abspath(s)) for s in sources] + cwd = os.getcwd() + os.chdir(output_dir) + cmdElements = ( + [binpath] + extra_preargs + compileOpts + + [pythonVersionOpt, self._unicodeOpt] + optimizationOpts + + includePathOpts + userVersionAndDebugOpts + + sources + extra_postargs + ) + cmdElements = [el for el in cmdElements if el] + + try: + self.spawn(cmdElements) + except DistutilsExecError, msg: + #os.chdir(cwd) + raise CompileError(msg) os.chdir(cwd) - raise CompileError(msg) - os.chdir(cwd) return [os.path.join(output_dir, fn) for fn in os.listdir(output_dir) if fn.endswith(self.obj_extension)] diff --git a/examples/testdll/test.py b/examples/testdll/test.py index 4c16dc4..24851a4 100644 --- a/examples/testdll/test.py +++ b/examples/testdll/test.py @@ -30,6 +30,10 @@ a = testdll.Foo(10) a.foo() +print "Testing opApply wrapping:" +for i in a: + print i + print print '--------' diff --git a/examples/testdll/testdll.d b/examples/testdll/testdll.d index 44bf86d..c7488d5 100644 --- a/examples/testdll/testdll.d +++ b/examples/testdll/testdll.d @@ -52,6 +52,14 @@ class Foo { writefln("Foo.foo(): i = %s", m_i); } Foo opAdd(Foo f) { return new Foo(m_i + f.m_i); } + int opApply(int delegate(inout int) dg) { + int result = 0; + for (int i=0; i<10; ++i) { + result = dg(i); + if (result) break; + } + return result; + } int i() { return m_i; } void i(int j) { m_i = j; } } diff --git a/infrastructure/pyd/class_wrap.d b/infrastructure/pyd/class_wrap.d index 351ce0f..f7c70c3 100644 --- a/infrastructure/pyd/class_wrap.d +++ b/infrastructure/pyd/class_wrap.d @@ -25,8 +25,10 @@ private import python; private import pyd.ctor_wrap; private import pyd.def; +private import pyd.exception; private import pyd.ftype; private import pyd.func_wrap; +private import pyd.iteration; private import pyd.make_object; private import pyd.op_wrap; private import pyd.tuples; @@ -110,27 +112,26 @@ template wrapped_methods(T) { /// The generic "__new__" method extern(C) PyObject* wrapped_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { - wrap_object* self; + return exception_catcher(delegate PyObject*() { + wrap_object* self; - self = cast(wrap_object*)type.tp_alloc(type, 0); - if (self !is null) { - self.d_obj = null; - } + self = cast(wrap_object*)type.tp_alloc(type, 0); + if (self !is null) { + self.d_obj = null; + } - return cast(PyObject*)self; + return cast(PyObject*)self; + }); } /// The generic dealloc method. extern(C) - void wrapped_dealloc(PyObject* _self) { - wrap_object* self = cast(wrap_object*)_self; - if (self.d_obj !is null) { - wrap_class_instances!(T)[self.d_obj]--; - if (wrap_class_instances!(T)[self.d_obj] <= 0) { - wrap_class_instances!(T).remove(self.d_obj); - } - } - self.ob_type.tp_free(self); + void wrapped_dealloc(PyObject* self) { + exception_catcher(delegate PyObject*() { + WrapPyObject_SetObj(self, null); + self.ob_type.tp_free(self); + return null; + }); } } @@ -139,9 +140,11 @@ template wrapped_repr(T) { /// The default repr method calls the class's toString. extern(C) PyObject* repr(PyObject* _self) { - wrap_object* self = cast(wrap_object*)_self; - char[] repr = self.d_obj.toString(); - return _py(repr); + return exception_catcher({ + wrap_object* self = cast(wrap_object*)_self; + char[] repr = self.d_obj.toString(); + return _py(repr); + }); } } @@ -151,10 +154,10 @@ template wrapped_init(T) { /// The default _init method calls the class's zero-argument constructor. extern(C) int init(PyObject* self, PyObject* args, PyObject* kwds) { - T t = new T; - (cast(wrap_object*)self).d_obj = t; - wrap_class_instances!(T)[t] = 1; - return 0; + return exception_catcher({ + WrapPyObject_SetObj(self, new T); + return 0; + }); } } @@ -181,6 +184,7 @@ template wrapped_get(T, alias Fn) { /// A generic wrapper around a "getter" property. extern(C) PyObject* func(PyObject* self, void* closure) { + // func_wrap already catches exceptions return func_wrap!(Fn, 0, T, property_parts!(Fn).getter_type).func(self, null); } } @@ -192,12 +196,15 @@ template wrapped_set(T, alias Fn) { int func(PyObject* self, PyObject* value, void* closure) { PyObject* temp_tuple = PyTuple_New(1); if (temp_tuple is null) return -1; + scope(exit) Py_DECREF(temp_tuple); Py_INCREF(value); PyTuple_SetItem(temp_tuple, 0, value); PyObject* res = func_wrap!(Fn, 1, T, property_parts!(Fn).setter_type).func(self, temp_tuple); - // We'll get something back, and need to DECREF it. - Py_DECREF(res); - Py_DECREF(temp_tuple); + // If we get something back, we need to DECREF it. + if (res) Py_DECREF(res); + // If we don't, propagate the exception + else return -1; + // Otherwise, all is well. return 0; } } @@ -340,6 +347,11 @@ void finalize_class(CLS) (CLS cls) { if (wrapped_class_as_number!(T) != PyNumberMethods.init) { type.tp_as_number = &wrapped_class_as_number!(T); } + + static if (is(typeof(&T.opApply))) { + DPySC_Ready(); + type.tp_iter = &wrapped_iter!(T).iter; + } // If a ctor wasn't supplied, try the default. if (type.tp_init is null) { @@ -355,3 +367,50 @@ void finalize_class(CLS) (CLS cls) { is_wrapped!(T) = true; wrapped_classes[typeid(T)] = true; } + +/** + * Returns a new Python object of a wrapped type. + */ +PyObject* WrapPyObject_FromObject(T) (T t) { + alias wrapped_class_object!(T) wrapped_object; + alias wrapped_class_type!(T) type; + if (is_wrapped!(T)) { + // Allocate the object + wrapped_object* obj = + cast(wrapped_object*)type.tp_new(&type, null, null); + // Set the contained instance + WrapPyObject_SetObj(obj, t); + return cast(PyObject*)obj; + } else { + PyErr_SetString(PyExc_RuntimeError, "Type " ~ typeid(T).toString() ~ " is not wrapped by Pyd."); + return null; + } +} + +T WrapPyObject_AsObject(T) (PyObject* _self) { + alias wrapped_class_object!(T) wrapped_object; + alias wrapped_class_type!(T) type; + wrapped_object* self = cast(wrapped_object*)_self; + if (!is_wrapped!(T) || self is null || !PyObject_TypeCheck(_self, &type)) { + // Throw something + } + return self.d_obj; +} + +/** + * Sets the contained object in self to t. + */ +void WrapPyObject_SetObj(PY, T) (PY* _self, T t) { + alias wrapped_class_object!(T) obj; + obj* self = cast(obj*)_self; + // Clean up the old object, if there is one + if (self.d_obj !is null) { + wrap_class_instances!(T)[self.d_obj]--; + if (wrap_class_instances!(T)[self.d_obj] <= 0) { + wrap_class_instances!(T).remove(self.d_obj); + } + } + self.d_obj = t; + // Handle the new one, if there is one + if (t !is null) wrap_class_instances!(T)[t]++; +} diff --git a/infrastructure/pyd/ctor_wrap.d b/infrastructure/pyd/ctor_wrap.d index fdd5a1a..39614be 100644 --- a/infrastructure/pyd/ctor_wrap.d +++ b/infrastructure/pyd/ctor_wrap.d @@ -82,123 +82,102 @@ template wrapped_ctors(T, Tuple) { int init_func(PyObject* self, PyObject* args, PyObject* kwds) { int len = PyObject_Length(args); - try { - - T t; - // Default ctor - static if (is(typeof(new T))) { - if (len == 0) { - t = new T; - goto Done; + return exception_catcher({ + // Default ctor + static if (is(typeof(new T))) { + if (len == 0) { + WrapPyObject_SetObj(self, new T); + return 0; + } } - } - // We only match the first supplied ctor with the proper number of - // arguments. (Eventually, we'll do some more sophisticated matching, - // but this will do for now.) - static if (ARGS >= 1) { - if (len == TypeNo!(Tuple, 0).length) { - // This works thusly: - // 1) outer!(T).call_ctor is a series of template functions - // that call a constructor with its passed arguments, and - // return the new object. - // 2) instant_from_tuple is a template that instantiates a - // template with the types in the passed tuple type. By - // combining call_ctor with the selected tuple representing - // the best match of constructor, we can get something like - // a pointer to a constructor function. - // 3) This function pointer is sent off to py_call, which calls - // it with the PyTuple that args points to. - alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 0) ) fn1; - t = py_call!(typeof(&fn1), PyObject) ( &fn1, args ); - goto Done; + // We only match the first supplied ctor with the proper number of + // arguments. (Eventually, we'll do some more sophisticated matching, + // but this will do for now.) + static if (ARGS >= 1) { + if (len == TypeNo!(Tuple, 0).length) { + // This works thusly: + // 1) outer!(T).call_ctor is a series of template functions + // that call a constructor with its passed arguments, and + // return the new object. + // 2) instant_from_tuple is a template that instantiates a + // template with the types in the passed tuple type. By + // combining call_ctor with the selected tuple representing + // the best match of constructor, we can get something like + // a pointer to a constructor function. + // 3) This function pointer is sent off to py_call, which calls + // it with the PyTuple that args points to. + alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 0) ) fn1; + WrapPyObject_SetObj(self, py_call( &fn1, args )); + return 0; + } } - } - static if (ARGS >= 2) { - if (len == TypeNo!(Tuple, 1).length) { - alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 1) ) fn2; - t = py_call!(typeof(&fn2), PyObject) ( &fn2, args ); - goto Done; + static if (ARGS >= 2) { + if (len == TypeNo!(Tuple, 1).length) { + alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 1) ) fn2; + WrapPyObject_SetObj(self, py_call( &fn2, args )); + return 0; + } } - } - static if (ARGS >= 3) { - if (len == TypeNo!(Tuple, 2).length) { - alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 2) ) fn3; - t = py_call!(typeof(&fn3), PyObject) ( &fn3, args ); - goto Done; + static if (ARGS >= 3) { + if (len == TypeNo!(Tuple, 2).length) { + alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 2) ) fn3; + WrapPyObject_SetObj(self, py_call( &fn3, args )); + return 0; + } } - } - static if (ARGS >= 4) { - if (len == TypeNo!(Tuple, 3).length) { - alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 3) ) fn4; - t = py_call!(typeof(&fn4), PyObject) ( &fn4, args ); - goto Done; + static if (ARGS >= 4) { + if (len == TypeNo!(Tuple, 3).length) { + alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 3) ) fn4; + WrapPyObject_SetObj(self, py_call( &fn4, args )); + return 0; + } } - } - static if (ARGS >= 5) { - if (len == TypeNo!(Tuple, 4).length) { - alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 4) ) fn5; - t = py_call!(typeof(&fn5), PyObject) ( &fn5, args ); - goto Done; + static if (ARGS >= 5) { + if (len == TypeNo!(Tuple, 4).length) { + alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 4) ) fn5; + WrapPyObject_SetObj(self, py_call( &fn5, args )); + return 0; + } } - } - static if (ARGS >= 6) { - if (len == TypeNo!(Tuple, 5).length) { - alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 5) ) fn6; - t = py_call!(typeof(&fn6), PyObject) ( &fn6, args ); - goto Done; + static if (ARGS >= 6) { + if (len == TypeNo!(Tuple, 5).length) { + alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 5) ) fn6; + WrapPyObject_SetObj(self, py_call( &fn6, args )); + return 0; + } } - } - static if (ARGS >= 7) { - if (len == TypeNo!(Tuple, 6).length) { - alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 6) ) fn7; - t = py_call!(typeof(&fn7), PyObject) ( &fn7, args ); - goto Done; + static if (ARGS >= 7) { + if (len == TypeNo!(Tuple, 6).length) { + alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 6) ) fn7; + WrapPyObject_SetObj(self, py_call( &fn7, args )); + return 0; + } } - } - static if (ARGS >= 8) { - if (len == TypeNo!(Tuple, 7).length) { - alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 7) ) fn8; - t = py_call!(typeof(&fn8), PyObject) ( &fn8, args ); - goto Done; + static if (ARGS >= 8) { + if (len == TypeNo!(Tuple, 7).length) { + alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 7) ) fn8; + WrapPyObject_SetObj(self, py_call( &fn8, args )); + return 0; + } } - } - static if (ARGS >= 9) { - if (len == TypeNo!(Tuple, 8).length) { - alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 8) ) fn9; - t = py_call!(typeof(&fn9), PyObject) ( &fn9, args ); - goto Done; + static if (ARGS >= 9) { + if (len == TypeNo!(Tuple, 8).length) { + alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 8) ) fn9; + WrapPyObject_SetObj(self, py_call( &fn9, args )); + return 0; + } } - } - static if (ARGS >= 10) { - if (len == TypeNo!(Tuple, 9).length) { - alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 9) ) fn10; - t = py_call!(typeof(&fn10), PyObject) ( &fn10, args ); - goto Done; + static if (ARGS >= 10) { + if (len == TypeNo!(Tuple, 9).length) { + alias instant_from_tuple!( outer!(T).call_ctor, TypeNo!(Tuple, 9) ) fn10; + WrapPyObject_SetObj(self, py_call( &fn10, args )); + return 0; + } + } else { + PyErr_SetString(PyExc_TypeError, "Unsupported number of constructor arguments."); + return -1; } - } else { - PyErr_SetString(PyExc_TypeError, "Unsupported number of constructor arguments."); - return -1; - } -Done: - (cast(wrapped_class_object!(T)*)self).d_obj = t; - wrap_class_instances!(T)[t]++; - - } /* end try */ - - // A Python exception was raised and duly re-thrown as a D exception. - // It should now be re-raised as a Python exception. - catch (PythonException e) { - PyErr_Restore(e.type(), e.value(), e.traceback()); - return -1; - } - // A D exception was raised and should be translated into a meaningful - // Python exception. - catch (Exception e) { - PyErr_SetString(PyExc_RuntimeError, "D Exception: " ~ e.classinfo.name ~ ": " ~ e.msg ~ \0); - return -1; - } - - return 0; + }); } } diff --git a/infrastructure/pyd/exception.d b/infrastructure/pyd/exception.d index e1b4b74..bcaaa5d 100644 --- a/infrastructure/pyd/exception.d +++ b/infrastructure/pyd/exception.d @@ -28,9 +28,8 @@ private import std.string; * This function first checks if a Python exception is set, and then (if one * is) pulls it out, stuffs it in a PythonException, and throws that exception. * - * If this exception is never caught, it will be handled by the function - * wrapping template and passed right back into Python as though nothing - * happened. + * If this exception is never caught, it will be handled by exception_catcher + * (below) and passed right back into Python as though nothing happened. */ void handle_exception() { PyObject* type, value, traceback; @@ -40,6 +39,65 @@ void handle_exception() { } } +/** + * It is intended that any functions that interface directly with Python which + * have the possibility of a D exception being raised wrap their contents in a + * call to this function, e.g.: + * + *$(D_CODE extern (C) + *PyObject* some_func(PyObject* self) { + * return _exception_catcher({ + * // ... + * }); + *}) + */ +PyObject* exception_catcher(PyObject* delegate() dg) { + try { + return dg(); + } + // A Python exception was raised and duly re-thrown as a D exception. + // It should now be re-raised as a Python exception. + catch (PythonException e) { + PyErr_Restore(e.type(), e.value(), e.traceback()); + return null; + } + // A D exception was raised and should be translated into a meaningful + // Python exception. + catch (Exception e) { + PyErr_SetString(PyExc_RuntimeError, "D Exception: " ~ e.classinfo.name ~ ": " ~ e.msg ~ \0); + return null; + } + // Some other D object was thrown. Deal with it. + catch (Object o) { + PyErr_SetString(PyExc_RuntimeError, "thrown D Object: " ~ o.classinfo.name ~ ": " ~ o.toString() ~ \0); + return null; + } +} + +// XXX: Some way to combine this with the above? +int exception_catcher(int delegate() dg) { + try { + return dg(); + } + // A Python exception was raised and duly re-thrown as a D exception. + // It should now be re-raised as a Python exception. + catch (PythonException e) { + PyErr_Restore(e.type(), e.value(), e.traceback()); + return -1; + } + // A D exception was raised and should be translated into a meaningful + // Python exception. + catch (Exception e) { + PyErr_SetString(PyExc_RuntimeError, "D Exception: " ~ e.classinfo.name ~ ": " ~ e.msg ~ \0); + return -1; + } + // Some other D object was thrown. Deal with it. + catch (Object o) { + PyErr_SetString(PyExc_RuntimeError, "thrown D Object: " ~ o.classinfo.name ~ ": " ~ o.toString() ~ \0); + return -1; + } +} + /** * This simple exception class holds a Python exception. */ diff --git a/infrastructure/pyd/func_wrap.d b/infrastructure/pyd/func_wrap.d index fff7642..4b9c2d9 100644 --- a/infrastructure/pyd/func_wrap.d +++ b/infrastructure/pyd/func_wrap.d @@ -41,7 +41,7 @@ template DPyFunc_FromDG(T, uint MIN_ARGS=NumberOfArgs!(T)) { type.ob_type = PyType_Type_p; type.tp_basicsize = obj.sizeof; type.tp_name = "DPyFunc"; - type.tp_call = &wrapped_func_call!(T/*, MIN_ARGS*/).call; + type.tp_call = &wrapped_func_call!(T).call; PyType_Ready(&type); is_wrapped!(T) = true; wrapped_classes[typeid(T)] = true; @@ -111,9 +111,12 @@ ReturnType!(fn_t) py_call(fn_t, PY)(fn_t fn, PY* args) { } } -template wrapped_func_call(fn_t/*, uint MIN_ARGS*/) { +template wrapped_func_call(fn_t) { const uint MAX_ARGS = NumberOfArgs!(fn_t); alias ReturnType!(fn_t) RetType; + // The entry for the tp_call slot of the DPyFunc types. + // (Or: What gets called when you pass a delegate or function pointer to + // Python.) extern(C) PyObject* call(PyObject* self, PyObject* args, PyObject* kwds) { if (self is null) { @@ -122,31 +125,17 @@ template wrapped_func_call(fn_t/*, uint MIN_ARGS*/) { } fn_t fn = (cast(wrapped_class_object!(fn_t)*)self).d_obj; - PyObject* ret; + //PyObject* ret; - try { + return exception_catcher({ static if (is(RetType == void)) { - py_call/+!(fn_t/*, MIN_ARGS*/)+/(fn, args); + py_call(fn, args); Py_INCREF(Py_None); - ret = Py_None; + return Py_None; } else { - ret = _py( py_call/+!(fn_t/*, MIN_ARGS*/)+/(fn, args) ); + return _py( py_call(fn, args) ); } - } - - // A Python exception was raised and duly re-thrown as a D exception. - // It should now be re-raised as a Python exception. - catch (PythonException e) { - PyErr_Restore(e.type(), e.value(), e.traceback()); - return null; - } - // A D exception was raised and should be translated into a meaningful - // Python exception. - catch (Exception e) { - PyErr_SetString(PyExc_RuntimeError, "D Exception: " ~ e.classinfo.name ~ ": " ~ e.msg ~ \0); - return null; - } - return ret; + }); } } @@ -213,7 +202,9 @@ template func_wrap(alias real_fn, uint MIN_ARGS, C=void, fn_t=typeof(&real_fn)) extern (C) PyObject* func(PyObject* self, PyObject* args) { - try { + // For some reason, D can't infer the return type of this function + // literal... + return exception_catcher(delegate PyObject*() { // If C is specified, then this is a method call. We need to pull out // the object in self and turn the member function pointer in real_fn // into a delegate. This conversion is done with a dirty hack; see @@ -238,21 +229,7 @@ template func_wrap(alias real_fn, uint MIN_ARGS, C=void, fn_t=typeof(&real_fn)) } else { return tuple_py_call(func_range!(real_fn, MIN_ARGS)(), args); } - } - - // A Python exception was raised and duly re-thrown as a D exception. - // It should now be re-raised as a Python exception. - catch (PythonException e) { - PyErr_Restore(e.type(), e.value(), e.traceback()); - return null; - } - // A D exception was raised and should be translated into a meaningful - // Python exception. - catch (Exception e) { - PyErr_SetString(PyExc_RuntimeError, "D Exception: " ~ e.classinfo.name ~ ": " ~ e.msg ~ \0); - return null; - } - //return wrapped_func_call!(typeof(fn), MIN_ARGS).call(cast(PyObject*)&fn_obj, args, null); + }); } } diff --git a/infrastructure/pyd/iteration.d b/infrastructure/pyd/iteration.d new file mode 100644 index 0000000..8815a1b --- /dev/null +++ b/infrastructure/pyd/iteration.d @@ -0,0 +1,113 @@ +/* +Copyright (c) 2006 Kirk McDonald + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * This module provides the support for wrapping opApply with Python's + * iteration interface using Mikola Lysenko's StackThreads module. + */ +module pyd.iteration; + +private import python; +private import pyd.class_wrap; +private import pyd.exception; +private import pyd.make_object; +private import st.stackcontext; + +// This exception is for yielding a PyObject* from within a StackContext. +class DPyYield : Exception { + PyObject* m_py; + this(PyObject* py) { + super(""); + m_py = py; + } + PyObject* item() { return m_py; } +} + +// Creates an iterator object from an object. +PyObject* DPySC_FromWrapped(T) (T obj) { + auto sc = new StackContext(delegate void() { + T t = obj; + foreach (i; t) { + StackContext.throwYield(new DPyYield(_py(i))); + } + }); + return WrapPyObject_FromObject(sc); +} + +template wrapped_iter(T) { + alias wrapped_class_object!(T) wrap_object; + + // Returns an iterator object for this class + extern (C) + PyObject* iter (PyObject* _self) { + return exception_catcher({ + wrap_object* self = cast(wrap_object*)_self; + + return DPySC_FromWrapped(self.d_obj); + }); + } +} + +// Advances an iterator object +extern (C) +PyObject* sc_iternext(PyObject* _self) { + return exception_catcher(delegate PyObject*() { + alias wrapped_class_object!(StackContext) DPySC_object; + DPySC_object* self = cast(DPySC_object*)_self; + + try { + // If the StackContext is done, cease iteration. + if (!self.d_obj.ready()) { + return null; + } + self.d_obj.run(); + } + // The StackContext class yields values by throwing an exception. + // We catch it and pass the converted value into Python. + catch (DPyYield y) { + return y.item(); + } + return null; + }); +} + +/// Readies the iterator class if it hasn't been already. +void DPySC_Ready() { + alias wrapped_class_type!(StackContext) type; + alias wrapped_class_object!(StackContext) DPySC_object; + + if (!is_wrapped!(StackContext)) { + type.ob_type = PyType_Type_p; + type.tp_basicsize = DPySC_object.sizeof; + //type.tp_doc = ""; + type.tp_name = "DPyOpApplyWrapper"; + + type.tp_iter = &PyObject_SelfIter; + type.tp_iternext = &sc_iternext; + + PyType_Ready(&type); + + // Mark the class as ready + is_wrapped!(StackContext) = true; + wrapped_classes[typeid(StackContext)] = true; + } +} diff --git a/infrastructure/pyd/make_object.d b/infrastructure/pyd/make_object.d index baef955..b46eab5 100644 --- a/infrastructure/pyd/make_object.d +++ b/infrastructure/pyd/make_object.d @@ -62,8 +62,6 @@ private template isAA(T) { const bool isAA = is(typeof(T.init.values[0])[typeof(T.init.keys[0])] == T); } -/// -template _py(T) { /** * Returns a new (owned) reference to a Python object based on the passed * argument. If the passed argument is a PyObject*, this "steals" the @@ -74,10 +72,7 @@ template _py(T) { * If the passed argument can't be converted to a PyObject, a Python * RuntimeError will be raised and this function will return null. */ -// I reverted these to the old-style function templates as ddoc can't seem to -// handle the new style. -//PyObject* _py(T) (T t) { -PyObject* _py(T t) { +PyObject* _py(T) (T t) { static if (is(T : bool)) { PyObject* temp = (t) ? Py_True : Py_False; Py_INCREF(temp); @@ -145,18 +140,7 @@ PyObject* _py(T t) { } else static if (is(T == class)) { // Put only if it actually is a wrapped type. :-) if (is_wrapped!(T)) { - // Allocate the object - wrapped_class_object!(T)* obj = - cast(wrapped_class_object!(T)*)wrapped_class_type!(T).tp_new(&wrapped_class_type!(T), null, null); - obj.d_obj = t; - // Add the reference to the class's reference AA to help keep the - // GC happy. - if (t in wrap_class_instances!(T)) { - wrap_class_instances!(T)[t] = wrap_class_instances!(T)[t] + 1; - } else { - wrap_class_instances!(T)[t] = 1; - } - return cast(PyObject*)obj; + return WrapPyObject_FromObject(t); } // If it's not a wrapped type, fall through to the exception. // This just passes the argument right back through without changing @@ -167,10 +151,7 @@ PyObject* _py(T t) { PyErr_SetString(PyExc_RuntimeError, "D conversion function _py failed with type " ~ typeid(T).toString()); return null; } -} /* end template _py */ -/// -template py(T) { /** * Constructs an object based on the type of the argument passed in. * @@ -181,17 +162,13 @@ template py(T) { * * Calling this with a PyObject* will "steal" the reference. */ -// I reverted these to the old-style function templates as ddoc can't seem to -// handle the new style. -//DPyObject py(T) (T t) { -DPyObject py(T t) { +DPyObject py(T) (T t) { static if(is(T : DPyObject)) { return t; } else { return new DPyObject(_py(t)); } } -} /* end template py */ /** * An exception class used by d_type. @@ -200,8 +177,6 @@ class DPyConversionException : Exception { this(char[] msg) { super(msg); } } -/// -template d_type(T) { /** * This converts a PyObject* to a D type. The template argument is the type to * convert to. The function argument is the PyObject* to convert. For instance: @@ -213,10 +188,7 @@ template d_type(T) { * This throws a DPyConversionException if the PyObject can't be converted to * the given D type. */ -// I reverted these to the old-style function templates as ddoc can't seem to -// handle the new style. -//T d_type(T) (PyObject* o) { -T d_type(PyObject* o) { +T d_type(T) (PyObject* o) { // This ordering is very important. If the check for bool came first, // then all integral types would be converted to bools (they would be // 0 or 1), because bool can be implicitly converted to any integral @@ -239,7 +211,7 @@ T d_type(PyObject* o) { // We can only convert to a class if it has been wrapped, and of course // we can only convert the object if it is the wrapped type. if (is_wrapped!(T) && PyObject_TypeCheck(o, &wrapped_class_type!(T))) { - return (cast(wrapped_class_object!(T)*)o).d_obj; + return WrapPyObject_AsObject!(T)(o); } // Otherwise, throw up an exception. could_not_convert!(T)(o); @@ -298,7 +270,6 @@ T d_type(PyObject* o) { could_not_convert!(T)(o); } } -} /* end template d_type */ private void could_not_convert(T) (PyObject* o) { diff --git a/infrastructure/pyd/op_wrap.d b/infrastructure/pyd/op_wrap.d index 44cdaec..1bff954 100644 --- a/infrastructure/pyd/op_wrap.d +++ b/infrastructure/pyd/op_wrap.d @@ -330,8 +330,8 @@ template opCatAssign_wrap(T) { template opIn_wrap(T) { - static if (is(typeof(&T.opIn))) { - const binaryfunc opIn_wrap = &opfunc_binary_wrap!(T, T.opIn).func; + static if (is(typeof(&T.opIn_r))) { + const binaryfunc opIn_wrap = &opfunc_binary_wrap!(T, T.opIn_r).func; } else { const binaryfunc opIn_wrap = null; } diff --git a/infrastructure/st/coroutine.d b/infrastructure/st/coroutine.d new file mode 100644 index 0000000..6dc5320 --- /dev/null +++ b/infrastructure/st/coroutine.d @@ -0,0 +1,651 @@ +/* *** Automatically generated: do not modify *** */ +/** + * This module contains a simple implementation of coroutines, based on the + * StackContext module. It supports both eagerly and non-eagerly evaluating + * coroutines, and coroutines with anywhere from zero to five initial + * arguments. + * + * If you define your coroutine as being both eager, and with an input type of + * void, then the coroutine will be usable as an iterator in foreach + * statements. + * + * Version: 0.1 + * Date: 2006-06-05 + * Copyright: Copyright © 2006 Daniel Keep. + * Authors: Daniel Keep, daniel.keep+spam@gmail.com + * License: zlib + * + * Bugs: + * None (yet). Well, ok; none that I *know* about. + * + * History: + * 0.1 - Initial version. + */ +module st.coroutine; + +/* + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in + * a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ + +private +{ + import st.stackcontext; +} + +/** + * This enumeration defines what kind of coroutine you want. + */ +enum +CoroType +{ + /** + * This is the default. Coroutines evaluate successive values on-demand. + */ + NonEager, + + /** + * Eager coroutines will evaluate the next value in the sequence before + * being asked. This is required for iterator support. + */ + Eager +} + +private +template +CoroutinePublicT(Tin, Tout, CoroType TCoroType) +{ + /// Records what kind of coroutine this is. + const CoroType coroType = TCoroType; + + static if( is( Tin == void ) ) + { + /** + * Resumes the coroutine. + * + * Params: + * value = This value will be passed into the coroutine. + * + * Returns: + * The next value from the coroutine. + */ + final + Tout + opCall() + in + { + static if( coroType == CoroType.Eager ) + assert( this.running ); + else + assert( context.ready ); + } + body + { + static if( coroType == CoroType.Eager ) + { + static if( !is( Tout == void ) ) + Tout temp = this.cout; + + context.run(); + if( context.dead ) + this.running = false; + + static if( !is( Tout == void ) ) + return temp; + } + else + { + context.run(); + static if( !is( Tout == void ) ) + return this.cout; + } + } + } + else + { + /** + * Resumes the coroutine. + * + * Params: + * value = This value will be passed into the coroutine. + * + * Returns: + * The next value from the coroutine. + */ + final + Tout + opCall(Tin value) + in + { + static if( coroType == CoroType.Eager ) + assert( this.running ); + else + assert( context.ready ); + } + body + { + this.cin = value; + + static if( coroType == CoroType.Eager ) + { + static if( !is( Tout == void ) ) + Tout temp = this.cout; + + context.run(); + if( context.dead ) + this.running = false; + + static if( !is( Tout == void ) ) + return temp; + } + else + { + context.run(); + static if( !is( Tout == void ) ) + return this.cout; + } + } + } + + static if( is( Tin == void ) ) + { + /** + * Returns a delegate that can be used to resume the coroutine. + * + * Returns: + * A delegate that is equivalent to calling the coroutine directly. + */ + Tout delegate() + asDelegate() + { + return &opCall; + } + } + else + { + /// ditto + Tout delegate(Tin) + asDelegate() + { + return &opCall; + } + } + + // TODO: Work out how to get iteration working with non-eager coroutines. + static if( coroType == CoroType.Eager ) + { + static if( is( Tin == void ) && !is( Tout == void ) ) + { + final + int + opApply(int delegate(inout Tout) dg) + { + int result = 0; + + while( this.running ) + { + Tout argTemp = opCall(); + result = dg(argTemp); + if( result ) + break; + } + + return result; + } + + final + int + opApply(int delegate(inout Tout, inout uint) dg) + { + int result = 0; + uint counter = 0; + + while( this.running ) + { + Tout argTemp = opCall(); + uint counterTemp = counter; + result = dg(argTemp, counterTemp); + if( result ) + break; + } + + return result; + } + } + } +} + +private +template +CoroutineProtectedT(Tin, Tout, CoroType TCoroType) +{ + size_t STACK_SIZE = DEFAULT_STACK_SIZE; + + static if( is( Tout == void ) ) + { + final + Tin + yield() + in + { + assert( StackContext.getRunning is context ); + } + body + { + StackContext.yield(); + static if( is( Tin == void ) ) {} + else + return this.cin; + } + } + else + { + final + Tin + yield(Tout value) + in + { + assert( StackContext.getRunning is context ); + } + body + { + this.cout = value; + StackContext.yield(); + static if( is( Tin == void ) ) {} + else + return this.cin; + } + } +} + +/** + * TODO + */ +class +Coroutine(Tin, Tout, CoroType TCoroType = CoroType.NonEager) +{ + mixin CoroutinePublicT!(Tin, Tout, TCoroType); + +protected: + mixin CoroutineProtectedT!(Tin, Tout, TCoroType); + + this() + { + + context = new StackContext(&startProc, STACK_SIZE); + static if( coroType == CoroType.Eager ) + context.run(); + } + + abstract + void + run(); + +private: + StackContext context; + + static if( coroType == CoroType.Eager ) + bool running = true; + + static if( !is( Tout == void ) ) + Tout cout; + + static if( !is( Tin == void ) ) + Tin cin; + + + + void + startProc() + { + // Initial call to coroutine proper + run(); + } +} + +/** + * TODO + */ +class +Coroutine(Tin, Tout, Ta1, CoroType TCoroType = CoroType.NonEager) +{ + mixin CoroutinePublicT!(Tin, Tout, TCoroType); + +protected: + mixin CoroutineProtectedT!(Tin, Tout, TCoroType); + + this(Ta1 arg1) + { + this.arg1 = arg1; + context = new StackContext(&startProc, STACK_SIZE); + static if( coroType == CoroType.Eager ) + context.run(); + } + + abstract + void + run(Ta1); + +private: + StackContext context; + + static if( coroType == CoroType.Eager ) + bool running = true; + + static if( !is( Tout == void ) ) + Tout cout; + + static if( !is( Tin == void ) ) + Tin cin; + + Ta1 arg1; + + void + startProc() + { + // Initial call to coroutine proper + run(arg1); + } +} + +/** + * TODO + */ +class +Coroutine(Tin, Tout, Ta1, Ta2, CoroType TCoroType = CoroType.NonEager) +{ + mixin CoroutinePublicT!(Tin, Tout, TCoroType); + +protected: + mixin CoroutineProtectedT!(Tin, Tout, TCoroType); + + this(Ta1 arg1, Ta2 arg2) + { + this.arg1 = arg1; + this.arg2 = arg2; + context = new StackContext(&startProc, STACK_SIZE); + static if( coroType == CoroType.Eager ) + context.run(); + } + + abstract + void + run(Ta1, Ta2); + +private: + StackContext context; + + static if( coroType == CoroType.Eager ) + bool running = true; + + static if( !is( Tout == void ) ) + Tout cout; + + static if( !is( Tin == void ) ) + Tin cin; + + Ta1 arg1; + Ta2 arg2; + + void + startProc() + { + // Initial call to coroutine proper + run(arg1, arg2); + } +} + +/** + * TODO + */ +class +Coroutine(Tin, Tout, Ta1, Ta2, Ta3, CoroType TCoroType = CoroType.NonEager) +{ + mixin CoroutinePublicT!(Tin, Tout, TCoroType); + +protected: + mixin CoroutineProtectedT!(Tin, Tout, TCoroType); + + this(Ta1 arg1, Ta2 arg2, Ta3 arg3) + { + this.arg1 = arg1; + this.arg2 = arg2; + this.arg3 = arg3; + context = new StackContext(&startProc, STACK_SIZE); + static if( coroType == CoroType.Eager ) + context.run(); + } + + abstract + void + run(Ta1, Ta2, Ta3); + +private: + StackContext context; + + static if( coroType == CoroType.Eager ) + bool running = true; + + static if( !is( Tout == void ) ) + Tout cout; + + static if( !is( Tin == void ) ) + Tin cin; + + Ta1 arg1; + Ta2 arg2; + Ta3 arg3; + + void + startProc() + { + // Initial call to coroutine proper + run(arg1, arg2, arg3); + } +} + +/** + * TODO + */ +class +Coroutine(Tin, Tout, Ta1, Ta2, Ta3, Ta4, CoroType TCoroType = CoroType.NonEager) +{ + mixin CoroutinePublicT!(Tin, Tout, TCoroType); + +protected: + mixin CoroutineProtectedT!(Tin, Tout, TCoroType); + + this(Ta1 arg1, Ta2 arg2, Ta3 arg3, Ta4 arg4) + { + this.arg1 = arg1; + this.arg2 = arg2; + this.arg3 = arg3; + this.arg4 = arg4; + context = new StackContext(&startProc, STACK_SIZE); + static if( coroType == CoroType.Eager ) + context.run(); + } + + abstract + void + run(Ta1, Ta2, Ta3, Ta4); + +private: + StackContext context; + + static if( coroType == CoroType.Eager ) + bool running = true; + + static if( !is( Tout == void ) ) + Tout cout; + + static if( !is( Tin == void ) ) + Tin cin; + + Ta1 arg1; + Ta2 arg2; + Ta3 arg3; + Ta4 arg4; + + void + startProc() + { + // Initial call to coroutine proper + run(arg1, arg2, arg3, arg4); + } +} + +/** + * TODO + */ +class +Coroutine(Tin, Tout, Ta1, Ta2, Ta3, Ta4, Ta5, CoroType TCoroType = CoroType.NonEager) +{ + mixin CoroutinePublicT!(Tin, Tout, TCoroType); + +protected: + mixin CoroutineProtectedT!(Tin, Tout, TCoroType); + + this(Ta1 arg1, Ta2 arg2, Ta3 arg3, Ta4 arg4, Ta5 arg5) + { + this.arg1 = arg1; + this.arg2 = arg2; + this.arg3 = arg3; + this.arg4 = arg4; + this.arg5 = arg5; + context = new StackContext(&startProc, STACK_SIZE); + static if( coroType == CoroType.Eager ) + context.run(); + } + + abstract + void + run(Ta1, Ta2, Ta3, Ta4, Ta5); + +private: + StackContext context; + + static if( coroType == CoroType.Eager ) + bool running = true; + + static if( !is( Tout == void ) ) + Tout cout; + + static if( !is( Tin == void ) ) + Tin cin; + + Ta1 arg1; + Ta2 arg2; + Ta3 arg3; + Ta4 arg4; + Ta5 arg5; + + void + startProc() + { + // Initial call to coroutine proper + run(arg1, arg2, arg3, arg4, arg5); + } +} + + +/** + * This mixin implements the constructor, and static opCall method for your + * coroutine. It is a good idea to mix this into your coroutine subclasses, + * so that you need only override the run method. + */ +template +CoroutineMixin(Tin, Tout) +{ + this() + { + super(); + } +} + +/** + * This mixin implements the constructor, and static opCall method for your + * coroutine. It is a good idea to mix this into your coroutine subclasses, + * so that you need only override the run method. + */ +template +CoroutineMixin(Tin, Tout, Ta1) +{ + this(Ta1 arg1) + { + super(arg1); + } +} + +/** + * This mixin implements the constructor, and static opCall method for your + * coroutine. It is a good idea to mix this into your coroutine subclasses, + * so that you need only override the run method. + */ +template +CoroutineMixin(Tin, Tout, Ta1, Ta2) +{ + this(Ta1 arg1, Ta2 arg2) + { + super(arg1, arg2); + } +} + +/** + * This mixin implements the constructor, and static opCall method for your + * coroutine. It is a good idea to mix this into your coroutine subclasses, + * so that you need only override the run method. + */ +template +CoroutineMixin(Tin, Tout, Ta1, Ta2, Ta3) +{ + this(Ta1 arg1, Ta2 arg2, Ta3 arg3) + { + super(arg1, arg2, arg3); + } +} + +/** + * This mixin implements the constructor, and static opCall method for your + * coroutine. It is a good idea to mix this into your coroutine subclasses, + * so that you need only override the run method. + */ +template +CoroutineMixin(Tin, Tout, Ta1, Ta2, Ta3, Ta4) +{ + this(Ta1 arg1, Ta2 arg2, Ta3 arg3, Ta4 arg4) + { + super(arg1, arg2, arg3, arg4); + } +} + +/** + * This mixin implements the constructor, and static opCall method for your + * coroutine. It is a good idea to mix this into your coroutine subclasses, + * so that you need only override the run method. + */ +template +CoroutineMixin(Tin, Tout, Ta1, Ta2, Ta3, Ta4, Ta5) +{ + this(Ta1 arg1, Ta2 arg2, Ta3 arg3, Ta4 arg4, Ta5 arg5) + { + super(arg1, arg2, arg3, arg4, arg5); + } +} + + diff --git a/infrastructure/st/stackcontext.d b/infrastructure/st/stackcontext.d new file mode 100644 index 0000000..5654b3c --- /dev/null +++ b/infrastructure/st/stackcontext.d @@ -0,0 +1,2216 @@ +/****************************************************** + * StackThreads are userland, cooperative, lightweight + * threads. StackThreads are very efficient, requiring + * much less time per context switch than real threads. + * They also require far fewer resources than real + * threads, which allows many more StackThreads to exist + * simultaneously. In addition, StackThreads do not + * require explicit synchronization since they are + * non-preemptive. There is no requirement that code + * be reentrant. + * + * This module implements the code necessary for context + * switching. StackContexts can be used independently + * of StackThreads, and may be used for implementing + * coroutines, custom scheduling or complex iterators. + * + * Thanks to Lars Ivar Igesunde (larsivar@igesundes.no) + * for the ucontext bindings on Linux used in earlier + * implementations. + * + * Version: 0.10 + * Date: June 30, 2006 + * Authors: Mikola Lysenko, mclysenk@mtu.edu + * License: Use/copy/modify freely, just give credit. + * Copyright: Public domain. + * + * Bugs: + * Debug builds will eat more stack space than release + * builds. To prevent this, you can allocate some + * extra stack in debug mode. This is not that tragic, + * since overflows are now trapped. + * + * Implementation is not thread safe. + * + * DMD has a bug on linux with multiple delegates in a + * scope. Be aware that the linux version may have + * issues due to a lack of proper testing. + * + * Due to the way DMD handles windows exceptions, it is + * impossible to trap for stack overflows. Once this + * gets fixed, it will be possible to allocate dynamic + * stacks. + * + * To prevent memory leaks, compile with -version=LEAK_FIX + * This will slow down the application, but it will + * improve memory usage. In an ideal world, it would be + * the default behavior, but due to issues with Phobos' + * removeRange I have set it as optional. + * + * History: + * v0.10 - Added the LEAK_FIX flag to work around the + * slowness of std.gc.removeRange + * + * v0.9 - Switched linux to an asm implementation. + * + * v0.8 - Added throwYield. + * + * v0.7 - Switched to system specific allocators + * (VirtualAlloc, mmap) in order to catch stack + * overflows. + * + * v0.6 - Fixed a bug with the window version. Now saves + * EBX, ESI, EDI across switches. + * + * v0.5 - Linux now fully supported. Discovered the cause + * of the exception problems: Bug in DMD. + * + * v0.4 - Fixed the GC, added some linux support + * + * v0.3 - Major refactoring + * + * v0.2 - Fixed exception handling + * + * v0.1 - Initial release + * + ******************************************************/ +module st.stackcontext; + +private import + std.thread, + std.stdio, + std.string, + std.gc; + +/// The default size of a StackContext's stack +const size_t DEFAULT_STACK_SIZE = 0x40000; + +/// The minimum size of a StackContext's stack +const size_t MINIMUM_STACK_SIZE = 0x1000; + +/// The state of a context object +enum CONTEXT_STATE +{ + READY, /// When a StackContext is in ready state, it may be run + RUNNING, /// When a StackContext is running, it is currently in use, and cannot be run + DEAD, /// When a StackContext is dead, it may no longer be run +} + +/****************************************************** + * A ContextException is generated whenever there is a + * problem in the StackContext system. ContextExceptions + * can be triggered by running out of memory, or errors + * relating to doubly starting threads. + ******************************************************/ +public class ContextException : Exception +{ + this(char[] msg) { super( msg ); } + + this(StackContext context, char[] msg) + { + if(context is null) + { + debug (StackContext) writefln("Generated an exception: %s", msg); + super(msg); + } + else + { + debug (StackContext) writefln("%s generated an exception: %s", context.toString, msg); + super(format("Context %s: %s", context.toString, msg)); + } + } +} + + + +/****************************************************** + * A ContextError is generated whenever something + * horrible and unrecoverable happens. Like writing out + * of the stack. + ******************************************************/ +public class ContextError : Error +{ + this(char[] msg) + { + super(msg); + } +} + + +/****************************************************** + * The StackContext is building block of the + * StackThread system. It allows the user to swap the + * stack of the running program. + * + * For most applications, there should be no need to use + * the StackContext, since the StackThreads are simpler. + * However, the StackContext can provide useful features + * for custom schedulers and coroutines. + * + * Any non running context may be restarted. A restarted + * context starts execution from the beginning of its + * delegate. + * + * Contexts may be nested arbitrarily, ie Context A invokes + * Context B, such that when B yields A is resumed. + * + * Calling run on already running or dead context will + * result in an exception. + * + * If an exception is generated in a context and it is + * not caught, then it will be rethrown from the run + * method. A program calling 'run' must be prepared + * to deal with any exceptions that might be thrown. Once + * a context has thrown an exception like this, it dies + * and must be restarted before it may be run again. + * + * Example: + *
+ * // Here is a trivial example using contexts. 
+ * // More sophisticated uses of contexts can produce
+ * // iterators, concurrent state machines and coroutines
+ * //
+ * void func1()
+ * {
+ *     writefln("Context 1 : Part 1");
+ *     StackContext.yield();
+ *     writefln("Context 1 : Part 2");
+ * }
+ * void func2()
+ * {
+ *     writefln("Context 2 : Part 1");
+ *     StackContext.yield();
+ *     writefln("Context 2 : Part 2");
+ * }
+ * //Create the contexts
+ * StackContext ctx1 = new StackContext(&func1);
+ * StackContext ctx2 = new StackContext(&func2);
+ *
+ * //Run the contexts
+ * ctx1.run();     // Prints "Context 1 : Part 1"
+ * ctx2.run();     // Prints "Context 2 : Part 1"
+ * ctx1.run();     // Prints "Context 1 : Part 2"
+ * ctx2.run();     // Prints "Context 2 : Part 2"
+ *
+ * //Here is a more sophisticated example using
+ * //exceptions
+ * //
+ * void func3()
+ * {
+ *      writefln("Going to throw");
+ *      StackContext.yield();
+ *      throw new Exception("Test Exception");
+ * }
+ * //Create the context
+ * StackContext ctx3 = new StackContext(&func3);
+ *
+ * //Now run the context
+ * try
+ * {
+ *      ctx3.run();     // Prints "Going to throw"
+ *      ctx3.run();     // Throws an exception
+ *      writefln("Bla");// Never gets here
+ * }
+ * catch(Exception e)
+ * {
+ *      e.print();      // Prints "Test Exception"
+ *      //We can't run ctx3 anymore unless we restart it
+ *      ctx3.restart();
+ *      ctx3.run();     // Prints "Going to throw"
+ * }
+ *
+ * //A final example illustrating context nesting
+ * //
+ * StackContext A, B;
+ *
+ * void funcA()
+ * {
+ *     writefln("A : Part 1");
+ *     B.run();
+ *     writefln("A : Part 2");
+ *     StackContext.yield();
+ *     writefln("A : Part 3");
+ * }
+ * void funcB()
+ * {
+ *      writefln("B : Part 1");
+ *      StackContext.yield();
+ *      writefln("B : Part 2");
+ * }
+ * A = new StackContext(&funcA);
+ * B = new StackContext(&funcB);
+ *
+ * //We first run A
+ * A.run();     //Prints "A : Part 1"
+ *              //       "B : Part 1"
+ *              //       "A : Part 2"
+ *              //
+ * //Now we run B
+ * B.run();     //Prints "B : Part 2"
+ *              //
+ * //Now we finish A
+ * A.run();     //Prints "A : Part 3"
+ *
+ * 
+ * + ******************************************************/ +public class StackContext +{ + /** + * Create a StackContext with the given stack size, + * using a delegate. + * + * Params: + * fn = The delegate we will be running. + * stack_size = The size of the stack for this thread + * in bytes. Note, Must be greater than the minimum + * stack size. + * + * Throws: + * A ContextException if there is insufficient memory + * for the stack. + */ + public this(void delegate() fn, size_t stack_size = DEFAULT_STACK_SIZE) + in + { + assert(fn !is null); + assert(stack_size >= MINIMUM_STACK_SIZE); + } + body + { + //Initalize the delegate + proc = fn; + + //Set up the stack + setupStack(stack_size); + + debug (StackContext) writefln("Created %s", this.toString); + } + + /** + * Create a StackContext with the given stack size, + * using a function pointer. + * + * Params: + * fn = The function pointer we are using + * stack_size = The size of the stack for this thread + * in bytes. Note, Must be greater than the minimum + * stack size. + * + * Throws: + * A ContextException if there is insufficient memory + * for the stack. + */ + public this(void function() fn, size_t stack_size = DEFAULT_STACK_SIZE) + in + { + assert(fn !is null); + assert(stack_size >= MINIMUM_STACK_SIZE); + } + body + { + //Caste fn to delegate + f_proc = fn; + proc = &to_dg; + + setupStack(stack_size); + + debug (StackContext) writefln("Created %s", this.toString); + } + + + /** + * Release the stack context. Note that since stack + * contexts are NOT GARBAGE COLLECTED, they must be + * explicitly freed. This usually taken care of when + * the user creates the StackContext implicitly via + * StackThreads, but in the case of a Context, it must + * be handled on a per case basis. + * + * Throws: + * A ContextError if the stack is corrupted. + */ + ~this() + in + { + assert(state != CONTEXT_STATE.RUNNING); + assert(current_context !is this); + } + body + { + debug (StackContext) writefln("Deleting %s", this.toString); + + //Delete the stack if we are not dead + deleteStack(); + } + + /** + * Run the context once. This causes the function to + * run until it invokes the yield method in this + * context, at which point control returns to the place + * where code invoked the program. + * + * Throws: + * A ContextException if the context is not READY. + * + * Any exceptions generated in the context are + * bubbled up through this method. + */ + public void run() + { + debug (StackContext) writefln("Running %s", this.toString); + + //We must be ready to run + if(state != CONTEXT_STATE.READY) + { + throw new ContextException(this, + "Context is not in a runnable state"); + } + + //Save the old context + StackContext tmp = current_context; + + version(LEAK_FIX) + { + //Mark GC info + debug (LogGC) writefln("Adding range: %8x-%8x", &tmp, getStackBottom()); + addRange(cast(void*)&tmp, getStackBottom()); + } + + //Set new context + current_context = this; + ctx.switchIn(); + current_context = tmp; + + assert(state != CONTEXT_STATE.RUNNING); + + version(LEAK_FIX) + { + //Clear GC info + debug (LogGC) writefln("Removing range: %8x", &tmp); + removeRange(cast(void*)&tmp); + + + //If we are dead, we need to release the GC + if(state == CONTEXT_STATE.DEAD && + gc_start !is null) + { + debug (LogGC) writefln("Removing range: %8x", gc_start); + removeRange(gc_start); + gc_start = null; + } + } + + // Pass any exceptions generated up the stack + if(last_exception !is null) + { + debug (StackContext) writefln("%s generated an exception: %s", this.toString, last_exception.toString); + + //Clear the exception + Object tmpo = last_exception; + last_exception = null; + + //Pass it up + throw tmpo; + } + + debug (StackContext) writefln("Done running context: %s", this.toString); + } + + + /** + * Returns control of the application to the routine + * which invoked the StackContext. At which point, + * the application runs. + * + * Throws: + * A ContextException when there is no currently + * running context. + */ + public static void yield() + { + if(current_context is null) + { + throw new ContextException( + null, + "Tried to yield without any running contexts."); + } + + debug (StackContext) writefln("Yielding %s", current_context.toString); + + assert(current_context.running); + + //Leave the current context + current_context.state = CONTEXT_STATE.READY; + StackContext tmp = current_context; + + version(LEAK_FIX) + { + //Save the GC range + current_context.gc_start = cast(void*)&tmp; + debug (LogGC) writefln("Adding range: %8x-%8x", + current_context.gc_start, current_context.ctx.stack_top); + addRange(current_context.gc_start, current_context.ctx.stack_top); + } + + //Swap + current_context.ctx.switchOut(); + + version(LEAK_FIX) + { + //Remove the GC range + debug (LogGC) writefln("Removing range: %8x", + current_context.gc_start); + assert(current_context.gc_start !is null); + removeRange(current_context.gc_start); + current_context.gc_start = null; + } + + //Return + current_context = tmp; + current_context.state = CONTEXT_STATE.RUNNING; + + debug (StackContext) writefln("Resuming context: %s", current_context.toString); + } + + /** + * Throws an exception and yields. The exception + * will propagate out of the run method, while the + * context will remain alive and functioning. + * The context may be resumed after the exception has + * been thrown. + * + * Params: + * t = The exception object we will propagate. + */ + public static void throwYield(Object t) + { + last_exception = t; + yield(); + } + + /** + * Resets the context to its original state. + * + * Throws: + * A ContextException if the context is running. + */ + public void restart() + { + debug (StackContext) writefln("Restarting %s", this.toString); + + if(state == CONTEXT_STATE.RUNNING) + { + throw new ContextException(this, + "Cannot reset this context while it is running!"); + } + + //Reset the context + restartStack(); + } + + /** + * Immediately sets the context state to dead. This + * can be used as an alternative to deleting the + * context since it releases any GC references, and + * may be easily reallocated. + * + * Throws: + * A ContextException if the context is not READY. + */ + public void kill() + { + if(state == CONTEXT_STATE.RUNNING) + { + throw new ContextException(this, "Cannot kill a context if it is not ready"); + } + else if(state == CONTEXT_STATE.DEAD) + { + return; + } + + version(LEAK_FIX) + { + //Clear the GC ranges if necessary + if(gc_start !is null) + { + debug (LogGC) writefln("Removing range: %8x", gc_start); + removeRange(gc_start); + gc_start = null; + } + } + + state = CONTEXT_STATE.DEAD; + } + + /** + * Convert the context into a human readable string, + * for debugging purposes. + * + * Returns: A string describing the context. + */ + public char[] toString() + { + static char[][] state_names = + [ + "RDY", + "RUN", + "XXX", + ]; + + //horrid hack for getting the address of a delegate + union hack + { + struct dele + { + void * frame; + void * fptr; + } + + dele d; + void delegate () dg; + } + hack h; + if(f_proc !is null) + h.d.fptr = cast(void*)f_proc; + else + h.dg = proc; + + return format( + "Context[sp:%8x,st:%s,fn:%8x]", + ctx.stack_pointer, + state_names[cast(int)state], + h.d.fptr); + } + + /** + * Returns: The state of this stack context. + */ + public CONTEXT_STATE getState() + { + return state; + } + + /** + * Returns: True if the context can be run. + */ + public bool ready() + { + return state == CONTEXT_STATE.READY; + } + + /** + * Returns: True if the context is currently running + */ + public bool running() + { + return state == CONTEXT_STATE.RUNNING; + } + + /** + * Returns: True if the context is currenctly dead + */ + public bool dead() + { + return state == CONTEXT_STATE.DEAD; + } + + /** + * Returns: The currently running stack context. + * null if no context is currently running. + */ + public static StackContext getRunning() + { + return current_context; + } + + invariant + { + + switch(state) + { + case CONTEXT_STATE.RUNNING: + //Make sure context is running + //assert(ctx.old_stack_pointer !is null); + assert(current_context !is null); + + case CONTEXT_STATE.READY: + //Make sure state is ready + assert(ctx.stack_bottom !is null); + assert(ctx.stack_top !is null); + assert(ctx.stack_top >= ctx.stack_bottom); + assert(ctx.stack_top - ctx.stack_bottom >= MINIMUM_STACK_SIZE); + assert(ctx.stack_pointer !is null); + assert(ctx.stack_pointer >= ctx.stack_bottom); + assert(ctx.stack_pointer <= ctx.stack_top); + assert(proc !is null); + break; + + case CONTEXT_STATE.DEAD: + //Make sure context is dead + //assert(gc_start is null); + break; + + default: assert(false); + } + } + + + version(LEAK_FIX) + { + // Start of GC range + private void * gc_start = null; + } + + // The system context + private SysContext ctx; + + // Context state + private CONTEXT_STATE state; + +//FIXME: All static objects should be in thread local +//storage. Not sure how to do this effectively yet. + +/*BEGIN TLS {*/ + + // The last exception generated + private static Object last_exception = null; + + // The currently running stack context + private static StackContext current_context = null; + +/*} END TLS*/ + + // The procedure this context is running + private void delegate() proc = null; + + // Used to convert a function pointer to a delegate + private void function() f_proc = null; + private void to_dg() { f_proc(); } + + + /** + * Initialize the stack for the context. + */ + private void setupStack(size_t stack_size) + { + //Initialize the stack + ctx.initStack(stack_size); + + //Initialize context state + state = CONTEXT_STATE.READY; + + version(LEAK_FIX) + { + assert(gc_start is null); + gc_start = null; + } + else + { + addRange(ctx.getStackStart, ctx.getStackEnd); + } + } + + /** + * Restart the context. + */ + private void restartStack() + { + version(LEAK_FIX) + { + //Clear the GC ranges if necessary + if(gc_start !is null) + { + debug (LogGC) writefln("Removing range: %8x", gc_start); + removeRange(gc_start); + gc_start = null; + } + } + + ctx.resetStack(); + state = CONTEXT_STATE.READY; + } + + /** + * Delete the stack + */ + private void deleteStack() + { + version(LEAK_FIX) + { + //Clear the GC ranges if necessary + if(gc_start !is null) + { + debug (LogGC) writefln("Removing range: %8x", gc_start); + removeRange(gc_start); + gc_start = null; + } + } + else + { + removeRange(ctx.getStackStart); + } + + // Clear state + state = CONTEXT_STATE.DEAD; + proc = null; + f_proc = null; + + // Kill the stack + ctx.killStack(); + } + + /** + * Run the context + */ + private static extern(C) void startContext() + in + { + assert(current_context !is null); + version(LEAK_FIX) + assert(current_context.gc_start is null); + } + body + { + try + { + //Set state to running, enter the context + current_context.state = CONTEXT_STATE.RUNNING; + debug (StackContext) writefln("Starting %s", current_context.toString); + current_context.proc(); + debug (StackContext) writefln("Finished %s", current_context.toString); + } + catch(Object o) + { + //Save exceptions so we can throw them later + debug (StackContext) writefln("Got an exception: %s, in %s", o.toString, current_context.toString); + last_exception = o; + } + finally + { + //Leave the object. Don't need to worry about + //GC, since it should already be released. + current_context.state = CONTEXT_STATE.DEAD; + debug (StackContext) writefln("Leaving %s", current_context.toString); + current_context.ctx.switchOut(); + } + + //This should never be reached + assert(false); + } + + /** + * Grab the stack bottom! + */ + private void * getStackBottom() + { + version(Win32) + { + if(current_context is null) + return os_query_stackBottom(); + + return current_context.ctx.stack_top; + } + else + { + Thread t = Thread.getThis; + return t.stackBottom; + } + } +} + +/******************************************************** + * SYSTEM SPECIFIC FUNCTIONS + * All information below this can be regarded as a + * black box. The details of the implementation are + * irrelevant to the workings of the rest of the + * context data. + ********************************************************/ + +private version (Win32) +{ + +import std.windows.syserror; + +struct SYSTEM_INFO +{ + union + { + int dwOemId; + + struct + { + short wProcessorArchitecture; + short wReserved; + } + } + + int dwPageSize; + void* lpMinimumApplicationAddress; + void* lpMaximumApplicationAddress; + int* dwActiveProcessorMask; + int dwNumberOfProcessors; + int dwProcessorType; + int dwAllocationGranularity; + short wProcessorLevel; + short wProcessorRevision; +} + +extern (Windows) void GetSystemInfo( + SYSTEM_INFO * sys_info); + +extern (Windows) void* VirtualAlloc( + void * addr, + size_t size, + uint type, + uint protect); + +extern (Windows) int VirtualFree( + void * addr, + size_t size, + uint type); + +extern (Windows) int GetLastError(); + +private debug(LogStack) +{ + import std.file; +} + +const uint MEM_COMMIT = 0x1000; +const uint MEM_RESERVE = 0x2000; +const uint MEM_RESET = 0x8000; +const uint MEM_LARGE_PAGES = 0x20000000; +const uint MEM_PHYSICAL = 0x400000; +const uint MEM_TOP_DOWN = 0x100000; +const uint MEM_WRITE_WATCH = 0x200000; + +const uint MEM_DECOMMIT = 0x4000; +const uint MEM_RELEASE = 0x8000; + +const uint PAGE_EXECUTE = 0x10; +const uint PAGE_EXECUTE_READ = 0x20; +const uint PAGE_EXECUTE_READWRITE = 0x40; +const uint PAGE_EXECUTE_WRITECOPY = 0x80; +const uint PAGE_NOACCESS = 0x01; +const uint PAGE_READONLY = 0x02; +const uint PAGE_READWRITE = 0x04; +const uint PAGE_WRITECOPY = 0x08; +const uint PAGE_GUARD = 0x100; +const uint PAGE_NOCACHE = 0x200; +const uint PAGE_WRITECOMBINE = 0x400; + +// Size of a page on the system +size_t page_size; + +static this() +{ + //Get the system's page size + SYSTEM_INFO sys_info; + GetSystemInfo(&sys_info); + page_size = sys_info.dwPageSize; +} + +private struct SysContext +{ + // Stack information + void * stack_bottom = null; + void * stack_top = null; + void * stack_pointer = null; + + // The old stack pointer + void * old_stack_pointer = null; + + + /** + * Returns: The size of the sys context + */ + size_t getSize() + { + return cast(size_t)(stack_top - stack_bottom - page_size); + } + + + /** + * Returns: The start of the stack. + */ + void * getStackStart() + { + return stack_bottom + page_size; + } + + /** + * Returns: The end of the stack. + */ + void * getStackEnd() + { + return stack_top; + } + + + /** + * Handle and report any system errors + */ + void handleWinError(char[] msg) + { + throw new ContextException(format( + "Failed to %s, %s", + msg, sysErrorString(GetLastError()))); + } + + /** + * Initialize the stack + */ + void initStack(size_t stack_size) + { + //Allocate the stack + guard page + + //Count number of pages + int num_pages = (stack_size + page_size - 1) / page_size; + + //Reserve the address space for the stack + stack_bottom = VirtualAlloc( + null, + (num_pages + 1) * page_size, + MEM_RESERVE, + PAGE_NOACCESS); + if(stack_bottom is null) + handleWinError("reserve stack address"); + + //Now allocate the base pages + void * res = VirtualAlloc( + stack_bottom + page_size, + num_pages * page_size, + MEM_COMMIT, + PAGE_READWRITE); + if(res is null) + handleWinError("allocate stack space"); + + stack_top = res + num_pages * page_size; + + //Create a guard page + res = VirtualAlloc( + stack_bottom, + page_size, + MEM_COMMIT, + PAGE_READWRITE | PAGE_GUARD); + if(res is null) + handleWinError("create guard page"); + + //Initialize the stack + resetStack(); + } + + /** + * Reset the stack. + */ + void resetStack() + { + stack_pointer = stack_top; + assert(cast(uint)stack_pointer % 4 == 0); + + //Initialize stack state + void push(uint val) + { + stack_pointer -= 4; + *cast(uint*)stack_pointer = val; + } + + push(cast(uint)&StackContext.startContext); //EIP + push(0xFFFFFFFF); //EBP + push(0xFFFFFFFF); //FS:[0] + push(cast(uint)stack_top); //FS:[4] + push(cast(uint)stack_bottom + 4); //FS:[8] + push(0); //EBX + push(0); //ESI + push(0); //EDI + + assert(stack_pointer > stack_bottom); + assert(stack_pointer < stack_top); + } + + /** + * Free the stack + */ + void killStack() + { + debug (LogStack) + { + static int log_num = 0; + write(format("lg%d.bin", log_num++), + stack_bottom[0..(stack_top - stack_bottom)]); + } + + assert(stack_pointer > stack_bottom); + assert(stack_pointer < stack_top); + + // Release the stack + assert(stack_bottom !is null); + + if(VirtualFree(stack_bottom, 0, MEM_RELEASE) == 0) + { + handleWinError("release stack"); + } + + //Clear all the old stack pointers + stack_bottom = + stack_top = + stack_pointer = + old_stack_pointer = null; + } + + /** + * Switch into a context. + */ + void switchIn() + { + asm + { + naked; + + //Save old state into stack + push EBP; + push dword ptr FS:[0]; + push dword ptr FS:[4]; + push dword ptr FS:[8]; + push EBX; + push ESI; + push EDI; + + //Save old sp + mov dword ptr old_stack_pointer[EAX], ESP; + + //Set the new stack pointer + mov ESP, stack_pointer[EAX]; + + //Restore saved state + pop EDI; + pop ESI; + pop EBX; + pop dword ptr FS:[8]; + pop dword ptr FS:[4]; + pop dword ptr FS:[0]; + pop EBP; + + //Return + ret; + } + } + + /** + * Switch out of a context + */ + void switchOut() + { + asm + { + naked; + + //Save current state + push EBP; + push dword ptr FS:[0]; + push dword ptr FS:[4]; + push dword ptr FS:[8]; + push EBX; + push ESI; + push EDI; + + // Set the stack pointer + mov dword ptr stack_pointer[EAX], ESP; + + // Restore the stack pointer + mov ESP, dword ptr old_stack_pointer[EAX]; + + //Zap the old stack pointer + xor EDX, EDX; + mov dword ptr old_stack_pointer[EAX], EDX; + + //Restore saved state + pop EDI; + pop ESI; + pop EBX; + pop dword ptr FS:[8]; + pop dword ptr FS:[4]; + pop dword ptr FS:[0]; + pop EBP; + + //Return + ret; + } + } +} +} +else private version(linux) +{ + +private extern(C) +{ + void * mmap(void * start, size_t length, int prot, int flags, int fd, int offset); + int munmap(void * start, size_t length); +} + +private const int PROT_EXEC = 4; +private const int PROT_WRITE = 2; +private const int PROT_READ = 1; +private const int PROT_NONE = 0; + +private const int MAP_SHARED = 0x0001; +private const int MAP_PRIVATE = 0x0002; +private const int MAP_FIXED = 0x0010; +private const int MAP_ANONYMOUS = 0x0020; +private const int MAP_GROWSDOWN = 0x0100; +private const int MAP_DENYWRITE = 0x0800; +private const int MAP_EXECUTABLE = 0x1000; +private const int MAP_LOCKED = 0x2000; +private const int MAP_NORESERVE = 0x4000; +private const int MAP_POPULATE = 0x8000; +private const int MAP_NONBLOCK = 0x10000; + +private const void * MAP_FAILED = cast(void*)-1; + +private struct SysContext +{ + void * stack_top = null; + void * stack_bottom = null; + void * stack_pointer = null; + void * old_stack_pointer = null; + + + size_t getSize() + { + return cast(size_t)(stack_top - stack_bottom); + } + + void * getStackStart() + { + return stack_bottom; + } + + void * getStackEnd() + { + return stack_top; + } + + /** + * Initialize the stack + */ + void initStack(size_t stack_size) + { + //Allocate stack + stack_bottom = mmap( + null, + stack_size, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, + 0, + 0); + + if(stack_bottom is MAP_FAILED) + { + stack_bottom = null; + throw new ContextException(null, "Could not allocate stack"); + } + + stack_top = stack_bottom + stack_size; + + //Initialize the context + resetStack(); + } + + /** + * Reset the stack. + */ + void resetStack() + { + //Initialize stack pointer + stack_pointer = stack_top; + + //Initialize stack state + void push(uint val) + { + stack_pointer -= 4; + *cast(uint*)stack_pointer = val; + } + + push(cast(uint)&StackContext.startContext); //Start point + push(0); //EBP + push(0); //EBX + push(0); //ESI + push(0); //EDI + } + + /** + * Release the stack + */ + void killStack() + { + //Deallocate the stack + if(munmap(stack_bottom, (stack_top - stack_bottom))) + throw new ContextException(null, "Could not deallocate stack"); + + //Remove pointer references + stack_top = + stack_bottom = + stack_pointer = + old_stack_pointer = null; + } + + /** + * Enter the stack context + */ + void switchIn() + { + //HACK: The GC needs to scan the thread's stack, however we are moving + //it. To accomplish this feat, we just write over the internal members + //in Thread, and hope it works, though it may not in the future. + Thread t = Thread.getThis(); + void *sb = t.stackBottom; + void *st = t.stackTop; + + //Note bottom & top are switched thanks to DMD's strange notation. + // + //Also, this is not necessarily thread safe, since a collection could + //occur between when we set the stack ranges and when we perform a + //context switch; however since we are gauranteed to still have our range + //marked before we leave, this is acceptable, since the result is + //merely under-collection. + t.stackBottom = stack_top; + t.stackTop = stack_bottom; + + pswiThunk(); + + t.stackBottom = sb; + t.stackTop = st; + } + + //Private switch in thunk + void pswiThunk() + { + asm + { + naked; + + //Save current state + push EBP; + push EBX; + push ESI; + push EDI; + + //Switch around the stack pointers + mov dword ptr old_stack_pointer[EAX], ESP; + mov ESP, dword ptr stack_pointer[EAX]; + + //Restore previous state + pop EDI; + pop ESI; + pop EBX; + pop EBP; + + ret; + } + } + + /** + * Leave current context + */ + void switchOut() + { + asm + { + naked; + + //Save the context's state + push EBP; + push EBX; + push ESI; + push EDI; + + //Return to previous context's sp. + mov dword ptr stack_pointer[EAX], ESP; + mov ESP, dword ptr old_stack_pointer[EAX]; + + //Restore previous context's state + pop EDI; + pop ESI; + pop EBX; + pop EBP; + + ret; + } + } +} +} +else +{ + //Unsupported system + static assert(false, "Stack Context: System Unsupported"); +} + + +unittest +{ + writefln("Testing context creation/deletion"); + int s0 = 0; + static int s1 = 0; + + StackContext a = new StackContext( + delegate void() + { + s0++; + }); + + static void fb() { s1++; } + + StackContext b = new StackContext(&fb); + + StackContext c = new StackContext( + delegate void() { assert(false); }); + + assert(a); + assert(b); + assert(c); + + assert(s0 == 0); + assert(s1 == 0); + assert(a.getState == CONTEXT_STATE.READY); + assert(b.getState == CONTEXT_STATE.READY); + assert(c.getState == CONTEXT_STATE.READY); + + delete c; + + assert(s0 == 0); + assert(s1 == 0); + assert(a.getState == CONTEXT_STATE.READY); + assert(b.getState == CONTEXT_STATE.READY); + + writefln("running a"); + a.run(); + writefln("done a"); + + assert(a); + + assert(s0 == 1); + assert(s1 == 0); + assert(a.getState == CONTEXT_STATE.DEAD); + assert(b.getState == CONTEXT_STATE.READY); + + try + { + a.run(); + assert(false); + } + catch(ContextException ce) + { + debug writefln("Generated exception correctly"); + } + + + assert(b.getState == CONTEXT_STATE.READY); + + writefln("Running b"); + b.run(); + writefln("Done b"); + + assert(s0 == 1); + assert(s1 == 1); + assert(b.getState == CONTEXT_STATE.DEAD); + + delete a; + delete b; + + writefln("Context creation passed"); +} + +unittest +{ + writefln("Testing context switching"); + int s0 = 0; + int s1 = 0; + int s2 = 0; + + StackContext a = new StackContext( + delegate void() + { + while(true) + { + debug writefln(" ---A---"); + s0++; + StackContext.yield(); + } + }); + + + StackContext b = new StackContext( + delegate void() + { + while(true) + { + debug writefln(" ---B---"); + s1++; + StackContext.yield(); + } + }); + + + StackContext c = new StackContext( + delegate void() + { + while(true) + { + debug writefln(" ---C---"); + s2++; + StackContext.yield(); + } + }); + + assert(a); + assert(b); + assert(c); + assert(s0 == 0); + assert(s1 == 0); + assert(s2 == 0); + + a.run(); + b.run(); + + assert(a); + assert(b); + assert(c); + assert(s0 == 1); + assert(s1 == 1); + assert(s2 == 0); + + for(int i=0; i<20; i++) + { + c.run(); + a.run(); + } + + assert(a); + assert(b); + assert(c); + assert(s0 == 21); + assert(s1 == 1); + assert(s2 == 20); + + delete a; + delete b; + delete c; + + writefln("Context switching passed"); +} + +unittest +{ + writefln("Testing nested contexts"); + StackContext a, b, c; + + int t0 = 0; + int t1 = 0; + int t2 = 0; + + a = new StackContext( + delegate void() + { + + t0++; + b.run(); + + }); + + b = new StackContext( + delegate void() + { + assert(t0 == 1); + assert(t1 == 0); + assert(t2 == 0); + + t1++; + c.run(); + + }); + + c = new StackContext( + delegate void() + { + assert(t0 == 1); + assert(t1 == 1); + assert(t2 == 0); + + t2++; + }); + + assert(a); + assert(b); + assert(c); + assert(t0 == 0); + assert(t1 == 0); + assert(t2 == 0); + + a.run(); + + assert(t0 == 1); + assert(t1 == 1); + assert(t2 == 1); + + assert(a); + assert(b); + assert(c); + + delete a; + delete b; + delete c; + + writefln("Nesting contexts passed"); +} + +unittest +{ + writefln("Testing basic exceptions"); + + + int t0 = 0; + int t1 = 0; + int t2 = 0; + + assert(t0 == 0); + assert(t1 == 0); + assert(t2 == 0); + + try + { + + try + { + throw new Exception("Testing"); + t2++; + } + catch(Exception fx) + { + t1++; + throw fx; + } + + t2++; + } + catch(Exception ex) + { + t0++; + ex.print; + } + + assert(t0 == 1); + assert(t1 == 1); + assert(t2 == 0); + + writefln("Basic exceptions are supported"); +} + + +//Anonymous delegates are slightly broken on linux. Don't run this test yet, +//since dmd will break it. +version(Win32) +unittest +{ + writefln("Testing exceptions"); + StackContext a, b, c; + + int t0 = 0; + int t1 = 0; + int t2 = 0; + + writefln("t0 = %s\nt1 = %s\nt2 = %s", t0, t1, t2); + + a = new StackContext( + delegate void() + { + t0++; + throw new Exception("A exception"); + t0++; + }); + + b = new StackContext( + delegate void() + { + t1++; + c.run(); + t1++; + }); + + c = new StackContext( + delegate void() + { + t2++; + throw new Exception("C exception"); + t2++; + }); + + assert(a); + assert(b); + assert(c); + assert(t0 == 0); + assert(t1 == 0); + assert(t2 == 0); + + try + { + a.run(); + assert(false); + } + catch(Exception e) + { + e.print; + } + + assert(a); + assert(a.getState == CONTEXT_STATE.DEAD); + assert(b); + assert(c); + assert(t0 == 1); + assert(t1 == 0); + assert(t2 == 0); + + try + { + b.run(); + assert(false); + } + catch(Exception e) + { + e.print; + } + + assert(a); + assert(b); + assert(b.getState == CONTEXT_STATE.DEAD); + assert(c); + assert(c.getState == CONTEXT_STATE.DEAD); + assert(t0 == 1); + assert(t1 == 1); + assert(t2 == 1); + + delete a; + delete b; + delete c; + + + StackContext t; + int q0 = 0; + int q1 = 0; + + t = new StackContext( + delegate void() + { + try + { + q0++; + throw new Exception("T exception"); + q0++; + } + catch(Exception ex) + { + q1++; + writefln("!!!!!!!!GOT EXCEPTION!!!!!!!!"); + ex.print; + } + }); + + + assert(t); + assert(q0 == 0); + assert(q1 == 0); + t.run(); + assert(t); + assert(t.dead); + assert(q0 == 1); + assert(q1 == 1); + + delete t; + + StackContext d, e; + int s0 = 0; + int s1 = 0; + + d = new StackContext( + delegate void() + { + try + { + s0++; + e.run(); + StackContext.yield(); + s0++; + e.run(); + s0++; + } + catch(Exception ex) + { + ex.print; + } + }); + + e = new StackContext( + delegate void() + { + s1++; + StackContext.yield(); + throw new Exception("E exception"); + s1++; + }); + + assert(d); + assert(e); + assert(s0 == 0); + assert(s1 == 0); + + d.run(); + + assert(d); + assert(e); + assert(s0 == 1); + assert(s1 == 1); + + d.run(); + + assert(d); + assert(e); + assert(s0 == 2); + assert(s1 == 1); + + assert(d.dead); + assert(e.dead); + + delete d; + delete e; + + writefln("Exceptions passed"); +} + +unittest +{ + writefln("Testing reset"); + int t0 = 0; + int t1 = 0; + int t2 = 0; + + StackContext a = new StackContext( + delegate void() + { + t0++; + StackContext.yield(); + t1++; + StackContext.yield(); + t2++; + }); + + assert(a); + assert(t0 == 0); + assert(t1 == 0); + assert(t2 == 0); + + a.run(); + assert(a); + assert(t0 == 1); + assert(t1 == 0); + assert(t2 == 0); + + a.run(); + assert(a); + assert(t0 == 1); + assert(t1 == 1); + assert(t2 == 0); + + a.run(); + assert(a); + assert(t0 == 1); + assert(t1 == 1); + assert(t2 == 1); + + a.restart(); + assert(a); + assert(t0 == 1); + assert(t1 == 1); + assert(t2 == 1); + + a.run(); + assert(a); + assert(t0 == 2); + assert(t1 == 1); + assert(t2 == 1); + + a.restart(); + a.run(); + assert(a); + assert(t0 == 3); + assert(t1 == 1); + assert(t2 == 1); + + a.run(); + assert(a); + assert(t0 == 3); + assert(t1 == 2); + assert(t2 == 1); + + a.restart(); + a.run(); + assert(a); + assert(t0 == 4); + assert(t1 == 2); + assert(t2 == 1); + + delete a; + + writefln("Reset passed"); +} + +//Same problem as above. +version (Win32) +unittest +{ + writefln("Testing standard exceptions"); + int t = 0; + + StackContext a = new StackContext( + delegate void() + { + uint * tmp = null; + + *tmp = 0xbadc0de; + + t++; + }); + + assert(a); + assert(t == 0); + + try + { + a.run(); + assert(false); + } + catch(Exception e) + { + e.print(); + } + + assert(a); + assert(a.dead); + assert(t == 0); + + delete a; + + StackContext b; + + b = new StackContext( + delegate void() + { + b.restart(); + assert(false); + }); + + try + { + b.run(); + assert(false); + } + catch(ContextException e) + { + e.print; + } + + try + { + StackContext.yield(); + assert(false); + } + catch(ContextException e) + { + e.print; + } + + writefln("Standard exceptions passed"); +} + +unittest +{ + writefln("Memory stress test"); + + const uint STRESS_SIZE = 5000; + + StackContext ctx[]; + ctx.length = STRESS_SIZE; + + int cnt0 = 0; + int cnt1 = 0; + + void threadFunc() + { + cnt0++; + StackContext.yield; + cnt1++; + } + + foreach(inout StackContext c; ctx) + { + c = new StackContext(&threadFunc, MINIMUM_STACK_SIZE); + } + + assert(cnt0 == 0); + assert(cnt1 == 0); + + foreach(inout StackContext c; ctx) + { + c.run; + } + + assert(cnt0 == STRESS_SIZE); + assert(cnt1 == 0); + + foreach(inout StackContext c; ctx) + { + c.run; + } + + assert(cnt0 == STRESS_SIZE); + assert(cnt1 == STRESS_SIZE); + + foreach(inout StackContext c; ctx) + { + delete c; + } + + assert(cnt0 == STRESS_SIZE); + assert(cnt1 == STRESS_SIZE); + + writefln("Memory stress test passed"); +} + +unittest +{ + writefln("Testing floating point"); + + float f0 = 1.0; + float f1 = 0.0; + + double d0 = 2.0; + double d1 = 0.0; + + real r0 = 3.0; + real r1 = 0.0; + + assert(f0 == 1.0); + assert(f1 == 0.0); + assert(d0 == 2.0); + assert(d1 == 0.0); + assert(r0 == 3.0); + assert(r1 == 0.0); + + StackContext a, b, c; + + a = new StackContext( + delegate void() + { + while(true) + { + f0 ++; + d0 ++; + r0 ++; + + StackContext.yield(); + } + }); + + b = new StackContext( + delegate void() + { + while(true) + { + f1 = d0 + r0; + d1 = f0 + r0; + r1 = f0 + d0; + + StackContext.yield(); + } + }); + + c = new StackContext( + delegate void() + { + while(true) + { + f0 *= d1; + d0 *= r1; + r0 *= f1; + + StackContext.yield(); + } + }); + + a.run(); + assert(f0 == 2.0); + assert(f1 == 0.0); + assert(d0 == 3.0); + assert(d1 == 0.0); + assert(r0 == 4.0); + assert(r1 == 0.0); + + b.run(); + assert(f0 == 2.0); + assert(f1 == 7.0); + assert(d0 == 3.0); + assert(d1 == 6.0); + assert(r0 == 4.0); + assert(r1 == 5.0); + + c.run(); + assert(f0 == 12.0); + assert(f1 == 7.0); + assert(d0 == 15.0); + assert(d1 == 6.0); + assert(r0 == 28.0); + assert(r1 == 5.0); + + a.run(); + assert(f0 == 13.0); + assert(f1 == 7.0); + assert(d0 == 16.0); + assert(d1 == 6.0); + assert(r0 == 29.0); + assert(r1 == 5.0); + + writefln("Floating point passed"); +} + + +version(x86) unittest +{ + writefln("Testing registers"); + + struct registers + { + int eax, ebx, ecx, edx; + int esi, edi; + int ebp, esp; + + //TODO: Add fpu stuff + } + + static registers old; + static registers next; + static registers g_old; + static registers g_next; + + //I believe that D calling convention requires that + //EBX, ESI and EDI be saved. In order to validate + //this, we write to those registers and call the + //stack thread. + static StackThread reg_test = new StackThread( + delegate void() + { + asm + { + naked; + + pushad; + + mov EBX, 1; + mov ESI, 2; + mov EDI, 3; + + mov [old.ebx], EBX; + mov [old.esi], ESI; + mov [old.edi], EDI; + mov [old.ebp], EBP; + mov [old.esp], ESP; + + call StackThread.yield; + + mov [next.ebx], EBX; + mov [next.esi], ESI; + mov [next.edi], EDI; + mov [next.ebp], EBP; + mov [next.esp], ESP; + + popad; + } + }); + + //Run the stack context + asm + { + naked; + + pushad; + + mov EBX, 10; + mov ESI, 11; + mov EDI, 12; + + mov [g_old.ebx], EBX; + mov [g_old.esi], ESI; + mov [g_old.edi], EDI; + mov [g_old.ebp], EBP; + mov [g_old.esp], ESP; + + mov EAX, [reg_test]; + call StackThread.run; + + mov [g_next.ebx], EBX; + mov [g_next.esi], ESI; + mov [g_next.edi], EDI; + mov [g_next.ebp], EBP; + mov [g_next.esp], ESP; + + popad; + } + + + //Make sure the registers are byte for byte equal. + assert(old.ebx = 1); + assert(old.esi = 2); + assert(old.edi = 3); + assert(old == next); + + assert(g_old.ebx = 10); + assert(g_old.esi = 11); + assert(g_old.edi = 12); + assert(g_old == g_next); + + writefln("Registers passed!"); +} + + +unittest +{ + writefln("Testing throwYield"); + + int q0 = 0; + + StackContext st0 = new StackContext( + delegate void() + { + q0++; + StackContext.throwYield(new Exception("testing throw yield")); + q0++; + }); + + try + { + st0.run(); + assert(false); + } + catch(Exception e) + { + e.print(); + } + + assert(q0 == 1); + assert(st0.ready); + + st0.run(); + assert(q0 == 2); + assert(st0.dead); + + writefln("throwYield passed!"); +} + diff --git a/infrastructure/st/stackthread.d b/infrastructure/st/stackthread.d new file mode 100644 index 0000000..6ff5923 --- /dev/null +++ b/infrastructure/st/stackthread.d @@ -0,0 +1,2456 @@ +/****************************************************** + * StackThreads are userland, cooperative, lightweight + * threads. StackThreads are very efficient, requiring + * much less time per context switch than real threads. + * They also require far fewer resources than real + * threads, which allows many more StackThreads to exist + * simultaneously. In addition, StackThreads do not + * require explicit synchronization since they are + * non-preemptive. There is no requirement that code + * be reentrant. + * + * This module implements the stack thread system on top + * of the context layer. + * + * Version: 0.3 + * Date: July 4, 2006 + * Authors: + * Mikola Lysenko, mclysenk@mtu.edu + * License: Use/copy/modify freely, just give credit. + * Copyright: Public domain. + * + * Bugs: + * None known yet. + * + * History: + * v0.7 - Switched timing resolution to milliseconds. + * + * v0.6 - Removed timing functions from st_yield/st_throwYield + * + * v0.5 - Addded st_throwYield and MAX/MIN_THREAD_PRIORITY + * + * v0.4 - Unittests finished-ready for an initial release. + * + * v0.3 - Changed name back to StackThread and added + * linux support. Context switching is now handled + * in the stackcontext module, and much simpler to + * port. + * + * v0.2 - Changed name to QThread, fixed many issues. + * + * v0.1 - Initial stack thread system. Very buggy. + * + ******************************************************/ +module st.stackthread; + +//Module imports +private import + st.stackcontext, + std.stdio, + std.string; + +/// The priority of a stack thread determines its order in +/// the scheduler. Higher priority threads go first. +alias int priority_t; + +/// The default priority for a stack thread is 0. +const priority_t DEFAULT_STACKTHREAD_PRIORITY = 0; + +/// Maximum thread priority +const priority_t MAX_STACKTHREAD_PRIORITY = 0x7fffffff; + +/// Minimum thread priority +const priority_t MIN_STACKTHREAD_PRIORITY = 0x80000000; + +/// The state of a stack thread +enum THREAD_STATE +{ + READY, /// Thread is ready to run + RUNNING, /// Thread is currently running + DEAD, /// Thread has terminated + SUSPENDED, /// Thread is suspended +} + +/// The state of the scheduler +enum SCHEDULER_STATE +{ + READY, /// Scheduler is ready to run a thread + RUNNING, /// Scheduler is running a timeslice +} + +//Timeslices +private STPriorityQueue active_slice; +private STPriorityQueue next_slice; + +//Scheduler state +private SCHEDULER_STATE sched_state; + +//Start time of the time slice +private ulong sched_t0; + +//Currently active stack thread +private StackThread sched_st; + +version(Win32) +{ + private extern(Windows) int QueryPerformanceFrequency(ulong *); + private ulong sched_perf_freq; +} + + +//Initialize the scheduler +static this() +{ + active_slice = new STPriorityQueue(); + next_slice = new STPriorityQueue(); + sched_state = SCHEDULER_STATE.READY; + sched_t0 = -1; + sched_st = null; + + version(Win32) + QueryPerformanceFrequency(&sched_perf_freq); +} + + +/****************************************************** + * StackThreadExceptions are generated whenever the + * stack threads are incorrectly invoked. Trying to + * run a time slice while a time slice is in progress + * will result in a StackThreadException. + ******************************************************/ +class StackThreadException : Exception +{ + this(char[] msg) + { + super(msg); + } + + this(StackThread st, char[] msg) + { + super(format("%s: %s", st.toString, msg)); + } +} + + + +/****************************************************** + * StackThreads are much like regular threads except + * they are cooperatively scheduled. A user may switch + * between StackThreads using st_yield. + ******************************************************/ +class StackThread +{ + /** + * Creates a new stack thread and adds it to the + * scheduler. + * + * Params: + * dg = The delegate we are invoking + * stack_size = The size of the stack for the stack + * thread. + * priority = The priority of the stack thread. + */ + public this + ( + void delegate() dg, + priority_t priority = DEFAULT_STACKTHREAD_PRIORITY, + size_t stack_size = DEFAULT_STACK_SIZE + ) + { + this.m_delegate = dg; + this.context = new StackContext(&m_proc, DEFAULT_STACK_SIZE); + this.m_priority = priority; + + //Schedule the thread + st_schedule(this); + + debug (StackThread) writefln("Created thread, %s", toString); + } + + /** + * Creates a new stack thread and adds it to the + * scheduler, using a function pointer. + * + * Params: + * fn = The function pointer that the stack thread + * invokes. + * stack_size = The size of the stack for the stack + * thread. + * priority = The priority of the stack thread. + */ + public this + ( + void function() fn, + priority_t priority = DEFAULT_STACKTHREAD_PRIORITY, + size_t stack_size = DEFAULT_STACK_SIZE + ) + { + this.m_delegate = &delegator; + this.m_function = fn; + this.context = new StackContext(&m_proc, DEFAULT_STACK_SIZE); + this.m_priority = priority; + + //Schedule the thread + st_schedule(this); + + debug (StackThread) writefln("Created thread, %s", toString); + } + + /** + * Converts the thread to a string. + * + * Returns: A string representing the stack thread. + */ + public char[] toString() + { + debug(PQueue) + { + return format("ST[t:%8x,p:%8x,l:%8x,r:%8x]", + cast(void*)this, + cast(void*)parent, + cast(void*)left, + cast(void*)right); + } + else + { + static char[][] state_names = + [ + "RDY", + "RUN", + "XXX", + "PAU", + ]; + + //horrid hack for getting the address of a delegate + union hack + { + struct dele + { + void * frame; + void * fptr; + } + + dele d; + void delegate () dg; + } + hack h; + if(m_function !is null) + h.d.fptr = cast(void*) m_function; + else if(m_delegate !is null) + h.dg = m_delegate; + else + h.dg = &run; + + return format( + "Thread[pr=%d,st=%s,fn=%8x]", + priority, + state_names[cast(uint)state], + h.d.fptr); + } + } + + invariant + { + assert(context); + + switch(state) + { + case THREAD_STATE.READY: + assert(context.ready); + break; + + case THREAD_STATE.RUNNING: + assert(context.running); + break; + + case THREAD_STATE.DEAD: + assert(!context.running); + break; + + case THREAD_STATE.SUSPENDED: + assert(context.ready); + break; + + default: assert(false); + } + + if(left !is null) + { + assert(left.parent is this); + } + + if(right !is null) + { + assert(right.parent is this); + } + } + + /** + * Removes this stack thread from the scheduler. The + * thread will not be run until it is added back to + * the scheduler. + */ + public final void pause() + { + debug (StackThread) writefln("Pausing %s", toString); + + switch(state) + { + case THREAD_STATE.READY: + st_deschedule(this); + state = THREAD_STATE.SUSPENDED; + break; + + case THREAD_STATE.RUNNING: + transition(THREAD_STATE.SUSPENDED); + break; + + case THREAD_STATE.DEAD: + throw new StackThreadException(this, "Cannot pause a dead thread"); + + case THREAD_STATE.SUSPENDED: + throw new StackThreadException(this, "Cannot pause a paused thread"); + + default: assert(false); + } + } + + /** + * Adds the stack thread back to the scheduler. It + * will resume running with its priority & state + * intact. + */ + public final void resume() + { + debug (StackThread) writefln("Resuming %s", toString); + + //Can only resume paused threads + if(state != THREAD_STATE.SUSPENDED) + { + throw new StackThreadException(this, "Thread is not suspended"); + } + + //Set state to ready and schedule + state = THREAD_STATE.READY; + st_schedule(this); + } + + /** + * Kills this stack thread in a violent manner. The + * thread does not get a chance to end itself or clean + * anything up, it is descheduled and all GC references + * are released. + */ + public final void kill() + { + debug (StackThread) writefln("Killing %s", toString); + + switch(state) + { + case THREAD_STATE.READY: + //Kill thread and remove from scheduler + st_deschedule(this); + state = THREAD_STATE.DEAD; + context.kill(); + break; + + case THREAD_STATE.RUNNING: + //Transition to dead + transition(THREAD_STATE.DEAD); + break; + + case THREAD_STATE.DEAD: + throw new StackThreadException(this, "Cannot kill already dead threads"); + + case THREAD_STATE.SUSPENDED: + //We need to kill the stack, no need to touch scheduler + state = THREAD_STATE.DEAD; + context.kill(); + break; + + default: assert(false); + } + } + + /** + * Waits to join with this thread. If the given amount + * of milliseconds expires before the thread is dead, + * then we return automatically. + * + * Params: + * ms = The maximum amount of time the thread is + * allowed to wait. The special value -1 implies that + * the join will wait indefinitely. + * + * Returns: + * The amount of millieconds the thread was actually + * waiting. + */ + public final ulong join(ulong ms = -1) + { + debug (StackThread) writefln("Joining %s", toString); + + //Make sure we are in a timeslice + if(sched_state != SCHEDULER_STATE.RUNNING) + { + throw new StackThreadException(this, "Cannot join unless a timeslice is currently in progress"); + } + + //And make sure we are joining with a valid thread + switch(state) + { + case THREAD_STATE.READY: + break; + + case THREAD_STATE.RUNNING: + throw new StackThreadException(this, "A thread cannot join with itself!"); + + case THREAD_STATE.DEAD: + throw new StackThreadException(this, "Cannot join with a dead thread"); + + case THREAD_STATE.SUSPENDED: + throw new StackThreadException(this, "Cannot join with a paused thread"); + + default: assert(false); + } + + //Do busy waiting until the thread dies or the + //timer runs out. + ulong start_time = getSysMillis(); + ulong timeout = (ms == -1) ? ms : start_time + ms; + + while( + state != THREAD_STATE.DEAD && + timeout > getSysMillis()) + { + StackContext.yield(); + } + + return getSysMillis() - start_time; + } + + /** + * Restarts the thread's execution from the very + * beginning. Suspended and dead threads are not + * resumed, but upon resuming, they will restart. + */ + public final void restart() + { + debug (StackThread) writefln("Restarting %s", toString); + + //Each state needs to be handled carefully + switch(state) + { + case THREAD_STATE.READY: + //If we are ready, + context.restart(); + break; + + case THREAD_STATE.RUNNING: + //Reset the thread. + transition(THREAD_STATE.READY); + break; + + case THREAD_STATE.DEAD: + //Dead threads become suspended + context.restart(); + state = THREAD_STATE.SUSPENDED; + break; + + case THREAD_STATE.SUSPENDED: + //Suspended threads stay suspended + context.restart(); + break; + + default: assert(false); + } + } + + /** + * Grabs the thread's priority. Intended for use + * as a property. + * + * Returns: The stack thread's priority. + */ + public final priority_t priority() + { + return m_priority; + } + + /** + * Sets the stack thread's priority. Used to either + * reschedule or reset the thread. Changes do not + * take effect until the next round of scheduling. + * + * Params: + * p = The new priority for the thread + * + * Returns: + * The new priority for the thread. + */ + public final priority_t priority(priority_t p) + { + //Update priority + if(sched_state == SCHEDULER_STATE.READY && + state == THREAD_STATE.READY) + { + next_slice.remove(this); + m_priority = p; + next_slice.add(this); + } + + return m_priority = p; + } + + /** + * Returns: The state of this thread. + */ + public final THREAD_STATE getState() + { + return state; + } + + /** + * Returns: True if the thread is ready to run. + */ + public final bool ready() + { + return state == THREAD_STATE.READY; + } + + /** + * Returns: True if the thread is currently running. + */ + public final bool running() + { + return state == THREAD_STATE.RUNNING; + } + + /** + * Returns: True if the thread is dead. + */ + public final bool dead() + { + return state == THREAD_STATE.DEAD; + } + + /** + * Returns: True if the thread is not dead. + */ + public final bool alive() + { + return state != THREAD_STATE.DEAD; + } + + /** + * Returns: True if the thread is paused. + */ + public final bool paused() + { + return state == THREAD_STATE.SUSPENDED; + } + + /** + * Creates a stack thread without a function pointer + * or delegate. Used when a user overrides the stack + * thread class. + */ + protected this + ( + priority_t priority = DEFAULT_STACKTHREAD_PRIORITY, + size_t stack_size = DEFAULT_STACK_SIZE + ) + { + this.context = new StackContext(&m_proc, stack_size); + this.m_priority = priority; + + //Schedule the thread + st_schedule(this); + + debug (StackThread) writefln("Created thread, %s", toString); + } + + /** + * Run the stack thread. This method may be overloaded + * by classes which inherit from stack thread, as an + * alternative to passing delegates. + * + * Throws: Anything. + */ + protected void run() + { + m_delegate(); + } + + // Heap information + private StackThread parent = null; + private StackThread left = null; + private StackThread right = null; + + // The thread's priority + private priority_t m_priority; + + // The state of the thread + private THREAD_STATE state; + + // The thread's context + private StackContext context; + + //Delegate handler + private void function() m_function; + private void delegate() m_delegate; + private void delegator() { m_function(); } + + //My procedure + private final void m_proc() + { + try + { + debug (StackThread) writefln("Starting %s", toString); + run; + } + catch(Object o) + { + debug (StackThread) writefln("Got a %s exception from %s", o.toString, toString); + throw o; + } + finally + { + debug (StackThread) writefln("Finished %s", toString); + state = THREAD_STATE.DEAD; + } + } + + /** + * Used to change the state of a running thread + * gracefully + */ + private final void transition(THREAD_STATE next_state) + { + state = next_state; + StackContext.yield(); + } +} + + + +/****************************************************** + * The STPriorityQueue is used by the scheduler to + * order the objects in the stack threads. For the + * moment, the implementation is binary heap, but future + * versions might use a binomial heap for performance + * improvements. + ******************************************************/ +private class STPriorityQueue +{ +public: + + /** + * Add a stack thread to the queue. + * + * Params: + * st = The thread we are adding. + */ + void add(StackThread st) + in + { + assert(st !is null); + assert(st); + assert(st.parent is null); + assert(st.left is null); + assert(st.right is null); + } + body + { + size++; + + //Handle trivial case + if(head is null) + { + head = st; + return; + } + + //First, insert st + StackThread tmp = head; + int pos; + for(pos = size; pos>3; pos>>>=1) + { + assert(tmp); + tmp = (pos & 1) ? tmp.right : tmp.left; + } + + assert(tmp !is null); + assert(tmp); + + if(pos&1) + { + assert(tmp.left !is null); + assert(tmp.right is null); + tmp.right = st; + } + else + { + assert(tmp.left is null); + assert(tmp.right is null); + tmp.left = st; + } + st.parent = tmp; + + assert(tmp); + assert(st); + + //Fixup the stack and we're good. + bubble_up(st); + } + + /** + * Remove a stack thread. + * + * Params: + * st = The stack thread we are removing. + */ + void remove(StackThread st) + in + { + assert(st); + assert(hasThread(st)); + } + out + { + assert(st); + assert(st.left is null); + assert(st.right is null); + assert(st.parent is null); + } + body + { + //Handle trivial case + if(size == 1) + { + assert(st is head); + + --size; + + st.parent = + st.left = + st.right = + head = null; + + return; + } + + //Cycle to the bottom of the heap + StackThread tmp = head; + int pos; + for(pos = size; pos>3; pos>>>=1) + { + assert(tmp); + tmp = (pos & 1) ? tmp.right : tmp.left; + } + tmp = (pos & 1) ? tmp.right : tmp.left; + + + assert(tmp !is null); + assert(tmp.left is null); + assert(tmp.right is null); + + //Remove tmp + if(tmp.parent.left is tmp) + { + tmp.parent.left = null; + } + else + { + assert(tmp.parent.right is tmp); + tmp.parent.right = null; + } + tmp.parent = null; + size--; + + assert(tmp); + + //Handle second trivial case + if(tmp is st) + { + return; + } + + //Replace st with tmp + if(st is head) + { + head = tmp; + } + + //Fix tmp's parent + tmp.parent = st.parent; + if(tmp.parent !is null) + { + if(tmp.parent.left is st) + { + tmp.parent.left = tmp; + } + else + { + assert(tmp.parent.right is st); + tmp.parent.right = tmp; + } + } + + //Fix tmp's left + tmp.left = st.left; + if(tmp.left !is null) + { + tmp.left.parent = tmp; + } + + //Fix tmp's right + tmp.right = st.right; + if(tmp.right !is null) + { + tmp.right.parent = tmp; + } + + //Unlink st + st.parent = + st.left = + st.right = null; + + + //Bubble up + bubble_up(tmp); + //Bubble back down + bubble_down(tmp); + + } + + /** + * Extract the top priority thread. It is removed from + * the queue. + * + * Returns: The top priority thread. + */ + StackThread top() + in + { + assert(head !is null); + } + out(r) + { + assert(r !is null); + assert(r); + assert(r.parent is null); + assert(r.right is null); + assert(r.left is null); + } + body + { + StackThread result = head; + + //Handle trivial case + if(size == 1) + { + //Drop size and return + --size; + result.parent = + result.left = + result.right = null; + head = null; + return result; + } + + //Cycle to the bottom of the heap + StackThread tmp = head; + int pos; + for(pos = size; pos>3; pos>>>=1) + { + assert(tmp); + tmp = (pos & 1) ? tmp.right : tmp.left; + } + tmp = (pos & 1) ? tmp.right : tmp.left; + + assert(tmp !is null); + assert(tmp.left is null); + assert(tmp.right is null); + + //Remove tmp + if(tmp.parent.left is tmp) + { + tmp.parent.left = null; + } + else + { + assert(tmp.parent.right is tmp); + tmp.parent.right = null; + } + tmp.parent = null; + + //Add tmp to top + tmp.left = head.left; + tmp.right = head.right; + if(tmp.left !is null) tmp.left.parent = tmp; + if(tmp.right !is null) tmp.right.parent = tmp; + + //Unlink head + head.right = + head.left = null; + + //Verify results + assert(head); + assert(tmp); + + //Set the new head + head = tmp; + + //Bubble down + bubble_down(tmp); + + //Drop size and return + --size; + return result; + } + + /** + * Merges two priority queues. The result is stored + * in this queue, while other is emptied. + * + * Params: + * other = The queue we are merging with. + */ + void merge(STPriorityQueue other) + { + StackThread[] stack; + stack ~= other.head; + + while(stack.length > 0) + { + StackThread tmp = stack[$-1]; + stack.length = stack.length - 1; + + if(tmp !is null) + { + stack ~= tmp.right; + stack ~= tmp.left; + + tmp.parent = + tmp.right = + tmp.left = null; + + add(tmp); + } + } + + //Clear the list + other.head = null; + other.size = 0; + } + + /** + * Returns: true if the heap actually contains the thread st. + */ + bool hasThread(StackThread st) + { + StackThread tmp = st; + while(tmp !is null) + { + if(tmp is head) + return true; + tmp = tmp.parent; + } + + return false; + } + + invariant + { + if(head !is null) + { + assert(head); + assert(size > 0); + } + } + + //Top of the heap + StackThread head = null; + + //Size of the stack + int size; + + debug (PQueue) void print() + { + StackThread[] stack; + stack ~= head; + + while(stack.length > 0) + { + StackThread tmp = stack[$-1]; + stack.length = stack.length - 1; + + if(tmp !is null) + { + writef("%s, ", tmp.m_priority); + + if(tmp.left !is null) + { + assert(tmp.left.m_priority <= tmp.m_priority); + stack ~= tmp.left; + } + + if(tmp.right !is null) + { + assert(tmp.right.m_priority <= tmp.m_priority); + stack ~= tmp.right; + } + + } + } + + writefln(""); + } + + void bubble_up(StackThread st) + { + //Ok, now we are at the bottom, so time to bubble up + while(st.parent !is null) + { + //Test for end condition + if(st.parent.m_priority >= st.m_priority) + return; + + //Otherwise, just swap + StackThread a = st.parent, tp; + + assert(st); + assert(st.parent); + + //writefln("%s <-> %s", a.toString, st.toString); + + //Switch parents + st.parent = a.parent; + a.parent = st; + + //Fixup + if(st.parent !is null) + { + if(st.parent.left is a) + { + st.parent.left = st; + } + else + { + assert(st.parent.right is a); + st.parent.right = st; + } + + assert(st.parent); + } + + //Switch children + if(a.left is st) + { + a.left = st.left; + st.left = a; + + tp = st.right; + st.right = a.right; + a.right = tp; + + if(st.right !is null) st.right.parent = st; + } + else + { + a.right = st.right; + st.right = a; + + tp = st.left; + st.left = a.left; + a.left = tp; + + if(st.left !is null) st.left.parent = st; + } + + if(a.right !is null) a.right.parent = a; + if(a.left !is null) a.left.parent = a; + + //writefln("%s <-> %s", a.toString, st.toString); + + assert(st); + assert(a); + } + + head = st; + } + + //Bubbles a thread downward + void bubble_down(StackThread st) + { + while(st.left !is null) + { + StackThread a, tp; + + assert(st); + + if(st.right is null || + st.left.m_priority >= st.right.m_priority) + { + if(st.left.m_priority > st.m_priority) + { + a = st.left; + assert(a); + //writefln("Left: %s - %s", st, a); + + st.left = a.left; + a.left = st; + + tp = st.right; + st.right = a.right; + a.right = tp; + + if(a.right !is null) a.right.parent = a; + } else break; + } + else if(st.right.m_priority > st.m_priority) + { + a = st.right; + assert(a); + //writefln("Right: %s - %s", st, a); + + st.right = a.right; + a.right = st; + + tp = st.left; + st.left = a.left; + a.left = tp; + + if(a.left !is null) a.left.parent = a; + } + else break; + + //Fix the parent + a.parent = st.parent; + st.parent = a; + if(a.parent !is null) + { + if(a.parent.left is st) + { + a.parent.left = a; + } + else + { + assert(a.parent.right is st); + a.parent.right = a; + } + } + else + { + head = a; + } + + if(st.left !is null) st.left.parent = st; + if(st.right !is null) st.right.parent = st; + + assert(a); + assert(st); + //writefln("Done: %s - %s", st, a); + } + } +} + +debug (PQueue) + unittest +{ + writefln("Testing priority queue"); + + + //Create some queue + STPriorityQueue q1 = new STPriorityQueue(); + STPriorityQueue q2 = new STPriorityQueue(); + STPriorityQueue q3 = new STPriorityQueue(); + + assert(q1); + assert(q2); + assert(q3); + + //Add some elements + writefln("Adding elements"); + q1.add(new StackThread(1)); + q1.print(); + assert(q1); + q1.add(new StackThread(2)); + q1.print(); + assert(q1); + q1.add(new StackThread(3)); + q1.print(); + assert(q1); + q1.add(new StackThread(4)); + q1.print(); + assert(q1); + + writefln("Removing elements"); + StackThread t; + + t = q1.top(); + writefln("t:%s",t.priority); + q1.print(); + assert(t.priority == 4); + assert(q1); + + t = q1.top(); + writefln("t:%s",t.priority); + q1.print(); + assert(t.priority == 3); + assert(q1); + + t = q1.top(); + writefln("t:%s",t.priority); + q1.print(); + assert(t.priority == 2); + assert(q1); + + t = q1.top(); + writefln("t:%s",t.priority); + q1.print(); + assert(t.priority == 1); + assert(q1); + + writefln("Second round of adds"); + q2.add(new StackThread(5)); + q2.add(new StackThread(4)); + q2.add(new StackThread(1)); + q2.add(new StackThread(3)); + q2.add(new StackThread(6)); + q2.add(new StackThread(2)); + q2.add(new StackThread(7)); + q2.add(new StackThread(0)); + assert(q2); + q2.print(); + + writefln("Testing top extraction again"); + assert(q2.top.priority == 7); + q2.print(); + assert(q2.top.priority == 6); + assert(q2.top.priority == 5); + assert(q2.top.priority == 4); + assert(q2.top.priority == 3); + assert(q2.top.priority == 2); + assert(q2.top.priority == 1); + assert(q2.top.priority == 0); + assert(q2); + + writefln("Third round"); + q2.add(new StackThread(10)); + q2.add(new StackThread(7)); + q2.add(new StackThread(5)); + q2.add(new StackThread(7)); + q2.print(); + assert(q2); + + writefln("Testing extraction"); + assert(q2.top.priority == 10); + assert(q2.top.priority == 7); + assert(q2.top.priority == 7); + assert(q2.top.priority == 5); + + writefln("Testing merges"); + q3.add(new StackThread(10)); + q3.add(new StackThread(-10)); + q3.add(new StackThread(10)); + q3.add(new StackThread(-10)); + + q2.add(new StackThread(-9)); + q2.add(new StackThread(9)); + q2.add(new StackThread(-9)); + q2.add(new StackThread(9)); + + q2.print(); + q3.print(); + q3.merge(q2); + + writefln("q2:%d", q2.size); + q2.print(); + writefln("q3:%d", q3.size); + q3.print(); + assert(q2); + assert(q3); + assert(q2.size == 0); + assert(q3.size == 8); + + writefln("Extracting merges"); + assert(q3.top.priority == 10); + assert(q3.top.priority == 10); + assert(q3.top.priority == 9); + assert(q3.top.priority == 9); + assert(q3.top.priority == -9); + assert(q3.top.priority == -9); + assert(q3.top.priority == -10); + assert(q3.top.priority == -10); + + writefln("Testing removal"); + StackThread ta = new StackThread(5); + StackThread tb = new StackThread(6); + StackThread tc = new StackThread(10); + + q2.add(new StackThread(7)); + q2.add(new StackThread(1)); + q2.add(ta); + q2.add(tb); + q2.add(tc); + + assert(q2); + assert(q2.size == 5); + + writefln("Removing"); + q2.remove(ta); + q2.remove(tc); + q2.remove(tb); + assert(q2.size == 2); + + writefln("Dumping heap"); + assert(q2.top.priority == 7); + assert(q2.top.priority == 1); + + + writefln("Testing big add/subtract"); + StackThread[100] st; + STPriorityQueue stq = new STPriorityQueue(); + + for(int i=0; i<100; i++) + { + st[i] = new StackThread(i); + stq.add(st[i]); + } + + stq.remove(st[50]); + stq.remove(st[10]); + stq.remove(st[31]); + stq.remove(st[88]); + + for(int i=99; i>=0; i--) + { + if(i != 50 && i!=10 &&i!=31 &&i!=88) + { + assert(stq.top.priority == i); + } + } + writefln("Big add/remove worked"); + + writefln("Priority queue passed"); +} + + +// ------------------------------------------------- +// SCHEDULER FUNCTIONS +// ------------------------------------------------- + +/** + * Grabs the number of milliseconds on the system clock. + * + * (Adapted from std.perf) + * + * Returns: The amount of milliseconds the system has been + * up. + */ +version(Win32) +{ + private extern(Windows) int + QueryPerformanceCounter(ulong * cnt); + + private ulong getSysMillis() + { + ulong result; + QueryPerformanceCounter(&result); + + if(result < 0x20C49BA5E353F7L) + { + result = (result * 1000) / sched_perf_freq; + } + else + { + result = (result / sched_perf_freq) * 1000; + } + + return result; + } +} +else version(linux) +{ + extern (C) + { + private struct timeval + { + int tv_sec; + int tv_usec; + }; + private struct timezone + { + int tz_minuteswest; + int tz_dsttime; + }; + private void gettimeofday(timeval *tv, timezone *tz); + } + + private ulong getSysMillis() + { + timeval tv; + timezone tz; + + gettimeofday(&tv, &tz); + + return + cast(ulong)tv.tv_sec * 1000 + + cast(ulong)tv.tv_usec / 1000; + } +} +else +{ + static assert(false); +} + + +/** + * Schedules a thread such that it will be run in the next + * timeslice. + * + * Params: + * st = Thread we are scheduling + */ +private void st_schedule(StackThread st) +in +{ + assert(st.state == THREAD_STATE.READY); +} +body +{ + debug(PQueue) { return; } + + debug (StackThread) writefln("Scheduling %s", st.toString); + next_slice.add(st); +} + +/** + * Removes a thread from the scheduler. + * + * Params: + * st = Thread we are removing. + */ +private void st_deschedule(StackThread st) +in +{ + assert(st.state == THREAD_STATE.READY); +} +body +{ + debug (StackThread) writefln("Descheduling %s", st.toString); + if(active_slice.hasThread(st)) + { + active_slice.remove(st); + } + else + { + next_slice.remove(st); + } +} + +/** + * Runs a single timeslice. During a timeslice each + * currently running thread is executed once, with the + * highest priority first. Any number of things may + * cause a timeslice to be aborted, inclduing; + * + * o An exception is unhandled in a thread which is run + * o The st_abortSlice function is called + * o The timelimit is exceeded in st_runSlice + * + * If a timeslice is not finished, it will be resumed on + * the next call to st_runSlice. If this is undesirable, + * calling st_resetSlice will cause the timeslice to + * execute from the beginning again. + * + * Newly created threads are not run until the next + * timeslice. + * + * This works just like the regular st_runSlice, except it + * is timed. If the lasts longer than the specified amount + * of nano seconds, it is immediately aborted. + * + * If no time quanta is specified, the timeslice runs + * indefinitely. + * + * Params: + * ms = The number of milliseconds the timeslice is allowed + * to run. + * + * Throws: The first exception generated in the timeslice. + * + * Returns: The total number of milliseconds used by the + * timeslice. + */ +ulong st_runSlice(ulong ms = -1) +{ + + if(sched_state != SCHEDULER_STATE.READY) + { + throw new StackThreadException("Cannot run a timeslice while another is already in progress!"); + } + + sched_t0 = getSysMillis(); + ulong stop_time = (ms == -1) ? ms : sched_t0 + ms; + + //Swap slices + if(active_slice.size == 0) + { + STPriorityQueue tmp = next_slice; + next_slice = active_slice; + active_slice = tmp; + } + + debug (StackThread) writefln("Running slice with %d threads", active_slice.size); + + sched_state = SCHEDULER_STATE.RUNNING; + + while(active_slice.size > 0 && + (getSysMillis() - sched_t0) < stop_time && + sched_state == SCHEDULER_STATE.RUNNING) + { + + sched_st = active_slice.top(); + debug(StackThread) writefln("Starting thread: %s", sched_st); + sched_st.state = THREAD_STATE.RUNNING; + + + try + { + sched_st.context.run(); + } + catch(Object o) + { + //Handle exit condition on thread + + sched_state = SCHEDULER_STATE.READY; + throw o; + } + finally + { + //Process any state transition + switch(sched_st.state) + { + case THREAD_STATE.READY: + //Thread wants to be restarted + sched_st.context.restart(); + next_slice.add(sched_st); + break; + + case THREAD_STATE.RUNNING: + //Nothing unusual, pass it to next state + sched_st.state = THREAD_STATE.READY; + next_slice.add(sched_st); + break; + + case THREAD_STATE.SUSPENDED: + //Don't reschedule + break; + + case THREAD_STATE.DEAD: + //Kill thread's context + sched_st.context.kill(); + break; + + default: assert(false); + } + + sched_st = null; + } + } + + sched_state = SCHEDULER_STATE.READY; + + return getSysMillis() - sched_t0; +} + +/** + * Aborts a currently running slice. The thread which + * invoked st_abortSlice will continue to run until it + * yields normally. + */ +void st_abortSlice() +{ + debug (StackThread) writefln("Aborting slice"); + + if(sched_state != SCHEDULER_STATE.RUNNING) + { + throw new StackThreadException("Cannot abort the timeslice while the scheduler is not running!"); + } + + sched_state = SCHEDULER_STATE.READY; +} + +/** + * Restarts the entire timeslice from the beginning. + * This has no effect if the last timeslice was started + * from the beginning. If a slice is currently running, + * then the current thread will continue to execute until + * it yields normally. + */ +void st_resetSlice() +{ + debug (StackThread) writefln("Resetting timeslice"); + next_slice.merge(active_slice); +} + +/** + * Yields the currently executing stack thread. This is + * functionally equivalent to StackContext.yield, except + * it returns the amount of time the thread was yielded. + */ +void st_yield() +{ + debug (StackThread) writefln("Yielding %s", sched_st.toString); + + StackContext.yield(); +} + +/** + * Throws an object and yields the thread. The exception + * is propagated out of the st_runSlice method. + */ +void st_throwYield(Object t) +{ + debug (StackThread) writefln("Throwing %s, Yielding %s", t.toString, sched_st.toString); + + StackContext.throwYield(t); +} + +/** + * Causes the currently executing thread to wait for the + * specified amount of milliseconds. After the time + * has passed, the thread resumes execution. + * + * Params: + * ms = The amount of milliseconds the thread will sleep. + * + * Returns: The number of milliseconds the thread was + * asleep. + */ +ulong st_sleep(ulong ms) +{ + debug(StackThread) writefln("Sleeping for %d in %s", ms, sched_st.toString); + + ulong t0 = getSysMillis(); + + while((getSysMillis - t0) >= ms) + StackContext.yield(); + + return getSysMillis() - t0; +} + +/** + * This function retrieves the number of milliseconds since + * the start of the timeslice. + * + * Returns: The number of milliseconds since the start of + * the timeslice. + */ +ulong st_time() +{ + return getSysMillis() - sched_t0; +} + +/** + * Returns: The currently running stack thread. null if + * a timeslice is not in progress. + */ +StackThread st_getRunning() +{ + return sched_st; +} + +/** + * Returns: The current state of the scheduler. + */ +SCHEDULER_STATE st_getState() +{ + return sched_state; +} + +/** + * Returns: True if the scheduler is running a timeslice. + */ +bool st_isRunning() +{ + return sched_state == SCHEDULER_STATE.RUNNING; +} + +/** + * Returns: The number of threads stored in the scheduler. + */ +int st_numThreads() +{ + return active_slice.size + next_slice.size; +} + +/** + * Returns: The number of threads remaining in the timeslice. + */ +int st_numSliceThreads() +{ + if(active_slice.size > 0) + return active_slice.size; + + return next_slice.size; +} + +debug (PQueue) {} +else +{ +unittest +{ + writefln("Testing stack thread creation & basic scheduling"); + + static int q0 = 0; + static int q1 = 0; + static int q2 = 0; + + //Run one empty slice + st_runSlice(); + + StackThread st0 = new StackThread( + delegate void() + { + while(true) + { + q0++; + st_yield(); + } + }); + + StackThread st1 = new StackThread( + function void() + { + while(true) + { + q1++; + st_yield(); + } + }); + + class TestThread : StackThread + { + this() { super(); } + + override void run() + { + while(true) + { + q2++; + st_yield(); + } + } + } + + StackThread st2 = new TestThread(); + + assert(st0); + assert(st1); + assert(st2); + + st_runSlice(); + + assert(q0 == 1); + assert(q1 == 1); + assert(q2 == 1); + + st1.pause(); + st_runSlice(); + + assert(st0); + assert(st1); + assert(st2); + + assert(st1.paused); + assert(q0 == 2); + assert(q1 == 1); + assert(q2 == 2); + + st2.kill(); + st_runSlice(); + + assert(st2.dead); + assert(q0 == 3); + assert(q1 == 1); + assert(q2 == 2); + + st0.kill(); + st_runSlice(); + + assert(st0.dead); + assert(q0 == 3); + assert(q1 == 1); + assert(q2 == 2); + + st1.resume(); + st_runSlice(); + + assert(st1.ready); + assert(q0 == 3); + assert(q1 == 2); + assert(q2 == 2); + + st1.kill(); + st_runSlice(); + + assert(st1.dead); + assert(q0 == 3); + assert(q1 == 2); + assert(q2 == 2); + + + assert(st_numThreads == 0); + writefln("Thread creation passed!"); +} + +unittest +{ + writefln("Testing priorities"); + + //Test priority based scheduling + int a = 0; + int b = 0; + int c = 0; + + + StackThread st0 = new StackThread( + delegate void() + { + a++; + assert(a == 1); + assert(b == 0); + assert(c == 0); + + st_yield; + + a++; + assert(a == 2); + assert(b == 2); + assert(c == 2); + + st_yield; + + a++; + + writefln("a=%d, b=%d, c=%d", a, b, c); + assert(a == 3); + writefln("b=%d : ", b, (b==2)); + assert(b == 2); + assert(c == 2); + + + }, 10); + + StackThread st1 = new StackThread( + delegate void() + { + b++; + assert(a == 1); + assert(b == 1); + assert(c == 0); + + st_yield; + + b++; + assert(a == 1); + assert(b == 2); + assert(c == 2); + + }, 5); + + StackThread st2 = new StackThread( + delegate void() + { + c++; + assert(a == 1); + assert(b == 1); + assert(c == 1); + + st_yield; + + c++; + assert(a == 1); + assert(b == 1); + assert(c == 2); + + st0.priority = 100; + + st_yield; + + c++; + assert(a == 3); + assert(b == 2); + assert(c == 3); + + }, 1); + + st_runSlice(); + + assert(st0); + assert(st1); + assert(st2); + + assert(a == 1); + assert(b == 1); + assert(c == 1); + + st0.priority = -10; + st1.priority = -5; + + st_runSlice(); + + assert(a == 2); + assert(b == 2); + assert(c == 2); + + st_runSlice(); + + assert(st0.dead); + assert(st1.dead); + assert(st2.dead); + + assert(a == 3); + assert(b == 2); + assert(c == 3); + + assert(st_numThreads == 0); + writefln("Priorities pass"); +} + +version(Win32) +unittest +{ + writefln("Testing exception handling"); + + int q0 = 0; + int q1 = 0; + int q2 = 0; + int q3 = 0; + + StackThread st0, st1; + + st0 = new StackThread( + delegate void() + { + q0++; + throw new Exception("Test exception"); + q0++; + }); + + try + { + q3++; + st_runSlice(); + q3++; + } + catch(Exception e) + { + e.print; + } + + assert(st0.dead); + assert(q0 == 1); + assert(q1 == 0); + assert(q2 == 0); + assert(q3 == 1); + + st1 = new StackThread( + delegate void() + { + try + { + q1++; + throw new Exception("Testing"); + q1++; + } + catch(Exception e) + { + e.print(); + } + + while(true) + { + q2++; + st_yield(); + } + }); + + st_runSlice(); + assert(st1.ready); + assert(q0 == 1); + assert(q1 == 1); + assert(q2 == 1); + assert(q3 == 1); + + st1.kill; + assert(st1.dead); + + assert(st_numThreads == 0); + writefln("Exception handling passed!"); +} + +unittest +{ + writefln("Testing thread pausing"); + + //Test pause + int q = 0; + int r = 0; + int s = 0; + + StackThread st0; + + st0 = new StackThread( + delegate void() + { + s++; + st0.pause(); + q++; + }); + + try + { + st0.resume(); + } + catch(Exception e) + { + e.print; + r ++; + } + + assert(st0); + assert(q == 0); + assert(r == 1); + assert(s == 0); + + st0.pause(); + assert(st0.paused); + + try + { + st0.pause(); + } + catch(Exception e) + { + e.print; + r ++; + } + + st_runSlice(); + + assert(q == 0); + assert(r == 2); + assert(s == 0); + + st0.resume(); + assert(st0.ready); + + st_runSlice(); + + assert(st0.paused); + assert(q == 0); + assert(r == 2); + assert(s == 1); + + st0.resume(); + st_runSlice(); + + assert(st0.dead); + assert(q == 1); + assert(r == 2); + assert(s == 1); + + try + { + st0.pause(); + } + catch(Exception e) + { + e.print; + r ++; + } + + st_runSlice(); + + assert(st0.dead); + assert(q == 1); + assert(r == 3); + assert(s == 1); + + assert(st_numThreads == 0); + writefln("Pause passed!"); +} + + +unittest +{ + writefln("Testing kill"); + + int q0 = 0; + int q1 = 0; + int q2 = 0; + + StackThread st0, st1, st2; + + st0 = new StackThread( + delegate void() + { + while(true) + { + q0++; + st_yield(); + } + }); + + st1 = new StackThread( + delegate void() + { + q1++; + st1.kill(); + q1++; + }); + + st2 = new StackThread( + delegate void() + { + while(true) + { + q2++; + st_yield(); + } + }); + + assert(st1.ready); + + st_runSlice(); + + assert(st1.dead); + assert(q0 == 1); + assert(q1 == 1); + assert(q2 == 1); + + st_runSlice(); + assert(q0 == 2); + assert(q1 == 1); + assert(q2 == 2); + + st0.kill(); + st_runSlice(); + assert(st0.dead); + assert(q0 == 2); + assert(q1 == 1); + assert(q2 == 3); + + st2.pause(); + assert(st2.paused); + st2.kill(); + assert(st2.dead); + + int r = 0; + + try + { + r++; + st2.kill(); + r++; + } + catch(StackThreadException e) + { + e.print; + } + + assert(st2.dead); + assert(r == 1); + + assert(st_numThreads == 0); + writefln("Kill passed"); +} + +unittest +{ + writefln("Testing join"); + + int q0 = 0; + int q1 = 0; + + StackThread st0, st1; + + st0 = new StackThread( + delegate void() + { + q0++; + st1.join(); + q0++; + }, 10); + + st1 = new StackThread( + delegate void() + { + q1++; + st_yield(); + q1++; + st1.join(); + q1++; + }, 0); + + try + { + st0.join(); + assert(false); + } + catch(StackThreadException e) + { + e.print(); + } + + st_runSlice(); + + assert(st0.alive); + assert(st1.alive); + assert(q0 == 1); + assert(q1 == 1); + + try + { + st_runSlice(); + assert(false); + } + catch(Exception e) + { + e.print; + } + + assert(st0.alive); + assert(st1.dead); + assert(q0 == 1); + assert(q1 == 2); + + st_runSlice(); + assert(st0.dead); + assert(q0 == 2); + assert(q1 == 2); + + assert(st_numThreads == 0); + writefln("Join passed"); +} + +unittest +{ + writefln("Testing restart"); + assert(st_numThreads == 0); + + int q0 = 0; + int q1 = 0; + + StackThread st0, st1; + + st0 = new StackThread( + delegate void() + { + q0++; + st_yield(); + st0.restart(); + }); + + st_runSlice(); + assert(st0.ready); + assert(q0 == 1); + + st_runSlice(); + assert(st0.ready); + assert(q0 == 1); + + st_runSlice(); + assert(st0.ready); + assert(q0 == 2); + + st0.kill(); + assert(st0.dead); + + assert(st_numThreads == 0); + writefln("Testing the other restart"); + + st1 = new StackThread( + delegate void() + { + q1++; + while(true) + { + st_yield(); + } + }); + + assert(st1.ready); + + st_runSlice(); + assert(q1 == 1); + + st_runSlice(); + assert(q1 == 1); + + st1.restart(); + st_runSlice(); + assert(st1.ready); + assert(q1 == 2); + + st1.pause(); + st_runSlice(); + assert(st1.paused); + assert(q1 == 2); + + st1.restart(); + st1.resume(); + st_runSlice(); + assert(st1.ready); + assert(q1 == 3); + + st1.kill(); + st1.restart(); + assert(st1.paused); + st1.resume(); + + st_runSlice(); + assert(st1.ready); + assert(q1 == 4); + + st1.kill(); + + assert(st_numThreads == 0); + writefln("Restart passed"); +} + +unittest +{ + writefln("Testing abort / reset"); + assert(st_numThreads == 0); + + try + { + st_abortSlice(); + assert(false); + } + catch(StackThreadException e) + { + e.print; + } + + + int q0 = 0; + int q1 = 0; + int q2 = 0; + + StackThread st0 = new StackThread( + delegate void() + { + while(true) + { + writefln("st0"); + q0++; + st_abortSlice(); + st_yield(); + } + }, 10); + + StackThread st1 = new StackThread( + delegate void() + { + while(true) + { + writefln("st1"); + q1++; + st_abortSlice(); + st_yield(); + } + }, 5); + + StackThread st2 = new StackThread( + delegate void() + { + while(true) + { + writefln("st2"); + q2++; + st_abortSlice(); + st_yield(); + } + }, 0); + + st_runSlice(); + assert(q0 == 1); + assert(q1 == 0); + assert(q2 == 0); + + st_runSlice(); + assert(q0 == 1); + assert(q1 == 1); + assert(q2 == 0); + + st_runSlice(); + assert(q0 == 1); + assert(q1 == 1); + assert(q2 == 1); + + st_runSlice(); + assert(q0 == 2); + assert(q1 == 1); + assert(q2 == 1); + + st_resetSlice(); + st_runSlice(); + assert(q0 == 3); + assert(q1 == 1); + assert(q2 == 1); + + st0.kill(); + st1.kill(); + st2.kill(); + + st_runSlice(); + assert(q0 == 3); + assert(q1 == 1); + assert(q2 == 1); + + assert(st_numThreads == 0); + writefln("Abort slice passed"); +} + +unittest +{ + writefln("Testing throwYield"); + + int q0 = 0; + + StackThread st0 = new StackThread( + delegate void() + { + q0++; + st_throwYield(new Exception("testing st_throwYield")); + q0++; + }); + + try + { + st_runSlice(); + assert(false); + } + catch(Exception e) + { + e.print(); + } + + assert(q0 == 1); + assert(st0.ready); + + st_runSlice(); + assert(q0 == 2); + assert(st0.dead); + + assert(st_numThreads == 0); + writefln("throwYield passed"); +} +}