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、在中间上部分弹出的配置文件…

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问题),在这篇文章里我们将讨论计算困难问题和各种加密算法的关系,从而引出我们的观点:密码学算法其实就是利用验证容易但是求…

Hippo4j线程池实现技术

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

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

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

【Spring】之AOP详解

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

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

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

游戏引擎中的声音系统

一、声音基础 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框架开发的宠物领养微信小程序系统。…

Elasticsearch的倒排索引是什么?

文章目录 什么是ES&#xff1f;什么是倒排索引&#xff1f;为什么叫做倒排索引&#xff1f;分词器的使用 什么是ES&#xff1f; Elasticsearch是基于 Apache Lucene【lusen】的搜索引擎&#xff0c;支持Restful API风格【可以使用常见的HTTP请求来访问】&#xff0c;并且搜索速…

在js中本地存储的数组如何转成对象

一、此方法仅适用一维数组&#xff1b; 二、效果图 使用后 三、js代码。 function gong(s){console.log(s);let data;let kk1;// 检查ask_id是否不为空 if (s.ask_id null ) { kk1}else{kk2let dd;dds.data;sessionStorage.setItem(wenda,JSON.stringify(dd[0]))window.l…

1、jvm基础知(一)

什么是JVM&#xff1f; 1、定义&#xff1a;JVM 指的是Java虚拟机&#xff08; Java Virtual Machine &#xff09;。JVM 本质上是一个运行在计算机上的程序&#xff0c; 他的职责是运行Java字节码文件&#xff0c;Java虚拟机上可以运行Java、Kotlin、Scala、Groovy等语言 任…

如何使用极狐GitLab Maven 仓库?

本文作者&#xff1a;徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了如何使用极狐GitLa…

VSCode - 离线安装扩展python插件教程

1&#xff0c;下载插件 &#xff08;1&#xff09;首先使用浏览器打开 VSCode 插件市场link &#xff08;2&#xff09;进入插件主页&#xff0c;点击右侧的 Download Extension 链接&#xff0c;将离线安装包下载下来&#xff08;文件后缀为 .vsix&#xff09; 2&#xff0c;…

学透Spring Boot 003 —— Spring 和 Spring Boot 常用注解(附面试题和思维导图)

这是 学透 Spring Boot 专栏 的第三篇&#xff0c;欢迎关注我&#xff0c;与我一起学习和探讨 Spring Boot 相关知识&#xff0c;学透 Spring Boot。 从面试题说起 今天我们通过一道和Spring Boot有关的常见面试题入手。 面试题&#xff1a;说说 Spring Boot 中有哪些常用注解…

13.Python图形用户界面

我们之前的程序运行结果都被输出到命令提示符&#xff08;终端&#xff09;窗口&#xff0c;界 面比较简陋。本章讲解如何将其输出到图形界面。 1 Python中的图形用户界面开发库 注Qt是一个跨平台的C应用程序开发框架&#xff0c;被广泛用于开发GUI 程序&#xff0c;也可用于开…