Kernel 与 .ent 语言
.ent 文件是 Kernel 库:它包含若干 kernel 以及它们共享的声明(struct、type、fn)。.ent 与 .esn 共用同一套语法、词法和字面量:// 注释、; 结束语句、{ } 块。诊断格式为 文件:行:列: 信息。语言的精确定义在前端源码(crates/frontend/language)及其测试中。
Kernel 的结构
Section titled “Kernel 的结构”struct Particle { position: Vec2; velocity: Vec2; } // 文档级 struct:下面所有 Kernel 可见type Swarm = Rows<Particle>; // 端口类型别名,同样共享
kernel image.grayscale@1 { requires { field.read; field.write; } // 需要的能力 input source: Image; // 输入端口 param amount: Float = 1.0; // 参数与默认值 output result: Image; // 输出端口 struct Hit { t: Float; id: Int; } // 只属于这个 Kernel 的 struct fn luma(c: Vec4) -> Float { return c.r * 0.2126 + c.g * 0.7152 + c.b * 0.0722; } compute { // 执行主体(这里是 map 模式) let c = sample(source); let g = luma(c); return mix(c, vec4(g, g, g, c.a), amount); }}- 身份:
名字@版本就是 Kernel 的身份。图绑定到这个 id,永远不绑定到文件。 - 能力:
requires { … }列出 Kernel 需要的能力,不声明就不能调用相应的内置函数。见能力与安全。 - 端口:
input、output,以及 scan 模式的memory。每个端口携带一个 Field。 - 主体:
compute、scan、scatter、prefix、group N之一,决定执行模式。 - 预算:
budget N;声明一个元素最多可以运行的循环迭代次数(默认 16777216)。
//! 预设图或包清单的文档:文档开头的几行。use "kernels/";
/// Kernel 的文档:紧挨在它上面的几行。kernel tally.twice@1 { requires { field.read; field.write; } /// 端口或参数的文档,紧挨在它上面。 input counts: Rows<Int>; output result: Rows<Int>; compute { return sample(counts) * 2; }}/// 放在 kernel、input、param、output、memory 正上方;空行或 // 行会结束它。对语法来说它们只是注释,但会随 Kernel 源码一起经过 publish、kernel 和 snapshot,修改它们也会重新发布。
compute 主体由 let 绑定和一个结果组成。
| 类别 | 写法与规则 |
|---|---|
| 绑定 | let n = …;;let { x, y } = window(); 按名字解构;let { x: width, y: height } = window(); 重命名;_ 不绑定;expr; 等价于 let _ = expr; |
| 算术 | + - * /、%(截断余数);Int 与 Float 严格分开,但 vec2/3/4 接受 Int 分量;Float 与向量运算时会广播 |
| 分量 | v.xy、v.zyx、c.rgb;v.x 取单个分量 |
| 位运算 | & | ^、<< >> >>>(移位计数取模 32,>> 算术、>>> 逻辑);0xFF 十六进制,超过 8 位或带 l 则为 Long |
| 逻辑 | &&、|| 短路;==、!= 比较标量和整个向量,< 等只比较标量 |
if |
if c { a } else { b } 是表达式;if c { return a; }(无 else)守卫后续语句;只含语句的 if hit { emit(p); } 执行后继续 |
match |
对 Int:match i { 0 => a, 1 | 2 => …, 8..16 => c, 16..=31 => d, _ => b };范围最多 256 个值,各分支同类型 |
| 枚举 | type Mode = enum { Idle, Walk, Attack = 10, Guard };——命名的 Int,Mode.Guard 是 11;列全所有变体的 match 不需要 _ |
loop |
loop n with x = init until done { next } 是表达式;次数可以是字面量(上限 65536)或任意 Int 表达式;if done { break x; } 提前结束最内层循环 |
| 结构体 | Name { a: …, b: … } 构造;Name { ..base, a: … } 复制并替换字段 |
| 嵌套 | 表达式最多嵌套 256 层,超过时文档被拒绝并指出位置 |
[T; N] 保存 N 个同类型值:[Int; 16]、[Vec3; 4]、[[Int; 4]; 4]。数组可以做结构体字段、函数参数与返回值、循环携带值,也可以是 Field 的元素。
let a = [1, 2, 3];let zeros = [0.0; 8];let w = [each 32 { unit(weights, x, index()) }]; // 逐元素构造let b = [each 4 { if index() == k { v } else { a[index()] } }]; // 替换第 k 个元素字面量下标在编译时检查,计算出的 Int 下标会被夹到范围内。数组长度 1–4096;任何值最多 65536 个字,结构体和数组最多嵌套 64 层。
参数可以是 Kernel 持有的任何值:
param lens: Mat4 = mat4(1.0);param cell: IVec2 = ivec2(3, 4);param edges: [IVec2; 12] = [ … ];param style: Style = Style { ground: vec3(0.1, 0.1, 0.1), width: 1.0 };默认值必须是字面量。值按布局匹配而不是按写法:从外部(场景、回放的 param、协议)给值时,按顺序排列的数组可以填充任意形状,例如 [[1, 0], [0, 1]] 是一个 Mat2。
函数、声明与 use
Section titled “函数、声明与 use”- 文档顶层的
struct、type、fn被该文档中所有 Kernel 共享,也被所有use它的文档共享。.ent可以use其他.ent文件或目录。 - 名字的查找顺序:Kernel 自己的声明 → 所在文档自己的 → 它
use的文档中唯一的那个。两个被使用的包对同一名字给出不同答案时必须限定:cast_fx::clear(p)、Rows<ui_panel::Mark>。 - 函数之间可以任意顺序调用,但不能递归(调用链回到自己会被拒绝并指出环);重复用
loop表达。 - 函数可以接收 Field 参数:
fn cast(voxels: Volume<Int>, eye: Vec3) -> Hit,调用时传入输入端口名,每个端口实例化一次。 - 泛型:
fn twice<T>(v: T) -> T,每组类型编译一个实例。 - 函数可以用数学内置函数的名字(如
step、mix),此时在本文档内代替它;Field、memory、上下文和构造类内置函数的名字不能被占用。
- 整数失败:
+ - *溢出、除零或对零取余、对最小整数取负,在所有后端上都会使该元素失败,报告按行优先顺序第一个出错的元素;本次求值什么都不提交。add_wrap、sub_wrap、mul_wrap是回绕版本。 - 浮点:正数除以零是无穷,零除以零是 NaN,NaN 不等于也不排序于任何值。GPU 上算术产生的 NaN 在驱动的快速数学下可能变成数字。
- 预算:一个元素所有循环加起来的迭代次数上限,默认 16777216,可用
budget N;声明(1 到 2^30)。一次运行所有元素合计的上限由 Host 决定(默认四十亿,--work <M>以百万为单位设置)。超出即失败,运行中的系统保留上一次状态。
64 位数字
Section titled “64 位数字”Double、Long 及其向量与矩阵(DVec2..4、LVec2/3、DMat2..4)。double(x)、long(x) 转换,float(x)、int(x) 转回。Double 与 Float、Long 与 Int 不会隐式相遇,混用会被拒绝并提示写哪个转换。字面量在遇到宽度之前保持精确:0.1d、8l 显式指定宽度。GPU 在 map、group、归约和部分 scatter 中计算 Long,但从不计算 Double——含 Double 的节点在 GPU 会话中会改在 JIT 上运行。
端口可以用 as 以更窄的格式存储标量:Grid<Vec4 as unorm8> 每像素 4 字节,Rows<Int as u8> 每行 1 字节。Kernel 仍以 Float / Int 计算,读时加宽、写时变窄。可选 f16、unorm8、unorm16、snorm8、u8、i8、u16、i16。窄 Field 与宽 Field 是不同布局,不能直接相连;只有 map 或 scan 的步骤能写窄输出;64 位标量不能变窄。

