summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/arch/arch.nim62
-rw-r--r--lib/arch/i386.asm79
-rw-r--r--lib/arch/ms_amd64.asm90
-rw-r--r--lib/arch/ms_i386.asm12
-rw-r--r--lib/arch/unix_amd64.asm89
-rw-r--r--lib/arch/unix_i386.asm12
-rw-r--r--lib/arch/x86/amd64.S96
-rw-r--r--lib/arch/x86/i386.S64
-rw-r--r--lib/pure/coro.nim355
-rw-r--r--lib/system/gc.nim3
-rw-r--r--lib/system/gc_common.nim7
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)