Haxe + HashLink (HL/C) でコールバック処理はどうコンパイルされるのか

# 前置き

## 概要

タイトルの件で、書き方や条件次第でパフォーマンスが変わったりしないかというのを確認したかった。

**単なる自分向けの備忘録というか記録の羅列です。**
コンパイル時の規則もC言語の読み方もよく分かっていません。
たぶん遠回りしてるんだろうなという感じがひしひしといたします。

## 環境

Haxe 4.0.5
HashLink 1.10

## GitHub

https://github.com/fal-works/hashlink-test/tree/master/callback

# プリミティブ値を対象とする処理

## （準備）コールバック関数を受け取る関数

### 単体実行

任意のコールバック関数を受け取って実行する関数を用意

```haxe:IntCaller.hx（抜粋）
class IntCaller {
	public static function call(value:Int, callback:Int->Int)
		return callback(value);
}
```

結果

```c:IntCaller.c（抜粋）
int IntCaller_call(int r0,vclosure* r1) {
	int r2;
	if( r1 == NULL ) hl_null_access();
	r2 = r1->hasValue ? ((int (*)(vdynamic*,int))r1->fun)((vdynamic*)r1->value,r0) : ((int (*)(int))r1->fun)(r0);
	return r2;
}
```

- 関数はぜんぶクロージャ扱いになるらしい
- nullチェックと、紐づくデータ（たぶんクロージャの環境とかインスタンスとか）の有無のチェックをしている
- staticメソッドのときに `hasValue` が 0 とのこと  
（参考： [C API Documentation - HaxeFoundation/hashlink Wiki](https://github.com/HaxeFoundation/hashlink/wiki/C-API-Documentation) ）
- 静的に解決するのは難しい or メリットが少ないということかしら


### ループ実行

コールバック関数を`for`文の中で実行する関数を用意

```haxe:IntCaller.hx（抜粋）
class IntCaller {
	public static function repeatCall(initialValue:Int, callback:Int->Int, repetition:Int) {
		var result = initialValue;
		for (_ in 0...repetition)
			result = callback(result);

		return result;
	}
}
```

結果

```c:IntCaller.c（抜粋）
int IntCaller_repeatCall(int r0,vclosure* r1,int r2) {
	int r3, r4, r5, r6;
	r3 = r0;
	r4 = 0;
	r5 = r2;
	label$84d70b4_2_3:
	if( r4 >= r5 ) goto label$84d70b4_2_10;
	++r4;
	if( r1 == NULL ) hl_null_access();
	r6 = r1->hasValue ? ((int (*)(vdynamic*,int))r1->fun)((vdynamic*)r1->value,r3) : ((int (*)(int))r1->fun)(r3);
	r3 = r6;
	goto label$84d70b4_2_3;
	label$84d70b4_2_10:
	return r3;
}
```

無駄なチェックに見えるけど後でCコンパイラに最適化される？


### 配列ループ実行

forEach的なやつを用意

```haxe:IntCaller.hx（抜粋）
class IntCaller {
	public static function forEach(array:Array<Int>, callback:Int->Void)
		for (i in 0...array.length)
			callback(array[i]);
}
```

<details>
<summary>結果</summary><div>

```c:IntCaller.hx（抜粋）
void IntCaller_forEach(hl__types__ArrayBytes_Int r0,vclosure* r1) {
	vbyte *r8;
	int r2, r4, r5, r6, r7;
	r2 = 0;
	if( r0 == NULL ) hl_null_access();
	r4 = r0->length;
	label$84d70b4_4_3:
	if( r2 >= r4 ) goto label$84d70b4_4_19;
	r5 = r2;
	++r2;
	if( r1 == NULL ) hl_null_access();
	if( r0 == NULL ) hl_null_access();
	r7 = r0->length;
	if( ((unsigned)r5) < ((unsigned)r7) ) goto label$84d70b4_4_13;
	r6 = 0;
	goto label$84d70b4_4_17;
	label$84d70b4_4_13:
	r8 = r0->bytes;
	r7 = 2;
	r7 = r5 << r7;
	r6 = *(int*)(r8 + r7);
	label$84d70b4_4_17:
	r1->hasValue ? ((void (*)(vdynamic*,int))r1->fun)((vdynamic*)r1->value,r6) : ((void (*)(int))r1->fun)(r6);
	goto label$84d70b4_4_3;
	label$84d70b4_4_19:
	return;
}
```
</div></details>

特記事項なし


### 単体実行（inline）

`call()` と同じやつの`inline`版も一応作っておく

```haxe:IntCaller.hx（抜粋）
class IntCaller {
	public static inline function callInline(value:Int, callback:Int->Int)
		return callback(value);
}
```


## （準備）コールバック関数

宣言方法を変えながらいくつか用意

```haxe:Callbacks.hx
class IntCallbacks {
	public static function triple(n:Int)
		return 3 * n;

	public static inline function tripleInline(n:Int)
		return 3 * n;

	public static dynamic function tripleDynamic(n:Int)
		return 3 * n;

	public static final tripleVar = triple; // (n:Int) -> 3 * n とかでもよい
}
```

結果（どれも同じ）

```c:Callbacks.c
int IntCallbacks_triple(int r0) {
	int r1;
	r1 = 3;
	r1 = r1 * r0;
	return r1;
}
```


## 単体実行

### 普通の関数を渡す

```haxe:Case00.hx
class Case00 {
	public static function main() {
		IntCaller.call(100, IntCallbacks.triple);
	}
}
```

結果

```c:Case00.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/Case00.h>
int IntCallbacks_triple(int);
extern hl_type t$fun_75691bc;
int IntCaller_call(int,vclosure*);

void Case00_main() {
	vclosure *r1;
	int r0;
	static vclosure cl$0 = { &t$fun_75691bc, IntCallbacks_triple, 0 };
	r0 = 100;
	r1 = &cl$0;
	r0 = IntCaller_call(r0,r1);
	return;
}
```

C言語よく分かってないんですが、関数をクロージャ型にするためにアロケートしてたりする？？


### inline関数を渡す

```haxe:Case01.hx
class Case01 {
	public static function main() {
		IntCaller.call(100, IntCallbacks.tripleInline);
	}
}
```

inlineで宣言しても、こういう使い方をした場合は展開されない。よって結果は Case00 と同じ


### dynamic関数を渡す
```haxe:Case02.hx
class Case02 {
	public static function main() {
		IntCaller.call(100, IntCallbacks.tripleDynamic);
	}
}
```

```c:Case02.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/Case02.h>
#include <_std/IntCallbacks.h>
extern $IntCallbacks g$_IntCallbacks;
int IntCaller_call(int,vclosure*);

void Case02_main() {
	$IntCallbacks r2;
	vclosure *r1;
	int r0;
	r0 = 100;
	r2 = ($IntCallbacks)g$_IntCallbacks;
	r1 = r2->tripleDynamic;
	r0 = IntCaller_call(r0,r1);
	return;
}
```

Case00（dynamicじゃない普通の関数）と違い、単純に引っ張ってきている（たぶん）


### 変数に入れた関数を渡す

```haxe:Case03.hx
class Case03 {
	public static function main() {
		IntCaller.call(100, IntCallbacks.tripleVar);
	}
}
```

結果： Case02（dynamic関数）と同じ


## ループ実行

とりあえずdynamic関数を渡したけど、どのパターンでも特記事項はなかったと思う（たぶん）

```haxe:Case10.hx
class Case10 {
	public static function main() {
		IntCaller.repeatCall(100, IntCallbacks.tripleDynamic, 2);
	}
}
```

<details>
<summary>結果</summary><div>

```c:Case10.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/Case10.h>
#include <_std/IntCallbacks.h>
extern $IntCallbacks g$_IntCallbacks;
int IntCaller_repeatCall(int,vclosure*,int);

void Case10_main() {
	$IntCallbacks r2;
	vclosure *r1;
	int r0, r3;
	r0 = 100;
	r2 = ($IntCallbacks)g$_IntCallbacks;
	r1 = r2->tripleDynamic;
	r3 = 2;
	r0 = IntCaller_repeatCall(r0,r1,r3);
	return;
}
```

</div></details>


## 配列ループ実行

### 普通の関数を渡す

```haxe:Case20.hx
class Case20 {
	static final array = [100, 200];

	public static function main() {
		IntCaller.forEach(array, IntCallbacks.triple);
	}
}
```

結果

```c:Case20.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/Case20.h>
int IntCallbacks_triple(int);
extern hl_type t$fun_75691bc;
extern $Case20 g$_Case20;
void IntCaller_forEach(hl__types__ArrayBytes_Int,vclosure*);

void Case20_main() {
	$Case20 r2;
	hl__types__ArrayBytes_Int r1;
	vclosure *r3;
	static vclosure cl$0 = { &t$fun_75691bc, IntCallbacks_triple, 0 };
	r2 = ($Case20)g$_Case20;
	r1 = r2->array;
	r3 = &cl$0;
	IntCaller_forEach(r1,r3);
	return;
}
```

<details>
<summary>訂正前</summary><div>

```c:Case20.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/Case20.h>
int IntCallbacks_triple(int);
extern hl_type t$fun_75691bc;
extern $Case20 g$_Case20;
void wrapt$fun_baab521(vclosure*,int);
extern hl_type t$fun_baab521;
void IntCaller_forEach(hl__types__ArrayBytes_Int,vclosure*);

void Case20_main() {
	$Case20 r2;
	hl__types__ArrayBytes_Int r1;
	vclosure *r3, *r4;
	static vclosure cl$0 = { &t$fun_75691bc, IntCallbacks_triple, 0 };
	r2 = ($Case20)g$_Case20;
	r1 = r2->array;
	r3 = &cl$0;
	if( r3 ) goto label$f08e34b_1_6;
	r4 = NULL;
	goto label$f08e34b_1_7;
	label$f08e34b_1_6:
	r4 = hl_alloc_closure_ptr(&t$fun_baab521,wrapt$fun_baab521,r3);
	label$f08e34b_1_7:
	IntCaller_forEach(r1,r4);
	return;
}
```

- 渡そうとしている関数（というかvclosure）について、渡す前の段階でnullチェックしている
- `hl_alloc_closure_ptr()` というのがある。alloc とあるだけで身構えてしまう。
- `call()` のときはこうならなくて `forEach()` でこうなる理由はよくわからない

→原因判明。

`forEach()` の定義を

```haxe
public static function forEach(array:Array<Int>, callback:Int->Void)
```

としてしまっていて（`callback` の型が `Int->Int` ではなく `Int-Void` になっていた）、
実際に渡す関数と一致していなかった（それでも怒られないのか……？）

（訂正前　ここまで）
</div></detail>

### dynamic関数を渡す

```haxe:Case21.hx
class Case21 {
	static final array = [100, 200];

	public static function main() {
		IntCaller.forEach(array, IntCallbacks.tripleDynamic);
	}
}
```

結果

```c:Case21.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/Case21.h>
#include <_std/IntCallbacks.h>
extern $Case21 g$_Case21;
extern $IntCallbacks g$_IntCallbacks;
void IntCaller_forEach(hl__types__ArrayBytes_Int,vclosure*);

void Case21_main() {
	hl__types__ArrayBytes_Int r1;
	$IntCallbacks r4;
	vclosure *r3;
	$Case21 r2;
	r2 = ($Case21)g$_Case21;
	r1 = r2->array;
	r4 = ($IntCallbacks)g$_IntCallbacks;
	r3 = r4->tripleDynamic;
	IntCaller_forEach(r1,r3);
	return;
}
```

- dynamicじゃないときとの違いは `call()` のときと同様で、  
`static vclosure cl$0 = ...` のくだりがなくなっている
- 変数に入れた関数の場合でも、dynamic関数のときと結果に変わりはなかった


## call() をinline展開

### 普通の関数を渡す

```haxe:Case30.hx
class Case30 {
	public static function main() {
		IntCaller.callInline(100, IntCallbacks.triple);
	}
}
```

結果。展開するとあっさりである

```c:Case30.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/Case30.h>
int IntCallbacks_triple(int);

void Case30_main() {
	int r0;
	r0 = 100;
	r0 = IntCallbacks_triple(r0);
	return;
}
```

### dynamic関数を渡す

```haxe:Case31.hx
class Case31 {
	public static function main() {
		IntCaller.callInline(100, IntCallbacks.tripleDynamic);
	}
}
```

結果

```c:Case31.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/Case31.h>
#include <_std/IntCallbacks.h>
extern $IntCallbacks g$_IntCallbacks;

void Case31_main() {
	$IntCallbacks r2;
	vclosure *r1;
	int r0;
	r2 = ($IntCallbacks)g$_IntCallbacks;
	r1 = r2->tripleDynamic;
	if( r1 == NULL ) hl_null_access();
	r0 = 100;
	r0 = r1->hasValue ? ((int (*)(vdynamic*,int))r1->fun)((vdynamic*)r1->value,r0) : ((int (*)(int))r1->fun)(r0);
	return;
}
```

今度は、dynamicのほうが結果が長くなった。
Case30よりもコールバック関数へのアクセスが間接的なためか、チェック処理が入ってしまっている。

変数に入れた関数の場合もdynamic関数と同様。


# オブジェクトを対象とする処理

## （準備）処理対象オブジェクトのクラス　含：各種関数

面倒だったので、コールバック関数もそれを実行する関数も一つのクラスで

```haxe:IntWrapper.hx
class IntWrapper {
	public static function triple(object:IntWrapper)
		object.value *= 3;

	public static dynamic function tripleDynamic(object:IntWrapper)
		object.value *= 3;

	public static final tripleVar = triple;

	public static function call(value:IntWrapper, callback:IntWrapper->Void)
		callback(value);

	public static function forEach(array:Array<IntWrapper>, callback:IntWrapper->Void)
		for (i in 0...array.length)
			callback(array[i]);

	public var value:Int;

	public function new(value:Int)
		this.value = value;
}
```

結果

```c:IntWrapper.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/IntWrapper.h>

void IntWrapper_triple(IntWrapper r0) {
	int r1, r2;
	if( r0 == NULL ) hl_null_access();
	r1 = r0->value;
	r2 = 3;
	r1 = r1 * r2;
	r0->value = r1;
	return;
}

void IntWrapper_tripleDynamic(IntWrapper r0) {
	// IntWrapper_triple と同一
}

void IntWrapper_call(IntWrapper r0,vclosure* r1) {
	if( r1 == NULL ) hl_null_access();
	r1->hasValue ? ((void (*)(vdynamic*,IntWrapper))r1->fun)((vdynamic*)r1->value,r0) : ((void (*)(IntWrapper))r1->fun)(r0);
	return;
}

void IntWrapper_forEach(hl__types__ArrayObj r0,vclosure* r1) {
	IntWrapper r7;
	vdynamic *r8;
	varray *r9;
	int r2, r4, r5, r6;
	r2 = 0;
	if( r0 == NULL ) hl_null_access();
	r4 = r0->length;
	label$272c0c7_4_3:
	if( r2 >= r4 ) goto label$272c0c7_4_18;
	r5 = r2;
	++r2;
	if( r1 == NULL ) hl_null_access();
	if( r0 == NULL ) hl_null_access();
	r6 = r0->length;
	if( ((unsigned)r5) < ((unsigned)r6) ) goto label$272c0c7_4_13;
	r7 = NULL;
	goto label$272c0c7_4_16;
	label$272c0c7_4_13:
	r9 = r0->array;
	r8 = ((vdynamic**)(r9 + 1))[r5];
	r7 = (IntWrapper)r8;
	label$272c0c7_4_16:
	r1->hasValue ? ((void (*)(vdynamic*,IntWrapper))r1->fun)((vdynamic*)r1->value,r7) : ((void (*)(IntWrapper))r1->fun)(r7);
	goto label$272c0c7_4_3;
	label$272c0c7_4_18:
	return;
}

void IntWrapper_new(IntWrapper r0,int r1) {
	r0->value = r1;
	return;
}
```


## 単体実行

### 普通の関数を渡す

```haxe:Case40.hx
class Case40 {
	static final object = new IntWrapper(100);

	public static function main() {
		IntWrapper.call(object, IntWrapper.triple);
	}
}
```


<details>
<summary>結果</summary><div>

```c:Case40.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/Case40.h>
void IntWrapper_triple(IntWrapper);
extern hl_type t$fun_5a742cc;
extern $Case40 g$_Case40;
void IntWrapper_call(IntWrapper,vclosure*);

void Case40_main() {
	$Case40 r2;
	IntWrapper r1;
	vclosure *r3;
	static vclosure cl$0 = { &t$fun_5a742cc, IntWrapper_triple, 0 };
	r2 = ($Case40)g$_Case40;
	r1 = r2->object;
	r3 = &cl$0;
	IntWrapper_call(r1,r3);
	return;
}
```

</div></detail>

特記事項なし。プリミティブ値のときと似てる


### dynamic関数を渡す

```haxe:Case41.hx
class Case41 {
	static final object = new IntWrapper(100);

	public static function main() {
		IntWrapper.call(object, IntWrapper.tripleDynamic);
	}
}
```

<details>
<summary>結果</summary><div>

```c:Case41.c
#define HLC_BOOT
#include <hlc.h>
#include <_std/Case41.h>
#include <_std/IntWrapper.h>
extern $Case41 g$_Case41;
extern $IntWrapper g$_IntWrapper;
void IntWrapper_call(IntWrapper,vclosure*);

void Case41_main() {
	$Case41 r2;
	IntWrapper r1;
	vclosure *r3;
	$IntWrapper r4;
	r2 = ($Case41)g$_Case41;
	r1 = r2->object;
	r4 = ($IntWrapper)g$_IntWrapper;
	r3 = r4->tripleDynamic;
	IntWrapper_call(r1,r3);
	return;
}
```

</div></detail>

これも特記事項なし


### 変数に入れた関数を渡す

- dynamic関数のときと同じだった
- なんか違う！ なんでや！ と思ったのですが、間違えて  
`tripleVar = (object:IntWrapper) -> triple;`  
としていただけだった（なぜかコンパイラに怒られない）


## 配列ループ実行

単体実行のときとまったく同じ

# まとめ？

渡す関数が以下のどちらで宣言されているかで挙動が違う。

- 普通の関数
- dynamic関数 または 変数

dynamic関数と変数とで、なんか違う結果になるケースがあったはずなんだけど再現できない。思い違いか。

~~プリミティブ値の配列ループ実行の結果だけ特殊なのがよくわからない（なにかミスってそう）~~
ミスってた。
関数の戻り値の型について、宣言時と使用時で一致しないと無駄な処理が増える上に
コンパイラが怒ったりもしないので気付けない可能性がある（思い違いでは？ 要確認）

inline展開のされ方についても要考慮。
展開されないのならdynamic関数または変数が安全牌に見える（ほんとか？）

全体的に、まだよくわからない