Lua是動態類型的編程語言,變量的值能夠是數值、字符串、table等全部支持的數據類型。在Lua虛擬機中每一個變量都是用一個TValue結構體表示。LuaJIT出於效率的考慮從新組織了TValue結構體。編程
lua-5.1中TValue的結構定義在lobject.h中,以下所示架構
/* ** Union of all Lua values */ typedef union { GCObject *gc; void *p; lua_Number n; int b; } Value; #define TValuefields Value value; int tt typedef struct lua_TValue { TValuefields; } TValue;
TValue結構體包含了兩個部分,int類型的成員tt表示類型,Value成員是一個union結構,依據類型,有不一樣的含義。編程語言
這樣一個變量只要對應一個TValue結構即可以表示Lua支持的全部類型。佈局
lua中全部的數值都是用double類型的浮點數來表示,須要佔用64位的空間。再加上額外的int類型成員tt來表示類型,一個TValue結構至少須要64+32=96位的空間,若是按照8字節對其的話就須要佔用128位的空間。而LuaJIT中經過Nan-boxing技術,重組了TValue,只須要佔用64位的空間。ui
ieee754是使用最普遍的浮點數編碼格式,它將浮點數編碼成三個部分,符號、指數和尾數。以下所示編碼
雙精度類型即double類型,最高位爲符號位,後面的11位表示指數,最低52位爲尾數。三個組合表示浮點數的值。lua
浮點數有些特殊的值,其中之一就是NaN(Not a Number)。有些浮點數運算如0/0獲得的結果就是NaN。IEEE 754標準中,若是指數部分全爲1,且尾數部分不全爲0時,表示值爲 NaN。double類型的浮點數尾數部分有52位,NaN只要求這52位不全爲0便可,只要其中一個是1剩餘的51位就能夠編碼表示其餘的含義。spa
實際使用的浮點數運算單元也只會產生一種NaN表示,即0xfff8_0000_0000_0000,只用了最高的13位,剩餘的的51位即可以表示Lua中其餘的字符串、table等。操作系統
TValue結構中有些成員是指針,64位系統中,指針的長度爲64位,那麼如何在剩下的51位中表示指針類型呢?爲此LuaJIT對不一樣類型有兩種處理方式。指針
對於64位系統,理論上每一個進程都有64位的線性地址空間,共有16,777,216TB。然而可預見的未來,操做系統和應用並不須要這麼多的內存,支持如此大的地址會增長地址轉換的複雜性和成本, 所以如今的實現並不容許使用所有的地址空間。
以率先實現64位架構的AMD爲例,在進行內存地址轉換時,只會使用地址的低48位,而且要求從第48到63的這16位須要與第47位相同。即地址必須在0到00007FFF'FFFFFFFF 和 FFFF8000'00000000 到 FFFFFFFF'FFFFFFFF這兩個範圍內,共有256TB的虛擬地址空間。
操做系統自己也會對內存使用進行限制,以Linux爲例,將高128TB的空間劃歸內核使用,這樣用戶態進程只能低128TB的地址,以下圖所示。地址的高17位皆爲0,所以使用47位便可表示全部可以使用的地址。
以Linux爲例,LuaJIT默認經過mmap系統調用來分配內存,對於x86_64平臺的64位程序,mmap有一個MAP_32BIT標記選項,表示只分配虛擬地址空間的低2GB的空間,這樣分配的內存地址,高33位皆爲0, 相應的指針只須要32位的空間便可。
對於lightuserdata類型的值,LuaJIT用47位表示,對於GC類型的對象,都是經過mmap加MAP_32BIT標記分配的,用32位表示,這限制了LuaJIT只能使用不超過2GB的內存。爲了擺脫這個限制,LuaJIT增長了GC64模式,開啓後,全部的指針類型,包括lightuserdata都使用47位指針來表示。
LuaJIT中的TValue佈局以下所示
能夠經過下面的步驟判斷值的類型
GC64模式開啓後以下所示
這裏比較特殊的是最高的13位全位1表double類型,itype表示類型,佔4位,指針佔用47位,總共還是64位。
LuaJIT的TValue定義在lj_obj.h中,以下面所示,由於Nan-boxing的緣故,這裏的TValue結構並無直接反映實際的內存佈局。
/* 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_GC64 GCRef 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. */ ) }; #else struct { 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 .......... } TValue;
全部的數據類型定義中最高的幾位均爲1,方便與浮點數的區分
#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) /* Integers have itype == LJ_TISNUM doubles have itype < LJ_TISNUM */ #if LJ_64 && !LJ_GC64 #define LJ_TISNUM 0xfffeffffu #else #define LJ_TISNUM LJ_TNUMX #endif
LuaJIT定義了一系列宏用來判斷值的類型。
#if LJ_GC64 #define itype(o) ((uint32_t)((o)->it64 >> 47)) #define tvisnil(o) ((o)->it64 == -1) #else #define itype(o) ((o)->it) #define tvisnil(o) (itype(o) == LJ_TNIL) #endif #define tvisfalse(o) (itype(o) == LJ_TFALSE) #define tvistrue(o) (itype(o) == LJ_TTRUE) #define tvisbool(o) (tvisfalse(o) || tvistrue(o)) #if LJ_64 && !LJ_GC64 #define tvislightud(o) (((int32_t)itype(o) >> 15) == -2) #else #define tvislightud(o) (itype(o) == LJ_TLIGHTUD) #endif #define tvisstr(o) (itype(o) == LJ_TSTR) #define tvisfunc(o) (itype(o) == LJ_TFUNC) #define tvisthread(o) (itype(o) == LJ_TTHREAD) #define tvisproto(o) (itype(o) == LJ_TPROTO) #define tviscdata(o) (itype(o) == LJ_TCDATA) #define tvistab(o) (itype(o) == LJ_TTAB) #define tvisudata(o) (itype(o) == LJ_TUDATA) #define tvisnumber(o) (itype(o) <= LJ_TISNUM) #define tvisint(o) (LJ_DUALNUM && itype(o) == LJ_TISNUM) #define tvisnum(o) (itype(o) < LJ_TISNUM)
Lua中數值都是double類型的浮點數,而實際使用時常常會用到整數,而位操做等都須要將double類型轉換成整數進行。爲此LuaJIT提供了LJ_DUALNUM的選項,一些數值能夠直接經過int類型存儲,方便使用,至關於爲Lua增長了整數這個數據類型。
LJ_DUALNUM的定義能夠參考lj_arch.h,不過對經常使用的x86_64架構,默認並無啓用LJ_DUALNUM。