MicroPython核心:编译器

MicroPython编译过程包括以下步骤:

  • 词法分析器将MicroPython程序文本流转换为标记。
  • 语法解释器将标记转换为抽象语法(语法树)。
  • 根据语法书输出字节码或本地代码。

本文以给MicroPython增加一个简单的语言特性为例来说明这一过程:

>>> add1 3
4
>>>

add1语句以整数作为参数,将其加 1。

添加语法规则

MicroPython 的语法基于CPython 语法,并在py/grammar.h中定义。该语法用于解析MicroPython源码文件。

要定义语法规则,需要关注两个宏:DEF_RULEDEF_RULE_NCDEF_RULE 允许您定义一个带有相关编译函数的规则,而 DEF_RULE_NC 则没有编译 (no compile NC) 函数。

对于新增的语句 add1,带有编译函数的简单语法定义如下:

DEF_RULE(add1_stmt, c(add1_stmt), and(2), tok(KW_ADD1), rule(testlist))

第二个参数 c(add1_stmt) 是相应的编译函数,需要在 py/compile.c 中实现,以便将此规则转化为可执行代码。

第三个必要参数可以是 orand,它指定了与语句相关的节点数。在本例中,add1语句类似于汇编语言中的ADD1,它需要一个数字参数,因此add1_stmt有两个相关节点:一个节点是语句本身,即与KW_ADD1对应的字面 add1;另一个节点是它的参数,即作为顶层表达式规则的testlist规则。

注意:

这里的add1规则只是一个示例,并非MicroPython标准语法的一部分。

本例中的第四个参数是与规则KW_ADD1相关的标记,可以通过编辑py/lexer.h在词典中定义该标记。

使用DEF_RULE_NC宏可省略编译函数参数,即在不使用编译函数的情况下定义相同的规则:

DEF_RULE_NC(add1_stmt, and(2), tok(KW_ADD1), rule(testlist))

其余参数的含义相同,无编译函数的规则必须由所有以该规则为节点的规则明确处理。这种 NC 规则通常用于表达复杂语法结构的子部分,这些子部分无法用一条规则表达。

注意:

DEF_RULEDEF_RULE_NC需要其他参数,要深入了解支持的参数,请参阅 py/grammar.h。

添加词法标记

语法中定义的每条规则都应与py/lexer.h中定义的标记相关联,通过编辑 _mp_token_kind_t 枚举来添加该标记:

typedef enum _mp_token_kind_t {...MP_TOKEN_KW_OR,MP_TOKEN_KW_PASS,MP_TOKEN_KW_RAISE,MP_TOKEN_KW_RETURN,MP_TOKEN_KW_TRY,MP_TOKEN_KW_WHILE,MP_TOKEN_KW_WITH,MP_TOKEN_KW_YIELD,MP_TOKEN_KW_ADD1,...
} mp_token_kind_t;

然后编辑py/lexer.c,添加新关键字的字面文本:

STATIC const char *const tok_kw[] = {..."or","pass","raise","return","try","while","with","yield","add1",...
};

请注意,关键字的命名可以自己定义,但为了保持一致性,还是要尽可能的遵守命名标准。

注意:

py/lexer.c中的关键字顺序必须与py/lexer.h中定义的枚举标记顺序一致。

解析

在解析阶段,解析器将词法生成器产生的标记转换为抽象语法树(AST abstract syntax tree )或语法树。解析器的实现定义在py/parse.c中。

解析器还维护一个常量表,用于解析的不同方面,这与符号表的作用类似。

在这一阶段,解析器还进行了一些优化,如针对逻辑、二进制、一元等大多数操作对整数进行常量折叠,对表达式周围的括号进行优化增强,以及对字符串进行一些优化。

值得注意的是,docstrings会被丢弃且无法访问,即使像字符串互调这样的优化也不会应用于docstrings

编译步骤

与许多编译器一样,MicroPython 会将所有代码编译为 MicroPython 字节码或本地代码。实现这一目标的功能在 py/compile.c 中实现:

mp_obj_t mp_compile(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl) {// 创建模块上下文并设置全局字典mp_module_context_t *context = m_new_obj(mp_module_context_t);context->module.globals = mp_globals_get();// 将输入的语法树编译为原始代码结构mp_compiled_module_t cm;cm.context = context;mp_compile_to_raw_code(parse_tree, source_file, is_repl, &cm);// 创建并返回一个执行外部模块的函数对象return mp_make_function_from_raw_code(cm.rc, cm.context, NULL);
}

