mruby で Raspberry Pi の GPIO をいじるというやつで、sleep をビジーループにしていたのがどうしてもひっかかっていた。

どうも ARM には割込みが起きるまで眠る命令があるみたいなので、それを使ってみることにした。コード全体

static mrb_value mrb_mruby_raspberrypi_gpio_gem_delay_us(mrb_state* mrb, mrb_value self) {
	mrb_int delay;
	mrb_get_args(mrb, "i", &delay);

	// Reset timer flags
	PUT32(ARM_TIMER_CONTROL, 0x3E0020);
	// Load count down timer value
	PUT32(ARM_TIMER_LOAD, delay-1);
	PUT32(ARM_TIMER_RELOAD, delay-1);
	// predevider = (apb_clk - freq) / freq
	PUT32(ARM_TIMER_PRE_DIVIDER, 250 - 1);
	PUT32(ARM_TIMER_IRQ_CLEAR_ACK, 0);
	PUT32(ARM_TIMER_CONTROL,
		(0x3E<<16) | // default free-running pre-scaler
		(1<<7)     | // timer enabled
		(1<<5)     | // timer interrupt enabled
		(1<<1)       // 23-bit counter
	);

	// Enable ARM Timer IRQ
	PUT32(ARM_INTERRUPT_ENABLE_BASIC_IRQS, 1);
	
	while ((GET32(ARM_INTERRUPT_IRQ_BASIC_PENDING) & 1) == 0) {
		// Waiting For Interrupt
		asm volatile ("wfi");
	}

	// Disable ARM Timer IRQ
	PUT32(ARM_INTERRUPT_DISABLE_BASIC_IRQS, 1);

	return mrb_nil_value();
}

コメントにある通りだけど、割込みを設定して、ARM_INTERRUPT_IRQ_BASIC_PENDING のフラグを見つつ、セットされるまでは wfi 命令で継続的に眠る、というようにしてみた。他に割込みを設定していないので、while は1回で抜けるつもり……

wfi 命令はオプショナルな命令らしく、ハードによっては nop として解釈されるらしい。なのでこのような実装の場合、フラグをポーリングするようなコードを併用したほうが安全そう。

というか、実際 wfi 命令がちゃんと動いているかを確かめる方法が面倒くさい。電流を測るしかなさそう。電流を今回測るところまでやってないので、ちゃんと動いてないのかもしれない。ただ、挙動として割込みをポーリングで待つ、というのはできいてるっぽい。

どうでもいいけど wfi で検索しても wifi 扱いされてだいぶウザい。

  1. トップ
  2. tech
  3. 割込みと WFI 命令を使った sleep の実装

//#!gcc -O0

#include <stdio.h>

int main (int argc, char *argv[]) {

        int a;
        int b;

        a = 0; b = 3;

        asm volatile (
                "mov r0, %[x];"
                "mov %[x], %[y];"
                "mov %[y], r0;"
                : [x] "+r" (a), [y] "+r" (b)
                :
                : "r0"
        );

        printf("a=%d, b=%d\n", a, b);

        return 0;
}

asm でやる必要は全然ないけど inline asm の文法が意味不明なのでちょっと書いてみた。

asm( code : output list : input list : clobber list)

という文法らしいけど、パっと見さっぱりわからない。上記の例(値の交換)だと

  • a と b どちらも読み書きが必要なので、output リストのほうで + (read/write) をつける。r はレジスタの意味らしい。
  • operand の指定は名前で行っている。角括弧を使うと名前で指定できるみたい
  • input list は空 (output list で read/write を指定しているので input として指定する必要はない)
  • clobber list というで、このコードによって上書きされるレジスタを指定する
    • これを指定しないと、意図せず他の変数のレジスタを破壊してしまったりする (GCC はどのレジスタが破壊されるかがわからないから、破壊されるレジスタに変数を割り当ててしまう)
    • 指定することで、GCC はそのレジスタを一旦退避させたりできる

このページが比較的わかりやすい。

  1. トップ
  2. tech
  3. ARM inline asm で値の交換を書いてみる

