# 40-PROMISE.md

本文件定义 MCS-251 工具链对嵌入程序、汇编模块、对象文件和链接结果保持稳定的语义承诺。命令行参数只在 `20-COMMAND.md` 中定义；源码语法只在 `30-GRAMMAR.md` 中定义；中间产物与输出文件格式只在 `90-PRODUCT.md` 中定义。

## ABI 版本与兼容承诺

本文件中的规则均视为 ABI 承诺。若修改这些规则，必须视为 ABI 变更，并在版本说明中明确记录兼容性影响。

对象文件、`.map` 和 `.mem` 应记录工具链 ABI 版本，便于检查不同对象文件和链接结果是否采用兼容规则。

## 基础字节序

除指令编码、外设寄存器硬件行为或单独说明的特殊访问规则外，多字节整数、指针、地址和复合对象默认按大端顺序存放，高有效字节在前。

示例：

| 值 | 宽度 | 存储字节顺序 |
| --- | ---: | --- |
| `0x1234` | 16 bit | `12 34` |
| `0x12345678` | 32 bit | `12 34 56 78` |

## 基础 C 对象表示

基础整数类型的对象表示与 `30-GRAMMAR.md` 中的类型宽度保持一致，并作为 ABI 稳定承诺。

| 类型 | 宽度 | 对象表示 |
| --- | ---: | --- |
| `_Bool` | 8 bit | 普通对象存储为 `0` 或 `1` |
| `char` | 8 bit | 默认等价于 `unsigned char` |
| `signed char` | 8 bit | 二进制补码 |
| `unsigned char` | 8 bit | 无符号二进制 |
| `short` / `signed short` | 16 bit | 二进制补码，大端 |
| `unsigned short` | 16 bit | 无符号二进制，大端 |
| `int` / `signed int` | 16 bit | 二进制补码，大端 |
| `unsigned int` | 16 bit | 无符号二进制，大端 |
| `long` / `signed long` | 32 bit | 二进制补码，大端 |
| `unsigned long` | 32 bit | 无符号二进制，大端 |

位对象不使用普通 `_Bool` 的 8 bit 对象表示。`__bit`、`__ebit`、`__sbit` 和 `object.N` 占用单 bit 存储；读取时按 `_Bool` 语义产生 `0` 或 `1`。

## 复合对象布局承诺

结构体、联合体、数组和位域的语法规则由 `30-GRAMMAR.md` 定义。本文件只给出布局层承诺。

- 结构体成员默认紧凑排列，不自动插入填充字节。
- 数组元素按元素对象表示连续排列。
- 联合体所有成员从同一偏移开始，联合体大小至少能容纳最大成员。
- `__align(n)` 对对象或成员生效时，插入满足该对象或成员对齐所需的最小填充。
- `__packed` 结构体或联合体内的位域可跨字节分配；非 `__packed` 位域默认不跨字节。
- 位域按声明顺序从当前字节 bit7 向 bit0 分配；进入下一字节时仍从 bit7 开始。
- 位域字段值 bit 与存储 bit 的逐位映射按“大端位序直观规则”处理：字段最高有效 bit 放在该字段占用范围中最靠前的存储 bit。

## 默认存储区布局

未通过命令行覆盖时，工具链采用以下默认布局：

| 存储区 | 默认起始地址 | 默认大小 | 含义 |
| --- | ---: | ---: | --- |
| `code` | `0x000000` | `0x010000` | 业务程序代码区 |
| `xram` | `0x000000` | `0x000000` | 扩展 RAM；默认不假设存在 |
| `eram` | `0x000000` | `0x000100` | 增强内部 RAM 兼容区；默认至少覆盖低 256 字节数据空间 |

`data`、`idata`、`bit`、`bdata` 不作为独立命令行存储区配置。它们由 `eram` 低地址兼容区派生，并由编译器、汇编器和链接器共同保证访问方式与布局约束。

## 派生存储区分配范围

以下范围约定用于编译器、汇编器和链接器进行自动分配、绝对地址校验和位操作合法性检查。未列出的存储区或扩展属性不在本节定义。

| 修饰/区域 | 自动分配范围 | 说明 |
| --- | --- | --- |
| `__data` | 默认从 `0x30` 起分配 | `0x00..0x1f` 保留给寄存器组和宽寄存器相关用途；`0x20..0x2f` 保留给传统可位寻址兼容区 |
| `__bdata` | `0x20..0x2f` | 传统可位寻址字节区 |
| `__bit` | 传统 bit 地址区 | 只使用传统兼容 bit 地址模型，不自动使用 MCS-251 扩展位地址 |
| `__sbit` | SFR 位地址 | 面向整个 SFR 区的位绑定；根据地址格式和可编码范围选择传统 bit 指令或 MCS-251 扩展 bit 指令 |
| `__ebit` | `0x30.0..0x7f.7` | 使用 MCS-251 扩展 bit 地址模型；自动分配避开 `0x00..0x1f` 和传统可位寻址兼容区 |

MCS-251 扩展 bit 操作的 data 地址合法范围为 `0x20.0..0x7f.7`。`0x00.0..0x1f.7` 不属于合法扩展 bit 操作范围，汇编器和编译器不得生成该范围的扩展 bit 操作。

普通 `__data` 自动分配不得占用 `0x00..0x1f`。该范围保留给四组通用寄存器映射、宽寄存器相关用途和编译器中间运算资源。

## `code` 对象承诺

- `__code` 对象位于 code 空间。
- `__code` 对象在 C 语义上只读，即使未写 `const` 也不得通过普通 C 左值写入。
- `const` 不自动表示 `__code`；对象进入 code 空间必须显式使用 `__code`。
- 字符串字面量默认位于 `__code`。
- `__code __at(addr)` 对象若超出当前 code 布局范围，普通模式和严格模式均只诊断为警告，不改变其语义，并允许继续生成产物；产物可能超出目标可执行或可烧录范围。

## 指针模型

MCS-251 工具链采用“显式窄指针 + 4 字节通用指针”的模型。

