rodfiles.nim 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2020 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Low level binary format used by the compiler to store and load various AST
  10. ## and related data.
  11. ##
  12. ## NB: this is incredibly low level and if you're interested in how the
  13. ## compiler works and less a storage format, you're probably looking for
  14. ## the `ic` or `packed_ast` modules to understand the logical format.
  15. from typetraits import supportsCopyMem
  16. ## Overview
  17. ## ========
  18. ## `RodFile` represents a Rod File (versioned binary format), and the
  19. ## associated data for common interactions such as IO and error tracking
  20. ## (`RodFileError`). The file format broken up into sections (`RodSection`)
  21. ## and preceeded by a header (see: `cookie`). The precise layout, section
  22. ## ordering and data following the section are determined by the user. See
  23. ## `ic.loadRodFile`.
  24. ##
  25. ## A basic but "wrong" example of the lifecycle:
  26. ## ---------------------------------------------
  27. ## 1. `create` or `open` - create a new one or open an existing
  28. ## 2. `storeHeader` - header info
  29. ## 3. `storePrim` or `storeSeq` - save your stuff
  30. ## 4. `close` - and we're done
  31. ##
  32. ## Now read the bits below to understand what's missing.
  33. ##
  34. ## ### Issues with the Example
  35. ## Missing Sections:
  36. ## This is a low level API, so headers and sections need to be stored and
  37. ## loaded by the user, see `storeHeader` & `loadHeader` and `storeSection` &
  38. ## `loadSection`, respectively.
  39. ##
  40. ## No Error Handling:
  41. ## The API is centered around IO and prone to error, each operation checks or
  42. ## sets the `RodFile.err` field. A user of this API needs to handle these
  43. ## appropriately.
  44. ##
  45. ## API Notes
  46. ## =========
  47. ##
  48. ## Valid inputs for Rod files
  49. ## --------------------------
  50. ## ASTs, hopes, dreams, and anything as long as it and any children it may have
  51. ## support `copyMem`. This means anything that is not a pointer and that does not contain a pointer. At a glance these are:
  52. ## * string
  53. ## * objects & tuples (fields are recursed)
  54. ## * sequences AKA `seq[T]`
  55. ##
  56. ## Note on error handling style
  57. ## ----------------------------
  58. ## A flag based approach is used where operations no-op in case of a
  59. ## preexisting error and set the flag if they encounter one.
  60. ##
  61. ## Misc
  62. ## ----
  63. ## * 'Prim' is short for 'primitive', as in a non-sequence type
  64. type
  65. RodSection* = enum
  66. versionSection
  67. configSection
  68. stringsSection
  69. checkSumsSection
  70. depsSection
  71. numbersSection
  72. exportsSection
  73. hiddenSection
  74. reexportsSection
  75. compilerProcsSection
  76. trmacrosSection
  77. convertersSection
  78. methodsSection
  79. pureEnumsSection
  80. macroUsagesSection
  81. toReplaySection
  82. topLevelSection
  83. bodiesSection
  84. symsSection
  85. typesSection
  86. typeInstCacheSection
  87. procInstCacheSection
  88. attachedOpsSection
  89. methodsPerTypeSection
  90. enumToStringProcsSection
  91. typeInfoSection # required by the backend
  92. backendFlagsSection
  93. aliveSymsSection # beware, this is stored in a `.alivesyms` file.
  94. RodFileError* = enum
  95. ok, tooBig, cannotOpen, ioFailure, wrongHeader, wrongSection, configMismatch,
  96. includeFileChanged
  97. RodFile* = object
  98. f*: File
  99. currentSection*: RodSection # for error checking
  100. err*: RodFileError # little experiment to see if this works
  101. # better than exceptions.
  102. const
  103. RodVersion = 1
  104. cookie = [byte(0), byte('R'), byte('O'), byte('D'),
  105. byte(sizeof(int)*8), byte(system.cpuEndian), byte(0), byte(RodVersion)]
  106. proc setError(f: var RodFile; err: RodFileError) {.inline.} =
  107. f.err = err
  108. #raise newException(IOError, "IO error")
  109. proc storePrim*(f: var RodFile; s: string) =
  110. ## Stores a string.
  111. ## The len is prefixed to allow for later retreival.
  112. if f.err != ok: return
  113. if s.len >= high(int32):
  114. setError f, tooBig
  115. return
  116. var lenPrefix = int32(s.len)
  117. if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
  118. setError f, ioFailure
  119. else:
  120. if s.len != 0:
  121. if writeBuffer(f.f, unsafeAddr(s[0]), s.len) != s.len:
  122. setError f, ioFailure
  123. proc storePrim*[T](f: var RodFile; x: T) =
  124. ## Stores a non-sequence/string `T`.
  125. ## If `T` doesn't support `copyMem` and is an object or tuple then the fields
  126. ## are written -- the user from context will need to know which `T` to load.
  127. if f.err != ok: return
  128. when supportsCopyMem(T):
  129. if writeBuffer(f.f, unsafeAddr(x), sizeof(x)) != sizeof(x):
  130. setError f, ioFailure
  131. elif T is tuple:
  132. for y in fields(x):
  133. storePrim(f, y)
  134. elif T is object:
  135. for y in fields(x):
  136. when y is seq:
  137. storeSeq(f, y)
  138. else:
  139. storePrim(f, y)
  140. else:
  141. {.error: "unsupported type for 'storePrim'".}
  142. proc storeSeq*[T](f: var RodFile; s: seq[T]) =
  143. ## Stores a sequence of `T`s, with the len as a prefix for later retrieval.
  144. if f.err != ok: return
  145. if s.len >= high(int32):
  146. setError f, tooBig
  147. return
  148. var lenPrefix = int32(s.len)
  149. if writeBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
  150. setError f, ioFailure
  151. else:
  152. for i in 0..<s.len:
  153. storePrim(f, s[i])
  154. proc loadPrim*(f: var RodFile; s: var string) =
  155. ## Read a string, the length was stored as a prefix
  156. if f.err != ok: return
  157. var lenPrefix = int32(0)
  158. if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
  159. setError f, ioFailure
  160. else:
  161. s = newString(lenPrefix)
  162. if lenPrefix > 0:
  163. if readBuffer(f.f, unsafeAddr(s[0]), s.len) != s.len:
  164. setError f, ioFailure
  165. proc loadPrim*[T](f: var RodFile; x: var T) =
  166. ## Load a non-sequence/string `T`.
  167. if f.err != ok: return
  168. when supportsCopyMem(T):
  169. if readBuffer(f.f, unsafeAddr(x), sizeof(x)) != sizeof(x):
  170. setError f, ioFailure
  171. elif T is tuple:
  172. for y in fields(x):
  173. loadPrim(f, y)
  174. elif T is object:
  175. for y in fields(x):
  176. when y is seq:
  177. loadSeq(f, y)
  178. else:
  179. loadPrim(f, y)
  180. else:
  181. {.error: "unsupported type for 'loadPrim'".}
  182. proc loadSeq*[T](f: var RodFile; s: var seq[T]) =
  183. ## `T` must be compatible with `copyMem`, see `loadPrim`
  184. if f.err != ok: return
  185. var lenPrefix = int32(0)
  186. if readBuffer(f.f, addr lenPrefix, sizeof(lenPrefix)) != sizeof(lenPrefix):
  187. setError f, ioFailure
  188. else:
  189. s = newSeq[T](lenPrefix)
  190. for i in 0..<lenPrefix:
  191. loadPrim(f, s[i])
  192. proc storeHeader*(f: var RodFile) =
  193. ## stores the header which is described by `cookie`.
  194. if f.err != ok: return
  195. if f.f.writeBytes(cookie, 0, cookie.len) != cookie.len:
  196. setError f, ioFailure
  197. proc loadHeader*(f: var RodFile) =
  198. ## Loads the header which is described by `cookie`.
  199. if f.err != ok: return
  200. var thisCookie: array[cookie.len, byte]
  201. if f.f.readBytes(thisCookie, 0, thisCookie.len) != thisCookie.len:
  202. setError f, ioFailure
  203. elif thisCookie != cookie:
  204. setError f, wrongHeader
  205. proc storeSection*(f: var RodFile; s: RodSection) =
  206. ## update `currentSection` and writes the bytes value of s.
  207. if f.err != ok: return
  208. assert f.currentSection < s
  209. f.currentSection = s
  210. storePrim(f, s)
  211. proc loadSection*(f: var RodFile; expected: RodSection) =
  212. ## read the bytes value of s, sets and error if the section is incorrect.
  213. if f.err != ok: return
  214. var s: RodSection
  215. loadPrim(f, s)
  216. if expected != s and f.err == ok:
  217. setError f, wrongSection
  218. proc create*(filename: string): RodFile =
  219. ## create the file and open it for writing
  220. if not open(result.f, filename, fmWrite):
  221. setError result, cannotOpen
  222. proc close*(f: var RodFile) = close(f.f)
  223. proc open*(filename: string): RodFile =
  224. ## open the file for reading
  225. if not open(result.f, filename, fmRead):
  226. setError result, cannotOpen