x86 で asm 書く機会ってほんとなくて、むしろ ARM の命令セットを学んだほうがいろいろモチベーションがでてきそう、ということで、とりあえず ARM Linux 上で簡単なのを書いてみます。

ARM

ARM は伝統的に OABI ("arm"), EABI ("armeb")という2つのインターフェイスがあるらしいです。OABI が古いほうで汎用 (Old)。EABI は組込み (Embeded) 用。ABI の違いということはシステムコールの呼びかたとか、サブルーチンの呼びかたが違いということです…… すなわちアセンブリで書くときはどっちを書いてるか明確にする必要があります。

ここでは EABI だけでやってみます。EABI の場合システムコールは、r7 にシステムコール番号を指定して、SVC 命令を発行 (オペランドは 0) すれば良いようです。

環境は GCC でやります。

その前に

アセンブリ書くとき、そもそも命令セット (instruction set) がわからないとどうにもならない。リストはARM のサイトから探す。

x86 とかも、とにかく命令セットがわからないと何もしようがない。x86 instruction set とかでググったらよさそう。x86 は命令いっぱいありすぎて pdf が4つぐらいにわかれてる。

ただ、GCC で書く場合 gas (gnu as) のフォーマットで書かないといけないので、データシートと形式がちがうのが罠い。

GCC as (gas) での書きかたは、公式のドキュメントを参照する必要がある。x86 と arm でも書きかたが違う。

exit するだけのコード

まず exit するだけのコードを書いてみます。

/*#!as -o sketch.o sketch.s && ld -o sketch -e _start sketch.o && ./sketch
 */

.global _start
_start:
        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */
$ as -o sketch.o sketch.s
$ ld -o sketch -e _start sketch.o
$ ./a.out

簡単!!!

r7 にシステムコール番号を入れ、r0 には exit の唯一の引数である終了ステータスを入れ、svc 命令でシステムコールが実行されます。当然 exit なので返ってきません。

obj-dump するとそのままのコードが出てきます

Disassembly of section .text:

00008054 <_start>:
    8054:       e3a00000        mov     r0, #0
    8058:       e3a07001        mov     r7, #1
    805c:       ef000000        svc     0x00000000

Hello, World

こっちは微妙に面倒です。普通に同じようにシステムコールを呼ぶだけですが、文字列をどこに置くか、どうやってアドレスを参照するかがいろいろあります。

.text に一緒に置く

たぶん一番シンプルな例

/* text section */

.section .text
.global _start

_start:
        mov r0, $0x01 /* set file descripter to 1 (stdout) */
        adr r1, msg   /* adr is GNU as's special instruction. This is translated to add */
        mov r2, $0x0d /* set msg length to 0x0d (13) */
        mov r7, $0x04 /* set system call number to 4 (write) */
        svc $0x00     /* supervisor call  */

        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */

msg:
        .asciz "Hello, World\n"
        .align 2

1行ごとにコメントつけてますが、この例では "Hello, World\n\0" という文字列が text セクションに置かれ、それを adr 命令で参照して r1 に書きこんでいます。adr 命令は GNU as が解釈して、指定したラベルを示すように add 命令に書きかえられます。試しにこれをアセンブルしたものを objdump すると

Disassembly of section .text:

00008054 <_start>:
    8054:       e3a00000        mov     r0, #0
    8058:       e28f1014        add     r1, pc, #20
    805c:       e3a0200d        mov     r2, #13
    8060:       e3a07004        mov     r7, #4
    8064:       ef000000        svc     0x00000000
    8068:       e3a00000        mov     r0, #0
    806c:       e3a07001        mov     r7, #1
    8070:       ef000000        svc     0x00000000

00008074 <msg>:
    8074:       6c6c6548        .word   0x6c6c6548
    8078:       57202c6f        .word   0x57202c6f
    807c:       646c726f        .word   0x646c726f
    8080:       000a            .short  0x000a
        ...

とかなってます。_start の中の adr があったところは add 命令に書きかわってます。.text はプログラム用のセクションですが、普通にデータも置けます。

.data セクションに置く

もう少しまともな例です。

/* text section */

.section .text
.global _start

