nimsuggest uses multithreading and full project recompiles

This commit is contained in:
Andreas Rumpf 2017-02-13 13:37:50 +01:00
commit 9f142e199d
6 changed files with 223 additions and 29 deletions

View file

@ -40,9 +40,13 @@ type
# module dependencies. # module dependencies.
backend*: RootRef # minor hack so that a backend can extend this easily backend*: RootRef # minor hack so that a backend can extend this easily
config*: ConfigRef config*: ConfigRef
doStopCompile*: proc(): bool {.closure.}
{.this: g.} {.this: g.}
proc stopCompile*(g: ModuleGraph): bool {.inline.} =
result = doStopCompile != nil and doStopCompile()
proc newModuleGraph*(config: ConfigRef = nil): ModuleGraph = proc newModuleGraph*(config: ConfigRef = nil): ModuleGraph =
result = ModuleGraph() result = ModuleGraph()
initStrTable(result.packageSyms) initStrTable(result.packageSyms)

View file

@ -171,6 +171,7 @@ proc processImplicits(implicits: seq[string], nodeKind: TNodeKind,
proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream,
rd: PRodReader; cache: IdentCache): bool {.discardable.} = rd: PRodReader; cache: IdentCache): bool {.discardable.} =
if graph.stopCompile(): return true
var var
p: TParsers p: TParsers
a: TPassContextArray a: TPassContextArray
@ -198,6 +199,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream,
processImplicits implicitIncludes, nkIncludeStmt, a, module processImplicits implicitIncludes, nkIncludeStmt, a, module
while true: while true:
if graph.stopCompile(): break
var n = parseTopLevelStmt(p) var n = parseTopLevelStmt(p)
if n.kind == nkEmpty: break if n.kind == nkEmpty: break
if sfNoForward in module.flags: if sfNoForward in module.flags:
@ -219,6 +221,8 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream,
else: else:
openPassesCached(graph, a, module, rd) openPassesCached(graph, a, module, rd)
var n = loadInitSection(rd) var n = loadInitSection(rd)
for i in countup(0, sonsLen(n) - 1): processTopLevelStmtCached(n.sons[i], a) for i in countup(0, sonsLen(n) - 1):
if graph.stopCompile(): break
processTopLevelStmtCached(n.sons[i], a)
closePassesCached(a) closePassesCached(a)
result = true result = true

View file

@ -28,7 +28,7 @@ type
column*: int # Starts at 0 column*: int # Starts at 0
doc*: string # Not escaped (yet) doc*: string # Not escaped (yet)
symkind*: TSymKind symkind*: TSymKind
forth*: string # XXX TODO object on symkind forth*: string # type
quality*: range[0..100] # matching quality quality*: range[0..100] # matching quality
isGlobal*: bool # is a global variable isGlobal*: bool # is a global variable
tokenLen*: int tokenLen*: int
@ -88,7 +88,7 @@ proc symToSuggest(s: PSym, isLocal: bool, section: string, li: TLineInfo;
when not defined(noDocgen): when not defined(noDocgen):
result.doc = s.extractDocComment result.doc = s.extractDocComment
proc `$`(suggest: Suggest): string = proc `$`*(suggest: Suggest): string =
result = $suggest.section result = $suggest.section
result.add(sep) result.add(sep)
if suggest.section == ideHighlight: if suggest.section == ideHighlight:
@ -131,7 +131,7 @@ proc suggestResult(s: Suggest) =
if not isNil(suggestionResultHook): if not isNil(suggestionResultHook):
suggestionResultHook(s) suggestionResultHook(s)
else: else:
suggestWriteln($(s)) suggestWriteln($s)
proc filterSym(s: PSym): bool {.inline.} = proc filterSym(s: PSym): bool {.inline.} =
result = s.kind != skModule result = s.kind != skModule

View file

@ -180,7 +180,7 @@ proc executeEpc(cmd: IdeCmd, args: SexpNode;
dirtyfile = args[3].getStr(nil) dirtyfile = args[3].getStr(nil)
execute(cmd, file, dirtyfile, int(line), int(column), graph, cache) execute(cmd, file, dirtyfile, int(line), int(column), graph, cache)
proc returnEpc(socket: var Socket, uid: BiggestInt, s: SexpNode|string, proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string,
return_symbol = "return") = return_symbol = "return") =
let response = $convertSexp([newSSymbol(return_symbol), uid, s]) let response = $convertSexp([newSSymbol(return_symbol), uid, s])
socket.send(toHex(len(response), 6)) socket.send(toHex(len(response), 6))
@ -208,6 +208,45 @@ template checkSanity(client, sizeHex, size, messageBuffer: typed) =
if client.recv(messageBuffer, size) != size: if client.recv(messageBuffer, size) != size:
raise newException(ValueError, "didn't get all the bytes") raise newException(ValueError, "didn't get all the bytes")
var
requests: Channel[string]
results: Channel[Suggest]
proc toStdout() {.gcsafe.} =
while true:
let res = results.recv()
case res.section
of ideNone: break
of ideChk: echo res.doc
else: echo res
proc toSocket(stdoutSocket: Socket) {.gcsafe.} =
while true:
let res = results.recv()
case res.section
of ideNone: break
of ideChk: stdoutSocket.send(res.doc & "\c\L")
else: stdoutSocket.send($res & "\c\L")
proc toEpc(client: Socket; uid: BiggestInt) {.gcsafe.} =
var list = newSList()
while true:
let res = results.recv()
case res.section
of ideNone: break
of ideChk:
returnEpc(client, uid, sexp(res.doc))
else:
list.add sexp(res)
if gIdeCmd != ideChk:
returnEPC(client, uid, list)
proc writelnHook(line: string) =
results.send(Suggest(section: ideChk, doc: line))
proc sugResultHook(s: Suggest) =
results.send(s)
template setVerbosity(level: typed) = template setVerbosity(level: typed) =
gVerbosity = level gVerbosity = level
gNotes = NotesVerbosity[gVerbosity] gNotes = NotesVerbosity[gVerbosity]
@ -217,16 +256,108 @@ proc connectToNextFreePort(server: Socket, host: string): Port =
let (_, port) = server.getLocalAddr let (_, port) = server.getLocalAddr
result = port result = port
type
ThreadParams = tuple[port: Port; address: string]
proc replStdin(x: ThreadParams) {.thread.} =
if gEmitEof:
echo DummyEof
while true:
let line = readLine(stdin)
requests.send line
toStdout()
echo DummyEof
flushFile(stdout)
else:
echo Help
var line = ""
while readLineFromStdin("> ", line):
requests.send line
toStdout()
echo ""
flushFile(stdout)
proc replTcp(x: ThreadParams) {.thread.} =
var server = newSocket()
server.bindAddr(x.port, x.address)
var inp = "".TaintedString
server.listen()
while true:
var stdoutSocket = newSocket()
accept(server, stdoutSocket)
stdoutSocket.readLine(inp)
requests.send inp
toSocket(stdoutSocket)
stdoutSocket.send("\c\L")
stdoutSocket.close()
proc replEpc(x: ThreadParams) {.thread.} =
var server = newSocket()
let port = connectToNextFreePort(server, "localhost")
server.listen()
echo port
var client = newSocket()
# Wait for connection
accept(server, client)
while true:
var
sizeHex = ""
size = 0
messageBuffer = ""
checkSanity(client, sizeHex, size, messageBuffer)
let
message = parseSexp($messageBuffer)
epcAPI = message[0].getSymbol
case epcAPI:
of "call":
let
uid = message[1].getNum
args = message[3]
gIdeCmd = parseIdeCmd(message[2].getSymbol)
case gIdeCmd
of ideChk:
setVerbosity(1)
# Use full path because other emacs plugins depends it
gListFullPaths = true
incl(gGlobalOptions, optIdeDebug)
of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight:
setVerbosity(0)
else: discard
requests.send messageBuffer
toEpc(client, uid)
of "methods":
returnEpc(client, message[1].getNum, listEPC())
of "epc-error":
# an unhandled exception forces down the whole process anyway, so we
# use 'quit' here instead of 'raise'
quit("recieved epc error: " & $messageBuffer)
else:
let errMessage = case epcAPI
of "return", "return-error":
"no return expected"
else:
"unexpected call: " & epcAPI
quit errMessage
proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) = proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) =
template sentinel() =
# send sentinel for the input reading thread:
results.send(Suggest(section: ideNone))
template toggle(sw) = template toggle(sw) =
if sw in gGlobalOptions: if sw in gGlobalOptions:
excl(gGlobalOptions, sw) excl(gGlobalOptions, sw)
else: else:
incl(gGlobalOptions, sw) incl(gGlobalOptions, sw)
sentinel()
return return
template err() = template err() =
echo Help echo Help
sentinel()
return return
var opc = "" var opc = ""
@ -260,8 +391,47 @@ proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) =
i += parseInt(cmd, col, i) i += parseInt(cmd, col, i)
execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache) execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache)
sentinel()
proc serveStdin(graph: ModuleGraph; cache: IdentCache) = proc recompileFullProject(graph: ModuleGraph; cache: IdentCache) =
resetSystemArtifacts()
#let graph = newGraph(oldgraph.config)
graph.resetAllModules()
compileProject(graph, cache)
proc mainThread(graph: ModuleGraph; cache: IdentCache) =
if gLogging:
var it = searchPaths.head
while it != nil:
logStr(PStrEntry(it).data)
it = it.next
msgs.writelnHook = writelnHook
suggestionResultHook = sugResultHook
graph.doStopCompile = proc (): bool = requests.peek() > 0
var idle = 0
while true:
let (hasData, req) = requests.tryRecv()
if hasData:
msgs.writelnHook = writelnHook
suggestionResultHook = sugResultHook
parseCmdLine(req, graph, cache)
idle = 0
else:
os.sleep 250
idle += 1
if idle == 20:
# we use some nimsuggest activity to enable a lazy recompile:
gIdeCmd = ideChk
msgs.writelnHook = proc (s: string) = discard
suggestionResultHook = proc (s: Suggest) = discard
recompileFullProject(graph, cache)
var
inputThread: Thread[ThreadParams]
proc serveStdin(graph: ModuleGraph; cache: IdentCache) {.deprecated.} =
if gEmitEof: if gEmitEof:
echo DummyEof echo DummyEof
while true: while true:
@ -277,7 +447,7 @@ proc serveStdin(graph: ModuleGraph; cache: IdentCache) =
echo "" echo ""
flushFile(stdout) flushFile(stdout)
proc serveTcp(graph: ModuleGraph; cache: IdentCache) = proc serveTcp(graph: ModuleGraph; cache: IdentCache) {.deprecated.} =
var server = newSocket() var server = newSocket()
server.bindAddr(gPort, gAddress) server.bindAddr(gPort, gAddress)
var inp = "".TaintedString var inp = "".TaintedString
@ -296,7 +466,7 @@ proc serveTcp(graph: ModuleGraph; cache: IdentCache) =
stdoutSocket.send("\c\L") stdoutSocket.send("\c\L")
stdoutSocket.close() stdoutSocket.close()
proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) = proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) {.deprecated.} =
var client = newSocket() var client = newSocket()
# Wait for connection # Wait for connection
accept(server, client) accept(server, client)
@ -365,25 +535,38 @@ proc mainCommand(graph: ModuleGraph; cache: IdentCache) =
# do not stop after the first error: # do not stop after the first error:
msgs.gErrorMax = high(int) msgs.gErrorMax = high(int)
open(requests)
open(results)
case gMode case gMode
of mstdin: of mstdin: createThread(inputThread, replStdin, (gPort, gAddress))
compileProject(graph, cache) of mtcp: createThread(inputThread, replTcp, (gPort, gAddress))
#modules.gFuzzyGraphChecking = false of mepc: createThread(inputThread, replEpc, (gPort, gAddress))
serveStdin(graph, cache) mainThread(graph, cache)
of mtcp: joinThread(inputThread)
# until somebody accepted the connection, produce no output (logging is too close(requests)
# slow for big projects): close(results)
msgs.writelnHook = proc (msg: string) = discard
compileProject(graph, cache) when false:
#modules.gFuzzyGraphChecking = false case gMode
serveTcp(graph, cache) of mstdin:
of mepc: compileProject(graph, cache)
var server = newSocket() #modules.gFuzzyGraphChecking = false
let port = connectToNextFreePort(server, "localhost") serveStdin(graph, cache)
server.listen() of mtcp:
echo port # until somebody accepted the connection, produce no output (logging is too
compileProject(graph, cache) # slow for big projects):
serveEpc(server, graph, cache) msgs.writelnHook = proc (msg: string) = discard
compileProject(graph, cache)
#modules.gFuzzyGraphChecking = false
serveTcp(graph, cache)
of mepc:
var server = newSocket()
let port = connectToNextFreePort(server, "localhost")
server.listen()
echo port
compileProject(graph, cache)
serveEpc(server, graph, cache)
proc processCmdLine*(pass: TCmdLinePass, cmd: string) = proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
var p = parseopt.initOptParser(cmd) var p = parseopt.initOptParser(cmd)

View file

@ -20,3 +20,6 @@ define:nimsuggest
#define:booting #define:booting
#define:noDocgen #define:noDocgen
--path:"$nim" --path:"$nim"
--threads:on
--noNimblePath
--path:"../../compiler"

View file

@ -206,8 +206,8 @@ $nimsuggest --tester $file
>sug $1 >sug $1
sug;;skField;;name;;string;;$file;;166;;6;;"";;100 sug;;skField;;name;;string;;$file;;166;;6;;"";;100
sug;;skField;;age;;int;;$file;;167;;6;;"";;100 sug;;skField;;age;;int;;$file;;167;;6;;"";;100
sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int{.noSideEffect, gcsafe, locks: 0.};;$file;;169;;9;;"";;100 sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100
sug;;skMacro;;twithin_macro.class;;proc (head: untyped, body: untyped): untyped{.gcsafe, locks: <unknown>.};;$file;;4;;6;;"Iterates over the children of the NimNode ``n``.";;100 sug;;skMacro;;twithin_macro.class;;proc (head: untyped, body: untyped): untyped{.gcsafe, locks: <unknown>.};;$file;;4;;6;;"Iterates over the children of the NimNode ``n``.";;100
sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string{.noSideEffect, gcsafe, locks: 0.};;$file;;168;;9;;"";;100 sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string;;$file;;168;;9;;"";;100
sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string{.noSideEffect, gcsafe, locks: 0.};;$file;;184;;9;;"";;100* sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;184;;9;;"";;100*
""" """