LuaJIT源码分析(二)数据类型

LuaJIT源码分析(二)数据类型

LuaJIT支持的lua数据类型和官方的lua 5.1版本保持一致,它的源文件中也有一个lua.h:

// lua.h
/*
** basic types
*/
#define LUA_TNONE		(-1)#define LUA_TNIL		0
#define LUA_TBOOLEAN		1
#define LUA_TLIGHTUSERDATA	2
#define LUA_TNUMBER		3
#define LUA_TSTRING		4
#define LUA_TTABLE		5
#define LUA_TFUNCTION		6
#define LUA_TUSERDATA		7
#define LUA_TTHREAD		8

不过LuaJIT用来表示这些类型的通用数据结构TValue定义就和官方lua不太一样了,它的定义要复杂一些:

// lj_obj.h
/* Tagged value. */
typedef LJ_ALIGN(8) union TValue {uint64_t u64;		/* 64 bit pattern overlaps number. */lua_Number n;		/* Number object overlaps split tag/value object. */
#if LJ_GC64GCRef gcr;		/* GCobj reference with tag. */int64_t it64;struct {LJ_ENDIAN_LOHI(int32_t i;	/* Integer value. */, uint32_t it;	/* Internal object tag. Must overlap MSW of number. */)};
#elsestruct {LJ_ENDIAN_LOHI(union {GCRef gcr;	/* GCobj reference (if any). */int32_t i;	/* Integer value. */};, uint32_t it;	/* Internal object tag. Must overlap MSW of number. */)};
#endif
#if LJ_FR2int64_t ftsz;		/* Frame type and size of previous frame, or PC. */
#elsestruct {LJ_ENDIAN_LOHI(GCRef func;	/* Function for next frame (or dummy L). */, FrameLink tp;	/* Link to previous frame. */)} fr;
#endifstruct {LJ_ENDIAN_LOHI(uint32_t lo;	/* Lower 32 bits of number. */, uint32_t hi;	/* Upper 32 bits of number. */)} u32;
} TValue;

首先可以看到有struct的定义有若干宏在里面,这就无形中增加了阅读的难度。我们先把这些宏给处理掉。

首先是打头的LJ_ALIGN宏,这个盲猜都能猜到,是用来控制struct内存8字节对齐用的,它在MSVC环境的定义如下:

// lj_def.h
#define LJ_ALIGN(n)	__declspec(align(n))

LJ_GC64宏会在LJ_TARGET_GC64宏生效时生效,而LJ_TARGET_GC64宏会在LuaJIT判断当前平台为64位平台时,而且没有强行禁用时生效。

// lj_arch.h
#if LUAJIT_TARGET == LUAJIT_ARCH_X86
...
#elif LUAJIT_TARGET == LUAJIT_ARCH_X64
#ifndef LUAJIT_DISABLE_GC64
#define LJ_TARGET_GC64		1
#endif
#elif LUAJIT_TARGET == LUAJIT_ARCH_ARM
...
#endif/* 64 bit GC references. */
#if LJ_TARGET_GC64
#define LJ_GC64			1
#else
#define LJ_GC64			0
#endif

默认编译时是不会强行禁用GC64的,那么这里就可以认为LJ_GC64宏定义为1。

LJ_ENDIAN_LOHI是一个跟大小端相关的宏,而x64都是小端序:

// lj_arch.h
#if LUAJIT_TARGET == LUAJIT_ARCH_X86
...
#elif LUAJIT_TARGET == LUAJIT_ARCH_X64
#define LJ_ARCH_ENDIAN		LUAJIT_LE
#elif LUAJIT_TARGET == LUAJIT_ARCH_ARM
...
#endif#if LJ_ARCH_ENDIAN == LUAJIT_BE
#define LJ_ENDIAN_LOHI(lo, hi)		hi lo
#else
#define LJ_ENDIAN_LOHI(lo, hi)		lo hi
#endif

LJ_FR2宏会在LJ_GC64宏生效时生效:

// lj_arch.h
/* 2-slot frame info. */
#if LJ_GC64
#define LJ_FR2			1
#else
#define LJ_FR2			0
#endif

那么根据当前的这些宏定义,我们整理一下TValue的定义:

