mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-19 05:50:30 +00:00
Merge pull request #5317 from rokups/feature/coroutines
Coroutine improvements
This commit is contained in:
12
.travis.yml
12
.travis.yml
@@ -22,18 +22,16 @@ addons:
|
||||
|
||||
before_script:
|
||||
- set -e
|
||||
- curl --out fasm-1.71.39.tgz https://nim-lang.org/download/fasm-1.71.39.tgz
|
||||
- tar xvf fasm-1.71.39.tgz
|
||||
- git clone --depth 1 https://github.com/nim-lang/csources.git
|
||||
- cd csources
|
||||
- sh build.sh
|
||||
- cd ..
|
||||
- sed -i -e 's,cc = gcc,cc = clang,' config/nim.cfg
|
||||
- export PATH=$(pwd)/bin:$(pwd)/fasm:$PATH
|
||||
- export PATH=$(pwd)/bin:$PATH
|
||||
script:
|
||||
- nim c koch
|
||||
- ./koch boot
|
||||
- ./koch boot -d:release
|
||||
- ./koch boot -d:nimCoroutines
|
||||
- ./koch boot -d:release -d:nimCoroutines
|
||||
- ./koch nimble
|
||||
- nim e tests/test_nimscript.nims
|
||||
- nimble install zip -y
|
||||
@@ -41,7 +39,7 @@ script:
|
||||
- nimble install sdl1
|
||||
- nimble install jester@#head
|
||||
- nimble install niminst
|
||||
- nim c --taintMode:on tests/testament/tester
|
||||
- tests/testament/tester --pedantic all
|
||||
- nim c --taintMode:on -d:nimCoroutines tests/testament/tester
|
||||
- tests/testament/tester --pedantic all -d:nimCoroutines
|
||||
- ./koch csource
|
||||
- ./koch xz
|
||||
|
||||
19
appveyor.yml
19
appveyor.yml
@@ -3,7 +3,6 @@ version: '{build}'
|
||||
cache:
|
||||
- x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z
|
||||
- sqlite-dll-win64-x64-3160200.zip
|
||||
- fasmw17159.zip
|
||||
# - i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z
|
||||
|
||||
matrix:
|
||||
@@ -16,18 +15,12 @@ environment:
|
||||
MINGW_ARCHIVE: x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z
|
||||
SQLITE_URL: http://www.sqlite.org/2017/sqlite-dll-win64-x64-3160200.zip
|
||||
SQLITE_ARCHIVE: sqlite-dll-win64-x64-3160200.zip
|
||||
FASM_DIR: fasm
|
||||
FASM_URL: https://flatassembler.net/fasmw17159.zip
|
||||
FASM_ARCHIVE: fasmw17159.zip
|
||||
platform: x64
|
||||
# - MINGW_DIR: mingw32
|
||||
# MINGW_URL: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/4.9.2/threads-win32/dwarf/i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z/download
|
||||
# MINGW_ARCHIVE: i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z
|
||||
# SQLITE_URL: http://www.sqlite.org/2017/sqlite-dll-win32-x86-3160200.zip
|
||||
# SQLITE_ARCHIVE: sqlite-dll-win32-x86-3160200.zip
|
||||
# FASM_DIR: fasm
|
||||
# FASM_URL: https://flatassembler.net/fasmw17159.zip
|
||||
# FASM_ARCHIVE: fasmw17159.zip
|
||||
# platform: x86
|
||||
|
||||
install:
|
||||
@@ -38,9 +31,7 @@ install:
|
||||
- 7z x -y "%SQLITE_ARCHIVE%" -o"%CD%\DIST"> nul
|
||||
- IF not exist "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%"
|
||||
- 7z x -y "%MINGW_ARCHIVE%" -o"%CD%\DIST"> nul
|
||||
- IF not exist "%FASM_ARCHIVE%" appveyor DownloadFile "%FASM_URL%" -FileName "%FASM_ARCHIVE%"
|
||||
- 7z x -y "%FASM_ARCHIVE%" -o"%CD%\DIST\%FASM_DIR%" > nul
|
||||
- SET PATH=%CD%\DIST\%MINGW_DIR%\BIN;%CD%\BIN;%CD%\DIST\%FASM_DIR%;%PATH%
|
||||
- SET PATH=%CD%\DIST\%MINGW_DIR%\BIN;%CD%\BIN;%PATH%
|
||||
- IF "%PLATFORM%" == "x64" ( copy C:\OpenSSL-Win64\libeay32.dll %CD%\BIN\libeay64.dll & copy C:\OpenSSL-Win64\libeay32.dll %CD%\BIN\libeay32.dll & copy C:\OpenSSL-Win64\libssl32.dll %CD%\BIN\libssl64.dll & copy C:\OpenSSL-Win64\libssl32.dll %CD%\BIN\libssl32.dll )
|
||||
ELSE ( copy C:\OpenSSL-Win32\libeay32.dll %CD%\BIN\libeay32.dll & copy C:\OpenSSL-Win32\libssl32.dll %CD%\BIN\libssl32.dll )
|
||||
- IF "%PLATFORM%" == "x64" ( copy %CD%\DIST\sqlite3.dll %CD%\BIN\sqlite3_64.dll ) ELSE ( copy %CD%\DIST\sqlite3.dll %CD%\BIN\sqlite3_32.dll )
|
||||
@@ -52,8 +43,8 @@ install:
|
||||
|
||||
build_script:
|
||||
- bin\nim c koch
|
||||
- koch boot
|
||||
- koch boot -d:release
|
||||
- koch boot -d:nimCoroutines
|
||||
- koch boot -d:release -d:nimCoroutines
|
||||
- koch nimble
|
||||
- nim e tests/test_nimscript.nims
|
||||
- nimble install zip -y
|
||||
@@ -61,10 +52,10 @@ build_script:
|
||||
- nimble install sdl1
|
||||
- nimble install jester@#head
|
||||
- nimble install niminst
|
||||
- nim c --taintMode:on tests/testament/tester
|
||||
- nim c --taintMode:on -d:nimCoroutines tests/testament/tester
|
||||
|
||||
test_script:
|
||||
- tests\testament\tester --pedantic all
|
||||
- tests\testament\tester --pedantic all -d:nimCoroutines
|
||||
- koch csource
|
||||
- koch zip
|
||||
|
||||
|
||||
@@ -3,11 +3,10 @@ echo "Running on $CI_RUNNER_ID ($CI_RUNNER_DESCRIPTION) with tags $CI_RUNNER_TAG
|
||||
|
||||
# Packages
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq build-essential git libcurl4-openssl-dev libsdl1.2-dev libgc-dev nodejs fasm
|
||||
apt-get install -y -qq build-essential git libcurl4-openssl-dev libsdl1.2-dev libgc-dev nodejs
|
||||
|
||||
gcc -v
|
||||
|
||||
fasm -v
|
||||
export PATH=$(pwd)/bin:$PATH
|
||||
|
||||
# Nimble deps
|
||||
|
||||
@@ -642,10 +642,6 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
of "experimental":
|
||||
expectNoArg(switch, arg, pass, info)
|
||||
gExperimentalMode = true
|
||||
of "assembler":
|
||||
cAssembler = nameToCC(arg)
|
||||
if cAssembler notin cValidAssemblers:
|
||||
localError(info, errGenerated, "'$1' is not a valid assembler." % [arg])
|
||||
of "nocppexceptions":
|
||||
expectNoArg(switch, arg, pass, info)
|
||||
incl(gGlobalOptions, optNoCppExceptions)
|
||||
|
||||
@@ -21,7 +21,7 @@ import
|
||||
type
|
||||
TSystemCC* = enum
|
||||
ccNone, ccGcc, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc,
|
||||
ccTcc, ccPcc, ccUcc, ccIcl, asmFasm
|
||||
ccTcc, ccPcc, ccUcc, ccIcl
|
||||
TInfoCCProp* = enum # properties of the C compiler:
|
||||
hasSwitchRange, # CC allows ranges in switch statements (GNU C)
|
||||
hasComputedGoto, # CC has computed goto (GNU C extension)
|
||||
@@ -320,31 +320,6 @@ compiler ucc:
|
||||
packedPragma: "", # XXX: not supported yet
|
||||
props: {})
|
||||
|
||||
# fasm assembler
|
||||
compiler fasm:
|
||||
result = (
|
||||
name: "fasm",
|
||||
objExt: "o",
|
||||
optSpeed: "",
|
||||
optSize: "",
|
||||
compilerExe: "fasm",
|
||||
cppCompiler: "fasm",
|
||||
compileTmpl: "$file $objfile",
|
||||
buildGui: "",
|
||||
buildDll: "",
|
||||
buildLib: "",
|
||||
linkerExe: "",
|
||||
linkTmpl: "",
|
||||
includeCmd: "",
|
||||
linkDirCmd: "",
|
||||
linkLibCmd: "",
|
||||
debug: "",
|
||||
pic: "",
|
||||
asmStmtFrmt: "",
|
||||
structStmtFmt: "",
|
||||
packedPragma: "",
|
||||
props: {})
|
||||
|
||||
const
|
||||
CC*: array[succ(low(TSystemCC))..high(TSystemCC), TInfoCC] = [
|
||||
gcc(),
|
||||
@@ -358,22 +333,17 @@ const
|
||||
tcc(),
|
||||
pcc(),
|
||||
ucc(),
|
||||
icl(),
|
||||
fasm()]
|
||||
icl()]
|
||||
|
||||
hExt* = ".h"
|
||||
|
||||
var
|
||||
cCompiler* = ccGcc # the used compiler
|
||||
cAssembler* = ccNone
|
||||
gMixedMode*: bool # true if some module triggered C++ codegen
|
||||
cIncludes*: seq[string] = @[] # directories to search for included files
|
||||
cLibs*: seq[string] = @[] # directories to search for lib files
|
||||
cLinkedLibs*: seq[string] = @[] # libraries to link
|
||||
|
||||
const
|
||||
cValidAssemblers* = {asmFasm}
|
||||
|
||||
# implementation
|
||||
|
||||
proc libNameTmpl(): string {.inline.} =
|
||||
@@ -577,21 +547,6 @@ proc getLinkerExe(compiler: TSystemCC): string =
|
||||
|
||||
proc getCompileCFileCmd*(cfile: Cfile): string =
|
||||
var c = cCompiler
|
||||
if cfile.cname.endswith(".asm"):
|
||||
var customAssembler = getConfigVar("assembler")
|
||||
if customAssembler.len > 0:
|
||||
c = nameToCC(customAssembler)
|
||||
else:
|
||||
if targetCPU == cpuI386 or targetCPU == cpuAmd64:
|
||||
c = asmFasm
|
||||
else:
|
||||
c = ccNone
|
||||
|
||||
if c == ccNone:
|
||||
rawMessage(errExternalAssemblerNotFound, "")
|
||||
elif c notin cValidAssemblers:
|
||||
rawMessage(errExternalAssemblerNotValid, customAssembler)
|
||||
|
||||
var options = cFileSpecificOptions(cfile.cname)
|
||||
var exe = getConfigVar(c, ".exe")
|
||||
if exe.len == 0: exe = c.getCompilerExe(cfile.cname)
|
||||
|
||||
@@ -108,8 +108,6 @@ type
|
||||
errCannotInferReturnType,
|
||||
errGenericLambdaNotAllowed,
|
||||
errCompilerDoesntSupportTarget,
|
||||
errExternalAssemblerNotFound,
|
||||
errExternalAssemblerNotValid,
|
||||
errUser,
|
||||
warnCannotOpenFile,
|
||||
warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
|
||||
@@ -372,8 +370,6 @@ const
|
||||
"it is used as an operand to another routine and the types " &
|
||||
"of the generic paramers can be inferred from the expected signature.",
|
||||
errCompilerDoesntSupportTarget: "The current compiler \'$1\' doesn't support the requested compilation target",
|
||||
errExternalAssemblerNotFound: "External assembler not found",
|
||||
errExternalAssemblerNotValid: "External assembler '$1' is not a valid assembler",
|
||||
errUser: "$1",
|
||||
warnCannotOpenFile: "cannot open \'$1\'",
|
||||
warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored",
|
||||
|
||||
@@ -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.}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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'
|
||||
96
lib/arch/x86/amd64.S
Normal file
96
lib/arch/x86/amd64.S
Normal file
@@ -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
|
||||
64
lib/arch/x86/i386.S
Normal file
64
lib/arch/x86/i386.S
Normal file
@@ -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
|
||||
@@ -6,138 +6,291 @@
|
||||
# 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".}
|
||||
when not nimCoroutines and not defined(nimdoc):
|
||||
when defined(noNimCoroutines):
|
||||
{.error: "Coroutines can not be used with -d:noNimCoroutines"}
|
||||
else:
|
||||
{.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
|
||||
proc GC_addStack(bottom: pointer) {.cdecl, importc.}
|
||||
proc GC_removeStack(bottom: pointer) {.cdecl, importc.}
|
||||
proc GC_setActiveStack(bottom: pointer) {.cdecl, importc.}
|
||||
|
||||
var coroutines = initDoublyLinkedList[Coroutine]()
|
||||
var current: Coroutine
|
||||
var mainCtx: JmpBuf
|
||||
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
|
||||
|
||||
proc GC_addStack(starts: pointer) {.cdecl, importc.}
|
||||
proc GC_removeStack(starts: pointer) {.cdecl, importc.}
|
||||
proc GC_setCurrentStack(starts, pos: pointer) {.cdecl, importc.}
|
||||
when coroBackend == CORO_BACKEND_FIBERS:
|
||||
import windows.winlean
|
||||
type
|
||||
Context = pointer
|
||||
|
||||
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)
|
||||
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_CREATED = 0
|
||||
CORO_EXECUTING = 1
|
||||
CORO_FINISHED = 2
|
||||
|
||||
type
|
||||
Stack = object
|
||||
top: pointer # Top of the stack. Pointer used for deallocating stack if we own it.
|
||||
bottom: pointer # Very bottom of the stack, acts as unique stack identifier.
|
||||
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
|
||||
|
||||
var ctx {.threadvar.}: 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()
|
||||
# Update position of current stack so gc invoked from another stack knows how much to scan.
|
||||
GC_setActiveStack(current.stack.bottom)
|
||||
var frame = getFrameState()
|
||||
block:
|
||||
# Execution will switch to another fiber now. We do not need to update current stack
|
||||
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.bottom)
|
||||
doAssert false
|
||||
else:
|
||||
{.error: "Invalid coroutine backend set.".}
|
||||
# Execution was just resumed. Restore frame information and set active stack.
|
||||
setFrameState(frame)
|
||||
GC_setActiveStack(current.stack.bottom)
|
||||
|
||||
{.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 sp {.volatile.}: pointer
|
||||
GC_setCurrentStack(current.stack, cast[pointer](addr sp))
|
||||
var current = getCurrent()
|
||||
current.sleepTime = sleepTime
|
||||
current.lastRun = epochTime()
|
||||
if setjmp(current.ctx) == 0:
|
||||
longjmp(mainCtx, 1)
|
||||
setFrame(oldFrame)
|
||||
{.pop.}
|
||||
switchTo(current, ctx.loop)
|
||||
|
||||
proc runCurrentTask() =
|
||||
## Starts execution of current coroutine and updates it's state through coroutine's life.
|
||||
var sp {.volatile.}: pointer
|
||||
sp = addr(sp)
|
||||
block:
|
||||
var current = getCurrent()
|
||||
current.stack.bottom = sp
|
||||
# Execution of new fiber just started. Since it was entered not through `switchTo` we
|
||||
# have to set active stack here as well. GC_removeStack() has to be called in main loop
|
||||
# because we still need stack available in final suspend(0) call from which we will not
|
||||
# return.
|
||||
GC_addStack(sp)
|
||||
# Activate current stack because we are executing in a new coroutine.
|
||||
GC_setActiveStack(sp)
|
||||
current.state = CORO_EXECUTING
|
||||
try:
|
||||
current.fn() # Start coroutine execution
|
||||
except:
|
||||
echo "Unhandled exception in coroutine."
|
||||
writeStackTrace()
|
||||
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)
|
||||
coro.stack.size = stacksize
|
||||
else:
|
||||
var stack: pointer
|
||||
while stack == nil:
|
||||
stack = alloc0(stacksize)
|
||||
coro.stack.top = stack
|
||||
when coroBackend == CORO_BACKEND_UCONTEXT:
|
||||
discard getcontext(coro.execContext)
|
||||
coro.execContext.uc_stack.ss_sp = cast[pointer](cast[ByteAddress](stack) + stacksize)
|
||||
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
|
||||
coro.state = CORO_CREATED
|
||||
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)
|
||||
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()
|
||||
|
||||
var remaining = int((coro.sleepTime - (epochTime() - coro.lastRun)) * 1000)
|
||||
var remaining = current.sleepTime - (float(getTicks() - current.lastRun) / 1_000_000_000)
|
||||
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)
|
||||
|
||||
elif remaining > 0:
|
||||
# Save main loop context. Suspending coroutine will resume after this statement with
|
||||
switchTo(ctx.loop, current)
|
||||
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:
|
||||
var next = ctx.current.prev
|
||||
if next == nil:
|
||||
# If first coroutine ends then `prev` is nil even if more coroutines
|
||||
# are to be scheduled.
|
||||
next = ctx.current.next
|
||||
ctx.coroutines.remove(ctx.current)
|
||||
GC_removeStack(current.stack.bottom)
|
||||
when coroBackend == CORO_BACKEND_FIBERS:
|
||||
DeleteFiber(current.execContext)
|
||||
else:
|
||||
dealloc(current.stack.top)
|
||||
current.stack.top = nil
|
||||
current.stack.bottom = 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"
|
||||
|
||||
|
||||
proc c2() =
|
||||
for i in 0 .. 3:
|
||||
echo "c2"
|
||||
suspend 0.025
|
||||
wait(c1)
|
||||
echo "c2 exits"
|
||||
|
||||
start(c1)
|
||||
start(c2)
|
||||
run()
|
||||
echo "done ", stackCheckValue
|
||||
suspend(interval)
|
||||
|
||||
@@ -2462,6 +2462,26 @@ template accumulateResult*(iter: untyped) =
|
||||
# we have to compute this here before turning it off in except.nim anyway ...
|
||||
const NimStackTrace = compileOption("stacktrace")
|
||||
|
||||
template coroutinesSupportedPlatform(): bool =
|
||||
when defined(sparc) or defined(ELATE) or compileOption("gc", "v2") or
|
||||
defined(boehmgc) or defined(gogc) or defined(nogc) or defined(gcStack) or
|
||||
defined(gcMarkAndSweep):
|
||||
false
|
||||
else:
|
||||
true
|
||||
|
||||
when defined(nimCoroutines):
|
||||
# Explicit opt-in.
|
||||
when not coroutinesSupportedPlatform():
|
||||
{.error: "Coroutines are not supported on this architecture and/or garbage collector.".}
|
||||
const nimCoroutines* = true
|
||||
elif defined(noNimCoroutines):
|
||||
# Explicit opt-out.
|
||||
const nimCoroutines* = false
|
||||
else:
|
||||
# Autodetect coroutine support.
|
||||
const nimCoroutines* = false
|
||||
|
||||
{.push checks: off.}
|
||||
# obviously we cannot generate checking operations here :-)
|
||||
# because it would yield into an endless recursion
|
||||
|
||||
@@ -45,6 +45,17 @@ var
|
||||
# a global variable for the root of all try blocks
|
||||
currException {.threadvar.}: ref Exception
|
||||
|
||||
type
|
||||
FrameState = tuple[framePtr: PFrame, excHandler: PSafePoint, currException: ref Exception]
|
||||
|
||||
proc getFrameState*(): FrameState {.compilerRtl, inl.} =
|
||||
return (framePtr, excHandler, currException)
|
||||
|
||||
proc setFrameState*(state: FrameState) {.compilerRtl, inl.} =
|
||||
framePtr = state.framePtr
|
||||
excHandler = state.excHandler
|
||||
currException = state.currException
|
||||
|
||||
proc getFrame*(): PFrame {.compilerRtl, inl.} = framePtr
|
||||
|
||||
proc popFrame {.compilerRtl, inl.} =
|
||||
|
||||
@@ -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
|
||||
@@ -66,17 +63,24 @@ type
|
||||
cycleTableSize: int # max entries in cycle table
|
||||
maxPause: int64 # max measured GC pause in nanoseconds
|
||||
|
||||
GcStack {.final.} = object
|
||||
prev: ptr GcStack
|
||||
next: ptr GcStack
|
||||
starts: pointer
|
||||
pos: pointer
|
||||
maxStackSize: int
|
||||
GcStack {.final, pure.} = object
|
||||
when nimCoroutines:
|
||||
prev: ptr GcStack
|
||||
next: ptr GcStack
|
||||
maxStackSize: int # Used to track statistics because we can not use
|
||||
# GcStat.maxStackSize when multiple stacks exist.
|
||||
bottom: pointer
|
||||
|
||||
when withRealTime or nimCoroutines:
|
||||
pos: pointer # Used with `withRealTime` only for code clarity, see GC_Step().
|
||||
when withRealTime:
|
||||
bottomSaved: pointer
|
||||
|
||||
GcHeap {.final, pure.} = object # this contains the zero count and
|
||||
# non-zero count table
|
||||
stack: ptr GcStack
|
||||
stackBottom: pointer
|
||||
# non-zero count table
|
||||
stack: GcStack
|
||||
when nimCoroutines:
|
||||
activeStack: ptr GcStack # current executing coroutine stack.
|
||||
cycleThreshold: int
|
||||
when useCellIds:
|
||||
idGenerator: int
|
||||
@@ -823,7 +827,10 @@ proc collectCTBody(gch: var GcHeap) =
|
||||
let t0 = getticks()
|
||||
sysAssert(allocInv(gch.region), "collectCT: begin")
|
||||
|
||||
when not defined(nimCoroutines):
|
||||
when nimCoroutines:
|
||||
for stack in gch.stack.items():
|
||||
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stack.stackSize())
|
||||
else:
|
||||
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
|
||||
sysAssert(gch.decStack.len == 0, "collectCT")
|
||||
prepareForInteriorPointerChecking(gch.region)
|
||||
@@ -849,19 +856,11 @@ proc collectCTBody(gch: var GcHeap) =
|
||||
if gch.maxPause > 0 and duration > gch.maxPause:
|
||||
c_fprintf(stdout, "[GC] missed deadline: %ld\n", duration)
|
||||
|
||||
when defined(nimCoroutines):
|
||||
proc currentStackSizes(): int =
|
||||
for stack in items(gch.stack):
|
||||
result = result + stackSize(stack.starts, stack.pos)
|
||||
|
||||
proc collectCT(gch: var GcHeap) =
|
||||
# stackMarkCosts prevents some pathological behaviour: Stack marking
|
||||
# becomes more expensive with large stacks and large stacks mean that
|
||||
# cells with RC=0 are more likely to be kept alive by the stack.
|
||||
when defined(nimCoroutines):
|
||||
let stackMarkCosts = max(currentStackSizes() div (16*sizeof(int)), ZctThreshold)
|
||||
else:
|
||||
let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold)
|
||||
let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold)
|
||||
if (gch.zct.len >= stackMarkCosts or (cycleGC and
|
||||
getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and
|
||||
gch.recGcLock == 0:
|
||||
@@ -888,18 +887,24 @@ when withRealTime:
|
||||
release(gch)
|
||||
|
||||
proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} =
|
||||
var stackTop {.volatile.}: pointer
|
||||
let prevStackBottom = gch.stackBottom
|
||||
if stackSize >= 0:
|
||||
stackTop = addr(stackTop)
|
||||
when stackIncreases:
|
||||
gch.stackBottom = cast[pointer](
|
||||
cast[ByteAddress](stackTop) - sizeof(pointer) * 6 - stackSize)
|
||||
else:
|
||||
gch.stackBottom = cast[pointer](
|
||||
cast[ByteAddress](stackTop) + sizeof(pointer) * 6 + stackSize)
|
||||
var stackTop {.volatile.}: pointer
|
||||
gch.getActiveStack().pos = addr(stackTop)
|
||||
|
||||
for stack in gch.stack.items():
|
||||
stack.bottomSaved = stack.bottom
|
||||
when stackIncreases:
|
||||
stack.bottom = cast[pointer](
|
||||
cast[ByteAddress](stack.pos) - sizeof(pointer) * 6 - stackSize)
|
||||
else:
|
||||
stack.bottom = cast[pointer](
|
||||
cast[ByteAddress](stack.pos) + sizeof(pointer) * 6 + stackSize)
|
||||
|
||||
GC_step(gch, us, strongAdvice)
|
||||
gch.stackBottom = prevStackBottom
|
||||
|
||||
if stackSize >= 0:
|
||||
for stack in gch.stack.items():
|
||||
stack.bottom = stack.bottomSaved
|
||||
|
||||
when not defined(useNimRtl):
|
||||
proc GC_disable() =
|
||||
@@ -943,10 +948,10 @@ when not defined(useNimRtl):
|
||||
"[GC] zct capacity: " & $gch.zct.cap & "\n" &
|
||||
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
|
||||
"[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) & "\n"
|
||||
when defined(nimCoroutines):
|
||||
when nimCoroutines:
|
||||
result = result & "[GC] number of stacks: " & $gch.stack.len & "\n"
|
||||
for stack in items(gch.stack):
|
||||
result = result & "[GC] stack " & stack.starts.repr & "[GC] max stack size " & $stack.maxStackSize & "\n"
|
||||
result = result & "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & cast[pointer](stack.maxStackSize).repr & "\n"
|
||||
else:
|
||||
result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
||||
GC_enable()
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
|
||||
# XXX Ensure by smart color masking that the object is not in the ZCT.
|
||||
|
||||
when defined(nimCoroutines):
|
||||
import arch
|
||||
|
||||
{.push profiler:off.}
|
||||
|
||||
const
|
||||
@@ -72,19 +69,26 @@ type
|
||||
maxStackCells: int # max stack cells in ``decStack``
|
||||
cycleTableSize: int # max entries in cycle table
|
||||
maxPause: int64 # max measured GC pause in nanoseconds
|
||||
|
||||
GcStack {.final, pure.} = object
|
||||
when nimCoroutines:
|
||||
prev: ptr GcStack
|
||||
next: ptr GcStack
|
||||
maxStackSize: int # Used to track statistics because we can not use
|
||||
# GcStat.maxStackSize when multiple stacks exist.
|
||||
bottom: pointer
|
||||
|
||||
GcStack = object
|
||||
prev: ptr GcStack
|
||||
next: ptr GcStack
|
||||
starts: pointer
|
||||
pos: pointer
|
||||
maxStackSize: int
|
||||
when withRealTime or nimCoroutines:
|
||||
pos: pointer # Used with `withRealTime` only for code clarity, see GC_Step().
|
||||
when withRealTime:
|
||||
bottomSaved: pointer
|
||||
|
||||
GcHeap = object # this contains the zero count and
|
||||
# non-zero count table
|
||||
black, red: int # either 0 or 1.
|
||||
stack: ptr GcStack
|
||||
stackBottom: pointer
|
||||
stack: GcStack
|
||||
when nimCoroutines:
|
||||
activeStack: ptr GcStack # current executing coroutine stack.
|
||||
phase: Phase
|
||||
cycleThreshold: int
|
||||
when useCellIds:
|
||||
@@ -913,7 +917,7 @@ proc collectCTBody(gch: var GcHeap) =
|
||||
let t0 = getticks()
|
||||
sysAssert(allocInv(gch.region), "collectCT: begin")
|
||||
|
||||
when not defined(nimCoroutines):
|
||||
when not nimCoroutines:
|
||||
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
|
||||
sysAssert(gch.decStack.len == 0, "collectCT")
|
||||
prepareForInteriorPointerChecking(gch.region)
|
||||
@@ -938,16 +942,16 @@ proc collectCTBody(gch: var GcHeap) =
|
||||
if gch.maxPause > 0 and duration > gch.maxPause:
|
||||
c_fprintf(stdout, "[GC] missed deadline: %ld\n", duration)
|
||||
|
||||
when defined(nimCoroutines):
|
||||
when nimCoroutines:
|
||||
proc currentStackSizes(): int =
|
||||
for stack in items(gch.stack):
|
||||
result = result + stackSize(stack.starts, stack.pos)
|
||||
result = result + stack.stackSize()
|
||||
|
||||
proc collectCT(gch: var GcHeap) =
|
||||
# stackMarkCosts prevents some pathological behaviour: Stack marking
|
||||
# becomes more expensive with large stacks and large stacks mean that
|
||||
# cells with RC=0 are more likely to be kept alive by the stack.
|
||||
when defined(nimCoroutines):
|
||||
when nimCoroutines:
|
||||
let stackMarkCosts = max(currentStackSizes() div (16*sizeof(int)), ZctThreshold)
|
||||
else:
|
||||
let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold)
|
||||
@@ -971,18 +975,24 @@ when withRealTime:
|
||||
collectCTBody(gch)
|
||||
|
||||
proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} =
|
||||
var stackTop {.volatile.}: pointer
|
||||
let prevStackBottom = gch.stackBottom
|
||||
if stackSize >= 0:
|
||||
stackTop = addr(stackTop)
|
||||
when stackIncreases:
|
||||
gch.stackBottom = cast[pointer](
|
||||
cast[ByteAddress](stackTop) - sizeof(pointer) * 6 - stackSize)
|
||||
else:
|
||||
gch.stackBottom = cast[pointer](
|
||||
cast[ByteAddress](stackTop) + sizeof(pointer) * 6 + stackSize)
|
||||
var stackTop {.volatile.}: pointer
|
||||
gch.getActiveStack().pos = addr(stackTop)
|
||||
|
||||
for stack in gch.stack.items():
|
||||
stack.bottomSaved = stack.bottom
|
||||
when stackIncreases:
|
||||
stack.bottom = cast[pointer](
|
||||
cast[ByteAddress](stack.pos) - sizeof(pointer) * 6 - stackSize)
|
||||
else:
|
||||
stack.bottom = cast[pointer](
|
||||
cast[ByteAddress](stack.pos) + sizeof(pointer) * 6 + stackSize)
|
||||
|
||||
GC_step(gch, us, strongAdvice)
|
||||
gch.stackBottom = prevStackBottom
|
||||
|
||||
if stackSize >= 0:
|
||||
for stack in gch.stack.items():
|
||||
stack.bottom = stack.bottomSaved
|
||||
|
||||
when not defined(useNimRtl):
|
||||
proc GC_disable() =
|
||||
@@ -1024,10 +1034,10 @@ when not defined(useNimRtl):
|
||||
"[GC] zct capacity: " & $gch.zct.cap & "\n" &
|
||||
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
|
||||
"[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000)
|
||||
when defined(nimCoroutines):
|
||||
when nimCoroutines:
|
||||
result = result & "[GC] number of stacks: " & $gch.stack.len & "\n"
|
||||
for stack in items(gch.stack):
|
||||
result = result & "[GC] stack " & stack.starts.repr & "[GC] max stack size " & $stack.maxStackSize & "\n"
|
||||
result = result & "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & $stack.maxStackSize & "\n"
|
||||
else:
|
||||
result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
||||
GC_enable()
|
||||
|
||||
@@ -57,76 +57,96 @@ proc isNotForeign*(x: ForeignCell): bool =
|
||||
## No deep copy has to be performed then.
|
||||
x.owner == addr(gch)
|
||||
|
||||
proc len(stack: ptr GcStack): int =
|
||||
if stack == nil:
|
||||
return 0
|
||||
|
||||
var s = stack
|
||||
result = 1
|
||||
while s.next != nil:
|
||||
inc(result)
|
||||
s = s.next
|
||||
|
||||
when defined(nimCoroutines):
|
||||
proc stackSize(stackBottom: pointer, pos: pointer=nil): int {.noinline.} =
|
||||
var sp: pointer
|
||||
if pos == nil:
|
||||
var stackTop {.volatile.}: pointer
|
||||
sp = addr(stackTop)
|
||||
else:
|
||||
sp = pos
|
||||
result = abs(cast[int](sp) - cast[int](stackBottom))
|
||||
|
||||
proc GC_addStack*(starts: pointer) {.cdecl, exportc.} =
|
||||
var sp {.volatile.}: pointer
|
||||
var stack = cast[ptr GcStack](alloc0(sizeof(GcStack)))
|
||||
stack.starts = starts
|
||||
stack.pos = addr sp
|
||||
if gch.stack == nil:
|
||||
gch.stack = stack
|
||||
else:
|
||||
stack.next = gch.stack
|
||||
gch.stack.prev = stack
|
||||
gch.stack = stack
|
||||
# c_fprintf(stdout, "[GC] added stack 0x%016X\n", starts)
|
||||
|
||||
proc GC_removeStack*(starts: pointer) {.cdecl, exportc.} =
|
||||
var stack = gch.stack
|
||||
while stack != nil:
|
||||
if stack.starts == starts:
|
||||
if stack.prev == nil:
|
||||
if stack.next != nil:
|
||||
stack.next.prev = nil
|
||||
gch.stack = stack.next
|
||||
else:
|
||||
stack.prev.next = stack.next
|
||||
if stack.next != nil:
|
||||
stack.next.prev = stack.prev
|
||||
dealloc(stack)
|
||||
# echo "[GC] removed stack ", starts.repr
|
||||
when nimCoroutines:
|
||||
iterator items(first: var GcStack): ptr GcStack =
|
||||
var item = addr(first)
|
||||
while true:
|
||||
yield item
|
||||
item = item.next
|
||||
if item == addr(first):
|
||||
break
|
||||
else:
|
||||
stack = stack.next
|
||||
|
||||
proc GC_setCurrentStack*(starts, pos: pointer) {.cdecl, exportc.} =
|
||||
var stack = gch.stack
|
||||
while stack != nil:
|
||||
if stack.starts == starts:
|
||||
stack.pos = pos
|
||||
stack.maxStackSize = max(stack.maxStackSize, stackSize(stack.starts, pos))
|
||||
return
|
||||
stack = stack.next
|
||||
gcAssert(false, "Current stack position does not belong to registered stack")
|
||||
proc append(first: var GcStack, stack: ptr GcStack) =
|
||||
## Append stack to the ring of stacks.
|
||||
first.prev.next = stack
|
||||
stack.prev = first.prev
|
||||
first.prev = stack
|
||||
stack.next = addr(first)
|
||||
|
||||
proc append(first: var GcStack): ptr GcStack =
|
||||
## Allocate new GcStack object, append it to the ring of stacks and return it.
|
||||
result = cast[ptr GcStack](alloc0(sizeof(GcStack)))
|
||||
first.append(result)
|
||||
|
||||
proc remove(first: var GcStack, stack: ptr GcStack) =
|
||||
## Remove stack from ring of stacks.
|
||||
gcAssert(addr(first) != stack, "Main application stack can not be removed")
|
||||
if addr(first) == stack or stack == nil:
|
||||
return
|
||||
stack.prev.next = stack.next
|
||||
stack.next.prev = stack.prev
|
||||
dealloc(stack)
|
||||
|
||||
proc remove(stack: ptr GcStack) =
|
||||
gch.stack.remove(stack)
|
||||
|
||||
proc find(first: var GcStack, bottom: pointer): ptr GcStack =
|
||||
## Find stack struct based on bottom pointer. If `bottom` is nil then main
|
||||
## thread stack is is returned.
|
||||
if bottom == nil:
|
||||
return addr(gch.stack)
|
||||
|
||||
for stack in first.items():
|
||||
if stack.bottom == bottom:
|
||||
return stack
|
||||
|
||||
proc len(stack: var GcStack): int =
|
||||
for _ in stack.items():
|
||||
result = result + 1
|
||||
else:
|
||||
proc stackSize(): int {.noinline.} =
|
||||
var stackTop {.volatile.}: pointer
|
||||
result = abs(cast[int](addr(stackTop)) - cast[int](gch.stackBottom))
|
||||
# This iterator gets optimized out in forEachStackSlot().
|
||||
iterator items(first: var GcStack): ptr GcStack = yield addr(first)
|
||||
proc len(stack: var GcStack): int = 1
|
||||
|
||||
iterator items(stack: ptr GcStack): ptr GcStack =
|
||||
var s = stack
|
||||
while not isNil(s):
|
||||
yield s
|
||||
s = s.next
|
||||
proc stackSize(stack: ptr GcStack): int {.noinline.} =
|
||||
when nimCoroutines:
|
||||
var pos = stack.pos
|
||||
else:
|
||||
var pos {.volatile.}: pointer
|
||||
pos = addr(pos)
|
||||
|
||||
if pos != nil:
|
||||
when defined(stackIncreases):
|
||||
result = cast[ByteAddress](pos) -% cast[ByteAddress](stack.bottom)
|
||||
else:
|
||||
result = cast[ByteAddress](stack.bottom) -% cast[ByteAddress](pos)
|
||||
else:
|
||||
result = 0
|
||||
|
||||
proc stackSize(): int {.noinline.} =
|
||||
for stack in gch.stack.items():
|
||||
result = result + stack.stackSize()
|
||||
|
||||
when nimCoroutines:
|
||||
proc setPosition(stack: ptr GcStack, position: pointer) =
|
||||
stack.pos = position
|
||||
stack.maxStackSize = max(stack.maxStackSize, stack.stackSize())
|
||||
|
||||
proc setPosition(stack: var GcStack, position: pointer) =
|
||||
setPosition(addr(stack), position)
|
||||
|
||||
proc getActiveStack(gch: var GcHeap): ptr GcStack =
|
||||
return gch.activeStack
|
||||
|
||||
proc isActiveStack(stack: ptr GcStack): bool =
|
||||
return gch.activeStack == stack
|
||||
else:
|
||||
# Stack positions do not need to be tracked if coroutines are not used.
|
||||
proc setPosition(stack: ptr GcStack, position: pointer) = discard
|
||||
proc setPosition(stack: var GcStack, position: pointer) = discard
|
||||
# There is just one stack - main stack of the thread. It is active always.
|
||||
proc getActiveStack(gch: var GcHeap): ptr GcStack = addr(gch.stack)
|
||||
proc isActiveStack(stack: ptr GcStack): bool = true
|
||||
|
||||
when declared(threadType):
|
||||
proc setupForeignThreadGc*() {.gcsafe.} =
|
||||
@@ -177,37 +197,69 @@ elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or
|
||||
else:
|
||||
const stackIncreases = false
|
||||
|
||||
{.push stack_trace: off.}
|
||||
when nimCoroutines:
|
||||
proc GC_addStack(bottom: pointer) {.cdecl, exportc.} =
|
||||
# c_fprintf(stdout, "GC_addStack: %p;\n", bottom)
|
||||
var stack = gch.stack.append()
|
||||
stack.bottom = bottom
|
||||
stack.setPosition(bottom)
|
||||
|
||||
proc GC_removeStack(bottom: pointer) {.cdecl, exportc.} =
|
||||
# c_fprintf(stdout, "GC_removeStack: %p;\n", bottom)
|
||||
gch.stack.find(bottom).remove()
|
||||
|
||||
proc GC_setActiveStack(bottom: pointer) {.cdecl, exportc.} =
|
||||
## Sets active stack and updates current stack position.
|
||||
# c_fprintf(stdout, "GC_setActiveStack: %p;\n", bottom)
|
||||
var sp {.volatile.}: pointer
|
||||
gch.activeStack = gch.stack.find(bottom)
|
||||
gch.activeStack.setPosition(addr(sp))
|
||||
|
||||
when not defined(useNimRtl):
|
||||
{.push stack_trace: off.}
|
||||
proc setStackBottom(theStackBottom: pointer) =
|
||||
#c_fprintf(stdout, "stack bottom: %p;\n", theStackBottom)
|
||||
# the first init must be the one that defines the stack bottom:
|
||||
when defined(nimCoroutines):
|
||||
GC_addStack(theStackBottom)
|
||||
else:
|
||||
if gch.stackBottom == nil: gch.stackBottom = theStackBottom
|
||||
# Initializes main stack of the thread.
|
||||
when nimCoroutines:
|
||||
if gch.stack.next == nil:
|
||||
# Main stack was not initialized yet
|
||||
gch.stack.next = addr(gch.stack)
|
||||
gch.stack.prev = addr(gch.stack)
|
||||
gch.stack.bottom = theStackBottom
|
||||
gch.stack.maxStackSize = 0
|
||||
gch.activeStack = addr(gch.stack)
|
||||
|
||||
if gch.stack.bottom == nil:
|
||||
# This branch will not be called when -d:nimCoroutines - it is fine,
|
||||
# because same thing is done just above.
|
||||
#c_fprintf(stdout, "stack bottom: %p;\n", theStackBottom)
|
||||
# the first init must be the one that defines the stack bottom:
|
||||
gch.stack.bottom = theStackBottom
|
||||
elif theStackBottom != gch.stack.bottom:
|
||||
var a = cast[ByteAddress](theStackBottom) # and not PageMask - PageSize*2
|
||||
var b = cast[ByteAddress](gch.stack.bottom)
|
||||
#c_fprintf(stdout, "old: %p new: %p;\n",gch.stack.bottom,theStackBottom)
|
||||
when stackIncreases:
|
||||
gch.stack.bottom = cast[pointer](min(a, b))
|
||||
else:
|
||||
var a = cast[ByteAddress](theStackBottom) # and not PageMask - PageSize*2
|
||||
var b = cast[ByteAddress](gch.stackBottom)
|
||||
#c_fprintf(stdout, "old: %p new: %p;\n",gch.stackBottom,theStackBottom)
|
||||
when stackIncreases:
|
||||
gch.stackBottom = cast[pointer](min(a, b))
|
||||
else:
|
||||
gch.stackBottom = cast[pointer](max(a, b))
|
||||
{.pop.}
|
||||
gch.stack.bottom = cast[pointer](max(a, b))
|
||||
|
||||
gch.stack.setPosition(theStackBottom)
|
||||
{.pop.}
|
||||
|
||||
proc isOnStack(p: pointer): bool =
|
||||
var stackTop {.volatile.}: pointer
|
||||
stackTop = addr(stackTop)
|
||||
var a = cast[ByteAddress](gch.getActiveStack().bottom)
|
||||
var b = cast[ByteAddress](stackTop)
|
||||
when not stackIncreases:
|
||||
swap(a, b)
|
||||
var x = cast[ByteAddress](p)
|
||||
result = a <=% x and x <=% b
|
||||
|
||||
when defined(sparc): # For SPARC architecture.
|
||||
when defined(nimCoroutines):
|
||||
when nimCoroutines:
|
||||
{.error: "Nim coroutines are not supported on this platform."}
|
||||
|
||||
proc isOnStack(p: pointer): bool =
|
||||
var stackTop {.volatile.}: pointer
|
||||
stackTop = addr(stackTop)
|
||||
var b = cast[ByteAddress](gch.stackBottom)
|
||||
var a = cast[ByteAddress](stackTop)
|
||||
var x = cast[ByteAddress](p)
|
||||
result = a <=% x and x <=% b
|
||||
|
||||
template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
|
||||
when defined(sparcv9):
|
||||
asm """"flushw \n" """
|
||||
@@ -215,7 +267,7 @@ when defined(sparc): # For SPARC architecture.
|
||||
asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """
|
||||
|
||||
var
|
||||
max = gch.stackBottom
|
||||
max = gch.stack.bottom
|
||||
sp: PPointer
|
||||
stackTop: array[0..1, pointer]
|
||||
sp = addr(stackTop[0])
|
||||
@@ -231,16 +283,6 @@ elif stackIncreases:
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generic code for architectures where addresses increase as the stack grows.
|
||||
# ---------------------------------------------------------------------------
|
||||
when defined(nimCoroutines):
|
||||
{.error: "Nim coroutines are not supported on this platform."}
|
||||
proc isOnStack(p: pointer): bool =
|
||||
var stackTop {.volatile.}: pointer
|
||||
stackTop = addr(stackTop)
|
||||
var a = cast[ByteAddress](gch.stackBottom)
|
||||
var b = cast[ByteAddress](stackTop)
|
||||
var x = cast[ByteAddress](p)
|
||||
result = a <=% x and x <=% b
|
||||
|
||||
var
|
||||
jmpbufSize {.importc: "sizeof(jmp_buf)", nodecl.}: int
|
||||
# a little hack to get the size of a JmpBuf in the generated C code
|
||||
@@ -248,84 +290,42 @@ elif stackIncreases:
|
||||
|
||||
template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
|
||||
var registers {.noinit.}: C_JmpBuf
|
||||
# sp will traverse the JMP_BUF as well (jmp_buf size is added,
|
||||
# otherwise sp would be below the registers structure).
|
||||
var regAddr = addr(registers) +% jmpbufSize
|
||||
|
||||
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
|
||||
var max = cast[ByteAddress](gch.stackBottom)
|
||||
var sp = cast[ByteAddress](addr(registers)) +% jmpbufSize -% sizeof(pointer)
|
||||
# sp will traverse the JMP_BUF as well (jmp_buf size is added,
|
||||
# otherwise sp would be below the registers structure).
|
||||
while sp >=% max:
|
||||
gcMark(gch, cast[PPointer](sp)[])
|
||||
sp = sp -% sizeof(pointer)
|
||||
for stack in gch.stack.items():
|
||||
var max = cast[ByteAddress](gch.stack.bottom)
|
||||
var sp = cast[ByteAddress](addr(registers)) -% sizeof(pointer)
|
||||
while sp >=% max:
|
||||
gcMark(gch, cast[PPointer](sp)[])
|
||||
sp = sp -% sizeof(pointer)
|
||||
|
||||
else:
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generic code for architectures where addresses decrease as the stack grows.
|
||||
# ---------------------------------------------------------------------------
|
||||
when defined(nimCoroutines):
|
||||
proc isOnStack(p: pointer): bool =
|
||||
var stackTop {.volatile.}: pointer
|
||||
stackTop = addr(stackTop)
|
||||
for stack in items(gch.stack):
|
||||
var b = cast[ByteAddress](stack.starts)
|
||||
var a = cast[ByteAddress](stack.starts) - stack.maxStackSize
|
||||
var x = cast[ByteAddress](p)
|
||||
if a <=% x and x <=% b:
|
||||
return true
|
||||
|
||||
template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
|
||||
# We use a jmp_buf buffer that is in the C stack.
|
||||
# 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]))
|
||||
|
||||
for stack in items(gch.stack):
|
||||
stack.maxStackSize = max(stack.maxStackSize, stackSize(stack.starts))
|
||||
var max = cast[ByteAddress](stack.starts)
|
||||
var sp = cast[ByteAddress](stack.pos)
|
||||
# loop unrolled:
|
||||
while sp <% max - 8*sizeof(pointer):
|
||||
gcMark(gch, cast[PStackSlice](sp)[0])
|
||||
gcMark(gch, cast[PStackSlice](sp)[1])
|
||||
gcMark(gch, cast[PStackSlice](sp)[2])
|
||||
gcMark(gch, cast[PStackSlice](sp)[3])
|
||||
gcMark(gch, cast[PStackSlice](sp)[4])
|
||||
gcMark(gch, cast[PStackSlice](sp)[5])
|
||||
gcMark(gch, cast[PStackSlice](sp)[6])
|
||||
gcMark(gch, cast[PStackSlice](sp)[7])
|
||||
sp = sp +% sizeof(pointer)*8
|
||||
# last few entries:
|
||||
while sp <=% max:
|
||||
gcMark(gch, cast[PPointer](sp)[])
|
||||
sp = sp +% sizeof(pointer)
|
||||
else:
|
||||
proc isOnStack(p: pointer): bool =
|
||||
var stackTop {.volatile.}: pointer
|
||||
stackTop = addr(stackTop)
|
||||
var b = cast[ByteAddress](gch.stackBottom)
|
||||
var a = cast[ByteAddress](stackTop)
|
||||
var x = cast[ByteAddress](p)
|
||||
result = a <=% x and x <=% b
|
||||
|
||||
template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
|
||||
# We use a jmp_buf buffer that is in the C stack.
|
||||
# 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.}: C_JmpBuf
|
||||
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
|
||||
var max = cast[ByteAddress](gch.stackBottom)
|
||||
template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
|
||||
# We use a jmp_buf buffer that is in the C stack.
|
||||
# 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.}: C_JmpBuf
|
||||
# Update position of stack gc is executing in.
|
||||
gch.getActiveStack().setPosition(addr(registers))
|
||||
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
|
||||
for stack in gch.stack.items():
|
||||
var max = cast[ByteAddress](stack.bottom)
|
||||
var sp = cast[ByteAddress](addr(registers))
|
||||
when defined(amd64):
|
||||
# words within the jmp_buf structure may not be properly aligned.
|
||||
let regEnd = sp +% sizeof(registers)
|
||||
while sp <% regEnd:
|
||||
gcMark(gch, cast[PPointer](sp)[])
|
||||
gcMark(gch, cast[PPointer](sp +% sizeof(pointer) div 2)[])
|
||||
sp = sp +% sizeof(pointer)
|
||||
if stack.isActiveStack():
|
||||
# words within the jmp_buf structure may not be properly aligned.
|
||||
let regEnd = sp +% sizeof(registers)
|
||||
while sp <% regEnd:
|
||||
gcMark(gch, cast[PPointer](sp)[])
|
||||
gcMark(gch, cast[PPointer](sp +% sizeof(pointer) div 2)[])
|
||||
sp = sp +% sizeof(pointer)
|
||||
# Make sure sp is word-aligned
|
||||
sp = sp and not (sizeof(pointer) - 1)
|
||||
# loop unrolled:
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
# A simple mark&sweep garbage collector for Nim. Define the
|
||||
# symbol ``gcUseBitvectors`` to generate a variant of this GC.
|
||||
|
||||
when defined(nimCoroutines):
|
||||
import arch
|
||||
|
||||
{.push profiler:off.}
|
||||
|
||||
const
|
||||
@@ -51,17 +48,22 @@ type
|
||||
maxStackSize: int # max stack size
|
||||
freedObjects: int # max entries in cycle table
|
||||
|
||||
GcStack {.final.} = object
|
||||
prev: ptr GcStack
|
||||
next: ptr GcStack
|
||||
starts: pointer
|
||||
pos: pointer
|
||||
maxStackSize: int
|
||||
GcStack {.final, pure.} = object
|
||||
when nimCoroutines:
|
||||
prev: ptr GcStack
|
||||
next: ptr GcStack
|
||||
maxStackSize: int # Used to track statistics because we can not use
|
||||
# GcStat.maxStackSize when multiple stacks exist.
|
||||
bottom: pointer
|
||||
|
||||
when nimCoroutines:
|
||||
pos: pointer
|
||||
|
||||
GcHeap = object # this contains the zero count and
|
||||
# non-zero count table
|
||||
stack: ptr GcStack
|
||||
stackBottom: pointer
|
||||
stack: GcStack
|
||||
when nimCoroutines:
|
||||
activeStack: ptr GcStack # current executing coroutine stack.
|
||||
cycleThreshold: int
|
||||
when useCellIds:
|
||||
idGenerator: int
|
||||
@@ -423,7 +425,7 @@ proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} =
|
||||
forEachStackSlot(gch, gcMark)
|
||||
|
||||
proc collectCTBody(gch: var GcHeap) =
|
||||
when not defined(nimCoroutines):
|
||||
when not nimCoroutines:
|
||||
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
|
||||
prepareForInteriorPointerChecking(gch.region)
|
||||
markStackAndRegisters(gch)
|
||||
@@ -479,10 +481,10 @@ when not defined(useNimRtl):
|
||||
"[GC] collections: " & $gch.stat.collections & "\n" &
|
||||
"[GC] max threshold: " & $gch.stat.maxThreshold & "\n" &
|
||||
"[GC] freed objects: " & $gch.stat.freedObjects & "\n"
|
||||
when defined(nimCoroutines):
|
||||
when nimCoroutines:
|
||||
result = result & "[GC] number of stacks: " & $gch.stack.len & "\n"
|
||||
for stack in items(gch.stack):
|
||||
result = result & "[GC] stack " & stack.starts.repr & "[GC] max stack size " & $stack.maxStackSize & "\n"
|
||||
result = result & "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & $stack.maxStackSize & "\n"
|
||||
else:
|
||||
result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
||||
GC_enable()
|
||||
|
||||
@@ -1036,3 +1036,17 @@ else:
|
||||
proc readConsoleInput*(hConsoleInput: Handle, lpBuffer: pointer, nLength: cint,
|
||||
lpNumberOfEventsRead: ptr cint): cint
|
||||
{.stdcall, dynlib: "kernel32", importc: "ReadConsoleInputW".}
|
||||
|
||||
type
|
||||
LPFIBER_START_ROUTINE* = proc (param: pointer): void {.stdcall.}
|
||||
|
||||
const
|
||||
FIBER_FLAG_FLOAT_SWITCH* = 0x01
|
||||
|
||||
proc CreateFiber*(stackSize: int, fn: LPFIBER_START_ROUTINE, param: pointer): pointer {.stdcall, discardable, dynlib: "kernel32", importc.}
|
||||
proc CreateFiberEx*(stkCommit: int, stkReserve: int, flags: int32, fn: LPFIBER_START_ROUTINE, param: pointer): pointer {.stdcall, discardable, dynlib: "kernel32", importc.}
|
||||
proc ConvertThreadToFiber*(param: pointer): pointer {.stdcall, discardable, dynlib: "kernel32", importc.}
|
||||
proc ConvertThreadToFiberEx*(param: pointer, flags: int32): pointer {.stdcall, discardable, dynlib: "kernel32", importc.}
|
||||
proc DeleteFiber*(fiber: pointer): void {.stdcall, discardable, dynlib: "kernel32", importc.}
|
||||
proc SwitchToFiber*(fiber: pointer): void {.stdcall, discardable, dynlib: "kernel32", importc.}
|
||||
proc GetCurrentFiber*(): pointer {.stdcall, importc, header: "Windows.h".}
|
||||
|
||||
23
tests/coroutines/texceptions.nim
Normal file
23
tests/coroutines/texceptions.nim
Normal file
@@ -0,0 +1,23 @@
|
||||
import coro
|
||||
var
|
||||
stackCheckValue = 1100220033
|
||||
numbers = newSeqOfCap[int](10)
|
||||
|
||||
proc testExceptions(id: int, sleep: float) =
|
||||
try:
|
||||
numbers.add(id)
|
||||
suspend(sleep)
|
||||
numbers.add(id)
|
||||
raise (ref ValueError)()
|
||||
except:
|
||||
numbers.add(id)
|
||||
suspend(sleep)
|
||||
numbers.add(id)
|
||||
suspend(sleep)
|
||||
numbers.add(id)
|
||||
|
||||
start(proc() = testExceptions(1, 0.01))
|
||||
start(proc() = testExceptions(2, 0.011))
|
||||
run()
|
||||
doAssert(stackCheckValue == 1100220033, "Thread stack got corrupted")
|
||||
doAssert(numbers == @[1, 2, 1, 2, 1, 2, 1, 2, 1, 2], "Coroutines executed in incorrect order")
|
||||
1
tests/coroutines/texceptions.nim.cfg
Normal file
1
tests/coroutines/texceptions.nim.cfg
Normal file
@@ -0,0 +1 @@
|
||||
-d:nimCoroutines
|
||||
15
tests/coroutines/tgc.nim
Normal file
15
tests/coroutines/tgc.nim
Normal file
@@ -0,0 +1,15 @@
|
||||
import coro
|
||||
|
||||
var maxOccupiedMemory = 0
|
||||
|
||||
proc testGC() =
|
||||
var numbers = newSeq[int](100)
|
||||
maxOccupiedMemory = max(maxOccupiedMemory, getOccupiedMem())
|
||||
suspend(0)
|
||||
|
||||
start(testGC)
|
||||
start(testGC)
|
||||
run()
|
||||
|
||||
GC_fullCollect()
|
||||
doAssert(getOccupiedMem() < maxOccupiedMemory, "GC did not free any memory allocated in coroutines")
|
||||
1
tests/coroutines/tgc.nim.cfg
Normal file
1
tests/coroutines/tgc.nim.cfg
Normal file
@@ -0,0 +1 @@
|
||||
-d:nimCoroutines
|
||||
24
tests/coroutines/titerators.nim
Normal file
24
tests/coroutines/titerators.nim
Normal file
@@ -0,0 +1,24 @@
|
||||
import coro
|
||||
include system/timers
|
||||
|
||||
var
|
||||
stackCheckValue = 1100220033
|
||||
numbers = newSeqOfCap[int](10)
|
||||
|
||||
iterator theIterator(id: int, sleep: float): int =
|
||||
for i in 0..<5:
|
||||
yield 10 * id + i
|
||||
suspend(sleep)
|
||||
|
||||
proc theCoroutine(id: int, sleep: float32) =
|
||||
for n in theIterator(id, sleep):
|
||||
numbers.add(n)
|
||||
|
||||
var start = getTicks()
|
||||
start(proc() = theCoroutine(1, 0.01))
|
||||
start(proc() = theCoroutine(2, 0.011))
|
||||
run()
|
||||
var executionTime = getTicks() - start
|
||||
doAssert(executionTime >= 55_000_000.Nanos and executionTime < 56_000_000.Nanos, "Coroutines executed too short")
|
||||
doAssert(stackCheckValue == 1100220033, "Thread stack got corrupted")
|
||||
doAssert(numbers == @[10, 20, 11, 21, 12, 22, 13, 23, 14, 24], "Coroutines executed in incorrect order")
|
||||
1
tests/coroutines/titerators.nim.cfg
Normal file
1
tests/coroutines/titerators.nim.cfg
Normal file
@@ -0,0 +1 @@
|
||||
-d:nimCoroutines
|
||||
Reference in New Issue
Block a user