PSPでmrubyを動かしてみた話

どうも、昔PSPを触ってた人です。
サークルでmrubyを触る機会があって、PSPでも動くかなーと思って試したら普通に動いたので、やり方のメモと言う形で書きたいと思います。

PSPの環境構築の話はここではしないので、各自ググるなりしてください。

## mrubyの準備
まずはGithubから[mruby](https://github.com/mruby/mruby)を落としてきます。
落としてきたら、`build_config.rb`にPSP用のmrubyライブラリを作るための設定を書いてあげます。
下のコードを`build_config.rb`に追記してください。

```rb
MRuby::Build.new("psp") do |conf|
  # load specific toolchain settings

  # Gets set by the VS command prompts.
  toolchain :gcc

  enable_debug

  # Use mrbgems
  # conf.gem 'examples/mrbgems/ruby_extension_example'
  # conf.gem 'examples/mrbgems/c_extension_example' do |g|
  #   g.cc.flags << '-g' # append cflags in this gem
  # end
  # conf.gem 'examples/mrbgems/c_and_ruby_extension_example'
  # conf.gem :github => 'masuidrive/mrbgems-example', :checksum_hash => '76518e8aecd131d047378448ac8055fa29d974a9'
  # conf.gem :git => 'git@github.com:masuidrive/mrbgems-example.git', :branch => 'master', :options => '-v'

  # include the default GEMs
  conf.gembox 'non-bin'
  # C compiler settings
  BASE_PATH = "/usr/local/pspdev/bin"
  PREFIX = "psp-"
  conf.cc do |cc|
    cc.command = ENV['CC'] || "#{BASE_PATH}/#{PREFIX}gcc"
    cc.flags = [ENV['CFLAGS'] || %w(-O2 -G0 -Wall -g -fno-exceptions)]
  #   cc.include_paths = ["#{root}/include"]
  #   cc.defines = %w(DISABLE_GEMS)
  #   cc.option_include_path = '-I%s'
  #   cc.option_define = '-D%s'
    cc.compile_options = "%{flags} -MMD -o %{outfile} -c %{infile}"
  end

  # mrbc settings
  # conf.mrbc do |mrbc|
  #   mrbc.compile_options = "-g -B%{funcname} -o-" # The -g option is required for line numbers
  # end

  # Linker settings
  # conf.linker do |linker|
  #   linker.command = ENV['LD'] || 'gcc'
  #   linker.flags = [ENV['LDFLAGS'] || []]
  #   linker.flags_before_libraries = []
  #   linker.libraries = %w()
  #   linker.flags_after_libraries = []
  #   linker.library_paths = []
  #   linker.option_library = '-l%s'
  #   linker.option_library_path = '-L%s'
  #   linker.link_options = "%{flags} -o %{outfile} %{objs} %{libs}"
  # end

  # Archiver settings
  conf.archiver do |archiver|
    archiver.command = ENV['AR'] || "#{BASE_PATH}/#{PREFIX}ar"
    archiver.archive_options = 'rs %{outfile} %{objs}'
  end

  # Parser generator settings
  # conf.yacc do |yacc|
  #   yacc.command = ENV['YACC'] || 'bison'
  #   yacc.compile_options = '-o %{outfile} %{infile}'
  # end

  # gperf settings
  # conf.gperf do |gperf|
  #   gperf.command = 'gperf'
  #   gperf.compile_options = '-L ANSI-C -C -p -j1 -i 1 -g -o -t -N mrb_reserved_word -k"1,3,$" %{infile} > %{outfile}'
  # end

  # file extensions
  # conf.exts do |exts|
  #   exts.object = '.o'
  #   exts.executable = '' # '.exe' if Windows
  #   exts.library = '.a'
  # end

  # file separetor
  # conf.file_separator = '/'

  # bintest
  # conf.enable_bintest
end
```

これはホスト用の設定をコピーしてきて、一部を変えただけです。
ポイントは使用するGCCとArchiverを変更している点です。
`ar`をpspのものにしておかないと、リンクできないライブラリが生成されてしまいます。

あとは、使用するgemboxを変えています。

```rb
conf.gembox 'non-bin'
```

PSP用の実行ファイルを生成できないため、それらを除いたgemboxを指定しています。
下のオプションを追加することで実行ファイルは生成しなくなるみたいな情報があったんですが、普通に生成しようとするので無理やりgemboxを書き換える方針にしました。

```
conf.bins = []
```

`non-bin`というgemboxは存在しないため、自分で作る必要があります。
まずはデフォルトのgemboxをコピーします。

```zsh
$ cp mrbgems/default.gembox mrbgems/non-bin.gembox
```

`mrbgems/non-bin.gembox`を開いて、`mruby-bin-`から始まるgemをコメントアウトします。

```rb
  ...

  # Use toplevel object (main) methods extension
  conf.gem :core => "mruby-toplevel-ext"

  # Generate mirb command
  # conf.gem :core => "mruby-bin-mirb"

  # Generate mruby command
  # conf.gem :core => "mruby-bin-mruby"

  # Generate mruby-strip command
  # conf.gem :core => "mruby-bin-strip"

  # Use Kernel module extension
  conf.gem :core => "mruby-kernel-ext"

  ...
```

## mrubyのコンパイル
準備が終わったらコンパイルするだけです。

```zsh
$ make

...

================================================
      Config Name: psp
 Output Directory: build/psp
    Included Gems:
             mruby-sprintf - standard Kernel#sprintf method
             mruby-print - standard print/puts/p
             mruby-math - standard Math module
             mruby-time - standard Time class
             mruby-struct - standard Struct class
             mruby-enum-ext - Enumerable module extension
             mruby-string-ext - String class extension
             mruby-numeric-ext - Numeric class extension
             mruby-array-ext - Array class extension
             mruby-hash-ext - Hash class extension
             mruby-range-ext - Range class extension
             mruby-proc-ext - Proc class extension
             mruby-symbol-ext - Symbol class extension
             mruby-random - Random class
             mruby-object-ext - Object class extension
             mruby-objectspace - ObjectSpace class
             mruby-fiber - Fiber class
             mruby-enumerator - Enumerator class
             mruby-enum-lazy - Enumerable::Lazy class
             mruby-toplevel-ext - toplevel object (main) methods extension
             mruby-kernel-ext - Kernel module extension
             mruby-compiler - mruby compiler library
================================================
```

`Config Name: psp`といった表示が出ていればコンパイル成功です。
生成されたライブラリは`build/psp/lib/`の中に`libmruby.a`という名前で入っているはずです。

## PSPアプリを作る

それではPSPでrubyのコードを動かしてみましょう。
とりあえずhello worldですかね。

mrubyのディレクトリと同じ階層にアプリを作る用のディレクトリを用意して、先ほど生成した`libmruby.a`とmrubyの関数群が入っているディレクトリもコピーします。

```zsh
$ ls
mruby/
$ mkdir mruby-psp
$ ls
mruby/  mruby-psp/
$ cp mruby/build/psp/lib/libmruby.a mruby-psp/
$ cp -r mruby/include/ mruby-psp/
$ cd mruby-psp
$ ls
include/  libmruby.a
```

このディレクトリにコードやMakefileを設置しましょう

```c:main.c
#include <pspkernel.h>
#include <pspmodulemgr.h>
#include <pspdisplay.h>
#include <pspdebug.h>
#include <pspthreadman.h>
#include <pspctrl.h>
#include <pspsdk.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <mruby.h>
#include <mruby/proc.h>
#include <mruby/compile.h>
#include <mruby/string.h>

#define printf pspDebugScreenPrintf

PSP_MODULE_INFO("Mruby", 0, 0, 1);
PSP_MAIN_THREAD_ATTR(0);
PSP_HEAP_SIZE_KB(-512);

/* exit callback */
int exit_callback(int arg1, int arg2, void *common){
	sceKernelExitGame();
	return 0;
}

/* callback thread */
int CallbackThread(SceSize args, void *argp){
	int cbid;
	cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
	sceKernelRegisterExitCallback(cbid);
	sceKernelSleepThreadCB();
	return 0;
}


/* setup the callback thread */
int SetupCallbacks(void){
	int thid = 0;
	thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0);
	if(thid >= 0)
		sceKernelStartThread(thid, 0, 0);
	return thid;
}

mrb_value myputs(mrb_state *mrb, mrb_value self){
	mrb_value val;
	mrb_get_args(mrb, "S", &val);
	printf(RSTRING_PTR(val));
	return mrb_nil_value();
}

int main(){
	/* exit callback */
	SetupCallbacks();

	/* setup debug info handler */
	pspDebugScreenInit();

	mrb_state *mrb = mrb_open();

	//Objectクラスにmyputsをメソッドとして定義
	mrb_define_method(mrb, mrb->object_class,"myputs", myputs, ARGS_REQ(1));

	mrb_load_string(mrb, "myputs 'hello world!'");

	mrb_close(mrb);

	while (1) {
		sceKernelDelayThread(10*1000*1000);
	}

	sceKernelExitGame();
}

```

ポイントは、`PSP_HEAP_SIZE_KB(-512);`でヒープ領域を多めに確保することです。
`PSP_HEAP_SIZE_KB`にマイナス値を渡すと、メモリの上限から指定した値を引いただけヒープを確保します(確か)


```mf:Makefile
TARGET = mruby
OBJS = main.o

OPTION_FLAGS =

INCDIR = ./include
CFLAGS = -O2 -G0 -Wall -g $(OPTION_FLAGS)
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS = $(CFLAGS)

BUILD_PRX = 1

LIBDIR =
LDFLAGS =
LIBS = libmruby.a
LIBS += -lm -lc

EXTRA_TARGETS = EBOOT.PBP
PSP_EBOOT_TITLE = mruby

PSPSDK=$(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak
```

`INCDIR`に先ほどコピーした`include`ディレクトリを、`LIBS`には`libmruby.a`を指定します。

あとはmakeすればEBOOT.PBPが生成されると思います。
PSPにコピーして起動し、無事に「wello world!」が表示されれば成功です。

## 感想とか
たぶん需要はすごく少ないんじゃないかと思います。
ただ、PSPをrubyで動かすことができるのは面白そうかなと思います。
最初、mrubyからPSPのAPIを叩けるライブラリとか作ろうかなと思いましたが、他にもしたいことがあったので、やめました。
興味がある人は是非挑戦して公開して欲しいです。