Common Lisp で型宣言を使ってゴリゴリに最適化しているときにハマったこと

# 背景
表題の通り。
処理系は sbcl を使いました。

# 問題
こちらが問題のコードです。
(実際には大きなコードで、問題の核だけ切り出しました)

``` Lisp
(defun a (i)
  (declare (optimize (speed 3) (space 0) (safety 2))
           (type fixnum i))
  (the fixnum (+ i 1)))
```

この関数をコンパイルすると以下のメッセージが得られます。

```
; note: doing signed word to integer coercion (cost 20), for:
;       the first result of inline (signed-byte 64) arithmetic
```

なんだか、`(signed-byte 64)` 型から `integer` 型に変換するのに 20 の cost がかかるよ、と言われている気がします。

このメッセージを消したいです。

# 考察
コードを見ると、まず、`declare` によって `i` は `fixnum` 型だよ、と宣言されています。
そこで、`(+ i 1)` の型を考えます。
`(+ i 1)` は `fixnum` に収まりません(`i = most-positive-fixnum` のときですね)。
そのため、コンパイラは `(+ i 1)` の型を、より表現力の大きい `(signed-byte 64)` と推論しました。
その値を `the` オペレータによって `fixnum` に強制しようとしているため、上記のメッセージが出たと考えられます。

## disassemle すると

```
; disassembly for A
; Size: 46 bytes. Origin: #x22B0A82B
; 2B:       48FFC0           INC RAX                          ; no-arg-parsing entry point
; 2E:       48D1E0           SHL RAX, 1
; 31:       7108             JNO L0
; 33:       48D1D8           RCR RAX, 1
; 36:       E86565FFFE       CALL #x21B00DA0                  ; ALLOC-SIGNED-BIGNUM-IN-RAX
; 3B: L0:   A801             TEST AL, 1
; 3D:       7509             JNE L1
; 3F:       488BD0           MOV RDX, RAX
; 42:       488BE5           MOV RSP, RBP
; 45:       F8               CLC
; 46:       5D               POP RBP
; 47:       C3               RET
; 48: L1:   488BD0           MOV RDX, RAX
; 4B:       48D1F8           SAR RAX, 1
; 4E:       7304             JNB L2
; 50:       488B42F9         MOV RAX, [RDX-7]
; 54: L2:   CC47             BREAK 71                         ; OBJECT-NOT-FIXNUM-ERROR
; 56:       02               BYTE #X02                        ; RAX
; 57:       CC0F             BREAK 15                         ; Invalid argument count trap
```

確かにコストの大きそうな関数です。ただ1を足すだけの関数なのに。

# fixnum にそんなに大きな値は求めていない
私のアプリケーションでは、`i` が `most-positive-fixnum` (`4611686018427387903`)のような大きな値にはなることはなく、`(+ i 1)` が `fixnum` の表現できる範囲を超える心配はありません(文字列を舐めるために使っている値なので)。
私のアプリケーションでなくとも、そんなに大きな値は扱わないよ！というケースは多いと思います。

# 安全性を捨てよう
結論からいうと、`(safety 2)` を `(safety 0)` にすることで解決しました。

``` Lisp
(defun b (i)
  (declare (optimize (speed 3) (space 0) (safety 0))
           (type fixnum i))
  (the fixnum (+ i 1)))
```

これでコンパイル時のメッセージは表示されなくなりました。

また、disassemble の結果は以下の通りです。

```
; disassembly for B
; Size: 13 bytes. Origin: #x22B0B929
; 29:       4883C002         ADD RAX, 2                       ; no-arg-parsing entry point
; 2D:       488BD0           MOV RDX, RAX
; 30:       488BE5           MOV RSP, RBP
; 33:       F8               CLC
; 34:       5D               POP RBP
; 35:       C3               RET
```

だいぶ早くなってそうです。

## 局所的に安全性を捨てよう
取り上げた関数の場合はとても小さいのでこれで問題ないのですが、関数全体を `(safety 0)` にするのはまずいという場合は `locally` オペレータを使いましょう。

``` Lisp
(defun c (i)
  (declare (optimize (speed 3) (space 0) (safety 2))
           (type fixnum i))
  (locally (declare (optimize (safety 0)))
    (the fixnum (+ i 1))))
```

こんな感じですね。

# ちなみに、捨て去られた安全性は…
`(safety 2)` の場合、 `most-positive-fixnum` を与えると、

```
The value
  4611686018427387904
is not of type
  FIXNUM
   [Condition of type TYPE-ERROR]
```

実行時型エラーとなりました。

`(safety 0)` の場合、

```
CL-USER> (b most-positive-fixnum)
-4611686018427387904
```

オーバーフローしてしまいました。