macOS 11.2 で M1 native な node を動かす

M1 Mac で macOS Big Sur 11.2 に更新したら M1 native な node が壊れたので、ソースを変更して動くようにしたメモです。

追記: node v15.9.0 で修正されました。

## 現象

homebrew で最新版の node v15.8.0 を入れて React での開発に使ってましたが、macOS を更新したら create-react-app で作ったプロジェクトで `yarn start` がクラッシュするようになりました。(create-react-app を使わずに `webpack serve` してれば大丈夫なようですが…。)

<details>
<summary>エラーを見る</summary><div>

```
Starting the development server...



#
# Fatal error in , line 0
# Check failed: allocator->SetPermissions(reinterpret_cast<void*>(region.begin()), region.size(), PageAllocator::kNoAccess).
#
#
#
#FailureMessage Object: 0x16d2c3268
 1: 0x102c29104 node::NodePlatform::GetStackTracePrinter()::$_3::__invoke() [/opt/homebrew/Cellar/node/15.8.0/bin/node]
 2: 0x1035b4054 V8_Fatal(char const*, ...) [/opt/homebrew/Cellar/node/15.8.0/bin/node]
 3: 0x10319d358 v8::internal::wasm::WasmCodeAllocator::GetNumCodeSpaces() const [/opt/homebrew/Cellar/node/15.8.0/bin/node]
 4: 0x10319d1f8 v8::internal::wasm::WasmCodeAllocator::FreeCode(v8::internal::Vector<v8::internal::wasm::WasmCode* const>) [/opt/homebrew/Cellar/node/15.8.0/bin/node]
 5: 0x1031a0048 v8::internal::wasm::NativeModule::FreeCode(v8::internal::Vector<v8::internal::wasm::WasmCode* const>) [/opt/homebrew/Cellar/node/15.8.0/bin/node]
 6: 0x1031adbcc v8::internal::wasm::WasmEngine::FreeDeadCodeLocked(std::__1::unordered_map<v8::internal::wasm::NativeModule*, std::__1::vector<v8::internal::wasm::WasmCode*, std::__1::allocator<v8::internal::wasm::WasmCode*> >, std::__1::hash<v8::internal::wasm::NativeModule*>, std::__1::equal_to<v8::internal::wasm::NativeModule*>, std::__1::allocator<std::__1::pair<v8::internal::wasm::NativeModule* const, std::__1::vector<v8::internal::wasm::WasmCode*, std::__1::allocator<v8::internal::wasm::WasmCode*> > > > > const&) [/opt/homebrew/Cellar/node/15.8.0/bin/node]
 7: 0x1031abbec v8::internal::wasm::WasmEngine::PotentiallyFinishCurrentGC() [/opt/homebrew/Cellar/node/15.8.0/bin/node]
 8: 0x1031ace28 v8::internal::wasm::WasmEngine::ReportLiveCodeForGC(v8::internal::Isolate*, v8::internal::Vector<v8::internal::wasm::WasmCode*>) [/opt/homebrew/Cellar/node/15.8.0/bin/node]
 9: 0x1031ad118 v8::internal::wasm::WasmEngine::ReportLiveCodeFromStackForGC(v8::internal::Isolate*) [/opt/homebrew/Cellar/node/15.8.0/bin/node]
10: 0x102e10ea4 v8::internal::StackGuard::HandleInterrupts() [/opt/homebrew/Cellar/node/15.8.0/bin/node]
11: 0x1030a0938 v8::internal::Runtime_StackGuard(int, unsigned long*, v8::internal::Isolate*) [/opt/homebrew/Cellar/node/15.8.0/bin/node]
12: 0x10332278c Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/opt/homebrew/Cellar/node/15.8.0/bin/node]
13: 0x1032bb530 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
14: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
15: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
16: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
17: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
18: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
19: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
20: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
21: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
22: 0x1032b2ac4 Builtins_ArgumentsAdaptorTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
23: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
24: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
25: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
26: 0x1032b2ac4 Builtins_ArgumentsAdaptorTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
27: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
28: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
29: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
30: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
31: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
32: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
33: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
34: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
35: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
36: 0x1032b2ac4 Builtins_ArgumentsAdaptorTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
37: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
38: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
39: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
40: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
41: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
42: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
43: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
44: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
45: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
46: 0x1032b2ac4 Builtins_ArgumentsAdaptorTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
47: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
48: 0x1032b2ac4 Builtins_ArgumentsAdaptorTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
49: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
50: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
51: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
52: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
53: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
54: 0x1032b2ac4 Builtins_ArgumentsAdaptorTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
55: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
56: 0x1032b2ac4 Builtins_ArgumentsAdaptorTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
57: 0x1032bb494 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/15.8.0/bin/node]
58: 0x10336ea1c Builtins_PromiseFulfillReactionJob [/opt/homebrew/Cellar/node/15.8.0/bin/node]
59: 0x1032da608 Builtins_RunMicrotasks [/opt/homebrew/Cellar/node/15.8.0/bin/node]
60: 0x1032b8c28 Builtins_JSRunMicrotasksEntry [/opt/homebrew/Cellar/node/15.8.0/bin/node]
61: 0x140008000 
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```
</div></details>

検索したところ、[nodejs/node の issue #37061](https://github.com/nodejs/node/issues/37061) が見つかりました。どうやらメモリの実行許可に関する動作変更に V8 の WebAssembly JIT が引っ掛かったようです。

根本的な修正ではないものの、[node/deps/v8/src/wasm/wasm-code-manager.cc 
の以下の 2 行](https://github.com/nodejs/node/blob/v15.8.0/deps/v8/src/wasm/wasm-code-manager.cc#L1594-L1595) をコメントアウトすれば問題は回避できるとのことで、早速対応しました。

```c++
  CHECK(allocator->SetPermissions(reinterpret_cast<void*>(region.begin()),
                                  region.size(), PageAllocator::kNoAccess));
```

## 対応

手動でソースを取ってくるのが面倒なので、nvm を使いました。まずは普通に node v15.8.0 をインストール。ビルドの時間は測ってなかったけど、たぶん 10 分くらいかかりました。

```zsh
% git clone https://github.com/nvm-sh/nvm.git ~/.nvm
% source ~/.nvm/nvm.sh
% nvm install node
```

この状態で `yarn start` して、やはりクラッシュすることを確認。nvm では、ビルドしたソースがそのまま `~/.nvm/.cache/src` の下に転がってるので、それを変更してビルド。

```zsh
% cd ~/.nvm/.cache/src/node-v15.8.0/files
% vi deps/v8/src/wasm/wasm-code-manager.cc # 問題の箇所をコメントアウト
% make
```

今度は変更した部分だけのビルドなので、すぐに終了。できたものは `out/Releases/node` にあるので、これを `~/.nvm/versions/node/v15.8.0/bin` にコピーしました。

```zsh
% cd ~/.nvm/versions/node/v15.8.0/bin
% mv node node.bak # 元の node を待避
% cp -p ~/.nvm/.cache/src/node-v15.8.0/files/out/Releases/node .
```

これで無事に `yarn start` できるようになりましたとさ。めでたしめでたし。

(なお、nvm をずっと有効にするには `~/.bashrc` や `~/.zshrc `に `source ~/.nvm/nvm.sh` を追加する必要がありますが、自分は一時的な対応のつもりなので、PATH に直接 `~/.nvm/versions/node/v15.8.0/bin` を追加しました。)