@fraczak/k 6.0.1 → 6.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/AGENTS.md +35 -0
  2. package/CHANGELOG.md +48 -0
  3. package/CLAUDE.md +64 -0
  4. package/DOCS/BIG_PICTURE.md +4 -8
  5. package/DOCS/BINARY_CODEC_STATUS_AND_TODO.md +74 -0
  6. package/DOCS/CONVERGENCE.md +92 -0
  7. package/DOCS/DIVERGENCE_DETECTION.md +24 -0
  8. package/DOCS/Makefile +99 -0
  9. package/DOCS/OBJECT_FILE_AND_PATTERN.md +221 -0
  10. package/DOCS/PATTERNS.md +114 -0
  11. package/DOCS/PATTERNS_AND_TYPED_VALUES.md +665 -0
  12. package/DOCS/REPL.md +127 -126
  13. package/DOCS/TEXTUAL_VALUES.md +108 -0
  14. package/DOCS/TYPE_DERIVATION.md +230 -0
  15. package/DOCS/book/06-values-in-memory.md +1085 -0
  16. package/DOCS/book/07-the-partial-function-abi.md +143 -0
  17. package/DOCS/book/08-operational-semantics-and-execution.md +201 -0
  18. package/DOCS/book/09-compiler-architecture.md +160 -0
  19. package/DOCS/book/10-from-ast-to-intermediate-representation.md +167 -0
  20. package/DOCS/book/14-canonical-serialization.md +176 -0
  21. package/DOCS/book/appendix-b-incorporating-external-predefined-types-and-functions.md +155 -0
  22. package/DOCS/book/overview.txt +68 -0
  23. package/README.md +78 -30
  24. package/UU/ta.k +1 -0
  25. package/UU/ta.k~ +0 -0
  26. package/UU/tb.k +0 -0
  27. package/UU/tc.k +0 -0
  28. package/codecs/README.md +12 -0
  29. package/codecs/pandoc.css +71 -0
  30. package/codecs/runtime/test-codec.mjs +89 -0
  31. package/core.klib +0 -0
  32. package/core.klib~ +0 -0
  33. package/ieee.klib +0 -0
  34. package/kkk.k +197 -0
  35. package/kkk.klib +0 -0
  36. package/kkk.klib~ +0 -0
  37. package/object.mjs +162 -8
  38. package/objects/README.md +10 -0
  39. package/objects/compile-lib.mjs +26 -2
  40. package/objects/compile.mjs +26 -2
  41. package/objects/decompile.mjs +25 -2
  42. package/objects/extract-aliases.mjs +59 -0
  43. package/package.json +3 -2
  44. package/{codecs/k-pattern.mjs → patterns/from-k.mjs} +3 -3
  45. package/repl2.mjs +345 -83
  46. package/scripts/generate-dependency-graph.mjs +121 -0
  47. package/test-k-object.mjs +77 -1
  48. package/test-repl2.mjs +65 -8
  49. package/DOCS/book-combined.html +0 -3798
  50. package/DOCS/book-combined.md +0 -3745
  51. package/DOCS/book-combined.pdf +0 -0
  52. package/DOCS/book-combined.tex +0 -5682
  53. package/DOCS/book.html +0 -65
  54. package/DOCS/book.pdf +0 -0
  55. package/DOCS/book.tex +0 -150
