diff options
-rw-r--r-- | compiler/commands.nim | 4 | ||||
-rw-r--r-- | compiler/extccomp.nim | 49 | ||||
-rw-r--r-- | compiler/msgs.nim | 4 | ||||
-rw-r--r-- | compiler/pragmas.nim | 2 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 65 | ||||
-rw-r--r-- | config/nim.cfg | 1 | ||||
-rw-r--r-- | lib/arch/arch.nim | 59 | ||||
-rw-r--r-- | lib/arch/i386.asm | 79 | ||||
-rw-r--r-- | lib/arch/ms_amd64.asm | 90 | ||||
-rw-r--r-- | lib/arch/ms_i386.asm | 12 | ||||
-rw-r--r-- | lib/arch/unix_amd64.asm | 89 | ||||
-rw-r--r-- | lib/arch/unix_i386.asm | 12 | ||||
-rw-r--r-- | lib/nimbase.h | 37 | ||||
-rw-r--r-- | lib/pure/concurrency/threadpool.nim | 10 | ||||
-rw-r--r-- | lib/pure/coro.nim | 145 | ||||
-rw-r--r-- | lib/pure/os.nim | 6 | ||||
-rw-r--r-- | lib/pure/osproc.nim | 12 | ||||
-rw-r--r-- | lib/pure/uri.nim | 27 | ||||
-rw-r--r-- | lib/system/excpt.nim | 4 | ||||
-rw-r--r-- | lib/system/gc.nim | 185 | ||||
-rw-r--r-- | lib/system/gc_common.nim | 275 | ||||
-rw-r--r-- | lib/system/gc_ms.nim | 177 | ||||
-rw-r--r-- | lib/system/inclrtl.nim | 2 | ||||
-rw-r--r-- | lib/system/sysio.nim | 5 | ||||
-rw-r--r-- | tests/gc/gctest.nim | 15 | ||||
-rw-r--r-- | tests/metatype/tprocbothmeta.nim | 10 | ||||
-rw-r--r-- | web/news.txt | 6 |
27 files changed, 1031 insertions, 351 deletions
diff --git a/compiler/commands.nim b/compiler/commands.nim index fc28577aa..dba117516 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -612,6 +612,10 @@ 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]) else: if strutils.find(switch, '.') >= 0: options.setConfigVar(switch, arg) else: invalidCmdLineOption(pass, switch, info) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 44a13e2f4..683de67e0 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -19,7 +19,7 @@ import type TSystemCC* = enum ccNone, ccGcc, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc, - ccTcc, ccPcc, ccUcc, ccIcl + ccTcc, ccPcc, ccUcc, ccIcl, asmFasm TInfoCCProp* = enum # properties of the C compiler: hasSwitchRange, # CC allows ranges in switch statements (GNU C) hasComputedGoto, # CC has computed goto (GNU C extension) @@ -318,6 +318,31 @@ 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(), @@ -331,17 +356,22 @@ const tcc(), pcc(), ucc(), - icl()] + icl(), + fasm()] 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.} = @@ -527,6 +557,21 @@ proc getLinkerExe(compiler: TSystemCC): string = proc getCompileCFileCmd*(cfilename: string, isExternal = false): string = var c = cCompiler + if cfilename.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(cfilename) var exe = getConfigVar(c, ".exe") if exe.len == 0: exe = c.getCompilerExe diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 9bff6c123..1b1f0a76e 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -108,6 +108,8 @@ type errCannotInferReturnType, errGenericLambdaNotAllowed, errCompilerDoesntSupportTarget, + errExternalAssemblerNotFound, + errExternalAssemblerNotValid, errUser, warnCannotOpenFile, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, @@ -371,6 +373,8 @@ 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", diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 128a90138..5f317ed24 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -395,6 +395,8 @@ proc processCompile(c: PContext, n: PNode) = var found = findFile(s) if found == "": found = s var trunc = changeFileExt(found, "") + if not isAbsolute(found): + found = parentDir(n.info.toFullPath) / found extccomp.addExternalFileToCompile(found) extccomp.addFileToLink(completeCFilePath(trunc, false)) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index ef1c01b7a..357c6cf32 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -58,6 +58,10 @@ type isSubtype, isSubrange, # subrange of the wanted type; no type conversion # but apart from that counts as ``isSubtype`` + isBothMetaConvertible # generic proc parameter was matched against + # generic type, e.g., map(mySeq, x=>x+1), + # maybe recoverable by rerun if the parameter is + # the proc's return value isInferred, # generic proc was matched against a concrete type isInferredConvertible, # same as above, but requiring proc CC conversion isGeneric, @@ -389,9 +393,30 @@ proc inconsistentVarTypes(f, a: PType): bool {.inline.} = result = f.kind != a.kind and (f.kind == tyVar or a.kind == tyVar) proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = - var f = f + ## For example we have: + ## .. code-block:: nim + ## proc myMap[T,S](sIn: seq[T], f: proc(x: T): S): seq[S] = ... + ## proc innerProc[Q,W](q: Q): W = ... + ## And we want to match: myMap(@[1,2,3], innerProc) + ## This proc (procParamTypeRel) will do the following steps in + ## three different calls: + ## - matches f=T to a=Q. Since f is metatype, we resolve it + ## to int (which is already known at this point). So in this case + ## Q=int mapping will be saved to c.bindings. + ## - matches f=S to a=W. Both of these metatypes are unknown, so we + ## return with isBothMetaConvertible to ask for rerun. + ## - matches f=S to a=W. At this point the return type of innerProc + ## is known (we get it from c.bindings). We can use that value + ## to match with f, and save back to c.bindings. + var + f = f + a = a if a.isMetaType: + let aResolved = PType(idTableGet(c.bindings, a)) + if aResolved != nil: + a = aResolved + if a.isMetaType: if f.isMetaType: # We are matching a generic proc (as proc param) # to another generic type appearing in the proc @@ -401,12 +426,15 @@ proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = f = generateTypeInstance(c.c, c.bindings, c.call.info, f) if f == nil or f.isMetaType: # no luck resolving the type, so the inference fails - return isNone + return isBothMetaConvertible + # Note that this typeRel call will save a's resolved type into c.bindings let reverseRel = typeRel(c, a, f) if reverseRel >= isGeneric: result = isInferred #inc c.genericMatches else: + # Note that this typeRel call will save f's resolved type into c.bindings + # if f is metatype. result = typeRel(c, f, a) if result <= isSubtype or inconsistentVarTypes(f, a): @@ -450,8 +478,9 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = elif f.callConv != a.callConv: # valid to pass a 'nimcall' thingie to 'closure': if f.callConv == ccClosure and a.callConv == ccDefault: - result = if result != isInferred: isConvertible - else: isInferredConvertible + result = if result == isInferred: isInferredConvertible + elif result == isBothMetaConvertible: isBothMetaConvertible + else: isConvertible else: return isNone when useEffectSystem: @@ -1225,7 +1254,7 @@ proc incMatches(m: var TCandidate; r: TTypeRelation; convMatch = 1) = case r of isConvertible, isIntConv: inc(m.convMatches, convMatch) of isSubtype, isSubrange: inc(m.subtypeMatches) - of isGeneric, isInferred: inc(m.genericMatches) + of isGeneric, isInferred, isBothMetaConvertible: inc(m.genericMatches) of isFromIntLit: inc(m.intConvMatches, 256) of isInferredConvertible: inc(m.convMatches) @@ -1291,6 +1320,29 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, put(m.bindings, f, inlined) return argSemantized + # If r == isBothMetaConvertible then we rerun typeRel. + # bothMetaCounter is for safety to avoid any infinite loop, + # I don't have any example when it is needed. + # lastBindingsLenth is used to check whether m.bindings remains the same, + # because in that case there is no point in continuing. + var bothMetaCounter = 0 + var lastBindingsLength = -1 + while r == isBothMetaConvertible and + lastBindingsLength != m.bindings.counter and + bothMetaCounter < 100: + lastBindingsLength = m.bindings.counter + inc(bothMetaCounter) + if arg.kind in {nkProcDef, nkIteratorDef} + nkLambdaKinds: + result = c.semInferredLambda(c, m.bindings, arg) + elif arg.kind != nkSym: + return nil + else: + let inferred = c.semGenerateInstance(c, arg.sym, m.bindings, arg.info) + result = newSymNode(inferred, arg.info) + inc(m.convMatches) + arg = result + r = typeRel(m, f, arg.typ) + case r of isConvertible: inc(m.convMatches) @@ -1336,6 +1388,9 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, result.typ = getInstantiatedType(c, arg, m, f) else: result = arg + of isBothMetaConvertible: + # This is the result for the 101th time. + result = nil of isFromIntLit: # too lazy to introduce another ``*matches`` field, so we conflate # ``isIntConv`` and ``isIntLit`` here: diff --git a/config/nim.cfg b/config/nim.cfg index a854a30ef..56bfbc64d 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -48,6 +48,7 @@ path="$lib/windows" path="$lib/posix" path="$lib/js" path="$lib/pure/unidecode" +path="$lib/arch" @if nimbabel: nimblepath="$home/.nimble/pkgs/" diff --git a/lib/arch/arch.nim b/lib/arch/arch.nim new file mode 100644 index 000000000..a11bfb21f --- /dev/null +++ b/lib/arch/arch.nim @@ -0,0 +1,59 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Rokas Kupstys +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +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): + # identical fastcall calling convention on all x86 OS + type + JmpBufReg* {.pure.} = enum + BX, SI, DI, BP, SP, IP, TOTAL + + Reg* {.pure.} = enum + AX, BX, CX, BP, SP, DI, SI, TOTAL + +else: + {.error: "Unsupported architecture".} + +{.compile: "./" & ABI & "_" & hostCPU & ".asm"} + +type + JmpBuf* = array[JmpBufReg.TOTAL, pointer] + Registers* = array[Reg.TOTAL, pointer] + + +proc getRegisters*(ctx: var Registers) {.importc: "narch_$1", fastcall.} + +proc setjmp*(ctx: var JmpBuf): int {.importc: "narch_$1", fastcall.} +proc longjmp*(ctx: JmpBuf, ret=1) {.importc: "narch_$1", fastcall.} + +proc coroSwitchStack*(sp: pointer) {.importc: "narch_$1", fastcall.} +proc coroRestoreStack*() {.importc: "narch_$1", fastcall.} diff --git a/lib/arch/i386.asm b/lib/arch/i386.asm new file mode 100644 index 000000000..61f6fdda7 --- /dev/null +++ b/lib/arch/i386.asm @@ -0,0 +1,79 @@ +; +; +; Nim's Runtime Library +; (c) Copyright 2015 Rokas Kupstys +; +; See the file "copying.txt", included in this +; distribution, for details about the copyright. +; + +section ".text" executable +public narch_getRegisters +public @narch_getRegisters@4 +public narch_setjmp +public @narch_setjmp@4 +public narch_longjmp +public @narch_longjmp@8 +public narch_coroSwitchStack +public @narch_coroSwitchStack@4 +public narch_coroRestoreStack +public @narch_coroRestoreStack@0 + +@narch_getRegisters@4: +narch_getRegisters: + mov [ecx], eax + mov [ecx+4], ebx + mov [ecx+8], ecx + mov [ecx+0Ch], ebp + mov [ecx+10h], esp + mov [ecx+14h], edi + mov [ecx+18h], esi + ret + + +@narch_setjmp@4: +narch_setjmp: + ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. + mov [ecx], ebx + mov [ecx+4], esi + mov [ecx+8], edi + mov [ecx+0Ch], ebp + lea eax, [esp+4] + mov [ecx+10h], eax + mov eax, [esp] + mov [ecx+14h], eax + xor eax, eax + ret + + +@narch_longjmp@8: +narch_longjmp: + ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. + mov eax, edx + test eax, eax + jnz @F + inc eax +@@: + mov ebx, [ecx] + mov esi, [ecx+4] + mov edi, [ecx+8] + mov ebp, [ecx+0Ch] + mov esp, [ecx+10h] + mov edx, [ecx+14h] + jmp edx + + +@narch_coroSwitchStack@4: +narch_coroSwitchStack: + pop eax ; return address + mov edx, esp ; old esp for saving + mov esp, ecx ; swap stack with one passed to func + push edx ; store old stack pointer on newly switched stack + jmp eax ; return + + +@narch_coroRestoreStack@0: +narch_coroRestoreStack: + pop eax ; return address + pop esp ; resture old stack pointer + jmp eax ; return diff --git a/lib/arch/ms_amd64.asm b/lib/arch/ms_amd64.asm new file mode 100644 index 000000000..0503b31c9 --- /dev/null +++ b/lib/arch/ms_amd64.asm @@ -0,0 +1,90 @@ +; +; +; Nim's Runtime Library +; (c) Copyright 2015 Rokas Kupstys +; +; See the file "copying.txt", included in this +; distribution, for details about the copyright. +; + +format MS64 COFF + +section ".text" executable align 16 +public narch_getRegisters +public narch_setjmp +public narch_longjmp +public narch_coroSwitchStack +public narch_coroRestoreStack + + +narch_getRegisters: + mov [rcx], rax + mov [rcx+8], rbx + mov [rcx+10h], rcx + mov [rcx+18h], rdx + mov [rcx+20h], rsi + mov [rcx+28h], rdi + mov [rcx+30h], rbp + mov [rcx+38h], rsp + mov rax, [rsp] + mov [rcx+40h], rax ; rip + mov [rcx+48h], r8 + mov [rcx+50h], r9 + mov [rcx+58h], r10 + mov [rcx+60h], r11 + mov [rcx+68h], r12 + mov [rcx+70h], r13 + mov [rcx+78h], r14 + mov [rcx+80h], r15 + ret + + +narch_setjmp: + ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. + mov [rcx], rbx ; rcx is jmp_buf, move registers onto it + mov [rcx+8], rbp + mov [rcx+10h], r12 + mov [rcx+18h], r13 + mov [rcx+20h], r14 + mov [rcx+28h], r15 + lea rdx, [rsp+8] ; this is our rsp WITHOUT current ret addr + mov [rcx+30h], rdx + mov rdx, [rsp] ; save return addr ptr for new rip + mov [rcx+38h], rdx + mov [rcx+40h], rsi + mov [rcx+48h], rdi + xor rax, rax ; always return 0 + ret + +narch_longjmp: + ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. + mov rax, rdx ; val will be longjmp return + test rax, rax + jnz @F + inc rax ; if val==0, val=1 per longjmp semantics +@@: + mov rbx, [rcx] ; rax is the jmp_buf, restore regs from it + mov rbp, [rcx+8] + mov r12, [rcx+10h] + mov r13, [rcx+18h] + mov r14, [rcx+20h] + mov r15, [rcx+28h] + mov rsp, [rcx+30h] ; this ends up being the stack pointer + mov rdx, [rcx+38h] ; this is the instruction pointer + jmp rdx ; goto saved address without altering rsp + + +narch_coroSwitchStack: + pop rax ; return address + mov rdx, rsp ; old rsp for saving + mov rsp, rcx ; swap stack with one passed to func + push rdx ; store old stack pointer on newly switched stack + sub rsp, 28h ; stack alignment + shadow space + jmp rax ; return + + +narch_coroRestoreStack: + pop rax ; return address + add rsp, 28h ; stack alignment + shadow space + pop rsp ; resture old stack pointer + jmp rax ; return diff --git a/lib/arch/ms_i386.asm b/lib/arch/ms_i386.asm new file mode 100644 index 000000000..a31a698d1 --- /dev/null +++ b/lib/arch/ms_i386.asm @@ -0,0 +1,12 @@ +; +; +; Nim's Runtime Library +; (c) Copyright 2015 Rokas Kupstys +; +; See the file "copying.txt", included in this +; distribution, for details about the copyright. +; + +format MS COFF + +include 'i386.asm' diff --git a/lib/arch/unix_amd64.asm b/lib/arch/unix_amd64.asm new file mode 100644 index 000000000..3005c150c --- /dev/null +++ b/lib/arch/unix_amd64.asm @@ -0,0 +1,89 @@ +; +; +; Nim's Runtime Library +; (c) Copyright 2015 Rokas Kupstys +; +; See the file "copying.txt", included in this +; distribution, for details about the copyright. +; + +format ELF64 + +section ".text" executable align 16 +public narch_getRegisters +public narch_setjmp +public narch_longjmp +public narch_coroSwitchStack +public narch_coroRestoreStack + + +narch_getRegisters: + mov [rdi], rax + mov [rdi+8], rbx + mov [rdi+10h], rcx + mov [rdi+18h], rdx + mov [rdi+20h], rsi + mov [rdi+28h], rdi + mov [rdi+30h], rbp + mov [rdi+38h], rsp + mov rax, [rsp] + mov [rdi+40h], rax ; rip + mov [rdi+48h], r8 + mov [rdi+50h], r9 + mov [rdi+58h], r10 + mov [rdi+60h], r11 + mov [rdi+68h], r12 + mov [rdi+70h], r13 + mov [rdi+78h], r14 + mov [rdi+80h], r15 + ret + + +narch_setjmp: + ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. + mov [rdi], rbx ; rdi is jmp_buf, move registers onto it + mov [rdi+8], rbp + mov [rdi+10h], r12 + mov [rdi+18h], r13 + mov [rdi+20h], r14 + mov [rdi+28h], r15 + lea rdx, [rsp+8] ; this is our rsp WITHOUT current ret addr + mov [rdi+30h], rdx + mov rdx, [rsp] ; save return addr ptr for new rip + mov [rdi+38h], rdx + xor rax, rax ; always return 0 + ret + + +narch_longjmp: + ; Based on code from musl libc Copyright © 2005-2014 Rich Felker, et al. + mov rax, rsi ; val will be longjmp return + test rax, rax + jnz @F + inc rax ; if val==0, val=1 per longjmp semantics +@@: + mov rbx, [rdi] ; rdi is the jmp_buf, restore regs from it + mov rbp, [rdi+8] + mov r12, [rdi+10h] + mov r13, [rdi+18h] + mov r14, [rdi+20h] + mov r15, [rdi+28h] + mov rsp, [rdi+30h] ; this ends up being the stack pointer + mov rdx, [rdi+38h] ; this is the instruction pointer + jmp rdx ; goto saved address without altering rsp + + +narch_coroSwitchStack: + pop rsi ; return address + mov rdx, rsp ; old rsp for saving + mov rsp, rdi ; swap stack with one passed to func + push rdx ; store old stack pointer on newly switched stack + sub rsp, 8h ; stack alignment + jmp rsi ; return + + +narch_coroRestoreStack: + pop rsi ; return address + add rsp, 8h ; stack alignment + pop rsp ; resture old stack pointer + jmp rsi ; return diff --git a/lib/arch/unix_i386.asm b/lib/arch/unix_i386.asm new file mode 100644 index 000000000..278679067 --- /dev/null +++ b/lib/arch/unix_i386.asm @@ -0,0 +1,12 @@ +; +; +; Nim's Runtime Library +; (c) Copyright 2015 Rokas Kupstys +; +; See the file "copying.txt", included in this +; distribution, for details about the copyright. +; + +format ELF + +include 'i386.asm' diff --git a/lib/nimbase.h b/lib/nimbase.h index d3108812c..2828eaff2 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -110,18 +110,31 @@ __clang__ # endif # define N_LIB_IMPORT extern __declspec(dllimport) #else -# define N_CDECL(rettype, name) rettype name -# define N_STDCALL(rettype, name) rettype name -# define N_SYSCALL(rettype, name) rettype name -# define N_FASTCALL(rettype, name) rettype name -# define N_SAFECALL(rettype, name) rettype name -/* function pointers with calling convention: */ -# define N_CDECL_PTR(rettype, name) rettype (*name) -# define N_STDCALL_PTR(rettype, name) rettype (*name) -# define N_SYSCALL_PTR(rettype, name) rettype (*name) -# define N_FASTCALL_PTR(rettype, name) rettype (*name) -# define N_SAFECALL_PTR(rettype, name) rettype (*name) - +# if defined(__GNUC__) +# define N_CDECL(rettype, name) rettype name +# define N_STDCALL(rettype, name) rettype name +# define N_SYSCALL(rettype, name) rettype name +# define N_FASTCALL(rettype, name) __attribute__((fastcall)) rettype name +# define N_SAFECALL(rettype, name) rettype name +/* function pointers with calling convention: */ +# define N_CDECL_PTR(rettype, name) rettype (*name) +# define N_STDCALL_PTR(rettype, name) rettype (*name) +# define N_SYSCALL_PTR(rettype, name) rettype (*name) +# define N_FASTCALL_PTR(rettype, name) __attribute__((fastcall)) rettype (*name) +# define N_SAFECALL_PTR(rettype, name) rettype (*name) +# else +# define N_CDECL(rettype, name) rettype name +# define N_STDCALL(rettype, name) rettype name +# define N_SYSCALL(rettype, name) rettype name +# define N_FASTCALL(rettype, name) rettype name +# define N_SAFECALL(rettype, name) rettype name +/* function pointers with calling convention: */ +# define N_CDECL_PTR(rettype, name) rettype (*name) +# define N_STDCALL_PTR(rettype, name) rettype (*name) +# define N_SYSCALL_PTR(rettype, name) rettype (*name) +# define N_FASTCALL_PTR(rettype, name) rettype (*name) +# define N_SAFECALL_PTR(rettype, name) rettype (*name) +# endif # ifdef __cplusplus # define N_LIB_EXPORT extern "C" # else diff --git a/lib/pure/concurrency/threadpool.nim b/lib/pure/concurrency/threadpool.nim index 7c9d8adfd..0079cf302 100644 --- a/lib/pure/concurrency/threadpool.nim +++ b/lib/pure/concurrency/threadpool.nim @@ -322,6 +322,9 @@ var distinguished: array[MaxDistinguishedThread, TThread[ptr Worker]] distinguishedData: array[MaxDistinguishedThread, Worker] +when defined(nimPinToCpu): + var gCpus: Natural + proc setMinPoolSize*(size: range[1..MaxThreadPoolSize]) = ## sets the minimal thread pool size. The default value of this is 4. minPoolSize = size @@ -342,6 +345,8 @@ proc activateWorkerThread(i: int) {.noinline.} = workersData[i].q.empty = createSemaphore() initLock(workersData[i].q.lock) createThread(workers[i], slave, addr(workersData[i])) + when defined(nimPinToCpu): + if gCpus > 0: pinToCpu(workers[i], i mod gCpus) proc activateDistinguishedThread(i: int) {.noinline.} = distinguishedData[i].taskArrived = createSemaphore() @@ -353,7 +358,10 @@ proc activateDistinguishedThread(i: int) {.noinline.} = createThread(distinguished[i], distinguishedSlave, addr(distinguishedData[i])) proc setup() = - currentPoolSize = min(countProcessors(), MaxThreadPoolSize) + let p = countProcessors() + when defined(nimPinToCpu): + gCpus = p + currentPoolSize = min(p, MaxThreadPoolSize) readyWorker = addr(workersData[0]) for i in 0.. <currentPoolSize: activateWorkerThread(i) diff --git a/lib/pure/coro.nim b/lib/pure/coro.nim new file mode 100644 index 000000000..6ef5f6f54 --- /dev/null +++ b/lib/pure/coro.nim @@ -0,0 +1,145 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Rokas Kupstys +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +when not defined(nimCoroutines): + {.error: "Coroutines require -d:nimCoroutines".} + +import os, times +import macros +import arch +import lists + +const coroDefaultStackSize = 512 * 1024 + + +type Coroutine = ref object + # prev: ptr Coroutine + # next: ptr Coroutine + ctx: JmpBuf + fn: proc() + started: bool + lastRun: float + sleepTime: float + stack: pointer + stacksize: int + +var coroutines = initDoublyLinkedList[Coroutine]() +var current: Coroutine +var mainCtx: JmpBuf + + +proc GC_addStack(starts: pointer) {.cdecl, importc.} +proc GC_removeStack(starts: pointer) {.cdecl, importc.} +proc GC_setCurrentStack(starts, pos: pointer) {.cdecl, importc.} + + +proc coroStart*(c: proc(), stacksize: int=coroDefaultStackSize) = + ## 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) + +{.push stackTrace: off.} +proc coroYield*(sleepTime: float=0) = + ## Stops coroutine execution and resumes no sooner than after ``sleeptime`` seconds. + ## Until then other coroutines are executed. + var oldFrame = getFrame() + var sp {.volatile.}: pointer + GC_setCurrentStack(current.stack, cast[pointer](addr sp)) + current.sleepTime = sleep_time + current.lastRun = epochTime() + if setjmp(current.ctx) == 0: + longjmp(mainCtx, 1) + setFrame(oldFrame) +{.pop.} + +proc coroRun*() = + ## Starts main event loop which exits when all coroutines exit. Calling this proc + ## starts execution of first coroutine. + var node = coroutines.head + var minDelay: float = 0 + var frame: PFrame + while node != nil: + var coro = node.value + current = coro + os.sleep(int(minDelay * 1000)) + + var remaining = coro.sleepTime - (epochTime() - coro.lastRun); + 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: + if minDelay > 0 and remaining > 0: + minDelay = min(remaining, minDelay) + else: + minDelay = remaining + + if node == nil or node.next == nil: + node = coroutines.head + else: + node = node.next + + +proc coroAlive*(c: proc()): bool = + ## Returns ``true`` if coroutine has not returned, ``false`` otherwise. + for coro in items(coroutines): + if coro.fn == c: + return true + +proc coroWait*(c: proc(), interval=0.01) = + ## Returns only after coroutine ``c`` has returned. ``interval`` is time in seconds how often. + while coroAlive(c): + coroYield interval + + +when isMainModule: + var stackCheckValue = 1100220033 + proc c2() + + proc c1() = + for i in 0 .. 3: + echo "c1" + coroYield 0.05 + echo "c1 exits" + + + proc c2() = + for i in 0 .. 3: + echo "c2" + coroYield 0.025 + coroWait(c1) + echo "c2 exits" + + coroStart(c1) + coroStart(c2) + coroRun() + echo "done ", stackCheckValue diff --git a/lib/pure/os.nim b/lib/pure/os.nim index c2c28c2b1..3d592a526 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -461,8 +461,10 @@ proc getLastAccessTime*(file: string): Time {.rtl, extern: "nos$1".} = proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} = ## Returns the `file`'s creation time. - ## Note that under posix OS's, the returned time may actually be the time at - ## which the file's attribute's were last modified. + ## + ## **Note:** Under POSIX OS's, the returned time may actually be the time at + ## which the file's attribute's were last modified. See + ## `here <https://github.com/nim-lang/Nim/issues/1058>`_ for details. when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index add4bc0a8..81befccff 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -468,7 +468,14 @@ when defined(Windows) and not defined(useNimRtl): fileClose(si.hStdError) if e != nil: dealloc(e) - if success == 0: raiseOSError(lastError, command) + if success == 0: + const errInvalidParameter = 87.int + const errFileNotFound = 2.int + if lastError.int in {errInvalidParameter, errFileNotFound}: + raiseOSError(lastError, + "Requested command not found: '$1'. OS error:" % command) + else: + raiseOSError(lastError, command) # Close the handle now so anyone waiting is woken: discard closeHandle(procInfo.hThread) result.fProcessHandle = procInfo.hProcess @@ -778,7 +785,8 @@ elif not defined(useNimRtl): var error: cint let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error)) if sizeRead == sizeof(error): - raiseOSError($strerror(error)) + raiseOSError("Could not find command: '$1'. OS error: $2" % + [$data.sysCommand, $strerror(error)]) return pid diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index 1890a9bf4..492de3b46 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -14,7 +14,7 @@ type Url* = distinct string Uri* = object - scheme*, username*, password*: string + scheme*, username*, password*: string hostname*, port*, path*, query*, anchor*: string opaque*: bool @@ -69,7 +69,7 @@ proc parseAuthority(authority: string, result: var Uri) = i.inc proc parsePath(uri: string, i: var int, result: var Uri) = - + i.inc parseUntil(uri, result.path, {'?', '#'}, i) # The 'mailto' scheme's PATH actually contains the hostname/username @@ -104,19 +104,23 @@ proc parseUri*(uri: string, result: var Uri) = var i = 0 # Check if this is a reference URI (relative URI) + let doubleSlash = uri.len > 1 and uri[1] == '/' if uri[i] == '/': - parsePath(uri, i, result) - return + # Make sure ``uri`` doesn't begin with '//'. + if not doubleSlash: + parsePath(uri, i, result) + return # Scheme i.inc parseWhile(uri, result.scheme, Letters + Digits + {'+', '-', '.'}, i) - if uri[i] != ':': + if uri[i] != ':' and not doubleSlash: # Assume this is a reference URI (relative URI) i = 0 result.scheme.setLen(0) parsePath(uri, i, result) return - i.inc # Skip ':' + if not doubleSlash: + i.inc # Skip ':' # Authority if uri[i] == '/' and uri[i+1] == '/': @@ -201,7 +205,7 @@ proc combine*(base: Uri, reference: Uri): Uri = ## ## let bar = combine(parseUri("http://example.com/foo/bar/"), parseUri("baz")) ## assert bar.path == "/foo/bar/baz" - + template setAuthority(dest, src: expr): stmt = dest.hostname = src.hostname dest.username = src.username @@ -369,6 +373,15 @@ when isMainModule: doAssert test.path == "test/no/slash" doAssert($test == str) + block: + let str = "//git@github.com:dom96/packages" + let test = parseUri(str) + doAssert test.scheme == "" + doAssert test.username == "git" + doAssert test.hostname == "github.com" + doAssert test.port == "dom96" + doAssert test.path == "/packages" + # Remove dot segments tests block: doAssert removeDotSegments("/foo/bar/baz") == "/foo/bar/baz" diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 5d2faa2d6..df28c1493 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -44,10 +44,12 @@ var # a global variable for the root of all try blocks currException {.threadvar.}: ref Exception +proc getFrame*(): PFrame {.compilerRtl, inl.} = framePtr + proc popFrame {.compilerRtl, inl.} = framePtr = framePtr.prev -proc setFrame(s: PFrame) {.compilerRtl, inl.} = +proc setFrame*(s: PFrame) {.compilerRtl, inl.} = framePtr = s proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} = diff --git a/lib/system/gc.nim b/lib/system/gc.nim index ae8bb724f..0c632aeb1 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -16,6 +16,10 @@ # Special care has been taken to avoid recursion as far as possible to avoid # stack overflows when traversing deep datastructures. It is well-suited # for soft real time applications (like games). + +when defined(nimCoroutines): + import arch + {.push profiler:off.} const @@ -64,8 +68,16 @@ 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 + GcHeap {.final, pure.} = object # this contains the zero count and # non-zero count table + stack: ptr GcStack stackBottom: pointer cycleThreshold: int when useCellIds: @@ -154,7 +166,7 @@ template gcTrace(cell, state: expr): stmt {.immediate.} = # forward declarations: proc collectCT(gch: var GcHeap) {.benign.} -proc isOnStack*(p: pointer): bool {.noinline, benign.} +proc isOnStack(p: pointer): bool {.noinline, benign.} proc forAllChildren(cell: PCell, op: WalkOp) {.benign.} proc doOperation(p: pointer, op: WalkOp) {.benign.} proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.benign.} @@ -293,20 +305,6 @@ proc initGC() = when useMarkForDebug or useBackupGc: init(gch.marked) -var - localGcInitialized {.rtlThreadVar.}: bool - -proc setupForeignThreadGc*() = - ## call this if you registered a callback that will be run from a thread not - ## under your control. This has a cheap thread-local guard, so the GC for - ## this thread will only be initialized once per thread, no matter how often - ## it is called. - if not localGcInitialized: - localGcInitialized = true - var stackTop {.volatile.}: pointer - setStackBottom(addr(stackTop)) - initGC() - when useMarkForDebug or useBackupGc: type GlobalMarkerProc = proc () {.nimcall, benign.} @@ -816,138 +814,7 @@ proc markThreadStacks(gch: var GcHeap) = sp = sp +% sizeof(pointer) it = it.next -# ----------------- stack management -------------------------------------- -# inspired from Smart Eiffel - -when defined(sparc): - const stackIncreases = false -elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or - defined(hp9000s700) or defined(hp9000s800) or defined(hp9000s820): - const stackIncreases = true -else: - const stackIncreases = false - -when not defined(useNimRtl): - {.push stack_trace: off.} - proc setStackBottom(theStackBottom: pointer) = - #c_fprintf(c_stdout, "stack bottom: %p;\n", theStackBottom) - # the first init must be the one that defines the stack bottom: - if gch.stackBottom == nil: gch.stackBottom = theStackBottom - else: - var a = cast[ByteAddress](theStackBottom) # and not PageMask - PageSize*2 - var b = cast[ByteAddress](gch.stackBottom) - #c_fprintf(c_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.} - -proc stackSize(): int {.noinline.} = - var stackTop {.volatile.}: pointer - result = abs(cast[int](addr(stackTop)) - cast[int](gch.stackBottom)) - -when defined(sparc): # For SPARC architecture. - proc isOnStack(p: pointer): bool = - var stackTop {.volatile.}: pointer - stackTop = addr(stackTop) - var b = cast[TAddress](gch.stackBottom) - var a = cast[TAddress](stackTop) - var x = cast[TAddress](p) - result = a <=% x and x <=% b - - template forEachStackSlot(gch, gcMark: expr) {.immediate, dirty.} = - when defined(sparcv9): - asm """"flushw \n" """ - else: - asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """ - - var - max = gch.stackBottom - sp: PPointer - stackTop: array[0..1, pointer] - sp = addr(stackTop[0]) - # Addresses decrease as the stack grows. - while sp <= max: - gcMark(gch, sp[]) - sp = cast[PPointer](cast[TAddress](sp) +% sizeof(pointer)) - -elif defined(ELATE): - {.error: "stack marking code is to be written for this architecture".} - -elif stackIncreases: - # --------------------------------------------------------------------------- - # Generic code for architectures where addresses increase as the stack grows. - # --------------------------------------------------------------------------- - proc isOnStack(p: pointer): bool = - var stackTop {.volatile.}: pointer - stackTop = addr(stackTop) - var a = cast[TAddress](gch.stackBottom) - var b = cast[TAddress](stackTop) - var x = cast[TAddress](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 - # in a platform independent way - - template forEachStackSlot(gch, gcMark: expr) {.immediate, dirty.} = - var registers: C_JmpBuf - if c_setjmp(registers) == 0'i32: # To fill the C stack with registers. - var max = cast[TAddress](gch.stackBottom) - var sp = cast[TAddress](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) - -else: - # --------------------------------------------------------------------------- - # Generic code for architectures where addresses decrease as the stack grows. - # --------------------------------------------------------------------------- - 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: expr) {.immediate, 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)) - 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) - # Make sure sp is word-aligned - sp = sp and not (sizeof(pointer) - 1) - # 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) +include gc_common proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = forEachStackSlot(gch, gcMark) @@ -956,10 +823,6 @@ when useMarkForDebug or useBackupGc: proc markStackAndRegistersForSweep(gch: var GcHeap) = forEachStackSlot(gch, stackMarkS) -# ---------------------------------------------------------------------------- -# end of non-portable code -# ---------------------------------------------------------------------------- - proc collectZCT(gch: var GcHeap): bool = # Note: Freeing may add child objects to the ZCT! So essentially we do # deep freeing, which is bad for incremental operation. In order to @@ -1033,7 +896,8 @@ proc collectCTBody(gch: var GcHeap) = let t0 = getticks() sysAssert(allocInv(gch.region), "collectCT: begin") - gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize()) + when not defined(nimCoroutines): + gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize()) sysAssert(gch.decStack.len == 0, "collectCT") prepareForInteriorPointerChecking(gch.region) markStackAndRegisters(gch) @@ -1064,11 +928,19 @@ when useMarkForDebug or useBackupGc: markStackAndRegistersForSweep(gch) markGlobals(gch) +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. - let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold) + when defined(nimCoroutines): + 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 getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and gch.recGcLock == 0: @@ -1137,8 +1009,13 @@ when not defined(useNimRtl): "[GC] max threshold: " & $gch.stat.maxThreshold & "\n" & "[GC] zct capacity: " & $gch.zct.cap & "\n" & "[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" & - "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" & "[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) + when defined(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" + else: + result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" GC_enable() {.pop.} diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim new file mode 100644 index 000000000..c7dd667e4 --- /dev/null +++ b/lib/system/gc_common.nim @@ -0,0 +1,275 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Rokas Kupstys +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +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(c_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 + 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") +else: + proc stackSize(): int {.noinline.} = + var stackTop {.volatile.}: pointer + result = abs(cast[int](addr(stackTop)) - cast[int](gch.stackBottom)) + +iterator items(stack: ptr GcStack): ptr GcStack = + var s = stack + while not isNil(s): + yield s + s = s.next + +var + localGcInitialized {.rtlThreadVar.}: bool + +proc setupForeignThreadGc*() = + ## call this if you registered a callback that will be run from a thread not + ## under your control. This has a cheap thread-local guard, so the GC for + ## this thread will only be initialized once per thread, no matter how often + ## it is called. + if not localGcInitialized: + localGcInitialized = true + var stackTop {.volatile.}: pointer + setStackBottom(addr(stackTop)) + initGC() + +# ----------------- stack management -------------------------------------- +# inspired from Smart Eiffel + +when defined(sparc): + const stackIncreases = false +elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or + defined(hp9000s700) or defined(hp9000s800) or defined(hp9000s820): + const stackIncreases = true +else: + const stackIncreases = false + +when not defined(useNimRtl): + {.push stack_trace: off.} + proc setStackBottom(theStackBottom: pointer) = + #c_fprintf(c_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 + else: + var a = cast[ByteAddress](theStackBottom) # and not PageMask - PageSize*2 + var b = cast[ByteAddress](gch.stackBottom) + #c_fprintf(c_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.} + +when defined(sparc): # For SPARC architecture. + 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 b = cast[TAddress](gch.stackBottom) + var a = cast[TAddress](stackTop) + var x = cast[TAddress](p) + result = a <=% x and x <=% b + + template forEachStackSlot(gch, gcMark: expr) {.immediate, dirty.} = + when defined(sparcv9): + asm """"flushw \n" """ + else: + asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """ + + var + max = gch.stackBottom + sp: PPointer + stackTop: array[0..1, pointer] + sp = addr(stackTop[0]) + # Addresses decrease as the stack grows. + while sp <= max: + gcMark(gch, sp[]) + sp = cast[PPointer](cast[TAddress](sp) +% sizeof(pointer)) + +elif defined(ELATE): + {.error: "stack marking code is to be written for this architecture".} + +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[TAddress](gch.stackBottom) + var b = cast[TAddress](stackTop) + var x = cast[TAddress](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 + # in a platform independent way + + template forEachStackSlot(gch, gcMark: expr) {.immediate, dirty.} = + var registers: C_JmpBuf + if c_setjmp(registers) == 0'i32: # To fill the C stack with registers. + var max = cast[TAddress](gch.stackBottom) + var sp = cast[TAddress](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) + +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: expr) {.immediate, 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: expr) {.immediate, 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)) + 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) + # Make sure sp is word-aligned + sp = sp and not (sizeof(pointer) - 1) + # 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) + +# ---------------------------------------------------------------------------- +# end of non-portable code +# ---------------------------------------------------------------------------- diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index ee80c61e9..d1aecb7a2 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -9,6 +9,10 @@ # 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 @@ -44,8 +48,16 @@ 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 + GcHeap = object # this contains the zero count and # non-zero count table + stack: ptr GcStack stackBottom: pointer cycleThreshold: int when useCellIds: @@ -118,7 +130,6 @@ when BitsPerPage mod (sizeof(int)*8) != 0: # forward declarations: proc collectCT(gch: var GcHeap) {.benign.} -proc isOnStack*(p: pointer): bool {.noinline, benign.} proc forAllChildren(cell: PCell, op: WalkOp) {.benign.} proc doOperation(p: pointer, op: WalkOp) {.benign.} proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.benign.} @@ -169,20 +180,6 @@ proc initGC() = init(gch.allocated) init(gch.marked) -var - localGcInitialized {.rtlThreadVar.}: bool - -proc setupForeignThreadGc*() = - ## call this if you registered a callback that will be run from a thread not - ## under your control. This has a cheap thread-local guard, so the GC for - ## this thread will only be initialized once per thread, no matter how often - ## it is called. - if not localGcInitialized: - localGcInitialized = true - var stackTop {.volatile.}: pointer - setStackBottom(addr(stackTop)) - initGC() - proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.benign.} = var d = cast[ByteAddress](dest) case n.kind @@ -407,145 +404,14 @@ proc gcMark(gch: var GcHeap, p: pointer) {.inline.} = if objStart != nil: mark(gch, objStart) -# ----------------- stack management -------------------------------------- -# inspired from Smart Eiffel +include gc_common -when defined(sparc): - const stackIncreases = false -elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or - defined(hp9000s700) or defined(hp9000s800) or defined(hp9000s820): - const stackIncreases = true -else: - const stackIncreases = false - -when not defined(useNimRtl): - {.push stack_trace: off.} - proc setStackBottom(theStackBottom: pointer) = - #c_fprintf(c_stdout, "stack bottom: %p;\n", theStackBottom) - # the first init must be the one that defines the stack bottom: - if gch.stackBottom == nil: gch.stackBottom = theStackBottom - else: - var a = cast[ByteAddress](theStackBottom) # and not PageMask - PageSize*2 - var b = cast[ByteAddress](gch.stackBottom) - #c_fprintf(c_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.} - -proc stackSize(): int {.noinline.} = - var stackTop {.volatile.}: pointer - result = abs(cast[int](addr(stackTop)) - cast[int](gch.stackBottom)) - -when defined(sparc): # For SPARC architecture. - 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 - - proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = - when defined(sparcv9): - asm """"flushw \n" """ - else: - asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """ - - var - max = gch.stackBottom - sp: PPointer - stackTop: array[0..1, pointer] - sp = addr(stackTop[0]) - # Addresses decrease as the stack grows. - while sp <= max: - gcMark(gch, sp[]) - sp = cast[ppointer](cast[ByteAddress](sp) +% sizeof(pointer)) - -elif defined(ELATE): - {.error: "stack marking code is to be written for this architecture".} - -elif stackIncreases: - # --------------------------------------------------------------------------- - # Generic code for architectures where addresses increase as the stack grows. - # --------------------------------------------------------------------------- - 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 - # in a platform independent way - - proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = - var registers: 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)) +% 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) - -else: - # --------------------------------------------------------------------------- - # Generic code for architectures where addresses decrease as the stack grows. - # --------------------------------------------------------------------------- - 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 - - proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = - # 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)) - 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) - # Make sure sp is word-aligned - sp = sp and not (sizeof(pointer) - 1) - # 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) - -# ---------------------------------------------------------------------------- -# end of non-portable code -# ---------------------------------------------------------------------------- +proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = + forEachStackSlot(gch, gcMark) proc collectCTBody(gch: var GcHeap) = - gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize()) + when not defined(nimCoroutines): + gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize()) prepareForInteriorPointerChecking(gch.region) markStackAndRegisters(gch) markGlobals(gch) @@ -599,8 +465,13 @@ when not defined(useNimRtl): "[GC] occupied memory: " & $getOccupiedMem() & "\n" & "[GC] collections: " & $gch.stat.collections & "\n" & "[GC] max threshold: " & $gch.stat.maxThreshold & "\n" & - "[GC] freed objects: " & $gch.stat.freedObjects & "\n" & - "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" + "[GC] freed objects: " & $gch.stat.freedObjects & "\n" + when defined(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" + else: + result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" GC_enable() {.pop.} diff --git a/lib/system/inclrtl.nim b/lib/system/inclrtl.nim index dbc961402..d0dc38284 100644 --- a/lib/system/inclrtl.nim +++ b/lib/system/inclrtl.nim @@ -33,7 +33,7 @@ elif defined(useNimRtl): when defined(windows): const nimrtl* = "nimrtl.dll" elif defined(macosx): - const nimrtl* = "nimrtl.dylib" + const nimrtl* = "libnimrtl.dylib" else: const nimrtl* = "libnimrtl.so" {.pragma: rtl, importc: "nimrtl_$1", dynlib: nimrtl, gcsafe.} diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 8ad48d368..265f92fa2 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -74,7 +74,10 @@ proc raiseEIO(msg: string) {.noinline, noreturn.} = proc readLine(f: File, line: var TaintedString): bool = var pos = 0 # Use the currently reserved space for a first try - var space = cast[PGenericSeq](line.string).space + when defined(nimscript): + var space = 80 + else: + var space = cast[PGenericSeq](line.string).space line.string.setLen(space) while true: diff --git a/tests/gc/gctest.nim b/tests/gc/gctest.nim index a634a47f2..b3b9af608 100644 --- a/tests/gc/gctest.nim +++ b/tests/gc/gctest.nim @@ -22,7 +22,7 @@ type data: string sons: seq[TBNode] # directly embedded! t: TTable - + TCaseKind = enum nkStr, nkWhole, nkList PCaseNode = ref TCaseNode TCaseNode {.final.} = object @@ -33,7 +33,7 @@ type TIdObj* = object of TObject id*: int # unique id; use this for comparisons and not the pointers - + PIdObj* = ref TIdObj PIdent* = ref TIdent TIdent*{.acyclic.} = object of TIdObj @@ -53,22 +53,21 @@ proc newCaseNode(data: string): PCaseNode = result.kind = nkWhole result.unused = @["", "abc", "abdc"] flip = 1 - flip - + proc newCaseNode(a, b: PCaseNode): PCaseNode = new(result) result.kind = nkList result.sons = @[a, b] - + proc caseTree(lvl: int = 0): PCaseNode = if lvl == 3: result = newCaseNode("data item") else: result = newCaseNode(caseTree(lvl+1), caseTree(lvl+1)) -proc finalizeBNode(n: TBNode) = writeLine(stdout, n.data) proc finalizeNode(n: PNode) = assert(n != nil) write(stdout, "finalizing: ") if isNil(n.data): writeLine(stdout, "nil!") - else: writeLine(stdout, n.data) + else: writeLine(stdout, "not nil") var id: int = 1 @@ -147,7 +146,7 @@ proc buildBTree(father: var TBNode) = father.t.data = @["ha", "lets", "stress", "it"] setSons(father) -proc getIdent(identifier: cstring, length: int, h: int): PIdent = +proc getIdent(identifier: cstring, length: int, h: int): PIdent = new(result) result.h = h result.s = newString(length) @@ -157,7 +156,7 @@ proc main() = discard getIdent("hall", 4, 0) discard getIdent("echo", 4, 0) discard getIdent("huch", 4, 0) - + var father: TBNode for i in 1..1_00: diff --git a/tests/metatype/tprocbothmeta.nim b/tests/metatype/tprocbothmeta.nim new file mode 100644 index 000000000..ad12c5d26 --- /dev/null +++ b/tests/metatype/tprocbothmeta.nim @@ -0,0 +1,10 @@ + +proc myFun[A,B](x: A): B = + result = float(x+10) + +proc myMap[T,S](sIn: seq[T], f: proc (q: T): S): seq[S] = + result = newSeq[S](sIn.len) + for i in 0..<sIn.len: + result[i] = f(sIn[i]) + +assert myMap(@[1,2,3], myFun) == @[11.0, 12.0, 13.0] diff --git a/web/news.txt b/web/news.txt index 46867ac4c..040442695 100644 --- a/web/news.txt +++ b/web/news.txt @@ -56,10 +56,12 @@ News Library additions ----------------- - - The nre module has been added, providing a better interface to PCRE than - re. + - The nre module has been added, providing a better interface to PCRE than re. - The ``expandSymlink`` proc has been added to the ``os`` module. - The ``tailDir`` proc has been added to the ``os`` module. + - Define ``nimPinToCpu`` to make the ``threadpool`` use explicit thread + affinities. This can speed up or slow down the thread pool; it's up to you + to benchmark it. Language Additions |