- 显式存储区指针可按其存储区使用较窄表示，由编译器在类型明确时优化。
- 通用指针固定为 4 字节：其中 3 字节为地址值，另 1 字节为指针空间类型 tag。
- 数据空间、代码空间中已进入通用指针 tag 表的窄指针，可以在需要时转换为通用指针。
- 明确类型的函数指针表示 24 位代码入口地址。
- 函数指针显式转换为通用指针时，转换结果使用 4 字节通用指针表示。
- 其它窄数据指针显式转换为通用指针时，也统一转换为 4 字节通用指针表示。

通用指针对象格式为：

```text
tag:addr[23:16]:addr[15:8]:addr[7:0]
```

含义：

- `tag` 保存指针空间类型标记。
- `addr[23:16]:addr[15:8]:addr[7:0]` 保存 24 位地址值。
- tag 编码表另行在本文件的“指针 tag 编码表”中定义；未定义的 tag 值保留。
- 通用指针作为参数或返回值传递时，使用调用约定章节定义的寄存器槽；通用指针对象格式不要求物理使用 DPX 或 B 寄存器。

### DPX / DPTR 地址格式承诺

指针和地址寄存器按地址数值解释时，必须遵守以下格式：

| 地址宽度 | 寄存器格式 | 位含义 |
| --- | --- | --- |
| 16 bit | `DPH:DPL` | `DPH = addr[15:8]`，`DPL = addr[7:0]` |
| 24 bit | `DPXL:DPH:DPL` | `DPXL = addr[23:16]`，`DPH = addr[15:8]`，`DPL = addr[7:0]` |

`DPL` 永远表示地址低 8 位；`DPH` 永远表示 16 位地址高 8 位；`DPXL` 表示 24 位地址扩展高 8 位。默认大端对象表示不改变 DPX / DPTR 作为硬件地址寄存器时的位含义。

### 指针 tag 编码结构

通用指针 tag 为 8 bit，拆分为高 4 bit 大类别和低 4 bit 具体类型：

```text
tag = (category << 4) | kind
```

规则：

- `category` 表示指针可解引用空间的大类别。
- `kind` 表示该大类别下的具体访问类型。
- 判断大类别时使用 `tag & 0xf0`。
- 未定义的 `category` 或 `kind` 值均为保留值。
- `sfr`、`xfr`、`bit`、`ebit` 不进入普通通用指针 tag 表；它们不是普通 C 指针可解引用空间。

已冻结的大类别：

| category | 大类别 | 含义 |
| ---: | --- | --- |
| `0x0` | special | 空指针、无效值和保留特殊值 |
| `0x1` | data | 可解引用数据空间 |
| `0x2` | code | 代码地址空间 |
| `0x3` | reserved | 保留 |
| `0x4..0xf` | reserved | 保留 |

本节列出的 tag 编码为 ABI 冻结值；未列出的 `category` 或 `kind` 值均为保留值。工具链不得生成保留 tag；运行期遇到保留 tag 时，应按无效通用指针诊断或进入安全失败路径。

已冻结的 special 类编码：

| tag | category.kind | 含义 |
| ---: | --- | --- |
| `0x00` | `special.0` | null 指针 |
| `0x01..0x0f` | `special.*` | 保留 |

已冻结的 data 类编码：

| tag | category.kind | 含义 |
| ---: | --- | --- |
| `0x10` | `data.0` | 保留 |
| `0x11` | `data.1` | `data` 直接寻址数据 |
| `0x12` | `data.2` | `idata` 间接寻址数据 |
| `0x13` | `data.3` | `edata` 增强内部 RAM 数据 |
| `0x14` | `data.4` | `xdata` 数据，对应 xram 布局 |
| `0x15..0x17` | `data.*` | 保留 |
| `0x18` | `data.8` | `pdata` 传统分页数据，兼容保留 |
| `0x19..0x1f` | `data.*` | 保留 |

规则：

- `bdata` 不独立分配通用指针 tag；按字节访问时使用 `data`，按 bit 访问不走通用指针。
- tag 优先表达访问模型；地址值相同但 tag 不同的通用指针，不视为同一解引用语义。

已冻结的 code 类编码：

| tag | category.kind | 含义 |
| ---: | --- | --- |
| `0x20` | `code.0` | `code` 地址空间；可表示只读对象地址，也可表示显式转换为通用指针后的函数入口地址 |
| `0x21..0x2f` | `code.*` | 保留 |

函数指针本身仍由 C 类型系统区分；只有在显式转换为通用指针时，才使用 `code` 类 tag 表示其代码地址。运行期通用指针 tag 不单独区分“代码对象地址”和“函数入口地址”。

### 指针空值与比较规则

通用指针空值固定为 4 字节全零：

```text
tag:addr[23:16]:addr[15:8]:addr[7:0] = 00:00:00:00
```

专用窄指针空值按自身宽度全零表示：

| 指针类型 | 空值表示 |
| --- | --- |
| 16 位专用指针 | `00:00` |
| 24 位专用指针 | `00:00:00` |
| 明确类型的函数指针 | `00:00:00` |

指针比较遵循以下规则：

- 同类型窄指针比较只比较地址值。
- 通用指针比较完整的地址值与 tag。
- 窄指针与通用指针比较时，先将窄指针转换为通用指针，再比较完整地址值与 tag。
- 与空指针常量比较时，按空指针语义判断；窄指针只检查自身地址是否为零，通用指针检查是否为 4 字节全零。

### 指针转换规则

窄指针转换为通用指针时，按源指针类型补齐地址宽度，写入对应 tag，不改变指向对象。

通用指针转换为窄指针时遵循以下规则：

- tag 与目标窄指针空间匹配时，允许转换。
- tag 与目标窄指针空间不匹配时，隐式转换报错。
- tag 与目标窄指针空间不匹配但存在显式强转时，允许转换并给出警告。
- 转换结果按目标窄指针宽度截断地址；被截断的高位非零时，普通模式警告，严格模式报错。