_start:
        mov r0, $0x01   /* set file descripter to 1 (stdout) */
        ldr r1, msg     /* load memory address of msg to register  */
        ldr r2, msg_len /* load memory address of msg_len to register  */
        mov r7, $0x04   /* set system call number to 4 (write) */
        svc $0x00       /* supervisor call  */

        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */

msg:
        .word data_msg /* write data_msg address to here */
msg_len:
        .word data_msg_len

/* data section */
.section .data
data_msg:
        .asciz "Hello, World\n"
data_msg_len = . - data_msg
        .align 2

テキストデータ本体は data セクション (初期化される・書き換え可能・Cで書いた場合グローバル変数の領域) において、text セクションにはポインタのアドレスを置いています。ldr 命令でそのアドレスをレジスタの読みこみ、write を呼んでいます。

ついでに文字列の長さもアセンブラで計算させています。. は現在のアドレスを示していて、. から data_msg のアドレスを引くことで文字列の長さを求められます。

これを objdump -d -j .text -j .data sketch すると

Disassembly of section .text:

00008074 <_start>:
    8074:       e3a00000        mov     r0, #0
    8078:       e59f1014        ldr     r1, [pc, #20]   ; 8094 <msg>
    807c:       e59f2014        ldr     r2, [pc, #20]   ; 8098 <msg_len>
    8080:       e3a07004        mov     r7, #4
    8084:       ef000000        svc     0x00000000
    8088:       e3a00000        mov     r0, #0
    808c:       e3a07001        mov     r7, #1
    8090:       ef000000        svc     0x00000000

00008094 <msg>:
    8094:       0001009c        .word   0x0001009c

00008098 <msg_len>:
    8098:       0000000e        .word   0x0000000e

Disassembly of section .data:

0001009c <data_msg>:
   1009c:       6c6c6548        .word   0x6c6c6548
   100a0:       57202c6f        .word   0x57202c6f
   100a4:       646c726f        .word   0x646c726f
   100a8:       000a            .short  0x000a
        ...

みたいな感じになります。

ちなみに、ld に --verbose オプションをつけるとリンカスクリプトが表示されます。これはコードやデータを実際にメモリ上にどのように配置するかを定義したもので、ld はこの定義に従って、シンボルを解決 (アドレスを決めるということ) オブジェクトを配置します。

  1. トップ
  2. tech
  3. ARM アセンブリ Hello, World on Raspberry Pi

[tech] perl 5.19.9 の signatures 構文 (普通に引数を書ける構文) を試す | Mon, Feb 24. 2014 - 氾濫原 で「ただ引数の名前とかは外からとることができない。せっかく構文に組込まれたのなら、とれてもよさそうだなと思った」と書いたので、すこしだけ実装を書いてみた。

blead perl ecb4de39577c95734821743685366f6fe7f59a2d へのパッチです。

#!./perl -Ilib
use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";
use Test::More;
package signatures {
sub args ($sub) {
$signatures::subs{$sub+0};
}
sub arity ($sub) {
$signatures::arities{$sub+0};
}
}
subtest "signatures" => sub {
package _test1 {
sub foo ($xxx, $yyy) {
}
sub bar ($zzz, $aaa=1) {
}
sub baz ($zzz, @rest) {
}
}
is_deeply signatures::args(_test1->can('foo')), [qw(
$xxx
$yyy
)];
is signatures::arity(_test1->can('foo')), 2;
is_deeply signatures::args(_test1->can('bar')), [qw(
$zzz
$aaa
)];
is signatures::arity(_test1->can('bar')), 1;
is_deeply signatures::args(_test1->can('baz')), [qw(
$zzz
@rest
)];
is signatures::arity(_test1->can('baz')), -2;
};
subtest "no signature subs" => sub {
package _test2 {
sub foo {
}
}
is signatures::args(_test2->can('foo')), undef;
is signatures::arity(_test2->can('foo')), undef;
};
TODO: subtest 'lexical_subs' => sub {
local $TODO = "FAIL";
use feature 'lexical_subs';
no warnings "experimental::lexical_subs";
state sub foo ($foo) {
}
is_deeply signatures::args(\&foo), [qw(
$foo
)];
is signatures::arity(\&foo), 1;
};
done_testing;
view raw 01-signatures-args.t hosted with ❤ by GitHub
diff --git a/toke.c b/toke.c
index 88524b4..b517e37 100644
--- a/toke.c
+++ b/toke.c
@@ -12432,6 +12432,48 @@ S_parse_opt_lexvar(pTHX)
return var;
}
+void
+Perl_parse_subsignature_remember_sub_arg(pTHX_ OP* var)
+{
+ HV* signaturesubmap;
+ SV* subid;
+ SV* name;
+ HE* signatures;
+ AV* list;
+
+ signaturesubmap = get_hv("signatures::subs", 0);
+ if (!signaturesubmap) signaturesubmap = get_hv("signatures::subs", GV_ADD);
+
+ subid = newSViv((IV)(PL_compcv));
+ name = newSVsv(PAD_COMPNAME_SV(var->op_targ));
+ signatures = hv_fetch_ent(signaturesubmap, subid, 0, 0);
+ if (signatures) {
+ list = (AV*)SvRV(HeVAL(signatures));
+ } else {
+ list = newAV();
+ }
+ av_push(list, name);
+ hv_store_ent(signaturesubmap, subid, newRV_inc((SV*)list), 0);
+}
+
+void
+Perl_parse_subsignature_remember_sub_arity(pTHX_ int min_arity, int max_arity) {
+ HV* signaturearitymap;
+ SV* subid;
+ int arity;
+
+ signaturearitymap = get_hv("signatures::arities", 0);
+ if (!signaturearitymap) signaturearitymap = get_hv("signatures::arities", GV_ADD);
+
+ subid = newSViv((IV)(PL_compcv));
+ if (max_arity == -1) {
+ arity = -(min_arity + 1);
+ } else {
+ arity = min_arity;
+ }
+ hv_store_ent(signaturearitymap, subid, newSViv(arity), 0);
+}
+
OP *
Perl_parse_subsignature(pTHX)
{
@@ -12488,7 +12530,10 @@ Perl_parse_subsignature(pTHX)
prev_type = 0;
min_arity = pos + 1;
}
- if (var) expr = newASSIGNOP(OPf_STACKED, var, 0, expr);
+ if (var) {
+ expr = newASSIGNOP(OPf_STACKED, var, 0, expr);
+ Perl_parse_subsignature_remember_sub_arg(var);
+ }
if (expr)
initops = op_append_list(OP_LINESEQ, initops,
newSTATEOP(0, NULL, expr));
@@ -12539,6 +12584,7 @@ Perl_parse_subsignature(pTHX)
initops = op_append_list(OP_LINESEQ, initops,
newSTATEOP(0, NULL,
newASSIGNOP(OPf_STACKED, var, 0, slice)));
+ Perl_parse_subsignature_remember_sub_arg(var);
}
prev_type = 2;
max_arity = -1;
@@ -12590,6 +12636,7 @@ Perl_parse_subsignature(pTHX)
newSVpvs("Too many arguments for subroutine"))))),
initops);
}
+ Perl_parse_subsignature_remember_sub_arity(min_arity, max_arity);
return initops;
}