@@ -1,3745 +0,0 @@
1
- ---
2
- title: "k-language"
3
- author: "W. Fraczak"
4
- ---
5
-
6
- <div align="center">
7
-
8
- ```
9
- { } { }
10
- / \ {.lang name} |
11
- lang by --> name
12
- | | |
13
- 'k' 'wojtek' 'k'
14
- ```
15
-
16
- </div>
17
-
18
- # Table of Contents
19
-
20
- 1. [Introduction](#chapter-1-introduction)
21
- 2. [Syntax and Values](#chapter-2-syntax-and-values)
22
- 3. [Types as Finite Automata](#chapter-3-types-as-finite-automata)
23
- 4. [Partial Functions and Composition](#chapter-4-partial-functions-and-composition)
24
- 5. [Typing, Filters, and Normalization](#chapter-5-typing-filters-and-normalization)
25
- 6. [Values in Memory](#chapter-6-values-in-memory)
26
- 7. [The Partial Function ABI](#chapter-7-the-partial-function-abi)
27
- 8. [Operational Semantics and Execution](#chapter-8-operational-semantics-and-execution)
28
- 9. [Compiler Architecture](#chapter-9-compiler-architecture)
29
- 10. [From AST to Intermediate Representation](#chapter-10-from-ast-to-intermediate-representation)
30
- 11. [LLVM Basics](#chapter-11-llvm-basics)
31
- 12. [Code Generation](#chapter-12-code-generation)
32
- 13. [Linking and Execution](#chapter-13-linking-and-execution)
33
- 14. [Canonical Serialization](#chapter-14-canonical-serialization)
34
- 15. [Optimization and Folding](#chapter-15-optimization-and-folding)
35
- 16. [Toward a Universal Schema Registry](#chapter-16-toward-a-universal-schema-registry)
36
-
37
- ## Appendices
38
-
39
- - [Appendix A: Implementing a Prototype Compiler in Python](#appendix-a-implementing-a-prototype-compiler-in-python)
40
- - [Appendix B: Incorporating External Predefined Types and Functions](#appendix-b-incorporating-external-predefined-types-and-functions)
41
-
42
- ---
43
-
44
-
45
- # Chapter 1 — Introduction
46
-
47
- ## **1.1 The purpose of this book**
48
-
49
- This book describes a small programming language called **k**.
50
- It is meant to show how data can be represented and transformed in a uniform, precise way.
51
-
52
- ---
53
-
54
- ## **1.2 Data as trees**
55
-
56
- All information in `k` is treated as a **tree**.
57
- A tree has:
58
-
59
- * a single root,
60
- * labeled edges (field names or tags),
61
- * and possibly subtrees.
62
-
63
- Both JSON objects and XML documents are trees in this sense.
64
- Every `k` value is such a finite labeled tree.
65
-
66
- ---
67
-
68
- ## **1.3 Functions that may fail**
69
-
70
- In ordinary mathematics a function always returns a result.
71
- In `k`, a function may be **undefined** for some inputs.
72
- For example, asking for the field `age` in a record that has no `age` is undefined.
73
-
74
- We call such mappings **partial functions**.
75
-
76
- ---
77
-
78
- ## **1.4 Simplicity over features**
79
-
80
- `k` avoids most language constructs: no *variables* and no *control statements*.
81
- It has only:
82
-
83
- 1. **types** (which describe tree shapes), and
84
- 2. **partial functions** (which transform one tree into another).
85
-
86
- These two ingredients are sufficient and powerful enough to express structured
87
- computation in a concise and understandable way.
88
-
89
- There is another concept in the language, called **filters**.
90
- Filters play a role in the process of reasoning about types of partial functions.
91
- Filters have been added to the syntax of the language as they allow explicit annotations
92
- of "polymorphic" expressions, i.e., expressions which make sense in different contexts.
93
- Filters are similar to *type classes* in other languages.
94
-
95
- ---
96
-
97
- ---
98
-
99
-
100
- # Chapter 2 — Syntax and Values
101
-
102
- ## **2.1 Language fragments**
103
-
104
- The `k` language has only one kind of program element: a **definition**.
105
- A program is a sequence of definitions of two kinds:
106
-
107
- ```k-lang
108
- $ type_name = ...type_expression... ;
109
- function_name = ...expression... ;
110
- ```
111
-
112
- The definitions are followed in the file by the *main expression*; it represents the partial function defined by the program as a whole.
113
-
114
- There is no syntax for values.
115
- Every syntactic form in an expression describes a **function** -- possibly a constant function that always returns the same value.
116
-
117
- ---
118
-
119
- ## **2.2 Types**
120
-
121
- A type describes the possible **tree shapes** of values.
122
- Types are formed from two constructors:
123
-
124
- 1. **Product** – written `{ T₁ l₁, T₂ l₂, … }`
125
- represents records with fixed labeled fields.
126
-
127
- 2. **Union** – written `< T₁ l₁, T₂ l₂, … >`
128
- represents a choice between labeled alternatives.
129
-
130
- Both are finite and fully explicit.
131
- Every type denotes a finite tree automaton whose accepted trees are the possible values of that type.
132
-
133
- ---
134
-
135
- ## **2.3 Special and limiting cases**
136
-
137
- 1. **Empty product**
138
- `{}` has no fields.
139
- It represents the type that admits exactly one value, called *unit*.
140
- There is nothing exceptional about the type and its value; it is simply the degenerate case of a product with zero fields.
141
-
142
- 2. **Empty union**
143
- `<>` has no variants.
144
- It represents a type with no possible values.
145
-
146
- ---
147
-
148
- ## **2.4 Projections**
149
-
150
- **Dot** notation, e.g.,
151
-
152
- ```k
153
- . field_name
154
- ```
155
-
156
- is used for extracting the value of a field from a record (product type value).
157
- **Div** notation, e.g.,
158
-
159
- ```k
160
- / variant_name
161
- ```
162
-
163
- is used for asserting a particular variant of a union and extracting its value.
164
- Product type `{ T₁ l₁, T₂ l₂, … , Tₙ lₙ }` naturally defines `n` partial functions: `.l₁, .l₂, … , .lₙ`,
165
- and union type `< T₁ l₁, T₂ l₂, … , Tₙ lₙ >` naturally defines `n` partial functions `/l₁, /l₂, … , /lₙ`.
166
- Those partial functions are called **projections** and they constitute the basis for the definition
167
- of all other partial functions in the `k` language.
168
-
169
- ---
170
-
171
- ## **2.5 Functions**
172
-
173
- Expressions combine partial functions using the following operators:
174
-
175
- 1. **Composition** `(f₁ f₂ …)` — sequential application.
176
- 2. **Union** `< f₁, f₂, … >` — try each in order, first defined wins.
177
- 3. **Product (parallel)** `{ f₁ l₁, f₂ l₂, … }` — apply all to the same input, succeed only if all succeed.
178
- 4. **Variant construction** `| tag` — lift the input as a variant `tag` of a union type.
179
-
180
- Parentheses around a composition may be omitted except for the empty composition `()` — the identity function.
181
- Empty product, `{ }`, defines a constant function (ignoring its input) returning *unit*.
182
-
183
- It is important to notice that after the three markers: `dot (.)`, `div (/)`, or `pipe (|)`, there must be a constant label literal. For that reason, it is possible and advised not to leave any blanks after the marker, e.g.:
184
-
185
- ```k-lang
186
- .field /'a-tag' |"strange and long tag name ✅"
187
- ```
188
-
189
- ---
190
-
191
- ## **2.6 Example**
192
-
193
- ```k-lang
194
- $ bool = < {} true, {} false >;
195
- true = {} |true $ bool;
196
- false = {} |false $ bool;
197
- neg = $ bool < /true false, /false true >;
198
- ```
199
-
200
- This defines a "two-variant" union type and three functions: two constants and one transformation exchanging the variants.
201
-
202
- ---
203
-
204
- ## **2.7 Summary**
205
-
206
- * All types describe tree shapes.
207
- * As type expression, `{}` is the empty product type (with one value, called *unit*).
208
- * Functions, not values, are the only expressions in `k`.
209
-
210
- ---
211
-
212
- ---
213
-
214
-
215
- # Chapter 3 — Types as Finite Automata
216
-
217
- ## **3.1 Motivation**
218
-
219
- A type in `k` describes a set of finite labeled trees, which can be recognized by a finite tree automaton (FTA). Equivalently, a type definition can be seen as a Context-Free Grammar (CFG), where the values of the type are the derivation trees of the grammar.
220
-
221
- For example, the recursive type definition of a `list`:
222
-
223
- ```k-lang
224
- $ bool = < {} true, {} false >;
225
- $ list = < {} nil, { bool head, list tail } cons >;
226
- ```
227
-
228
- corresponds to the following 4-rule grammar:
229
-
230
- ```bnf
231
- 1: <bool> ::= {} |true
232
- 2: <bool> ::= {} |false
233
- 3: <list> ::= {} |nil
234
- 4: <list> ::= { <bool> head, <list> tail } |cons
235
- ```
236
-
237
- A value of type `list` is a derivation tree of this grammar. For instance, a list containing a single boolean value `true` can be represented by the following derivation:
238
-
239
- ```
240
- <list>
241
- -- (rule 4) --> { <bool> head, <list> tail } |cons
242
- | |
243
- | -- (rule 3) --> {} |nil
244
- |
245
- -- (rule 1) --> {} |true
246
- ```
247
-
248
- This derivation tree corresponds to the value generated by constant function: `{ {} |true head, {} |nil tail } |cons`.
249
-
250
- Representing types as automata (or CFGs) provides each type with a well-defined structure, independent of the names used in the source code. This allows for canonical normal forms and for comparing types for equality by structure alone.
251
-
252
- ---
253
-
254
- ## **3.2 States and Transitions**
255
-
256
- Each type definition introduces one or more *states* in the automaton. A union or a product definition corresponds to a state, and the variants or fields correspond to transitions to other states.
257
-
258
- For example, consider the `$bool` type:
259
-
260
- ```k-lang
261
- $ bool = < {} true, {} false >;
262
- ```
263
-
264
- This definition translates to an automaton with two states:
265
-
266
- - A root state, let's call it `C0`, representing the `$bool` type itself (a union).
267
- - A state `C1` representing the empty product `{}`.
268
-
269
- From the root state `C0`, there are two transitions:
270
-
271
- - A transition labeled `true` to state `C1`.
272
- - A transition labeled `false` to state `C1`.
273
-
274
- `C0` is the root state of the type, and `C1` is a leaf state, representing an empty value.
275
-
276
- ---
277
-
278
- ## **3.3 Canonical Form**
279
-
280
- Different type expressions can describe the same automaton. For example:
281
-
282
- ```k-lang
283
- $ bool = < {} false, {} true >;
284
- $ pair = { bool x, bool y };
285
- $ pair2 = { < {} true, {} false > x, bool y };
286
- ```
287
-
288
- The types `pair` and `pair2` are structurally equivalent because `$bool` is defined as `< {} true, {} false >`, making the definition of `x` in `pair2` identical to the standalone `$bool` type used in `pair`. Canonicalization is the process of removing these superficial differences by erasing local names and renumbering states to produce a single stable representation for each unique type structure.
289
-
290
- The canonical form is generated by traversing the type's graph structure starting from its root state. The process ensures a unique representation by following a consistent ordering:
291
-
292
- 1. **Start at the Root**: Assign the number `C0` to the root state of the type.
293
- 2. **Explore Neighbors**: Visit all states reachable from `C0`. Sort the transitions alphabetically by their labels (field or variant names) and assign consecutive numbers (`C1`, `C2`, ...) to the destination states in that order.
294
- 3. **Iterate**: Move to the next state in the numbered sequence (`C1`, then `C2`, etc.) and repeat the process for any of its neighbors that have not yet been numbered.
295
- 4. **Complete**: Continue until all reachable states have been assigned a unique number.
296
-
297
- This traversal produces a unique and deterministic numbering for any finite directed graph, ensuring that any two structurally identical types will have the exact same canonical representation.
298
-
299
- ---
300
-
301
- ## **3.4 Examples**
302
-
303
- For a type representing binary natural numbers:
304
-
305
- ```k-lang
306
- $ bnat = < bnat 0, bnat 1, {} _ >;
307
- ```
308
-
309
- the canonical form is:
310
-
311
- ```k-lang
312
- $C0 = < C0 "0", C0 "1", C1 "_" >;
313
- $C1 = {};
314
- ```
315
-
316
- The automaton has two states. `C0` is a recursive state that recognizes sequences of "0"s and "1"s, while `C1` is the terminal state, marked by the "_" transition.
317
-
318
- ---
319
-
320
- ## **3.5 Normalization Process**
321
-
322
- When compiling a program, the following steps are taken to normalize types:
323
-
324
- 1. **Collect**: Gather all type definitions from the program.
325
- 2. **Expand**: Recursively replace all named types with their underlying definitions until only products and unions remain.
326
- 3. **Deduplicate**: Eliminate duplicate type structures by comparing them structurally.
327
- 4. **Canonicalize**: Assign stable state numbers to the unique types to produce their canonical text representation.
328
-
329
- This process is deterministic, ensuring that the same type structure always produces the exact same canonical form.
330
-
331
- ---
332
-
333
- ## **3.6 Hash-Based Naming**
334
-
335
- To create a globally unique and stable identifier for each type, the compiler computes a hash of its canonical representation. This hash becomes the official, unambiguous name of the type.
336
-
337
- For example:
338
-
339
- ```text
340
- Canonical Form: $C0=<C0"0",C0"1",C1"_">;$C1={}
341
- ↓
342
- Hash
343
- ↓
344
- Identifier: @56b95dc611742a8e1cbb72d399660b5f18e4c3426dba0171f86dcdb2c41e9d91
345
- ```
346
-
347
- Program objects can then refer to types by their hash-based identifiers (prefixed with `@`) without ambiguity.
348
-
349
- ---
350
-
351
- ## **3.7 Interpretation**
352
-
353
- The canonical representation of a type can be interpreted as:
354
-
355
- - A finite set of states, `C0…Cn`.
356
- - Each state is either a product (a record with named fields) or a union (a set of variants).
357
- - Transitions between states are labeled with strings (field or variant names).
358
-
359
- Every value of a given type is a finite tree that is accepted by this automaton.
360
-
361
- ---
362
-
363
- ## **3.8 Summary**
364
-
365
- - Types in `k` denote sets of finite, labeled trees.
366
- - Every type can be expressed as a finite tree automaton or a context-free grammar.
367
- - The canonical form eliminates naming differences, providing a single representation for structurally identical types.
368
- - Hash-based naming gives each unique type a stable and globally unique identifier.
369
-
370
- ---
371
-
372
- ---
373
-
374
-
375
- # Chapter 4 — Partial Functions and Composition
376
-
377
- ## **4.1 Partial functions**
378
-
379
- A **partial function** is a mapping that may be undefined for some inputs.
380
- In `k`, every expression denotes such a function.
381
- If a function is undefined for a given input, the evaluation process halts at that point, producing no result.
382
-
383
- There is no notion of error or exception.
384
- Undefined means simply “no output”.
385
-
386
- ---
387
-
388
- ## **4.2 Composition**
389
-
390
- Composition combines two or more partial functions sequentially.
391
- If `f` and `g` are functions, `(f g)` means “apply `f`, then apply `g` to the result”.
392
-
393
- A composition `(f g)` is defined on a value `x` if and only if:
394
-
395
- * `f` is defined on `x`, and
396
- * `g` is defined on the result of `f(x)`.
397
-
398
- Otherwise, the composition is undefined.
399
-
400
- Composition is **associative**:
401
-
402
- ```math
403
- (f (g h)) ≡ ((f g) h)
404
- ```
405
-
406
- Therefore, parentheses are unnecessary, except for the special case of the **empty composition**, written `()`, which is the identity function:
407
-
408
- ```math
409
- () x = x () = x
410
- ```
411
-
412
- ---
413
-
414
- ## **4.3 Product composition**
415
-
416
- Product composition creates a function that applies several subfunctions in parallel to the same input and gathers their results into a product value.
417
-
418
- Syntax:
419
-
420
- ```math
421
- { f₁ l₁, f₂ l₂, …, fₙ lₙ }
422
- ```
423
-
424
- This expression is defined on an input value if all component functions are defined on that input.
425
- Its result is a product with fields labeled `l₁ … lₙ`, each containing the result of the corresponding function.
426
-
427
- ---
428
-
429
- ## **4.4 Union composition**
430
-
431
- Union composition represents concurrent evaluation with fallback:
432
-
433
- ```k-lang
434
- < f₁, f₂, …, fₙ >
435
- ```
436
-
437
- The result is defined for an input `x` if at least one subfunction is defined.
438
- Evaluation proceeds left to right; the first defined result is used.
439
- If none are defined, the result is undefined.
440
-
441
- Example:
442
-
443
- ```k-lang
444
- < /x, /y >
445
- ```
446
-
447
- extracts variant `x` if present; otherwise `y` if present.
448
-
449
- ---
450
-
451
- ## **4.5 Constants**
452
-
453
- A constant function is one that always returns the same value, regardless of its input.
454
- Since `k` lacks a direct syntax for literal values, constants are created using functions that produce them.
455
-
456
- For example, we can define functions that return `true` and `false`:
457
-
458
- ```k-lang
459
- $ bool = < {} true, {} false >;
460
- true_bool = {} |true $bool ;
461
- false_bool = {} |false $bool ;
462
- ```
463
-
464
- Here, `true_bool` and `false_bool` are constant functions. They are also **total functions**, meaning they are defined for all inputs. Each function ignores its input and produces a fixed boolean value.
465
-
466
- ---
467
-
468
- ## **4.6 Projection and Variant Value constructor**
469
-
470
- Projection selects a field or a variant from a product or union.
471
- It is written with a leading dot `.` or a leading slash `/`, respectively:
472
-
473
- ```k-lang
474
- .x // Selects field 'x' from a product: { T x, ... }
475
- /x // Extracts the value from a union variant 'x': < T x, ... >
476
- ```
477
-
478
- If the input is a product, `.x` returns the value of field `x`. If the input is a union, `/x` returns the value of variant `x`. In all other cases, the projection is undefined.
479
-
480
- Variant value constructors for a given union type `T = <T₁ tag₁, T₂ tag₂, … >` are written as `|tag₁`, `|tag₂`, ….
481
-
482
- ```k-lang
483
- |tag₁ // lift a value of type T₁ into a value of type T
484
- ```
485
-
486
- Projections and variant constructors are the most basic partial functions.
487
-
488
- ---
489
-
490
- ## **4.7 Derived combinations**
491
-
492
- Complex functions are built by nesting compositions.
493
- For example:
494
-
495
- ```k-lang
496
- { .x field1, .y |tag field2 }
497
- ```
498
-
499
- ---
500
-
501
- ## **4.8 Identity and emptiness**
502
-
503
- The empty composition `()` is the identity function.
504
- The empty union `<>` is the always-undefined function.
505
- The empty product `{}` is the constant function returning the unit value.
506
-
507
- ---
508
-
509
- ## **4.9 Summary**
510
-
511
- * In `k`, all expressions denote **partial functions**.
512
- * An undefined result simply means “no output,” not an error.
513
- * **Composition** `(f g)` applies functions sequentially and is associative, so parentheses are often optional.
514
- * The empty composition `()` is the **identity function**.
515
- * **Product composition** `{...}` runs functions in parallel and gathers their results.
516
- * **Union composition** `<...>` tries functions in order, returning the first successful result.
517
- * **Projections** (`.x`, `/x`) are fundamental building blocks for accessing data structures.
518
- * **Variant constructor** (`|tag`) allows lifting a value into a variant type value.
519
-
520
- ---
521
-
522
- ---
523
-
524
-
525
- # Chapter 5 — Typing, Filters, and Normalization
526
-
527
- ## **5.1 Types as functions**
528
-
529
- A type expression in `k` can appear wherever a function is expected.
530
- When used this way, it is prefixed by `$`.
531
- It then behaves as a partial **identity** function that is defined only for values of that type.
532
- For example, `$ bool` is a partial identity: it returns its argument unchanged when the argument is of type `bool`, and it is undefined otherwise.
533
-
534
- This convention eliminates any special syntax for annotating sub-expressions with types.
535
- An expression may be *restricted* to a type simply by composing it with the corresponding type expression.
536
-
537
- ---
538
-
539
- ## **5.2 Filters**
540
-
541
- A **filter** is a syntactic form that denotes a *class of types* — a set of types that share a common structure.
542
-
543
- Filter expressions are introduced by `?` and can be:
544
-
545
- - **Type expressions** — `? $bool`, `? $< {} x, bool y >`
546
- - **Product filters** — `? { Filter1 field1, Filter2 field2 }`
547
- - **Union filters** — `? < Filter1 tag1, Filter2 tag2 >`
548
- - **Any-type filter** — `? ( ... )`
549
- - **Filter variables** — `? X` (metavariables representing unknown types)
550
- - **Filter bindings** — `? < X f, (...) = Y g, ... > = X`
551
-
552
- Filters may contain `...` to indicate that additional fields or tags are allowed:
553
- `? { Filter1 field1, ... }` matches any product with at least `field1`.
554
-
555
- The filter `? (...)` matches any type.
556
- The filter `? {...}` matches any product type.
557
- The filter `? <...>` matches any union type.
558
- There is no closed unknown filter: use `?{}` for the empty product or `?<>` for the empty union.
559
-
560
- ---
561
-
562
- ## **5.3 Examples of filters**
563
-
564
- - `?(...)` — represents any type.
565
- - `?< (...) f, (...) g >` — represents all union types having exactly two variants `f` and `g`.
566
- - `?{ X f, X g }` — represents all product types with two fields `f` and `g`, both of the same type.
567
-
568
- A filter constrains where a partial function is defined; it does not affect the operational behavior of the function once defined.
569
-
570
- ---
571
-
572
- ## **5.4 Recursive filters**
573
-
574
- Filters may be recursive.
575
- They can describe families of recursive types by defining a metavariable in terms of a filter that references it.
576
-
577
- Example (list definition):
578
-
579
- ```k-lang
580
- ?< {} nil, {X car, Y cdr} cons > = Y
581
- ```
582
-
583
- This filter states that `Y` is a union type with two variants: `nil` and `cons`.
584
-
585
- - The `nil` variant holds an empty product `{}`.
586
- - The `cons` variant is a product with two fields: `car` of some type `X`, and `cdr` of type `Y` itself.
587
-
588
- This recursive structure defines a linked list where each element has a `car` (the value) and a `cdr` (the rest of the list).
589
- It thus denotes lists of elements of type `X`.
590
-
591
- ---
592
-
593
- ## **5.5 Type variables and scope**
594
-
595
- A type variable (an identifier, typically starting with an uppercase letter) introduced in a filter is visible within the enclosing function definition.
596
- For example:
597
-
598
- ```k-lang
599
- car = ?< {} nil, {X car, Y cdr} cons > = Y /cons .car ?X;
600
- ```
601
-
602
- Here:
603
-
604
- - `X` and `Y` are type variables.
605
- - The filter `?<{}nil,{X car,Y cdr}cons>=Y` constrains the function `car` to be defined only on types that match the recursive list structure.
606
- - The expression `/cons` selects the `cons` variant of the union.
607
- - The expression `.car` accesses the `car` field of a `cons` variant.
608
- - The final filter `?X` asserts that the result of the field access is of type `X`, the type of the list's elements.
609
-
610
- ---
611
-
612
- ## **5.6 Type inference and normalization**
613
-
614
- Every `k` program can be analyzed to assign an input and an output filter to every sub-expression. This process is called type inference abd normalization.
615
-
616
- Type inference and normalization proceeds as follows:
617
-
618
- 1. Build a graph of all type references appearing explicitly and implicitly in the program.
619
- 2. Annotate each expression node with a pair of (input filter, output filter).
620
- 3. Replace filters that match a single, concrete type with that type expression, adding any newly-discovered types to the graph.
621
- 4. Repeat until a fixed point is reached and no more changes occur.
622
- 5. Compute canonical forms for all newly introduced types.
623
-
624
- ---
625
-
626
- ## **5.7 Summary**
627
-
628
- - Type expressions act as identity functions defined on values of that type.
629
- - Filters describe sets of types and can constrain where a function is defined.
630
- - Filters may be products, unions, or the unconstrained any-type form, and can be recursive.
631
- - Variables in filters have definition-level scope.
632
- - Normalization computes explicit input/output filters for every expression.
633
- - Filters are used in the normalization and type checking process. They are ignored at runtime.
634
-
635
- ---
636
-
637
- ---
638
-
639
-
640
- # Chapter 6 — Values in Memory and Canonical Serialization
641
-
642
- ## **6.1 Unified value representation**
643
-
644
- Every `k` value exists in one of three forms during execution:
645
-
646
- 1. **Serialized form** — a canonical bit sequence that can be stored, transmitted, or passed between processes.
647
- 2. **Lazy (or hybrid) form** — a partially deserialized structure that keeps most data in its serialized form until access is needed.
648
- 3. **Materialized form** — a fully deserialized tree of nodes in memory.
649
-
650
- This unified approach enables programs to operate on serialized inputs without unnecessary deserialization, optimizing performance for identity operations and partial data access patterns.
651
-
652
- ---
653
-
654
- ## **6.2 The KValue abstraction**
655
-
656
- All values are represented by a single abstraction:
657
-
658
- ```c
659
- struct KValue {
660
- enum ValueForm {
661
- SERIALIZED, // pure bitstream + type info
662
- LAZY, // hybrid: some nodes materialized, others serialized
663
- MATERIALIZED, // traditional tree structure
664
- EXTERNAL // built-in/foreign type with custom handlers
665
- } form;
666
-
667
- union {
668
- struct {
669
- uint8_t* bits; // canonical bitstream
670
- int type_id; // identifies canonical type
671
- } serialized;
672
-
673
- struct {
674
- struct KNode* root; // root of materialized or partially materialized tree
675
- } materialized;
676
-
677
- struct {
678
- void* data; // opaque external data
679
- int type_id; // external type identifier
680
- ExternalOps* ops; // function pointers for operations
681
- } external;
682
- };
683
- };
684
- ```
685
-
686
- ### **6.2.1 Self-delimiting streams**
687
-
688
- The canonical encoding is **self-delimiting**, meaning each value can be parsed without knowing its length in advance:
689
-
690
- - **Union nodes**: Fixed-length discriminators define clear boundaries
691
- - **Product nodes**: Type structure determines field count and termination
692
- - **External data**: Length-prefixed encoding (LEB128 + payload)
693
- - **Unit values**: Zero bytes consumed
694
-
695
- This enables **streaming evaluation** where programs can process data incrementally without buffering entire inputs.
696
-
697
- ---
698
-
699
- ## **6.3 Lazy node structure**
700
-
701
- Nodes in lazy values can be in different states of materialization:
702
-
703
- ```c
704
- struct KNode {
705
- int state; // canonical type state (C0, C1,…)
706
- int tag; // variant index, −1 for product
707
- int arity; // number of children
708
-
709
- enum NodeState {
710
- SERIALIZED_CHILD, // child exists as bitstream only
711
- MATERIALIZED_CHILD, // child is fully materialized
712
- EXTERNAL_CHILD // child is external/built-in type
713
- };
714
-
715
- union KChild {
716
- struct {
717
- enum NodeState state; // which kind of child this is
718
- union {
719
- struct KNode* node; // materialized child
720
- struct {
721
- uint8_t* bits; // serialized child data
722
- int bit_offset; // position within parent's bitstream
723
- } serialized;
724
- struct KValue* external; // external child
725
- };
726
- };
727
- } child[];
728
- };
729
- ```
730
-
731
- ---
732
-
733
- ## **6.4 Canonical serialization format**
734
-
735
- The serialized form uses a deterministic canonical encoding that produces identical bit sequences for structurally identical values, extended to support lazy evaluation:
736
-
737
- ### **6.4.0 Canonical encoding principles**
738
-
739
- The encoding is based on the canonical finite automaton representation of types (Chapter 3):
740
-
741
- #### **Fixed-length union codes**
742
-
743
- For each union state with *m* variants, we use `k = ceil(log2(m))` bits to encode the variant selection:
744
-
745
- | Variants | Bits needed | Example codes |
746
- |----------|-------------|---------------|
747
- | 2 | 1 | `0`, `1` |
748
- | 3 | 2 | `00`, `01`, `10` |
749
- | 4 | 2 | `00`, `01`, `10`, `11` |
750
- | 5-8 | 3 | `000` through `111` |
751
-
752
- #### **Encoding by node type**
753
-
754
- Each node in the value tree is encoded based on its automaton state:
755
-
756
- - **Product nodes** — emit no bits, encode children in canonical field order (alphabetical)
757
- - **Union nodes** — emit fixed-length variant code, then encode the selected child
758
- - **Unit nodes** — emit nothing (arity = 0, no children)
759
- - **External nodes** — emit type marker + delegate to external serializer
760
-
761
- #### **Example: Natural numbers**
762
-
763
- For type `bnat`, intuitively binary natural numbers defined by `$bnat = < bnat 0, bnat 1, {} _ >`, we get
764
-
765
- ```k-repl
766
- --C bnat
767
- $ @56b95dc611742a8e1cbb72d399660b5f18e4c3426dba0171f86dcdb2c41e9d91 = < @56b95dc611742a8e1cbb72d399660b5f18e4c3426dba0171f86dcdb2c41e9d91 0, @56b95dc611742a8e1cbb72d399660b5f18e4c3426dba0171f86dcdb2c41e9d91 1, @368d370568fa01bc70e837de57958e968bee510ba5e512e6a6b7b8c1c184a20e _ >; -- $C0=<C0"0",C0"1",C1"_"> $C1={};
768
- ```
769
-
770
- The corresponding Context-Free grammar is:
771
-
772
- ```text
773
- // 3 rules for C0, so we need 2 bits to encode the variant choice:
774
- C0 -> C0 "|0" // encoded by '00'
775
- C0 -> C0 "|1" // encoded by '01'
776
- C0 -> C1 "|_" // encoded by '10'
777
- // 1 rule for C1 so we need zero bits:
778
- C1 -> '{}'; // encoded by ''
779
- ```
780
-
781
- - 3 variants require `ceil(log2(3)) = 2` bits
782
- - Codes: `00` (0-bit), `01` (1-bit), `10` (end marker)
783
-
784
- The value `{}|_|0|1` or in JSON-like notation `{1:{0:{_:{}}}}` (binary 10₂ = decimal 2) encodes as:
785
-
786
- ```text
787
- 01 00 10
788
- ```
789
-
790
- This represents the left-most derivation of `{}|_|0|1` from `C0`.
791
-
792
- #### **Prefix-free property**
793
-
794
- The encoding is **prefix-free**: no valid encoding is a prefix of another. This enables:
795
-
796
- - Linear-time parsing without backtracking
797
- - Efficient `skip_field()` operations for lazy evaluation
798
- - Deterministic decoding from any valid position in the stream
799
-
800
- ### **6.4.1 DAG compression for repeated subtrees**
801
-
802
- The canonical serialization can be compressed using **DAG (Directed Acyclic Graph) representation** to eliminate duplicate subtrees:
803
-
804
- #### **Subtree identification**
805
-
806
- Each unique subtree is assigned a **node ID** based on its canonical hash:
807
-
808
- ```text
809
- hash(node) = hash(state, tag, arity, hash(child_0), hash(child_1), ...)
810
- ```
811
-
812
- Identical subtrees (same structure and content) get the same hash and share the same node ID.
813
-
814
- #### **Two-pass encoding**
815
-
816
- ##### **Pass 1: Collection**
817
-
818
- - Traverse the value tree and compute hashes for all subtrees
819
- - Build a **node table** mapping `node_id → (first_occurrence_offset, reference_count)`
820
- - Subtrees with `reference_count > 1` are candidates for sharing
821
-
822
- ##### **Pass 2: Emission**
823
-
824
- - **First occurrence**: emit the full subtree encoding
825
- - **Subsequent occurrences**: emit a **back-reference** to the node ID
826
-
827
- #### **Back-reference encoding**
828
-
829
- ```text
830
- | Reference Bit (1) | Node ID (variable length) |
831
- ```
832
-
833
- - `0` bit indicates normal encoding follows
834
- - `1` bit indicates back-reference follows
835
- - Node ID uses variable-length encoding (e.g., LEB128)
836
-
837
- #### **Example: Shared subtrees**
838
-
839
- Consider a binary tree with repeated leaf patterns:
840
-
841
- ```k-lang
842
- $tree = < { tree left, tree right } branch, int leaf >;
843
- ```
844
-
845
- Value: A tree corresponding to the structure:
846
- `branch(
847
- leaf(42),
848
- branch(
849
- leaf(42),
850
- leaf(17)
851
- )
852
- )`:
853
-
854
- **Without DAG**: Each leaf `(42)` is encoded separately
855
- **With DAG**: The `(42)` subtree is encoded once, then referenced
856
-
857
- **Encoding sequence**:
858
-
859
- ```text
860
- Node Table:
861
- N0: 42 (appears 2 times)
862
- N1: 17 (appears 1 time)
863
-
864
- Bitstream:
865
- 00 // branch variant
866
- 0 <N0> // first occurrence of value: 42
867
- 00 // branch variant
868
- 1 N0 // back-reference to value: 42
869
- 0 <N1> // {value: 17}
870
- ```
871
-
872
- ### **6.4.2 Stream structure**
873
-
874
- ```text
875
- | Header (8 bytes) | Node Count (4 bytes) | Node Table | Bitstream |
876
- ```
877
-
878
- - **Header** — type hash identifying the canonical automaton
879
- - **Node Count** — number of shared nodes (0 = no DAG compression)
880
- - **Node Table** — `[node_id, offset_in_bitstream, size_in_bits]` for each shared node
881
- - **Bitstream** — concatenated prefix-free codes with back-references
882
-
883
- #### **Node table format**
884
-
885
- When DAG compression is used (`Node Count > 0`):
886
-
887
- ```text
888
- For each shared node:
889
- | Node ID (4 bytes) | Offset (4 bytes) | Size in bits (4 bytes) |
890
- ```
891
-
892
- This allows the decoder to:
893
-
894
- 1. **Random access**: Jump directly to any shared subtree definition
895
- 2. **Lazy loading**: Resolve back-references only when needed
896
- 3. **Validation**: Verify subtree boundaries during parsing
897
-
898
- #### **Compression algorithms**
899
-
900
- **Greedy sharing**: Share any subtree that appears more than once
901
-
902
- - Simple to implement
903
- - Good compression for naturally occurring patterns
904
-
905
- **Size-based sharing**: Only share subtrees above a minimum size threshold
906
-
907
- - Avoids overhead of referencing tiny subtrees
908
- - Better compression ratio in practice
909
-
910
- **Frequency-weighted sharing**: Prioritize subtrees by `size × frequency`
911
-
912
- - Optimal compression for space-constrained scenarios
913
- - Requires more sophisticated analysis
914
-
915
- #### **Lazy back-reference resolution**
916
-
917
- Back-references integrate seamlessly with lazy evaluation:
918
-
919
- ```c
920
- DecoratedPtr resolve_reference(DecoratedPtr backref) {
921
- node_id = read_variable_int(backref.bits, backref.bit_offset);
922
-
923
- // Look up in node table
924
- node_entry = node_table[node_id];
925
-
926
- return (DecoratedPtr) {
927
- .bits = backref.bits,
928
- .bit_offset = node_entry.offset,
929
- .type_state = node_entry.type_state,
930
- .ref_count = 1
931
- };
932
- }
933
- ```
934
-
935
- The decorated pointer model naturally handles back-references as lightweight pointer redirections.
936
-
937
- #### **Memory vs. compression trade-offs**
938
-
939
- | Strategy | Memory usage | Compression ratio | Decode speed |
940
- |----------|--------------|-------------------|--------------|
941
- | No DAG | Low | 1.0× (baseline) | Fastest |
942
- | Greedy DAG | Medium | 2-10× | Fast |
943
- | Optimal DAG | High | 5-50× | Medium |
944
-
945
- For **streaming applications**: Prefer greedy DAG with small node tables
946
- For **archival storage**: Use optimal DAG compression
947
- For **real-time processing**: Skip DAG compression entirely
948
-
949
- #### **Compression example: Repeated data structures**
950
-
951
- Consider a list of similar records:
952
-
953
- ```k-lang
954
- $record = { string name, int age, string department };
955
- $company = < { record head, company tail } cons, {} nil >;
956
- ```
957
-
958
- Value representing 1000 employees where 80% work in "Engineering":
959
-
960
- ```text
961
- Without DAG: Each "Engineering" string encoded separately
962
- - 1000 records × 30 bytes/record = 30KB
963
-
964
- With DAG: "Engineering" string shared via back-references
965
- - 1 × "Engineering" (11 bytes) + 800 × back-reference (3 bytes) = 2.4KB
966
- - Compression ratio: ~12.5×
967
- ```
968
-
969
- The DAG representation transforms the **tree** into a more efficient **directed acyclic graph**:
970
-
971
- ```text
972
- Tree: Record1 → "Engineering"
973
- Record2 → "Engineering"
974
- Record3 → "Engineering"
975
- ...
976
-
977
- DAG: Record1 ↘
978
- Record2 → Shared("Engineering")
979
- Record3 ↗
980
- ...
981
- ```
982
-
983
- ### **6.4.3 Hardware-optimized encoding**
984
-
985
- While bit-level precision is theoretically optimal, modern hardware operates on **byte-aligned data** using **word-sized registers**. A practical encoding must balance compression with decode performance.
986
-
987
- #### **Register-friendly union encoding**
988
-
989
- Instead of `ceil(log2(m))` bits, use **byte-aligned codes** optimized for common union sizes:
990
-
991
- | Union variants | Theoretical bits | Hardware-optimized |
992
- |----------------|------------------|-------------------|
993
- | 2-3 variants | 1-2 bits | 1 byte (8 bits) |
994
- | 4-15 variants | 2-4 bits | 1 byte (8 bits) |
995
- | 16-255 variants| 4-8 bits | 1 byte (8 bits) |
996
- | 256+ variants | 8+ bits | 2+ bytes (LEB128) |
997
-
998
- **Rationale**: Single-byte discriminators enable efficient decoding with simple memory loads and bit masking.
999
-
1000
- #### **SIMD-friendly data layout**
1001
-
1002
- Modern CPUs can process multiple bytes simultaneously using **SIMD instructions**. Structure the encoding to leverage this:
1003
-
1004
- ```c
1005
- // Bad: bit-packed discriminators
1006
- struct BitPacked {
1007
- uint64_t discriminators; // 8 × 8-bit = 64 discriminators packed
1008
- // Requires expensive bit extraction
1009
- };
1010
-
1011
- // Good: byte-aligned discriminators
1012
- struct ByteAligned {
1013
- uint8_t discriminators[8]; // 8 discriminators, SIMD-friendly
1014
- // Can be processed with single AVX2 instruction
1015
- };
1016
- ```
1017
-
1018
- #### **Cache-line aware field ordering**
1019
-
1020
- Arrange product fields to minimize **cache misses**:
1021
-
1022
- ```c
1023
- // Product field layout optimization
1024
- struct ProductLayout {
1025
- uint8_t discriminators[N]; // All discriminators together (hot data)
1026
- uint8_t padding[64 - N % 64]; // Align to cache line boundary
1027
- uint8_t field_data[]; // Field data follows (cold data)
1028
- };
1029
- ```
1030
-
1031
- Fields are ordered by **access frequency** rather than alphabetical order:
1032
-
1033
- - **Hot fields** (frequently accessed): placed first
1034
- - **Cold fields** (rarely accessed): placed last
1035
- - **Large fields** (arrays, strings): placed at end to avoid cache pollution
1036
-
1037
- #### **Word-aligned pointer arithmetic**
1038
-
1039
- Replace bit-level navigation with **word-aligned operations**:
1040
-
1041
- ```c
1042
- // Bit-level navigation (slow)
1043
- DecoratedPtr navigate_bitwise(DecoratedPtr ptr, int field_index) {
1044
- size_t bit_offset = ptr.bit_offset;
1045
- for (int i = 0; i < field_index; i++) {
1046
- bit_offset += extract_field_bit_size(ptr.bits, bit_offset); // Expensive!
1047
- }
1048
- return make_ptr(ptr.bits, bit_offset);
1049
- }
1050
-
1051
- // Word-aligned navigation (fast)
1052
- DecoratedPtr navigate_wordwise(DecoratedPtr ptr, int field_index) {
1053
- uint8_t* byte_ptr = ptr.bits + (ptr.bit_offset >> 3); // Convert to byte offset
1054
-
1055
- // Read field offset table (precomputed during encoding)
1056
- uint32_t* offset_table = (uint32_t*)byte_ptr;
1057
- uint32_t field_offset = offset_table[field_index]; // Single memory load
1058
-
1059
- return make_ptr(ptr.bits, field_offset << 3); // Convert back to bits
1060
- }
1061
- ```
1062
-
1063
- #### **CPU instruction optimization**
1064
-
1065
- **Branch prediction friendly encoding**:
1066
-
1067
- ```c
1068
- // Bad: unpredictable branching
1069
- uint8_t discriminator = *byte_ptr++;
1070
- switch (discriminator) {
1071
- case 0: handle_variant_0(); break;
1072
- case 1: handle_variant_1(); break;
1073
- case 2: handle_variant_2(); break;
1074
- // ... many cases cause branch misprediction
1075
- }
1076
-
1077
- // Good: function table dispatch
1078
- typedef void (*VariantHandler)(uint8_t* data);
1079
- static VariantHandler variant_handlers[256] = {
1080
- handle_variant_0, handle_variant_1, handle_variant_2, /* ... */
1081
- };
1082
-
1083
- uint8_t discriminator = *byte_ptr++;
1084
- variant_handlers[discriminator](byte_ptr); // Single indirect call
1085
- ```
1086
-
1087
- **Prefetch-aware access patterns**:
1088
-
1089
- ```c
1090
- // Sequential prefetching for product fields
1091
- void prefetch_product_fields(DecoratedPtr ptr, int num_fields) {
1092
- uint32_t* offset_table = (uint32_t*)(ptr.bits + (ptr.bit_offset >> 3));
1093
-
1094
- // Prefetch offset table into L1 cache
1095
- __builtin_prefetch(offset_table, 0, 3); // Read, high temporal locality
1096
-
1097
- // Prefetch first few fields
1098
- for (int i = 0; i < min(num_fields, 4); i++) {
1099
- uint8_t* field_ptr = ptr.bits + offset_table[i];
1100
- __builtin_prefetch(field_ptr, 0, 2); // Read, medium temporal locality
1101
- }
1102
- }
1103
- ```
1104
-
1105
- #### **Memory bandwidth optimization**
1106
-
1107
- **Streaming-friendly encoding**:
1108
-
1109
- ```c
1110
- // Structure data for optimal memory bandwidth
1111
- struct StreamLayout {
1112
- struct {
1113
- uint32_t total_size; // 4 bytes
1114
- uint16_t num_fields; // 2 bytes
1115
- uint16_t flags; // 2 bytes (padding + metadata)
1116
- } header; // 8 bytes total (fits in single cache line)
1117
-
1118
- uint32_t field_offsets[]; // 4N bytes (word-aligned)
1119
- uint8_t field_data[]; // Variable length data
1120
- };
1121
- ```
1122
-
1123
- **Memory access patterns**:
1124
-
1125
- - **Sequential reads**: Optimal for streaming workloads
1126
- - **Random access**: Offset table enables O(1) field access
1127
- - **Bulk operations**: SIMD can process multiple discriminators simultaneously
1128
-
1129
- #### **Hardware-specific optimizations**
1130
-
1131
- **x86-64 optimizations**:
1132
-
1133
- ```c
1134
- // Exploit x86-64 addressing modes
1135
- DecoratedPtr navigate_x86(DecoratedPtr ptr, int field_index) {
1136
- uint32_t* offset_table = (uint32_t*)(ptr.bits + (ptr.bit_offset >> 3));
1137
-
1138
- // Single instruction: LEA + memory operand
1139
- uint8_t* field_ptr = ptr.bits + offset_table[field_index];
1140
-
1141
- return make_ptr_from_bytes(field_ptr);
1142
- }
1143
-
1144
- // Utilize SIMD for bulk discriminator reading
1145
- void decode_discriminators_avx2(uint8_t* input, uint8_t* output, size_t count) {
1146
- __m256i* input_vec = (__m256i*)input;
1147
- __m256i* output_vec = (__m256i*)output;
1148
-
1149
- for (size_t i = 0; i < count / 32; i++) {
1150
- __m256i data = _mm256_load_si256(&input_vec[i]);
1151
-
1152
- // Process 32 discriminators in parallel
1153
- __m256i decoded = _mm256_and_si256(data, _mm256_set1_epi8(0x0F));
1154
-
1155
- _mm256_store_si256(&output_vec[i], decoded);
1156
- }
1157
- }
1158
- ```
1159
-
1160
- **ARM64 optimizations**:
1161
-
1162
- ```c
1163
- // Exploit ARM64 load-with-offset instructions
1164
- DecoratedPtr navigate_arm64(DecoratedPtr ptr, int field_index) {
1165
- uint32_t* offset_table = (uint32_t*)(ptr.bits + (ptr.bit_offset >> 3));
1166
-
1167
- // ARM64: single LDR instruction with register offset
1168
- uint32_t offset = offset_table[field_index];
1169
- uint8_t* field_ptr = ptr.bits + offset;
1170
-
1171
- return make_ptr_from_bytes(field_ptr);
1172
- }
1173
- ```
1174
-
1175
- #### **Performance analysis: Hardware vs. theoretical encoding**
1176
-
1177
- **Decoding performance comparison** (cycles per field access):
1178
-
1179
- | Encoding strategy | Field access cost | Cache behavior | SIMD support |
1180
- |-------------------|-------------------|----------------|--------------|
1181
- | Bit-packed optimal | 50-100 cycles | Poor (bit manipulation) | No |
1182
- | Byte-aligned | 5-15 cycles | Good (word loads) | Yes |
1183
- | Word-aligned + offset table | 2-8 cycles | Excellent (prefetchable) | Yes |
1184
-
1185
- **Space vs. time trade-offs**:
1186
-
1187
- ```
1188
- Bit-packed: 100% space efficiency, 10× decode overhead
1189
- Byte-aligned: 90% space efficiency, 2× decode overhead
1190
- Word-aligned: 80% space efficiency, 1× decode overhead (baseline)
1191
- ```
1192
-
1193
- **Real-world measurements** (modern x86-64, 3.2GHz):
1194
-
1195
- ```c
1196
- // Benchmark: Navigate to field in 1000-field product
1197
- Bit-manipulation: 2,400 ns (7,680 cycles)
1198
- Byte-aligned: 480 ns (1,536 cycles)
1199
- Word-aligned table: 160 ns (512 cycles)
1200
- ```
1201
-
1202
- The **offset table approach** provides the best practical performance despite modest space overhead.
1203
-
1204
- #### **Recommended encoding strategy**
1205
-
1206
- **Hybrid approach** balancing theory and practice:
1207
-
1208
- 1. **Small products** (≤8 fields): Byte-aligned, no offset table
1209
- 2. **Medium products** (9-64 fields): Word-aligned with offset table
1210
- 3. **Large products** (65+ fields): Hierarchical offset tables + prefetching
1211
- 4. **Unions**: Single byte discriminator (sufficient for 99.9% of real types)
1212
-
1213
- **Encoding format**:
1214
-
1215
- ```c
1216
- struct OptimalEncoding {
1217
- uint8_t type_tag; // 1 byte: product/union/external marker
1218
- uint8_t arity; // 1 byte: number of children (0-255)
1219
- uint16_t flags; // 2 bytes: compression flags, alignment info
1220
- uint32_t total_size; // 4 bytes: total size for skipping
1221
-
1222
- // For products with >8 fields:
1223
- uint32_t field_offsets[arity]; // 4×N bytes: random access table
1224
-
1225
- // Payload data follows
1226
- uint8_t data[];
1227
- };
1228
- ```
1229
-
1230
- **Benefits**:
1231
-
1232
- - **8-byte aligned** structures for optimal memory access
1233
- - **Single cache line** headers for small products
1234
- - **SIMD-friendly** data layout
1235
- - **Prefetch-optimized** for large structures
1236
- - **Hardware-agnostic** but optimized for common architectures
1237
-
1238
- ### **6.4.4 External type integration**
1239
-
1240
- ```text
1241
- | External Marker (2 bits) | Type ID | External Data Length | External Data |
1242
- ```
1243
-
1244
- This allows built-in types (strings, floats, etc.) to be efficiently serialized within `k` values while maintaining the canonical property.
1245
-
1246
- ---
1247
-
1248
- ## **6.5 Demand-driven deserialization**
1249
-
1250
- Operations on values trigger selective deserialization:
1251
-
1252
- ### **6.5.1 Access patterns**
1253
-
1254
- - **Identity operation `()`** — no deserialization, direct bitstream copy
1255
- - **Field access `.field`** — deserialize only the path to the requested field
1256
- - **Pattern matching** — deserialize only the discriminator, then the matched branch
1257
- - **Full evaluation** — progressive deserialization as computation proceeds
1258
-
1259
- ### **6.5.2 Lazy evaluation strategy**
1260
-
1261
- ```c
1262
- function access_field(value: KValue, field_path: String[]): KValue {
1263
- if (value.form == SERIALIZED) {
1264
- // Partially deserialize to reach the field
1265
- root = deserialize_to_depth(value, field_path.length);
1266
- value = make_lazy(root, value.serialized.bits);
1267
- }
1268
-
1269
- if (value.form == LAZY) {
1270
- node = navigate_path(value.lazy.root, field_path);
1271
- if (node.child[field_idx].state == SERIALIZED_CHILD) {
1272
- // Deserialize this child on demand
1273
- child_value = deserialize_child(node, field_idx);
1274
- node.child[field_idx].state = MATERIALIZED_CHILD;
1275
- node.child[field_idx].node = child_value;
1276
- }
1277
- return &node.child[field_idx];
1278
- }
1279
-
1280
- // Traditional navigation for fully materialized values
1281
- return navigate_materialized(value, field_path);
1282
- }
1283
- ```
1284
-
1285
- ---
1286
-
1287
- ## **6.6 External type system**
1288
-
1289
- Built-in and foreign types integrate through a plugin interface:
1290
-
1291
- ### **6.6.1 External operations**
1292
-
1293
- ```c
1294
- struct ExternalOps {
1295
- KValue* (*serialize)(void* data);
1296
- void* (*deserialize)(KValue* serialized);
1297
- KValue* (*apply_function)(void* data, const char* func_name, KValue* args);
1298
- bool (*equals)(void* a, void* b);
1299
- uint64_t (*hash)(void* data);
1300
- void (*destroy)(void* data);
1301
- };
1302
- ```
1303
-
1304
- ### **6.6.2 Built-in type examples**
1305
-
1306
- - **Strings** — UTF-8 encoded with length prefix
1307
- - **Integers** — variable-length encoding (LEB128)
1308
- - **Floats** — IEEE 754 binary representation
1309
- - **Blobs** — raw binary data with length prefix
1310
- - **Handles** — opaque references to external resources
1311
-
1312
- ### **6.6.3 Type registration**
1313
-
1314
- ```c
1315
- void register_external_type(int type_id, const char* name, ExternalOps* ops) {
1316
- external_types[type_id] = {
1317
- .name = name,
1318
- .ops = ops,
1319
- .canonical_hash = compute_type_hash(name, ops->signature)
1320
- };
1321
- }
1322
- ```
1323
-
1324
- ---
1325
-
1326
- ## **6.7 Memory management and performance**
1327
-
1328
- ### **6.7.1 Arena allocation**
1329
-
1330
- All materialized nodes use arena allocation for fast allocation and bulk deallocation:
1331
-
1332
- ```c
1333
- struct Arena {
1334
- uint8_t* memory;
1335
- size_t capacity;
1336
- size_t used;
1337
- Arena* next; // linked list of arena blocks
1338
- };
1339
- ```
1340
-
1341
- ### **6.7.2 Sharing and deduplication**
1342
-
1343
- - **Canonical folding** — identical subtrees share the same materialized nodes
1344
- - **Bitstream sharing** — multiple lazy values can reference the same underlying bitstream
1345
- - **DAG back-references** — shared subtrees in serialized form avoid duplication during lazy materialization
1346
- - **External sharing** — external values use reference counting or garbage collection as appropriate
1347
-
1348
- ### **6.7.3 Optimization opportunities**
1349
-
1350
- - **Zero-copy operations** — identity, field projection on serialized values
1351
- - **Streaming evaluation** — process large datasets without full materialization
1352
- - **Parallel deserialization** — independent subtrees can be materialized concurrently
1353
- - **DAG-aware caching** — cache resolutions of frequently accessed back-references
1354
- - **Memoization** — cache frequently accessed paths in lazy structures
1355
-
1356
- ---
1357
-
1358
- ## **6.8 Example: Lazy evaluation in practice**
1359
-
1360
- Consider processing a large JSON-like structure where we only need one field:
1361
-
1362
- ```k-lang
1363
- $record = { string name, list data, metadata meta };
1364
- $list = < {nat value, list next} cons, {} nil >;
1365
- $metadata = { timestamp created, string author };
1366
- ```
1367
-
1368
- For the input program `\x.x.name` (extract name field):
1369
-
1370
- 1. **Input** arrives as serialized bitstream (e.g., from network/disk)
1371
- 2. **Lazy parsing** deserializes only the discriminator of the root product
1372
- 3. **Field access** deserializes path to `name` field, leaving `data` and `meta` serialized
1373
- 4. **Output** the extracted string value (possibly in external string format)
1374
-
1375
- The entire `data` list and `metadata` remain as untouched bitstreams, providing massive performance benefits for large, sparsely accessed data structures.
1376
-
1377
- ### **6.8.1 Serialized representation**
1378
-
1379
- The same value can exist in different forms simultaneously:
1380
-
1381
- ```c
1382
- // Original input: fully serialized
1383
- KValue input = {
1384
- .form = SERIALIZED,
1385
- .serialized = {
1386
- .bits = bitstream_buffer,
1387
- .type_hash = hash("record")
1388
- }
1389
- };
1390
-
1391
- // After partial access: hybrid lazy
1392
- KValue hybrid = {
1393
- .form = LAZY,
1394
- .lazy.root = {
1395
- .state = 0, .tag = -1, .arity = 3,
1396
- .child = {
1397
- [0] = { .state = MATERIALIZED_CHILD, .node = materialized_string_node }, // name: materialized
1398
- [1] = { .state = SERIALIZED_CHILD, .serialized = {bits+offset, 64} }, // data: still serialized
1399
- [2] = { .state = SERIALIZED_CHILD, .serialized = {bits+offset, 1088} } // meta: still serialized
1400
- }
1401
- }
1402
- };
1403
- ```
1404
-
1405
- ### **6.8.2 External type integration example**
1406
-
1407
- ```c
1408
- // Built-in string type
1409
- KValue string_value = {
1410
- .form = EXTERNAL,
1411
- .external = {
1412
- .data = utf8_string_data,
1413
- .type_id = BUILTIN_STRING,
1414
- .ops = &string_operations
1415
- }
1416
- };
1417
-
1418
- // The string_operations provide:
1419
- ExternalOps string_operations = {
1420
- .serialize = string_to_canonical_bits,
1421
- .deserialize = canonical_bits_to_string,
1422
- .apply_function = string_builtin_functions, // length, concat, etc.
1423
- .equals = utf8_string_compare,
1424
- .hash = utf8_string_hash,
1425
- .destroy = free_string_data
1426
- };
1427
- ```
1428
-
1429
- ---
1430
-
1431
- ## **6.9 Summary**
1432
-
1433
- This unified representation provides several key advantages:
1434
-
1435
- - **Performance** — avoid unnecessary deserialization through lazy evaluation
1436
- - **Memory efficiency** — share serialized data between multiple references
1437
- - **Composability** — external types integrate seamlessly with k's type system
1438
- - **Streaming** — process large data structures with bounded memory usage
1439
- - **Determinism** — canonical serialization ensures reproducible computation
1440
-
1441
- The design supports the full spectrum from zero-copy identity operations to fully materialized tree traversal, allowing the runtime to automatically choose the most efficient representation for each use case.
1442
-
1443
- ### **6.9.1 Key design principles**
1444
-
1445
- - **Immutability** — all representations are immutable, enabling safe sharing
1446
- - **Demand-driven** — materialization happens only when computation requires it
1447
- - **Canonical** — serialized forms are deterministic and hashable
1448
- - **Extensible** — external types extend the system without breaking canonical properties
1449
- - **Unified** — single abstraction handles all value representations
1450
-
1451
- ---
1452
-
1453
- ## **6.10 Execution model with decorated pointers**
1454
-
1455
- The formal execution model is based on decorated pointers, which represent values during execution:
1456
-
1457
- ### **6.10.1 Decorated pointers**
1458
-
1459
- Every value during execution is represented by a **decorated pointer**:
1460
-
1461
- ```c
1462
- struct DecoratedPtr {
1463
- uint8_t* bits; // pointer into serialized input heap
1464
- size_t bit_offset; // bit position within the stream
1465
- TypeState type_state; // canonical automaton state (C0, C1, ...)
1466
- int ref_count; // for sharing and caching
1467
- struct {
1468
- bool is_cached; // memoization flag
1469
- uint64_t content_hash; // for cache lookup
1470
- } cache;
1471
- };
1472
- ```
1473
-
1474
- The key insight: **decorated pointers are lightweight and can be computed without memory allocation**.
1475
-
1476
- ### **6.10.2 Program execution pipeline**
1477
-
1478
- A compiled k program is a composition of operations: `projection ∘ product ∘ union ∘ builtin_call ∘ ...`
1479
-
1480
- Each operation transforms a decorated pointer:
1481
-
1482
- ```text
1483
- DecoratedPtr → DecoratedPtr
1484
- ```
1485
-
1486
- #### **Projection operation**
1487
-
1488
- For field access `.field_name`:
1489
-
1490
- ```c
1491
- DecoratedPtr project_field_optimized(DecoratedPtr input, int field_index) {
1492
- // Hardware-optimized field navigation
1493
- uint8_t* byte_ptr = input.bits + (input.bit_offset >> 3);
1494
-
1495
- // Read encoding header
1496
- struct OptimalEncoding* header = (struct OptimalEncoding*)byte_ptr;
1497
-
1498
- if (header->arity <= 8) {
1499
- // Small product: sequential scan (cache-friendly)
1500
- uint8_t* field_ptr = byte_ptr + sizeof(struct OptimalEncoding);
1501
- for (int i = 0; i < field_index; i++) {
1502
- uint32_t field_size = *(uint32_t*)field_ptr; // First 4 bytes = size
1503
- field_ptr += field_size;
1504
- }
1505
- return make_ptr_from_bytes(field_ptr);
1506
- } else {
1507
- // Large product: offset table lookup (O(1) access)
1508
- uint32_t field_offset = header->field_offsets[field_index];
1509
- return make_ptr_from_bytes(byte_ptr + field_offset);
1510
- }
1511
- }
1512
- ```
1513
-
1514
- **Key property**: Hardware-optimized navigation using word-aligned loads and offset tables.
1515
-
1516
- #### **Union operation**
1517
-
1518
- For pattern matching `case { tag1 → ..., tag2 → ... }`:
1519
-
1520
- ```c
1521
- DecoratedPtr match_union_optimized(DecoratedPtr input, int expected_tag) {
1522
- // Read single-byte discriminator (hardware-friendly)
1523
- uint8_t* byte_ptr = input.bits + (input.bit_offset >> 3);
1524
- struct OptimalEncoding* header = (struct OptimalEncoding*)byte_ptr;
1525
-
1526
- uint8_t actual_tag = header->data[0]; // First byte of data = discriminator
1527
-
1528
- if (actual_tag != expected_tag) {
1529
- return UNDEFINED_PTR; // Pattern match failure
1530
- }
1531
-
1532
- // Skip past header + discriminator
1533
- uint8_t* child_ptr = byte_ptr + sizeof(struct OptimalEncoding) + 1;
1534
-
1535
- return make_ptr_from_bytes(child_ptr);
1536
- }
1537
- ```
1538
-
1539
- #### **Product operation**
1540
-
1541
- For product construction `{ field1, field2, ... }`:
1542
-
1543
- ```c
1544
- DecoratedPtr construct_product(DecoratedPtr* field_ptrs, int arity) {
1545
- // This is the primary point where memory allocation is required.
1546
- ProductNode* result = allocate_product_node(arity);
1547
-
1548
- // Evaluate each field (possibly in parallel)
1549
- for (int i = 0; i < arity; i++) {
1550
- result->fields[i] = evaluate_expression(field_ptrs[i]);
1551
- }
1552
-
1553
- return make_materialized_ptr(result);
1554
- }
1555
- ```
1556
-
1557
- #### **Built-in call operation**
1558
-
1559
- For external functions `string_length`, `add_int`, etc.:
1560
-
1561
- ```c
1562
- DecoratedPtr builtin_call(DecoratedPtr input, BuiltinFunc func) {
1563
- // Force materialization of the input
1564
- ExternalValue* materialized = force_external(input);
1565
-
1566
- // Delegate to external implementation
1567
- ExternalValue* result = func->call(materialized);
1568
-
1569
- return make_external_ptr(result);
1570
- }
1571
- ```
1572
-
1573
- ### **6.10.3 Execution strategy**
1574
-
1575
- 1. **Input setup**: Place serialized input in heap, create root decorated pointer
1576
- 2. **Lazy evaluation**: Transform decorated pointers without allocation when possible
1577
- 3. **Forced materialization**: Allocate memory only when constructing products or calling built-ins
1578
- 4. **Reference counting**: Share decorated pointers for common subexpressions
1579
- 5. **Output serialization**: Convert final decorated pointer back to canonical bits
1580
-
1581
- ### **6.10.4 Example execution trace**
1582
-
1583
- For program `\x.(x.data.head.value, x.name)` on input `{ name: "alice", data: [42, 17] }`:
1584
-
1585
- ```text
1586
- Step 1: input_ptr = {bits: heap_start, offset: 0, state: C0_record}
1587
-
1588
- Step 2: data_ptr = project_field(input_ptr, 1) // .data
1589
- = {bits: heap_start, offset: 120, state: C0_list}
1590
-
1591
- Step 3: head_ptr = match_union(data_ptr, CONS_TAG) // pattern match
1592
- = {bits: heap_start, offset: 122, state: C0_cons}
1593
-
1594
- Step 4: value_ptr = project_field(head_ptr, 0) // .value
1595
- = {bits: heap_start, offset: 122, state: C0_int}
1596
-
1597
- Step 5: name_ptr = project_field(input_ptr, 0) // .name
1598
- = {bits: heap_start, offset: 8, state: C0_string}
1599
-
1600
- Step 6: result_ptr = construct_product([value_ptr, name_ptr])
1601
- // Note: memory allocation for the result tuple occurs here.
1602
- ```
1603
-
1604
- ### **6.10.5 Optimization opportunities**
1605
-
1606
- - **Parallel field evaluation** — Independent fields can be computed concurrently
1607
- - **Memoization** — Cache decorated pointers for repeated subexpressions
1608
- - **Streaming** — Process parts of large inputs without loading everything
1609
- - **Zero-copy identity** — `()` just returns the input decorated pointer
1610
- - **Prefix-free parsing** — No backtracking needed, linear-time navigation
1611
-
1612
- This model provides **optimal performance** for sparse data access while maintaining the mathematical precision of k's type system.
1613
-
1614
- ---
1615
-
1616
- ## **6.11 Implementation considerations**
1617
-
1618
- ### **6.11.1 Prefix-free code requirements**
1619
-
1620
- The execution model depends critically on **prefix-free codes** in the serialization:
1621
-
1622
- - **Union discriminators** — Fixed-length codes ensure constant-time tag reading
1623
- - **External data** — Length-prefixed to enable skipping without parsing
1624
- - **Product fields** — Implicit boundaries defined by type structure
1625
-
1626
- This enables the `skip_field()` function to advance pointers without full deserialization.
1627
-
1628
- ### **6.11.2 Reference counting and caching**
1629
-
1630
- Decorated pointers should be reference-counted for several reasons:
1631
-
1632
- Benefits:
1633
-
1634
- - **Sharing** — Multiple references to the same subexpression
1635
- - **Caching** — Avoid recomputation of expensive operations
1636
- - **Memory efficiency** — Single copy of large serialized inputs
1637
-
1638
- ### **6.11.3 Parallel evaluation**
1639
-
1640
- Product construction offers natural parallelization:
1641
-
1642
- ```c
1643
- DecoratedPtr construct_product_parallel(DecoratedPtr* field_ptrs, int arity) {
1644
- ProductNode* result = allocate_product_node(arity);
1645
-
1646
- #pragma omp parallel for
1647
- for (int i = 0; i < arity; i++) {
1648
- result->fields[i] = evaluate_expression(field_ptrs[i]);
1649
- }
1650
-
1651
- return make_materialized_ptr(result);
1652
- }
1653
- ```
1654
-
1655
- Each field evaluation is independent and can run on separate threads.
1656
-
1657
- ### **6.11.4 Memory layout optimization**
1658
-
1659
- The input heap should be designed for efficient navigation:
1660
-
1661
- ```c
1662
- struct InputHeap {
1663
- uint8_t* serialized_data; // the original bitstream
1664
- size_t total_bits; // total size
1665
-
1666
- // Optional: precomputed navigation table for large inputs
1667
- struct {
1668
- size_t* field_offsets; // byte offsets for major fields
1669
- int table_size; // number of cached offsets
1670
- } navigation_cache;
1671
- };
1672
- ```
1673
-
1674
- For very large inputs, precomputing field offsets can eliminate repeated prefix-free parsing.
1675
-
1676
- ---
1677
-
1678
- ## **6.12 Final summary**
1679
-
1680
- This unified design achieves several important goals:
1681
-
1682
- 1. **Zero-copy operations** — Identity and projection work directly on serialized data
1683
- 2. **Demand-driven materialization** — Memory allocation only when necessary
1684
- 3. **External type integration** — Built-ins fit seamlessly into the execution model
1685
- 4. **Parallel execution** — Natural parallelization opportunities
1686
- 5. **Streaming capability** — Process large inputs with bounded memory
1687
-
1688
- The decorated pointer model provides a clean abstraction that bridges the gap between the mathematical elegance of k's type system and the performance requirements of real-world data processing.
1689
-
1690
- **Key insight**: By treating serialized data as the primary representation and materialized structures as secondary optimization, we invert the traditional approach and achieve much better performance for sparse data access patterns.
1691
-
1692
- ### **6.12.1 Hardware-optimized encoding summary**
1693
-
1694
- The final encoding strategy balances theoretical optimality with practical hardware constraints:
1695
-
1696
- **Trade-offs achieved**:
1697
-
1698
- - **Space efficiency**: 80-90% of theoretical optimum
1699
- - **Decode performance**: 5-10× faster than bit-level encoding
1700
- - **Cache behavior**: Excellent (word-aligned, sequential access)
1701
- - **SIMD compatibility**: Yes (bulk discriminator processing)
1702
- - **Architecture agnostic**: Optimized for x86-64 and ARM64
1703
-
1704
- **Performance characteristics**:
1705
-
1706
- ```text
1707
- Operation Hardware-optimized Bit-level optimal
1708
- Field access 2-8 cycles 50-100 cycles
1709
- Union matching 1-3 cycles 20-40 cycles
1710
- Memory bandwidth 95% theoretical 60% theoretical
1711
- Cache miss ratio <5% >25%
1712
- ```
1713
-
1714
- **Recommended for**:
1715
-
1716
- - **Production systems** requiring high performance
1717
- - **Streaming applications** with large data volumes
1718
- - **Mobile/embedded** systems with limited CPU resources
1719
- - **SIMD-heavy workloads** processing bulk data
1720
-
1721
- The hardware-optimized approach provides the best practical balance for real-world k implementations.
1722
-
1723
- ---
1724
-
1725
- ---
1726
-
1727
-
1728
- # Chapter 7 — The Partial Function ABI
1729
-
1730
- ## **7.1 Purpose**
1731
-
1732
- The **application binary interface (ABI)** defines how partial functions in `k` are represented and invoked at runtime.
1733
- Its goal is to make all functions - whether user-defined or compiled - compatible with the same calling convention and data layout.
1734
-
1735
- Every compiled `k` function receives a single argument (a value tree) and may either produce a result or remain undefined.
1736
- The ABI provides a uniform way to express both outcomes.
1737
-
1738
- ---
1739
-
1740
- ## **7.2 Function result representation**
1741
-
1742
- The result of every function call is a pair of fields:
1743
-
1744
- ```c
1745
- struct KOpt {
1746
- bool ok; // 1 if function is defined for the given input
1747
- struct KNode* val; // valid only if ok == true
1748
- };
1749
- ```
1750
-
1751
- If `ok == 1`, the function succeeded and `val` points to the root node of the resulting value.
1752
- If `ok == 0`, the function is undefined for the input, and `val` is meaningless.
1753
-
1754
- This structure carries both the success flag and the value pointer, allowing partiality to be expressed directly in generated code.
1755
-
1756
- ---
1757
-
1758
- ## **7.3 Function parameter representation**
1759
-
1760
- Each function takes exactly one parameter:
1761
-
1762
- ```c
1763
- struct KNode* input;
1764
- ```
1765
-
1766
- The parameter points to the root of the input value.
1767
- Since all values are immutable, the function must not modify this structure.
1768
-
1769
- ---
1770
-
1771
- ## **7.4 Calling convention**
1772
-
1773
- Every `k` function compiled to machine code follows this C-style signature:
1774
-
1775
- ```c
1776
- struct KOpt k_function(struct KNode* input);
1777
- ```
1778
-
1779
- The return value indicates success or failure.
1780
- The convention is uniform for all functions, regardless of the specific types involved.
1781
-
1782
- ---
1783
-
1784
- ## **7.5 Core runtime operations**
1785
-
1786
- The runtime provides a small set of primitive functions implementing the fundamental operations of `k`:
1787
-
1788
- | Operation | Purpose | Result |
1789
- | -------------------------------------- | ----------------------------- | ------------------------------------------------- |
1790
- | `k_project(input, label_id)` | projection `.label` | defined if the field or variant `label_id` exists |
1791
- | `k_make_product(state, children[], n)` | construct a product node | always defined |
1792
- | `k_make_union(state, tag, child)` | construct a union node | always defined |
1793
- | `k_fail()` | represent undefined result | returns `{ ok = 0 }` |
1794
- | `k_unit()` | return the constant unit node | returns `{ ok = 1, val = &unit_node }` |
1795
-
1796
- All user-defined functions can be compiled entirely in terms of these operations.
1797
-
1798
- ---
1799
-
1800
- ## **7.6 Metadata tables**
1801
-
1802
- To interpret or construct nodes correctly, the runtime holds constant tables derived from canonical type definitions:
1803
-
1804
- * `state_kind[state]` — `0` for product, `1` for union,
1805
- * `state_arity[state]` — number of child fields for products (always 1 for unions),
1806
- * `field_index[state][label_id]` — index of field in product (or `-1` if absent),
1807
- * `variant_index[state][label_id]` — index of variant in union (or `-1` if absent).
1808
-
1809
- These tables are read-only and known at compile time for each canonical type.
1810
-
1811
- ---
1812
-
1813
- ## **7.7 Example: projection**
1814
-
1815
- To implement the projection `.x`:
1816
-
1817
- ```c
1818
- struct KOpt k_project_x(struct KNode* input) {
1819
- int state = input->state;
1820
- int kind = state_kind[state];
1821
-
1822
- if (kind == PRODUCT) {
1823
- int idx = field_index[state]["x"];
1824
- if (idx < 0 || idx >= input->arity)
1825
- return (struct KOpt){0, NULL};
1826
- return (struct KOpt){1, input->child[idx]};
1827
- }
1828
-
1829
- if (kind == UNION) {
1830
- int tag = variant_index[state]["x"];
1831
- if (tag < 0 || input->tag != tag)
1832
- return (struct KOpt){0, NULL};
1833
- return (struct KOpt){1, input->child[0]};
1834
- }
1835
-
1836
- return (struct KOpt){0, NULL};
1837
- }
1838
- ```
1839
-
1840
- This code demonstrates the generic principle:
1841
- the projection is defined if and only if the input has a field or variant named `x`.
1842
-
1843
- ---
1844
-
1845
- ## **7.8 Error propagation**
1846
-
1847
- When one function calls another, the success flag `ok` propagates forward.
1848
- If a subcall returns `ok == 0`, the caller must return `{0, NULL}` immediately.
1849
- This makes undefinedness explicit and local—no exceptions, no global flags.
1850
-
1851
- ---
1852
-
1853
- ## **7.9 Summary**
1854
-
1855
- * Every function has the form `KOpt f(KNode*)`.
1856
- * Partiality is expressed by the `ok` flag.
1857
- * Runtime operations handle projection and construction.
1858
- * Metadata tables describe field positions and variant indices.
1859
- * By inspecting `arity`, `state`, and `tag`, the runtime can interpret any value correctly.
1860
- * This ABI ensures all generated and runtime functions can interoperate safely.
1861
-
1862
- ---
1863
-
1864
- ---
1865
-
1866
-
1867
- # Chapter 8 — Operational Semantics and Execution
1868
-
1869
- ## **8.1 Purpose**
1870
-
1871
- The **operational semantics** of `k` describe how expressions are evaluated step by step on actual value trees.
1872
- It defines when a function is *defined* for a particular input and what value it returns.
1873
- All execution—interpretive or compiled—follows these rules.
1874
-
1875
- ---
1876
-
1877
- ## **8.2 Evaluation relation**
1878
-
1879
- Evaluation is written as:
1880
-
1881
- ```text
1882
- ⟨ e , v ⟩ ⇓ r
1883
- ```
1884
-
1885
- meaning that expression `e` applied to value `v` yields result `r`.
1886
- If `e` is undefined for `v`, the relation does not hold.
1887
-
1888
- `r` is always a tree (or node) in memory, represented as described in Chapter 6.
1889
- Undefined results are expressed by the absence of any rule that produces `r`.
1890
-
1891
- ---
1892
-
1893
- ## **8.3 Rules for base expressions**
1894
-
1895
- ### **Identity**
1896
-
1897
- ```text
1898
- ⟨ () , v ⟩ ⇓ v
1899
- ```
1900
-
1901
- The empty composition `()` returns its argument unchanged.
1902
-
1903
- ### **Projection**
1904
-
1905
- Let `label` be a field or variant name.
1906
- If `v` has a child under `label`,
1907
-
1908
- ```text
1909
- ⟨ .label , v ⟩ ⇓ v.label
1910
- ```
1911
-
1912
- Otherwise the projection is undefined.
1913
-
1914
- ### **Type expression**
1915
-
1916
- For a type `T`,
1917
-
1918
- ```text
1919
- ⟨ $T , v ⟩ ⇓ v if v ∈ T
1920
- ```
1921
-
1922
- and undefined otherwise.
1923
- Type expressions thus act as identity functions restricted to their type.
1924
-
1925
- ---
1926
-
1927
- ## **8.4 Rules for composition**
1928
-
1929
- ### **Sequential composition**
1930
-
1931
- ```text
1932
- ⟨ (f g) , v ⟩ ⇓ r
1933
- ```
1934
-
1935
- if there exists `u` such that
1936
- `⟨ f , v ⟩ ⇓ u` and `⟨ g , u ⟩ ⇓ r`.
1937
-
1938
- If either step is undefined, the composition is undefined.
1939
- Because composition is associative,
1940
-
1941
- ```text
1942
- (f (g h)) ≡ ((f g) h) ≡ (f g h)
1943
- ```
1944
-
1945
- Parentheses are needed only for `()`.
1946
-
1947
- ---
1948
-
1949
- ## **8.5 Rules for product composition**
1950
-
1951
- ```text
1952
- ⟨ { f₁ l₁ , f₂ l₂ , … , fₙ lₙ } , v ⟩ ⇓ { r₁ l₁ , r₂ l₂ , … , rₙ lₙ }
1953
- ```
1954
-
1955
- if and only if all subfunctions `fᵢ` are defined on `v` and yield `rᵢ`.
1956
- If any subfunction is undefined, the whole product composition is undefined.
1957
-
1958
- A product composition constructs a new product node;
1959
- each result `rᵢ` becomes one child in canonical field order.
1960
-
1961
- ---
1962
-
1963
- ## **8.6 Rules for union composition**
1964
-
1965
- ```text
1966
- ⟨ < f₁ , f₂ , … , fₙ > , v ⟩ ⇓ rⱼ
1967
- ```
1968
-
1969
- if there exists the smallest index `j` such that
1970
- `⟨ fⱼ , v ⟩ ⇓ rⱼ`.
1971
-
1972
- If no subfunction is defined, the union composition is undefined.
1973
-
1974
- ---
1975
-
1976
- ## **8.7 Rules for filters**
1977
-
1978
- If a filter `F` matches a single type `T`
1979
-
1980
- ```text
1981
- ⟨ ?F , v ⟩ ⇓ ⟨ $T , v ⟩
1982
- ```
1983
-
1984
- otherwise it is an identity
1985
-
1986
- ```text
1987
- ⟨ ?F , v ⟩ ⇓ v
1988
- ```
1989
-
1990
- Filters therefore act as compile time annotations for type checking only.
1991
- They are ignored at run-time, unless they lead to fully typed expressions (i.e., can be replaced by a type).
1992
-
1993
- ---
1994
-
1995
- ## **8.8 Evaluation order**
1996
-
1997
- Evaluation proceeds left to right.
1998
- In products, all subfunctions receive the same input;
1999
- in unions, later functions are evaluated only if earlier ones fail.
2000
-
2001
- The semantics are deterministic:
2002
- for any given input, at most one result tree can be produced.
2003
-
2004
- ---
2005
-
2006
- ## **8.9 Example**
2007
-
2008
- Given:
2009
-
2010
- ```k-lang
2011
- $bool = < {} true, {} false >;
2012
- neg = $bool < .true {{ } false}, .false {{ } true} > $bool;
2013
- ```
2014
-
2015
- and input value `{ {} true }` of type `bool`,
2016
- evaluation steps are:
2017
-
2018
- 1. `⟨ $bool , { {} true } ⟩ ⇓ { {} true }`
2019
- 2. `⟨ < .true {{ } false}, .false {{ } true} > , { {} true } ⟩ ⇓ {{ } false}`
2020
- 3. `⟨ $bool , {{ } false} ⟩ ⇓ {{ } false}`
2021
-
2022
- Final result: `{{ } false}`.
2023
- If the input were of another type, step 1 would be undefined.
2024
-
2025
- ---
2026
-
2027
- ## **8.10 Implementation correspondence**
2028
-
2029
- The evaluation rules map directly onto the runtime ABI:
2030
-
2031
- | Semantic rule | Runtime operation |
2032
- | ------------------- | ------------------------------------- |
2033
- | Projection | `k_project` |
2034
- | Product composition | multiple subcalls + `k_make_product` |
2035
- | Union composition | sequential subcalls with early return |
2036
- | Type | runtime check of `state` |
2037
- | Composition | function call chain |
2038
-
2039
- In compiled form, the `ok` flag of `KOpt` represents whether a rule applies;
2040
- the node pointer represents the result value.
2041
-
2042
- ---
2043
-
2044
- ## **8.11 Summary**
2045
-
2046
- * Execution follows deterministic, left-to-right rules.
2047
- * All expressions denote partial functions on value trees.
2048
- * Type expressions act as restricted identities.
2049
- * Composition is associative; undefined propagates automatically.
2050
- * Runtime semantics match the formal evaluation relation exactly.
2051
-
2052
- ---
2053
-
2054
- ---
2055
-
2056
-
2057
- # Chapter 9 — Compiler Architecture
2058
-
2059
- ## **9.1 What the compiler does**
2060
-
2061
- A *compiler* for the `k` language is a program that reads another program (written in `k`) and produces a lower-level version of it.
2062
- The lower-level version can then be executed efficiently by a computer.
2063
-
2064
- For `k`, the compiler’s task is simple in concept:
2065
-
2066
- 1. **Read** the definitions of types and functions.
2067
- 2. **Understand** their structure and the relationships between them.
2068
- 3. **Translate** them into equivalent instructions that follow the runtime conventions described in Chapters 6–8.
2069
-
2070
- The compiler must ensure that every function in the source program becomes a machine-level function that behaves in exactly the same way:
2071
- it takes a value as input, may or may not be defined for it, and, if defined, produces another value.
2072
-
2073
- ---
2074
-
2075
- ## **9.2 The main stages**
2076
-
2077
- A `k` compiler is organized as a sequence of well-defined stages.
2078
- Each stage transforms one representation of the program into another, simpler one.
2079
-
2080
- 1. **Reading and parsing** — The compiler converts the source text into a tree of syntactic objects.
2081
- This tree is called the *abstract syntax tree* (AST).
2082
- It records the structure of expressions such as `{ .x a, .y b }` or `< .x, .y >`.
2083
-
2084
- 2. **Type analysis and normalization** —
2085
- The compiler expands type names, checks their correctness, and replaces all filters and meta-variables by concrete type information, following the procedure from Chapter 5.
2086
- The result is a fully typed AST.
2087
-
2088
- 3. **Preparation for execution** —
2089
- The compiler builds a *type table* for all canonical type states (C0, C1, …).
2090
- For each type, it records whether it is a product or a union and the number and names of its fields or variants.
2091
- These tables become the metadata used at runtime.
2092
-
2093
- 4. **Code generation** —
2094
- Finally, the compiler walks through the typed AST and produces an equivalent description in a lower-level form that follows the runtime conventions: functions returning a success flag and a value pointer.
2095
- This form can be expressed directly in C or in the *LLVM intermediate representation*, which is a platform-independent format understood by many compilers.
2096
-
2097
- ---
2098
-
2099
- ## **9.3 Internal structure**
2100
-
2101
- A minimal compiler can be viewed as three connected modules:
2102
-
2103
- * **Front end** — reads source code, builds the AST, performs type analysis.
2104
- * **Middle** — resolves all references, normalizes types, and simplifies expressions.
2105
- * **Back end** — produces the executable form.
2106
-
2107
- Although these terms are traditional, the distinction is simple:
2108
- the front end *understands* the program, the back end *emits* it, and the middle part connects the two.
2109
-
2110
- ---
2111
-
2112
- ## **9.4 Data kept by the compiler**
2113
-
2114
- During translation the compiler maintains several tables:
2115
-
2116
- | Name | Purpose |
2117
- | ------------------ | ----------------------------------------------------------------- |
2118
- | **Type table** | Maps type names to canonical forms and assigns numeric state IDs. |
2119
- | **Field table** | For each product type, records the order and index of its fields. |
2120
- | **Variant table** | For each union type, records the tags and their numeric indices. |
2121
- | **Function table** | Associates function names with their input and output types. |
2122
-
2123
- These tables ensure that generated code can find fields and variants by number rather than by name, which simplifies execution.
2124
-
2125
- ---
2126
-
2127
- ## **9.5 Example of transformation**
2128
-
2129
- Consider a simple k program:
2130
-
2131
- ```k-lang
2132
- $bool = < {} true, {} false >;
2133
- neg = $bool < /true | false, /false | true > $bool;
2134
- ```
2135
-
2136
- 1. **Parsing:**
2137
- The compiler recognizes a type definition `$bool` and a function `neg`.
2138
-
2139
- 2. **Type analysis:**
2140
- Both the input and output of `neg` are of type `bool`.
2141
-
2142
- 3. **Internal representation:**
2143
- The compiler constructs an internal tree describing that
2144
- `< /true f₁, /false f₂ >` means:
2145
- try the projection `/true`, then `/false`, each producing a constant value.
2146
-
2147
- 4. **Generated form:**
2148
- In the target language, `neg` becomes a small function that:
2149
-
2150
- * calls `k_project` to check which variant the input has,
2151
- * creates a new node with the opposite variant using `k_make_union`, and
2152
- * returns the result with `ok = 1`.
2153
-
2154
- This sequence of steps is fully automatic and follows directly from the semantic rules in Chapter 8.
2155
-
2156
- ---
2157
-
2158
- ## **9.6 Intermediate representation**
2159
-
2160
- Before producing final code, many compilers use an *intermediate representation* (IR).
2161
- It is a language designed to be easy to generate and easy to translate further into real machine code.
2162
- For `k`, the IR mirrors the structure of partial functions:
2163
-
2164
- | Concept in k | IR operation |
2165
- | --------------------- | -------------------------------- |
2166
- | Projection `.x` '/x' | `PROJECT label_id` |
2167
- | Composition `(f g)` | `CALL f; CALL g` |
2168
- | Union `<f,g>` | `TRY f; IF undefined THEN TRY g` |
2169
- | Product `{.x f, .y g}` | `PRODUCT_CALL (f, g); COMBINE` |
2170
- | Type restriction `$T` | `CHECK_TYPE state_id` |
2171
-
2172
- The compiler converts each AST node into one or more IR instructions.
2173
- The `PRODUCT_CALL(f, g)` instruction is a higher-level concept that implies applying both `f` and `g` to the same input value.
2174
- The low-level implementation of this would use stack operations to duplicate the input, but the IR itself remains more abstract.
2175
- Later, these instructions are translated into LLVM or C code using the runtime ABI.
2176
-
2177
- ---
2178
-
2179
- ## **9.7 Linking with the runtime**
2180
-
2181
- The generated code does not contain memory allocation or type tables itself.
2182
- Instead, it relies on the runtime library (`krt`), which provides:
2183
-
2184
- * functions such as `k_project` and `k_make_union`,
2185
- * metadata tables for each canonical type, and
2186
- * the unit value `{}` shared by all programs.
2187
-
2188
- When the compiler finishes, it outputs a text file in the target language plus references to this runtime library.
2189
- The two together form an executable program.
2190
-
2191
- ---
2192
-
2193
- ## **9.8 Simplicity of the model**
2194
-
2195
- Because every expression in `k` takes exactly one input and may return one output or be undefined,
2196
- the compiler never has to deal with variable environments, loops, or side effects.
2197
- This makes the structure of the generated program very regular:
2198
-
2199
- ```text
2200
- input → [series of calls and checks] → output or undefined
2201
- ```
2202
-
2203
- Each compiled function is independent and stateless.
2204
- This regularity is what allows `k` to map cleanly to low-level code.
2205
-
2206
- ---
2207
-
2208
- ## **9.9 Summary**
2209
-
2210
- * The compiler transforms `k` programs into executable form through a sequence of simple, deterministic steps.
2211
- * Type information and structure are resolved before code generation.
2212
- * All generated functions follow the same calling convention (`ok`, `val`).
2213
- * The runtime library provides the shared operations needed by every program.
2214
- * The entire process mirrors the formal semantics defined earlier but expresses it as concrete machine operations.
2215
-
2216
- ---
2217
-
2218
- ---
2219
-
2220
-
2221
- # Chapter 10 — From AST to Intermediate Representation
2222
-
2223
- ## **10.1 The purpose of an intermediate form**
2224
-
2225
- The abstract syntax tree (AST) of a `k` program directly reflects how the source code was written.
2226
- It is easy to read but not convenient for generating machine code.
2227
- Before code can be produced, the compiler rewrites this tree into a **simpler, regular structure** called the *intermediate representation* (IR).
2228
-
2229
- The IR is close to what the machine will execute, but it still looks like a structured program rather than raw binary instructions.
2230
- Each node in the IR corresponds to a single, well-defined operation such as *project a field*, *apply a function*, or *combine results*.
2231
-
2232
- Using an intermediate form helps in three ways:
2233
-
2234
- 1. It separates understanding of the language from machine details.
2235
- 2. It allows the compiler to verify that every function is well formed.
2236
- 3. It provides a uniform surface for optimization and for later translation into C or LLVM.
2237
-
2238
- ---
2239
-
2240
- ## **10.2 Shape of the intermediate representation**
2241
-
2242
- Each IR instruction corresponds to one semantic rule from Chapter 8.
2243
- The IR does not need to represent filters or meta-variables, since those are resolved earlier.
2244
- Only concrete actions remain.
2245
-
2246
- | Operation | Meaning |
2247
- | ----------------------------- | ---------------------------------------------------------------- |
2248
- | **PROJECT label_id** | Apply `.label` to the input value. |
2249
- | **TYPECHECK state_id** | Accept the input only if its root has the given canonical state. |
2250
- | **CALL function_id** | Invoke another function following the ABI. |
2251
- | **UNION [f₁, f₂, …]** | Try several functions in order until one succeeds. |
2252
- | **PRODUCT [l₁=f₁, l₂=f₂, …]** | Apply each function to the same input and combine results. |
2253
- | **FAIL** | Always undefined. |
2254
- | **SEQ [f₁, f₂, …]** | Apply functions in sequence (composition). |
2255
-
2256
- Each function in the program becomes a small list or tree of such IR instructions.
2257
-
2258
- ---
2259
-
2260
- ## **10.3 Example**
2261
-
2262
- Source program:
2263
-
2264
- ```k-lang
2265
- $bool = < {} true, {} false >;
2266
- neg = $bool < .true {{ } false}, .false {{ } true} > $bool;
2267
- ```
2268
-
2269
- The IR for `neg` can be written informally as:
2270
-
2271
- ```text
2272
- neg:
2273
- TYPECHECK bool
2274
- UNION [
2275
- SEQ [ PROJECT true, CONST false ],
2276
- SEQ [ PROJECT false, CONST true ]
2277
- ]
2278
- TYPECHECK bool
2279
- ```
2280
-
2281
- This IR expresses the same logic as the source:
2282
- check the type, then try the first projection `.true`, otherwise the second, and finally ensure the result is again of type `$bool`.
2283
-
2284
- ---
2285
-
2286
- ## **10.4 Relation to evaluation**
2287
-
2288
- Every IR operation corresponds to a runtime action:
2289
-
2290
- | IR operation | Runtime function or behavior |
2291
- | ------------ | --------------------------------------------------- |
2292
- | PROJECT | `k_project(input, label_id)` |
2293
- | TYPECHECK | verify `input->state == state_id` |
2294
- | CALL | call another generated function |
2295
- | UNION | sequential `if`–`else` test on success flags |
2296
- | PRODUCT | multiple subcalls + `k_make_product` |
2297
- | FAIL | return `{ ok = 0 }` immediately |
2298
- | SEQ | connect the output of one step as input to the next |
2299
-
2300
- By describing computation in these terms, the compiler can generate executable code simply by replacing each IR instruction with its runtime equivalent.
2301
-
2302
- ---
2303
-
2304
- ## **10.5 Building the IR**
2305
-
2306
- The process of converting the AST to IR follows a recursive pattern:
2307
-
2308
- 1. **Projections** become single instructions `PROJECT`.
2309
- 2. **Compositions** `(f g h)` become a `SEQ` list of their components.
2310
- 3. **Unions** `<f,g>` become a `UNION` list.
2311
- 4. **Products** `{f l₁, g l₂}` become a `PRODUCT` list with labeled entries.
2312
- 5. **Type expressions** `$T` become `TYPECHECK` instructions.
2313
- 6. **Filters** disappear; their meaning is already captured by typechecks.
2314
-
2315
- Each transformation step produces IR nodes with fixed behavior and explicit order.
2316
-
2317
- ---
2318
-
2319
- ## **10.6 Verifying the IR**
2320
-
2321
- Because k programs are defined by simple composition, a small set of checks ensures correctness:
2322
-
2323
- 1. Each `UNION` list must have at least one element.
2324
- 2. Every `PROJECT` must have a valid label.
2325
- 3. Input and output states of consecutive steps must match.
2326
- 4. A function must end in an operation that produces a result (`PROJECT`, `CALL`, or combination).
2327
-
2328
- After verification, the IR is guaranteed to represent a valid partial function according to the semantics in Chapter 8.
2329
-
2330
- ---
2331
-
2332
- ## **10.7 Intermediate form as a small language**
2333
-
2334
- The IR can be viewed as a minimal language on its own.
2335
- It has:
2336
-
2337
- * one data type (`KNode*`),
2338
- * one universal value format (trees),
2339
- * and a fixed set of operators.
2340
-
2341
- Its execution model is the same as the runtime ABI.
2342
- This means an interpreter for the IR can serve as a reference implementation of the k semantics before any machine code generation is added.
2343
-
2344
- ---
2345
-
2346
- ## **10.8 Example: product composition**
2347
-
2348
- For input function `{ .x a, .y b }`,
2349
- the IR is:
2350
-
2351
- ```text
2352
- PRODUCT [
2353
- a = PROJECT x,
2354
- b = PROJECT y
2355
- ]
2356
- ```
2357
-
2358
- The runtime sequence is:
2359
-
2360
- 1. Apply each projection.
2361
- 2. If both succeed, build a new node with two children in order `(a,b)`.
2362
-
2363
- This pattern covers all record-building operations in the language.
2364
-
2365
- ---
2366
-
2367
- ## **10.9 Why this representation is useful**
2368
-
2369
- This intermediate form provides three practical benefits:
2370
-
2371
- 1. **Clarity** – every step is explicit and local.
2372
- 2. **Verifiability** – type consistency and definedness can be checked mechanically.
2373
- 3. **Flexibility** – the same IR can be translated to many targets (LLVM IR, C, or even interpreted directly).
2374
-
2375
- Because of these advantages, most compilers for declarative languages use an intermediate form of this kind.
2376
-
2377
- ---
2378
-
2379
- ## **10.10 Summary**
2380
-
2381
- * The abstract syntax tree is rewritten into a simpler, machine-oriented form.
2382
- * Each IR instruction represents one operation defined by the `k` semantics.
2383
- * Type and filter information appear as explicit checks.
2384
- * The IR is easy to verify and to translate into executable code.
2385
- * The IR can also serve as a precise description of how `k` functions behave step by step.
2386
-
2387
- ---
2388
-
2389
- ---
2390
-
2391
-
2392
- # Chapter 11 — LLVM Basics
2393
-
2394
- ## **11.1 Purpose**
2395
-
2396
- The previous chapters described how a `k` program can be reduced to a small set of intermediate operations.
2397
- To execute these operations efficiently, the compiler must translate them into an actual machine language.
2398
- Instead of generating raw machine code directly, we use a common intermediate format called **LLVM IR** (LLVM *Intermediate Representation*).
2399
-
2400
- LLVM IR is a low-level, strongly typed language that resembles a simplified form of assembly.
2401
- It can be read and written as text, analyzed, optimized, and compiled to machine code for almost any processor.
2402
- For the compiler writer, it provides a bridge between high-level language semantics and executable programs.
2403
-
2404
- ---
2405
-
2406
- ## **11.2 Structure of an LLVM program**
2407
-
2408
- An LLVM program consists of **global definitions** and **functions**.
2409
-
2410
- * *Global definitions* describe data structures shared between functions.
2411
- In the `k` compiler, this includes the runtime metadata tables and constant nodes such as `{}`.
2412
-
2413
- * *Functions* contain sequences of instructions grouped into **basic blocks**.
2414
- A basic block is a straight-line segment of code with no branches except at the end.
2415
- Branches connect blocks, forming a control-flow graph.
2416
-
2417
- Each instruction produces a new value; values are immutable once created.
2418
- This property is called *single static assignment* (SSA).
2419
-
2420
- ---
2421
-
2422
- ## **11.3 Data types**
2423
-
2424
- LLVM provides several primitive types.
2425
- Only a few are required for the `k` compiler:
2426
-
2427
- | LLVM type | Meaning |
2428
- | --------------- | -------------------------------------------------- |
2429
- | `i1` | one-bit boolean (used for success flag) |
2430
- | `i32` | 32-bit integer (used for state IDs, indices, tags) |
2431
- | `ptr` or `%T*` | pointer to data in memory |
2432
- | `%struct {...}` | user-defined record (used for `KNode` and `KOpt`) |
2433
-
2434
- The main structures used by generated code are:
2435
-
2436
- ```llvm
2437
- %KNode = type { i32, i32, i32, [0 x %KNode*] }
2438
- %KVal = type { %KNode* }
2439
- %KOpt = type { i1, %KVal }
2440
- ```
2441
-
2442
- `%KNode` represents one tree node in memory;
2443
- `%KOpt` represents the result of a partial function, with an `ok` flag and a value pointer.
2444
-
2445
- ---
2446
-
2447
- ## **11.4 Function signatures**
2448
-
2449
- Every compiled `k` function is translated into one LLVM function with the following signature:
2450
-
2451
- ```llvm
2452
- define %KOpt @f_name(%KVal %in) { ... }
2453
- ```
2454
-
2455
- The function takes a single argument `%in`, which holds the pointer to the input value.
2456
- It returns a `%KOpt` structure containing the success flag and the resulting value.
2457
-
2458
- This mirrors the runtime ABI introduced in Chapter 7.
2459
- By using the same layout, all functions can call each other without conversions.
2460
-
2461
- ---
2462
-
2463
- ## **11.5 Control flow**
2464
-
2465
- LLVM IR uses **conditional branches** to express choices and **phi-nodes** to merge results from different paths.
2466
-
2467
- A typical pattern for union composition `<f,g>` is:
2468
-
2469
- ```llvm
2470
- %r1 = call %KOpt @f(%in)
2471
- %ok1 = extractvalue %KOpt %r1, 0
2472
- br i1 %ok1, label %success, label %try_next
2473
-
2474
- try_next:
2475
- %r2 = call %KOpt @g(%in)
2476
- br label %merge
2477
-
2478
- success:
2479
- br label %merge
2480
-
2481
- merge:
2482
- %res = phi %KOpt [ %r1, %success ], [ %r2, %try_next ]
2483
- ret %res
2484
- ```
2485
-
2486
- This code means: try `f`; if it succeeds, use that result; otherwise, try `g`.
2487
- The `phi` instruction chooses between values coming from different blocks.
2488
-
2489
- ---
2490
-
2491
- ## **11.6 Memory operations**
2492
-
2493
- Most functions in k do not modify existing nodes; they create new ones.
2494
- Node creation is implemented as calls to runtime functions:
2495
-
2496
- ```llvm
2497
- declare %KVal @k_make_product(i32 %state, i32 %n, %KVal* %children)
2498
- declare %KVal @k_make_union(i32 %state, i32 %tag, %KVal %child)
2499
- ```
2500
-
2501
- These calls allocate new immutable nodes.
2502
- The compiler provides the correct `state`, `tag`, and child references according to the canonical type.
2503
-
2504
- ---
2505
-
2506
- ## **11.7 Constants**
2507
-
2508
- Constant values such as `{{} true}` are represented as global variables in LLVM:
2509
-
2510
- ```llvm
2511
- @unit_node = constant %KNode { 1, -1, 0, [] }
2512
- @true_node = constant %KNode { 0, 0, 1, [@unit_node] }
2513
- @false_node = constant %KNode { 0, 1, 1, [@unit_node] }
2514
- ```
2515
-
2516
- Each constant is a fully formed node with fixed fields.
2517
- When a constant function is called, the compiler returns a pointer to the corresponding global node.
2518
-
2519
- ---
2520
-
2521
- ## **11.8 From IR to LLVM**
2522
-
2523
- Translating the compiler’s intermediate form (Chapter 10) into LLVM is mechanical:
2524
-
2525
- | IR operation | LLVM translation |
2526
- | ------------ | ------------------------------------------------------ |
2527
- | `PROJECT` | call `@k_project` |
2528
- | `TYPECHECK` | compare `input->state` with constant |
2529
- | `CONST` | return constant node |
2530
- | `PRODUCT` | evaluate subcalls, check flags, call `@k_make_product` |
2531
- | `UNION` | generate branching structure as above |
2532
- | `SEQ` | chain calls: feed output to next input |
2533
- | `FAIL` | return `%KOpt { 0, undef }` |
2534
-
2535
- These mappings produce correct, low-level code while remaining faithful to the language semantics.
2536
-
2537
- ---
2538
-
2539
- ## **11.9 Example**
2540
-
2541
- For the function `neg` from earlier:
2542
-
2543
- ```k-lang
2544
- $bool = < {} true, {} false >;
2545
- neg = $bool < .true {{ } false}, .false {{ } true} > $bool;
2546
- ```
2547
-
2548
- The simplified LLVM version may look as follows (comments added):
2549
-
2550
- ```llvm
2551
- define %KOpt @neg(%KVal %in) {
2552
- entry:
2553
- ; Try the .true branch
2554
- %r1 = call %KOpt @project_true(%in)
2555
- %ok1 = extractvalue %KOpt %r1, 0
2556
- br i1 %ok1, label %case_true, label %case_false
2557
-
2558
- case_true:
2559
- %res_true = insertvalue %KOpt undef, i1 1, 0
2560
- %res_true2 = insertvalue %KOpt %res_true, %KVal @false_node, 1
2561
- ret %KOpt %res_true2
2562
-
2563
- case_false:
2564
- %r2 = call %KOpt @project_false(%in)
2565
- %ok2 = extractvalue %KOpt %r2, 0
2566
- br i1 %ok2, label %case_false_valid, label %undefined
2567
-
2568
- case_false_valid:
2569
- %res_false = insertvalue %KOpt undef, i1 1, 0
2570
- %res_false2 = insertvalue %KOpt %res_false, %KVal @true_node, 1
2571
- ret %KOpt %res_false2
2572
-
2573
- undefined:
2574
- ret %KOpt { i1 0, %KVal undef }
2575
- }
2576
- ```
2577
-
2578
- While verbose, this code mirrors the evaluation rules exactly and can be optimized by standard LLVM tools.
2579
-
2580
- ---
2581
-
2582
- ## **11.10 Optimization and verification**
2583
-
2584
- LLVM provides built-in passes that simplify generated code:
2585
-
2586
- * **mem2reg** — removes unnecessary memory operations,
2587
- * **instcombine** — merges simple operations,
2588
- * **gvn** — removes redundant calculations,
2589
- * **simplifycfg** — cleans up control flow.
2590
-
2591
- The compiler can run these passes automatically to produce compact and efficient output.
2592
- LLVM also verifies that all types and values are consistent before emitting machine code.
2593
-
2594
- ---
2595
-
2596
- ## **11.11 Summary**
2597
-
2598
- * LLVM IR is a portable, low-level language ideal for code generation.
2599
- * Every `k` function becomes an LLVM function returning the pair `{ok, value}`.
2600
- * All constants, type checks, and runtime calls are represented explicitly.
2601
- * The translation from `k`’s intermediate form to LLVM is direct and systematic.
2602
- * Standard LLVM tools can then optimize and compile the result into executable machine code.
2603
-
2604
- ---
2605
-
2606
- ---
2607
-
2608
-
2609
- # Chapter 12 — Code Generation
2610
-
2611
- ## **12.1 Purpose**
2612
-
2613
- The **code generation** phase translates the intermediate representation (IR) of each k function into executable code using the LLVM format described in Chapter 11.
2614
- This translation is systematic: every IR operation corresponds to a specific sequence of LLVM instructions or calls to the runtime library.
2615
-
2616
- The compiler’s objective is to produce code that behaves exactly like the formal semantics of k—no more and no less.
2617
- Each step of the translation can therefore be viewed as a mechanical implementation of one of the rules introduced in Chapter 8.
2618
-
2619
- ---
2620
-
2621
- ## **12.2 Structure of generated functions**
2622
-
2623
- Each k function becomes a stand-alone LLVM function with the signature:
2624
-
2625
- ```llvm
2626
- define %KOpt @f_name(%KVal %in)
2627
- ```
2628
-
2629
- Internally, the function has one or more *basic blocks*.
2630
- Each block performs a small action: a projection, a check, a call, or a node construction.
2631
- Blocks are connected by explicit branches that depend on the `ok` flag of intermediate results.
2632
-
2633
- At the end of the function, exactly one `%KOpt` value is returned.
2634
-
2635
- ---
2636
-
2637
- ## **12.3 Translating core operations**
2638
-
2639
- The compiler maintains a mapping from IR operations (Chapter 10) to LLVM code patterns.
2640
-
2641
- | IR Operation | Generated Code Pattern |
2642
- | ---------------------- | ---------------------------------------------------------------------------------- |
2643
- | **PROJECT label** | `call %KOpt @k_project(%KVal %in, i32 label_id)` |
2644
- | **TYPECHECK state** | Compare `input->state` with constant `state`; if unequal, return `{0, undef}` |
2645
- | **CONST node** | Return `{1, node_pointer}` |
2646
- | **SEQ [f₁,f₂,…]** | Emit each function call in order, test `ok` after each |
2647
- | **UNION [f₁,f₂,…]** | Emit chained `if` blocks; return first successful result |
2648
- | **PRODUCT [l₁=f₁, …]** | Call all subfunctions; if all succeed, collect children and call `@k_make_product` |
2649
- | **FAIL** | Return `{0, undef}` immediately |
2650
-
2651
- These templates cover the entire language.
2652
-
2653
- ---
2654
-
2655
- ## **12.4 Example: sequential composition**
2656
-
2657
- For IR:
2658
-
2659
- ```
2660
- SEQ [ PROJECT x, CALL f, CALL g ]
2661
- ```
2662
-
2663
- the compiler emits:
2664
-
2665
- ```llvm
2666
- %r1 = call %KOpt @k_project_x(%in)
2667
- %ok1 = extractvalue %KOpt %r1, 0
2668
- br i1 %ok1, label %cont1, label %fail
2669
-
2670
- cont1:
2671
- %r2 = call %KOpt @f(%r1.val)
2672
- %ok2 = extractvalue %KOpt %r2, 0
2673
- br i1 %ok2, label %cont2, label %fail
2674
-
2675
- cont2:
2676
- %r3 = call %KOpt @g(%r2.val)
2677
- ret %KOpt %r3
2678
-
2679
- fail:
2680
- ret %KOpt { i1 0, %KVal undef }
2681
- ```
2682
-
2683
- Each block corresponds to one stage in the composition.
2684
- The use of conditional branches ensures that undefinedness propagates correctly.
2685
-
2686
- ---
2687
-
2688
- ## **12.5 Example: product composition**
2689
-
2690
- For IR:
2691
-
2692
- ```
2693
- PRODUCT [ a = PROJECT x, b = PROJECT y ]
2694
- ```
2695
-
2696
- the compiler generates code equivalent to:
2697
-
2698
- ```llvm
2699
- %ra = call %KOpt @k_project_x(%in)
2700
- %ok_a = extractvalue %KOpt %ra, 0
2701
- br i1 %ok_a, label %try_b, label %fail
2702
-
2703
- try_b:
2704
- %rb = call %KOpt @k_project_y(%in)
2705
- %ok_b = extractvalue %KOpt %rb, 0
2706
- %ok_all = and i1 %ok_a, %ok_b
2707
- br i1 %ok_all, label %build, label %fail
2708
-
2709
- build:
2710
- %children = alloca [%KVal, 2]
2711
- %p0 = getelementptr [%KVal,2], [%KVal,2]* %children, i32 0, i32 0
2712
- store %KVal (extractvalue %KOpt %ra, 1), %KVal* %p0
2713
- %p1 = getelementptr [%KVal,2], [%KVal,2]* %children, i32 0, i32 1
2714
- store %KVal (extractvalue %KOpt %rb, 1), %KVal* %p1
2715
- %res = call %KVal @k_make_product(i32 state_id, i32 2, %KVal* %children)
2716
- ret %KOpt { i1 1, %KVal %res }
2717
-
2718
- fail:
2719
- ret %KOpt { i1 0, %KVal undef }
2720
- ```
2721
-
2722
- The resulting LLVM code faithfully follows the semantic rule: the product is defined only if all components are defined.
2723
-
2724
- ---
2725
-
2726
- ## **12.6 Example: union composition**
2727
-
2728
- For IR:
2729
-
2730
- ```
2731
- UNION [ .x, .y ]
2732
- ```
2733
-
2734
- the compiler produces a branching structure:
2735
-
2736
- ```llvm
2737
- %r1 = call %KOpt @k_project_x(%in)
2738
- %ok1 = extractvalue %KOpt %r1, 0
2739
- br i1 %ok1, label %merge, label %try_next
2740
-
2741
- try_next:
2742
- %r2 = call %KOpt @k_project_y(%in)
2743
- br label %merge
2744
-
2745
- merge:
2746
- %res = phi %KOpt [ %r1, %ok1 ], [ %r2, %try_next ]
2747
- ret %KOpt %res
2748
- ```
2749
-
2750
- This pattern ensures that the first defined branch wins, as specified by the semantics.
2751
-
2752
- ---
2753
-
2754
- ## **12.7 Managing constants and types**
2755
-
2756
- Whenever an expression references a constant value or a type restriction:
2757
-
2758
- * **Constant values** are replaced by pointers to global constant nodes.
2759
- * **Type expressions `$T`** are compiled to a `TYPECHECK` instruction:
2760
- if `input->state != T_state`, the function returns undefined.
2761
-
2762
- This approach removes all high-level notation before reaching machine level.
2763
-
2764
- ---
2765
-
2766
- ## **12.8 Optimizing during generation**
2767
-
2768
- Because the structure of k is very regular, many optimizations can be applied while generating code:
2769
-
2770
- * **Eliminate redundant checks** — consecutive type checks on the same state can be merged.
2771
- * **Inline small functions** — short constant or projection functions can be inserted directly into their callers.
2772
- * **Remove unreachable branches** — if a type restriction guarantees that one union branch can never apply, it is omitted.
2773
-
2774
- Such simplifications can be done by the compiler itself or delegated to LLVM’s optimization passes.
2775
-
2776
- ---
2777
-
2778
- ## **12.9 Interaction with the runtime**
2779
-
2780
- All allocation and field access are performed through runtime calls.
2781
- The compiler never manipulates pointers directly beyond passing them to these helpers.
2782
- This design ensures that compiled code is independent of the internal memory layout.
2783
-
2784
- The generated functions thus resemble small graphs of calls and conditionals built entirely on top of the fixed ABI.
2785
-
2786
- ---
2787
-
2788
- ## **12.10 Linking and verification**
2789
-
2790
- After all functions are emitted, the compiler writes a single LLVM module containing:
2791
-
2792
- * all generated functions,
2793
- * declarations of runtime primitives (`k_project`, `k_make_product`, etc.),
2794
- * and constant nodes representing literal values.
2795
-
2796
- The LLVM verifier then checks that:
2797
-
2798
- * every function returns a correctly typed `%KOpt`,
2799
- * all control-flow paths end with a `ret`,
2800
- * and all global references are defined.
2801
-
2802
- Only verified code is passed to the optimizer or the machine-code generator.
2803
-
2804
- ---
2805
-
2806
- ## **12.11 Summary**
2807
-
2808
- * Code generation turns IR into real executable instructions.
2809
- * Each k function maps to an LLVM function returning `{ok, value}`.
2810
- * Projection, composition, union, and product translate into small, fixed code patterns.
2811
- * Type restrictions become simple integer comparisons.
2812
- * The compiler can apply immediate simplifications during translation.
2813
- * The final output is a verified LLVM module ready for optimization and linking.
2814
-
2815
- ---
2816
- ---
2817
-
2818
-
2819
- # Chapter 13 — Linking and Execution
2820
-
2821
- ## **13.1 Purpose**
2822
-
2823
- The last stage of compilation connects the generated code with the runtime library and produces an executable program.
2824
- This process is called **linking**.
2825
- Once linked, the program can be run like any other compiled program: it reads input values, executes the compiled k functions, and returns a result.
2826
-
2827
- The linking and execution stages ensure that the compiled code, the runtime library, and the data it manipulates all agree on structure and conventions.
2828
-
2829
- ---
2830
-
2831
- ## **13.2 The runtime library**
2832
-
2833
- All compiled k programs depend on a small runtime component, often distributed as a file named `krt.c` or `krt.o`.
2834
- This library provides:
2835
-
2836
- | Function | Purpose |
2837
- | ------------------------------------ | -------------------------------------------------- |
2838
- | `k_make_product(state, n, children)` | Allocate a product node with `n` fields. |
2839
- | `k_make_union(state, tag, child)` | Allocate a union node with a single variant. |
2840
- | `k_project(input, label_id)` | Perform projection `.label`. |
2841
- | `k_fail()` | Return a predefined undefined result `{ ok = 0 }`. |
2842
- | `k_unit()` | Return the constant unit value `{}`. |
2843
-
2844
- These functions implement the same concepts as the semantic rules described earlier, but at the level of actual memory operations.
2845
- They are compiled once in a low-level language such as C and reused by all generated programs.
2846
-
2847
- ---
2848
-
2849
- ## **13.3 Metadata tables**
2850
-
2851
- Along with the runtime functions, the compiler produces **metadata** describing each canonical type used in the program:
2852
-
2853
- * `state_kind[state]` — `0` for product, `1` for union
2854
- * `state_arity[state]` — number of children
2855
- * `field_index[state][label_id]` — position of field `label_id` within product
2856
- * `variant_index[state][label_id]` — tag number for variant `label_id` within union
2857
-
2858
- These tables are stored as constant arrays in the compiled module.
2859
- Runtime functions use them to interpret and construct nodes correctly without needing any dynamic reflection.
2860
-
2861
- ---
2862
-
2863
- ## **13.4 Linking**
2864
-
2865
- The generated LLVM file is compiled and linked with the runtime library in two simple steps:
2866
-
2867
- ```bash
2868
- clang -O2 -c krt.c -o krt.o
2869
- clang -O2 program.ll krt.o -o program
2870
- ```
2871
-
2872
- The first command compiles the runtime once.
2873
- The second command combines the runtime with the program’s own functions and constant definitions.
2874
-
2875
- The result is a native executable file named `program`.
2876
- When run, it behaves exactly like the original k program.
2877
-
2878
- ---
2879
-
2880
- ## **13.5 Initialization**
2881
-
2882
- When the executable starts, it performs a few basic steps:
2883
-
2884
- 1. Allocate an **arena** for storing newly created nodes.
2885
- 2. Initialize constant nodes such as the unit `{}` and any user-defined constants.
2886
- 3. Optionally, prepare a table of function pointers for fast invocation.
2887
-
2888
- After initialization, the main compiled function can be called directly.
2889
-
2890
- ---
2891
-
2892
- ## **13.6 Executing a compiled function**
2893
-
2894
- Every compiled function has the signature:
2895
-
2896
- ```c
2897
- struct KOpt function(struct KNode* input);
2898
- ```
2899
-
2900
- To run a program, a caller must supply an input value tree in the expected canonical form.
2901
- The value is typically created by another function or loaded from serialized data.
2902
-
2903
- For example, to execute a function `neg` expecting a `$bool`:
2904
-
2905
- ```c
2906
- struct KOpt result = neg(&true_node);
2907
- if (result.ok)
2908
- print_value(result.val);
2909
- else
2910
- printf("undefined\n");
2911
- ```
2912
-
2913
- If the input is `{ {} true }`, the output will be `{ {} false }`.
2914
-
2915
- ---
2916
-
2917
- ## **13.7 Input and output conventions**
2918
-
2919
- At the machine level, a “value” is always represented by a pointer to a `KNode`.
2920
- Each compiled function expects exactly one such pointer.
2921
- When a function returns, its output pointer may refer to an existing constant node, a new node allocated in the arena, or nothing at all if undefined.
2922
-
2923
- There is no special I/O system in k itself; printing or reading values is the responsibility of the host environment.
2924
- In practice, programs are tested by calling compiled functions from C or Python and inspecting their returned structures.
2925
-
2926
- ---
2927
-
2928
- ## **13.8 Debugging and inspection**
2929
-
2930
- Because both the generated code and the runtime library are deterministic and memory-safe, debugging usually consists of examining node contents.
2931
-
2932
- Two simple runtime utilities are useful:
2933
-
2934
- * `print_value(node)` — prints a tree in human-readable form.
2935
- * `print_type(state)` — prints the canonical description of a type state.
2936
-
2937
- They allow the user to confirm that compiled functions construct the correct tree shapes.
2938
-
2939
- ---
2940
-
2941
- ## **13.9 Verification**
2942
-
2943
- After linking, the compiler can run a *self-verification pass*:
2944
-
2945
- 1. Construct small sample inputs for each exported function.
2946
- 2. Execute the compiled function.
2947
- 3. Compare results with the interpreter’s result using structural equality.
2948
-
2949
- If all match, the compiler’s translation is confirmed correct for those test cases.
2950
- This method is especially valuable during compiler development and textbook exercises.
2951
-
2952
- ---
2953
-
2954
- ## **13.10 Example: complete run**
2955
-
2956
- Example program:
2957
-
2958
- ```
2959
- $bool = < {} true, {} false >;
2960
- neg = $bool < .true {{ } false}, .false {{ } true} > $bool;
2961
- ```
2962
-
2963
- Steps:
2964
-
2965
- 1. Compile to `neg.ll`.
2966
- 2. Link with `krt.o`.
2967
- 3. Run a small C driver that calls `neg`.
2968
-
2969
- Output:
2970
-
2971
- ```
2972
- input = {{ } true}
2973
- result = {{ } false}
2974
- ```
2975
-
2976
- If `input` is `{}` or any non-`$bool` value, the function is undefined and prints “undefined”.
2977
-
2978
- ---
2979
-
2980
- ## **13.11 Summary**
2981
-
2982
- * Linking combines generated LLVM code with the shared runtime library.
2983
- * The runtime provides allocation, projection, and constant handling.
2984
- * Metadata tables describe type structure to the runtime.
2985
- * Every compiled function follows the uniform calling convention `KOpt f(KNode*)`.
2986
- * Execution produces immutable tree values exactly matching the semantics of k.
2987
- * Verification against the interpreter ensures correctness of the translation.
2988
-
2989
- ---
2990
- ---
2991
-
2992
-
2993
- # Chapter 14 — Canonical Serialization
2994
-
2995
- ## **14.1 Purpose**
2996
-
2997
- Serialization is the process of converting a value in memory into a compact sequence of bits or bytes that can be stored or transmitted.
2998
- For the k language, serialization serves an additional goal: it defines a **canonical representation** of each value that is unique and independent of the machine on which it was produced.
2999
-
3000
- A canonical encoding allows values to be compared, hashed, or stored in a registry in a reproducible way.
3001
- Two values that are structurally identical will always produce the same bit sequence.
3002
-
3003
- ---
3004
-
3005
- ## **14.2 Canonical type information**
3006
-
3007
- Serialization always depends on the **canonical form of the type**.
3008
- A type in canonical form is a finite tree automaton (Chapter 3) that describes the structure of all its values.
3009
- Because this form is unique, every possible value of the type can be encoded deterministically.
3010
-
3011
- Before any value is serialized, the compiler or runtime must know:
3012
-
3013
- * the list of states (C0, C1, …),
3014
- * for each state, whether it is a product or a union,
3015
- * the number of possible transitions, and
3016
- * for unions, the order of their variants.
3017
-
3018
- This information acts as the grammar from which all bit sequences are derived.
3019
-
3020
- ---
3021
-
3022
- ## **14.3 Encoding principle**
3023
-
3024
- Each node in a value tree corresponds to one of the type’s states.
3025
-
3026
- * **Product node** – emits no bits; the encoder simply serializes each child in canonical field order.
3027
- * **Union node** – emits a small binary code that identifies which variant is used, then serializes its single child.
3028
- * **Unit node** (empty product) – emits nothing; it has no children.
3029
- * **Empty union** – cannot be encoded because it has no possible values.
3030
-
3031
- Thus, the sequence of bits records exactly which union branches were taken while descending the tree.
3032
-
3033
- ---
3034
-
3035
- ## **14.4 Fixed-length codes per state**
3036
-
3037
- For simplicity, each union state uses fixed-length binary codes of equal size.
3038
-
3039
- If a union has *m* variants, then
3040
-
3041
- ```
3042
- k = ceil(log2(m))
3043
- ```
3044
-
3045
- bits are needed.
3046
- Each variant is numbered in canonical order from 0 to m − 1 and represented by the binary form of its index.
3047
-
3048
- Products and units use k = 0 and emit no bits.
3049
-
3050
- This rule allows the decoder to know, from the type alone, how many bits to read at each step.
3051
-
3052
- ---
3053
-
3054
- ## **14.5 Example: natural numbers**
3055
-
3056
- For the type
3057
-
3058
- ```
3059
- $bnat = < bnat 0, bnat 1, {} _ >;
3060
- ```
3061
-
3062
- the canonical form has three transitions from state C0.
3063
- Therefore, `k = ceil(log2(3)) = 2` bits are required per union node.
3064
-
3065
- Assigning codes in order:
3066
-
3067
- | Rule | Code |
3068
- | -------------- | ---- |
3069
- | C0 → {C0 "0"} | `00` |
3070
- | C0 → {C0 "1"} | `01` |
3071
- | C0 → {C1 "_" } | `10` |
3072
-
3073
- The value `{ {{ {} _ } 1 } 0 }` is encoded by concatenating the codes of the transitions chosen during a leftmost derivation:
3074
-
3075
- ```
3076
- 00 → first C0
3077
- 01 → next C0
3078
- 10 → final C0
3079
- ```
3080
-
3081
- Resulting bit sequence: `000110`.
3082
-
3083
- ---
3084
-
3085
- ## **14.6 Decoding**
3086
-
3087
- Decoding is deterministic and mirrors the encoding process:
3088
-
3089
- 1. Start at the root state (C0).
3090
- 2. If the current state is a union, read k bits to choose a rule.
3091
- 3. Create the corresponding node, then recursively decode its child or children.
3092
- 4. For products, decode all children in order; for units, stop.
3093
-
3094
- The decoder stops when the entire tree has been reconstructed and all bits have been consumed.
3095
-
3096
- Because every union code is of fixed length, the decoding process requires no backtracking and can be implemented as a simple loop.
3097
-
3098
- ---
3099
-
3100
- ## **14.7 Folding repeated subtrees**
3101
-
3102
- Many values contain repeated substructures.
3103
- To avoid serializing the same tree several times, identical subtrees can be *folded* into a **directed acyclic graph (DAG)** before encoding.
3104
-
3105
- Each unique node is assigned an identifier.
3106
- When the encoder encounters a repeated node, it emits a reference to its identifier instead of encoding it again.
3107
-
3108
- During decoding, the identifier is resolved to the corresponding subtree, reconstructing the shared structure.
3109
-
3110
- Folding is optional; it saves space without changing the logical value.
3111
-
3112
- ---
3113
-
3114
- ## **14.8 Stream format**
3115
-
3116
- A minimal canonical bitstream consists of:
3117
-
3118
- 1. **Header** – type hash (e.g., 8 bytes) identifying the canonical type.
3119
- 2. **Node count** – integer *N* if DAG encoding is used.
3120
- 3. **Encoded data** – concatenation of all fixed-length union codes and, if folded, node references.
3121
-
3122
- If two systems share the same canonical type table, the bitstream alone is enough to reconstruct the original value.
3123
-
3124
- ---
3125
-
3126
- ## **14.9 Implementation sketch**
3127
-
3128
- A compact encoder can be written as follows:
3129
-
3130
- ```
3131
- procedure encode(node):
3132
- if node.arity == 0:
3133
- return
3134
- if node.arity == 1:
3135
- write_bits(code_for(node.state, node.tag))
3136
- encode(node.child[0])
3137
- else:
3138
- for each child in node.children:
3139
- encode(child)
3140
- ```
3141
-
3142
- The corresponding decoder reverses the process.
3143
- Both procedures require only the canonical type tables and simple bit operations.
3144
-
3145
- ---
3146
-
3147
- ## **14.10 Determinism and equality**
3148
-
3149
- Because encoding is fully determined by the canonical type and field order, two equal values always produce identical bit sequences.
3150
- Conversely, decoding the same bit sequence always yields the same tree.
3151
-
3152
- This property allows equality comparison by direct byte comparison of serialized forms, without traversing the trees in memory.
3153
-
3154
- ---
3155
-
3156
- ## **14.11 Summary**
3157
-
3158
- * Every type’s canonical automaton defines a unique bit-level grammar for its values.
3159
- * Products emit no bits; unions emit fixed-length variant codes.
3160
- * The number of bits required for each state depends only on the number of variants in that state.
3161
- * Optional DAG folding eliminates repeated subtrees.
3162
- * The resulting bitstream is compact, deterministic, and machine-independent.
3163
-
3164
- ---
3165
- ---
3166
-
3167
-
3168
- # Chapter 15 — Optimization and Folding
3169
-
3170
- ## **15.1 Purpose**
3171
-
3172
- Optimization in the k compiler is the process of simplifying functions without changing their meaning.
3173
- The goal is to make the generated code smaller, faster, and easier to verify.
3174
- Because k is purely functional and has no side effects, optimizations are safe whenever the simplified function is equivalent to the original one.
3175
-
3176
- This chapter describes a few basic optimizations that follow directly from the semantics introduced earlier.
3177
-
3178
- ---
3179
-
3180
- ## **15.2 Constant folding**
3181
-
3182
- A **constant** is a function that ignores its input and always produces the same value.
3183
- If a composition of functions can be evaluated entirely at compile time, it can be replaced by a single constant function.
3184
-
3185
- Example:
3186
-
3187
- ```
3188
- true_bool = {{ } true} $bool;
3189
- not_true = (true_bool neg);
3190
- ```
3191
-
3192
- The compiler observes that `(true_bool neg)` always yields `false_bool`, so it replaces it with a constant:
3193
-
3194
- ```
3195
- not_true = {{ } false} $bool;
3196
- ```
3197
-
3198
- Constant folding removes unnecessary runtime computation.
3199
-
3200
- ---
3201
-
3202
- ## **15.3 Type-based simplifications**
3203
-
3204
- Since type expressions act as identity functions defined only on specific values, they can be eliminated when redundant:
3205
-
3206
- ```
3207
- $bool $bool f → $bool f
3208
- ```
3209
-
3210
- If a function’s input or output type is already known from context, repeated checks can be omitted.
3211
- The compiler ensures that at least one valid check remains to preserve correctness.
3212
-
3213
- ---
3214
-
3215
- ## **15.4 Inlining**
3216
-
3217
- Inlining replaces a call to a small function by its body.
3218
- It avoids the overhead of a function call and often exposes further simplifications.
3219
-
3220
- Example:
3221
-
3222
- ```
3223
- id = ();
3224
- f = (id g);
3225
- ```
3226
-
3227
- Because `id` is the identity function, `(id g)` is equivalent to `g`.
3228
- Inlining removes the useless call.
3229
-
3230
- For larger functions, inlining is applied selectively—only when it shortens the resulting code or eliminates intermediate values.
3231
-
3232
- ---
3233
-
3234
- ## **15.5 Dead-branch elimination**
3235
-
3236
- In a union composition `<f, g>`, if type analysis shows that the input type can only satisfy the first branch, the second branch can never be defined and is removed.
3237
-
3238
- Example:
3239
-
3240
- ```
3241
- $bool = < {} true, {} false >;
3242
- f = $bool < .true {{ } false} >;
3243
- ```
3244
-
3245
- Here, since `$bool` restricts the input to only the two variants `true` and `false`, and the second variant `.false` is not handled, the compiler infers that `f` is undefined for `.false` and simplifies the code accordingly.
3246
-
3247
- Dead-branch elimination keeps the generated control flow minimal.
3248
-
3249
- ---
3250
-
3251
- ## **15.6 Common subexpression elimination**
3252
-
3253
- If the same partial function is applied multiple times to the same input, the result will always be the same.
3254
- The compiler can compute it once and reuse the result.
3255
-
3256
- Example:
3257
-
3258
- ```
3259
- { .x .y, .x .z }
3260
- ```
3261
-
3262
- Both fields start with the projection `.x`.
3263
- The compiler computes `.x` once, stores the result, and reuses it for `.y` and `.z`.
3264
- This optimization reduces repeated traversal of the same input structure.
3265
-
3266
- ---
3267
-
3268
- ## **15.7 Canonical folding**
3269
-
3270
- During evaluation, many values share identical subtrees.
3271
- A **folding pass** replaces identical subtrees by shared nodes, forming a minimal DAG (directed acyclic graph).
3272
-
3273
- At compile time, folding may also apply to constant expressions.
3274
- If two constant subtrees are identical, they are merged and stored as a single global node.
3275
-
3276
- This process ensures that structural equality corresponds to pointer equality: two values are identical if they share the same root node.
3277
-
3278
- ---
3279
-
3280
- ## **15.8 Function canonicalization**
3281
-
3282
- Every function can be normalized by expanding all type aliases, inlining trivial definitions, and folding identical subfunctions.
3283
- The normalized form of a function depends only on its semantics, not on the way it was written.
3284
-
3285
- A stable hash computed from this canonical representation serves as a permanent identifier for the function, just as type hashes identify canonical types.
3286
-
3287
- This property is essential for the schema registry described in the next chapter.
3288
-
3289
- ---
3290
-
3291
- ## **15.9 Runtime simplifications**
3292
-
3293
- At runtime, additional micro-optimizations are possible:
3294
-
3295
- * **Arena reuse:** allocate nodes in a memory region that is cleared after each function call.
3296
- * **Hash-consing:** maintain a small table of recently created nodes to reuse identical ones automatically.
3297
- * **Shortcut constants:** return pointers to global constants instead of allocating new copies for unit or boolean values.
3298
-
3299
- Such optimizations reduce memory usage without changing program behavior.
3300
-
3301
- ---
3302
-
3303
- ## **15.10 Equivalence and correctness**
3304
-
3305
- Each optimization must preserve *semantic equivalence*: for every input, the optimized function must be defined for exactly the same values and must return the same result when defined.
3306
-
3307
- Because k’s semantics are purely functional, equivalence can be tested mechanically by comparing evaluation results on all finite inputs of a small type, or by structural reasoning when types are infinite.
3308
-
3309
- ---
3310
-
3311
- ## **15.11 Summary**
3312
-
3313
- * Optimization in k relies on algebraic properties of pure functions.
3314
- * Common transformations include constant folding, inlining, dead-branch elimination, and subexpression reuse.
3315
- * Folding shared subtrees yields compact DAG representations of values.
3316
- * Canonical function forms allow stable hashing and reproducible compilation results.
3317
- * All optimizations preserve exact semantic behavior.
3318
-
3319
- ---
3320
- ---
3321
-
3322
-
3323
- # Chapter 16 — Toward a Universal Schema Registry
3324
-
3325
- ## **16.1 Motivation**
3326
-
3327
- Every canonical type and function in k has a unique structural form and a stable hash.
3328
- These hashes can serve as global identifiers.
3329
- A registry that maps such identifiers to definitions allows programs and systems to exchange schemas and functions safely and reproducibly.
3330
-
3331
- ---
3332
-
3333
- ## **16.2 Basic structure**
3334
-
3335
- A schema registry stores entries of two kinds:
3336
-
3337
- | Kind | Example content |
3338
- | ------------ | ------------------------------------------------------------- |
3339
- | **Type** | canonical automaton text for a type hash |
3340
- | **Function** | canonical IR or serialized representation for a function hash |
3341
-
3342
- Each entry is immutable.
3343
- New versions are added as new hashes.
3344
-
3345
- ---
3346
-
3347
- ## **16.3 Key operations**
3348
-
3349
- 1. **Lookup by hash** — return the canonical definition.
3350
- 2. **Lookup by shape** — search for functions with given input/output type hashes.
3351
- 3. **Verification** — ensure that two parties share the same canonical form before exchanging values.
3352
-
3353
- These operations can be implemented as simple key–value queries.
3354
-
3355
- ---
3356
-
3357
- ## **16.4 Example format**
3358
-
3359
- A minimal JSON entry:
3360
-
3361
- ```json
3362
- {
3363
- "hash": "C0ABCDEF",
3364
- "kind": "type",
3365
- "definition": "$C0=<C0\"0\",C0\"1\",C1\"_\">;$C1={};"
3366
- }
3367
- ```
3368
-
3369
- or for a function:
3370
-
3371
- ```json
3372
- {
3373
- "hash": "F7B1A2",
3374
- "kind": "function",
3375
- "input": "C0ABCDEF",
3376
- "output": "C9FFF1",
3377
- "ir": "SEQ [PROJECT x, CONST true]"
3378
- }
3379
- ```
3380
-
3381
- ---
3382
-
3383
- ## **16.5 Uses**
3384
-
3385
- * Preventing schema mismatches in distributed systems.
3386
- * Deduplicating identical definitions across projects.
3387
- * Enabling reproducible builds where all types and functions are referenced by hash.
3388
-
3389
- ---
3390
-
3391
- ## **16.6 Summary**
3392
-
3393
- * Canonical types and functions can be globally identified by stable hashes.
3394
- * A simple registry provides lookup and verification of these definitions.
3395
- * Such registries make k programs portable, self-describing, and safe to share between systems.
3396
-
3397
- ---
3398
- ---
3399
-
3400
-
3401
- # Appendix A — Implementing a Prototype Compiler in Python
3402
- ## **A.1 Purpose**
3403
-
3404
- This appendix outlines how to implement a simple, working prototype of the k compiler in Python.
3405
- The goal is to let the reader experiment with parsing, type normalization, and LLVM code generation using only standard tools and a few small libraries.
3406
- The prototype does not need to support optimization or a full runtime—only the basic translation pipeline.
3407
-
3408
- ---
3409
-
3410
- ## **A.2 Required environment**
3411
-
3412
- Install:
3413
-
3414
- ```bash
3415
- python3 -m venv kenv
3416
- source kenv/bin/activate
3417
- pip install llvmlite
3418
- ```
3419
-
3420
- `llvmlite` provides an interface to LLVM for code generation.
3421
- No additional dependencies are required.
3422
-
3423
- ---
3424
-
3425
- ## **A.3 Recommended directory structure**
3426
-
3427
- ```
3428
- k-compiler/
3429
- ├── main.py # command-line driver
3430
- ├── parser.py # converts source text to AST
3431
- ├── types.py # canonical type construction
3432
- ├── normalize.py # filter resolution and typing
3433
- ├── ir.py # intermediate representation
3434
- ├── codegen.py # translation from IR to LLVM
3435
- ├── runtime.ll # minimal runtime in LLVM
3436
- └── examples/ # sample k programs
3437
- ```
3438
-
3439
- Each module is small and focused on one task.
3440
-
3441
- ---
3442
-
3443
- ## **A.4 The AST**
3444
-
3445
- Represent the language syntax with Python classes:
3446
-
3447
- ```python
3448
- class Expr: pass
3449
-
3450
- class Composition(Expr):
3451
- def __init__(self, parts): self.parts = parts
3452
-
3453
- class Product(Expr):
3454
- def __init__(self, fields): self.fields = fields # [(label, expr), ...]
3455
-
3456
- class Union(Expr):
3457
- def __init__(self, options): self.options = options # [expr, ...]
3458
-
3459
- class Projection(Expr):
3460
- def __init__(self, label): self.label = label
3461
-
3462
- class Constant(Expr):
3463
- def __init__(self, name): self.name = name
3464
- ```
3465
-
3466
- The parser builds these objects from source text.
3467
-
3468
- ---
3469
-
3470
- ## **A.5 Type normalization**
3471
-
3472
- Maintain canonical type states as small Python objects:
3473
-
3474
- ```python
3475
- class TypeState:
3476
- def __init__(self, kind, transitions):
3477
- self.kind = kind # 'product' or 'union'
3478
- self.transitions = transitions # [(label, next_state)]
3479
- ```
3480
-
3481
- Normalization expands type aliases and assigns numeric state IDs (`C0`, `C1`, …).
3482
- For a prototype, a simple structural hash (using `json.dumps`) can serve as the canonical identifier.
3483
-
3484
- ---
3485
-
3486
- ## **A.6 Intermediate representation**
3487
-
3488
- Represent IR instructions as simple tuples or small classes:
3489
-
3490
- ```python
3491
- class Project: def __init__(self, label): self.label = label
3492
- class Const: def __init__(self, node): self.node = node
3493
- class Seq: def __init__(self, parts): self.parts = parts
3494
- class UnionIR: def __init__(self, parts): self.parts = parts
3495
- class ProductIR:def __init__(self, fields):self.fields = fields
3496
- ```
3497
-
3498
- A recursive function converts an AST expression into IR following Chapter 10.
3499
-
3500
- ---
3501
-
3502
- ## **A.7 Generating LLVM code**
3503
-
3504
- Using `llvmlite.ir`, create one LLVM function per compiled function:
3505
-
3506
- ```python
3507
- from llvmlite import ir
3508
-
3509
- mod = ir.Module(name="k")
3510
-
3511
- # LLVM type definitions
3512
- KNodePtr = ir.PointerType(ir.IntType(8))
3513
- KVal = ir.LiteralStructType([KNodePtr])
3514
- KOpt = ir.LiteralStructType([ir.IntType(1), KVal])
3515
-
3516
- def emit_function(name, ir_expr):
3517
- fn_type = ir.FunctionType(KOpt, [KVal])
3518
- fn = ir.Function(mod, fn_type, name=name)
3519
- block = fn.append_basic_block('entry')
3520
- builder = ir.IRBuilder(block)
3521
- # translate ir_expr recursively into builder calls
3522
- builder.ret(ir.Constant(KOpt, (ir.Constant(ir.IntType(1), 0),
3523
- ir.Constant(KVal, ir.Constant(KNodePtr, None)))))
3524
- ```
3525
-
3526
- The translation patterns follow those described in Chapter 12.
3527
- Each IR node is emitted as a call or conditional block using `builder.call`, `builder.if_then`, and so on.
3528
-
3529
- ---
3530
-
3531
- ## **A.8 Minimal runtime**
3532
-
3533
- Write a minimal `runtime.ll` that defines stubs for:
3534
-
3535
- ```llvm
3536
- declare %KOpt @k_project(%KVal %v, i32 %label)
3537
- declare %KVal @k_make_product(i32 %state, i32 %n, %KVal* %children)
3538
- declare %KVal @k_make_union(i32 %state, i32 %tag, %KVal %child)
3539
- ```
3540
-
3541
- These can simply return placeholder constants for testing.
3542
- Later, they can be replaced with the real runtime implemented in C.
3543
-
3544
- ---
3545
-
3546
- ## **A.9 Putting it together**
3547
-
3548
- A small driver script (`main.py`) can compile a file and emit LLVM text:
3549
-
3550
- ```python
3551
- import parser, normalize, ir, codegen
3552
-
3553
- def compile_file(path):
3554
- src = open(path).read()
3555
- ast = parser.parse(src)
3556
- typed = normalize.process(ast)
3557
- ir_tree = ir.from_ast(typed)
3558
- llvm_module = codegen.emit(ir_tree)
3559
- print(str(llvm_module))
3560
- ```
3561
-
3562
- Run:
3563
-
3564
- ```bash
3565
- python main.py examples/neg.k > neg.ll
3566
- clang -O2 neg.ll runtime.ll -o neg
3567
- ```
3568
-
3569
- ---
3570
-
3571
- ## **A.10 Suggested extensions**
3572
-
3573
- 1. Implement the runtime functions in Python for quick testing.
3574
- 2. Add a textual REPL that reads a k expression and prints its IR.
3575
- 3. Integrate a simple interpreter to compare interpreter and compiled results.
3576
- 4. Use hashing of canonical forms to name generated LLVM functions.
3577
-
3578
- ---
3579
-
3580
- ## **A.11 Summary**
3581
-
3582
- A prototype compiler can be realized in fewer than a thousand lines of Python.
3583
- It can parse k definitions, construct canonical types, translate expressions into IR, and generate valid LLVM code.
3584
- Such a prototype is enough to experiment with all ideas presented in this textbook and to verify the semantics of the k language in practice.
3585
-
3586
- ---
3587
-
3588
-
3589
- # Appendix B — Incorporating External Predefined Types and Functions
3590
- ## **B.1 Purpose**
3591
-
3592
- The k language is intentionally minimal.
3593
- Nevertheless, real systems often need to interact with data types and operations defined outside of k—for example, numeric values, text, or platform-specific constants.
3594
- This appendix explains how external predefined types and functions can be introduced into a k compiler without changing the language itself.
3595
-
3596
- ---
3597
-
3598
- ## **B.2 External types**
3599
-
3600
- An **external type** is a canonical type that is *not* expressed through k's own product-and-union syntax but is known to the compiler through registration.
3601
-
3602
- Each external type has:
3603
-
3604
- | Field | Description |
3605
- | --------------------- | ---------------------------------------------------------------------------- |
3606
- | **Name** | symbolic name (e.g., `$int`, `$string`) |
3607
- | **Hash** | unique stable identifier, just like canonical types |
3608
- | **Runtime kind** | how the value is stored in memory (pointer, immediate integer, etc.) |
3609
- | **Adapter functions** | conversion between the external representation and the standard `KNode` tree |
3610
-
3611
- Example registry entry:
3612
-
3613
- ```json
3614
- {
3615
- "hash": "E001",
3616
- "kind": "type",
3617
- "external": true,
3618
- "name": "$int",
3619
- "runtime_kind": "primitive_i64"
3620
- }
3621
- ```
3622
-
3623
- The compiler treats such a type as an atomic node, represented internally as a single `KNode` with `arity = 0` but tagged as *external*.
3624
-
3625
- ---
3626
-
3627
- ## **B.3 External constructors**
3628
-
3629
- External types may provide special constructors or constants.
3630
- For example, `$int` might include predefined constant functions:
3631
-
3632
- ```
3633
- zero = {} $int;
3634
- one = {} $int;
3635
- ```
3636
-
3637
- At compile time these appear as constant functions returning fixed external nodes provided by the runtime.
3638
-
3639
- ---
3640
-
3641
- ## **B.4 External functions**
3642
-
3643
- External functions are partial functions implemented outside of k but declared inside a k program.
3644
- They have known input and output types, possibly external.
3645
-
3646
- Declaration syntax:
3647
-
3648
- ```
3649
- extern add : $int $int → $int;
3650
- extern less : $int $int → $bool;
3651
- ```
3652
-
3653
- At compile time the compiler records the signature and associates it with a runtime symbol name such as `k_ext_add`.
3654
-
3655
- When generating code, each external call becomes a standard function call following the ABI:
3656
-
3657
- ```llvm
3658
- declare %KOpt @k_ext_add(%KVal %a, %KVal %b)
3659
- ```
3660
-
3661
- The runtime library provides these implementations in a native language (C, C++, Rust, etc.).
3662
-
3663
- ---
3664
-
3665
- ## **B.5 Integration with canonical types**
3666
-
3667
- External types are integrated into the canonical system as *leaf states*.
3668
- They can appear as components in products or unions just like built-in ones.
3669
-
3670
- Example:
3671
-
3672
- ```
3673
- $point = { $int x, $int y };
3674
- ```
3675
-
3676
- Canonical form:
3677
-
3678
- ```
3679
- $C0 = { C1 "x", C1 "y" };
3680
- $C1 = external $int;
3681
- ```
3682
-
3683
- This allows normal serialization and type comparison; the external leaf is treated as opaque but identified by its hash.
3684
-
3685
- ---
3686
-
3687
- ## **B.6 Serialization and external values**
3688
-
3689
- During serialization, an external node is encoded by:
3690
-
3691
- 1. Its type hash (to indicate which external kind it represents).
3692
- 2. A binary blob produced by a runtime-supplied encoder for that type.
3693
-
3694
- Deserialization reverses the process using the corresponding decoder.
3695
- All external codecs must be deterministic and version-stable to preserve canonical equality.
3696
-
3697
- ---
3698
-
3699
- ## **B.7 Example**
3700
-
3701
- Suppose we introduce `$int` and external function `add`:
3702
-
3703
- ```
3704
- $pair_int = { $int a, $int b };
3705
- sum = { .a x, .b y } (add x y);
3706
- ```
3707
-
3708
- The compiler generates code that:
3709
-
3710
- * projects fields `.a` and `.b`,
3711
- * passes them as `KVal` pointers to `@k_ext_add`,
3712
- * and returns the result as a new `$int` node.
3713
-
3714
- All logic outside arithmetic remains standard k code.
3715
-
3716
- ---
3717
-
3718
- ## **B.8 Practical implementation**
3719
-
3720
- In a Python prototype:
3721
-
3722
- ```python
3723
- EXTERNAL_TYPES = {
3724
- "$int": {"hash": "E001", "kind": "external", "runtime_kind": "primitive_i64"}
3725
- }
3726
-
3727
- EXTERNAL_FUNCS = {
3728
- "add": {"inputs": ["$int", "$int"], "output": "$int", "symbol": "k_ext_add"},
3729
- "less": {"inputs": ["$int", "$int"], "output": "$bool", "symbol": "k_ext_less"}
3730
- }
3731
- ```
3732
-
3733
- During code generation, when a call refers to an external function, the compiler emits a call to the symbol given in the table.
3734
-
3735
- ---
3736
-
3737
- ## **B.9 Summary**
3738
-
3739
- * External predefined types extend k without altering its core semantics.
3740
- * They are registered with hashes and treated as atomic nodes.
3741
- * External functions follow the same calling convention as normal ones.
3742
- * Serialization of external values uses type-specific codecs.
3743
- * This mechanism allows integration of native computations (numbers, strings, system data) while preserving the purity and determinism of the k language.
3744
-
3745
- ---