diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/arch/arch.nim | 62 | ||||
-rw-r--r-- | lib/arch/i386.asm | 79 | ||||
-rw-r--r-- | lib/arch/ms_amd64.asm | 90 | ||||
-rw-r--r-- | lib/arch/ms_i386.asm | 12 | ||||
-rw-r--r-- | lib/arch/unix_amd64.asm | 89 | ||||
-rw-r--r-- | lib/arch/unix_i386.asm | 12 | ||||
-rw-r--r-- | lib/arch/x86/amd64.S | 96 | ||||
-rw-r--r-- | lib/arch/x86/i386.S | 64 | ||||
-rw-r--r-- | lib/pure/coro.nim | 355 | ||||
-rw-r--r-- | lib/system/gc.nim | 3 | ||||
-rw-r--r-- | lib/system/gc_common.nim | 7 |
11 files changed, 422 insertions, 447 deletions
diff --git a/lib/arch/arch.nim b/lib/arch/arch.nim deleted file mode 100644 index 0b3df3d3c..000000000 --- a/lib/arch/arch.nim +++ /dev/null @@ -1,62 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2015 Rokas Kupstys -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# -# Architecture-specific optimizations and features. -# arch.nim can be imported by only a subset of the -# architectures supported by Nim. - -when defined(windows): - const - ABI* = "ms" -elif defined(unix): - const - ABI* = "unix" -else: - {.error: "Unsupported ABI".} - -when defined(amd64): - when defined(unix): - # unix (sysv) ABI - type - JmpBufReg* {.pure.} = enum - BX, BP, R12, R13, R14, R15, SP, IP, TOTAL - elif defined(windows): - # ms ABI - type - JmpBufReg* {.pure.} = enum - BX, BP, R12, R13, R14, R15, SP, IP, SI, DI, TOTAL - type - Reg* {.pure.} = enum - AX, BX, CX, DX, SI, DI, BP, SP, IP, R8, R9, R10, R11, R12, R13, R14, R15, TOTAL - -elif defined(i386) or defined(nimdoc): - # identical fastcall calling convention on all x86 OS - type - JmpBufReg* {.pure.} = enum - BX, SI, DI, BP, SP, IP, TOTAL - - Reg* {.pure.} = enum - AX, BX, CX, BP, SP, DI, SI, TOTAL - -else: - {.error: "Unsupported architecture".} - -{.compile: "./" & ABI & "_" & hostCPU & ".asm"} - -type - JmpBuf* = array[JmpBufReg.TOTAL, pointer] - Registers* = array[Reg.TOTAL, pointer] - - -proc getRegisters*(ctx: var Registers) {.importc: "narch_$1", fastcall.} - -proc setjmp*(ctx: var JmpBuf): int {.importc: "narch_$1", fastcall.} -proc longjmp*(ctx: JmpBuf, ret=1) {.importc: "narch_$1", fastcall.} - -proc coroSwitchStack*(sp: pointer) {.importc: "narch_$1", fastcall.} -proc coroRestoreStack*() {.importc: "narch_$1", fastcall.} diff --git a/lib/arch/i386.asm b/lib/arch/i386.asm deleted file mode 100644 index 61f6fdda7..000000000 --- a/lib/arch/i386.asm +++ /dev/null @@ -1,79 +0,0 @@ -; -; -; Nim's Runtime Library -; (c) Copyright 2015 Rokas Kupstys -; -; See the file "copying.txt", included in this -; distribution, for details about the copyright. -; - -section ".text" executable -public narch_getRegisters -public @narch_getRegisters@4 -public narch_setjmp -public @narch_setjmp@4 -public narch_longjmp -public @narch_longjmp@8 -public narch_coroSwitchStack -public @narch_coroSwitchStack@4 -public narch_coroRestoreStack -public @narch_coroRestoreStack@0 - -@narch_getRegisters@4: -narch_getRegisters: - mov [ecx], eax - mov [ecx+4], ebx - mov [ecx+8], ecx - mov [ecx+0Ch], ebp - mov [ecx+10h], esp - mov [ecx+14h], edi - mov [ecx+18h], esi - ret - - -@narch_setjmp@4: -narch_setjmp: - ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. - mov [ecx], ebx - mov [ecx+4], esi - mov [ecx+8], edi - mov [ecx+0Ch], ebp - lea eax, [esp+4] - mov [ecx+10h], eax - mov eax, [esp] - mov [ecx+14h], eax - xor eax, eax - ret - - -@narch_longjmp@8: -narch_longjmp: - ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. - mov eax, edx - test eax, eax - jnz @F - inc eax -@@: - mov ebx, [ecx] - mov esi, [ecx+4] - mov edi, [ecx+8] - mov ebp, [ecx+0Ch] - mov esp, [ecx+10h] - mov edx, [ecx+14h] - jmp edx - - -@narch_coroSwitchStack@4: -narch_coroSwitchStack: - pop eax ; return address - mov edx, esp ; old esp for saving - mov esp, ecx ; swap stack with one passed to func - push edx ; store old stack pointer on newly switched stack - jmp eax ; return - - -@narch_coroRestoreStack@0: -narch_coroRestoreStack: - pop eax ; return address - pop esp ; resture old stack pointer - jmp eax ; return diff --git a/lib/arch/ms_amd64.asm b/lib/arch/ms_amd64.asm deleted file mode 100644 index 0503b31c9..000000000 --- a/lib/arch/ms_amd64.asm +++ /dev/null @@ -1,90 +0,0 @@ -; -; -; Nim's Runtime Library -; (c) Copyright 2015 Rokas Kupstys -; -; See the file "copying.txt", included in this -; distribution, for details about the copyright. -; - -format MS64 COFF - -section ".text" executable align 16 -public narch_getRegisters -public narch_setjmp -public narch_longjmp -public narch_coroSwitchStack -public narch_coroRestoreStack - - -narch_getRegisters: - mov [rcx], rax - mov [rcx+8], rbx - mov [rcx+10h], rcx - mov [rcx+18h], rdx - mov [rcx+20h], rsi - mov [rcx+28h], rdi - mov [rcx+30h], rbp - mov [rcx+38h], rsp - mov rax, [rsp] - mov [rcx+40h], rax ; rip - mov [rcx+48h], r8 - mov [rcx+50h], r9 - mov [rcx+58h], r10 - mov [rcx+60h], r11 - mov [rcx+68h], r12 - mov [rcx+70h], r13 - mov [rcx+78h], r14 - mov [rcx+80h], r15 - ret - - -narch_setjmp: - ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. - mov [rcx], rbx ; rcx is jmp_buf, move registers onto it - mov [rcx+8], rbp - mov [rcx+10h], r12 - mov [rcx+18h], r13 - mov [rcx+20h], r14 - mov [rcx+28h], r15 - lea rdx, [rsp+8] ; this is our rsp WITHOUT current ret addr - mov [rcx+30h], rdx - mov rdx, [rsp] ; save return addr ptr for new rip - mov [rcx+38h], rdx - mov [rcx+40h], rsi - mov [rcx+48h], rdi - xor rax, rax ; always return 0 - ret - -narch_longjmp: - ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. - mov rax, rdx ; val will be longjmp return - test rax, rax - jnz @F - inc rax ; if val==0, val=1 per longjmp semantics -@@: - mov rbx, [rcx] ; rax is the jmp_buf, restore regs from it - mov rbp, [rcx+8] - mov r12, [rcx+10h] - mov r13, [rcx+18h] - mov r14, [rcx+20h] - mov r15, [rcx+28h] - mov rsp, [rcx+30h] ; this ends up being the stack pointer - mov rdx, [rcx+38h] ; this is the instruction pointer - jmp rdx ; goto saved address without altering rsp - - -narch_coroSwitchStack: - pop rax ; return address - mov rdx, rsp ; old rsp for saving - mov rsp, rcx ; swap stack with one passed to func - push rdx ; store old stack pointer on newly switched stack - sub rsp, 28h ; stack alignment + shadow space - jmp rax ; return - - -narch_coroRestoreStack: - pop rax ; return address - add rsp, 28h ; stack alignment + shadow space - pop rsp ; resture old stack pointer - jmp rax ; return diff --git a/lib/arch/ms_i386.asm b/lib/arch/ms_i386.asm deleted file mode 100644 index a31a698d1..000000000 --- a/lib/arch/ms_i386.asm +++ /dev/null @@ -1,12 +0,0 @@ -; -; -; Nim's Runtime Library -; (c) Copyright 2015 Rokas Kupstys -; -; See the file "copying.txt", included in this -; distribution, for details about the copyright. -; - -format MS COFF - -include 'i386.asm' diff --git a/lib/arch/unix_amd64.asm b/lib/arch/unix_amd64.asm deleted file mode 100644 index 3005c150c..000000000 --- a/lib/arch/unix_amd64.asm +++ /dev/null @@ -1,89 +0,0 @@ -; -; -; Nim's Runtime Library -; (c) Copyright 2015 Rokas Kupstys -; -; See the file "copying.txt", included in this -; distribution, for details about the copyright. -; - -format ELF64 - -section ".text" executable align 16 -public narch_getRegisters -public narch_setjmp -public narch_longjmp -public narch_coroSwitchStack -public narch_coroRestoreStack - - -narch_getRegisters: - mov [rdi], rax - mov [rdi+8], rbx - mov [rdi+10h], rcx - mov [rdi+18h], rdx - mov [rdi+20h], rsi - mov [rdi+28h], rdi - mov [rdi+30h], rbp - mov [rdi+38h], rsp - mov rax, [rsp] - mov [rdi+40h], rax ; rip - mov [rdi+48h], r8 - mov [rdi+50h], r9 - mov [rdi+58h], r10 - mov [rdi+60h], r11 - mov [rdi+68h], r12 - mov [rdi+70h], r13 - mov [rdi+78h], r14 - mov [rdi+80h], r15 - ret - - -narch_setjmp: - ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. - mov [rdi], rbx ; rdi is jmp_buf, move registers onto it - mov [rdi+8], rbp - mov [rdi+10h], r12 - mov [rdi+18h], r13 - mov [rdi+20h], r14 - mov [rdi+28h], r15 - lea rdx, [rsp+8] ; this is our rsp WITHOUT current ret addr - mov [rdi+30h], rdx - mov rdx, [rsp] ; save return addr ptr for new rip - mov [rdi+38h], rdx - xor rax, rax ; always return 0 - ret - - -narch_longjmp: - ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. - mov rax, rsi ; val will be longjmp return - test rax, rax - jnz @F - inc rax ; if val==0, val=1 per longjmp semantics -@@: - mov rbx, [rdi] ; rdi is the jmp_buf, restore regs from it - mov rbp, [rdi+8] - mov r12, [rdi+10h] - mov r13, [rdi+18h] - mov r14, [rdi+20h] - mov r15, [rdi+28h] - mov rsp, [rdi+30h] ; this ends up being the stack pointer - mov rdx, [rdi+38h] ; this is the instruction pointer - jmp rdx ; goto saved address without altering rsp - - -narch_coroSwitchStack: - pop rsi ; return address - mov rdx, rsp ; old rsp for saving - mov rsp, rdi ; swap stack with one passed to func - push rdx ; store old stack pointer on newly switched stack - sub rsp, 8h ; stack alignment - jmp rsi ; return - - -narch_coroRestoreStack: - pop rsi ; return address - add rsp, 8h ; stack alignment - pop rsp ; resture old stack pointer - jmp rsi ; return diff --git a/lib/arch/unix_i386.asm b/lib/arch/unix_i386.asm deleted file mode 100644 index 278679067..000000000 --- a/lib/arch/unix_i386.asm +++ /dev/null @@ -1,12 +0,0 @@ -; -; -; Nim's Runtime Library -; (c) Copyright 2015 Rokas Kupstys -; -; See the file "copying.txt", included in this -; distribution, for details about the copyright. -; - -format ELF - -include 'i386.asm' diff --git a/lib/arch/x86/amd64.S b/lib/arch/x86/amd64.S new file mode 100644 index 000000000..47a26f627 --- /dev/null +++ b/lib/arch/x86/amd64.S @@ -0,0 +1,96 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Rokas Kupstys +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# +# Partially based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. + +.globl narch_coroExecWithStack +.globl narch_setjmp +.globl narch_longjmp +.text + + +# SysV ABI - first argument is rdi. +# MS ABI - first argument is rcx. +#if defined(__MINGW32__) || defined(__MINGW64__) + #define REG_ARG1 rcx + #define REG_ARG2 rdx +#else + #define REG_ARG1 rdi + #define REG_ARG2 rsi +#endif + + +narch_coroExecWithStack: + mov %REG_ARG2, %rsp # swap stack with one passed to func + sub $0x30, %rsp # shadow space (for ms ABI) 0x20 + 0x10 for possible misalignment + and $-0x10, %rsp # 16-byte stack alignment + call *%REG_ARG1 + + +narch_setjmp: + add $0x10, %REG_ARG1 # 16-byte alignment + and $-0x10, %REG_ARG1 + mov %rbx, 0x00(%REG_ARG1) # jmp_buf, move registers onto it + mov %rbp, 0x08(%REG_ARG1) + mov %r12, 0x10(%REG_ARG1) + mov %r13, 0x18(%REG_ARG1) + mov %r14, 0x20(%REG_ARG1) + mov %r15, 0x28(%REG_ARG1) + lea 0x08(%rsp), %rdx # this is our rsp WITHOUT current ret addr + mov %rdx, 0x30(%REG_ARG1) + mov (%rsp), %rdx # save return addr ptr for new rip + mov %rdx, 0x38(%REG_ARG1) + mov %rsi, 0x40(%REG_ARG1) + mov %rdi, 0x48(%REG_ARG1) +#if defined(__MINGW32__) || defined(__MINGW64__) + movaps %xmm6, 0x50(%REG_ARG1) + movaps %xmm7, 0x60(%REG_ARG1) + movaps %xmm8, 0x70(%REG_ARG1) + movaps %xmm9, 0x80(%REG_ARG1) + movaps %xmm10, 0x90(%REG_ARG1) + movaps %xmm11, 0xA0(%REG_ARG1) + movaps %xmm12, 0xB0(%REG_ARG1) + movaps %xmm13, 0xC0(%REG_ARG1) + movaps %xmm14, 0xD0(%REG_ARG1) + movaps %xmm15, 0xE0(%REG_ARG1) +#endif + xor %rax, %rax # always return 0 + ret + + +narch_longjmp: + add $0x10, %REG_ARG1 # 16-byte alignment + and $-0x10, %REG_ARG1 # + mov %REG_ARG2, %rax # val will be longjmp return + test %rax, %rax + jnz narch_longjmp_1 + inc %rax # if val==0, val=1 per longjmp semantics +narch_longjmp_1: + mov 0x00(%REG_ARG1), %rbx # jmp_buf, restore regs from it + mov 0x08(%REG_ARG1), %rbp + mov 0x10(%REG_ARG1), %r12 + mov 0x18(%REG_ARG1), %r13 + mov 0x20(%REG_ARG1), %r14 + mov 0x28(%REG_ARG1), %r15 + mov 0x30(%REG_ARG1), %rsp # this ends up being the stack pointer + mov 0x38(%REG_ARG1), %rdx # this is the instruction pointer + mov 0x40(%REG_ARG1), %rsi + mov 0x48(%REG_ARG1), %rdi +#if defined(__MINGW32__) || defined(__MINGW64__) + movaps 0x50(%REG_ARG1), %xmm6 + movaps 0x60(%REG_ARG1), %xmm7 + movaps 0x70(%REG_ARG1), %xmm8 + movaps 0x80(%REG_ARG1), %xmm9 + movaps 0x90(%REG_ARG1), %xmm10 + movaps 0xA0(%REG_ARG1), %xmm11 + movaps 0xB0(%REG_ARG1), %xmm12 + movaps 0xC0(%REG_ARG1), %xmm13 + movaps 0xD0(%REG_ARG1), %xmm14 + movaps 0xE0(%REG_ARG1), %xmm15 +#endif + jmp *%rdx # goto saved address without altering rsp diff --git a/lib/arch/x86/i386.S b/lib/arch/x86/i386.S new file mode 100644 index 000000000..d7de4a4c3 --- /dev/null +++ b/lib/arch/x86/i386.S @@ -0,0 +1,64 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Rokas Kupstys +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# +# Partially based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. + +.globl narch_coroExecWithStack +.globl narch_setjmp +.globl narch_longjmp +#if defined(__MINGW32__) || defined(__MINGW64__) +.globl @narch_coroExecWithStack@8 +.globl @narch_setjmp@4 +.globl @narch_longjmp@8 +#endif +.text + + +#if defined(__MINGW32__) || defined(__MINGW64__) +@narch_coroExecWithStack@8: +#endif +narch_coroExecWithStack: + mov %edx, %esp # swap stack with one passed to func + sub $0x10, %esp # 16-byte alignment + and $-0x10, %esp # + sub $4, %esp # Simulate misalignment caused by return addr + jmp *%ecx + + +#if defined(__MINGW32__) || defined(__MINGW64__) +@narch_setjmp@4: +#endif +narch_setjmp: + mov %ebx, (%ecx) + mov %esi, 0x04(%ecx) + mov %edi, 0x08(%ecx) + mov %ebp, 0x0C(%ecx) + lea 0x04(%esp), %eax + mov %eax, 0x10(%ecx) + mov (%esp), %eax + mov %eax, 0x14(%ecx) + xor %eax, %eax + ret + + +#if defined(__MINGW32__) || defined(__MINGW64__) +@narch_longjmp@8: +#endif +narch_longjmp: + mov %edx, %eax + test %eax, %eax + jnz narch_longjmp_1 + inc %eax +narch_longjmp_1: + mov (%ecx), %ebx + mov 0x04(%ecx), %esi + mov 0x08(%ecx), %edi + mov 0x0C(%ecx), %ebp + mov 0x10(%ecx), %esp + mov 0x14(%ecx), %edx + jmp *%edx diff --git a/lib/pure/coro.nim b/lib/pure/coro.nim index 0373708d0..c9bea2eb4 100644 --- a/lib/pure/coro.nim +++ b/lib/pure/coro.nim @@ -6,138 +6,303 @@ # See the file "copying.txt", included in this # distribution, for details about the copyright. # +## Nim coroutines implementation supports several context switching methods: +## ucontext: available on unix and alike (default) +## setjmp: available on unix and alike (x86/64 only) +## Fibers: available and required on windows. +## +## -d:nimCoroutines Required to build this module. +## -d:nimCoroutinesUcontext Use ucontext backend. +## -d:nimCoroutinesSetjmp Use setjmp backend. +## -d:nimCoroutinesSetjmpBundled Use bundled setjmp implementation. when not defined(nimCoroutines) and not defined(nimdoc): {.error: "Coroutines require -d:nimCoroutines".} -import os, times +import os import macros -import arch import lists +include system/timers const defaultStackSize = 512 * 1024 -type Coroutine = ref object - # prev: ptr Coroutine - # next: ptr Coroutine - ctx: JmpBuf - fn: proc() - started: bool - lastRun: float - sleepTime: float - stack: pointer - stacksize: int - -var coroutines = initDoublyLinkedList[Coroutine]() -var current: Coroutine -var mainCtx: JmpBuf - - proc GC_addStack(starts: pointer) {.cdecl, importc.} proc GC_removeStack(starts: pointer) {.cdecl, importc.} proc GC_setCurrentStack(starts, pos: pointer) {.cdecl, importc.} -proc start*(c: proc(), stacksize: int=defaultStackSize) = - ## Adds coroutine to event loop. It does not run immediately. - var coro = Coroutine() - coro.fn = c - while coro.stack == nil: - coro.stack = alloc0(stacksize) - coro.stacksize = stacksize - coroutines.append(coro) +const + CORO_BACKEND_UCONTEXT = 0 + CORO_BACKEND_SETJMP = 1 + CORO_BACKEND_FIBERS = 2 + +when defined(windows): + const coroBackend = CORO_BACKEND_FIBERS + when defined(nimCoroutinesUcontext): + {.warning: "ucontext coroutine backend is not available on windows, defaulting to fibers.".} + when defined(nimCoroutinesSetjmp): + {.warning: "setjmp coroutine backend is not available on windows, defaulting to fibers.".} +elif defined(nimCoroutinesSetjmp) or defined(nimCoroutinesSetjmpBundled): + const coroBackend = CORO_BACKEND_SETJMP +else: + const coroBackend = CORO_BACKEND_UCONTEXT + +when coroBackend == CORO_BACKEND_FIBERS: + import windows.winlean + type + Context = pointer + Fiber {.final, pure.} = object + parameter: pointer + pad1: pointer + stackStart: pointer + stackEnd: pointer + +elif coroBackend == CORO_BACKEND_UCONTEXT: + type + stack_t {.importc, header: "<sys/ucontext.h>".} = object + ss_sp: pointer + ss_flags: int + ss_size: int + + ucontext_t {.importc, header: "<sys/ucontext.h>".} = object + uc_link: ptr ucontext_t + uc_stack: stack_t + + Context = ucontext_t + + proc getcontext(context: var ucontext_t): int32 {.importc, header: "<sys/ucontext.h>".} + proc setcontext(context: var ucontext_t): int32 {.importc, header: "<sys/ucontext.h>".} + proc swapcontext(fromCtx, toCtx: var ucontext_t): int32 {.importc, header: "<sys/ucontext.h>".} + proc makecontext(context: var ucontext_t, fn: pointer, argc: int32) {.importc, header: "<sys/ucontext.h>", varargs.} + +elif coroBackend == CORO_BACKEND_SETJMP: + proc coroExecWithStack*(fn: pointer, stack: pointer) {.noreturn, importc: "narch_$1", fastcall.} + when defined(amd64): + {.compile: "../arch/x86/amd64.S".} + elif defined(i386): + {.compile: "../arch/x86/i386.S".} + else: + # coroExecWithStack is defined in assembly. To support other platforms + # please provide implementation of this procedure. + {.error: "Unsupported architecture.".} + + when defined(nimCoroutinesSetjmpBundled): + # Use setjmp/longjmp implementation shipped with compiler. + when defined(amd64): + type + JmpBuf = array[0x50 + 0x10, uint8] + elif defined(i386): + type + JmpBuf = array[0x1C, uint8] + else: + # Bundled setjmp/longjmp are defined in assembly. To support other + # platforms please provide implementations of these procedures. + {.error: "Unsupported architecture.".} + + proc setjmp(ctx: var JmpBuf): int {.importc: "narch_$1".} + proc longjmp(ctx: JmpBuf, ret=1) {.importc: "narch_$1".} + else: + # Use setjmp/longjmp implementation provided by the system. + type + JmpBuf {.importc: "jmp_buf", header: "<setjmp.h>".} = object + + proc setjmp(ctx: var JmpBuf): int {.importc, header: "<setjmp.h>".} + proc longjmp(ctx: JmpBuf, ret=1) {.importc, header: "<setjmp.h>".} + + type + Context = JmpBuf + +when defined(unix): + # GLibc fails with "*** longjmp causes uninitialized stack frame ***" because + # our custom stacks are not initialized to a magic value. + {.passC: "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0"} + +const + CORO_EXECUTING = 1 + CORO_FINISHED = 2 + +type + Stack = object + start: pointer + ends: pointer + size: int + + Coroutine = ref object + execContext: Context + fn: proc() + state: int + lastRun: Ticks + sleepTime: float + stack: Stack + + CoroutineLoopContext = ref object + coroutines: DoublyLinkedList[Coroutine] + current: DoublyLinkedNode[Coroutine] + loop: Coroutine + +# Per-thread coroutine loop state. +var ctx: CoroutineLoopContext + +proc getCurrent(): Coroutine = + ## Returns current executing coroutine object. + var node = ctx.current + if node != nil: + return node.value + return nil + +proc initialize() = + ## Initializes coroutine state of current thread. + if ctx == nil: + ctx = CoroutineLoopContext() + ctx.coroutines = initDoublyLinkedList[Coroutine]() + ctx.loop = Coroutine() + ctx.loop.state = CORO_EXECUTING + when coroBackend == CORO_BACKEND_FIBERS: + ctx.loop.execContext = ConvertThreadToFiberEx(nil, FIBER_FLAG_FLOAT_SWITCH) + +proc runCurrentTask() + +proc switchTo(current, to: Coroutine) = + ## Switches execution from `current` into `to` context. + to.lastRun = getTicks() + when coroBackend == CORO_BACKEND_FIBERS: + SwitchToFiber(to.execContext) + elif coroBackend == CORO_BACKEND_UCONTEXT: + discard swapcontext(current.execContext, to.execContext) + elif coroBackend == CORO_BACKEND_SETJMP: + var res = setjmp(current.execContext) + if res == 0: + if to.state == CORO_EXECUTING: + # Coroutine is resumed. + longjmp(to.execContext, 1) + elif to.state == CORO_CREATED: + # Coroutine is started. + coroExecWithStack(runCurrentTask, to.stack.ends) + doAssert false + else: + {.error: "Invalid coroutine backend set.".} -{.push stackTrace: off.} proc suspend*(sleepTime: float=0) = ## Stops coroutine execution and resumes no sooner than after ``sleeptime`` seconds. ## Until then other coroutines are executed. - ## - ## This is similar to a `yield`:idx:, or a `yieldFrom`:idx in Python. - var oldFrame = getFrame() + var frame = getFrame() var sp {.volatile.}: pointer - GC_setCurrentStack(current.stack, cast[pointer](addr sp)) + var current = getCurrent() + GC_setCurrentStack(current.stack.start, cast[pointer](addr sp)) current.sleepTime = sleepTime - current.lastRun = epochTime() - if setjmp(current.ctx) == 0: - longjmp(mainCtx, 1) - setFrame(oldFrame) -{.pop.} + switchTo(current, ctx.loop) + setFrame(frame) + +proc runCurrentTask() = + ## Starts execution of current coroutine and updates it's state through coroutine's life. + var current = getCurrent() + current.state = CORO_EXECUTING + current.fn() # Start coroutine execution + current.state = CORO_FINISHED + suspend(0) # Exit coroutine without returning from coroExecWithStack() + doAssert false + +proc start*(c: proc(), stacksize: int=defaultStackSize) = + ## Schedule coroutine for execution. It does not run immediately. + if ctx == nil: + initialize() + + var coro = Coroutine() + coro.fn = c + when coroBackend == CORO_BACKEND_FIBERS: + coro.execContext = CreateFiberEx(stacksize, stacksize, + FIBER_FLAG_FLOAT_SWITCH, (proc(p: pointer): void {.stdcall.} = runCurrentTask()), nil) + var fiber = cast[ptr Fiber](coro.execContext) + coro.stack.start = fiber.stackStart + coro.stack.ends = fiber.stackEnd + coro.stack.size = stacksize + else: + var stack: pointer + while stack == nil: + stack = alloc0(stacksize) + coro.stack.start = stack + coro.stack.ends = cast[pointer](cast[ByteAddress](stack) + stacksize) + when coroBackend == CORO_BACKEND_UCONTEXT: + discard getcontext(coro.execContext) + coro.execContext.uc_stack.ss_sp = coro.stack.ends + coro.execContext.uc_stack.ss_size = coro.stack.size + coro.execContext.uc_link = addr ctx.loop.execContext + makecontext(coro.execContext, runCurrentTask, 0) + coro.stack.size = stacksize + GC_addStack(coro.stack.ends) + ctx.coroutines.append(coro) proc run*() = - ## Starts main event loop which exits when all coroutines exit. Calling this proc - ## starts execution of first coroutine. - var node = coroutines.head - var minDelay: int = 0 # in milliseconds - var frame: PFrame - while node != nil: - var coro = node.value - current = coro - os.sleep(minDelay) - - var remaining = int((coro.sleepTime - (epochTime() - coro.lastRun)) * 1000) - if remaining <= 0: - remaining = 0 - let res = setjmp(mainCtx) - if res == 0: - frame = getFrame() - if coro.started: # coroutine resumes - longjmp(coro.ctx, 1) - else: - coro.started = true # coroutine starts - var stackEnd = cast[pointer](cast[ByteAddress](coro.stack) + coro.stacksize) - GC_addStack(coro.stack) - coroSwitchStack(stackEnd) - coro.fn() - coroRestoreStack() - GC_removeStack(coro.stack) - var next = node.prev - coroutines.remove(node) - dealloc(coro.stack) - node = next - setFrame(frame) - else: - setFrame(frame) + initialize() + ## Starts main coroutine scheduler loop which exits when all coroutines exit. + ## Calling this proc starts execution of first coroutine. + ctx.current = ctx.coroutines.head + var minDelay: float = 0 + while ctx.current != nil: + var current = getCurrent() - elif remaining > 0: + var remaining = current.sleepTime - (float(getTicks() - current.lastRun) / 1_000_000_000) + if remaining <= 0: + # Save main loop context. Suspending coroutine will resume after this statement with + var frame = getFrame() + switchTo(ctx.loop, current) + setFrame(frame) + else: if minDelay > 0 and remaining > 0: minDelay = min(remaining, minDelay) else: minDelay = remaining - if node == nil or node.next == nil: - node = coroutines.head + if current.state == CORO_FINISHED: + GC_removeStack(current.stack.start) + var next = ctx.current.prev + ctx.coroutines.remove(ctx.current) + when coroBackend != CORO_BACKEND_FIBERS: + dealloc(current.stack.start) + current.stack.start = nil + current.stack.ends = nil + ctx.current = next + elif ctx.current == nil or ctx.current.next == nil: + ctx.current = ctx.coroutines.head + os.sleep(int(minDelay * 1000)) else: - node = node.next + ctx.current = ctx.current.next proc alive*(c: proc()): bool = ## Returns ``true`` if coroutine has not returned, ``false`` otherwise. - for coro in items(coroutines): + for coro in items(ctx.coroutines): if coro.fn == c: - return true + return coro.state != CORO_FINISHED proc wait*(c: proc(), interval=0.01) = ## Returns only after coroutine ``c`` has returned. ``interval`` is time in seconds how often. while alive(c): - suspend interval - -when defined(nimCoroutines) and isMainModule: - var stackCheckValue = 1100220033 - proc c2() - - proc c1() = - for i in 0 .. 3: - echo "c1" - suspend 0.05 - echo "c1 exits" + suspend(interval) +when isMainModule: + var + stackCheckValue = 1100220033 + first: float64 = 0 + second: float64 = 1 + steps = 10 + i: int + order = newSeq[int](10) - proc c2() = - for i in 0 .. 3: - echo "c2" - suspend 0.025 - wait(c1) - echo "c2 exits" + proc Fibonacci(id: int, sleep: float32) = + var sleepTime: float + while steps > 0: + echo id, " executing, slept for ", sleepTime + order[i] = id + i += 1 + steps -= 1 + swap first, second + second += first + var sleepStart = getTicks() + suspend(sleep) + sleepTime = float(getTicks() - sleepStart) / 1_000_000_000 - start(c1) - start(c2) + start(proc() = Fibonacci(1, 0.01)) + start(proc() = Fibonacci(2, 0.021)) run() - echo "done ", stackCheckValue + doAssert stackCheckValue == 1100220033 + doAssert first == 55.0 + doAssert order == @[1, 2, 1, 1, 2, 1, 1, 2, 1, 1] diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 703146484..c17442a97 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -12,9 +12,6 @@ # Refcounting + Mark&Sweep. Complex algorithms avoided. # Been there, done that, didn't work. -when defined(nimCoroutines): - import arch - {.push profiler:off.} const diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index 6ab6bd920..4116a8525 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -277,11 +277,8 @@ else: # Used to traverse the stack and registers assuming # that 'setjmp' will save registers in the C stack. type PStackSlice = ptr array[0..7, pointer] - var registers {.noinit.}: Registers - getRegisters(registers) - for i in registers.low .. registers.high: - gcMark(gch, cast[PPointer](registers[i])) - + var registers {.noinit.}: C_JmpBuf + discard c_setjmp(registers) for stack in items(gch.stack): stack.maxStackSize = max(stack.maxStackSize, stackSize(stack.starts)) var max = cast[ByteAddress](stack.starts) |