编译器分四次编译代码:作用域、堆栈大小、代码大小和发射。每次都在相同的 AST 数据结构上运行相同的 C 代码,每次都根据前一次的结果计算不同的内容。

第一遍

在第一道工序中,编译器会了解已知标识符(变量)及其作用域(全局、局部、封闭等)。在同一过程中,发射器(字节码或本地代码)还会计算发射代码所需的标签数量。

// 第一遍
comp->emit = emit_bc;
comp->emit_method_table = &emit_bc_method_table;uint max_num_labels = 0;
for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) {if (s->emit_options == MP_EMIT_OPT_ASM) {compile_scope_inline_asm(comp, s, MP_PASS_SCOPE);} else {compile_scope(comp, s, MP_PASS_SCOPE);// 检查是否要关闭隐式声明的变量。for (size_t i = 0; i < s->id_info_len; ++i) {id_info_t *id = &s->id_info[i];if (id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) {scope_check_to_close_over(s, id);}}}...
}

第二遍和第三遍

第二遍和第三遍涉及计算字节码或代码的Python堆栈和代码大小。第三次计算后,代码大小不能改变,否则跳转标签将不正确。

for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) {...// 第二遍: 计算python堆栈大小compile_scope(comp, s, MP_PASS_STACK_SIZE);// 第三遍: 计算代码大小if (comp->compile_error == MP_OBJ_NULL) {compile_scope(comp, s, MP_PASS_CODE_SIZE);}...
}

在第二步之前,可以选择要输出的代码类型,可以是本地代码或字节码。

// 选择发射器类型
switch (s->emit_options) {case MP_EMIT_OPT_NATIVE_PYTHON:case MP_EMIT_OPT_VIPER:if (emit_native == NULL) {emit_native = NATIVE_EMITTER(new)(&comp->compile_error, &comp->next_label, max_num_labels);}comp->emit_method_table = NATIVE_EMITTER_TABLE;comp->emit = emit_native;break;default:comp->emit = emit_bc;comp->emit_method_table = &emit_bc_method_table;break;
}

缺省选项是字节码,但需要注意,通过VIPER还有另一个本地代码选项。有关 viper注释的更多详情,请参阅 "生成本地代码"部分。

此外,这里还支持内联汇编代码,即汇编指令以Python函数调用的形式编写,但直接以相应的机器码形式输出。这种汇编程序只有三次传递(作用域、代码大小、发射),并使用不同的实现,而不是compile_scope函数。

第四遍

第四步是输出可执行的最终代码,既可以是虚拟机中的字节码,也可以是 CPU 直接执行的本地代码。

for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) {...// 第四遍: 生成编译的字节码或本地代码if (comp->compile_error == MP_OBJ_NULL) {compile_scope(comp, s, MP_PASS_EMIT);}
}

生成字节码

Python 代码中的语句通常与所生成的字节码相对应,例如a + b会产生 “push a”,然后是 “push b”,然后是 “binary op add”。有些语句不会做任何事情,但会影响其他一些事情,比如变量的作用域,例如global a

输出字节码的函数的实现与此类似:

void mp_emit_bc_unary_op(emit_t *emit, mp_unary_op_t op) {emit_write_bytecode_byte(emit, 0, MP_BC_UNARY_OP_MULTI + op);
}

这里使用一元运算符表达式作为示例,但其他语句/表达式的实现细节与此类似。emit_write_bytecode_byte()方法是对主函数 emit_get_cur_to_write_bytecode() 的封装,所有函数都必须调用该函数才能生成字节码。

生成本地代码

与字节码的生成方式类似,py/emitnative.c 中的每个代码语句都应该有一个相应的函数:

STATIC void emit_native_unary_op(emit_t *emit, mp_unary_op_t op) {vtype_kind_t vtype;emit_pre_pop_reg(emit, &vtype, REG_ARG_2);if (vtype == VTYPE_PYOBJ) {emit_call_with_imm_arg(emit, MP_F_UNARY_OP, op, REG_ARG_1);emit_post_push_reg(emit, VTYPE_PYOBJ, REG_RET);} else {adjust_stack(emit, 1);EMIT_NATIVE_VIPER_TYPE_ERROR(emit,MP_ERROR_TEXT("unary op %q not implemented"), mp_unary_op_method_name[op]);}
}

