64bit対応でハマりそうなのでメモ。
結論
- LuaVMは32bitで固定させる
- CocoaPods使う
- バイトコードを使わない
環境
Lua | 5.2.1 |
---|---|
OS X | 10.8.5 |
Xcode | 5.0 |
CocoaPods | 0.25.0 |
cc | Apple LLVM version 5.0 (clang-500.2.76) (based on LLVM 3.3svn) |
iOSでLuaを動かす
適当にプロジェクトを作る。
[Create a new Xcode project] -> [iOS] -> [Application] -> [Empty Application]
LuaはCocoaPodsで持ってくるか、公式サイトのtarをダウンロードして自前で組込む。
CocoaPodsの場合
Podfileを準備。
platform :ios, '7.0'
pod 'lua', '~> 5.2.1'
Xcodeプロジェクトにインジェクション。
$ cd /path/to/xcodeproject
$ pod install
自前の場合
公式サイトからtarを取ってくる。
$ cd /path/to/work
$ curl -R -O http://www.lua.org/ftp/lua-5.2.1.tar.gz
$ tar zxf lua-5.2.1.tar.gz
展開されたsrcフォルダをXcodeに追加。
全部追加するとコンパイルエラーになるのでmain関数を含んでいるlua.cとluac.cを除外。
Luaを動かす
Luaの面倒を見るヘルパー。
#import <Foundation/Foundation.h>
#import "lualib.h"
#import "lauxlib.h"
@interface LuaManager : NSObject {
lua_State *_L;
}
- (void)regFunction:(NSString*)function callback:(lua_CFunction)callback;
- (BOOL)doLuaData:(NSData *)data tag:(NSString *)tag;
- (BOOL)doLuaFile:(NSString*)file;
- (BOOL)doLuaString:(NSString*)script tag:(NSString*)tag;
@end
#import "LuaManager.h"
@implementation LuaManager
- (id)init
{
if( (self = [super init]) != nil) {
_L = luaL_newstate();
luaL_openlibs(_L);
}
return self;
}
- (void)dealloc
{
lua_close(_L);
}
- (void)regFunction:(NSString*)function callback:(lua_CFunction)callback
{
const char *functionname = [function cStringUsingEncoding:NSUTF8StringEncoding];
lua_register(_L, functionname, callback);
}
- (BOOL)doLuaData:(NSData *)data tag:(NSString *)tag
{
int err;
const char *scriptname = [tag cStringUsingEncoding:NSUTF8StringEncoding];
if ( (err = luaL_loadbuffer(_L, data.bytes, data.length, scriptname)) == LUA_OK) {
err = lua_pcall(_L, 0, 0, 0);
}
if (err == LUA_OK) {
return YES;
} else {
NSLog(@"Lua error on '%@' '%s'", name, lua_tostring(_L, lua_gettop(_L)));
}
return NO;
}
- (BOOL)doLuaFile:(NSString*)file
{
if( !file) return NO;
NSData *data = [NSData dataWithContentsOfFile:file];
if( !data) return NO;
return [self doLuaData:data tag:file];
}
- (BOOL)doLuaString:(NSString*)script tag:(NSString*)tag
{
if( !script || !tag) return NO;
NSData *data = [script dataUsingEncoding:NSUTF8StringEncoding];
if( !data) return NO;
return [self doLuaData:data tag:tag];
}
@end
luaL_newstate()でLua環境を準備し、luaL_loadbuffer()でスクリプトをロード、lua_pcall()で実行。
Luaからコールバックされる関数をlua_register()で登録しておけばLuaが本当に動いているのか確認できる。
#import "LuaManager.h"
..
/* コールバックされる関数 */
static int _luaf_piyo(lua_State *L)
{
NSLog( @"piyo");
return 0;
}
..
- (void)exec
{
LuaManager *lua = [[LuaManager alloc] init];
[lua regFunction:@"piyo" callback:_luaf_piyo]; // コールバック登録
[lua doLuaString:@"piyo()" tag:@"test"]; // スクリプト実行(さっき登録したpiyo()関数をコールするだけ)
}
_luaf_piyo()のNSLog()が実行されていれば準備完了。
バイトコードのヘッダーチェック
Luaのバージョンが同じでも、luacのコンパイル環境によってLuaVMがバイトコードを読んでくれない。
/* the code below must be consistent with the code in luaU_header */
#define N0 LUAC_HEADERSIZE
#define N1 (sizeof(LUA_SIGNATURE)-sizeof(char))
#define N2 N1+2
#define N3 N2+6
static void LoadHeader(LoadState* S)
{
lu_byte h[LUAC_HEADERSIZE];
lu_byte s[LUAC_HEADERSIZE];
luaU_header(h);
memcpy(s,h,sizeof(char)); /* first char already read */
LoadBlock(S,s+sizeof(char),LUAC_HEADERSIZE-sizeof(char));
if (memcmp(h,s,N0)==0) return;
if (memcmp(h,s,N1)!=0) error(S,"not a");
if (memcmp(h,s,N2)!=0) error(S,"version mismatch in");
if (memcmp(h,s,N3)!=0) error(S,"incompatible"); else error(S,"corrupted");
}
..
/*
* make header for precompiled chunks
* if you change the code below be sure to update LoadHeader and FORMAT above
* and LUAC_HEADERSIZE in lundump.h
*/
void luaU_header (lu_byte* h)
{
int x=1;
memcpy(h,LUA_SIGNATURE,sizeof(LUA_SIGNATURE)-sizeof(char));
h+=sizeof(LUA_SIGNATURE)-sizeof(char);
*h++=cast_byte(VERSION);
*h++=cast_byte(FORMAT);
*h++=cast_byte(*(char*)&x); /* endianness */
*h++=cast_byte(sizeof(int));
*h++=cast_byte(sizeof(size_t));
*h++=cast_byte(sizeof(Instruction));
*h++=cast_byte(sizeof(lua_Number));
*h++=cast_byte(((lua_Number)0.5)==0); /* is lua_Number integral? */
memcpy(h,LUAC_TAIL,sizeof(LUAC_TAIL)-sizeof(char));
}
例えばLuaVMがILP32でluacがI32LP64だと、バイトコードはincompatibleエラーになってしまう。
iOSシミュレータの挙動
型のサイズを確認。
int main(int argc, char * argv[])
{
NSLog( @"sizeof(int): %zd", sizeof(int));
NSLog( @"sizeof(long): %zd", sizeof(long));
NSLog( @"sizeof(void*): %zd", sizeof(void*));
NSLog( @"sizeof(size_t): %zd", sizeof(size_t));
}
iPhone Retina (64bit) シミュレータで実行。
sizeof(int): 4
sizeof(long): 8
sizeof(void*): 8
sizeof(size_t): 8
64bitシミュレータはI32LP64。
iPhone Retina (32bit) シミュレータで実行。
sizeof(int): 4
sizeof(long): 4
sizeof(void*): 4
sizeof(size_t): 4
32bitシミュレータはILP32。
Mac側の挙動
MacでluacしたバイトコードをiPhoneで使うので、こちらも検証。
int main( int argc, char **argv)
{
printf( "sizeof(int): %zd\n", sizeof( int));
printf( "sizeof(long): %zd\n", sizeof( long));
printf( "sizeof(size_t): %zd\n", sizeof( size_t));
printf( "sizeof(void*): %zd\n", sizeof( void*));
return 0;
}
コンパイルして実行。
$ gcc -o test64.o test.c
$ ./test64.o
sizeof(int): 4
sizeof(long): 8
sizeof(void*): 8
sizeof(size_t): 8
普通にコンパイルするとI32LP64。
32bitコンパイルの場合。
$ gcc -m32 -o test32.o test.c
$ ./test32.o
sizeof(int): 4
sizeof(long): 4
sizeof(void*): 4
sizeof(size_t): 4
ILP32なので、iOS/Macで適切に組み合わせないと動かない。
luacの32bitビルド
デフォルトだと64bitでコンパイルされるのでMakefileを修正。-m32オプションを追加。
CFLAGS= -O2 -Wall -DLUA_COMPAT_ALL $(SYSCFLAGS) $(MYCFLAGS)
LDFLAGS= $(SYSLDFLAGS) $(MYLDFLAGS)
CFLAGS= -O2 -Wall -DLUA_COMPAT_ALL -m32 $(SYSCFLAGS) $(MYCFLAGS)
LDFLAGS= $(SYSLDFLAGS) $(MYLDFLAGS) -m32
ビルド。
$ cd path/to/work/lua-5.2.1
$ make generic
$ ./src/luac -v
ILP32なバイトコードを生成するluacが完成。
iPhoneでLuaバイトコードを動かす
検証用のLuaを書く。
piyo() -- ObjC側でlua_register()した関数を呼び出す
検証用Luaコードをコンパイル。生成されるのはILP32なバイトコード。
$ luac -s -o test32.bin test.lua
64bit版luacでI32LP64なバイトコードも生成しておく。
$ luac64 -s -o test64.bin test.lua
Xcodeに組み込んで動かしてみる。
- test.lua
- test32.bin
- test64.bin
LuaManager *lua = [[LuaManager alloc] init];
[lua regFunction:@"piyo" callback:_luaf_piyo];
[lua doLuaFile:[[NSBundle mainBundle] pathForResource:@"test" ofType:@"lua"]]; // OK
[lua doLuaFile:[[NSBundle mainBundle] pathForResource:@"test32" ofType:@"bin"]]; // オプションに依存
[lua doLuaFile:[[NSBundle mainBundle] pathForResource:@"test64" ofType:@"bin"]]; // オプションに依存
ビルドオプションによる違い
Build SettingsのArchitecturesの設定値よる結果。
$(ARCHS_STANDARD)
シミュレーター | 32bit | 64bit |
---|---|---|
test.lua | OK | OK |
test32.bin | OK | OK |
test64.bin | NG | NG |
LuaVMがILP32なので64bitのバイトコードは動かない。 |
$(ARCHS_STANDARD_INCLUDING_64_BIT)
シミュレーター | 32bit | 64bit |
---|---|---|
test.lua | OK | OK |
test32.bin | OK | NG |
test64.bin | NG | OK |
LuaVMがILP32とI32LP64に別れてしまうのでどちらかが動かない。 | ||
両方のバイトコードを搭載する必要があり、この挙動は困る。 |
思ったこと
- 自前でLuaを組み込むと事故る恐れ
- CocoaPodsを使っていれば32bit固定になる(?)ので、とりあえず安心
- 64bit対応するならバイトコード使わず平文で配布する
- バイトコードしかない場合は64bit対応を諦める
- 実機はどうなるのか?iPhone5s持ってない。。