Files
OxideJS-New/crates/oxide_emit/src/program.rs
T

526 lines
27 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 顶层程序发射:`emit_program`(全局声明实例化与 sub-pass 调度)与全局写入族。
//!
//! 关键不变式:
//! - `BUILTIN_GLOBALS` / `NON_WRITABLE_GLOBAL_BUILTINS` 是全 crate 的内置名集
//! 单一真值,`compile_ctx` 各编译期谓词经此二表引用;
//! - GDI 序言名集 = 顶层 var 名 ∪ 块级函数泄漏名;顶层函数声明名不进序言
//! (其 A 侧值由首 sub-pass 的声明写以真闭包建立);
//! - `Emitter` 结构体留 `emit.rs`,本文件以跨文件 `impl` 块扩展之。
use std::collections::HashSet;
use oxide_bytecode::module::Constant;
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
use oxide_ir::IRFunction;
use oxide_parser::Statement;
use crate::capture::{
collect_block_level_decl_names, collect_captured_bindings, collect_direct_lexical_names, collect_own_binding_names,
collect_top_level_function_names, collect_top_level_function_names_ordered, collect_var_binding_names,
};
use crate::compile_ctx::CompileCtx;
use crate::Emitter;
pub(crate) const BUILTIN_GLOBALS: &[&str] = &[
"NaN",
"undefined",
"Infinity",
"globalThis",
"Object",
"Array",
"String",
"Number",
"Boolean",
"Function",
"Error",
"TypeError",
"ReferenceError",
"RangeError",
"SyntaxError",
"URIError",
"EvalError",
"eval",
"SuppressedError",
"DisposableStack",
"AsyncDisposableStack",
"Math",
"JSON",
"Promise",
"AggregateError",
"Date",
"Set",
"Map",
"RegExp",
"Symbol",
"parseInt",
"parseFloat",
"isNaN",
"isFinite",
"Proxy",
"WeakMap",
"WeakSet",
"WeakRef",
"FinalizationRegistry",
"Atomics",
"SharedArrayBuffer",
"ArrayBuffer",
"DataView",
"Iterator",
"BigInt",
"TypedArray",
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array",
"BigInt64Array",
"BigUint64Array",
"Reflect",
"escape",
"unescape",
"encodeURI",
"decodeURI",
"encodeURIComponent",
"decodeURIComponent",
// test262 宿主对象:编译期按已知全局解析,运行期由 VM 绑定(详见 bind_test262_host)。
"$262",
];
/// 只读全局内置名:全局对象上的数据属性为 {writable:false, configurable:false},
/// 对它们的 put 永不成功。写路径命中这些名字的全局绑定(非局部遮蔽)时编译期
/// 拦截:sloppy 静默丢弃、strict 抛 TypeError;BUILTIN_GLOBALS 内其余名属性可写
/// (writable:true),标识符写须双写全局属性(见 targets_writable_builtin)。
pub(crate) const NON_WRITABLE_GLOBAL_BUILTINS: &[&str] = &["undefined", "NaN", "Infinity"];
impl Emitter {
/// 隐式全局写(sloppy 未声明标识符写):把值寄存器写到全局对象可写/可枚举/
/// 可配置数据属性(未解析引用上的 PutValue 语义)。全局对象由 VM 运行期从
/// session 解析,不依赖 this。
///
/// # 边界与前提
/// - 仅读写登记集并集命中的寄存器调用(未声明标识符写):未声明名谁先引用谁
/// 登记全局槽,读侧与写侧登记同属一个全局槽,写一律须穿透到全局对象。
///
/// # 副作用
/// - 定义全局对象数据属性(可写/可枚举/可配置),属性缺失时新建。
pub(crate) fn emit_implicit_global_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
let idx = ctx.add_constant(Constant::String(name.to_string()));
ctx.inst(Inst::define_global_prop_c(Operand::Reg(val_reg), idx));
}
/// 严格模式未声明写:发射 ReferenceError 抛错指令序列(未解析引用不可 put,
/// 值无关,编译期拦截)。错误消息格式与读侧 LOAD_GLOBAL 运行期消息一致。
///
/// # 副作用
/// - 发射 THROW 指令序列,其后控制流不可达,dummy 值保持寄存器良定义。
pub(crate) fn emit_strict_undeclared_write(&self, name: &str, ctx: &mut CompileCtx) -> Result<u32, String> {
self.emit_throw_error("ReferenceError", &format!("{name} is not defined"), ctx)
}
/// 把脚本顶层 var/function 绑定的当前值同步写入全局对象属性,使顶层声明
/// 可经 `globalThis` 反射(脚本环境记录的 var 绑定全局对象属性)。
///
/// # 边界与前提
/// - 仅顶层模块上下文调用。
/// - let/const/class 不落全局对象,不得调用本函数。
///
/// # 副作用
/// - 普通脚本:定义可写/可枚举/不可配置数据属性(经顶层 `this` = 全局对象)。
/// - eval 脚本(`is_eval_script`):属性可配置(configurable:true),全局对象
/// 经 session 解析——eval var 声明允许后续 redefine/delete。
pub(crate) fn emit_global_prop_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
let idx = ctx.add_constant(Constant::String(name.to_string()));
if ctx.is_eval_script {
ctx.inst(Inst::define_global_prop_c(Operand::Reg(val_reg), idx));
} else {
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
ctx.inst(Inst::define_global_prop(Operand::This, Operand::Reg(val_reg), Operand::Reg(key_reg)));
}
}
/// 顶层函数声明的全局对象属性同步写:eval 脚本沿用
/// [`emit_global_prop_write`] 的 `DEFINE_GLOBAL_PROP_C` 分支(属性可配置,
/// 创建检查不适用于 eval 动态路径);普通脚本经顶层 This 走
/// `DEFINE_GLOBAL_FUNC_BIND`(规范 CreateGlobalFunctionBinding 三臂,声明检查
/// 由 GlobalDeclarationInstantiation(脚本顶层声明实例化)序言的
/// `CAN_DECLARE_GLOBAL_FUNC` 段预先完成)。
pub(crate) fn emit_global_func_bind_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
if ctx.is_eval_script {
self.emit_global_prop_write(name, val_reg, ctx);
return;
}
let idx = ctx.add_constant(Constant::String(name.to_string()));
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
ctx.inst(Inst::define_global_func_bind(Operand::This, Operand::Reg(val_reg), Operand::Reg(key_reg)));
}
/// 顶层 var 声明带初始化的全局对象属性同步:PutValue 语义——既有不可写数据
/// 属性 strict 抛 TypeError / sloppy 静默 no-op;可写照原描述符仅更值。脚本与
/// eval 均经 session 解析全局对象,不依赖顶层 this。
///
/// # 边界与前提
/// - 仅顶层模块上下文调用;builtin 名不走本写点(该名由
/// GlobalDeclarationInstantiation(脚本顶层声明实例化)序言写点处理:属性
/// 已存在时不改动值,仅缺失时新建)。
///
/// # 副作用
/// - 更新全局对象数据属性,失败面抛 TypeError(strict 不可写)。
pub(crate) fn emit_global_put_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
let idx = ctx.add_constant(Constant::String(name.to_string()));
ctx.inst(Inst::define_global_prop_c(Operand::Reg(val_reg), idx));
}
/// 判定名字的裸写是否落全局对象属性。
///
/// 判定条件:名字在顶层 var/函数声明名集内,且当前作用域解析落在全局作用域
/// (scope 0)。嵌套函数内的同名局部绑定遮蔽顶层绑定时,解析命中更高作用域,
/// 判定为局部绑定,写走既有 cell/寄存器路径,不触全局对象。
pub(crate) fn is_global_tier_name(&self, ctx: &CompileCtx, name: &str) -> bool {
ctx.global_tier_names.contains(name) && matches!(ctx.scopes.symbols.lookup_any_binding(name), Some((_, 0)))
}
/// 顶层已声明 var 的裸写落到全局对象属性(顶层 var 的唯一存储):顶层普通
/// 脚本经 This=全局对象走 `DEFINE_GLOBAL_PROP`;顶层 eval 与嵌套函数
/// This≠全局对象,经 session 解析走 `DEFINE_GLOBAL_PROP_C`(configurable:true;
/// 既有 configurable:false 描述符不升级,仅更新值)。
pub(crate) fn emit_tier_global_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
let idx = ctx.add_constant(Constant::String(name.to_string()));
if ctx.is_global_scope && !ctx.is_eval_script {
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
ctx.inst(Inst::define_global_prop(Operand::This, Operand::Reg(val_reg), Operand::Reg(key_reg)));
} else {
ctx.inst(Inst::define_global_prop_c(Operand::Reg(val_reg), idx));
}
}
/// GlobalDeclarationInstantiation(脚本顶层声明实例化)序言:顶层 var 全局
/// 属性 define-if-absent。既有属性(数据或 accessor)不改动值——
/// CreateGlobalVarBinding 对既有数据描述符零修改(不更值);仅属性缺失时新建。
/// 顶层普通脚本经 This 走 `DEFINE_GLOBAL_PROP_IF_ABSENT`;eval 经
/// KernelSession 解析全局对象走 `DEFINE_GLOBAL_PROP_C_IF_ABSENT`。
pub(crate) fn emit_global_prop_write_if_absent(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
let idx = ctx.add_constant(Constant::String(name.to_string()));
if ctx.is_eval_script {
ctx.inst(Inst::define_global_prop_c_if_absent(Operand::Reg(val_reg), idx));
} else {
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
ctx.inst(Inst::define_global_prop_if_absent(
Operand::This,
Operand::Reg(val_reg),
Operand::Reg(key_reg),
));
}
}
/// 规范 GlobalDeclarationInstantiation step 9(函数臂)的检查阶段:顶层函数
/// 声明名按声明逆序逐名去重,每名发一条 `CAN_DECLARE_GLOBAL_FUNC`
/// (CanDeclareGlobalFunction 运行期判定,撞既有不可配置非可写数据属性或
/// 不可扩展上的缺失名抛 TypeError)。发射位必须位于 var 序言之先——
/// 检查失败时任何绑定(含 var 序言新建属性)不得实例化。
///
/// # 边界与前提
/// - 仅普通脚本调用(`is_eval_script` 面由调用点按条件判定排除);eval 动态
/// 路径与 eval 三常量编译期条件开关零触碰。
/// - 仅遍历语句列表直接子级具名函数声明(生成器/异步声明同节点类型,天然
/// 覆盖);块内函数声明与 `export default function` 不入全局检查面。
pub(crate) fn emit_gdi_func_decl_checks(&self, stmts: &[Statement], ctx: &mut CompileCtx) {
let names = collect_top_level_function_names_ordered(stmts);
let mut seen = HashSet::new();
for name in names.iter().rev() {
// 逆序首见即源序最后声明者(规范去重口径),重名只查一次。
if !seen.insert(name.as_str()) {
continue;
}
let idx = ctx.add_constant(Constant::String(name.clone()));
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
ctx.inst(Inst::can_declare_global_func(Operand::This, Operand::Reg(key_reg)));
}
}
/// 把完整程序编译为顶层模块体的 IRFunction。
///
/// 调用方为 `oxide_compiler::Compiler::compile`:本函数完成 emit 半程,
/// 随后由 `oxide_ir::lower::lower` 降为字节码。
/// `repl_persist` 为 true 时脚本顶层 let/const 也写全局对象(REPL 跨轮次持久);
/// `is_eval_script` 为 true 时顶层 var/function 声明落全局属性 configurable:true。
/// 顶层 var 的全局对象属性在求值开始前统一创建(值 undefined),声明语句
/// 保持赋值语义——声明语句出现之前的读取(typeof/反射/自引用)即见绑定。
pub fn emit_program(
&self, program: &oxide_parser::Program, repl_persist: bool, is_eval_script: bool,
) -> Result<IRFunction, String> {
crate::emit_debug!("emit_program: {} stmts", program.body.len());
let mut ctx = CompileCtx::new();
// 脚本顶层:var/function 声明需落到全局对象,let/const/class 不进全局。
ctx.is_global_scope = true;
ctx.repl_persist = repl_persist;
ctx.is_eval_script = is_eval_script;
// eval 起源随嵌套函数继承,供 delete 分类判定 eval 顶层可删名;与
// `is_eval_script` 区分——后者只标识当前顶层程序,传播会误改全局写点。
ctx.is_eval_origin = is_eval_script;
ctx.source_encoded = self.source_encoded;
// 脚本顶层严格模式由源码 "use strict" directive 决定(嵌套函数经父 ctx 继承)。
ctx.is_strict = program.has_use_strict_directive();
// eval 代码顶层函数声明撞不可写全局内置(三常量的全局绑定在任何符合规范的
// 实现中均不可配置,声明实例化无法建立全局绑定):规范在建立全局 var 绑定
// 之前抛 TypeError(step 8 的 abrupt 先于绑定实例化),故 throw 发为程序首
// 指令,其后预声明/序言/声明发射均不可达(寄存器保持良定义,运行期零开销)。
// 严格 eval 代码函数声明绑定 eval 自身 lexical 环境、不触全局、不抛,门禁
// 随 !is_strict 关闭,其写点抑制在声明发射处。
if ctx.is_eval_script && !ctx.is_strict {
for name in collect_top_level_function_names_ordered(&program.body) {
if NON_WRITABLE_GLOBAL_BUILTINS.contains(&name.as_str()) {
let _ = self.emit_throw_error(
"TypeError",
&format!("Cannot declare function '{name}': global property is not configurable"),
&mut ctx,
)?;
break;
}
}
}
// GDI step 9 函数臂检查阶段(运行期,先于任何绑定实例化):普通脚本
// 顶层函数声明撞既有不可配置全局属性抛 TypeError,sloppy/strict 同形。
// eval 面零触碰:静态三常量面由上方 eval 三常量编译期门禁拦,动态臂另归口。
if !ctx.is_eval_script {
self.emit_gdi_func_decl_checks(&program.body, &mut ctx);
}
self.predeclare_function_declarations(&program.body, &mut ctx);
// 预注册 builtin 引用(先于任何临时寄存器),builtin 槽不进入临时寄存器池。
// 顶层词法声明名(let/const/class)排除登记:词法绑定遮蔽同名全局属性,
// 镜像占位先建会与词法预声明撞 scope 0 致合法遮蔽形被编译期误拒。
let lexical_excluded = collect_direct_lexical_names(&program.body);
self.pre_register_builtin_references(&program.body, &lexical_excluded, &mut ctx);
// 预声明顶层 `var` 名,使首个 sub-pass 中提升的函数声明能解析外层 var。
self.predeclare_var_declarations(&program.body, &mut ctx);
// 预声明顶层 `let`/`const`/`class`(未初始化 TDZ 占位)。
self.predeclare_lexical_declarations(&program.body, &mut ctx)?;
// 顶层块级函数名按 sloppy 模式下与浏览器/web 实现惯例兼容的行为处理
// (块级函数声明建外层 var 绑定并求值写回;sloppy eval 代码同走
// Annex B.3.3.3,其名并入 eval 的 var 环境):实例化 var 绑定
// (新建 var 槽;顶层只读三常量名不可声明不建)、并入顶层 var 名集
// (裸读/写路由全局对象属性)、GlobalDeclarationInstantiation 序言建属性
// (define-if-absent,既有属性不改动值)。strict 代码不建。
let mut block_fn_names: Vec<String> = Vec::new();
if !ctx.is_strict {
ctx.block_fn_suppressed = self.collect_block_fn_suppressed_names(&program.body, &ctx.param_names);
block_fn_names = self.collect_block_function_names(&program.body);
for name in &block_fn_names {
if !ctx.block_fn_suppressed.contains(name) && !CompileCtx::is_non_writable_global_builtin(name) {
self.predeclare_var_name(name, &mut ctx);
}
}
}
// 闭包捕获分析(AST 级,emit 前确定)
ctx.own_bindings = collect_own_binding_names(&[], &program.body);
// 顶层已声明名(全局对象属性为唯一存储):裸读走全局对象属性、裸写走感知
// 全局对象属性描述符的写,不落引擎侧镜像副本——从捕获集剔除,使嵌套函数经
// 继承 scope 0 直连全局。
// 集 = 顶层 var 名 ∪ 顶层函数声明名:函数值是编译闭包,编译期不可得,
// 其全局对象属性值由首个 sub-pass(`emit_program` 顶层的内部阶段划分:先
// 声明实例化、后用户代码发射)以真闭包建立,先于任何用户代码。
// 仅在此顶层调用点过滤:嵌套函数的局部同名遮蔽是独立绑定,其调用点不过滤。
let var_names = collect_var_binding_names(&program.body);
let mut tier_names = var_names.clone();
tier_names.extend(collect_top_level_function_names(&program.body));
// 块级函数泄漏名并入:求值期写回与块入口头写同走全局对象属性(全局对象
// 属性为唯一存储)。
for name in &block_fn_names {
if !ctx.block_fn_suppressed.contains(name) && !CompileCtx::is_non_writable_global_builtin(name) {
tier_names.insert(name.clone());
}
}
ctx.global_tier_names = tier_names;
ctx.set_captured_bindings(collect_captured_bindings(&program.body, &[], &ctx.own_bindings));
// 顶层 tier 名(顶层 var/函数)的全局对象属性是唯一存储,从捕获集剔除;
// 被同名块级绑定(catch 参数 / 块级 let/const)遮蔽时保留,使块级绑定能
// 追加新索引供闭包捕获(顶层 var 本身不建 cell,回填与解析点均跳过)。
let block_level_names = collect_block_level_decl_names(&program.body);
ctx.captured_bindings
.retain(|n, _| !ctx.global_tier_names.contains(n) || block_level_names.contains(n));
// 顶层函数级绑定 cell 索引回填:预声明已完成,绑定已齐。
ctx.backfill_captured_cell_idxs();
// 顶层 var 入口实例化:被捕获的 var 名统一 MAKE_CELL(undefined),使 var
// 声明语句执行前创建的闭包读取到 undefined(脚本 GlobalDeclarationInstantiation
// 语义),而非占位 cell 的 TDZ 误报。声明语句的 MAKE_CELL 覆盖此初值。
// 名集保持 var-only:函数声明名从不入捕获集(tier 剔除),无需入口 cell。
// tier 名(全局对象属性为唯一存储)不建入口 cell。
let mut var_names: Vec<String> = var_names
.into_iter()
.filter(|n| ctx.captured_bindings.contains_key(n) && !ctx.global_tier_names.contains(n))
.collect();
// HashSet 迭代序带随机种子,排序后入口 MAKE_CELL 发射序跨进程稳定。
var_names.sort();
if !var_names.is_empty() {
let undef_reg = self.emit_undefined(&mut ctx);
for name in var_names {
if let Some(&cell_idx) = ctx.captured_bindings.get(&name) {
ctx.inst(Inst::new(
OpCode::MAKE_CELL,
Operand::Reg(undef_reg),
Operand::Imm(cell_idx as u16),
Operand::None,
));
}
}
}
// 被捕获顶层词法(let/const/class)入口 TDZ 占位 cell:声明语句执行前
// 创建的闭包指向此未初始化 cell,声明前读写经运行时抛真 TDZ;声明语句
// 的 MAKE_CELL 按占位更新语义原位翻转为已初始化。名集取捕获集交集
// (直接子级词法名,块级名不提升到顶层),排序保证发射序跨进程稳定。
let mut lex_names: Vec<String> = collect_direct_lexical_names(&program.body)
.into_iter()
.filter(|n| ctx.captured_bindings.contains_key(n))
.collect();
lex_names.sort();
if !lex_names.is_empty() {
let undef_reg = self.emit_undefined(&mut ctx);
for name in lex_names {
if let Some(&cell_idx) = ctx.captured_bindings.get(&name) {
// 未初始化标志折入 16 位立即数高字节(0x0100),dispatch 侧
// 按字节拆回两字段。
ctx.inst(Inst::new(
OpCode::MAKE_CELL,
Operand::Reg(undef_reg),
Operand::Imm(cell_idx as u16 | 0x0100),
Operand::None,
));
}
}
}
// 全局声明实例化序言:脚本求值前为顶层 var 名创建全局对象属性(值 undefined),
// 使声明语句执行前的读取(typeof、反射、自引用)可经全局对象见绑定。
// 顶层函数声明名不进序言:函数值是编译闭包,编译期不可得,其全局对象属性值
// 由首个 sub-pass 的声明写以真闭包建立,先于任何用户代码(首个 sub-pass 仅发
// 函数声明),无 undefined 读窗口。
// CreateGlobalVarBinding 对既有属性不改动值:define-if-absent 只在属性缺失时
// 新建,可写/不可写/可配置既有属性(含值)一律保留。内置名的全局属性
// 运行期预存(session 绑定):缺失分支写入值取内置名镜像槽(本轮求值开始前
// 把全局属性值预载进固定寄存器槽),既有属性不改动值,值幂等保留。
// 序言名集 = 顶层 var 名 ∪ 顶层块级函数泄漏名(sloppy 模式浏览器兼容行为的
// 外层绑定同样在求值前实例化,同走 define-if-absent)。
let mut gdi_var_names: Vec<String> = collect_var_binding_names(&program.body).into_iter().collect();
gdi_var_names.extend(
block_fn_names
.iter()
.filter(|n| !ctx.block_fn_suppressed.contains(*n) && !CompileCtx::is_non_writable_global_builtin(n))
.cloned(),
);
// HashSet 迭代序带随机种子,排序后 GDI 序言与内置镜像槽分配序跨进程稳定。
gdi_var_names.sort();
if !gdi_var_names.is_empty() {
let undef_reg = self.emit_undefined(&mut ctx);
for name in &gdi_var_names {
let value_reg = if CompileCtx::is_known_builtin(name) {
// 内置名镜像槽未预登记时(解构 pattern 名等)就地登记,本轮求值
// 开始前预载全局属性值。
ctx.lookup_or_builtin(name).unwrap_or(undef_reg)
} else {
undef_reg
};
self.emit_global_prop_write_if_absent(name, value_reg, &mut ctx);
}
}
// 首个 sub-pass:发函数声明(hoisting),保证任何代码运行前函数对象已就绪。
for stmt in &program.body {
if matches!(stmt, Statement::FunctionDeclaration(_)) {
self.emit_statement(stmt, &mut ctx)?;
}
}
// 第二个 sub-pass:发其余所有语句。
let mut last_result: Option<u32> = None;
// directive 序言是 AST 独立字段(顶格字符串字面量,不在 body):作为语句
// 序列的头前缀按源序先于 body 发射。完成值即字符串值本身,参与"最后非空
// 完成值"收敛;严格模式标志由 has_use_strict_directive 独立处理,此处不涉。
for dir in &program.directives {
last_result = Some(self.emit_string_literal_expression(&dir.expression, &mut ctx)?);
}
for stmt in &program.body {
if matches!(stmt, Statement::FunctionDeclaration(_)) {
continue; // Already emitted above
}
// 空完成值语句(变量/函数等声明)不覆写此前结果:保留最后一个非空
// 完成值,与函数体 emit_body_stmts 的收敛口径一致。
if let Some(r) = self.emit_statement(stmt, &mut ctx)? {
last_result = Some(r);
}
}
if let Some(r) = last_result {
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::None, Operand::Reg(r), Operand::None));
} else {
let undef_idx = ctx.add_constant(Constant::Undefined);
ctx.inst(Inst::load_const(Operand::None, undef_idx));
}
ctx.inst(Inst::new(OpCode::HALT, Operand::None, Operand::None, Operand::None));
let ir = ctx.assemble_ir(
oxide_ir::ParamLayout {
// 顶层模块无父函数:base 恒 0。base 若误含父函数槽偏移,会把模块自身
// 低号虚拟寄存器误判为父槽,寄存器分配的恒等约束(identity 约束)
// 随之收缩可分配色集并增加 spill。
base: 0,
count: 0,
},
None,
);
Ok(ir)
}
}
#[cfg(test)]
mod tests {
use super::{BUILTIN_GLOBALS, NON_WRITABLE_GLOBAL_BUILTINS};
/// 漂移守卫:只读名单是 builtin 母集子集,且只读/可写两谓词对母集构成
/// 划分(不重不漏)——防三常量集或母集改名/删名时拦截面与双写面单边漂移。
#[test]
fn readonly_and_writable_partition_builtin_globals() {
for name in NON_WRITABLE_GLOBAL_BUILTINS {
assert!(BUILTIN_GLOBALS.contains(name), "只读全局名缺失于 builtin 名单:{name}");
}
for name in BUILTIN_GLOBALS {
let readonly = NON_WRITABLE_GLOBAL_BUILTINS.contains(name);
let writable = crate::CompileCtx::is_writable_builtin_global(name);
assert!(readonly ^ writable, "builtin 名只读/可写归属漂移:{name}");
}
}
/// 漂移守卫:可删名集自可写划分派生(可删 ⊆ 可写、只读三常量不入可删),
/// 且宿主名 $262 恒不可删——防派生口径改动时 delete 面单边漂移。
#[test]
fn deletable_global_builtins_derive_from_writable_partition() {
for name in BUILTIN_GLOBALS {
let deletable = crate::CompileCtx::is_deletable_global_builtin(name);
let writable = crate::CompileCtx::is_writable_builtin_global(name);
assert!(!deletable || writable, "可删名缺失于可写划分:{name}");
if NON_WRITABLE_GLOBAL_BUILTINS.contains(name) {
assert!(!deletable, "只读三常量名误入可删集:{name}");
}
}
assert!(!crate::CompileCtx::is_deletable_global_builtin("$262"), "不可删名误入可删集:$262");
}
}