iOS7でLuaバイトコードを実行するときの注意

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を準備。

```ruby: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の面倒を見るヘルパー。

``` objectivec:LuaManager.h
#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
```

``` objectivec:LuaManager.m
#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が本当に動いているのか確認できる。

``` objectivec:ViewController.m
#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がバイトコードを読んでくれない。

``` cpp:lua-5.2.1/src/lundump.c
/* 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シミュレータの挙動
型のサイズを確認。

``` objectivec:main.m
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で使うので、こちらも検証。

``` cpp:test.c
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オプションを追加。

``` CMake:lua-5.2.1/src/Makefile(修正前)
CFLAGS= -O2 -Wall -DLUA_COMPAT_ALL $(SYSCFLAGS) $(MYCFLAGS)
LDFLAGS= $(SYSLDFLAGS) $(MYLDFLAGS)
```

``` CMake:lua-5.2.1/src/Makefile(修正後)
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を書く。

```Lua:test.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

``` objectivec:ViewController.m
    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持ってない。。