buffers.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. //! Flat buffer contract — the ONE boundary crossing per file.
  2. //!
  3. //! The kernel returns five Buffers: meta, nodes, edges, refs, arena. All rows
  4. //! are fixed-width little-endian; every string is an (offset, len) pair into
  5. //! the UTF-8 arena. `OFFSET == NONE (0xFFFF_FFFF)` means "field absent".
  6. //!
  7. //! THIS FILE AND `src/extraction/kernel/layout.ts` MUST MATCH BYTE FOR BYTE.
  8. //! Any layout change bumps `KERNEL_ABI_VERSION` — the TS loader refuses a
  9. //! version it doesn't know and falls back to the wasm path.
  10. //!
  11. //! Layout (v1):
  12. //!
  13. //! meta (36 bytes):
  14. //! 0 u8 KERNEL_ABI_VERSION
  15. //! 1 [3] pad
  16. //! 4 u32 node count
  17. //! 8 u32 edge count
  18. //! 12 u32 ref count
  19. //! 16 u32 arena byte length
  20. //! 20 u32 errors-JSON arena offset (NONE = no errors)
  21. //! 24 u32 errors-JSON byte length
  22. //! 28 f64 kernel-side wall duration (ms) — introspection only; the TS
  23. //! wrapper measures the ExtractionResult.durationMs it reports
  24. //!
  25. //! node row (96 bytes):
  26. //! 0 u8 NodeKind index (NODE_KINDS order)
  27. //! 1 u8 visibility (0 absent, 1 public, 2 private, 3 protected, 4 internal)
  28. //! 2 u16 bool flags — bit pairs (present, value):
  29. //! 0/1 isExported, 2/3 isAsync, 4/5 isStatic, 6/7 isAbstract
  30. //! 4 u32 startLine (1-based)
  31. //! 8 u32 endLine
  32. //! 12 u32 startColumn (0-based)
  33. //! 16 u32 endColumn
  34. //! 20 str name
  35. //! 28 str qualifiedName
  36. //! 36 str id (kernel-computed: "kind:hash32", or "file:<path>" for the file node)
  37. //! 44 str docstring
  38. //! 52 str signature
  39. //! 60 str decorators (NUL-joined list)
  40. //! 68 str typeParameters (NUL-joined list)
  41. //! 76 str returnType
  42. //! 84 str extraJson (escape hatch: JSON of any extra Node props)
  43. //! 92 u32 metrics slot (reserved for Arc 3.2 per-node code metrics; 0)
  44. //!
  45. //! edge row (44 bytes):
  46. //! 0 u32 source node row index (NONE → use sourceIdStr)
  47. //! 4 u32 target node row index (NONE → use targetIdStr)
  48. //! 8 u8 EdgeKind index (EDGE_KINDS order)
  49. //! 9 u8 provenance (0 absent, 1 tree-sitter, 2 scip, 3 heuristic)
  50. //! 10 u16 pad
  51. //! 12 u32 line (NONE absent)
  52. //! 16 u32 column (NONE absent)
  53. //! 20 str metadataJson
  54. //! 28 str sourceIdStr
  55. //! 36 str targetIdStr
  56. //!
  57. //! ref row (40 bytes):
  58. //! 0 u32 fromNode row index (NONE → use fromNodeIdStr)
  59. //! 4 u8 ReferenceKind (EDGE_KINDS index, or 200 = function_ref)
  60. //! 5 [3] pad
  61. //! 8 u32 line (1-based)
  62. //! 12 u32 column (0-based)
  63. //! 16 str referenceName
  64. //! 24 str candidates (NUL-joined list)
  65. //! 32 str fromNodeIdStr
  66. pub const KERNEL_ABI_VERSION: u8 = 1;
  67. pub const NONE: u32 = 0xFFFF_FFFF;
  68. pub const META_SIZE: usize = 36;
  69. pub const NODE_ROW_SIZE: usize = 96;
  70. pub const EDGE_ROW_SIZE: usize = 44;
  71. pub const REF_ROW_SIZE: usize = 40;
  72. /// Mirror of NODE_KINDS in src/types.ts — order is the wire contract.
  73. pub const NODE_KINDS: [&str; 22] = [
  74. "file",
  75. "module",
  76. "class",
  77. "struct",
  78. "interface",
  79. "trait",
  80. "protocol",
  81. "function",
  82. "method",
  83. "property",
  84. "field",
  85. "variable",
  86. "constant",
  87. "enum",
  88. "enum_member",
  89. "type_alias",
  90. "namespace",
  91. "parameter",
  92. "import",
  93. "export",
  94. "route",
  95. "component",
  96. ];
  97. /// Mirror of EDGE_KINDS in src/types.ts — order is the wire contract.
  98. pub const EDGE_KINDS: [&str; 12] = [
  99. "contains",
  100. "calls",
  101. "imports",
  102. "exports",
  103. "extends",
  104. "implements",
  105. "references",
  106. "type_of",
  107. "returns",
  108. "instantiates",
  109. "overrides",
  110. "decorates",
  111. ];
  112. /// ReferenceKind code for the internal-only `function_ref` (#756).
  113. pub const FUNCTION_REF_CODE: u8 = 200;
  114. pub fn node_kind_index(kind: &str) -> Option<u8> {
  115. NODE_KINDS.iter().position(|k| *k == kind).map(|i| i as u8)
  116. }
  117. pub fn edge_kind_index(kind: &str) -> Option<u8> {
  118. EDGE_KINDS.iter().position(|k| *k == kind).map(|i| i as u8)
  119. }
  120. /// (offset, len) arena reference. `NONE_STR` encodes an absent field.
  121. pub type StrRef = (u32, u32);
  122. pub const NONE_STR: StrRef = (NONE, 0);
  123. /// UTF-8 string arena. Strings are appended verbatim; no dedup (per-file
  124. /// buffers are transient and small — intern later if profiling says so).
  125. #[derive(Default)]
  126. pub struct Arena {
  127. buf: Vec<u8>,
  128. }
  129. impl Arena {
  130. pub fn put(&mut self, s: &str) -> StrRef {
  131. let off = self.buf.len() as u32;
  132. self.buf.extend_from_slice(s.as_bytes());
  133. (off, s.len() as u32)
  134. }
  135. /// Not used by the seed emitter yet — R2 (docstring/signature/etc.). Kept
  136. /// so the arena API is complete alongside the layout it feeds.
  137. #[allow(dead_code)]
  138. pub fn put_opt(&mut self, s: Option<&str>) -> StrRef {
  139. match s {
  140. Some(s) => self.put(s),
  141. None => NONE_STR,
  142. }
  143. }
  144. /// NUL-joined list; absent when the list is empty. (R2 surface: decorators,
  145. /// typeParameters, candidates.)
  146. #[allow(dead_code)]
  147. pub fn put_list(&mut self, items: &[String]) -> StrRef {
  148. if items.is_empty() {
  149. return NONE_STR;
  150. }
  151. let joined = items.join("\0");
  152. self.put(&joined)
  153. }
  154. pub fn len(&self) -> u32 {
  155. self.buf.len() as u32
  156. }
  157. pub fn into_vec(self) -> Vec<u8> {
  158. self.buf
  159. }
  160. }
  161. /// Tri-state booleans packed as (present, value) bit pairs.
  162. #[derive(Default, Clone, Copy)]
  163. pub struct BoolFlags(pub u16);
  164. impl BoolFlags {
  165. pub fn set(&mut self, pair: u16, value: bool) {
  166. self.0 |= 1 << (pair * 2);
  167. if value {
  168. self.0 |= 1 << (pair * 2 + 1);
  169. }
  170. }
  171. }
  172. pub const FLAG_IS_EXPORTED: u16 = 0;
  173. #[allow(dead_code)] // R2 surface — part of the v1 wire contract
  174. pub const FLAG_IS_ASYNC: u16 = 1;
  175. #[allow(dead_code)] // R2 surface — part of the v1 wire contract
  176. pub const FLAG_IS_STATIC: u16 = 2;
  177. #[allow(dead_code)] // R2 surface — part of the v1 wire contract
  178. pub const FLAG_IS_ABSTRACT: u16 = 3;
  179. pub struct NodeRow {
  180. pub kind: u8,
  181. pub visibility: u8,
  182. pub flags: BoolFlags,
  183. pub start_line: u32,
  184. pub end_line: u32,
  185. pub start_column: u32,
  186. pub end_column: u32,
  187. pub name: StrRef,
  188. pub qualified_name: StrRef,
  189. pub id: StrRef,
  190. pub docstring: StrRef,
  191. pub signature: StrRef,
  192. pub decorators: StrRef,
  193. pub type_parameters: StrRef,
  194. pub return_type: StrRef,
  195. pub extra_json: StrRef,
  196. }
  197. pub struct EdgeRow {
  198. pub source_idx: u32,
  199. pub target_idx: u32,
  200. pub kind: u8,
  201. pub provenance: u8,
  202. pub line: u32,
  203. pub column: u32,
  204. pub metadata_json: StrRef,
  205. pub source_id_str: StrRef,
  206. pub target_id_str: StrRef,
  207. }
  208. pub struct RefRow {
  209. pub from_idx: u32,
  210. pub kind: u8,
  211. pub line: u32,
  212. pub column: u32,
  213. pub reference_name: StrRef,
  214. pub candidates: StrRef,
  215. pub from_id_str: StrRef,
  216. }
  217. fn push_str_ref(buf: &mut Vec<u8>, r: StrRef) {
  218. buf.extend_from_slice(&r.0.to_le_bytes());
  219. buf.extend_from_slice(&r.1.to_le_bytes());
  220. }
  221. pub struct Tables {
  222. pub nodes: Vec<u8>,
  223. pub edges: Vec<u8>,
  224. pub refs: Vec<u8>,
  225. pub node_count: u32,
  226. pub edge_count: u32,
  227. pub ref_count: u32,
  228. }
  229. impl Default for Tables {
  230. fn default() -> Self {
  231. Tables {
  232. nodes: Vec::with_capacity(NODE_ROW_SIZE * 64),
  233. edges: Vec::with_capacity(EDGE_ROW_SIZE * 64),
  234. refs: Vec::with_capacity(REF_ROW_SIZE * 64),
  235. node_count: 0,
  236. edge_count: 0,
  237. ref_count: 0,
  238. }
  239. }
  240. }
  241. impl Tables {
  242. pub fn push_node(&mut self, r: &NodeRow) -> u32 {
  243. let buf = &mut self.nodes;
  244. buf.push(r.kind);
  245. buf.push(r.visibility);
  246. buf.extend_from_slice(&r.flags.0.to_le_bytes());
  247. buf.extend_from_slice(&r.start_line.to_le_bytes());
  248. buf.extend_from_slice(&r.end_line.to_le_bytes());
  249. buf.extend_from_slice(&r.start_column.to_le_bytes());
  250. buf.extend_from_slice(&r.end_column.to_le_bytes());
  251. push_str_ref(buf, r.name);
  252. push_str_ref(buf, r.qualified_name);
  253. push_str_ref(buf, r.id);
  254. push_str_ref(buf, r.docstring);
  255. push_str_ref(buf, r.signature);
  256. push_str_ref(buf, r.decorators);
  257. push_str_ref(buf, r.type_parameters);
  258. push_str_ref(buf, r.return_type);
  259. push_str_ref(buf, r.extra_json);
  260. buf.extend_from_slice(&0u32.to_le_bytes()); // metrics slot (Arc 3.2)
  261. let idx = self.node_count;
  262. self.node_count += 1;
  263. idx
  264. }
  265. pub fn push_edge(&mut self, r: &EdgeRow) {
  266. let buf = &mut self.edges;
  267. buf.extend_from_slice(&r.source_idx.to_le_bytes());
  268. buf.extend_from_slice(&r.target_idx.to_le_bytes());
  269. buf.push(r.kind);
  270. buf.push(r.provenance);
  271. buf.extend_from_slice(&0u16.to_le_bytes()); // pad
  272. buf.extend_from_slice(&r.line.to_le_bytes());
  273. buf.extend_from_slice(&r.column.to_le_bytes());
  274. push_str_ref(buf, r.metadata_json);
  275. push_str_ref(buf, r.source_id_str);
  276. push_str_ref(buf, r.target_id_str);
  277. self.edge_count += 1;
  278. }
  279. pub fn push_ref(&mut self, r: &RefRow) {
  280. let buf = &mut self.refs;
  281. buf.extend_from_slice(&r.from_idx.to_le_bytes());
  282. buf.push(r.kind);
  283. buf.extend_from_slice(&[0u8; 3]); // pad
  284. buf.extend_from_slice(&r.line.to_le_bytes());
  285. buf.extend_from_slice(&r.column.to_le_bytes());
  286. push_str_ref(buf, r.reference_name);
  287. push_str_ref(buf, r.candidates);
  288. push_str_ref(buf, r.from_id_str);
  289. self.ref_count += 1;
  290. }
  291. }
  292. /// One file's encoded tables, ready to hand across the JS boundary.
  293. pub struct EmitOut {
  294. pub meta: Vec<u8>,
  295. pub nodes: Vec<u8>,
  296. pub edges: Vec<u8>,
  297. pub refs: Vec<u8>,
  298. pub arena: Vec<u8>,
  299. }
  300. pub fn build_meta(t: &Tables, arena_len: u32, errors_json: StrRef, duration_ms: f64) -> Vec<u8> {
  301. let mut m = Vec::with_capacity(META_SIZE);
  302. m.push(KERNEL_ABI_VERSION);
  303. m.extend_from_slice(&[0u8; 3]);
  304. m.extend_from_slice(&t.node_count.to_le_bytes());
  305. m.extend_from_slice(&t.edge_count.to_le_bytes());
  306. m.extend_from_slice(&t.ref_count.to_le_bytes());
  307. m.extend_from_slice(&arena_len.to_le_bytes());
  308. m.extend_from_slice(&errors_json.0.to_le_bytes());
  309. m.extend_from_slice(&errors_json.1.to_le_bytes());
  310. m.extend_from_slice(&duration_ms.to_le_bytes());
  311. debug_assert_eq!(m.len(), META_SIZE);
  312. m
  313. }
  314. #[cfg(test)]
  315. mod tests {
  316. use super::*;
  317. #[test]
  318. fn row_sizes_match_constants() {
  319. let mut t = Tables::default();
  320. let mut a = Arena::default();
  321. let name = a.put("x");
  322. t.push_node(&NodeRow {
  323. kind: 0,
  324. visibility: 0,
  325. flags: BoolFlags::default(),
  326. start_line: 1,
  327. end_line: 1,
  328. start_column: 0,
  329. end_column: 0,
  330. name,
  331. qualified_name: name,
  332. id: name,
  333. docstring: NONE_STR,
  334. signature: NONE_STR,
  335. decorators: NONE_STR,
  336. type_parameters: NONE_STR,
  337. return_type: NONE_STR,
  338. extra_json: NONE_STR,
  339. });
  340. assert_eq!(t.nodes.len(), NODE_ROW_SIZE);
  341. t.push_edge(&EdgeRow {
  342. source_idx: 0,
  343. target_idx: 0,
  344. kind: 0,
  345. provenance: 0,
  346. line: NONE,
  347. column: NONE,
  348. metadata_json: NONE_STR,
  349. source_id_str: NONE_STR,
  350. target_id_str: NONE_STR,
  351. });
  352. assert_eq!(t.edges.len(), EDGE_ROW_SIZE);
  353. t.push_ref(&RefRow {
  354. from_idx: 0,
  355. kind: 1,
  356. line: 1,
  357. column: 0,
  358. reference_name: name,
  359. candidates: NONE_STR,
  360. from_id_str: NONE_STR,
  361. });
  362. assert_eq!(t.refs.len(), REF_ROW_SIZE);
  363. let meta = build_meta(&t, a.len(), NONE_STR, 0.0);
  364. assert_eq!(meta.len(), META_SIZE);
  365. }
  366. }