Merge pull request #5317 from rokups/feature/coroutines
Coroutine improvements
This commit is contained in:
commit
895e2aea15
28 changed files with 800 additions and 769 deletions
12
.travis.yml
12
.travis.yml
|
|
@ -22,18 +22,16 @@ addons:
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- set -e
|
- 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
|
- git clone --depth 1 https://github.com/nim-lang/csources.git
|
||||||
- cd csources
|
- cd csources
|
||||||
- sh build.sh
|
- sh build.sh
|
||||||
- cd ..
|
- cd ..
|
||||||
- sed -i -e 's,cc = gcc,cc = clang,' config/nim.cfg
|
- sed -i -e 's,cc = gcc,cc = clang,' config/nim.cfg
|
||||||
- export PATH=$(pwd)/bin:$(pwd)/fasm:$PATH
|
- export PATH=$(pwd)/bin:$PATH
|
||||||
script:
|
script:
|
||||||
- nim c koch
|
- nim c koch
|
||||||
- ./koch boot
|
- ./koch boot -d:nimCoroutines
|
||||||
- ./koch boot -d:release
|
- ./koch boot -d:release -d:nimCoroutines
|
||||||
- ./koch nimble
|
- ./koch nimble
|
||||||
- nim e tests/test_nimscript.nims
|
- nim e tests/test_nimscript.nims
|
||||||
- nimble install zip -y
|
- nimble install zip -y
|
||||||
|
|
@ -41,7 +39,7 @@ script:
|
||||||
- nimble install sdl1
|
- nimble install sdl1
|
||||||
- nimble install jester@#head
|
- nimble install jester@#head
|
||||||
- nimble install niminst
|
- nimble install niminst
|
||||||
- nim c --taintMode:on tests/testament/tester
|
- nim c --taintMode:on -d:nimCoroutines tests/testament/tester
|
||||||
- tests/testament/tester --pedantic all
|
- tests/testament/tester --pedantic all -d:nimCoroutines
|
||||||
- ./koch csource
|
- ./koch csource
|
||||||
- ./koch xz
|
- ./koch xz
|
||||||
|
|
|
||||||
19
appveyor.yml
19
appveyor.yml
|
|
@ -3,7 +3,6 @@ version: '{build}'
|
||||||
cache:
|
cache:
|
||||||
- x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z
|
- x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z
|
||||||
- sqlite-dll-win64-x64-3160200.zip
|
- sqlite-dll-win64-x64-3160200.zip
|
||||||
- fasmw17159.zip
|
|
||||||
# - i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z
|
# - i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -16,18 +15,12 @@ environment:
|
||||||
MINGW_ARCHIVE: x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z
|
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_URL: http://www.sqlite.org/2017/sqlite-dll-win64-x64-3160200.zip
|
||||||
SQLITE_ARCHIVE: 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
|
platform: x64
|
||||||
# - MINGW_DIR: mingw32
|
# - 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_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
|
# 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_URL: http://www.sqlite.org/2017/sqlite-dll-win32-x86-3160200.zip
|
||||||
# SQLITE_ARCHIVE: 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
|
# platform: x86
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|
@ -38,9 +31,7 @@ install:
|
||||||
- 7z x -y "%SQLITE_ARCHIVE%" -o"%CD%\DIST"> nul
|
- 7z x -y "%SQLITE_ARCHIVE%" -o"%CD%\DIST"> nul
|
||||||
- IF not exist "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%"
|
- IF not exist "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%"
|
||||||
- 7z x -y "%MINGW_ARCHIVE%" -o"%CD%\DIST"> nul
|
- 7z x -y "%MINGW_ARCHIVE%" -o"%CD%\DIST"> nul
|
||||||
- IF not exist "%FASM_ARCHIVE%" appveyor DownloadFile "%FASM_URL%" -FileName "%FASM_ARCHIVE%"
|
- SET PATH=%CD%\DIST\%MINGW_DIR%\BIN;%CD%\BIN;%PATH%
|
||||||
- 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%
|
|
||||||
- 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 )
|
- 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 )
|
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 )
|
- 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:
|
build_script:
|
||||||
- bin\nim c koch
|
- bin\nim c koch
|
||||||
- koch boot
|
- koch boot -d:nimCoroutines
|
||||||
- koch boot -d:release
|
- koch boot -d:release -d:nimCoroutines
|
||||||
- koch nimble
|
- koch nimble
|
||||||
- nim e tests/test_nimscript.nims
|
- nim e tests/test_nimscript.nims
|
||||||
- nimble install zip -y
|
- nimble install zip -y
|
||||||
|
|
@ -61,10 +52,10 @@ build_script:
|
||||||
- nimble install sdl1
|
- nimble install sdl1
|
||||||
- nimble install jester@#head
|
- nimble install jester@#head
|
||||||
- nimble install niminst
|
- nimble install niminst
|
||||||
- nim c --taintMode:on tests/testament/tester
|
- nim c --taintMode:on -d:nimCoroutines tests/testament/tester
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- tests\testament\tester --pedantic all
|
- tests\testament\tester --pedantic all -d:nimCoroutines
|
||||||
- koch csource
|
- koch csource
|
||||||
- koch zip
|
- koch zip
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,10 @@ echo "Running on $CI_RUNNER_ID ($CI_RUNNER_DESCRIPTION) with tags $CI_RUNNER_TAG
|
||||||
|
|
||||||
# Packages
|
# Packages
|
||||||
apt-get update -qq
|
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
|
gcc -v
|
||||||
|
|
||||||
fasm -v
|
|
||||||
export PATH=$(pwd)/bin:$PATH
|
export PATH=$(pwd)/bin:$PATH
|
||||||
|
|
||||||
# Nimble deps
|
# Nimble deps
|
||||||
|
|
|
||||||
|
|
@ -642,10 +642,6 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||||
of "experimental":
|
of "experimental":
|
||||||
expectNoArg(switch, arg, pass, info)
|
expectNoArg(switch, arg, pass, info)
|
||||||
gExperimentalMode = true
|
gExperimentalMode = true
|
||||||
of "assembler":
|
|
||||||
cAssembler = nameToCC(arg)
|
|
||||||
if cAssembler notin cValidAssemblers:
|
|
||||||
localError(info, errGenerated, "'$1' is not a valid assembler." % [arg])
|
|
||||||
of "nocppexceptions":
|
of "nocppexceptions":
|
||||||
expectNoArg(switch, arg, pass, info)
|
expectNoArg(switch, arg, pass, info)
|
||||||
incl(gGlobalOptions, optNoCppExceptions)
|
incl(gGlobalOptions, optNoCppExceptions)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import
|
||||||
type
|
type
|
||||||
TSystemCC* = enum
|
TSystemCC* = enum
|
||||||
ccNone, ccGcc, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc,
|
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:
|
TInfoCCProp* = enum # properties of the C compiler:
|
||||||
hasSwitchRange, # CC allows ranges in switch statements (GNU C)
|
hasSwitchRange, # CC allows ranges in switch statements (GNU C)
|
||||||
hasComputedGoto, # CC has computed goto (GNU C extension)
|
hasComputedGoto, # CC has computed goto (GNU C extension)
|
||||||
|
|
@ -320,31 +320,6 @@ compiler ucc:
|
||||||
packedPragma: "", # XXX: not supported yet
|
packedPragma: "", # XXX: not supported yet
|
||||||
props: {})
|
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
|
const
|
||||||
CC*: array[succ(low(TSystemCC))..high(TSystemCC), TInfoCC] = [
|
CC*: array[succ(low(TSystemCC))..high(TSystemCC), TInfoCC] = [
|
||||||
gcc(),
|
gcc(),
|
||||||
|
|
@ -358,22 +333,17 @@ const
|
||||||
tcc(),
|
tcc(),
|
||||||
pcc(),
|
pcc(),
|
||||||
ucc(),
|
ucc(),
|
||||||
icl(),
|
icl()]
|
||||||
fasm()]
|
|
||||||
|
|
||||||
hExt* = ".h"
|
hExt* = ".h"
|
||||||
|
|
||||||
var
|
var
|
||||||
cCompiler* = ccGcc # the used compiler
|
cCompiler* = ccGcc # the used compiler
|
||||||
cAssembler* = ccNone
|
|
||||||
gMixedMode*: bool # true if some module triggered C++ codegen
|
gMixedMode*: bool # true if some module triggered C++ codegen
|
||||||
cIncludes*: seq[string] = @[] # directories to search for included files
|
cIncludes*: seq[string] = @[] # directories to search for included files
|
||||||
cLibs*: seq[string] = @[] # directories to search for lib files
|
cLibs*: seq[string] = @[] # directories to search for lib files
|
||||||
cLinkedLibs*: seq[string] = @[] # libraries to link
|
cLinkedLibs*: seq[string] = @[] # libraries to link
|
||||||
|
|
||||||
const
|
|
||||||
cValidAssemblers* = {asmFasm}
|
|
||||||
|
|
||||||
# implementation
|
# implementation
|
||||||
|
|
||||||
proc libNameTmpl(): string {.inline.} =
|
proc libNameTmpl(): string {.inline.} =
|
||||||
|
|
@ -577,21 +547,6 @@ proc getLinkerExe(compiler: TSystemCC): string =
|
||||||
|
|
||||||
proc getCompileCFileCmd*(cfile: Cfile): string =
|
proc getCompileCFileCmd*(cfile: Cfile): string =
|
||||||
var c = cCompiler
|
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 options = cFileSpecificOptions(cfile.cname)
|
||||||
var exe = getConfigVar(c, ".exe")
|
var exe = getConfigVar(c, ".exe")
|
||||||
if exe.len == 0: exe = c.getCompilerExe(cfile.cname)
|
if exe.len == 0: exe = c.getCompilerExe(cfile.cname)
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,6 @@ type
|
||||||
errCannotInferReturnType,
|
errCannotInferReturnType,
|
||||||
errGenericLambdaNotAllowed,
|
errGenericLambdaNotAllowed,
|
||||||
errCompilerDoesntSupportTarget,
|
errCompilerDoesntSupportTarget,
|
||||||
errExternalAssemblerNotFound,
|
|
||||||
errExternalAssemblerNotValid,
|
|
||||||
errUser,
|
errUser,
|
||||||
warnCannotOpenFile,
|
warnCannotOpenFile,
|
||||||
warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
|
warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
|
||||||
|
|
@ -372,8 +370,6 @@ const
|
||||||
"it is used as an operand to another routine and the types " &
|
"it is used as an operand to another routine and the types " &
|
||||||
"of the generic paramers can be inferred from the expected signature.",
|
"of the generic paramers can be inferred from the expected signature.",
|
||||||
errCompilerDoesntSupportTarget: "The current compiler \'$1\' doesn't support the requested compilation target",
|
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",
|
errUser: "$1",
|
||||||
warnCannotOpenFile: "cannot open \'$1\'",
|
warnCannotOpenFile: "cannot open \'$1\'",
|
||||||
warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored",
|
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
|
# See the file "copying.txt", included in this
|
||||||
# distribution, for details about the copyright.
|
# 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):
|
when not nimCoroutines and not defined(nimdoc):
|
||||||
{.error: "Coroutines require -d:nimCoroutines".}
|
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 macros
|
||||||
import arch
|
|
||||||
import lists
|
import lists
|
||||||
|
include system/timers
|
||||||
|
|
||||||
const defaultStackSize = 512 * 1024
|
const defaultStackSize = 512 * 1024
|
||||||
|
|
||||||
type Coroutine = ref object
|
proc GC_addStack(bottom: pointer) {.cdecl, importc.}
|
||||||
# prev: ptr Coroutine
|
proc GC_removeStack(bottom: pointer) {.cdecl, importc.}
|
||||||
# next: ptr Coroutine
|
proc GC_setActiveStack(bottom: pointer) {.cdecl, importc.}
|
||||||
ctx: JmpBuf
|
|
||||||
fn: proc()
|
|
||||||
started: bool
|
|
||||||
lastRun: float
|
|
||||||
sleepTime: float
|
|
||||||
stack: pointer
|
|
||||||
stacksize: int
|
|
||||||
|
|
||||||
var coroutines = initDoublyLinkedList[Coroutine]()
|
const
|
||||||
var current: Coroutine
|
CORO_BACKEND_UCONTEXT = 0
|
||||||
var mainCtx: JmpBuf
|
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.}
|
when coroBackend == CORO_BACKEND_FIBERS:
|
||||||
proc GC_removeStack(starts: pointer) {.cdecl, importc.}
|
import windows.winlean
|
||||||
proc GC_setCurrentStack(starts, pos: pointer) {.cdecl, importc.}
|
type
|
||||||
|
Context = pointer
|
||||||
|
|
||||||
proc start*(c: proc(), stacksize: int=defaultStackSize) =
|
elif coroBackend == CORO_BACKEND_UCONTEXT:
|
||||||
## Adds coroutine to event loop. It does not run immediately.
|
type
|
||||||
var coro = Coroutine()
|
stack_t {.importc, header: "<sys/ucontext.h>".} = object
|
||||||
coro.fn = c
|
ss_sp: pointer
|
||||||
while coro.stack == nil:
|
ss_flags: int
|
||||||
coro.stack = alloc0(stacksize)
|
ss_size: int
|
||||||
coro.stacksize = stacksize
|
|
||||||
coroutines.append(coro)
|
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) =
|
proc suspend*(sleepTime: float=0) =
|
||||||
## Stops coroutine execution and resumes no sooner than after ``sleeptime`` seconds.
|
## Stops coroutine execution and resumes no sooner than after ``sleeptime`` seconds.
|
||||||
## Until then other coroutines are executed.
|
## Until then other coroutines are executed.
|
||||||
##
|
var current = getCurrent()
|
||||||
## 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))
|
|
||||||
current.sleepTime = sleepTime
|
current.sleepTime = sleepTime
|
||||||
current.lastRun = epochTime()
|
switchTo(current, ctx.loop)
|
||||||
if setjmp(current.ctx) == 0:
|
|
||||||
longjmp(mainCtx, 1)
|
proc runCurrentTask() =
|
||||||
setFrame(oldFrame)
|
## Starts execution of current coroutine and updates it's state through coroutine's life.
|
||||||
{.pop.}
|
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*() =
|
proc run*() =
|
||||||
## Starts main event loop which exits when all coroutines exit. Calling this proc
|
initialize()
|
||||||
## starts execution of first coroutine.
|
## Starts main coroutine scheduler loop which exits when all coroutines exit.
|
||||||
var node = coroutines.head
|
## Calling this proc starts execution of first coroutine.
|
||||||
var minDelay: int = 0 # in milliseconds
|
ctx.current = ctx.coroutines.head
|
||||||
var frame: PFrame
|
var minDelay: float = 0
|
||||||
while node != nil:
|
while ctx.current != nil:
|
||||||
var coro = node.value
|
var current = getCurrent()
|
||||||
current = coro
|
|
||||||
os.sleep(minDelay)
|
|
||||||
|
|
||||||
var remaining = int((coro.sleepTime - (epochTime() - coro.lastRun)) * 1000)
|
var remaining = current.sleepTime - (float(getTicks() - current.lastRun) / 1_000_000_000)
|
||||||
if remaining <= 0:
|
if remaining <= 0:
|
||||||
remaining = 0
|
# Save main loop context. Suspending coroutine will resume after this statement with
|
||||||
let res = setjmp(mainCtx)
|
switchTo(ctx.loop, current)
|
||||||
if res == 0:
|
else:
|
||||||
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:
|
|
||||||
if minDelay > 0 and remaining > 0:
|
if minDelay > 0 and remaining > 0:
|
||||||
minDelay = min(remaining, minDelay)
|
minDelay = min(remaining, minDelay)
|
||||||
else:
|
else:
|
||||||
minDelay = remaining
|
minDelay = remaining
|
||||||
|
|
||||||
if node == nil or node.next == nil:
|
if current.state == CORO_FINISHED:
|
||||||
node = coroutines.head
|
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:
|
else:
|
||||||
node = node.next
|
ctx.current = ctx.current.next
|
||||||
|
|
||||||
proc alive*(c: proc()): bool =
|
proc alive*(c: proc()): bool =
|
||||||
## Returns ``true`` if coroutine has not returned, ``false`` otherwise.
|
## Returns ``true`` if coroutine has not returned, ``false`` otherwise.
|
||||||
for coro in items(coroutines):
|
for coro in items(ctx.coroutines):
|
||||||
if coro.fn == c:
|
if coro.fn == c:
|
||||||
return true
|
return coro.state != CORO_FINISHED
|
||||||
|
|
||||||
proc wait*(c: proc(), interval=0.01) =
|
proc wait*(c: proc(), interval=0.01) =
|
||||||
## Returns only after coroutine ``c`` has returned. ``interval`` is time in seconds how often.
|
## Returns only after coroutine ``c`` has returned. ``interval`` is time in seconds how often.
|
||||||
while alive(c):
|
while alive(c):
|
||||||
suspend interval
|
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
|
|
||||||
|
|
|
||||||
|
|
@ -2462,6 +2462,26 @@ template accumulateResult*(iter: untyped) =
|
||||||
# we have to compute this here before turning it off in except.nim anyway ...
|
# we have to compute this here before turning it off in except.nim anyway ...
|
||||||
const NimStackTrace = compileOption("stacktrace")
|
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.}
|
{.push checks: off.}
|
||||||
# obviously we cannot generate checking operations here :-)
|
# obviously we cannot generate checking operations here :-)
|
||||||
# because it would yield into an endless recursion
|
# because it would yield into an endless recursion
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,17 @@ var
|
||||||
# a global variable for the root of all try blocks
|
# a global variable for the root of all try blocks
|
||||||
currException {.threadvar.}: ref Exception
|
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 getFrame*(): PFrame {.compilerRtl, inl.} = framePtr
|
||||||
|
|
||||||
proc popFrame {.compilerRtl, inl.} =
|
proc popFrame {.compilerRtl, inl.} =
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,6 @@
|
||||||
# Refcounting + Mark&Sweep. Complex algorithms avoided.
|
# Refcounting + Mark&Sweep. Complex algorithms avoided.
|
||||||
# Been there, done that, didn't work.
|
# Been there, done that, didn't work.
|
||||||
|
|
||||||
when defined(nimCoroutines):
|
|
||||||
import arch
|
|
||||||
|
|
||||||
{.push profiler:off.}
|
{.push profiler:off.}
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
@ -66,17 +63,24 @@ type
|
||||||
cycleTableSize: int # max entries in cycle table
|
cycleTableSize: int # max entries in cycle table
|
||||||
maxPause: int64 # max measured GC pause in nanoseconds
|
maxPause: int64 # max measured GC pause in nanoseconds
|
||||||
|
|
||||||
GcStack {.final.} = object
|
GcStack {.final, pure.} = object
|
||||||
prev: ptr GcStack
|
when nimCoroutines:
|
||||||
next: ptr GcStack
|
prev: ptr GcStack
|
||||||
starts: pointer
|
next: ptr GcStack
|
||||||
pos: pointer
|
maxStackSize: int # Used to track statistics because we can not use
|
||||||
maxStackSize: int
|
# 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
|
GcHeap {.final, pure.} = object # this contains the zero count and
|
||||||
# non-zero count table
|
# non-zero count table
|
||||||
stack: ptr GcStack
|
stack: GcStack
|
||||||
stackBottom: pointer
|
when nimCoroutines:
|
||||||
|
activeStack: ptr GcStack # current executing coroutine stack.
|
||||||
cycleThreshold: int
|
cycleThreshold: int
|
||||||
when useCellIds:
|
when useCellIds:
|
||||||
idGenerator: int
|
idGenerator: int
|
||||||
|
|
@ -823,7 +827,10 @@ proc collectCTBody(gch: var GcHeap) =
|
||||||
let t0 = getticks()
|
let t0 = getticks()
|
||||||
sysAssert(allocInv(gch.region), "collectCT: begin")
|
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())
|
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
|
||||||
sysAssert(gch.decStack.len == 0, "collectCT")
|
sysAssert(gch.decStack.len == 0, "collectCT")
|
||||||
prepareForInteriorPointerChecking(gch.region)
|
prepareForInteriorPointerChecking(gch.region)
|
||||||
|
|
@ -849,19 +856,11 @@ proc collectCTBody(gch: var GcHeap) =
|
||||||
if gch.maxPause > 0 and duration > gch.maxPause:
|
if gch.maxPause > 0 and duration > gch.maxPause:
|
||||||
c_fprintf(stdout, "[GC] missed deadline: %ld\n", duration)
|
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) =
|
proc collectCT(gch: var GcHeap) =
|
||||||
# stackMarkCosts prevents some pathological behaviour: Stack marking
|
# stackMarkCosts prevents some pathological behaviour: Stack marking
|
||||||
# becomes more expensive with large stacks and large stacks mean that
|
# 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.
|
# cells with RC=0 are more likely to be kept alive by the stack.
|
||||||
when defined(nimCoroutines):
|
let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold)
|
||||||
let stackMarkCosts = max(currentStackSizes() div (16*sizeof(int)), ZctThreshold)
|
|
||||||
else:
|
|
||||||
let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold)
|
|
||||||
if (gch.zct.len >= stackMarkCosts or (cycleGC and
|
if (gch.zct.len >= stackMarkCosts or (cycleGC and
|
||||||
getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and
|
getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and
|
||||||
gch.recGcLock == 0:
|
gch.recGcLock == 0:
|
||||||
|
|
@ -888,18 +887,24 @@ when withRealTime:
|
||||||
release(gch)
|
release(gch)
|
||||||
|
|
||||||
proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} =
|
proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} =
|
||||||
var stackTop {.volatile.}: pointer
|
|
||||||
let prevStackBottom = gch.stackBottom
|
|
||||||
if stackSize >= 0:
|
if stackSize >= 0:
|
||||||
stackTop = addr(stackTop)
|
var stackTop {.volatile.}: pointer
|
||||||
when stackIncreases:
|
gch.getActiveStack().pos = addr(stackTop)
|
||||||
gch.stackBottom = cast[pointer](
|
|
||||||
cast[ByteAddress](stackTop) - sizeof(pointer) * 6 - stackSize)
|
for stack in gch.stack.items():
|
||||||
else:
|
stack.bottomSaved = stack.bottom
|
||||||
gch.stackBottom = cast[pointer](
|
when stackIncreases:
|
||||||
cast[ByteAddress](stackTop) + sizeof(pointer) * 6 + stackSize)
|
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)
|
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):
|
when not defined(useNimRtl):
|
||||||
proc GC_disable() =
|
proc GC_disable() =
|
||||||
|
|
@ -943,10 +948,10 @@ when not defined(useNimRtl):
|
||||||
"[GC] zct capacity: " & $gch.zct.cap & "\n" &
|
"[GC] zct capacity: " & $gch.zct.cap & "\n" &
|
||||||
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
|
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
|
||||||
"[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) & "\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"
|
result = result & "[GC] number of stacks: " & $gch.stack.len & "\n"
|
||||||
for stack in items(gch.stack):
|
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:
|
else:
|
||||||
result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
||||||
GC_enable()
|
GC_enable()
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,6 @@
|
||||||
|
|
||||||
# XXX Ensure by smart color masking that the object is not in the ZCT.
|
# XXX Ensure by smart color masking that the object is not in the ZCT.
|
||||||
|
|
||||||
when defined(nimCoroutines):
|
|
||||||
import arch
|
|
||||||
|
|
||||||
{.push profiler:off.}
|
{.push profiler:off.}
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
@ -73,18 +70,25 @@ type
|
||||||
cycleTableSize: int # max entries in cycle table
|
cycleTableSize: int # max entries in cycle table
|
||||||
maxPause: int64 # max measured GC pause in nanoseconds
|
maxPause: int64 # max measured GC pause in nanoseconds
|
||||||
|
|
||||||
GcStack = object
|
GcStack {.final, pure.} = object
|
||||||
prev: ptr GcStack
|
when nimCoroutines:
|
||||||
next: ptr GcStack
|
prev: ptr GcStack
|
||||||
starts: pointer
|
next: ptr GcStack
|
||||||
pos: pointer
|
maxStackSize: int # Used to track statistics because we can not use
|
||||||
maxStackSize: int
|
# 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 = object # this contains the zero count and
|
GcHeap = object # this contains the zero count and
|
||||||
# non-zero count table
|
# non-zero count table
|
||||||
black, red: int # either 0 or 1.
|
black, red: int # either 0 or 1.
|
||||||
stack: ptr GcStack
|
stack: GcStack
|
||||||
stackBottom: pointer
|
when nimCoroutines:
|
||||||
|
activeStack: ptr GcStack # current executing coroutine stack.
|
||||||
phase: Phase
|
phase: Phase
|
||||||
cycleThreshold: int
|
cycleThreshold: int
|
||||||
when useCellIds:
|
when useCellIds:
|
||||||
|
|
@ -913,7 +917,7 @@ proc collectCTBody(gch: var GcHeap) =
|
||||||
let t0 = getticks()
|
let t0 = getticks()
|
||||||
sysAssert(allocInv(gch.region), "collectCT: begin")
|
sysAssert(allocInv(gch.region), "collectCT: begin")
|
||||||
|
|
||||||
when not defined(nimCoroutines):
|
when not nimCoroutines:
|
||||||
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
|
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
|
||||||
sysAssert(gch.decStack.len == 0, "collectCT")
|
sysAssert(gch.decStack.len == 0, "collectCT")
|
||||||
prepareForInteriorPointerChecking(gch.region)
|
prepareForInteriorPointerChecking(gch.region)
|
||||||
|
|
@ -938,16 +942,16 @@ proc collectCTBody(gch: var GcHeap) =
|
||||||
if gch.maxPause > 0 and duration > gch.maxPause:
|
if gch.maxPause > 0 and duration > gch.maxPause:
|
||||||
c_fprintf(stdout, "[GC] missed deadline: %ld\n", duration)
|
c_fprintf(stdout, "[GC] missed deadline: %ld\n", duration)
|
||||||
|
|
||||||
when defined(nimCoroutines):
|
when nimCoroutines:
|
||||||
proc currentStackSizes(): int =
|
proc currentStackSizes(): int =
|
||||||
for stack in items(gch.stack):
|
for stack in items(gch.stack):
|
||||||
result = result + stackSize(stack.starts, stack.pos)
|
result = result + stack.stackSize()
|
||||||
|
|
||||||
proc collectCT(gch: var GcHeap) =
|
proc collectCT(gch: var GcHeap) =
|
||||||
# stackMarkCosts prevents some pathological behaviour: Stack marking
|
# stackMarkCosts prevents some pathological behaviour: Stack marking
|
||||||
# becomes more expensive with large stacks and large stacks mean that
|
# 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.
|
# 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)
|
let stackMarkCosts = max(currentStackSizes() div (16*sizeof(int)), ZctThreshold)
|
||||||
else:
|
else:
|
||||||
let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold)
|
let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold)
|
||||||
|
|
@ -971,18 +975,24 @@ when withRealTime:
|
||||||
collectCTBody(gch)
|
collectCTBody(gch)
|
||||||
|
|
||||||
proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} =
|
proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} =
|
||||||
var stackTop {.volatile.}: pointer
|
|
||||||
let prevStackBottom = gch.stackBottom
|
|
||||||
if stackSize >= 0:
|
if stackSize >= 0:
|
||||||
stackTop = addr(stackTop)
|
var stackTop {.volatile.}: pointer
|
||||||
when stackIncreases:
|
gch.getActiveStack().pos = addr(stackTop)
|
||||||
gch.stackBottom = cast[pointer](
|
|
||||||
cast[ByteAddress](stackTop) - sizeof(pointer) * 6 - stackSize)
|
for stack in gch.stack.items():
|
||||||
else:
|
stack.bottomSaved = stack.bottom
|
||||||
gch.stackBottom = cast[pointer](
|
when stackIncreases:
|
||||||
cast[ByteAddress](stackTop) + sizeof(pointer) * 6 + stackSize)
|
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)
|
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):
|
when not defined(useNimRtl):
|
||||||
proc GC_disable() =
|
proc GC_disable() =
|
||||||
|
|
@ -1024,10 +1034,10 @@ when not defined(useNimRtl):
|
||||||
"[GC] zct capacity: " & $gch.zct.cap & "\n" &
|
"[GC] zct capacity: " & $gch.zct.cap & "\n" &
|
||||||
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
|
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
|
||||||
"[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000)
|
"[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"
|
result = result & "[GC] number of stacks: " & $gch.stack.len & "\n"
|
||||||
for stack in items(gch.stack):
|
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:
|
else:
|
||||||
result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
||||||
GC_enable()
|
GC_enable()
|
||||||
|
|
|
||||||
|
|
@ -57,76 +57,96 @@ proc isNotForeign*(x: ForeignCell): bool =
|
||||||
## No deep copy has to be performed then.
|
## No deep copy has to be performed then.
|
||||||
x.owner == addr(gch)
|
x.owner == addr(gch)
|
||||||
|
|
||||||
proc len(stack: ptr GcStack): int =
|
when nimCoroutines:
|
||||||
if stack == nil:
|
iterator items(first: var GcStack): ptr GcStack =
|
||||||
return 0
|
var item = addr(first)
|
||||||
|
while true:
|
||||||
var s = stack
|
yield item
|
||||||
result = 1
|
item = item.next
|
||||||
while s.next != nil:
|
if item == addr(first):
|
||||||
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
|
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
stack = stack.next
|
|
||||||
|
|
||||||
proc GC_setCurrentStack*(starts, pos: pointer) {.cdecl, exportc.} =
|
proc append(first: var GcStack, stack: ptr GcStack) =
|
||||||
var stack = gch.stack
|
## Append stack to the ring of stacks.
|
||||||
while stack != nil:
|
first.prev.next = stack
|
||||||
if stack.starts == starts:
|
stack.prev = first.prev
|
||||||
stack.pos = pos
|
first.prev = stack
|
||||||
stack.maxStackSize = max(stack.maxStackSize, stackSize(stack.starts, pos))
|
stack.next = addr(first)
|
||||||
return
|
|
||||||
stack = stack.next
|
proc append(first: var GcStack): ptr GcStack =
|
||||||
gcAssert(false, "Current stack position does not belong to registered stack")
|
## 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:
|
else:
|
||||||
proc stackSize(): int {.noinline.} =
|
# This iterator gets optimized out in forEachStackSlot().
|
||||||
var stackTop {.volatile.}: pointer
|
iterator items(first: var GcStack): ptr GcStack = yield addr(first)
|
||||||
result = abs(cast[int](addr(stackTop)) - cast[int](gch.stackBottom))
|
proc len(stack: var GcStack): int = 1
|
||||||
|
|
||||||
iterator items(stack: ptr GcStack): ptr GcStack =
|
proc stackSize(stack: ptr GcStack): int {.noinline.} =
|
||||||
var s = stack
|
when nimCoroutines:
|
||||||
while not isNil(s):
|
var pos = stack.pos
|
||||||
yield s
|
else:
|
||||||
s = s.next
|
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):
|
when declared(threadType):
|
||||||
proc setupForeignThreadGc*() {.gcsafe.} =
|
proc setupForeignThreadGc*() {.gcsafe.} =
|
||||||
|
|
@ -177,37 +197,69 @@ elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or
|
||||||
else:
|
else:
|
||||||
const stackIncreases = false
|
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):
|
when not defined(useNimRtl):
|
||||||
{.push stack_trace: off.}
|
|
||||||
proc setStackBottom(theStackBottom: pointer) =
|
proc setStackBottom(theStackBottom: pointer) =
|
||||||
#c_fprintf(stdout, "stack bottom: %p;\n", theStackBottom)
|
# Initializes main stack of the thread.
|
||||||
# the first init must be the one that defines the stack bottom:
|
when nimCoroutines:
|
||||||
when defined(nimCoroutines):
|
if gch.stack.next == nil:
|
||||||
GC_addStack(theStackBottom)
|
# Main stack was not initialized yet
|
||||||
else:
|
gch.stack.next = addr(gch.stack)
|
||||||
if gch.stackBottom == nil: gch.stackBottom = theStackBottom
|
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:
|
else:
|
||||||
var a = cast[ByteAddress](theStackBottom) # and not PageMask - PageSize*2
|
gch.stack.bottom = cast[pointer](max(a, b))
|
||||||
var b = cast[ByteAddress](gch.stackBottom)
|
|
||||||
#c_fprintf(stdout, "old: %p new: %p;\n",gch.stackBottom,theStackBottom)
|
gch.stack.setPosition(theStackBottom)
|
||||||
when stackIncreases:
|
{.pop.}
|
||||||
gch.stackBottom = cast[pointer](min(a, b))
|
|
||||||
else:
|
proc isOnStack(p: pointer): bool =
|
||||||
gch.stackBottom = cast[pointer](max(a, b))
|
var stackTop {.volatile.}: pointer
|
||||||
{.pop.}
|
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(sparc): # For SPARC architecture.
|
||||||
when defined(nimCoroutines):
|
when nimCoroutines:
|
||||||
{.error: "Nim coroutines are not supported on this platform."}
|
{.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.} =
|
template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
|
||||||
when defined(sparcv9):
|
when defined(sparcv9):
|
||||||
asm """"flushw \n" """
|
asm """"flushw \n" """
|
||||||
|
|
@ -215,7 +267,7 @@ when defined(sparc): # For SPARC architecture.
|
||||||
asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """
|
asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """
|
||||||
|
|
||||||
var
|
var
|
||||||
max = gch.stackBottom
|
max = gch.stack.bottom
|
||||||
sp: PPointer
|
sp: PPointer
|
||||||
stackTop: array[0..1, pointer]
|
stackTop: array[0..1, pointer]
|
||||||
sp = addr(stackTop[0])
|
sp = addr(stackTop[0])
|
||||||
|
|
@ -231,16 +283,6 @@ elif stackIncreases:
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Generic code for architectures where addresses increase as the stack grows.
|
# 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
|
var
|
||||||
jmpbufSize {.importc: "sizeof(jmp_buf)", nodecl.}: int
|
jmpbufSize {.importc: "sizeof(jmp_buf)", nodecl.}: int
|
||||||
# a little hack to get the size of a JmpBuf in the generated C code
|
# 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.} =
|
template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
|
||||||
var registers {.noinit.}: C_JmpBuf
|
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.
|
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
|
||||||
var max = cast[ByteAddress](gch.stackBottom)
|
for stack in gch.stack.items():
|
||||||
var sp = cast[ByteAddress](addr(registers)) +% jmpbufSize -% sizeof(pointer)
|
var max = cast[ByteAddress](gch.stack.bottom)
|
||||||
# sp will traverse the JMP_BUF as well (jmp_buf size is added,
|
var sp = cast[ByteAddress](addr(registers)) -% sizeof(pointer)
|
||||||
# otherwise sp would be below the registers structure).
|
while sp >=% max:
|
||||||
while sp >=% max:
|
gcMark(gch, cast[PPointer](sp)[])
|
||||||
gcMark(gch, cast[PPointer](sp)[])
|
sp = sp -% sizeof(pointer)
|
||||||
sp = sp -% sizeof(pointer)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Generic code for architectures where addresses decrease as the stack grows.
|
# Generic code for architectures where addresses decrease as the stack grows.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
when defined(nimCoroutines):
|
template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
|
||||||
proc isOnStack(p: pointer): bool =
|
# We use a jmp_buf buffer that is in the C stack.
|
||||||
var stackTop {.volatile.}: pointer
|
# Used to traverse the stack and registers assuming
|
||||||
stackTop = addr(stackTop)
|
# that 'setjmp' will save registers in the C stack.
|
||||||
for stack in items(gch.stack):
|
type PStackSlice = ptr array[0..7, pointer]
|
||||||
var b = cast[ByteAddress](stack.starts)
|
var registers {.noinit.}: C_JmpBuf
|
||||||
var a = cast[ByteAddress](stack.starts) - stack.maxStackSize
|
# Update position of stack gc is executing in.
|
||||||
var x = cast[ByteAddress](p)
|
gch.getActiveStack().setPosition(addr(registers))
|
||||||
if a <=% x and x <=% b:
|
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
|
||||||
return true
|
for stack in gch.stack.items():
|
||||||
|
var max = cast[ByteAddress](stack.bottom)
|
||||||
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)
|
|
||||||
var sp = cast[ByteAddress](addr(registers))
|
var sp = cast[ByteAddress](addr(registers))
|
||||||
when defined(amd64):
|
when defined(amd64):
|
||||||
# words within the jmp_buf structure may not be properly aligned.
|
if stack.isActiveStack():
|
||||||
let regEnd = sp +% sizeof(registers)
|
# words within the jmp_buf structure may not be properly aligned.
|
||||||
while sp <% regEnd:
|
let regEnd = sp +% sizeof(registers)
|
||||||
gcMark(gch, cast[PPointer](sp)[])
|
while sp <% regEnd:
|
||||||
gcMark(gch, cast[PPointer](sp +% sizeof(pointer) div 2)[])
|
gcMark(gch, cast[PPointer](sp)[])
|
||||||
sp = sp +% sizeof(pointer)
|
gcMark(gch, cast[PPointer](sp +% sizeof(pointer) div 2)[])
|
||||||
|
sp = sp +% sizeof(pointer)
|
||||||
# Make sure sp is word-aligned
|
# Make sure sp is word-aligned
|
||||||
sp = sp and not (sizeof(pointer) - 1)
|
sp = sp and not (sizeof(pointer) - 1)
|
||||||
# loop unrolled:
|
# loop unrolled:
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,6 @@
|
||||||
# A simple mark&sweep garbage collector for Nim. Define the
|
# A simple mark&sweep garbage collector for Nim. Define the
|
||||||
# symbol ``gcUseBitvectors`` to generate a variant of this GC.
|
# symbol ``gcUseBitvectors`` to generate a variant of this GC.
|
||||||
|
|
||||||
when defined(nimCoroutines):
|
|
||||||
import arch
|
|
||||||
|
|
||||||
{.push profiler:off.}
|
{.push profiler:off.}
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
@ -51,17 +48,22 @@ type
|
||||||
maxStackSize: int # max stack size
|
maxStackSize: int # max stack size
|
||||||
freedObjects: int # max entries in cycle table
|
freedObjects: int # max entries in cycle table
|
||||||
|
|
||||||
GcStack {.final.} = object
|
GcStack {.final, pure.} = object
|
||||||
prev: ptr GcStack
|
when nimCoroutines:
|
||||||
next: ptr GcStack
|
prev: ptr GcStack
|
||||||
starts: pointer
|
next: ptr GcStack
|
||||||
pos: pointer
|
maxStackSize: int # Used to track statistics because we can not use
|
||||||
maxStackSize: int
|
# GcStat.maxStackSize when multiple stacks exist.
|
||||||
|
bottom: pointer
|
||||||
|
|
||||||
|
when nimCoroutines:
|
||||||
|
pos: pointer
|
||||||
|
|
||||||
GcHeap = object # this contains the zero count and
|
GcHeap = object # this contains the zero count and
|
||||||
# non-zero count table
|
# non-zero count table
|
||||||
stack: ptr GcStack
|
stack: GcStack
|
||||||
stackBottom: pointer
|
when nimCoroutines:
|
||||||
|
activeStack: ptr GcStack # current executing coroutine stack.
|
||||||
cycleThreshold: int
|
cycleThreshold: int
|
||||||
when useCellIds:
|
when useCellIds:
|
||||||
idGenerator: int
|
idGenerator: int
|
||||||
|
|
@ -423,7 +425,7 @@ proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} =
|
||||||
forEachStackSlot(gch, gcMark)
|
forEachStackSlot(gch, gcMark)
|
||||||
|
|
||||||
proc collectCTBody(gch: var GcHeap) =
|
proc collectCTBody(gch: var GcHeap) =
|
||||||
when not defined(nimCoroutines):
|
when not nimCoroutines:
|
||||||
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
|
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
|
||||||
prepareForInteriorPointerChecking(gch.region)
|
prepareForInteriorPointerChecking(gch.region)
|
||||||
markStackAndRegisters(gch)
|
markStackAndRegisters(gch)
|
||||||
|
|
@ -479,10 +481,10 @@ when not defined(useNimRtl):
|
||||||
"[GC] collections: " & $gch.stat.collections & "\n" &
|
"[GC] collections: " & $gch.stat.collections & "\n" &
|
||||||
"[GC] max threshold: " & $gch.stat.maxThreshold & "\n" &
|
"[GC] max threshold: " & $gch.stat.maxThreshold & "\n" &
|
||||||
"[GC] freed objects: " & $gch.stat.freedObjects & "\n"
|
"[GC] freed objects: " & $gch.stat.freedObjects & "\n"
|
||||||
when defined(nimCoroutines):
|
when nimCoroutines:
|
||||||
result = result & "[GC] number of stacks: " & $gch.stack.len & "\n"
|
result = result & "[GC] number of stacks: " & $gch.stack.len & "\n"
|
||||||
for stack in items(gch.stack):
|
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:
|
else:
|
||||||
result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
||||||
GC_enable()
|
GC_enable()
|
||||||
|
|
|
||||||
|
|
@ -1036,3 +1036,17 @@ else:
|
||||||
proc readConsoleInput*(hConsoleInput: Handle, lpBuffer: pointer, nLength: cint,
|
proc readConsoleInput*(hConsoleInput: Handle, lpBuffer: pointer, nLength: cint,
|
||||||
lpNumberOfEventsRead: ptr cint): cint
|
lpNumberOfEventsRead: ptr cint): cint
|
||||||
{.stdcall, dynlib: "kernel32", importc: "ReadConsoleInputW".}
|
{.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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue