nimhcr.nim 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2018 Nim Contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. # This is the Nim hot code reloading run-time for the native targets.
  10. #
  11. # This minimal dynamic library is not subject to reloading when the
  12. # `hotCodeReloading` build mode is enabled. It's responsible for providing
  13. # a permanent memory location for all globals and procs within a program
  14. # and orchestrating the reloading. For globals, this is easily achieved
  15. # by storing them on the heap. For procs, we produce on the fly simple
  16. # trampolines that can be dynamically overwritten to jump to a different
  17. # target. In the host program, all globals and procs are first registered
  18. # here with ``hcrRegisterGlobal`` and ``hcrRegisterProc`` and then the
  19. # returned permanent locations are used in every reference to these symbols
  20. # onwards.
  21. #
  22. # Detailed description:
  23. #
  24. # When code is compiled with the hotCodeReloading option for native targets
  25. # a couple of things happen for all modules in a project:
  26. # - the useNimRtl option is forced (including when building the HCR runtime too)
  27. # - all modules of a target get built into separate shared libraries
  28. # - the smallest granularity of reloads is modules
  29. # - for each .c (or .cpp) in the corresponding nimcache folder of the project
  30. # a shared object is built with the name of the source file + DLL extension
  31. # - only the main module produces whatever the original project type intends
  32. # (again in nimcache) and is then copied to its original destination
  33. # - linking is done in parallel - just like compilation
  34. # - function calls to functions from the same project go through function pointers:
  35. # - with a few exceptions - see the nonReloadable pragma
  36. # - the forward declarations of the original functions become function
  37. # pointers as static globals with the same names
  38. # - the original function definitions get suffixed with <name>_actual
  39. # - the function pointers get initialized with the address of the corresponding
  40. # function in the DatInit of their module through a call to either hcrRegisterProc
  41. # or hcrGetProc. When being registered, the <name>_actual address is passed to
  42. # hcrRegisterProc and a permanent location is returned and assigned to the pointer.
  43. # This way the implementation (<name>_actual) can change but the address for it
  44. # will be the same - this works by just updating a jump instruction (trampoline).
  45. # For functions from other modules hcrGetProc is used (after they are registered).
  46. # - globals are initialized only once and their state is preserved
  47. # - including locals with the {.global.} pragma
  48. # - their definitions are changed into pointer definitions which are initialized
  49. # in the DatInit() of their module with calls to hcrRegisterGlobal (supplying the
  50. # size of the type that this HCR runtime should allocate) and a bool is returned
  51. # which when true triggers the initialization code for the global (only once).
  52. # Globals from other modules: a global pointer coupled with a hcrGetGlobal call.
  53. # - globals which have already been initialized cannot have their values changed
  54. # by changing their initialization - use a handler or some other mechanism
  55. # - new globals can be introduced when reloading
  56. # - top-level code (global scope) is executed only once - at the first module load
  57. # - the runtime knows every symbol's module owner (globals and procs)
  58. # - both the RTL and HCR shared libraries need to be near the program for execution
  59. # - same folder, in the PATH or LD_LIBRARY_PATH env var, etc (depending on OS)
  60. # - the main module is responsible for initializing the HCR runtime
  61. # - the main module loads the RTL and HCR shared objects
  62. # - after that a call to hcrInit() is done in the main module which triggers
  63. # the loading of all modules the main one imports, and doing that for the
  64. # dependencies of each module recursively. Basically a DFS traversal.
  65. # - then initialization takes place with several passes over all modules:
  66. # - HcrInit - initializes the pointers for HCR procs such as hcrRegisterProc
  67. # - HcrCreateTypeInfos - creates globals which will be referenced in the next pass
  68. # - DatInit - usual dat init + register/get procs and get globals
  69. # - Init - it does the following multiplexed operations:
  70. # - register globals (if already registered - then just retrieve pointer)
  71. # - execute top level scope (only if loaded for the first time)
  72. # - when modules are loaded the originally built shared libraries get copied in
  73. # the same folder and the copies are loaded instead of the original files
  74. # - a module import tree is built in the runtime (and maintained when reloading)
  75. # - hcrPerformCodeReload
  76. # - named `performCodeReload`, requires the hotcodereloading module
  77. # - explicitly called by the user - the current active callstack shouldn't contain
  78. # any functions which are defined in modules that will be reloaded (or crash!).
  79. # The reason is that old dynamic libraries get unloaded.
  80. # Example:
  81. # if A is the main module and it imports B, then only B is reloadable and only
  82. # if when calling hcrPerformCodeReload there is no function defined in B in the
  83. # current active callstack at the point of the call (it has to be done from A)
  84. # - for reloading to take place the user has to have rebuilt parts of the application
  85. # without changes affecting the main module in any way - it shouldn't be rebuilt.
  86. # - to determine what needs to be reloaded the runtime starts traversing the import
  87. # tree from the root and checks the timestamps of the loaded shared objects
  88. # - modules that are no longer referenced are unloaded and cleaned up properly
  89. # - symbols (procs/globals) that have been removed in the code are also cleaned up
  90. # - so changing the init of a global does nothing, but removing it, reloading,
  91. # and then re-introducing it with a new initializer works
  92. # - new modules can be imported, and imports can also be reodereded/removed
  93. # - hcrReloadNeeded() can be used to determine if any module needs reloading
  94. # - named `hasAnyModuleChanged`, requires the hotcodereloading module
  95. # - code in the beforeCodeReload/afterCodeReload handlers is executed on each reload
  96. # - require the hotcodereloading module
  97. # - such handlers can be added and removed
  98. # - before each reload all "beforeCodeReload" handlers are executed and after
  99. # that all handlers (including "after") from the particular module are deleted
  100. # - the order of execution is the same as the order of top-level code execution.
  101. # Example: if A imports B which imports C, then all handlers in C will be executed
  102. # first (from top to bottom) followed by all from B and lastly all from A
  103. # - after the reload all "after" handlers are executed the same way as "before"
  104. # - the handlers for a reloaded module are always removed when reloading and then
  105. # registered when the top-level scope is executed (thanks to `executeOnReload`)
  106. #
  107. # TODO next:
  108. #
  109. # - implement the before/after handlers and hasModuleChanged for the javascript target
  110. # - ARM support for the trampolines
  111. # - investigate:
  112. # - soon the system module might be importing other modules - the init order...?
  113. # (revert https://github.com/nim-lang/Nim/pull/11971 when working on this)
  114. # - rethink the closure iterators
  115. # - ability to keep old versions of dynamic libraries alive
  116. # - because of async server code
  117. # - perhaps with refcounting of .dlls for unfinished closures
  118. # - linking with static libs
  119. # - all shared objects for each module will (probably) have to link to them
  120. # - state in static libs gets duplicated
  121. # - linking is slow and therefore iteration time suffers
  122. # - have just a single .dll for all .nim files and bulk reload?
  123. # - think about the compile/link/passc/passl/emit/injectStmt pragmas
  124. # - if a passc pragma is introduced (either written or dragged in by a new
  125. # import) the whole command line for compilation changes - for example:
  126. # winlean.nim: {.passc: "-DWIN32_LEAN_AND_MEAN".}
  127. # - play with plugins/dlls/lfIndirect/lfDynamicLib/lfExportLib - shouldn't add an extra '*'
  128. # - everything thread-local related
  129. # - tests
  130. # - add a new travis build matrix entry which builds everything with HCR enabled
  131. # - currently building with useNimRtl is problematic - lots of problems...
  132. # - how to supply the nimrtl/nimhcr shared objects to all test binaries...?
  133. # - think about building to C++ instead of only to C - added type safety
  134. # - run tests through valgrind and the sanitizers!
  135. #
  136. # TODO - nice to have cool stuff:
  137. #
  138. # - separate handling of global state for much faster reloading and manipulation
  139. # - imagine sliders in an IDE for tweaking variables
  140. # - perhaps using shared memory
  141. # - multi-dll projects - how everything can be reloaded..?
  142. # - a single HCR instance shared across multiple .dlls
  143. # - instead of having to call hcrPerformCodeReload from a function in each dll
  144. # - which currently renders the main module of each dll not reloadable
  145. # - ability to check with the current callstack if a reload is "legal"
  146. # - if it is in any function which is in a module about to be reloaded ==> error
  147. # - pragma annotations for files - to be excluded from dll shenanigans
  148. # - for such file-global pragmas look at codeReordering or injectStmt
  149. # - how would the initialization order be kept? messy...
  150. # - C code calling stable exportc interface of nim code (for bindings)
  151. # - generate proxy functions with the stable names
  152. # - in a non-reloadable part (the main binary) that call the function pointers
  153. # - parameter passing/forwarding - how? use the same trampoline jumping?
  154. # - extracting the dependencies for these stubs/proxies will be hard...
  155. # - changing memory layout of types - detecting this..?
  156. # - implement with registerType() call to HCR runtime...?
  157. # - and checking if a previously registered type matches
  158. # - issue an error
  159. # - or let the user handle this by transferring the state properly
  160. # - perhaps in the before/afterCodeReload handlers
  161. # - implement executeOnReload for global vars too - not just statements (and document!)
  162. # - cleanup at shutdown - freeing all globals
  163. # - fallback mechanism if the program crashes (the program should detect crashes
  164. # by itself using SEH/signals on Windows/Unix) - should be able to revert to
  165. # previous versions of the .dlls by calling some function from HCR
  166. # - improve runtime performance - possibilities
  167. # - implement a way for multiple .nim files to be bundled into the same dll
  168. # and have all calls within that domain to use the "_actual" versions of
  169. # procs so there are no indirections (or the ability to just bundle everything
  170. # except for a few unreloadable modules into a single mega reloadable dll)
  171. # - try to load the .dlls at specific addresses of memory (close to each other)
  172. # allocated with execution flags - check this: https://github.com/fancycode/MemoryModule
  173. #
  174. # TODO - unimportant:
  175. #
  176. # - have a "bad call" trampoline that all no-longer-present functions are routed to call there
  177. # - so the user gets some error msg if he calls a dangling pointer instead of a crash
  178. # - before/afterCodeReload and hasModuleChanged should be accessible only where appropriate
  179. # - nim_program_result is inaccessible in HCR mode from external C code (see nimbase.h)
  180. # - proper .json build file - but the format is different... multiple link commands...
  181. # - avoid registering globals on each loop when using an iterator in global scope
  182. #
  183. # TODO - REPL:
  184. # - proper way (as proposed by Zahary):
  185. # - parse the input code and put everything in global scope except for
  186. # statements with side effects only - those go in afterCodeReload blocks
  187. # - my very hacky idea: just append to a closure iterator the new statements
  188. # followed by a yield statement. So far I can think of 2 problems:
  189. # - import and some other code cannot be written inside of a proc -
  190. # has to be parsed and extracted in the outer scope
  191. # - when new variables are created they are actually locals to the closure
  192. # so the struct for the closure state grows in memory, but it has already
  193. # been allocated when the closure was created with the previous smaller size.
  194. # That would lead to working with memory outside of the initially allocated
  195. # block. Perhaps something can be done about this - some way of re-allocating
  196. # the state and transferring the old...
  197. when not defined(JS) and (defined(hotcodereloading) or
  198. defined(createNimHcr) or
  199. defined(testNimHcr)):
  200. const
  201. dllExt = when defined(windows): "dll"
  202. elif defined(macosx): "dylib"
  203. else: "so"
  204. type
  205. HcrProcGetter* = proc (libHandle: pointer, procName: cstring): pointer {.nimcall.}
  206. HcrGcMarkerProc = proc () {.nimcall.}
  207. HcrModuleInitializer* = proc () {.nimcall.}
  208. when defined(createNimHcr):
  209. when system.appType != "lib":
  210. {.error: "This file has to be compiled as a library!".}
  211. import os, tables, sets, times, strutils, reservedmem, dynlib
  212. template trace(args: varargs[untyped]) =
  213. when defined(testNimHcr) or defined(traceHcr):
  214. echo args
  215. proc sanitize(arg: Time): string =
  216. when defined(testNimHcr): return "<time>"
  217. else: return $arg
  218. proc sanitize(arg: string|cstring): string =
  219. when defined(testNimHcr): return ($arg).splitFile.name.splitFile.name
  220. else: return $arg
  221. {.pragma: nimhcr, compilerProc, exportc, dynlib.}
  222. when hostCPU in ["i386", "amd64"]:
  223. type
  224. ShortJumpInstruction {.packed.} = object
  225. opcode: byte
  226. offset: int32
  227. LongJumpInstruction {.packed.} = object
  228. opcode1: byte
  229. opcode2: byte
  230. offset: int32
  231. absoluteAddr: pointer
  232. proc writeJump(jumpTableEntry: ptr LongJumpInstruction, targetFn: pointer) =
  233. let
  234. jumpFrom = jumpTableEntry.shift(sizeof(ShortJumpInstruction))
  235. jumpDistance = distance(jumpFrom, targetFn)
  236. if abs(jumpDistance) < 0x7fff0000:
  237. let shortJump = cast[ptr ShortJumpInstruction](jumpTableEntry)
  238. shortJump.opcode = 0xE9 # relative jump
  239. shortJump.offset = int32(jumpDistance)
  240. else:
  241. jumpTableEntry.opcode1 = 0xff # indirect absolute jump
  242. jumpTableEntry.opcode2 = 0x25
  243. when hostCPU == "i386":
  244. # on x86 we write the absolute address of the following pointer
  245. jumpTableEntry.offset = cast[int32](addr jumpTableEntry.absoluteAddr)
  246. else:
  247. # on x64, we use a relative address for the same location
  248. jumpTableEntry.offset = 0
  249. jumpTableEntry.absoluteAddr = targetFn
  250. elif hostCPU == "arm":
  251. const jumpSize = 8
  252. elif hostCPU == "arm64":
  253. const jumpSize = 16
  254. const defaultJumpTableSize = case hostCPU
  255. of "i386": 50
  256. of "amd64": 500
  257. else: 50
  258. let jumpTableSizeStr = getEnv("HOT_CODE_RELOADING_JUMP_TABLE_SIZE")
  259. let jumpTableSize = if jumpTableSizeStr.len > 0: parseInt(jumpTableSizeStr)
  260. else: defaultJumpTableSize
  261. # TODO: perhaps keep track of free slots due to removed procs using a free list
  262. var jumpTable = ReservedMemSeq[LongJumpInstruction].init(
  263. memStart = cast[pointer](0x10000000),
  264. maxLen = jumpTableSize * 1024 * 1024 div sizeof(LongJumpInstruction),
  265. accessFlags = memExecReadWrite)
  266. type
  267. ProcSym = object
  268. jump: ptr LongJumpInstruction
  269. gen: int
  270. GlobalVarSym = object
  271. p: pointer
  272. markerProc: HcrGcMarkerProc
  273. gen: int
  274. ModuleDesc = object
  275. procs: Table[string, ProcSym]
  276. globals: Table[string, GlobalVarSym]
  277. imports: seq[string]
  278. handle: LibHandle
  279. hash: string
  280. gen: int
  281. lastModification: Time
  282. handlers: seq[tuple[isBefore: bool, cb: proc ()]]
  283. proc newModuleDesc(): ModuleDesc =
  284. result.procs = initTable[string, ProcSym]()
  285. result.globals = initTable[string, GlobalVarSym]()
  286. result.handle = nil
  287. result.gen = -1
  288. result.lastModification = low(Time)
  289. # the global state necessary for traversing and reloading the module import tree
  290. var modules = initTable[string, ModuleDesc]()
  291. var root: string
  292. var system: string
  293. var mainDatInit: HcrModuleInitializer
  294. var generation = 0
  295. # necessary for queries such as "has module X changed" - contains all but the main module
  296. var hashToModuleMap = initTable[string, string]()
  297. # necessary for registering handlers and keeping them up-to-date
  298. var currentModule: string
  299. # supplied from the main module - used by others to initialize pointers to this runtime
  300. var hcrDynlibHandle: pointer
  301. var getProcAddr: HcrProcGetter
  302. proc hcrRegisterProc*(module: cstring, name: cstring, fn: pointer): pointer {.nimhcr.} =
  303. trace " register proc: ", module.sanitize, " ", name
  304. # Please note: We must allocate a local copy of the strings, because the supplied
  305. # `cstring` will reside in the data segment of a DLL that will be later unloaded.
  306. let name = $name
  307. let module = $module
  308. var jumpTableEntryAddr: ptr LongJumpInstruction
  309. modules[module].procs.withValue(name, p):
  310. trace " update proc: ", name
  311. jumpTableEntryAddr = p.jump
  312. p.gen = generation
  313. do:
  314. let len = jumpTable.len
  315. jumpTable.setLen(len + 1)
  316. jumpTableEntryAddr = addr jumpTable[len]
  317. modules[module].procs[name] = ProcSym(jump: jumpTableEntryAddr, gen: generation)
  318. writeJump jumpTableEntryAddr, fn
  319. return jumpTableEntryAddr
  320. proc hcrGetProc*(module: cstring, name: cstring): pointer {.nimhcr.} =
  321. trace " get proc: ", module.sanitize, " ", name
  322. return modules[$module].procs.getOrDefault($name, ProcSym()).jump
  323. proc hcrRegisterGlobal*(module: cstring,
  324. name: cstring,
  325. size: Natural,
  326. gcMarker: HcrGcMarkerProc,
  327. outPtr: ptr pointer): bool {.nimhcr.} =
  328. trace " register global: ", module.sanitize, " ", name
  329. # Please note: We must allocate local copies of the strings, because the supplied
  330. # `cstring` will reside in the data segment of a DLL that will be later unloaded.
  331. # Also using a ptr pointer instead of a var pointer (an output parameter)
  332. # because for the C++ backend var parameters use references and in this use case
  333. # it is not possible to cast an int* (for example) to a void* and then pass it
  334. # to void*& since the casting yields an rvalue and references bind only to lvalues.
  335. let name = $name
  336. let module = $module
  337. modules[module].globals.withValue(name, global):
  338. trace " update global: ", name
  339. outPtr[] = global.p
  340. global.gen = generation
  341. global.markerProc = gcMarker
  342. return false
  343. do:
  344. outPtr[] = alloc0(size)
  345. modules[module].globals[name] = GlobalVarSym(p: outPtr[],
  346. gen: generation,
  347. markerProc: gcMarker)
  348. return true
  349. proc hcrGetGlobal*(module: cstring, name: cstring): pointer {.nimhcr.} =
  350. trace " get global: ", module.sanitize, " ", name
  351. return modules[$module].globals[$name].p
  352. proc getListOfModules(cstringArray: ptr pointer): seq[string] =
  353. var curr = cast[ptr cstring](cstringArray)
  354. while len(curr[]) > 0:
  355. result.add($curr[])
  356. curr = cast[ptr cstring](cast[int64](curr) + sizeof(ptr cstring))
  357. template cleanup(collection, body) =
  358. var toDelete: seq[string]
  359. for name, data in collection.pairs:
  360. if data.gen < generation:
  361. toDelete.add(name)
  362. trace "HCR Cleaning ", astToStr(collection), " :: ", name, " ", data.gen
  363. for name {.inject.} in toDelete:
  364. body
  365. proc cleanupGlobal(module: string, name: string) =
  366. var g: GlobalVarSym
  367. if modules[module].globals.take(name, g):
  368. dealloc g.p
  369. proc cleanupSymbols(module: string) =
  370. cleanup modules[module].globals:
  371. cleanupGlobal(module, name)
  372. cleanup modules[module].procs:
  373. modules[module].procs.del(name)
  374. proc unloadDll(name: string) =
  375. if modules[name].handle != nil:
  376. unloadLib(modules[name].handle)
  377. proc loadDll(name: cstring) {.nimhcr.} =
  378. let name = $name
  379. trace "HCR LOADING: ", name.sanitize
  380. if modules.contains(name):
  381. unloadDll(name)
  382. else:
  383. modules.add(name, newModuleDesc())
  384. let copiedName = name & ".copy." & dllExt
  385. copyFileWithPermissions(name, copiedName)
  386. let lib = loadLib(copiedName)
  387. assert lib != nil
  388. modules[name].handle = lib
  389. modules[name].gen = generation
  390. modules[name].lastModification = getLastModificationTime(name)
  391. # update the list of imports by the module
  392. let getImportsProc = cast[proc (): ptr pointer {.nimcall.}](
  393. checkedSymAddr(lib, "HcrGetImportedModules"))
  394. modules[name].imports = getListOfModules(getImportsProc())
  395. # get the hash of the module
  396. let getHashProc = cast[proc (): cstring {.nimcall.}](
  397. checkedSymAddr(lib, "HcrGetSigHash"))
  398. modules[name].hash = $getHashProc()
  399. hashToModuleMap[modules[name].hash] = name
  400. # Remove handlers for this module if reloading - they will be re-registered.
  401. # In order for them to be re-registered we need to de-register all globals
  402. # that trigger the registering of handlers through calls to hcrAddEventHandler
  403. modules[name].handlers.setLen(0)
  404. proc initHcrData(name: cstring) {.nimhcr.} =
  405. trace "HCR Hcr init: ", name.sanitize
  406. cast[proc (h: pointer, gpa: HcrProcGetter) {.nimcall.}](
  407. checkedSymAddr(modules[$name].handle, "HcrInit000"))(hcrDynlibHandle, getProcAddr)
  408. proc initTypeInfoGlobals(name: cstring) {.nimhcr.} =
  409. trace "HCR TypeInfo globals init: ", name.sanitize
  410. cast[HcrModuleInitializer](checkedSymAddr(modules[$name].handle, "HcrCreateTypeInfos"))()
  411. proc initPointerData(name: cstring) {.nimhcr.} =
  412. trace "HCR Dat init: ", name.sanitize
  413. cast[HcrModuleInitializer](checkedSymAddr(modules[$name].handle, "DatInit000"))()
  414. proc initGlobalScope(name: cstring) {.nimhcr.} =
  415. trace "HCR Init000: ", name.sanitize
  416. # set the currently inited module - necessary for registering the before/after HCR handlers
  417. currentModule = $name
  418. cast[HcrModuleInitializer](checkedSymAddr(modules[$name].handle, "Init000"))()
  419. var modulesToInit: seq[string] = @[]
  420. var allModulesOrderedByDFS: seq[string] = @[]
  421. proc recursiveDiscovery(dlls: seq[string]) =
  422. for curr in dlls:
  423. if modules.contains(curr):
  424. # skip updating modules that have already been updated to the latest generation
  425. if modules[curr].gen >= generation:
  426. trace "HCR SKIP: ", curr.sanitize, " gen is already: ", modules[curr].gen
  427. continue
  428. # skip updating an unmodified module but continue traversing its dependencies
  429. if modules[curr].lastModification >= getLastModificationTime(curr):
  430. trace "HCR SKIP (not modified): ", curr.sanitize, " ", modules[curr].lastModification.sanitize
  431. # update generation so module doesn't get collected
  432. modules[curr].gen = generation
  433. # recurse to imported modules - they might be changed
  434. recursiveDiscovery(modules[curr].imports)
  435. allModulesOrderedByDFS.add(curr)
  436. continue
  437. loadDll(curr)
  438. # first load all dependencies of the current module and init it after that
  439. recursiveDiscovery(modules[curr].imports)
  440. allModulesOrderedByDFS.add(curr)
  441. modulesToInit.add(curr)
  442. proc initModules() =
  443. # first init the pointers to hcr functions and also do the registering of typeinfo globals
  444. for curr in modulesToInit:
  445. initHcrData(curr)
  446. initTypeInfoGlobals(curr)
  447. # for now system always gets fully inited before any other module (including when reloading)
  448. initPointerData(system)
  449. initGlobalScope(system)
  450. # proceed with the DatInit calls - for all modules - including the main one!
  451. for curr in allModulesOrderedByDFS:
  452. if curr != system:
  453. initPointerData(curr)
  454. mainDatInit()
  455. # execute top-level code (in global scope)
  456. for curr in modulesToInit:
  457. if curr != system:
  458. initGlobalScope(curr)
  459. # cleanup old symbols which are gone now
  460. for curr in modulesToInit:
  461. cleanupSymbols(curr)
  462. proc hcrInit*(moduleList: ptr pointer, main, sys: cstring,
  463. datInit: HcrModuleInitializer, handle: pointer, gpa: HcrProcGetter) {.nimhcr.} =
  464. trace "HCR INITING: ", main.sanitize, " gen: ", generation
  465. # initialize globals
  466. root = $main
  467. system = $sys
  468. mainDatInit = datInit
  469. hcrDynlibHandle = handle
  470. getProcAddr = gpa
  471. # the root is already added and we need it because symbols from it will also be registered in the HCR system
  472. modules[root].imports = getListOfModules(moduleList)
  473. modules[root].gen = high(int) # something huge so it doesn't get collected
  474. # recursively initialize all modules
  475. recursiveDiscovery(modules[root].imports)
  476. initModules()
  477. # the next module to be inited will be the root
  478. currentModule = root
  479. proc hcrHasModuleChanged*(moduleHash: string): bool {.nimhcr.} =
  480. let module = hashToModuleMap[moduleHash]
  481. return modules[module].lastModification < getLastModificationTime(module)
  482. proc hcrReloadNeeded*(): bool {.nimhcr.} =
  483. for hash, _ in hashToModuleMap:
  484. if hcrHasModuleChanged(hash):
  485. return true
  486. return false
  487. proc hcrPerformCodeReload*() {.nimhcr.} =
  488. if not hcrReloadNeeded():
  489. trace "HCR - no changes"
  490. return
  491. # We disable the GC during the reload, because the reloading procedures
  492. # will replace type info objects and GC marker procs. This seems to create
  493. # problems when the GC is executed while the reload is underway.
  494. # Future versions of NIMHCR won't use the GC, because all globals and the
  495. # metadata needed to access them will be placed in shared memory, so they
  496. # can be manipulated from external programs without reloading.
  497. GC_disable()
  498. defer: GC_enable()
  499. inc(generation)
  500. trace "HCR RELOADING: ", generation
  501. var traversedHandlerModules = initSet[string]()
  502. proc recursiveExecuteHandlers(isBefore: bool, module: string) =
  503. # do not process an already traversed module
  504. if traversedHandlerModules.containsOrIncl(module): return
  505. traversedHandlerModules.incl module
  506. # first recurse to do a DFS traversal
  507. for curr in modules[module].imports:
  508. recursiveExecuteHandlers(isBefore, curr)
  509. # and then execute the handlers - from leaf modules all the way up to the root module
  510. for curr in modules[module].handlers:
  511. if curr.isBefore == isBefore:
  512. curr.cb()
  513. # first execute the before reload handlers
  514. traversedHandlerModules.clear()
  515. recursiveExecuteHandlers(true, root)
  516. # do the reloading
  517. modulesToInit = @[]
  518. allModulesOrderedByDFS = @[]
  519. recursiveDiscovery(modules[root].imports)
  520. initModules()
  521. # execute the after reload handlers
  522. traversedHandlerModules.clear()
  523. recursiveExecuteHandlers(false, root)
  524. # collecting no longer referenced modules - based on their generation
  525. cleanup modules:
  526. cleanupSymbols(name)
  527. unloadDll(name)
  528. hashToModuleMap.del(modules[name].hash)
  529. modules.del(name)
  530. proc hcrAddEventHandler*(isBefore: bool, cb: proc ()) {.nimhcr.} =
  531. modules[currentModule].handlers.add(
  532. (isBefore: isBefore, cb: cb))
  533. proc hcrAddModule*(module: cstring) {.nimhcr.} =
  534. if not modules.contains($module):
  535. modules.add($module, newModuleDesc())
  536. proc hcrGeneration*(): int {.nimhcr.} =
  537. generation
  538. proc hcrMarkGlobals*() {.nimhcr, nimcall, gcsafe.} =
  539. # This is gcsafe, because it will be registered
  540. # only in the GC of the main thread.
  541. {.gcsafe.}:
  542. for _, module in modules:
  543. for _, global in module.globals:
  544. if global.markerProc != nil:
  545. global.markerProc()
  546. elif defined(hotcodereloading) or defined(testNimHcr):
  547. when not defined(JS):
  548. const
  549. nimhcrLibname = when defined(windows): "nimhcr." & dllExt
  550. elif defined(macosx): "libnimhcr." & dllExt
  551. else: "libnimhcr." & dllExt
  552. {.pragma: nimhcr, compilerProc, importc, dynlib: nimhcrLibname.}
  553. proc hcrRegisterProc*(module: cstring, name: cstring, fn: pointer): pointer {.nimhcr.}
  554. proc hcrGetProc*(module: cstring, name: cstring): pointer {.nimhcr.}
  555. proc hcrRegisterGlobal*(module: cstring, name: cstring, size: Natural,
  556. gcMarker: HcrGcMarkerProc, outPtr: ptr pointer): bool {.nimhcr.}
  557. proc hcrGetGlobal*(module: cstring, name: cstring): pointer {.nimhcr.}
  558. proc hcrInit*(moduleList: ptr pointer,
  559. main, sys: cstring,
  560. datInit: HcrModuleInitializer,
  561. handle: pointer,
  562. gpa: HcrProcGetter) {.nimhcr.}
  563. proc hcrAddModule*(module: cstring) {.nimhcr.}
  564. proc hcrHasModuleChanged*(moduleHash: string): bool {.nimhcr.}
  565. proc hcrReloadNeeded*(): bool {.nimhcr.}
  566. proc hcrPerformCodeReload*() {.nimhcr.}
  567. proc hcrAddEventHandler*(isBefore: bool, cb: proc ()) {.nimhcr.}
  568. proc hcrMarkGlobals*() {.nimhcr, nimcall, gcsafe.}
  569. when declared(nimRegisterGlobalMarker):
  570. nimRegisterGlobalMarker(hcrMarkGlobals)
  571. else:
  572. proc hcrHasModuleChanged*(moduleHash: string): bool =
  573. # TODO
  574. false
  575. proc hcrAddEventHandler*(isBefore: bool, cb: proc ()) =
  576. # TODO
  577. discard