窄指针之间转换遵循以下规则：

- 同一空间内不同对象类型的窄指针转换允许。
- 不同空间的窄指针隐式转换报错。
- 不同空间的窄指针显式强转允许，并给出警告。
- 地址宽度收窄时，被截断的高位非零，普通模式警告，严格模式报错。

## 寄存器堆 ABI 基础

ABI 将寄存器堆分为以下用途：

| 范围 | ABI 用途 | 说明 |
| --- | --- | --- |
| `R0..R7` | 剩余 8 位参数候选槽 | 受当前寄存器组选择影响，只用于 8 位剩余参数 |
| `R8..R11` | 不作为 ABI 参数主槽 | 其中 `R10`、`R11` 分别对应特殊寄存器 `B`、`ACC` |
| `R12..R15` / `DR12` | 首个参数与返回值主槽 | 固定寄存器堆，不受当前寄存器组选择影响 |
| `R16..R31` | 剩余 16/32 位参数候选槽 | 固定寄存器堆，用完整 `WR` / `DR` 槽传递宽参数 |
| `DR56` | 数据指针专用 | 不作为普通 ABI 参数或返回值槽 |
| `DR60` | 栈指针专用 | 函数返回时必须恢复到入口值 |

### 首个参数与返回值

首个参数与返回值固定使用 `DR12` 这一组寄存器。小于 32 bit 的值在 `DR12` 内按低位右对齐：

| 宽度 | 寄存器位置 | 说明 |
| ---: | --- | --- |
| 8 bit | `R15` | 低 8 位 |
| 16 bit | `WR14` | `R14:R15` |
| 24 bit | `R13:R14:R15` | 高到低排列 |
| 32 bit | `DR12` | `R12:R13:R14:R15` |

通用指针作为首个参数或返回值时使用完整 `DR12`，格式为：

```text
R12:R13:R14:R15 = tag:addr[23:16]:addr[15:8]:addr[7:0]
```

明确类型的 24 bit 指针或函数指针作为首个参数或返回值时使用 `R13:R14:R15`；`R12` 不属于该返回值或参数的一部分。

### 结构体与联合体返回

结构体和联合体按最终对象大小选择返回方式：

- 大小不超过 4 字节时，返回值按对象表示右对齐放入 `DR12`。
- 1 字节返回使用 `R15`；2 字节返回使用 `WR14`；3 字节返回使用 `R13:R14:R15`；4 字节返回使用完整 `DR12`。
- 大小超过 4 字节时，由调用方提供返回对象存储地址；该地址作为隐藏首参传递，用户源码中的第一个显式参数顺延为第二个参数。
- 结构体和联合体返回对象大小必须在编译期确定。

### 剩余参数寄存器分配

从第二个参数开始，按源码声明顺序逐个分配剩余寄存器参数。每个参数根据自身 ABI 宽度选择对应候选槽；若候选槽覆盖的任意字节已被占用，则该槽不可用，并继续向下查找。

候选槽按以下顺序查找：

| 参数 ABI 宽度 | 候选槽顺序 |
| ---: | --- |
| 8 bit | `R7`, `R6`, `R5`, `R4`, `R3`, `R2`, `R1`, `R0` |
| 16 bit | `WR30`, `WR28`, `WR26`, `WR24`, `WR22`, `WR20`, `WR18`, `WR16` |
| 32 bit | `DR28`, `DR24`, `DR20`, `DR16` |

24 bit 专用指针、明确类型的函数指针和通用指针作为剩余参数时均按 32 bit 槽分配。24 bit 专用指针和明确类型的函数指针在 32 bit 槽内低 24 bit 右对齐，最高 8 bit 不属于该参数值；通用指针使用完整 32 bit 槽，格式为 `tag:addr[23:16]:addr[15:8]:addr[7:0]`。

示例：

```c
f(first, int a, long b, int c);
```

分配结果：

| 参数 | 寄存器 |
| --- | --- |
| `first` | `DR12` 组内按宽度右对齐 |
| `a` | `WR30` |
| `b` | `DR24` |
| `c` | `WR28` |

其中 `b` 不能使用 `DR28`，因为 `DR28` 覆盖 `WR30` 已占用的字节。

### 普通函数寄存器保存规则

普通函数调用约定默认采用 caller-saved 策略。除本节明确列出的必须保持项外，调用方在调用后不得假设参数候选寄存器或普通临时寄存器保持调用前的值。

- `DR12` 是首个参数和返回值主槽；函数返回后，`DR12` 组内内容只按返回值语义保证。
- `R0..R7`、`R8..R11`、`R16..R31` 均为 caller-saved；若调用方需要保留其中内容，必须在调用前自行保存并在调用后恢复。
- `ACC` 和 `B` 作为 `R11`、`R10` 对应的特殊寄存器，也按 caller-saved 处理。
- `DR56` / DPX 作为数据指针专用寄存器，普通函数调用后默认不保证保持。
- `SPX` / `DR60` 是硬件栈指针；被调用方返回前必须恢复到本函数入口栈帧状态。调用方实参清理不属于被调用方职责。
- `BPX` 若被被调用方用于建立栈帧，则由被调用方保存并恢复。
- `__using(n)` 引起的寄存器组切换和恢复由编译器按函数属性规则保证。

中断函数上下文保存规则单独定义，不直接套用普通函数 caller-saved 默认规则。

### 溢出参数去向

当参数无法分配到寄存器槽时，按函数类别选择溢出参数区域：

| 函数类别 | 溢出参数去向 |
| --- | --- |
| 普通非可重入函数 | 静态参数区 |
| `__reentrant` 函数 | 栈参数区 |
| 可变参数函数 | 全部参数进入栈参数区 |

静态参数区用于普通非可重入函数的调用参数存放；它不适合保存可重入调用状态。栈参数区用于需要运行期连续参数游标或多实例调用状态的函数类别。

### 非可重入函数静态参数区

每个普通非可重入函数拥有自己的静态参数区。静态参数区只存放无法进入寄存器槽的溢出参数，不存放已经在寄存器中的参数、返回值或普通局部变量。

