LoginSignup
11
12

More than 5 years have passed since last update.

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

Posted at

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

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

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
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が本当に動いているのか確認できる。

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がバイトコードを読んでくれない。

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シミュレータの挙動

型のサイズを確認。

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で使うので、こちらも検証。

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オプションを追加。

lua-5.2.1/src/Makefile(修正前)
CFLAGS= -O2 -Wall -DLUA_COMPAT_ALL $(SYSCFLAGS) $(MYCFLAGS)
LDFLAGS= $(SYSLDFLAGS) $(MYLDFLAGS)
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を書く。

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
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持ってない。。
11
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
12