RustのPlottersがSSE4を使えない環境で Illegal instruction となる問題を回避する

## 概要

Plotters は環境によっては次のように Illegal instruction でクラッシュする。今回は `sse41` のCPU命令が使えない環境であることが原因だった。

```console
$ cargo run
...
   Compiling pathfinder_simd v0.5.1
   Compiling pathfinder_geometry v0.5.1
   Compiling font-kit v0.10.1
   Compiling plotters v0.3.1
   Compiling quick-start v0.1.0 (.../quick-start)
    Finished dev [unoptimized + debuginfo] target(s) in 26.58s
     Running `target/debug/quick-start`
Illegal instruction (core dumped)
```

### 環境

* CPU ... AMD Phenom(tm) II X4 910e Processor
* WSL (Ubuntu 18.04 LTS)
  * rustc 1.57.0
  * plotters v0.3.1
  * pathfinder_geometry v0.5.1
  * pathfinder_simd v0.5.1


## 回避方法

Cargo.toml の `pathfinder_simd` のクレートに対して `pf-no-simd` を指定する。

```diff_toml
@@ -1,7 +1,8 @@
 [package]
 name = "quick-start"
 version = "0.1.0"
 edition = "2021"

 [dependencies]
+pathfinder_simd = { version = "0.5", features = ["pf-no-simd"] }
 plotters = "0.3"
```

### 解説

* `plotters` は `pathfinder_geometry` を依存関係に持つ
* `pathfinder_geometry` は `pathfinder_simd` の v0.5を依存関係にもつ
* `pathfinder_simd` は何も指定しない場合は `sse41` のCPU命令を含む形でビルドする  
  `pathfinder_simd` のソースコードを見るに `pf-no-simd` の feature でこの動作を変えられる。  
  ただ、[Crate pathfinder_geometry](https://docs.rs/pathfinder_geometry/latest/pathfinder_geometry/) や [Crate pathfinder_simd](https://docs.rs/pathfinder_simd/latest/pathfinder_simd/) のサイトにはそんな解説はないので、隠し機能的な扱いと思われる。

GitHub の [pathfinder/simd/Cargo.toml](https://github.com/servo/pathfinder/blob/master/simd/Cargo.toml) のCargo.toml をみると `pf-no-simd` という features があることが分かる。

```toml
[package]
name = "pathfinder_simd"
...

[features]
pf-no-simd = []

...
```

また、 GitHub の [pathfinder/geometry/Cargo.toml](https://github.com/servo/pathfinder/blob/master/geometry/Cargo.toml) からは、バージョン 0.5 を依存関係にもつので同じバージョンで `pf-no-simd` を指定してビルドすればよいと推測できる。

```toml
[package]
name = "pathfinder_geometry"
...

[dependencies.pathfinder_simd]
path = "../simd"
version = "0.5"
```

## 参考: 調べ方

### 参考1: `Illegal instruction` がどのクレートで発生したのか確認する方法

`cargo build` や `cargo run` はデバッグモードでコンパイルされているので、 `gdb` を使えば調べることができる。

`gdb` に深い意味はない。Rust流のもっと適した方法があるはずだが、これを調べた時点では知らないので今ある知識を活用する意図で `gdb` を使った。

`bt` でバックトレースが確認できるが `#0` で `sse41` に関する命令が原因と推測できるし、 `#1` でそれが `pathfinder_simd` によって呼び出されたことが分かる。

```console
$ gdb target/debug/quick-start
...
(gdb) run
...
(gdb) bt
#0  0x00000000080dc4f5 in core::core_arch::x86::sse41::_mm_max_epi32 (a=..., b=...)
    at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/../../stdarch/crates/core_arch/src/x86/sse41.rs:287
#1  0x00000000080db055 in pathfinder_simd::x86::I32x4::max (self=..., other=...)
...
```


### 参考2: CPUのサポートする命令の確認方法

`cat /proc/cpuinfo` や `lshw` を使って `sse41` の有無を確認できる。

* `/proc/cpuinfo` を使う方法

   ```console
   $ grep flags /proc/cpuinfo
   flags           : fpu vme de pse tsc msr ... sse sse2 ... sse4a ...
   ```

* `lshw` を使う方法

   ```console
   $ sudo lshw -class processor
   [sudo] password for ********:
     *-cpu
          ...
          capabilities: fpu fpu_exception wp vme de pse tsc msr 
   ... sse sse2 ... sse4a ...
   ```


### 参考3: 事例で調べる
CPU名が分れば、カタログスペックやらで確認出来たりもする。
以下、「amd phenom sse」あたりで検索して出てきたサイトで SIMD 命令の対応状況が表にまとめられている。

* [AMD デスクトップCPU スペック・性能比較](https://www.pc-koubou.jp/magazine/5813)

## 参考: 検証に使ったソース

[Crate plotters > Quick start](https://docs.rs/plotters/latest/plotters/#quick-start)にあるそのままのコードです。 `plotters-doc-data/` のディレクトリは事前に作っておく必要がありそう。

```rust
use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("plotters-doc-data/0.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;
    let mut chart = ChartBuilder::on(&root)
        .caption("y=x^2", ("sans-serif", 50).into_font())
        .margin(5)
        .x_label_area_size(30)
        .y_label_area_size(30)
        .build_cartesian_2d(-1f32..1f32, -0.1f32..1f32)?;

    chart.configure_mesh().draw()?;

    chart
        .draw_series(LineSeries::new(
            (-50..=50).map(|x| x as f32 / 50.0).map(|x| (x, x * x)),
            &RED,
        ))?
        .label("y = x^2")
        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED));

    chart
        .configure_series_labels()
        .background_style(&WHITE.mix(0.8))
        .border_style(&BLACK)
        .draw()?;

    Ok(())
}
```

**実行結果**


![0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326797/b528951e-5e13-0dd4-5ab6-4d926f6b8afd.png)