// lj_obj.h
/* Tagged value. */
typedef __declspec(align(8)) union TValue {uint64_t u64;		/* 64 bit pattern overlaps number. */lua_Number n;		/* Number object overlaps split tag/value object. */GCRef gcr;		/* GCobj reference with tag. */int64_t it64;struct {int32_t i;	/* Integer value. */uint32_t it;	/* Internal object tag. Must overlap MSW of number. */};int64_t ftsz;		/* Frame type and size of previous frame, or PC. */struct {uint32_t lo;	/* Lower 32 bits of number. */uint32_t hi;	/* Upper 32 bits of number. */} u32;
} TValue;

那么,不同类型的lua数据是怎么统一都装进这个数据结构里的呢?首先,我们注意到它是一个union,而且实际大小居然只有仅仅64位。luajit为了节省空间,使用了一种名为NaN Boxing的技术。我们在luajit的源码注释中可以看到解释:

// lj_obj.h
/*
** Format for 64 bit GC references (LJ_GC64):
**
** The upper 13 bits must be 1 (0xfff8...) for a special NaN. The next
** 4 bits hold the internal tag. The lowest 47 bits either hold a pointer,
** a zero-extended 32 bit integer or all bits set to 1 for primitive types.
**
**                     ------MSW------.------LSW------
** primitive types    |1..1|itype|1..................1|
** GC objects         |1..1|itype|-------GCRef--------|
** lightuserdata      |1..1|itype|seg|------ofs-------|
** int (LJ_DUALNUM)   |1..1|itype|0..0|-----int-------|
** number              ------------double-------------
*/

IEEE754规定,64位的浮点数编码分为3个部分,1个符号位,11个指数位,以及52个尾数位。

LuaJIT源码分析(二)1

不过,浮点数中有些特殊的值,比如NaN。IEEE754规定,如果一个浮点数,指数位全为1,且尾数部分不全为0(与无穷大区分),那么它就是NaN。换句话说,只要满足这个条件,剩下的51位尾数,完全可以用来编码其他的数据。这就是NaN boxing。

LuaJIT源码分析(二)2

有个这个先验知识,我们就能明白luajit的注释了。对于普通的number,就用一个double来表示,此时64位编码就是浮点数的编码;对于其他类型,64位编码中的前13位,设定为1用来表示NaN,接下来的4位叫做itype,用来表示TValue的具体类型,不同itype的定义如下:

// lj_obj.h
/*
** ORDER LJ_T
** Primitive types nil/false/true must be first, lightuserdata next.
** GC objects are at the end, table/userdata must be lowest.
** Also check lj_ir.h for similar ordering constraints.
*/
#define LJ_TNIL			(~0u)
#define LJ_TFALSE		(~1u)
#define LJ_TTRUE		(~2u)
#define LJ_TLIGHTUD		(~3u)
#define LJ_TSTR			(~4u)
#define LJ_TUPVAL		(~5u)
#define LJ_TTHREAD		(~6u)
#define LJ_TPROTO		(~7u)
#define LJ_TFUNC		(~8u)
#define LJ_TTRACE		(~9u)
#define LJ_TCDATA		(~10u)
#define LJ_TTAB			(~11u)
#define LJ_TUDATA		(~12u)
/* This is just the canonical number type used in some places. */
#define LJ_TNUMX		(~13u)

可以看到luajit内部用到的数据类型还要更多一些。这里巧妙的是,luajit直接取反定义了这些type,这样它们的二进制表示都是1打头的,从而可以非常快速地通过移位,即可得到一个TValue的itype:

// lj_obj.h
#define itype(o)	((uint32_t)((o)->it64 >> 47))

接下来我们来看下不同的数据类型,luajit是如何在这64位中存储的。首先是number,luajit定义了numV这个宏来取出number的值,以及setnumV这个宏来设置number的值:

// lj_obj.h
#define tvisnum(o)	(itype(o) < LJ_TISNUM)
#define numV(o)		check_exp(tvisnum(o), (o)->n)
#define setnumV(o, x)		((o)->n = (x))

check_exp是luajit的一种assert的宏,当assert通过时才会执行后续的表达式,numV就是先判断TValue的类型是否为number,如果是则按union的n字段取出值。这个n字段是lua_number类型的,其实就是个double。

// luaconf.h
#define LUA_NUMBER		double

tvisnum的实现也比较巧妙,它不是直接去判断TValue是否为一个非NaN的double,而是尝试取出它的itype,如果itype比最后一个定义的LJ_TISNUM都还要小(注意itype的定义都是取反过的),那么说明它必定不是一个合法定义的TValue类型,也就只能是个double了。

