Skip to content

RFC-025: 可扩展原语类型机制

摘要

本文档定义 YaoXiang 编译器的可扩展原语类型机制Primitive::Extension)。允许外部代码向编译器注册自定义原语类型,使编译器无需硬编码即可支持领域特定类型(量子位、GPU 缓冲区、SIMD 向量、硬件寄存器等)。

动机

为什么需要这个机制?

当前编译器硬编码了所有原语类型:IntFloatStringBoolUnit。每添加一种新的原语类型,都需要修改编译器源码的多个位置——类型检查器、代码生成器、所有权分析器、优化器。

这违反了开闭原则:对扩展开放,对修改关闭。

设计边界

硬编码核心类型(语言地基):Int, Float, String, Bool, Unit
动态扩展类型(领域插件):  通过 Primitive::Extension 注册

核心类型硬编码是因为编译器深度依赖它们的语义(算术运算、条件分支、哈希、比较)。扩展类型对编译器是不透明值——编译器只知道它们的大小、对齐和所有权属性,不知道它们的内部语义。

这不是"把所有类型统一成动态加载"。核心类型和扩展类型是两件不同的事。

提案

核心设计

1. Extension 类型属性

每个扩展原语类型注册时必须声明以下属性:

rust
pub struct PrimitiveExtension {
    /// 类型名称,如 "Qubit", "Buffer", "Vec128"
    pub name: String,

    /// 字节大小(固定大小类型)
    /// None 表示大小在编译期未知(需运行时确定)
    pub size: Option<usize>,

    /// 对齐要求
    pub align: Option<usize>,

    /// 是否允许隐式复制
    /// false = Move 语义(赋值即移动,如 Qubit)
    /// true = Copy 语义(赋值即复制,如 Vec128)
    pub is_copy: bool,

    /// 是否允许空值(zero-sized type)
    pub allow_zst: bool,
}

2. 注册接口

rust
// 编译器内部 API
compiler.register_primitive(PrimitiveExtension {
    name: "Qubit".to_string(),
    size: Some(0),           // 逻辑大小为0,物理状态在量子处理器上
    align: Some(1),
    is_copy: false,          // Move 语义,符合 no-cloning
    allow_zst: true,
});

注册后,Qubit 在类型系统中成为合法的原语类型,可用于变量声明、函数参数、结构体字段。

3. 类型检查器行为

扩展原语类型在类型检查中遵循以下规则:

场景行为
变量声明 q: Qubit = ...✅ 合法
函数参数 fn(q: Qubit)✅ 合法
结构体字段 { q: Qubit }✅ 合法
is_copy == false 时赋值Move 语义,原变量失效
is_copy == true 时赋值Copy 语义,原变量保留
隐式复制(函数多处使用)取决于 is_copy
比较 ==!=❌ 编译错误(无内置比较)
算术 +-❌ 编译错误(无内置运算)
泛型约束 T: Copy仅当 is_copy == true 时满足

4. 代码生成器行为

扩展原语类型在代码生成中作为不透明值处理:

  • LLVM IR:生成为 {size} x i8 或对应大小的结构体
  • 不生成任何特殊指令——语义由后端或库负责
  • 如果后端需要特殊处理(如 QIR 量子门),通过后端注册机制实现(不在本 RFC 范围内)

示例

注册一个 Move 语义类型

rust
// 量子位:不可复制,大小为0(物理状态在 QPU 上)
compiler.register_primitive(PrimitiveExtension {
    name: "Qubit".into(),
    size: Some(0),
    align: Some(1),
    is_copy: false,
    allow_zst: true,
});
yaoxiang
# 用户代码
q: Qubit = qubit(0)
q2 = q          # ❌ 编译错误:Qubit 是 Move 类型,q 已失效
q = H(q)        # ✅ 消费 q,返回新 q

注册一个 Copy 语义类型

rust
// SIMD 向量:可复制,固定大小
compiler.register_primitive(PrimitiveExtension {
    name: "Vec128".into(),
    size: Some(16),
    align: Some(16),
    is_copy: true,
    allow_zst: false,
});
yaoxiang
# 用户代码
a: Vec128 = load_vec128(data)
b = a           # ✅ Copy 语义,a 仍然有效
c = add_vec128(a, b)  # ✅ a 和 b 都可用

详细设计

编译器改动

组件改动
类型系统新增 Ty::Extension 变体,存储 PrimitiveExtension 元数据
类型检查器扩展类型不参与内置运算解析,不满足内置 trait 约束(除非显式实现)
所有权分析器根据 is_copy 决定 Move 或 Copy 语义
代码生成器size/align 生成不透明值,无特殊指令
错误信息扩展类型的错误消息引用注册时的 name

与 FFI 的关系

Primitive::Extension 与 RFC-021(FFI)是正交的:

Primitive::ExtensionFFI
作用注册新类型调用外部函数
层级类型系统运行时
示例Qubit 是一个类型native("sin") 是一个函数调用

一个领域可能同时需要两者:Qubit 类型通过 Extension 注册,量子门函数通过 FFI 注册。

向后兼容性

  • ✅ 完全向后兼容
  • 不修改任何现有类型的语义
  • 扩展类型是新增能力,不影响已有代码

权衡

优点

  • ✅ 编译器无需为每个新领域修改源码
  • ✅ 领域专家可以自行注册类型,不依赖编译器团队
  • ✅ 核心类型保持硬编码,不牺牲编译器对基础类型的深度优化
  • ✅ 接口简单,一个结构体定义所有属性

缺点

  • ⚠️ 扩展类型不支持内置运算——需要额外的函数或后端机制来实现语义
  • ⚠️ 调试时扩展类型显示为不透明值,不如核心类型直观

替代方案

方案为什么不选择
所有类型动态加载核心类型(Int/Float/Bool)需要编译器深度优化,动态加载会丧失这些能力
每个领域硬编码每加一个领域就改编译器,不可扩展
纯库方案(不注册类型)无法在类型系统层面保证语义(如 no-cloning),只能运行时检查

实现策略

阶段 1:核心接口

  • [ ] 在类型系统中添加 Ty::Extension 变体
  • [ ] 实现 register_primitive API
  • [ ] 扩展类型检查器处理 is_copy 语义
  • [ ] 扩展代码生成器处理不透明值
  • [ ] 单元测试

阶段 2:注册时机

  • [ ] 支持编译器初始化时批量注册(配置文件或 builder API)
  • [ ] 支持标准库预注册(std.primitive 模块导出扩展类型定义)

依赖关系

  • 无硬依赖。可独立于其他 RFC 实现。

开放问题

  • [ ] 扩展类型是否应该支持 trait 实现(如让 Qubit 实现自定义的 QuantumGate trait)?
  • [ ] 是否需要生命周期钩子(如 on_drop)来支持 RAII 语义的扩展类型?
  • [ ] 配置文件格式:TOML、YAML、还是 YaoXiang 自身的配置语法(参考 RFC-015)?

设计决策记录

决策决定原因日期
核心类型不动态加载保持硬编码编译器深度依赖核心类型语义,动态化收益为零2026-06-05
扩展类型为不透明值不注入语义语义由后端/库负责,编译器只保证类型安全和所有权2026-06-05
与 FFI 正交不合并类型注册和函数调用是不同抽象层级2026-06-05