LuaJIT的變量實現-TValue

Lua是動態類型的編程語言,變量的值能夠是數值、字符串、table等全部支持的數據類型。在Lua虛擬機中每一個變量都是用一個TValue結構體表示。LuaJIT出於效率的考慮從新組織了TValue結構體。編程

lua-5.1中的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結構,依據類型,有不一樣的含義。編程語言

  • 當類型位nil時,nil自己再也不須要其餘標識,Value成員沒有意義
  • 當類型爲boolean時,成員b爲0或1表示false或true
  • 當類型爲number時,成員n表示,爲double類型
  • 當類型爲lightuserdata時,成員p,表示指針
  • 當類型爲function/string/userdata/table/thread等須要GC管理的類型時,成員gc表示相應GC對象的指針。

這樣一個變量只要對應一個TValue結構即可以表示Lua支持的全部類型。佈局

lua中全部的數值都是用double類型的浮點數來表示,須要佔用64位的空間。再加上額外的int類型成員tt來表示類型,一個TValue結構至少須要64+32=96位的空間,若是按照8字節對其的話就須要佔用128位的空間。而LuaJIT中經過Nan-boxing技術,重組了TValue,只須要佔用64位的空間。ui

Nan-boxing

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對不一樣類型有兩種處理方式。指針

1. 用47位地址表示指針

對於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位便可表示全部可以使用的地址。

輸入圖片說明

2. 只使用最低的2G的地址空間

以Linux爲例,LuaJIT默認經過mmap系統調用來分配內存,對於x86_64平臺的64位程序,mmap有一個MAP_32BIT標記選項,表示只分配虛擬地址空間的低2GB的空間,這樣分配的內存地址,高33位皆爲0, 相應的指針只須要32位的空間便可。

3. LuaJIT的處理與GC64模式

對於lightuserdata類型的值,LuaJIT用47位表示,對於GC類型的對象,都是經過mmap加MAP_32BIT標記分配的,用32位表示,這限制了LuaJIT只能使用不超過2GB的內存。爲了擺脫這個限制,LuaJIT增長了GC64模式,開啓後,全部的指針類型,包括lightuserdata都使用47位指針來表示。

LuaJIT的TValue結構

默認模式下TValue佈局

LuaJIT中的TValue佈局以下所示

輸入圖片說明

能夠經過下面的步驟判斷值的類型

  1. 若是最高的16位(即48到63位)不全爲1,表示這是一個double類型的浮點數,
  2. 最高的16位全爲1,若是第47位爲0時表示一個lightuserdata類型,
  3. 其他狀況下,高32位表示類型,低32位表示實際值。

GC64模式下TValue佈局

GC64模式開啓後以下所示

輸入圖片說明

這裏比較特殊的是最高的13位全位1表double類型,itype表示類型,佔4位,指針佔用47位,總共還是64位。

TValue結構

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)

LJ_DUALNUM

Lua中數值都是double類型的浮點數,而實際使用時常常會用到整數,而位操做等都須要將double類型轉換成整數進行。爲此LuaJIT提供了LJ_DUALNUM的選項,一些數值能夠直接經過int類型存儲,方便使用,至關於爲Lua增長了整數這個數據類型。

LJ_DUALNUM的定義能夠參考lj_arch.h,不過對經常使用的x86_64架構,默認並無啓用LJ_DUALNUM。

參考

相關文章
相關標籤/搜索