logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

Reflection.md (11560B)


  1. ---
  2. title: How reflection works in ****
  3. date: 2021-10-05
  4. ---
  5. *Note: this is a redacted copy of a blog post published on the internal
  6. development blog of a new systems programming language. The name of the project
  7. and further details are deliberately being kept in confidence until the initial
  8. release. You may be able to find it if you look hard enough — you have my
  9. thanks in advance for keeping it to yourself. For more information, see "[We are
  10. building a new systems programming language][post]".*
  11. <style>
  12. .redacted {
  13. background: black;
  14. foreground: black;
  15. }
  16. </style>
  17. [post]: https://drewdevault.com/2021/03/19/A-new-systems-language.html
  18. I've just merged support for reflection in <span class="redacted">xxxx</span>.
  19. Here's how it works!
  20. ## Background
  21. "Reflection" refers to the ability for a program to examine the type system of
  22. its programming language, and to dynamically manipulate types and their values
  23. at runtime. You can learn more at [Wikipedia][0].
  24. [0]: https://en.wikipedia.org/wiki/Reflective_programming
  25. ## Reflection from a user perspective
  26. Let's start with a small sample program:
  27. ```hare
  28. use fmt;
  29. use types;
  30. export fn main() void = {
  31. const my_type: type = type(int);
  32. const typeinfo: *types::typeinfo = types::reflect(my_type);
  33. fmt::printfln("int\nid: {}\nsize: {}\nalignment: {}",
  34. typeinfo.id, typeinfo.sz, typeinfo.al)!;
  35. };
  36. ```
  37. Running this program produces the following output:
  38. ```
  39. int
  40. id: 1099590421
  41. size: 4
  42. alignment: 4
  43. ```
  44. This gives us a simple starting point to look at. We can see that "type" is used
  45. as the type of the "my_type" variable, and initialized with a "type(int)"
  46. expression. This expression returns a type value for the type given in the
  47. parenthesis &mdash; in this case, for the "int" type.
  48. To learn anything useful, we have to convert this to a "types::typeinfo"
  49. pointer, which we do via `types::reflect`. The typeinfo structure looks like
  50. this:
  51. ```hare
  52. type typeinfo = struct {
  53. id: uint,
  54. sz: size,
  55. al: size,
  56. flags: flags,
  57. repr: repr,
  58. };
  59. ```
  60. The ID field is the type's unique identifier, which is universally unique and
  61. deterministic, and forms part of <span class="redacted">xxxx</span>'s ABI. This
  62. is derived from an FNV-32 hash of the type information. You can find the ID for
  63. any type by modifying our little example program, or you can use the helper
  64. program in the <code>cmd/<span class="redacted">xxxx</span>type</code> directory
  65. of the <span class="redacted">xxxx</span> source tree.
  66. Another important field is the "repr" field, which is short for
  67. "representation", and it gives details about the inner structure of the type.
  68. The repr type is defined as a tagged union of all possible type representations
  69. in the <span class="redacted">xxxx</span> type system:
  70. ```hare
  71. type repr = (alias | array | builtin | enumerated | func | pointer | slice | struct_union | tagged | tuple);
  72. ```
  73. In the case of the "int" type, the representation is "builtin":
  74. ```hare
  75. type builtin = enum uint {
  76. BOOL, CHAR, F32, F64, I16, I32, I64, I8, INT, NULL, RUNE, SIZE, STR, U16, U32,
  77. U64, U8, UINT, UINTPTR, VOID, TYPE,
  78. };
  79. ```
  80. `builtin::INT`, in this case. The structure and representation of the "int" type
  81. is defined by the <span class="redacted">xxxx</span> specification and cannot be
  82. overridden by the program, so no further information is necessary. The relevant
  83. part of the spec is:
  84. !["The precision of 'int' and 'uint' are implementation-defined. 'int' shall be signed, and 'uint' shall be unsigned. Both types shall be at least 32-bits in precision. The precision in bits shall be a power of two."](https://redacted.moe/f/9fb3b7e2.png)
  85. ![A table from the specification showing the precision ranges of each integer type](https://redacted.moe/f/f13236c9.png)
  86. More information is provided for more complex types, such as structs.
  87. ```hare
  88. use fmt;
  89. use types;
  90. export fn main() void = {
  91. const my_type: type = type(struct {
  92. x: int,
  93. y: int,
  94. });
  95. const typeinfo: *types::typeinfo = types::reflect(my_type);
  96. fmt::printfln("id: {}\nsize: {}\nalignment: {}",
  97. typeinfo.id, typeinfo.sz, typeinfo.al)!;
  98. const st = typeinfo.repr as types::struct_union;
  99. assert(st.kind == types::struct_kind::STRUCT);
  100. for (let i = 0z; i < len(st.fields); i += 1) {
  101. const field = st.fields[i];
  102. assert(field.type_ == type(int));
  103. fmt::printfln("\t{}: offset {}", field.name, field.offs)!;
  104. };
  105. };
  106. ```
  107. The output of this program is:
  108. ```
  109. id: 2617358403
  110. size: 8
  111. alignment: 4
  112. x: offset 0
  113. y: offset 4
  114. ```
  115. Here the "repr" field provides the "types::struct_union" structure:
  116. ```hare
  117. type struct_union = struct {
  118. kind: struct_kind,
  119. fields: []struct_field,
  120. };
  121. type struct_kind = enum {
  122. STRUCT,
  123. UNION,
  124. };
  125. type struct_field = struct {
  126. name: str,
  127. offs: size,
  128. type_: type,
  129. };
  130. ```
  131. Makes sense? Excellent. So how does it all work?
  132. ## Reflection internals
  133. Let me first draw the curtain back from the magic "types::reflect" function:
  134. ```hare
  135. // Returns [[typeinfo]] for the provided type.
  136. export fn reflect(in: type) const *typeinfo = in: *typeinfo;
  137. ```
  138. It simply casts the "type" value to a pointer, which is what it is. When the
  139. compiler sees an expression like `let x = type(int)`, it statically allocates
  140. the typeinfo data structure into the program and returns a pointer to it, which
  141. is then wrapped up in the opaque "type" meta-type. The "reflect" function simply
  142. converts it to a useful pointer. Here's the generated IR for this:
  143. ```hare
  144. %binding.4 =l alloc8 8
  145. storel $rt.builtin_int, %binding.4
  146. ```
  147. A clever eye will note that we initialize the value to a pointer to
  148. "rt.builtin_int", rather than allocating a typeinfo structure here and now. The
  149. runtime module provides static typeinfos for all built-in types, which look like
  150. this:
  151. ```hare
  152. export const @hidden builtin_int: types::typeinfo = types::typeinfo {
  153. id = 1099590421,
  154. sz = 4, al = 4, flags = 0,
  155. repr = types::builtin::INT,
  156. };
  157. ```
  158. These are an internal implementation detail, hence "@hidden". But many types are
  159. not built-in, so the compiler is required to statically allocate a typeinfo
  160. structure:
  161. ```hare
  162. export fn main() void = {
  163. let x = type(struct { x: int, y: int });
  164. };
  165. ```
  166. ```
  167. data $strdata.7 = section ".data.strdata.7" { b "x" }
  168. data $strdata.8 = section ".data.strdata.8" { b "y" }
  169. data $sldata.6 = section ".data.sldata.6" {
  170. l $strdata.7, l 1, l 1, l 0, l $rt.builtin_int,
  171. l $strdata.8, l 1, l 1, l 4, l $rt.builtin_int,
  172. }
  173. data $typeinfo.5 = section ".data.typeinfo.5" {
  174. w 2617358403, z 4,
  175. l 8,
  176. l 4,
  177. w 0, z 4,
  178. w 5555256, z 4,
  179. w 0, z 4,
  180. l $sldata.6, l 2, l 2,
  181. }
  182. export function section ".text.main" "ax" $main() {
  183. @start.0
  184. %binding.4 =l alloc8 8
  185. @body.1
  186. storel $typeinfo.5, %binding.4
  187. @.2
  188. ret
  189. }
  190. ```
  191. This has the unfortunate effect of re-generating all of these typeinfo
  192. structures every time someone uses `type(struct { x: int, y: int })`. We still
  193. have one trick up our sleeve, though: type aliases! Most people don't actually
  194. use anonymous structs like this often, preferring to use a type alias to give
  195. them a name like "coords". When they do this, the situation improves:
  196. ```hare
  197. type coords = struct { x: int, y: int };
  198. export fn main() void = {
  199. let x = type(coords);
  200. };
  201. ```
  202. ```
  203. data $strdata.1 = section ".data.strdata.1" { b "coords" }
  204. data $sldata.0 = section ".data.sldata.0" { l $strdata.1, l 6, l 6 }
  205. data $strdata.4 = section ".data.strdata.4" { b "x" }
  206. data $strdata.5 = section ".data.strdata.5" { b "y" }
  207. data $sldata.3 = section ".data.sldata.3" {
  208. l $strdata.4, l 1, l 1, l 0, l $rt.builtin_int,
  209. l $strdata.5, l 1, l 1, l 4, l $rt.builtin_int,
  210. }
  211. data $typeinfo.2 = section ".data.typeinfo.2" {
  212. w 2617358403, z 4,
  213. l 8,
  214. l 4,
  215. w 0, z 4,
  216. w 5555256, z 4,
  217. w 0, z 4,
  218. l $sldata.3, l 2, l 2,
  219. }
  220. data $type.1491593906 = section ".data.type.1491593906" {
  221. w 1491593906, z 4,
  222. l 8,
  223. l 4,
  224. w 0, z 4,
  225. w 3241765159, z 4,
  226. l $sldata.0, l 1, l 1,
  227. l $typeinfo.2
  228. }
  229. export function section ".text.main" "ax" $main() {
  230. @start.6
  231. %binding.10 =l alloc8 8
  232. @body.7
  233. storel $type.1491593906, %binding.10
  234. @.8
  235. ret
  236. }
  237. ```
  238. The declaration of a type alias provides us with the perfect opportunity to
  239. statically allocate a typeinfo singleton for it. Any of these which go unused by
  240. the program are automatically stripped out by the linker thanks to the
  241. `--gc-sections` flag. Also note that a type alias is considered a distinct
  242. representation from the underlying struct type:
  243. ```hare
  244. type alias = struct {
  245. ident: []str,
  246. secondary: type,
  247. };
  248. ```
  249. This explains the differences in the structure of the "type.1491593906" global.
  250. The <code>struct&nbsp;{&nbsp;x:&nbsp;int,&nbsp;y:&nbsp;int&nbsp;}</code> type is
  251. the "secondary" field of this type.
  252. ## Future improvements
  253. This is just the first half of the equation. The next half is to provide useful
  254. functions to work with this data. One such example is "types::strenum":
  255. ```hare
  256. // Returns the value of the enum at "val" as a string. Aborts if the value is
  257. // not present. Note that this does not work with enums being used as a flag
  258. // type, see [[strflag]] instead.
  259. export fn strenum(ty: type, val: *void) str = {
  260. const ty = unwrap(ty);
  261. const en = ty.repr as enumerated;
  262. const value: u64 = switch (en.storage) {
  263. case builtin::CHAR, builtin::I8, builtin::U8 =>
  264. yield *(val: *u8);
  265. case builtin::I16, builtin::U16 =>
  266. yield *(val: *u16);
  267. case builtin::I32, builtin::U32 =>
  268. yield *(val: *u32);
  269. case builtin::I64, builtin::U64 =>
  270. yield *(val: *u64);
  271. case builtin::INT, builtin::UINT =>
  272. yield switch (size(int)) {
  273. case 4 =>
  274. yield *(val: *u32);
  275. case 8 =>
  276. yield *(val: *u64);
  277. case => abort();
  278. };
  279. case builtin::SIZE =>
  280. yield switch (size(size)) {
  281. case 4 =>
  282. yield *(val: *u32);
  283. case 8 =>
  284. yield *(val: *u64);
  285. case => abort();
  286. };
  287. case => abort();
  288. };
  289. for (let i = 0z; i < len(en.values); i += 1) {
  290. if (en.values[i].1.u == value) {
  291. return en.values[i].0;
  292. };
  293. };
  294. abort("enum has invalid value");
  295. };
  296. ```
  297. This is used like so:
  298. ```hare
  299. use types;
  300. use fmt;
  301. type watchmen = enum {
  302. VIMES,
  303. CARROT,
  304. ANGUA,
  305. COLON,
  306. NOBBY = -1,
  307. };
  308. export fn main() void = {
  309. let officer = watchmen::ANGUA;
  310. fmt::println(types::strenum(type(watchmen), &officer))!; // Prints ANGUA
  311. };
  312. ```
  313. Additional work is required to make more useful tools like this. We will
  314. probably want to introduce a "value" abstraction which can store an arbitrary
  315. value for an arbitrary type, and helper functions to assign to or read from
  316. those values. A particularly complex case is likely to be some kind of helper
  317. for calling a function pointer via reflection, which we I may cover in a later
  318. article. There will also be some work to bring the "types" (reflection) module
  319. closer to the <span class="redacted">xxxx</span>::* namespace, which already
  320. features <span class="redacted">xxxx</span>::ast, <span
  321. class="redacted">xxxx</span>::parse, and <span
  322. class="redacted">xxxx</span>::types, so that the parser, type checker, and
  323. reflection systems are interopable and work together to implement the <span
  324. class="redacted">xxxx</span> type system.
  325. ---
  326. *Want to help us build this language? We are primarily looking for help in the
  327. following domains:*
  328. - *Architectures or operating systems, to help with ports*
  329. - *Compilers & language design*
  330. - *Cryptography implementations*
  331. - *Date & time implementations*
  332. - *Unix*
  333. *If you're an expert in a domain which is not listed, but that you think we
  334. should know about, then feel free to reach out. Experts are perferred, motivated
  335. enthusiasts are acceptable. [Send me an email][mail] if you want to help!*
  336. [mail]: mailto:sir@cmpwn.com