再看primitive types,也就是lua里的nil,true,false,它们只有itype这4位是有效信息,后面47位的payload均为1。

// lj_obj.h
#define tvisnil(o)	((o)->it64 == -1)
#define tvisfalse(o)	(itype(o) == LJ_TFALSE)
#define tvistrue(o)	(itype(o) == LJ_TTRUE)
#define tvisbool(o)	(tvisfalse(o) || tvistrue(o))
#define boolV(o)	check_exp(tvisbool(o), (LJ_TFALSE - itype(o)))
#define setnilV(o)		((o)->it64 = -1)
#define setboolV(o, x)		((o)->it64 = (int64_t)~((uint64_t)((x)+1)<<47))

由宏定义可看出,nil的值就是-1,而-1的二进制表示即为64个1,中间4位itype就是1111,也就是~0u。类似也可以根据false和true的值算出它们的itype,分别为~1u~2u

接下来就是GC objects了。所谓GC objects,就是指lua中可被自动gc回收的对象,例如string,table类型。对于luajit,除了nil,bool,以及light userdata类型之外,其他的类型均属于GC objects。nil和bool类型是值类型,无需gc管理,而light userdata的定义就是外部管理的对象,只是将指针传给了lua,所以也不受lua的gc管理。那么这里就能看出luajit定义LJ_T顺序的讲究了,不属于GC objects的类型都定义在前面,luajit也提供了一个宏来判断一个TValue是否为GC objects:

// lj_obj.h
#define LJ_TISGCV		(LJ_TSTR+1)
#define tvisgcv(o)	((itype(o) - LJ_TISGCV) > (LJ_TNUMX - LJ_TISGCV))

这个宏设计的也很巧妙。如果是不属于GC objects的类型,例如nil和bool类型的itype,对应64位无符号整数的值,都比LJ_TISGCV要大,相减得到的值最大也就是3。而LJ_TNUMX的值要比LJ_TISGCV小,相减得到的值是负数,转换成无符号整数又会变成一个很大的值。而如果itype是属于GC objects的类型,itype对应的64位无符号整数的值,都要大于LJ_TNUMX,且小于LJ_TISGCV。最后,如果TValue是一个普通的double,那么取它的itype得到的值,一定要比LJ_TNUMX要小。luajit通过这样一个简单的宏,就能把这几种数据类型给区分开,实在是令人惊讶。

在开启LJ_GC64的情况下,从TValue中取出GC Object的宏定义如下:

// lj_obj.h
#define LJ_GCVMASK		(((uint64_t)1 << 47) - 1)
#define gcrefu(r)	((r).gcptr64)
#define gcval(o)	((GCobj *)(gcrefu((o)->gcr) & LJ_GCVMASK))

可以看到,这次用到的是TValue的gcr字段,这个字段用来保存真正GC Object的地址。在LJ_GC64下,它是个64位的地址:

// lj_obj.h
/* GCobj reference */
typedef struct GCRef {
#if LJ_GC64uint64_t gcptr64;	/* True 64 bit pointer. */
#elseuint32_t gcptr32;	/* Pseudo 32 bit pointer. */
#endif
} GCRef;

不过,由于TValue前13位需要设置为全1,中间4位用来表示数据类型,实际上luajit只使用低47位的地址空间,也就是128TB,这在当今的现实世界中也绰绰有余了。

再来看看GCobj这个数据结构,它也是一个union:

// lj_obj.h
typedef union GCobj {GChead gch;GCstr str;GCupval uv;lua_State th;GCproto pt;GCfunc fn;GCcdata cd;GCtab tab;GCudata ud;
} GCobj;

GChead是一个通用的数据结构,用来在不知道GCobj具体类型时,获取它的通用信息。

// lj_obj.h
#define GCHeader	GCRef nextgc; uint8_t marked; uint8_t gct
typedef struct GChead {GCHeader;uint8_t unused1;uint8_t unused2;GCRef env;GCRef gclist;GCRef metatable;
} GChead;

nextgc和marked字段是用于gc管理的,gct则是用来表示GCObj类型的字段。GCHeader宏所定义的三个字段,是所有类型的GCObj所共有的。luajit会根据gct字段的值,将一个GCObj转换为实际的类型对象。