やってることは signatures::subs みたいな名前でハッシュをつくって放りこんでるだけです。perl のコード難しくてあってるかわからないのと、たぶん参照カウントがおかしかったりしてそうですが、とりあえず簡単には動く。

ただ、lexical_subs に対してはうまくとれない (とってるサブルーチンのアドレスがちがう) ので、そこが TODO かな、というのと、サブルーチンとか大量に定義されるので、結構メモリ食ったりしそうだな、ってのが怪しい感じです。

  1. トップ
  2. tech
  3. perl 5.19.9 の signatures 構文に引数リストをとれる機能を足す

Perl 5.19.9 で実装された signatures の構文をためしてみる - tokuhirom blog を見てそんなのできたのか〜と思ったので、いろいろ試してみた。なんとこれは、今までになく直感的に引数が書けてしまう革命的構文です。

use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";

use feature 'lexical_subs';
no warnings "experimental::lexical_subs";

# 引数の数チェックをしてくれる。便利
eval {
	say "check length of arguments";
	state sub hello ($foo) {
		say "hello $foo";
	}


	hello();
	#=> Too few arguments for subroutine at pl.pl line 9.
}; if ($@) { warn $@ }

# デフォルト値も普通に書けます
eval {
	say "default value";
	state sub hello ($foo="world") {
		say "hello $foo";
	}


	hello();
	#=> "hello world"

	hello("yunotti");
	#=> "hello yunotti"
}; if ($@) { warn $@ }

