Skip to content

Native Functions

Liu.Yandong.Hanks edited this page Aug 26, 2026 · 1 revision

Native 函数(native func

显式声明可编译为 CLR 原生 ABI 的模块函数,用于热路径上的直接调用与零分配数值内核。

适用于 4.0.0

首页 · 类型与强类型 · 性能与基准测试

Important

native func 与「原生模块」不是同一概念。

  • native func:脚本里用 native func 声明的函数,编译器为其生成 $native 入口(见下文)。
  • 原生模块:宿主通过 BuiltInModules 启用的 fshttp 等模块(见 fs 原生模块http 原生模块)。
本页目录

概述

AuroraScript 的普通 func 使用灵活的 ScriptDatum 调用约定:参数装箱、闭包帧、热补丁友好,但热循环中的数值调用会有额外开销。

native func模块作用域上的显式 ABI 契约。编译器为每个 native 函数生成:

  1. name$native:接收 ScriptContext 与声明类型的 CLR 参数,函数体直接发射到此方法;不推送脚本调用帧,也不包装 try/catch。
  2. name$typed(按需):与 ScriptDatum 兼容的壳层,供宿主 Execute、未证明类型的脚本调用,以及把函数当作值逃逸时使用。

因此,native func 既是性能优化手段,也是语义边界:它不改变 AuroraScript 仍是动态脚本语言这一事实,但为已声明的热路径提供可预测的原生 CIL 表示。

何时使用

在以下情况考虑 native func

  • 热循环或模块边界上反复调用的数值 / 布尔 / packed array 辅助函数。
  • 需要稳定参数与返回类型契约,且调用方参数在编译期可证明兼容。
  • 希望跨模块导入后,证明兼容的调用直接走 $native,避免通用动态分派。

不应在以下情况使用:

  • 需要 HotPatch 增删改函数(native 函数不可热更新)。
  • 需要默认参数、rest 参数(...)或 $args
  • 需要把函数重新赋值或当作可替换的回调槽(native 函数不可赋值)。
  • 整个 API 都应保持完全动态、频繁变更且依赖热补丁——请继续使用普通 func

语法与 ABI 契约

native上下文关键字:仅修饰模块作用域的 func / function 声明;在其他位置仍是合法标识符。

@module(MATH);

// 私有 native 辅助函数
native func add(Number left, Number right) Number {
    return left + right;
}

// 导出的 native 入口
export native func distance(Number dx, Number dy) Number {
    return Math.sqrt(dx * dx + dy * dy);
}

export func run() Number {
    return add(20, 22);
}

ABI 要求:

要求 说明
模块作用域 仅模块顶层;不能嵌套在块或另一函数内。
显式返回类型 必须写 NumberBooleanStringObject、数组类型等返回契约;省略会编译失败。
显式参数类型 参数类型决定原生槽位;未声明类型的参数走 ScriptDatum
无默认 / rest / $args 签名必须固定,便于生成稳定 $native 方法。
不可赋值 不能对 native 函数名做 =+= 等赋值。
正常构建 仅通过完整编译引入或修改;不能通过热补丁添加、替换或重定义。

可配合 export 导出;私有 native 函数仍可在模块内被证明兼容的调用直接命中 $native

优化机制

1. 原生方法体($native

编译器将函数体直接发射到 name$native,第一个参数为隐式的 ScriptContext(脚本源码中不可见)。声明为 NumberBooleanInt32 流以及 packed array 的参数与局部变量,在证明稳定时保持为 CLR 原生表示(doubleboolint、packed 存储等),避免在算术循环中反复构造 ScriptDatum

2. 动态边界(Datum 转换)

函数体内仍可使用完全动态的表达式(例如读取 Object 属性、调用普通 func)。编译器仅在跨越动态边界的表达式处转换为 ScriptDatum不会因此让整个函数回退到纯动态路径。其余证明稳定的局部变量继续走原生 CIL。

export native func weighted(Number value, Object options) Number {
    // value 保持 CLR double;options.factor 的动态读取仅在该表达式处跨 Datum 边界
    return value * options.factor;
}

3. 直接调用分派

调用场景 行为
同模块内,参数类型已证明兼容 直接 call $native
同模块内,参数未证明兼容 $typed 壳层,保留精确参数检查
跨模块导入的 export native func,参数已证明兼容 直接调用导入模块的 $native
跨模块,参数未证明兼容 经导出壳层(ScriptDatum 路径)
宿主 ScriptDomain.Execute / 动态调用 $typed 壳层

私有 native 函数若作为值逃逸(例如 return increment),调用方同样走壳层路径。

4. 与帧管理和内联

$native 为每次调用推送脚本帧,也包裹额外异常适配器。这使热路径更接近手写 CIL 辅助方法。编译器对 $native 使用积极内联提示;是否内联仍由 CLR/JIT 决定。

支持的类型与调用路径

可进入原生 ABI 的参数类型

声明为下列类型时,参数可映射到 CLR 原生槽位(否则为 ScriptDatum):

脚本类型 CLR 表示(概念上)
Number double
Boolean bool
String string
通用 Array ScriptArray
Int8ArrayUInt64ArrayFloat64ArrayBooleanArray 对应 packed 存储
Object 及其他 ScriptDatum

返回类型

  • Number / 整数流 → doubleint
  • Booleanbool
  • 其他声明返回 → ScriptDatum(例如 StringObject

若签名无法生成合法的原生方法(绑定/发射阶段无法构造 $native),编译器报错:cannot be emitted with a native signature

调用方参数证明

即使被调函数是 native func调用方仍须在每个实参上满足类型证明,才会走直接 $native 路径。把 packed array 存入普通 Object 再读回,会擦除元素类型,调用回退到动态壳层——这与 性能与基准测试 中的「强类型路径」建议一致。

函数体内的动态边界

Native 函数不是「整函数静态类型化」:

  • 仍可使用 throw、动态属性访问、普通闭包调用与模块访问(经 ScriptContext)。
  • 动态表达式局部跨越 Datum 边界;不会禁用函数内其他原生局部变量。
  • 需要 $args、可变 arity 或默认参数时,应使用普通 func,或把可变部分拆到普通函数,由 native 内核处理固定 arity 部分。

错误与调用栈

$native 不推送脚本调用帧。运行时出错时,引擎通过 CLR 异常栈 与已记录的脚本源位置合并栈信息:RuntimeExceptionStackAnalyzer$native 帧与脚本追踪关联,使 AuroraRuntimeException 仍能报告 native 函数名与脚本位置。

从 native 函数内部 throw new Error(...) 时,栈追踪可包含 native callee 与 native caller 名称(见引擎测试 NativeDirectCallsPreserveScriptStackFrames)。

限制与不可做的事

限制 后果
不可热补丁 HotPatch.replace / incremental 不得添加、替换或重定义 native 函数;违反时编译失败。
不可赋值 不能把 native 函数绑定到其他变量槽位。
固定签名 无默认参数、无 ...rest、函数体内不可用 $args
必须声明返回类型 无返回类型注解的 native 声明被拒绝。
仅正常构建 修改 native 函数需重新 BuildAsync,不能靠运行时补丁。
非全局优化开关 没有「自动把普通 func 提升为 native」的引擎选项;必须显式写 native func
函数注解已移除 4.0 不再支持 @directCall 等函数注解;请改用 native func

与普通 func 的对比

普通 func native func
调用约定 ScriptDatum / 闭包 $native + 可选 $typed 壳层
返回类型 由 flow 推断 必须显式声明
参数默认值 / rest 支持 不支持
$args 支持 不支持
热补丁 支持(宿主启用时) 不支持
赋值 / 替换 支持 不支持
热路径分配 可能有装箱 证明路径上零分配(见基准测试)
适用场景 通用业务逻辑 稳定 ABI 的热内核

与编译器类型推断的关系

AuroraScript 的「强类型路径」主要依赖 flow 分析,而非源码上的全面类型注解:

  1. 稳定数值局部、布尔条件、整数循环变量、已知 packed array → 原生 CIL 局部变量。
  2. Packed array → 连续原始存储,而非每元素一个 ScriptDatum
  3. native func → 显式函数调用 ABI,避免通用属性查找与动态分派。

普通 func 在推断足够时同样能在函数体内使用原生局部变量;native func 额外固定入口/出口跨函数调用的 ABI。若只需循环内优化,可先保持普通 func 并遵循 性能与基准测试 的编写建议;当模块边界调用本身成为瓶颈时,再提升为 native func

示例

数值内核与同模块调用

@module(KERNEL);

native func clamp(Number value, Number min, Number max) Number {
    if (value < min) return min;
    if (value > max) return max;
    return value;
}

export func run(Number input) Number {
    return clamp(input, 0, 100);
}

跨模块直接 native 调用

// math.as
export native func add(Number left, Number right) Number {
    return left + right;
}

// main.as
@module(APP);
import math from "./math";

export func run() Number {
    return math.add(20, 22);
}

math.add 的两个实参在 run 中证明为 Number 时,发射代码直接调用 math 模块的 add$native

导出 native 与宿主调用

@module(API);

export native func add(Number a, Number b) Number {
    return a + b;
}

宿主 ScriptDomain.Execute("API", "add", ...)$typed 壳层调用;同模块内证明兼容的脚本调用可走 $native

后续步骤

Clone this wiki locally