静态参数区符号由工具链生成，格式以 `__param_` 为前缀，并保证链接后唯一。

静态参数区只包含溢出参数，按源码声明顺序连续存放。已经放入寄存器槽的参数不占静态参数区空间；第一个溢出参数从偏移 `0` 开始，后面的溢出参数紧随前一个溢出参数。每个参数按自身对象表示存放，多字节参数遵守默认大端对象表示。

静态参数区默认不自动插入对齐填充，按字节紧凑排列。

调用方负责在发出调用指令前写入目标函数静态参数区；被调用方按固定偏移读取静态参数区。调用返回后，静态参数区内容不保证仍有意义。由于静态参数区是函数固定存储，普通非可重入函数不能安全递归，也不能被并发重入。

通过函数指针调用无参函数时，允许使用普通调用约定。通过函数指针调用带参数函数时，函数指针类型必须指向采用可重入调用约定的函数。

该规则用于避免运行期无法确定非可重入目标函数静态参数区的问题：非可重入函数的溢出参数需要写入目标函数专属静态参数区，而函数指针目标可能到运行期才确定。

### 局部变量与静态局部变量

普通自动局部变量按函数可重入属性选择存放策略：

- 普通非可重入函数的自动局部变量，可由编译器放入寄存器、栈帧或编译器管理的静态局部区。
- `__reentrant` 函数以及可能递归执行的函数，其自动局部变量不得依赖固定静态槽保存调用状态；需要保留地址或跨调用存活时，必须位于栈帧或其它可区分调用实例的位置。
- 自动局部变量若被取地址，且所在函数可能重入，必须拥有稳定的栈帧位置或等价的可重入实例位置。
- 自动局部变量若被取地址，但所在函数明确为普通非可重入函数，可放入静态局部区；此类分配应在 `.map` / `.mem` 中可见。

`static` 局部变量始终是静态存储期对象，不进入栈帧，也不随函数调用创建多个实例。函数内 `static` 局部变量由工具链生成链接唯一的内部符号。

### 非可重入静态区 overlay

工具链允许对普通非可重入函数的静态参数区和静态局部区进行 overlay 复用，以降低 RAM 占用。

overlay 复用必须基于调用图或等价分析确认多个存储区所属函数不会同时活跃。存在以下情况时，除非工具链能进一步证明安全，否则不得参与 overlay：

- 函数通过函数指针调用且目标集合不明确；
- 函数可能递归或间接递归；
- 函数采用 `__reentrant` 调用约定；
- 函数可能在中断可达路径中与普通执行路径并发活跃。

`.map` / `.mem` 必须能显示 overlay 组和复用关系，避免人工审查时将合法 overlay 误判为地址冲突。

### 可变参数函数参数区

可变参数函数使用全栈参数调用约定。可变参数函数的所有实参均进入栈参数区，包括固定参数和 `...` 对应的可变参数；可变参数函数不使用寄存器参数槽传递实参，也不使用静态参数区传递实参。

本规则只定义参数传递方式，不表示可变参数函数在语法或类型系统中等价于 `__reentrant`。

### 栈参数区

栈参数区按调用方写入顺序位于硬件栈上，并随 `SPX` 向高地址增长。进入栈参数区的参数按源码声明顺序连续存放；每个参数按自身对象表示存放，多字节参数遵守默认大端对象表示。

栈参数区默认不自动插入对齐填充，按字节紧凑排列。

16 bit 和 32 bit 参数的栈内字节顺序与硬件 `PUSH WRj`、`PUSH DRk` 后的栈内顺序一致。也就是说，若 `WRj = 0x1234`，压栈后从低地址到高地址为 `12 34`；若 `DRk = 0x12345678`，压栈后从低地址到高地址为 `12 34 56 78`。

调用方负责在调用前构造栈参数区，并在被调用方返回后清理本次调用压入的栈参数。被调用方只负责恢复自己建立的栈帧、局部变量空间和按调用约定需恢复的寄存器，不负责弹出调用方实参。

对于 `__reentrant` 固定参数函数，调用方只清理进入栈参数区的溢出参数；对于可变参数函数，调用方清理进入栈参数区的全部实参。


## 调用约定基础

本文件冻结调用约定。未在本文件中定义的调用场景，工具链不得擅自生成稳定 ABI；需要支持时必须先补充本文件。

- 固定参数函数与可变参数函数使用不同的参数放置规则；可变参数函数采用全栈参数调用约定。
- 可变参数实参按 C 默认实参提升规则传递。
- 通过 `...` 传递的、已进入通用指针 tag 表的窄指针，在可变参数区域中使用 4 字节通用指针表示。
- `va_list` 表示指向当前可变参数位置的 ABI 参数游标。
- `va_arg` 读取专用指针类型时，先按通用指针读取，再按目标指针类型收窄并按语法规则诊断。

### `va_list` 游标布局

`va_list` 表示指向栈参数区当前位置的 16 bit `eram` 地址游标。

可变参数函数采用全栈参数调用约定，因此固定参数和 `...` 对应参数均位于同一个连续栈参数区。`va_start(ap, last_fixed)` 将 `ap` 初始化为指向 `last_fixed` 之后的第一个可变参数。

`va_arg(ap, T)` 按 `T` 在可变参数中的 ABI 传参宽度读取当前参数，然后将 `ap` 前移到下一个参数位置。窄整数类型遵守 C 默认实参提升规则；例如 `char` 和 `short` 通过 `...` 传递时按 `int` 宽度读取。

通过 `...` 传递的、已进入通用指针 tag 表的窄指针使用 4 字节通用指针表示。`va_arg` 读取专用指针类型时，先按通用指针读取，再按目标指针类型收窄并按语法规则诊断。

`va_end(ap)` 不释放资源；实现可将游标置零或作为空操作处理。

## 栈模型

