今天要研究wax的熱更方案,重拾l(fā)ua云稚。面對(duì)64位lua的問題隧饼。阿里給出的方案是:分別編譯。也就是說64位引擎只支持64位編譯器生產(chǎn)的字節(jié)碼静陈。32位引擎只支持32位編譯器產(chǎn)生的字節(jié)碼燕雁。為此,阿里給出了一組編譯腳本來(lái)解決這個(gè)問題,在我看來(lái)是小題大做了贵白。
而且率拒,這個(gè)方案有個(gè)小小的問題,那就是iOS應(yīng)用目前還是一份代碼同時(shí)編譯arm64和arm32版本的(比如在iPhone 5上的APP安裝得到的是32位的執(zhí)行代碼)禁荒。
我的解決辦法是:直接修改一下lua的引擎就好了猬膨。
我們先來(lái)看為什么64位的引擎不能執(zhí)行32位luac編譯出來(lái)的字節(jié)碼?
- 第一個(gè)是加載頭的時(shí)候
void luaU_header (char* h)
{
int x=1;
memcpy(h,LUA_SIGNATURE,sizeof(LUA_SIGNATURE)-1);
h+=sizeof(LUA_SIGNATURE)-1;
*h++=(char)LUAC_VERSION;
*h++=(char)LUAC_FORMAT;
*h++=(char)*(char*)&x; /* endianness */
*h++=(char)sizeof(int);
*h++=(char)sizeof(size_t);
*h++=(char)sizeof(Instruction);
*h++=(char)sizeof(lua_Number);
*h++=(char)(((lua_Number)0.5)==0); /* is lua_Number integral? */
}
其中的*h++=(char)sizeof(size_t);
是平臺(tái)依賴的呛伴,size_t在32位平臺(tái)下是4字節(jié)勃痴,在64位平臺(tái)下是8字節(jié)。
修改的辦法就是改成:*h++=(char)sizeof(int);
热康。
- 第二個(gè)地方是加載字符串的時(shí)候
static TString* LoadString(LoadState* S)
{
size_t size;
LoadVar(S,size);
if (size==0)
return NULL;
else
{
char* s=luaZ_openspace(S->L,S->b,size);
LoadBlock(S,s,size);
return luaS_newlstr(S->L,s,size-1); /* remove trailing '\0' */
}
}
size的類型是size_t沛申,而LoadVar的實(shí)現(xiàn)是這樣的;
#define LoadVar(S,x) LoadMem(S,&x,1,sizeof(x))
它根據(jù)size的類型從緩沖區(qū)獲得數(shù)據(jù)姐军,原因同上铁材,這里它取了8個(gè)字節(jié)的長(zhǎng)度。而實(shí)際上32位luac編譯產(chǎn)生字節(jié)碼的時(shí)候奕锌,這個(gè)長(zhǎng)度只用了4個(gè)字節(jié)來(lái)表示著觉。
修改的辦法也很簡(jiǎn)單,改成:int size;
即可惊暴。
其實(shí)饼丘,lua這種做法不夠地道,字節(jié)碼格式本應(yīng)該使用一種平臺(tái)無(wú)關(guān)的格式來(lái)定義才是辽话。
那么肄鸽,怎么讓luac編譯產(chǎn)生一個(gè)平臺(tái)無(wú)關(guān)的格式呢?簡(jiǎn)單修改一下luac的實(shí)現(xiàn)也是可以辦到的油啤。
首先典徘,我們需要假定就用4個(gè)字節(jié)表示字符串長(zhǎng)度。(當(dāng)然啦益咬,你也可以假定字符串的長(zhǎng)度就是8字節(jié)逮诲,不過,因?yàn)榇蟛糠脂F(xiàn)存系統(tǒng)上的luac都是32位編譯的础废,他們沒有被修改之前汛骂,產(chǎn)生的字節(jié)碼中,字符串的長(zhǎng)度是用4字節(jié)表示的评腺。)
然后帘瞭,修改一下這個(gè)方法:
static void DumpString(const TString* s, DumpState* D)
{
if (s==NULL || getstr(s)==NULL)
{
size_t size=0;
DumpVar(size,D);
}
else
{
size_t size=s->tsv.len+1; /* include trailing '\0' */
DumpVar(size,D);
DumpBlock(getstr(s),size,D);
}
}
如果你真正讀懂了文章前面的部分,那么這里怎么改蒿讥,應(yīng)該很清楚了吧蝶念?
好了抛腕,重新編譯出來(lái)的luac,無(wú)論你用32位方式編譯媒殉,還是64位方式編譯担敌,最終得到的編譯器(luac)編譯的lua字節(jié)碼,它都是能被上面修改過的引擎正確加載了廷蓉。
再也不用折騰32位一個(gè)版本全封,64位一個(gè)版本了~。
我是怎么定位到這些需要修改的地方的呢桃犬?
首先刹悴,我們要有問題的環(huán)境,也就是一個(gè)64位編譯出來(lái)的lua64運(yùn)行引擎攒暇,一個(gè)32位luac32編譯出來(lái)的字節(jié)碼luac32.out土匀。
然后,我們用這個(gè)lua64執(zhí)行這個(gè)luac32.out形用。這時(shí)候就轧,會(huì)出現(xiàn)第一個(gè)錯(cuò)誤信息:
bad header in precompiled chunk
在源代碼里搜索這個(gè)錯(cuò)誤提示,沒有找到任何的結(jié)果田度。于是妒御,把錯(cuò)誤提示縮小一些(因?yàn)椋覀兤綍r(shí)也也經(jīng)常寫"error:%s"這樣的日志輸出)每币。直到查找"bad header"的時(shí)候携丁,定位到一處函數(shù)了LoadHeader
琢歇,不需要費(fèi)太多力氣大概就知道是最終這個(gè)函數(shù)luaU_header
的結(jié)果出錯(cuò)導(dǎo)致的錯(cuò)誤日志兰怠。好,第一處修改點(diǎn)就是這么定位出來(lái)的了李茫。
繼續(xù)跑測(cè)試用例揭保,這次直接crash了
malloc: *** mach_vm_map(size=8461182042380451840) failed (error code=3)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
一看就是分配了一個(gè)非常非常大的內(nèi)存了。這里魄宏,是需要一些靈感的秸侣,我自己看到這個(gè)錯(cuò)誤的第一直覺是,加載字符串的時(shí)候出錯(cuò)了(不要問我為什么有這樣的直覺宠互,一般只有犯過錯(cuò)的人才有這樣的直覺)味榛。
這個(gè)錯(cuò)誤沒有什么日志信息可以輔助定位,大概只能單步調(diào)試一個(gè)函數(shù)一個(gè)函數(shù)的執(zhí)行過去予跌,看看到那個(gè)函數(shù)執(zhí)行就報(bào)這個(gè)錯(cuò)了搏色。
然后依次縮小范圍。幸運(yùn)的是券册,這只是一個(gè)很單純的單線程程序频轿,很快就可以定位到是LoadFunction-->f->source=LoadString(S)
這個(gè)語(yǔ)句出現(xiàn)的問題了垂涯,果然和我的猜想一樣。
就這樣航邢,問題搞定啦:)
后記:
有一位讀者受到文章啟發(fā)耕赘,針對(duì)5.3.4版本做出了對(duì)應(yīng)的調(diào)整。社區(qū)的互動(dòng)感覺還是蠻神奇的膳殷,放上鏈接操骡,有需要的可以參考一下:
http://www.reibang.com/p/67d9baabd318