# デフォルト値に式も書けます。Ruby なんかでも書けますね。引数として指定されない場合だけ氷菓されます
eval {
	say "expression in default value (evaluated same as Ruby)";
	my $i = 0;
	state sub hello ($foo=$i++) {
		say "hello $foo";
	}


	hello();
	#=> hello 0

	hello();
	#=> hello 1

	hello("yunotti");
	#=> "hello yunotti"

	hello();
	#=> hello 2
}; if ($@) { warn $@ }

# @foo を指定したら可変引数にできます。%hash とかもできます。
eval {
	say "variable length arguments";
	state sub hello ($foo, @rest) {
		say "hello $foo and " . join(", ", @rest);
	}


	hello("yunotti", "miyachan", "sae", "hiro");
	#=> hello yunotti and miyachan, sae, hiro
}; if ($@) { warn $@ }

# @_ の挙動は今までどおり変数渡しです。
eval {
	say 'with @_: @_ is passed by variable (same as prev perls)';
	state sub hello ($foo, $bar) {
		$foo = 'xxx';
		$_[1] = 'xxx';
	}


	my $foo = 'foo';
	my $bar = 'bar';
	hello($foo, $bar);
	say "$foo, $bar"; #=> foo, xxx

}; if ($@) { warn $@ }

ただ引数の名前とかは外からとることができない。せっかく構文に組込まれたのなら、とれてもよさそうだなと思った。すこしコード追ってみたけど、基本、今までで同じようなコードが内部的に生成されるだけっぽい。サブルーチンリファレンスに附属する形でなんかメタデータを入れれたらいいんだけど、よくわからなかった。

あと、Smart::Args みたいなのは使えるかと思って試してみたけど、既存コードと同じ感じなので、当然普通に使える。ただ、signatures を活用した感じにはできなそう。うまいやりかたあるのかな。

use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";

use feature 'lexical_subs';
no warnings "experimental::lexical_subs";


{
	# Smart::Args は普通に使える
	use Smart::Args;
	{
		# % を書けば呼ばれる時点でハッシュかどうかのチェックは入る
		state sub hello (%) {
			args(my $foo);
			say "hello $foo";
		}

		hello(foo => "foobar");
		eval {
			hello(1);
		}; if ($@) { warn $@ }; #=> Odd name/value argument for subroutine at pl.pl line 20.
	};

	{
		# % を書かなければ今までどおり Smart::Args 側でエラる
		state sub hello {
			args(my $foo);
			say "hello $foo";
		}
		eval {
			hello(1);
		}; if ($@) { warn $@ }; #=> Odd number of elements in anonymous hash at lib/site_perl/5.19.9/Smart/Args.pm line 39.
	}
};

{
	# せっかくなら signatures を活用したい感あるけどいい感じにならない (引数名を2度書かないといけない)

	use PadWalker qw/var_name/;
	sub validate ($var, $rule) {
		my $name = var_name(1, \$_[0]) =~ s{^\$}{}r;
		require Smart::Args;
		$_[0] = Smart::Args::_validate_by_rule($_[0], 1, $name, $_[1]);
	}

	{
		state sub hello ($foo) {
			validate($foo, { isa => 'Int' });
			say "hello $foo";
		}
		eval {
			hello("xxx");
		}; if ($@) { warn $@ }; #=> Validation failed for 'Int' with value xxx
	}
};
  1. トップ
  2. tech
  3. perl 5.19.9 の signatures 構文 (普通に引数を書ける構文) を試す