工具链 ABI 使用 `SPX` 表示 16 位硬件栈指针，使用 `BPX` 表示 16 位栈帧基址指针。除非明确讨论 8051 兼容低级行为，栈模型文档不使用 `SP` / `BP` 描述 ABI 栈指针。

硬件栈位于 `eram` 地址空间内，并向高地址增长。函数调用、返回、中断现场、可重入调用、可变参数访问和递归调用均基于同一套 `SPX` 硬件栈模型。

`BPX` 不是独立栈指针，只表示当前 `SPX` 栈上的栈帧基址。汇编代码可以按 ABI 约定使用 `SPX` / `BPX`；C 代码不提供直接读写 `BPX` 的内建接口，确需操作时使用内联汇编。


### BPX 栈帧布局

非 `__naked` 且需要栈帧的函数使用 `BPX` 建立帧基址。函数入口序言保存调用者 `BPX`，再将当前 `SPX` 作为本函数 `BPX`：

```asm
PUSH BPX
MOV  BPX,SPX
```

保存旧 `BPX` 后，`BPX` 指向已保存旧 `BPX` 的末端。局部变量区从 `BPX + 1` 开始向高地址增长；返回地址和栈参数位于 `BPX` 下方的低地址侧。

栈帧相对位置如下：

```text
低地址
  栈参数区
  返回地址
  old BPX 高字节
  old BPX 低字节   <- BPX
  局部变量区       <- BPX + 1 开始向高地址增长
高地址
```

因此：

```text
BPX-1 : old BPX 高字节
BPX   : old BPX 低字节
BPX+1 : 第一个局部变量字节
```

栈参数使用 `BPX` 的负偏移访问。具体参数偏移由编译器根据栈参数总大小、返回地址宽度和参数对象大小在编译期确定。普通调用使用 16 bit 返回地址，因此普通 `call16` 栈帧中返回地址占用 2 字节。

普通函数退出时，先丢弃本函数局部变量区，再恢复调用者 `BPX`，最后执行返回指令：

```asm
MOV  SPX,BPX
POP  BPX
RET
```

若函数使用扩展返回序列，最后的返回指令按调用地址模型替换。函数退出序列不清理调用方实参；栈参数清理由调用方完成。

## 启动入口 ABI

复位入口跳转到工具链启动入口。启动入口负责建立进入 C 程序前的基础运行环境。

启动入口至少执行以下工作：

- 进入不依赖最终栈内容的启动序列；
- 清零 `eram[0x0020..eram_end]`；
- 若 `xram_size` 非零，清零 `xram[xram_addr..xram_addr + xram_size - 1]`；
- 根据链接器提供的初始化表，将 `CINIT` 初始值镜像拷贝到对应运行期 RAM 对象；
- 按链接布局建立正式硬件栈：`SPX = STACK start - 1`；
- 建立空栈帧基址：`BPX = SPX`；
- 配置当前 ABI 必需的处理器状态；
- 执行启动序列中已链接的初始化汇编片段；
- 进入 `main`。

不提供任何 C 函数形式的启动钩子，也不提供用 C 函数形式跳过默认 RAM 清零和 `CINIT` 的机制。

汇编源码可以显式进入 `.area INIT0`..`.area INIT5`，插入启动阶段汇编片段。此类代码按启动阶段汇编处理，不承诺普通 C 调用环境已经建立；使用者必须自行保证不依赖未初始化 RAM、未建立的最终栈或未配置完成的处理器状态。C 编译器默认不从 C 源码生成用户 `INIT0`..`INIT5` 代码；若用户确需启动早期逻辑，应使用汇编文件显式声明对应 `.area`。

默认硬件栈位于静态 `eram` 分配之后的剩余连续区域。链接器完成 `DATA` / `IDATA` / `EDATA`、静态参数区、overlay 区和其它 `eram` 保留区分配后，`STACK start` 为最后一个静态 `eram` 已用地址的下一个字节，`STACK end = eram_end`。若 `0x0020` 之后没有静态 `eram` 分配，`STACK start` 可为 `0x0020`，此时 `SPX` 初值为 `0x001f`。

链接器必须报告默认硬件栈容量：小于 8 字节为错误，8 到 63 字节为警告，64 字节及以上为正常。

`main` 推荐声明为 `int main(void)`。`void main(void)` 可接受但应给出警告。不定义 `argc` / `argv` 入口形式。

`main` 返回后，默认进入死循环；工具链不承诺返回到任何外部环境。

## area / section / 逻辑区模型

工具链采用三层模型描述代码和数据布局：

| 层级 | 含义 | 示例 |
| --- | --- | --- |
| memory space | 目标存储空间和访问空间 | `code`、`eram`、`xram` |
| logical area | 工具链承诺的逻辑用途和初始化顺序 | `VECTORS`、`STARTUP`、`CODE`、`DATA`、`STACK` |
| section / area | 对象文件和汇编源码中的可重定位或绝对片段 | `.area CODE (REL,CON,CODE)` |

汇编源码使用 `.area NAME (ATTR,...)` 形式声明和切换 section / area。对象文件中记录 section；链接器将 section 归入 logical area，再布置到 memory space。

area 分为内置 area 和用户 area。

- 内置 area 由工具链按固定名称识别，拥有固定默认属性、logical area、memory space 和布局顺序。
- 用户 area 由用户自定义名称，必须通过属性集合归类到一个明确的 logical area 和一个明确的 memory space。
- area 名称大小写不敏感，规范化后统一使用大写。
- 内置 area 不要求显式写属性；省略属性时使用本节定义的默认属性。若显式写属性，必须与本节定义的默认属性完全一致；少写、多写或写错均报错。
- 用户 area 可以省略 `REL` / `ABS` 和 `CON` / `OVR`，由默认规则补齐；但不得省略决定存储空间和逻辑归属的属性。
- 用户 area 属性不足以推出归属，或同时推出多个互斥归属时，链接器必须诊断为错误。

属性采用集合语义，顺序不影响含义。已确认属性如下：