// lj_obj.h
/* Macros to convert a GCobj pointer into a specific value. */
#define gco2str(o)	check_exp((o)->gch.gct == ~LJ_TSTR, &(o)->str)
#define gco2uv(o)	check_exp((o)->gch.gct == ~LJ_TUPVAL, &(o)->uv)
#define gco2th(o)	check_exp((o)->gch.gct == ~LJ_TTHREAD, &(o)->th)
#define gco2pt(o)	check_exp((o)->gch.gct == ~LJ_TPROTO, &(o)->pt)
#define gco2func(o)	check_exp((o)->gch.gct == ~LJ_TFUNC, &(o)->fn)
#define gco2cd(o)	check_exp((o)->gch.gct == ~LJ_TCDATA, &(o)->cd)
#define gco2tab(o)	check_exp((o)->gch.gct == ~LJ_TTAB, &(o)->tab)
#define gco2ud(o)	check_exp((o)->gch.gct == ~LJ_TUDATA, &(o)->ud)

最后,我们来看下int类型。默认情况下,luajit是不开启int的,所有的数值都以double类型存储。但是在实际使用中,整数是会经常被用到的,为此luajit提供了LJ_DUALNUM的选项,一些数值可以直接通过int类型存储,方便使用。此时TValue的i字段用来保存int的值。先前提到大小端的struct在这里就发挥作用了,它保证写入int的i字段一定是TValue的后32位,同时int类型的itype则需要写入代表TValue前32位的it字段,也就是需要向左移位47 - 32 =15位。

// lj_obj.h
#define tvisint(o)	(LJ_DUALNUM && itype(o) == LJ_TISNUM)
#define intV(o)		check_exp(tvisint(o), (int32_t)(o)->i)#define setitype(o, i)		((o)->it = ((i) << 15))static LJ_AINLINE void setintV(TValue *o, int32_t i)
{
#if LJ_DUALNUMo->i = (uint32_t)i; setitype(o, LJ_TISNUM);
#elseo->n = (lua_Number)i;
#endif
}

Reference

[1] LuaJIT的变量实现-TValue

[2] LuaJIT Internals: Intro

[3] LuaJIT GC64 模式

[4] NaN boxing or how to make the world dynamic

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

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

相关文章

【数据结构】顺序表的实现——动态分配

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;数据结构 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

vscode通过ssh连接服务器(吐血总结)

一、通过ssh连接服务器 1、打开vscode&#xff0c;进入拓展&#xff08;CtrlShiftX&#xff09;&#xff0c;下载拓展Remote - SSH。 2、点击远程资源管理器选项卡&#xff0c;选择远程&#xff08;隧道/SSH&#xff09;类别。 3、点击SSH配置。 4、在中间上部分弹出的配置文件…

【WPF应用28】WPF中的ProgressBar控件详解与应用示例

在C#开发中&#xff0c;进度条是一个非常重要的用户界面元素&#xff0c;它能够向用户展示操作的进度。ProgressBar控件是.NET Framework组件库中的一部分&#xff0c;专门用于显示任务的进度。本文将详细介绍ProgressBar控件的功能、使用方法、属性设置&#xff0c;并提供在不…

LangChain入门:9.使用FewShotPromptTemplate实现智能提示工程

在构建智能提示工程时&#xff0c;LangChain 提供了强大的 FewShotPromptTemplate 模型&#xff0c;它可以帮助我们更好地利用示例来指导大模型生成更加优质的提示。 在这篇博文中&#xff0c;我们将使用 LangChain 的 FewShotPromptTemplate 模型来设计一个智能提示工程&#…

游戏引擎中的粒子系统

一、粒子基础 粒子系统里有各种发射器&#xff08;emitter&#xff09;&#xff0c;发射器发射粒子&#xff08;particle&#xff09;。 粒子是拥有位置、速度、大小尺寸、颜色和生命周期的3D模型。 粒子的生命周期中&#xff0c;包含产生&#xff08;Spawn&#xff09;、与环…

AcrelEMS-EV 汽车制造能效管理系统解决方案

安科瑞电气股份有限公司 祁洁 15000363176 一、行业现状 1、政府、市场越来越关注碳排放指标。 2、用能设备缺乏完整的在线监视分析系统&#xff0c;无法及时发现用能异常和能源利用效率。 3、不能生产全流程监测和分析能源利用水平&#xff0c;无法及时发现浪费。 4、用…

用计算困难问题的视角看密码学算法

从计算困难问题的视角看密码学算法 计算困难问题是理论计算机和密码学的交叉论题,密码学的加密算法都基于计算困难问题(一般来说是NP-Complete和NP-Hard问题),在这篇文章里我们将讨论计算困难问题和各种加密算法的关系,从而引出我们的观点:密码学算法其实就是利用验证容易但是求…

