lua vm 五: upvalue
前言
在 lua vm 中,upvalue 是一个重要的数据结构。upvalue 以一种高效的方式实现了词法作用域,使得函数能成为 lua 中的第一类值,也因其高效的设计,导致在实现上有点复杂。
函数 (proto) + upvalue 构成了闭包(closure),在 lua 中调用一个函数,实际上是调用一个闭包。upvalue 就相当于函数的上下文。
这种带 “上下文” 的函数,也导致了热更新的麻烦,可以说是麻烦透顶了。没法简单的通过替换新的函数代码来更新一个旧闭包,因为旧闭包上可能带着几个 upvalue,这几个 upvalue 的值可能已经发生改变,或者也被其他的函数引用着。
所以,要更新一个旧闭包,得把旧闭包上的所有 upvalue 都找出来,绑定到新函数上,形成一个新闭包,再用这个新闭包替换旧闭包。
本文主要讲 upvalue 在 lua vm 中的实现,下篇文章再讲如何解决带有 upvalue 的闭包的热更新问题。
下文分析基于 lua5.4.6。
1. upvalue
1.1 upvalue 实现上要解决的问题
upvalue 就是外部函数的局部变量,比如下面的函数定义中,var1 就是 inner 的一个 upvalue。
local function getf(delta)local var1 = 100local function inner()return var1+deltaendreturn inner
endlocal f1 = getf(10)
upvalue 复杂的地方在于,在离开了 upvalue 的作用域之后,还要能够访问得到。比如上面调用了 local f1 = getf(10) ,var1 是在 getf 的栈上分配的,getf 返回后,栈空间被抹掉,但 inner 还要能访问 var1,所以要想办法把它捕捉下来。
1.2 upvalue 的实现
下面先讲 lua 闭包的 upvalue,最后再讲 c 闭包的,因为复杂性几乎都在 lua 闭包这里面了。
1.2.1 upvalue 相关的结构体
与 upvalue 相关的结构体有:
1、UpVal,可以说是 upvalue 的本体了,很巧妙的结构,运行时用到的变量。
typedef struct UpVal {CommonHeader;union {TValue *p; /* points to stack or to its own value */ptrdiff_t offset; /* used while the stack is being reallocated */} v;union {struct { /* (when open) */struct UpVal *next; /* linked list */struct UpVal **previous;} open;TValue value; /* the value (when closed) */} u;
} UpVal;
2、Upvaldesc,这个是编译时产生的信息,Proto 结构体就包含 Upvaldesc* 类型的数组:upvalues,用于描述当前函数用到的 upvalue 信息。
typedef struct Upvaldesc {TString *name; /* upvalue name (for debug information) */lu_byte instack; /* whether it is in stack (register) */lu_byte idx; /* index of upvalue (in stack or in outer function's list) */lu_byte kind; /* kind of corresponding variable */
} Upvaldesc;typedef struct Proto {...Upvaldesc *upvalues; /* upvalue information */...
} Proto;
3、lua_State 中的 openupval 字段,它是 UpVal* 类型的链表,它相当于一个 cache,保存当前栈上还存活着的被引用到的 upvalue。
struct lua_State {...UpVal *openupval; /* list of open upvalues in this stack */...
};
4、LClosure 中的 upvals 数组。
typedef struct LClosure {ClosureHeader;struct Proto *p;UpVal *upvals[1]; /* list of upvalues */
} LClosure;
1.2.2 upvalue 的访问
upvalue 是间接访问的,LClosure 结构体的 upvals 字段是 UpVal* 类型的数组。访问的时候先通过 upvals 获得到 UpVal 指针,再通过 UpVal 里面的 v.p 去访问具体的变量,伪码如下:
UpVal* UpValPtr = closure->upvals[upidx];
TValue* p = UpValPtr->v.p;
需要这样间接访问,主要是因为 UpVal 本身会随着函数调用的返回发生状态的变化:从 open 改为 close,这时它的值也从栈上被拷贝到了 “自己身上”,所以指针(v.p)是变化的,不能写死。
至于为什么会发生 open 到 close 的变化,后面会讲。
1.2.3 upvalue 的创建
upvalue 是在编译的时候计算好一个 Proto 需要什么 upvalue,相关信息存放在 Proto 的 upvalues 数组( Upvaldesc *upvalues; /* upvalue information */)中的。
举个例子,对于这样一个脚本,内部的函数 f1、f2 既引用了 getf 之外的变量 var1,也引用了 getf 之内的变量 var2、var3,并且在 local f1, f2 = getf() 调用完成后,f1 还要能访问到 var1、var2,f2 还要能访问到 var1、var3。
local var1 = 1local function getf()local var2 = 2local var3 = 3local function f1()return var1 + var2endlocal function f2()return var1 + var3endreturn f1, f2
endlocal retf1, retf2 = getf()
编译结果是:
从编译结果可以看到,每个 Proto 都会生成 UpvalueDesc 数组,用于描述这个函数(proto)会用到的 upvalue。
index 表示在 LClosure 的 upvals 数组中是第几个。
name 表示变量名。
instack 表示这个 upvalue 是否刚好是上一层函数的局部变量,比如 var2 是 f1 的上一层的,所以 instack 为 true,而 var1 是上两层的,所以为 false。
idx 表示 instack 为 false 的情况下,可以在上一层函数的 upvals 数组的第几个找到这个 upvalue。
kind 表示 upvalue 类型,一般都是 VDKREG,即普通类型。
补充说明,kind 是 lua5.4 才整出来的,lua5.3 及之前都只有 VDKREG。5.4 新增了 RDKCONST,RDKTOCLOSE,RDKCTC。
RDKCONST 是对应到 <const>,指定变量为常量。
RDKTOCLOSE 是对应到 <close>,指定变量为 to be closed 的(类似于 RAII 特性,超出作用域后执行 __close 元函数)。
RDKCTC 我也闹不清楚。
从例子上可以看到,f1 引用了上一层函数 getf 的局部变量 var2,所以它的 instack 值是 true,而引用了上两层的局部变量 var1,则它的 instack 是 false。
instack 主要就是在创建 Closure 的时候帮助初始化 Closure 的 upvals 数组,对于 instack 为 true 的 upvalue,直接搜索上一层函数的栈空间即可,对于 instack 为 false 的 upvalue,就不能这样了,为什么呢?因为上两层的有可能已经不在栈上了。能想象得到吗?举个例子:
local function l1()local var1 = 1local function l2()local var2 = 2local function l3()return var1+var2+3endreturn l3endreturn l2
endlocal ret_l2 = l1()local ret_l3 = ret_l2()
调用 l1 的时候,得到了 l2,这时候 l1 已经返回了,它的栈已经回收了,这时候再调用 l2,在创建 l3 这个闭包的时候,是不可能再找到 l1 的栈去搜索 var1 这个变量的。
所以,要解决这个问题,就需要让 l2 在创建的时候,先帮忙把 var1 捕捉下来保存到自己的 upvals 数组中,等 l3 创建的时候,就可以从 l2 的 upvals 数组中找到了。
这正是 pushclosure 干的活:
static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base,StkId ra) {int nup = p->sizeupvalues;Upvaldesc *uv = p->upvalues;int i;LClosure *ncl = luaF_newLclosure(L, nup);ncl->p = p;setclLvalue2s(L, ra, ncl); /* anchor new closure in stack */for (i = 0; i < nup; i++) { /* fill in its upvalues */if (uv[i].instack) /* upvalue refers to local variable? */ncl->upvals[i] = luaF_findupval(L, base + uv[i].idx);else /* get upvalue from enclosing function */ncl->upvals[i] = encup[uv[i].idx];luaC_objbarrier(L, ncl, ncl->upvals[i]);}
}
函数实现可以看到,instack 为 true 时,调用 luaF_findupval 去上一层函数的栈上搜索,instack 为 false 时,上一层函数已经帮忙捕捉好了,直接从它的 upvals 数组(即这里的 encup 变量中)索引。
这里 uv[i].idx 就是上面 upvaldesc 的 idx 列,即当 instack 为 false 时,它对应于上一层函数的 upvals 数组的第几项。
1.2.4 upvalue 的变化:从 open 到 close
分两个阶段讲,getf 调用时以及 getf 调用后。
1、getf 调用时,var2、var3 这两个变量作为 f1, f2 的 upvalue,它们还处在 getf 的栈上,这时候它们会被放在 lua_State 的 openupval 链表中。
2、getf 调用后,它的栈要被收回的,这时候 lua vm 会调用 luaF_close 来关闭 getf 栈上被引用的 upvalue,最终是 luaF_closeupval 这个函数执行:
void luaF_closeupval (lua_State *L, StkId level) {UpVal *uv;StkId upl; /* stack index pointed by 'uv' */while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) {TValue *slot = &uv->u.value; /* new position for value */lua_assert(uplevel(uv) < L->top.p);luaF_unlinkupval(uv); /* remove upvalue from 'openupval' list */setobj(L, slot, uv->v.p); /* move value to upvalue slot */uv->v.p = slot; /* now current value lives here */if (!iswhite(uv)) { /* neither white nor dead? */nw2black(uv); /* closed upvalues cannot be gray */luaC_barrier(L, uv, slot);}}
}
要理解这个函数,就要知道 StkId level 这个参数的意义,它在这里是 getf 的 base 指针,即它的栈底。同个 lua_State 的函数调用链上的所有函数共用一个栈,按顺序各占一段栈空间,栈是一个数组,所以后调用的函数的变量在栈上的索引是更大的,表现上就是指针值更大。而 openupval 链表里面 Upval 里的 p 就是指向这指针,所以遍历 openupval 的时候,遇到 p 比 base 大的,就表明这个是 getf 栈上的变量,要把它 close 掉。
close 的操作就是把 upval 从 openupval 链表移掉,同时把 upval 的 p 指向的值拷贝到它自身上。
1.2.5 C 闭包中的 upvalue
C 闭包(CClosure)也是有 upvalue 的,是在 lua_pushcclosure 时设置的,但用的是值拷贝,所以多个 C 闭包不能共享 upvalue。如果要在多个 C 闭包,只能是各自的upvalue 指向同一个 table 这样的变量。
CClosure 的 upvalue 直接用的是 TValue 类型的数组(不是指针),在创建的时候用的值拷贝。
typedef struct CClosure {ClosureHeader;lua_CFunction f;TValue upvalue[1]; /* list of upvalues */
} CClosure;
2. 参考
相关文章:
lua vm 五: upvalue
前言 在 lua vm 中,upvalue 是一个重要的数据结构。upvalue 以一种高效的方式实现了词法作用域,使得函数能成为 lua 中的第一类值,也因其高效的设计,导致在实现上有点复杂。 函数 (proto) upvalue 构成了闭包(closu…...
React Native中集成ArcGIS以显示地图、渲染自定义图层和获取地理信息数据
在您的数据采集上传的应用中集成ArcGIS以显示地图、渲染自定义图层和获取地理信息数据是一项常见需求。下面是如何实现这些功能的详细指南,包括具体步骤和示例代码。 1. 显示地图 原生开发 Android: 使用ArcGIS Android SDK。您需要在AndroidManifest…...
java中的异常-异常处理(try、catch、finally、throw、throws)+自定义异常
一、概述 1、java程序员在编写程序时提前编写好对异常的处理程序,在程序发生异常时就可以执行预先设定好的处理程序,处理程序执行完之后,可以继续向后执行后面的程序 2、异常处理程序是在程序执行出现异常时才执行的 二、5个关键字 1、tr…...
深入了解反射
newInstance 可访问性限制: newInstance()方法只能调用无参的公共构造函数。如果类没有无参公共构造函数,那么newInstance()方法将无法使用。 异常处理: newInstance()方法在创建对象时会抛出受检异常InstantiationException和IllegalAcces…...
5、搭建前端项目
5.1 使用vite vue搭建 win r 打开终端 切换到你想要搭建的盘 npm init vitelatest跟着以下步骤取名即可 cd fullStackBlognpm installnpm run dev默认在 http://localhost:5173/ 下启动了 5.2 用vscode打开项目并安装需要的插件 1、删除多余的 HelloWorld.vue 文件 2、安装…...
LLM之Agent初探
Agent是什么? Agent一词起源于拉丁语中的Agere,意思是“to do”。在LLM语境下,Agent可以理解为在某种能自主理解、规划决策、执行复杂任务的智能体。 Agent并非ChatGPT升级版,它不仅告诉你“如何做”,更会帮你去做。…...
目录穿越漏洞CVE-2018-7171复现 又学到一招小技巧!!!!
还是半夜睡不着,打开靶机开始操作。今天看了文件下载和目录穿越漏洞想结合以及防御方法。半夜来进行操作一波。复现一下漏洞,这个网上的文章页比较的少!!! 开始操作起来!!! 进入到页…...
代码随想录算法训练营day41
题目:01背包理论基础、416. 分割等和子集 参考链接:代码随想录 动态规划:01背包理论基础 思路:01背包是所有背包问题的基础,第一次看到比较懵,完全不知道dp数据怎么设置。具体分析还是dp五部曲ÿ…...
从0~1开发财务软件
1.获取图形验证码接口 功能要求 1、随机生成6位字符 2、将字符生成base64位格式的图片,返回给前端 3、将生成的字符存储到redis中,用匿名身份id(clientId)作为key,验证码作为value。 clientId通过/login/getClien…...
Python实现连连看9
(2)标识选中的图片 在判断出玩家选中的是哪一张图片之后,接下来就可以标识选中的图片了,即在该选中的图片外围画矩形。代码如下所示。 FIRSTCLICK True #FIRSTCLICK是全局变量 if(click_col>0 and click_row>0) and \(no…...
项目验收总体计划书(实际项目验收原件参考Word)
测试目标:确保项目的需求分析说明书中的所有功能需求都已实现,且能正常运行;确保项目的业务流程符合用户和产品设计要求;确保项目的界面美观、风格一致、易学习、易操作、易理解。 软件全套文档过去进主页。 一、 前言 ࿰…...
C++基础与深度解析 | 异常处理 | 枚举与联合 | 嵌套类与局部类 | 嵌套名字空间与匿名名字空间 | 位域与volatile关键字
文章目录 一、异常处理二、枚举与联合三、嵌套类与局部类四、嵌套名字空间与匿名名字空间五、位域与volatile关键字 一、异常处理 异常处理用于处理程序在调用过程中的非正常行为。 传统的处理方法:传返回值表示函数调用是否正常结束。 例如,返回 0 表示…...
番外篇 | 利用华为2023最新Gold-YOLO中的Gatherand-Distribute对特征融合模块进行改进
前言:Hello大家好,我是小哥谈。论文提出一种改进的信息融合机制Gather-and-Distribute (GD) ,通过全局融合多层特征并将全局信息注入高层,以提高YOLO系列模型的信息融合能力和检测性能。通过引入MAE-style预训练方法,进一步提高模型的准确性。🌈 目录 🚀1.论文解…...
python记录之字符串
在Python中,字符串是一种非常常见且重要的数据类型,用于存储文本信息。下面,我们将对Python字符串进行深入的讲解,包括其基本操作、常见方法、格式化以及高级特性。 1. 字符串的创建 在Python中,字符串可以通过单引号…...
Elasticsearch 认证模拟题 - 15
一、题目 原索引 task1 的字段 title 字段包含单词 The,查询 the 可以查出 1200 篇文档。重建 task1 索引为 task1_new,重建后的索引, title 字段查询 the 单词,不能匹配到任何文档。 PUT task1 {"mappings": {"…...
g++ 预处理 编译 汇编 链接 命令
g 预处理 编译 汇编 链接 命令 在命令行中使用 g 预处理、编译、汇编和链接源代码文件通常遵循以下步骤: 预处理(Preprocessing):将源代码文件转换为经过预处理器处理的中间文件。 g -E source.cpp -o source.i 编译ÿ…...
计算机视觉中的low-level与 high-level任务
文章目录 low-level任务high-level任务区别联系others参考在计算机视觉领域中,low-level任务和high-level任务是两个重要的概念,他们分别涉及图像处理和分析的不同的层次。 low-level任务 low-level任务主要关注的是图像的底层特征,如颜色、纹理、边缘、形状等。通常涉及对…...
安徽京准NTP时钟系统:GPS北斗卫星授时下的生活重塑
安徽京准NTP时钟系统:GPS北斗卫星授时下的生活重塑 安徽京准NTP时钟系统:GPS北斗卫星授时下的生活重塑 时间的流逝自古以来时钟都是人类生活与活动的基础。然而,随着科技的进步,我们对时间管理和测量的方法已经发生了翻天覆地的变…...
图论第8天
685.冗余连接II 这题需要考虑两种情况: 1.两个输入 2.没有两个输入就是有成环 class Solution { public:static const int N 1005;int father[N];int n;void init(){for (int i 0; i < n; i){father[i] i;}}int find(int x){return x father[x] ? x : f…...
Python怎么配置环境变量:深度探索与实战指南
Python怎么配置环境变量:深度探索与实战指南 在Python编程的世界中,环境变量的配置是一个至关重要的步骤。它不仅影响着Python解释器的运行,还关系到各种第三方库和工具的使用。本文将带你深度探索如何配置Python的环境变量,并为…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