| 属性 | 含义 |
| --- | --- |
| `CODE` | 位于 code 存储空间，可执行或可作为代码区数据读取 |
| `DATA` | 位于 data 直接寻址兼容区域 |
| `IDATA` | 位于 idata 间接寻址区域 |
| `EDATA` | 位于 eram 增强内部 RAM 区域 |
| `XDATA` | 位于 xram 扩展 RAM 区域 |
| `BIT` | 位地址空间或 bit 相关区域 |
| `REL` | 可重定位 area |
| `ABS` | 绝对地址 area |
| `CON` | 同名 area 内容按输入顺序串接 |
| `OVR` | 同名或同组 area 可按 overlay 规则复用地址 |
| `INIT` | 含初始化代码或初始化表 |
| `STACK` | 栈保留区域 |

`REL` 与 `ABS` 互斥。`CON` 与 `OVR` 互斥。未显式写 `REL` / `ABS` 时默认为 `REL`；未显式写 `CON` / `OVR` 时默认为 `CON`。

存储归属属性按以下规则映射：

| 属性 | memory space | 默认 logical area |
| --- | --- | --- |
| `CODE` | `code` | `CODE` |
| `DATA` | `eram` | `DATA` |
| `IDATA` | `eram` | `IDATA` |
| `EDATA` | `eram` | `EDATA` |
| `XDATA` | `xram` | `XDATA` |
| `BIT` | `eram` | `BIT` |
| `STACK` | `eram` | `STACK` |

归类规则：

- `INIT` 是保留给内置初始化 area 的属性，只允许用于内置 `INIT0`..`INIT5` 和 `CINIT`。
- `INIT` 与内置 `INIT0`..`INIT5` 或 `CINIT` 名称组合时，logical area 使用对应内置名称。
- 用户自定义 area 不得使用 `INIT` 属性；若使用则报错。用户要插入启动汇编，必须显式切换到内置 `.area INIT0`..`.area INIT5`。
- `STACK` 与其它存储归属属性组合时，logical area 为 `STACK`；默认 memory space 为 `eram`。
- 同一 area 不得同时包含多个会推出不同 memory space 的属性，例如 `CODE,XDATA`。
- 同一 area 不得同时包含多个会推出不同默认 logical area 且没有更高优先级属性消解的属性，例如 `DATA,IDATA`。

内置 area 名称和默认属性如下。未列出的名称均视为用户 area。

| area 名称 | 默认属性 | 默认 logical area | 默认 memory space | 说明 |
| --- | --- | --- | --- | --- |
| `VECTORS` | `REL,CON,CODE` | `VECTORS` | `code` | 复位向量和中断向量入口 |
| `STARTUP` | `REL,CON,CODE` | `STARTUP` | `code` | 工具链启动入口 |
| `INIT0`..`INIT5` | `REL,CON,CODE,INIT` | `INIT0`..`INIT5` | `code` | 初始化汇编片段 |
| `CODE` | `REL,CON,CODE` | `CODE` | `code` | 普通函数和汇编代码主体 |
| `CONST` | `REL,CON,CODE` | `CONST` | `code` | code 空间常量和字符串字面量 |
| `CINIT` | `REL,CON,CODE,INIT` | `CINIT` | `code` | RAM 初始值镜像和初始化表 |
| `DATA` | `REL,CON,DATA` | `DATA` | `eram` | 直接寻址兼容区对象 |
| `IDATA` | `REL,CON,IDATA` | `IDATA` | `eram` | 间接寻址兼容区对象 |
| `EDATA` | `REL,CON,EDATA` | `EDATA` | `eram` | 增强内部 RAM 对象 |
| `XDATA` | `REL,CON,XDATA` | `XDATA` | `xram` | 扩展 RAM 对象 |
| `BIT` | `REL,CON,BIT` | `BIT` | `eram` | bit 相关对象 |
| `STACK` | `REL,CON,EDATA,STACK` | `STACK` | `eram` | 硬件栈保留区域 |

同一 area 在多个对象文件中出现时，`CON` area 按链接输入顺序串接。`OVR` area 的地址复用规则由 overlay 分析和链接布局共同决定；不能证明可安全复用时不得自动重叠。

布局顺序规则：

1. 链接器先按 memory space 分组。
2. 每个 memory space 内，内置 logical area 按本文件“逻辑区与初始化顺序”定义的顺序布局。
3. 用户 area 根据属性归入对应 logical area。
4. 同一 logical area 内，内置 area 优先使用其固定位置；用户 `CON` area 按链接输入顺序稳定串接。
5. 用户 `OVR` area 只在 overlay 规则允许时复用地址；不能证明可复用时按错误诊断或退化为非重叠布局，具体由链接器策略决定。
6. `ABS` area 不参与普通重定位分配，但仍必须映射到明确 memory space 和 logical area，用于冲突检查和 `.map` / `.mem` 展示。

汇编源码未显式声明 `.area` 时，默认进入 `CODE` area，等价于位于 `.area CODE (REL,CON,CODE)` 后开始汇编。C 编译器生成的汇编必须显式输出 `.area`，不依赖该默认行为。

## 逻辑区与初始化顺序

工具链按逻辑区组织链接结果和启动初始化。汇编 area、对象 section 和链接输出必须能映射到本节定义的逻辑区。

逻辑区按以下顺序组织：

| 逻辑区 | 主要职责 |
| --- | --- |
| `VECTORS` | 复位向量和中断向量入口 |
| `STARTUP` | 工具链启动入口 |
| `INIT0..INIT5` | 分阶段初始化汇编片段 |
| `CODE` | 普通函数和汇编代码主体 |
| `CONST` | 可直接位于代码区读取的常量对象和字符串字面量 |
| `CINIT` | 已初始化 RAM 对象在代码区中的初始值镜像 |
| `DATA` / `IDATA` / `EDATA` / `XDATA` | 运行期 RAM 数据对象所在区域；无显式初值的对象不产生初始值镜像 |
| `STACK` | 硬件栈区域 |