这里的区别在于必须处理viper typing。Viper装饰器允许处理不止一种类型的变量。默认情况下,所有变量都是 Python 对象,但使用 viper,变量也可以声明为机器类型变量,如本地整数或指针。可以将 Viper 视为 Python 的超集,其中普通 Python 对象的处理方式与通常一样,而本地机器变量的处理方式则经过优化,直接使用机器指令进行操作。Viper 类型化可能会破坏 Python 的等价性,例如,整数会变成本地整数,并可能溢出(不像 Python 整数会自动扩展到任意精度)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/658070.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux Archcraft结合内网穿透实现SSH远程连接

文章目录 1. 本地SSH连接测试2. Archcraft安装Cpolar3. 配置 SSH公网地址4. 公网远程SSH连接5. 固定SSH公网地址6. SSH固定地址连接7. 结语 Archcraft是一个基于Arch Linux的Linux发行版&#xff0c;它使用最简主义的窗口管理器而不是功能齐全的桌面环境来提供图形化用户界面。…

JVM 内存模型

1 什么是 JVM 内存模型 JVM 需要使用计算机的内存&#xff0c;Java 程序运行中所处理的对象或者算法都会使用 JVM 的内 存空间&#xff0c;JVM 将内存区划分为 5 块&#xff0c;这样的结构称之为 JVM 内存模型。 2 JVM 为什么进行内存区域划分 随着对象数量的增加&#xff…

马哈鱼SQLFlow Lite的python版本

Gudu SQLFlow 是一款用来分析各种数据库的 SQL 语句和存储过程来获取复杂的数据血缘关系并进行可视化的工具。 Gudu SQLFlow Lite version for python 可以让 python 开发者把数据血缘分析和可视化能力快速集成到他们自己的 python 应用中。 Gudu SQLFlow Lite version for p…

Banana Pi BPI-R4开源路由器开发板快速上手用户手册,采用联发科MT7988芯片设计

介绍 Banana Pi BPI-R4 路由器板采用 MediaTek MT7988A (Filogic 880) 四核 ARM Corex-A73 设计&#xff0c;4GB DDR4 RAM&#xff0c;8GB eMMC&#xff0c;板载 128MB SPI-NAND 闪存&#xff0c;还有 2x 10Gbe SFP、4x Gbe 网络端口&#xff0c;带 USB3 .2端口&#xff0c;M.2…

计算机服务器中了halo勒索病毒怎么办,halo勒索病毒解密

在网络技术飞速发展的今天&#xff0c;越来越多的企业依赖数字化办公&#xff0c;为企业的生产生活提供了极大便利&#xff0c;但网络是一把双刃剑&#xff0c;网络安全威胁无处不在。近期&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机服务器中了ha…

【Java】实现图书管理系统

文章目录 1. 设计背景2. 需求分析3. 设计思路4. 实现4.1 book包4.1.1 Book类4.1.2 BookList类(书架) 4.2 user包4.2.1 User 类4.2.2 AdminUser类&#xff08;管理员用户&#xff09;4.2.3 NormalUser类&#xff08;普通用户&#xff09; 4.3 operation包4.3.1 IOPeration接口4.…

Maven基本使用

Maven简介: Apache Maven 是一个项目管理和构建工具&#xff0c;它基于项目模型(POM)的概念&#xff0c;通过一小段描述信息来管理项目的构建&#xff0c;报告和文档 Maven的作用: (1&#xff09;项目搭建&#xff1b; &#xff08;2&#xff09;依赖管理&#xff1b; &#xf…

[力扣 Hot100]Day16 除自身以外数组的乘积

题目描述 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n…

Mac Monitor:一款为macOS安全研究量身定制的高级独立系统监控工具

关于Mac Monitor Mac Monitor是一款功能强大的高级独立系统安全监控工具&#xff0c;该工具专为macOS安全研究、恶意软件分类和系统故障排除而设计&#xff0c;主要基于Apple Endpoint Security&#xff08;ES&#xff09;实现其功能。 Mac Monitor能够收集各种类型的系统事件…

ASP.NET Core 过滤器 使用依赖项注入

过滤器是 ASP.NET Core 中的特殊组件&#xff0c;允许我们在请求管道的特定阶段控制请求的执行。这些过滤器在中间件执行后以及 MVC 中间件匹配路由并调用特定操作时发挥作用。 简而言之&#xff0c;过滤器提供了一种在操作级别自定义应用程序行为的方法。它们就像检查点&#…

