Skip to main content

polydat_grammar/
port_type.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! The port type vocabulary: every type a wire, a port, a cast, or a
5//! declaration can name, with its keyword. What a type means to a
6//! compiled buffer (its slot color, width, and scratch element) is
7//! the runtime's, defined on this type by `polydat`.
8
9use std::fmt;
10
11/// Compile-time type tag for a port on a Polydat node.
12///
13/// **Narrow types and runtime storage:**
14///
15/// `PortType` includes narrow integer and float variants (U32, I32,
16/// I64, F32) that have no corresponding `Value` variant. At runtime,
17/// narrow values are stored inside `Value::U64` (for integers) or
18/// `Value::F64` (for f32), with the assumption that the bits fit:
19///
20/// - `u32` → zero-extended in `Value::U64`
21/// - `i32` → sign-extended or bit-reinterpreted in `Value::U64`
22/// - `i64` → bit-reinterpreted in `Value::U64`
23/// - `f32` → losslessly widened in `Value::F64`
24///
25/// The narrow `PortType` variants exist for compile-time type
26/// checking and auto-adapter insertion (`U32ToU64`, `F32ToF64`).
27/// P2/P3 compiled kernels use flat u64 buffers where this packing
28/// is natural. The `Value` enum stays small — no combinatorial
29/// explosion of narrow variant types.
30///
31/// Every input and output port declares its `PortType`. The assembler
32/// uses these to validate wiring and auto-insert type adapters (e.g.,
33/// `u64 → f64` widening). At runtime, the corresponding `Value`
34/// variant is used.
35///
36/// **Widening rules** (auto-inserted by the assembler):
37/// - `U32 → U64`, `I32 → I64`, `F32 → F64` (lossless widening)
38/// - `U64 → F64` (lossless for values < 2^53)
39/// - `Bool → U64` (true=1, false=0)
40/// - Any type → `Str` (via display conversion)
41///
42/// **Narrowing** is never implicit — use explicit cast functions.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44pub enum PortType {
45    /// 64-bit unsigned integer. The primary numeric type.
46    U64,
47    /// 64-bit IEEE 754 float. Used for math, distributions, noise.
48    F64,
49    /// 32-bit unsigned integer. Widens to U64 automatically.
50    U32,
51    /// 32-bit signed integer. Widens to I64 automatically.
52    I32,
53    /// 64-bit signed integer.
54    I64,
55    /// 32-bit IEEE 754 float. Widens to F64 automatically.
56    F32,
57    /// 8-bit unsigned integer (cranelift I8 lane, unsigned
58    /// interpretation). Zero-extended in `Value::U64`; widens to
59    /// U64 automatically.
60    U8,
61    /// 8-bit signed integer (cranelift I8 lane, signed
62    /// interpretation). Sign-extended in `Value::I64`; widens to
63    /// I64 automatically.
64    I8,
65    /// 16-bit unsigned integer (cranelift I16 lane, unsigned
66    /// interpretation). Zero-extended in `Value::U64`; widens to
67    /// U64 automatically.
68    U16,
69    /// 16-bit signed integer (cranelift I16 lane, signed
70    /// interpretation). Sign-extended in `Value::I64`; widens to
71    /// I64 automatically.
72    I16,
73    /// 16-bit IEEE 754-2008 binary16 float (cranelift F16).
74    /// Carried as its bit pattern in `Value::U64` (low 16 bits),
75    /// the same stuffing convention as `F32`; widens to F32/F64
76    /// automatically (every f16 is exactly representable in both).
77    F16,
78    /// 128-bit unsigned integer (cranelift I128, unsigned
79    /// interpretation). Real `Value::U128` two-limb carrier — a
80    /// 128-bit value cannot ride a 64-bit slot. Interpreter-only.
81    U128,
82    /// 128-bit signed integer (cranelift I128, signed
83    /// interpretation). Same carrier story as `U128`.
84    I128,
85    /// 128-bit SIMD register word, raw view — the full word as
86    /// algorithm-defined buffer state (heterogeneous lane
87    /// roles). Free bitcast to/from every lane-typed view.
88    Reg128,
89    /// Register word viewed as 16 × i8 lanes.
90    RegI8x16,
91    /// Register word viewed as 8 × i16 lanes.
92    RegI16x8,
93    /// Register word viewed as 4 × i32 lanes.
94    RegI32x4,
95    /// Register word viewed as 2 × i64 lanes.
96    RegI64x2,
97    /// Register word viewed as 8 × f16 lanes.
98    RegF16x8,
99    /// Register word viewed as 4 × f32 lanes.
100    RegF32x4,
101    /// Register word viewed as 2 × f64 lanes.
102    RegF64x2,
103    /// Boolean (true/false). Widens to U64 (1/0).
104    Bool,
105    /// Heap-allocated string. Any type auto-converts to Str.
106    Str,
107    /// Raw byte buffer.
108    Bytes,
109    /// Structured JSON value.
110    Json,
111    /// Adapter-contributed reflected type (e.g., CQL UUID).
112    Ext,
113    /// Type-erased Arc handle to a resolved resource (dataset,
114    /// prepared statement, ...). The producer node populates an
115    /// `Arc<dyn Any + Send + Sync>`; the consumer node downcasts to
116    /// the concrete type via `Value::as_handle::<T>()`.
117    Handle,
118    /// Typed `f32` vector slice (`Arc<[f32]>`). Bound natively by
119    /// adapters that understand `[f32]` (CQL `vector<float, N>`).
120    VecF32,
121    /// Typed `i32` vector slice (`Arc<[i32]>`).
122    VecI32,
123    /// Typed `f64` vector slice (`Arc<[f64]>`). Bound natively
124    /// for CQL `vector<double, N>`.
125    VecF64,
126    /// Typed `i64` vector slice (`Arc<[i64]>`). Bound natively
127    /// for CQL `vector<bigint, N>`.
128    VecI64,
129    /// Typed half-precision float vector (`Arc<[half::f16]>`).
130    /// Bound natively for CQL `vector<half_float, N>`-style
131    /// columns; stays at f16 on the wire so embeddings stored
132    /// as 16-bit floats don't widen to f32 at the boundary.
133    VecF16,
134    /// Typed `i16` vector slice (`Arc<[i16]>`). Bound natively
135    /// for CQL `vector<smallint, N>`.
136    VecI16,
137    /// Typed `i8` vector slice (`Arc<[i8]>`). Completes the
138    /// cranelift lane family; CQL `vector<tinyint, N>`.
139    VecI8,
140}
141
142impl fmt::Display for PortType {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        match self {
145            PortType::U64 => write!(f, "u64"),
146            PortType::F64 => write!(f, "f64"),
147            PortType::U32 => write!(f, "u32"),
148            PortType::I32 => write!(f, "i32"),
149            PortType::I64 => write!(f, "i64"),
150            PortType::F32 => write!(f, "f32"),
151            PortType::U8 => write!(f, "u8"),
152            PortType::I8 => write!(f, "i8"),
153            PortType::U16 => write!(f, "u16"),
154            PortType::I16 => write!(f, "i16"),
155            PortType::F16 => write!(f, "f16"),
156            PortType::U128 => write!(f, "u128"),
157            PortType::I128 => write!(f, "i128"),
158            PortType::Reg128 => write!(f, "reg128"),
159            PortType::RegI8x16 => write!(f, "reg_i8x16"),
160            PortType::RegI16x8 => write!(f, "reg_i16x8"),
161            PortType::RegI32x4 => write!(f, "reg_i32x4"),
162            PortType::RegI64x2 => write!(f, "reg_i64x2"),
163            PortType::RegF16x8 => write!(f, "reg_f16x8"),
164            PortType::RegF32x4 => write!(f, "reg_f32x4"),
165            PortType::RegF64x2 => write!(f, "reg_f64x2"),
166            PortType::Bool => write!(f, "bool"),
167            PortType::Str => write!(f, "String"),
168            PortType::Bytes => write!(f, "bytes"),
169            PortType::Json => write!(f, "json"),
170            PortType::Ext => write!(f, "ext"),
171            PortType::Handle => write!(f, "handle"),
172            PortType::VecF32 => write!(f, "vec_f32"),
173            PortType::VecI32 => write!(f, "vec_i32"),
174            PortType::VecF64 => write!(f, "vec_f64"),
175            PortType::VecI64 => write!(f, "vec_i64"),
176            PortType::VecF16 => write!(f, "vec_f16"),
177            PortType::VecI16 => write!(f, "vec_i16"),
178            PortType::VecI8 => write!(f, "vec_i8"),
179        }
180    }
181}
182
183impl PortType {
184    /// The canonical lowercase keyword for this `PortType`.
185    ///
186    /// This is the single source of truth for the str↔PortType
187    /// mapping used by every synthesizer and parser in the
188    /// workspace — synthesized polydat source (`extern <name>:
189    /// <keyword>`), the workload-author `{name:<keyword>}` lvalue
190    /// spec, and reverse parsing via [`Self::from_keyword`].
191    /// Inverse of [`Self::from_keyword`].
192    ///
193    /// Exhaustive over the enum — adding a new `PortType` variant
194    /// is a compile error here, forcing the addition of its
195    /// canonical keyword and the round-trip closure to update.
196    pub fn to_keyword(&self) -> &'static str {
197        match self {
198            Self::U64 => "u64",
199            Self::F64 => "f64",
200            Self::U32 => "u32",
201            Self::I32 => "i32",
202            Self::I64 => "i64",
203            Self::F32 => "f32",
204            Self::U8 => "u8",
205            Self::I8 => "i8",
206            Self::U16 => "u16",
207            Self::I16 => "i16",
208            Self::F16 => "f16",
209            Self::U128 => "u128",
210            Self::I128 => "i128",
211            Self::Reg128 => "reg128",
212            Self::RegI8x16 => "reg_i8x16",
213            Self::RegI16x8 => "reg_i16x8",
214            Self::RegI32x4 => "reg_i32x4",
215            Self::RegI64x2 => "reg_i64x2",
216            Self::RegF16x8 => "reg_f16x8",
217            Self::RegF32x4 => "reg_f32x4",
218            Self::RegF64x2 => "reg_f64x2",
219            Self::Bool => "bool",
220            Self::Str => "str",
221            Self::Bytes => "bytes",
222            Self::Json => "json",
223            Self::Ext => "ext",
224            Self::Handle => "handle",
225            Self::VecF32 => "vec_f32",
226            Self::VecI32 => "vec_i32",
227            Self::VecF64 => "vec_f64",
228            Self::VecI64 => "vec_i64",
229            Self::VecF16 => "vec_f16",
230            Self::VecI16 => "vec_i16",
231            Self::VecI8 => "vec_i8",
232        }
233    }
234
235    /// Parse a polydat type keyword into a `PortType`.
236    ///
237    /// Inverse of [`Self::to_keyword`]: accepts every keyword
238    /// that `to_keyword` emits, plus a small set of legacy aliases
239    /// (`"String"`, `"Json"`, `"Ext"`) that survive in older
240    /// hand-written workload source. Returns `None` for any
241    /// unrecognized keyword so callers can surface a loud
242    /// diagnostic rather than silently coercing to a default.
243    ///
244    /// Used by the DSL `extern <name>: <keyword>` parser
245    /// (`polydat/src/dsl/compile.rs`). Round-trips cleanly with
246    /// any source `to_keyword` emits.
247    pub fn from_keyword(name: &str) -> Option<Self> {
248        match name {
249            "u64" => Some(Self::U64),
250            "f64" => Some(Self::F64),
251            "u32" => Some(Self::U32),
252            "i32" => Some(Self::I32),
253            "i64" => Some(Self::I64),
254            "f32" => Some(Self::F32),
255            "u8" => Some(Self::U8),
256            "i8" => Some(Self::I8),
257            "u16" => Some(Self::U16),
258            "i16" => Some(Self::I16),
259            "f16" => Some(Self::F16),
260            "u128" => Some(Self::U128),
261            "i128" => Some(Self::I128),
262            "reg128" => Some(Self::Reg128),
263            "reg_i8x16" => Some(Self::RegI8x16),
264            "reg_i16x8" => Some(Self::RegI16x8),
265            "reg_i32x4" => Some(Self::RegI32x4),
266            "reg_i64x2" => Some(Self::RegI64x2),
267            "reg_f16x8" => Some(Self::RegF16x8),
268            "reg_f32x4" => Some(Self::RegF32x4),
269            "reg_f64x2" => Some(Self::RegF64x2),
270            "bool" => Some(Self::Bool),
271            "str" | "Str" | "String" => Some(Self::Str),
272            "bytes" => Some(Self::Bytes),
273            "json" | "Json" => Some(Self::Json),
274            "ext" | "Ext" => Some(Self::Ext),
275            "handle" => Some(Self::Handle),
276            "vec_f32" => Some(Self::VecF32),
277            "vec_i32" => Some(Self::VecI32),
278            "vec_f64" => Some(Self::VecF64),
279            "vec_i64" => Some(Self::VecI64),
280            "vec_f16" => Some(Self::VecF16),
281            "vec_i16" => Some(Self::VecI16),
282            "vec_i8" => Some(Self::VecI8),
283            _ => None,
284        }
285    }
286
287    /// Workload-author-facing parser for the `{name:<keyword>}`
288    /// lvalue-spec surface. Strict subset of [`Self::from_keyword`]
289    /// — `handle`, `ext`, and `none` are rejected because they're
290    /// internal-only types a workload author should never assert.
291    ///
292    /// Returns `None` for any unrecognized name; the caller
293    /// surfaces the unknown spec as a workload-shape diagnostic.
294    pub fn from_workload_name(name: &str) -> Option<Self> {
295        match Self::from_keyword(name)? {
296            Self::Handle | Self::Ext => None,
297            pt => Some(pt),
298        }
299    }
300}