`VECTORS` 必须能放置在目标要求的固定入口位置。`STARTUP` 由 `VECTORS` 跳入，负责建立 C 运行环境。`INIT0..INIT5` 为分阶段初始化汇编片段保留，允许工具链和用户通过汇编在确定阶段插入启动代码；这些片段不是 C 函数钩子。

启动代码先清零工具链管理的 RAM 范围，再根据链接器提供的初始化表，将 `CINIT` 中的初始值镜像复制到对应运行期 RAM 对象。无显式初值的静态存储期对象不产生初始值镜像，依靠启动清零获得初始零值。

`.map` / `.mem` 必须能展示各逻辑区的地址范围、大小和所属存储空间，便于人工检查布局和初始化结果。

## 内存溢出与布局诊断

链接器必须检查每个逻辑区是否超出对应存储布局范围。普通自动分配对象、代码区、运行期数据区、RAM 清零范围、初始化镜像区和栈保留区超出当前布局范围时，默认报错。

`__code __at(addr)` 对象越出 code 布局范围时，普通模式和严格模式均只警告，不改变其语义，并允许继续生成产物。该产物可能超出目标可执行或可烧录范围，风险由用户确认。

多个对象显式绝对地址重叠、启动清零范围与保留区重叠、或 overlay 复用导致地址范围重叠时，按本文件绝对地址、启动清零范围与 overlay 规则诊断。显式地址复用和合法 overlay 复用在普通模式和严格模式下均只警告不报错；无法归类为显式复用或合法 overlay 的非法冲突默认报错。`.map` / `.mem` 必须用明确标识区分非法冲突、显式地址复用和合法 overlay 复用。

链接器必须检查 `STACK` 保留区本身是否落入合法 `eram` 范围。运行期最大栈深度通常无法完全静态证明；工具链可根据调用图、已知栈帧和中断嵌套模型给出估算和警告，但不将估算结果作为唯一正确性依据。

## 函数属性 ABI 承诺

### `__using(n)`

- `__using(n)` 指定函数使用寄存器组 `n`。
- 编译器必须在函数入口切换到指定寄存器组，并在函数离开前恢复原 PSW 中的寄存器组选择。
- 即使函数同时声明 `__naked`，`__using(n)` 的寄存器组切换与恢复仍由编译器保证。
- `__using(n)` 不表示自动保存所有寄存器；其它寄存器保存规则由调用约定定义。

### `__reentrant`

- `__reentrant` 表示函数采用当前 ABI 定义的可重入调用约定。
- `__reentrant` 不在语义上固定为“全部参数通过栈传递”。
- `__reentrant` 函数不得依赖不可重入的固定全局暂存槽保存参数、局部变量或调用状态。

### `__naked`

- `__naked` 取消普通函数序言、尾声、自动上下文保存和自动返回序列。
- `__naked` 不取消 `__interrupt(n)` 的中断向量绑定。
- `__naked` 不取消 `__using(n)` 的寄存器组切换与恢复。
- `__naked` 不取消 `__reentrant` 的可重入调用约定。
- 除其它属性明确要求的最小动作外，其余进入、退出、保存、恢复和返回工作由用户负责。

### `__interrupt(n)`

- `__interrupt(n)` 标记函数为中断服务函数，并绑定到目标约定中的中断向量槽。
- `n` 必须是非负编译期整数常量表达式。目标描述提供最大向量编号、向量入口大小和向量地址计算规则。
- 允许稀疏向量：用户只需定义实际使用的中断向量，未定义向量入口由启动代码或链接器按默认策略填充。
- `n` 超出目标向量表范围时报错；同一链接结果中同一个 `n` 被多个中断函数绑定时报错；普通函数符号不得与中断向量入口符号冲突。
- 非 `__naked` 的中断函数由编译器生成中断返回序列。
- 非 `__naked` 的中断函数由编译器自动保存并恢复 ISR 代码实际使用到的寄存器和状态。
- 若非 `__naked` 中断函数调用普通函数，编译器必须保证会被普通函数破坏的中断现场不会影响中断返回后的执行状态。可采用保守策略，保存全部 caller-saved 寄存器、PSW / PSW1、DPX 和 BPX。
- `__naked __interrupt(n)` 函数不自动生成普通中断现场保存、恢复和返回序列，用户必须显式完成寄存器保存、恢复和返回指令。
- 即使函数同时声明 `__naked __interrupt(n) __using(m)`，`__using(m)` 的寄存器组切换与恢复仍由编译器保证。
- MCS-251 中断返回按 4 字节返回地址模型处理；传统 2 字节返回地址兼容模式不作为默认 ABI。
- 面向传统 2 字节中断返回配置的目标时，必须由启动代码或目标配置保证进入 4 字节返回地址模式。

## 符号命名与链接名

C 外部符号映射到对象层和汇编层可见符号时，默认增加一个前导 `_`。例如 C 源码中的 `main`、`foo`、`global_counter` 分别映射为 `_main`、`_foo`、`_global_counter`。

汇编源码中显式定义的全局符号原样导出，汇编器不自动增加或删除前导 `_`。若汇编代码需要实现 C 可调用函数，应显式定义带前导 `_` 的对象层符号；若汇编代码需要调用 C 函数，应使用带前导 `_` 的对象层符号。

`__mcs251_` 前缀表示工具链和运行库关键符号。这类符号可能影响启动、初始化、栈、算术、内存访问、中断和其它芯片运行关键行为。

用户允许定义或覆盖 `__mcs251_` 符号。链接器发现用户对象覆盖默认运行库中的 `__mcs251_` 符号时，普通模式和严格模式均必须给出明确警告，不报错。警告应提示：错误覆盖可能导致程序无法启动、运行期状态损坏或产生难以预期的错误。

已公开且 ABI 已冻结的 `__mcs251_` helper 必须在相应文档中定义名称、参数、返回值、可破坏寄存器和可重入属性。未公开的 `__mcs251_` 符号不承诺兼容性；用户仍可覆盖，但风险自负。

其它双下划线开头符号保留给工具链和运行库使用。用户定义其它 `__` 开头符号时，普通模式警告，严格模式报错。

