MicroPython核心:编译器
MicroPython编译过程包括以下步骤:
- 词法分析器将MicroPython程序文本流转换为标记。
- 语法解释器将标记转换为抽象语法(语法树)。
- 根据语法书输出字节码或本地代码。
本文以给MicroPython增加一个简单的语言特性为例来说明这一过程:
>>> add1 3
4
>>>
add1
语句以整数作为参数,将其加 1。
添加语法规则
MicroPython 的语法基于CPython 语法,并在py/grammar.h
中定义。该语法用于解析MicroPython源码文件。
要定义语法规则,需要关注两个宏:DEF_RULE
和 DEF_RULE_NC
。DEF_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
中实现,以便将此规则转化为可执行代码。
第三个必要参数可以是 or
或 and
,它指定了与语句相关的节点数。在本例中,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_RULE
和DEF_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 整数会自动扩展到任意精度)。
相关文章:
MicroPython核心:编译器
MicroPython编译过程包括以下步骤: 词法分析器将MicroPython程序文本流转换为标记。语法解释器将标记转换为抽象语法(语法树)。根据语法书输出字节码或本地代码。 本文以给MicroPython增加一个简单的语言特性为例来说明这一过程:…...
R语言【taxlist】——tax2traits():将分类信息设置为分类单元特征
Package taxlist version 0.2.4 Description 分类法分类可以包含在taxonRelations插槽提供的信息中的 taxlist 对象中。然而,对于统计分析来说,将这些信息插入到插槽taxonTraits中可能更方便。 Usage tax2traits(object, ...)## S3 method for class …...

CTF-WEB的知识体系
CTF概念 CTF是Capture The Flag的缩写,中文一般译作夺旗赛 CTF起源于1996年DEFCON全球黑客大会 DEFCONCTF是全球技术水平和影响力最高的CTF竞赛 竞赛模式 解题模式:解决网络安全技术挑战(即找到flag),提交后获取相应分值。 攻防赛模式:要求找到其他队…...
【Spring框架】@Cacheable注解:缓存最佳实践
在Java开发中,性能优化是一个永恒的话题。对于使用Spring框架的应用程序来说,Cacheable 注解提供了一种简单有效的方式来提升性能,特别是对于那些计算成本高或数据变化不频繁的操作。本文将深入探讨 Cacheable 的使用方法和注意事项ÿ…...

iZotope RX 10.4.2 mac激活版 音频修复和增强工具
iZotope RX 10 for Mac是一款专业的音频修复软件,旨在提供强大、精确的工具,让用户能够清晰、纯净地处理音频。以下是其主要功能和特点: 软件下载:iZotope RX 10.4.2 mac激活版下载 强大的降噪功能:iZotope RX 10采用了…...

vue核心知识点
一、Vue基础知识点总结 开发vue项目的模式有两种: 基于vue.js,在html中引入vue.js,让vue.js管理div#app元素。基于脚手架环境:通过vue脚手架环境可以方便的创建一个通用的vue项目框架的模板,在此基础之上开发vue项目…...

【乳腺肿瘤诊断分类及预测】基于Elman神经网络
课题名称:基于Elman神经网络的乳腺肿瘤诊断分类及预测 版本日期:2023-05-15 运行方式: 直接运行Elman0501.m 文件即可 代码获取方式:私信博主或QQ:491052175 模型描述: 威斯康辛大学医学院经过多年的收集和整理&a…...
【kubernets】由Evicted状态的Pod探讨k8s中pod的驱逐策略
背景 某天突然发现自己的测试环境中有Evicted状态的pod,于是需要排查原因。先来看看大致情况: [rootk8s-m1 ~]# kubectl get pod -A -o wide|grep k8s-m1 kube-system calico-kube-controllers-bcc6f659f-575mr 1/1 Running 3 177d…...

vxe-table3.0的表格树如何做深层查找,返回搜索关键字的树形结构
vxe-table2.0版本是提供深层查找功能的,因为他的数据源本身就是树形结构,所以深层查找查询出来也是树形结构。 但是vxe-table3.0版本为了做虚拟树功能,将整个数据源由树形垂直结构变成了扁平结构,便不提供深层查询功能,…...

幻兽帕鲁越玩越卡,内存溢出问题如何解决?
近期幻兽帕鲁游戏大火,在联机组队快乐游玩的同时,玩家们也发现了一些小问题。由于游戏有随机掉落材料的设定,服务器在加载掉落物的过程中很容易会出现掉帧、卡顿的情况。某些玩家甚至在游戏1~2时后就出现服务器崩溃的情况…...

C++_list
目录 一、模拟实现list 1、list的基本结构 2、迭代器封装 2.1 正向迭代器 2.2 反向迭代器 3、指定位置插入 4、指定位置删除 5、结语 前言: list是STL(标准模板库)中的八大容器之一,而STL属于C标准库的一部分,因此在C中可以直接使用…...
使用docker部署mongodb
1.创建目录 mkdir -p /opt/mongodb/{data,logs,config} 2.创建配置文件 进入目录 cd /opt写入配置 vim mongod.conf 内容如下 systemLog:# MongoDB发送所有日志输出的目标指定为文件destination: file# mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径path:…...

C#,打印漂亮的贝尔三角形(Bell Triangle)的源程序
以贝尔数为基础,参考杨辉三角形,也可以生成贝尔三角形(Bell triangle),也称为艾特肯阵列(Aitkens Array),皮埃斯三角形(Peirce Triangle)。 贝尔三角形的构造…...
开源电商系统
前言 做电商永不过时,但形式会不断变化。任何赚钱的事情大体都分为两大块:生产和销售。两者是并重的,首先要有好的产品,其次是做好推广运营和销售渠道建设。对于小微企业来说,前期如果能通过销售赚到第一桶金…...

责任链模式在java中的实现
1 总览 2 概念 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式。 3 实现 公共部分,一个系…...

粤嵌Gec6818---小项目功能实现简单步骤(RFID+图片显示+音乐+视频)
项目设计开发环境: (1)VMware Workstation Pro软件 (2)ubuntu12 .04 (能交叉编译就行) (3)SecureCRT (4)代码编译器(notepad/Vis…...
opencv学习 特征提取
内容来源于《opencv4应用开发入门、进阶与工程化实践》 图像金字塔 略 拉普拉斯金字塔 对输入图像进行reduce操作会生成不同分辨率的图像,对这些图像进行expand操作,然后使用reduce减去expand之后的结果,就会得到拉普拉斯金字塔图像。 …...

关于maven项目构建的解释
在Idea中使用模块化构建项目 项目介绍: sky-take-out sky-common pom.xml sky-pojo pom.xml sky-server pom.xml pom.xml 说明 sky-server依赖sky-pojo和sky-common,继承sky-take-outsky-pojo继承sky-take-outsky-common继承sky-take-out 由于Idea编…...

IMU/捷联惯导常见的术语,以及性能评价标准(附Python解析代码)
0. 简介 现在的机器人领域在普遍使用IMU(惯性导航单元)。该系统有三个加速度传感器与三个角速度传感器(陀螺)组成,加速度计用来感受飞机相对于地垂线的加速度分量,陀螺仪用来感知飞机的角速率变化…...
Debezium发布历史98
原文地址: https://debezium.io/blog/2020/11/12/debezium-1-3-1-final-released/ 欢迎关注留言,我是收集整理小能手,工具翻译,仅供参考,笔芯笔芯. Debezium 1.3.1.Final 发布 十一月 12, 2020 作者: 克里…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...
Monorepo架构: Nx Cloud 扩展能力与缓存加速
借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...