UltraScale系列底层结构(1)——引言

目录 一、概述 二、Kintex UltraScale FPGA 三、Kintex UltraScale™ FPGA 四、Virtex UltraScale FPGA 五、Virtex UltraScale FPGA 六、Zynq UltraScale MPSoCs 一、概述 Xilinx UltraScale™ 架构是一种革命性的方法&#xff0c;用于创建可编程设备&#xff0c;这些设…

npm 与 yarn 命令比较

npm 和 yarn 都是 JavaScript 的包管理工具&#xff0c;用于管理项目中的依赖包。 安装速度 yarn: 速度较快&#xff0c;因为它会缓存已下载的包&#xff0c;并在安装时利用并行下载来最大化资源利用率。 npm: 速度较慢&#xff0c;尤其是在网络不稳定的情况下&#xff0c;可…

统计HBase表记录条数的方法

java 表的记录集个数_HBase统计表行数(RowCount)的四种方法-CSDN博客 一、hbase-shell的count命令 这是最简单直接的操作&#xff0c;但是执行效率非常低&#xff0c;适用于百万级以下的小表RowCount统计&#xff01; hbase> count ns1:t1 hbase> count t1 hbase>…

Hippo4j线程池实现技术

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容部署运行模式集成线程池监控配置参数默认配置 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家…

力扣热题100_链表_142_环形链表 II

文章目录 题目链接解题思路解题代码 题目链接 142. 环形链表 II 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中…

2024.2.14力扣每日一题——二叉树的层序遍历

2024.2.14 题目来源我的题解方法一 递归实现&#xff08;前序遍历记录深度&#xff09;方法二 非递归实现&#xff08;队列&#xff09; 题目来源 力扣每日一题&#xff1b;题序&#xff1a;102 我的题解 方法一 递归实现&#xff08;前序遍历记录深度&#xff09; 在递归遍…

【Spring】之AOP详解

AOP 什么是AOP&#xff1f; AOP&#xff1a;Aspect Oriented Programming&#xff0c;面向切面编程。 切面指的是某一类特定问题&#xff0c;因此面向切面编程也可以理解为面向特定方法编程。例如&#xff0c;在任何一个系统中&#xff0c;总有一些页面不是用户可以随便访问…

部分背包问题

题源看着是背包&#xff0c;其实是贪心 题目描述 阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 (N≤100) 堆金币&#xff0c;第 i 堆金币的总重量和总价值分别是mi​,vi​(1≤mi​,vi​≤100)。阿里巴巴有一个承重量为T(T≤1000) 的背包&#xff0c;但并不一定有办法将全部的…

Matlab|配电网三相不平衡潮流计算【隐式Zbus高斯法】【可设定变压器数量、位置、绕组方式】

目录 主要内容 部分代码 结果一览 1.以33节点为例 2.以12节点系统为例 下载链接 主要内容 该模型基于隐式Zbus高斯法实现对配电网的三相不平衡潮流计算&#xff0c;通过选项可实现【不含变压器】和【含变压器】两种方式下的潮流计算&#xff0c;并且通过参数设置…

题目:学习gotoxy()与clrscr()函数

题目&#xff1a;学习gotoxy()与clrscr()函数    There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheated …

游戏引擎中的声音系统

一、声音基础 1.1 音量 声音振幅的大小 压强p&#xff1a;由声音引起的与环境大气压的局部偏差 1.2 音调 1.3 音色 1.4 降噪 1.5 人的听觉范围 1.6 电子音乐 将自然界中连续的音乐转换成离散的信号记录到内存中 采样 - 量化 - 编码 香农定理&#xff1a;采样频率是信…

如何查询网站是否被搜索引擎收录

怎么看网站有没有被百度收录 对于网站所有者来说&#xff0c;了解自己的网站是否被百度搜索引擎收录是非常重要的。只有被收录&#xff0c;网站才能在百度搜索结果中展现&#xff0c;从而获取流量和曝光。下面介绍几种方法&#xff0c;让您快速了解自己的网站是否被百度收录。…

SpringBoot+uniApp宠物领养小程序系统 附带详细运行指导视频

文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码1.保存宠物信息代码2.提交订单信息代码3.查询评论信息代码 一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SpringBootuniApp框架开发的宠物领养微信小程序系统。…