单下划线开头符号不禁止，因为 C 外部符号映射到对象层后天然使用前导 `_`。

### 运行库 helper 覆盖

编译器可以将算术、内存访问、通用指针访问、初始化辅助等操作降级为对运行库 helper 的调用。默认运行库提供通用软件实现。

用户或目标支持包可以提供同名 `__mcs251_` helper 覆盖默认实现，例如使用硬件数学扩展实现乘除法。覆盖实现必须遵守该 helper 已公开的 ABI；未公开 helper 的名称、参数、返回值和可破坏寄存器不属于 ABI 承诺。

编译器也可以在不改变语义的前提下将 helper 调用优化为内联代码或目标特性代码。只有已经公开的 helper ABI 才作为稳定承诺。

链接输入优先级从高到低为：用户显式输入对象、目标支持包对象、默认运行库对象。默认运行库对象只在符号未由更高优先级输入满足时参与解析。

若用户显式输入对象和默认运行库对象定义同名 `__mcs251_` 符号，链接器选择用户对象定义，普通模式和严格模式均给出警告但不报错。若目标支持包对象和用户对象定义同名 `__mcs251_` 符号，链接器选择用户对象定义并给出同类警告。

两个用户显式输入对象定义同名全局符号时报错。目标支持包对象和用户对象定义同名非 `__mcs251_` 全局符号时，除非该符号被目标支持包明确声明为可覆盖符号，否则报错。

### 公开整数二元算术 helper

公开以下整数二元算术 helper 名称。名称格式已冻结：

| helper | 含义 |
| --- | --- |
| `__mcs251_mul_i16` | 16 bit 有符号乘法 |
| `__mcs251_mul_u16` | 16 bit 无符号乘法 |
| `__mcs251_mul_i32` | 32 bit 有符号乘法 |
| `__mcs251_mul_u32` | 32 bit 无符号乘法 |
| `__mcs251_div_i16` | 16 bit 有符号除法 |
| `__mcs251_div_u16` | 16 bit 无符号除法 |
| `__mcs251_div_i32` | 32 bit 有符号除法 |
| `__mcs251_div_u32` | 32 bit 无符号除法 |
| `__mcs251_mod_i16` | 16 bit 有符号取模 |
| `__mcs251_mod_u16` | 16 bit 无符号取模 |
| `__mcs251_mod_i32` | 32 bit 有符号取模 |
| `__mcs251_mod_u32` | 32 bit 无符号取模 |

这些 helper 均表示二元运算：

```c
T helper(T lhs, T rhs);
```

这些 helper 的参数和返回值使用默认双参数函数调用约定，不另行定义专用寄存器布局。

内建整数乘法、除法和取模 helper 均按可重入实现要求处理，不得依赖会被嵌套调用覆盖的静态临时状态。可重入不改变普通调用约定中的寄存器保存责任；中断中调用这些 helper 时，中断函数仍必须按中断 ABI 保存必要现场。

用户或目标支持包覆盖同名 helper 时，必须自行保证临时状态、硬件数学单元状态和中断嵌套场景下的安全性。除零等异常输入按 C 语言未定义行为处理，helper 不承诺统一诊断或运行期保护。

## 调用地址与重定位术语

为避免混淆，工具链内部使用以下调用地址术语：

| 术语 | 含义 |
| --- | --- |
| `call11` | 11 位调用或跳转地址形态 |
| `call16` | 16 位调用或跳转地址形态 |
| `call24` | 24 位调用或跳转地址形态 |

代码生成使用 `call16` 范围内的普通调用和返回能力。`call24` 只作为重定位和函数指针地址术语保留，不定义普通调用返回序列。

函数指针 24 位重定位记录使用内部名称 `R_FPTR24`。

## area 空洞与填充规则

`.org`、`.align` 和等价布局动作只推进当前 area 的当前位置，不直接表示必须输出某个填充值。

`.org` 只允许在 `ABS` area 中使用，参数表示当前 area 内偏移，不能使当前位置后退。`REL` area 中使用 `.org` 报错。

`.align` 可在 `REL` 和 `ABS` area 中使用，只表示当前位置向前推进到满足对齐的位置。

`code` 镜像中的空洞默认不强制写入 `.ihx`；Intel HEX 可以使用不连续地址记录表达前后两段有效字节。若某个镜像生成模式必须补齐连续 code 块，则默认使用 `0xff` 作为填充值。

RAM area 中由 `.ds`、`.org`、`.align` 产生的空洞不进入 `.ihx`，只影响符号地址、对象布局以及 `.map` / `.mem` 展示。

对象文件必须保留足够的位置信息，使链接器能区分真实数据字节和由 `.org` / `.align` 产生的空洞。不定义可配置空洞填充值。

## 绝对地址与重叠诊断

- `__at(addr)` 明确指定对象地址。
- 多个对象显式指定到同一地址或重叠地址范围时，视为显式地址复用，普通模式警告，严格模式也只警告不报错。
- 这种重叠可用于确认不会同时工作的模块复用存储空间。
- `.map` / `.mem` 应能用明确标识展示显式地址重叠，便于人工审查。
- 若地址值超出目标字段宽度，普通模式警告并截断，严格模式报错；语法规则另有更严格要求时，以语法规则为准。

## SFR / XFR 与混合逻辑寄存器承诺

- `__sfr` 和 `__xfr` 均表示 8 bit `volatile unsigned char` 真实寄存器对象。
- `__sfr` 地址必须位于 8 bit SFR 直接地址范围内。
- `__xfr` 地址使用完整 24 位地址，并通过扩展访问方式访问。
- `__mix16`、`__mix24`、`__mix32` 只定义逻辑整数对象各字节对应的真实 8 bit 寄存器。
- `__mixNN` 不改变对应整数类型本身的读写字节顺序；对应整数类型按本文件默认大端对象表示处理。
- `__mixNN` 的每个组件必须引用已经定义的 `__sfr` 或 `__xfr` 对象。