科普类——设计一套无人驾驶遥操作系统的步骤、架构、软硬件需求(十一)

设计一套无人驾驶遥操作系统的步骤、架构、软硬件需求 **设计步骤**&#xff1a;**整体架构**&#xff1a;**所需硬件及提供商**&#xff1a;**人员需求**&#xff1a; 设计无人驾驶中的遥操作系统是一个跨学科的工程项目&#xff0c;涉及机械工程、电子工程、计算机科学和通信…

ARM与X86架构的区别与联系

文章目录 1.什么是CPU2.复杂指令集和精简指令集3.ARM架构与X86架构的比较3.1.制造工艺3.2 64位计算3.3 异构计算3.4 功耗 4.ARM和X86的发展现状Reference 1.什么是CPU 中央处理单元&#xff08;CPU&#xff09;主要由运算器、控制器、寄存器三部分组成&#xff0c;从字面意思看…

Shell - 学习笔记 - 2.5 - Shell特殊变量:Shell $#、$*、$@、$?、$$

前面讲到了 $n&#xff0c;它是特殊变量的一种&#xff0c;用来接收位置参数。下面将继续讲解剩下的几个特殊变量&#xff0c;它们分别是&#xff1a;$#、$*、$、$?、$$。 Shell 特殊变量及其含义 变量 含义 $0 当前脚本的文件名。 $n&#xff08;n≥1 传递给脚本或函数的…

如何安全地多开Facebook/Twitter/TK/Ins等账号?

随着社交媒体的普及&#xff0c;人们需要在不同平台上管理多个账号。然而&#xff0c;如何安全地多开这些账号却是一个需要关注的问题。本文将介绍如何安全地多开Facebook、twitter、YouTube、TikTok等平台账号的方法。 重要关联因素&#xff1a; 1. 隐私和安全&#xff1a;保…

CCF CSP 202312-2因子化简

题目描述 样例 输入 3 2155895064 3 2 2 10000000000 10 输出 2238728 1 10000000000 基本思路 首先&#xff0c;要找出构成n的所有素因子&#xff0c;这些因子满足两个条件&#xff1a;是素数&#xff08;函数判断&#xff09;&#xff0c;且能被n或n的中间值整除&#xf…

【RT-DETR有效改进】CARAFE提高精度的上采样方法(助力细节长点)

👑欢迎大家订阅本专栏,一起学习RT-DETR👑 一、本文介绍 本文给大家带来的CARAFE(Content-Aware ReAssembly of FEatures)是一种用于增强卷积神经网络特征图的上采样方法。其主要旨在改进传统的上采样方法(就是我们的Upsample)的性能。CARAFE的核心思想是:使用输…

ONLYOFFICE:兼顾协作与安全的开源办公套件

文章目录 前言ONLYOFFICE是什么&#xff1f;ONLYOFFICE的特点多人在线协同灵活集成安全可靠跨平台和设备扩展丰富 实操注册登录编写文档插件安装智谱CopilotDraw.io 新版强势功能显示协作者头像插件 UI 界面更新 总结 前言 随着数字化时代的到来&#xff0c;越来越多的文档处理…

【前端】日期转换

记录项目中需要处理的日期格式 默认vue2 初级版 将后端传来的数组 [2024/01/29 08:55:18, 2024/01/29 09:55:18, 2024/01/29 10:11:18]转为 [2024-01-29 08:55, 2024-01-29 09:55, 2024-01-29 10:11]方法 convertDateTimeFormat(arr) {var tempArr arr.map(function (dateT…

操作系统-线程的实现方式和多线程模型(用户级线程 内核级线程 多线程模型的情况)和线程的状态,转换,组织,控制

文章目录 线程的实现方式和多线程模型总览线程的实现方式用户级线程内核级线程多线程模型一对一多对一多对多 小结 线程的状态,转换,组织,控制总览 线程的状态与转换线程的组织与控制 线程的实现方式和多线程模型 总览 线程的实现方式 用户级线程 程序自己通过自己设计的线程…

06 单目初始化器 Initializer

文章目录 06 单目初始化器 Initializer6.1 成员变量/函数6.2 初始化函数 Initialize()6.3 计算基础矩阵 F \boldsymbol{F} F 和单应矩阵 H \boldsymbol{H} H6.3.1 RANSAC 算法6.3.2 八点法计算 F \boldsymbol{F} F 矩阵: ComputeF21()6.3.3 计算基础矩阵 F \boldsymbol{F} …