# Wasm 관련 삽질기



## 이슈

- [Plugin crashes with rkyv LayoutError](https://github.com/swc-project/swc/issues/6807)

특정한 경우에 `rkyv`에서 `LayoutError`로 패닉이 난다는 이슈 제보가 있었다.
꽤 오래된 이슈인데, 알고보니까 러스트 컴파일러 버전에 따라 다르게 작동하는 것이었다.
이것저것 코드를 보다가 `rkyv@v0.7.37`에 UB가 있어서 깨지는 것이라고 결론을 내렸다.
방법은 `rkyv`를 업데이트하거나 다른 걸로 바꾸는 것뿐이었는데, 어떻게 처리할지 오래 고민했다.
`rkyv@0.7.39`엔 데이터가 날아가버리는 버그가 있어서 업데이트하기도 곤란했다.

한 플러그인 저자분이 스테이블인 1.67으로 컴파일해도 깨진다고 하셔서 `rkyv`의 UB인 게 확실해졌다.
다른 분이 [테스트한 결과](https://github.com/swc-project/swc/issues/6807#issuecomment-1463706888), `nightly-2022-11-23`까지 작동하고 `nightly-2022-11-24`부터 깨진다고 한다.

그런데 내가 이 이슈를 팀하고 공유하는 걸 까먹은지라 팀원이 `next.js`의 rustc 버전을 올렸고, 덕분에 [next.js v13.2.4에서 swc 플러그인을 실행할 수 없다는 이슈](https://github.com/vercel/next.js/issues/46989)가 올라왔다.
발등에 불이 떨어진 셈이라 빠르게 해결해야했다.

## rustc & `rkyv@v0.7.40` 업데이트

- [PR](https://github.com/swc-project/swc/pull/7154)

이 `rkyv` 버그는 호스트나 플러그인 둘 중 하나만 깨지면 작동 안하는 버그라 `@swc/core`의 러스트 컴파일러 버전을 한동안 못 올리고 있었다.
최신 버전 `rkyv`가 새로운 rustc에선 작동하는지 검증할 겸해서 rsutc를 `nightly-2023-03-28`로, `rkyv`를 `v0.7.40`으로 올렸다.
근데 러스트는 컴파일러 버전을 업그레이드하면 린트 룰이 많이 추가되기 때문에 이 작업에 시간이 꽤 많이 들었다.
힘들긴 했는데 그래도 보니까 잘 작동하는 것 같아서 기분이 좋았다.
이 이슈가 오랫동안 골치였는데 그래도 잘 해결됐구나 싶었다.

### 배포 실패

그런데 배포를 시도해보니까 문제가 발생했다.
`swc`는 배포하기 전에 도커를 이용해 각 CPU 아키텍쳐별로 테스트를 돌리는데 리눅스에서 `jest`가 별다른 에러 메시지 없이 죽어버리는 문제가 발생했다.
`rkyv@v0.7.37`, `rkyv@v0.7.39` 때문에 전에도 삽질을 엄청 했던지라 당연히 `rkyv` 문제일거라 생각했다.
그래서 일단 Revert하고 고민하기로 했다.

### Revert

- [PR](https://github.com/swc-project/swc/pull/7162)

위에서 말한 rustc 업데이트 작업에 시간이 꽤 들었기 때문에 Revert 할 때 기분이 안 좋았다.
CI 기다리는 걸 좋아하지 않지만 이떄는 특히 짜증났다.
그래도 뭐... 선택지가 없으니 했다.

## `rkyv` => `rmp-serde`

- [PR](https://github.com/swc-project/swc/pull/7163)

당연히 `rkyv`가 문제일 거라 생각했기에 아예 드랍하려고 했다.
조금 느려지더라도 `serde` 기반으로 바꾸면 하위 호환성 문제도 어느 정도 해결될 것이라서 열심히 작업했다.
근데 `serde`는 여러가지 메시지 포맷을 지원하는 프레임워크라 여러가지 포맷을 시도해봤다.

일단 `rkyv` 의존성을 없앴는데 쉬운 작업은 아니었지만 그래도 어찌어찌 하긴 했다.
`vscode`의 프로젝트 검색 기능으로 `rkyv`를 찾은 뒤 하나하나 수작업으로 지웠다.
그러면서 `rkyv::Archive` 트레잇 바운드를 `serde::Serialize`로 바꾸는 등, `rkyv` 타입 사용을 전부 `serde` 기반으로 바꿨다.

`serde` 기반으로 플러그인 시스템을 재구성한 후, `rmp-serde`의 `to_vec`을 사용했는데 안 됐다.
에러 메시지도 그닥 도움되는 종류는 아니어서 쓸데없는 짓을 한건가 싶은 마응으로 `serde_json`을 시도해봤다.
작동헀다.
희망을 봤다고 해야하나?
`bincode`도 시도해봤다.
그런데 `bincode`는 `serde::Deserializer::deserialize_any`를 구현하지 않기 때문에 사용할 수 없었다.
그래서 고민했다.
`serde_json`은 너무 느리고, 빠른 포맷은 대부분 `deserialize_any`를 지원하지 않을 것이었다.
근데 생각해보니까 이상했다.
`msgpack` 포맷에 대해 잘 아는 건 아니지만, `rmp-serde`가 `deserialize_any`를 구현하는 건 확실했다.
그리고 위에서 말한 도움이 되지 않는 에러 메시지는 직렬화된 데이터를 전혀 다른 필드에 갖다 꽂으면 나올 수 있는 메시지였다.
포맷 내부에 필드 이름 같은 걸 인코딩하는 방법이 있을 거라는 결론을 내리고 문서를 보니까 `rmp-serde`에 [`to_vec_named`](https://docs.rs/rmp-serde/1.1.1/rmp_serde/encode/fn.to_vec_named.html)라는 메소드가 있더라.
noop 플러그인 테스트를 돌려보니까 잘 작동했다.
noop 플러그인 테스트는 파서 테스트셋 전체를 noop 플러그인에 넘겼다가 돋려받아서 데이터가 유지되는지 확인하는 테스트인데, 이게 작동한다는 건 AST의 직렬화와 비직렬화가 올바르게 동작한다는 것을 의미했다.
그래서 프로파일링을 돌려봤다.
성능은 2배 이상 느려졌지만, 머리 아픈 이슈도 해결하고 어느 정도의 하위호환성까지 얻는 거라 감수할 수 있다고 생각하고 기쁜 마음으로 PR을 마무리하고 다른 작업으로 넘어갔다.

### CI 실패

그런데 CI 테스트가 실패해서 자동 머지가 작동하지 않았다.
CI 로그 보고나서 로컬에서 디버깅을 해보니까 튜플 타입에 문제가 있더라.
대체 어느 쪽 버그인지는 모르겠는데, 관련해서 디버깅을 하다가 갑자기 든 생각이 하나 있었다.

## rustc & `rkyv@v0.7.40` 업데이트 재시도

- [PR](https://github.com/swc-project/swc/pull/7170)

그 생각이란 이미 `swc_plugin_runner`의 테스트에서 `rkyv`를 지겹도록 테스트하고 있다는 것이다.
`swc_plugin_runner` 테스트는 모든 PR에 대해서 도는 테스트기 때문에, `rkyv`가 깨진 거라면 위의 PR에서 잡혔어야한다.
그리고 배포 파이프라인 로그를 보면 테스트가 리눅스에서만 실패했다.
그래서 위의 Revert PR을 Revert 한 뒤, 깃허브 코드스페이스를 이용해 리눅스 환경에서 빌드했다.
그러고나서 테스트를 돌려보는데 `rkyv` 테스트들은 잘 작동했다.
그리고 디버깅하다가보니까 전체 테스트를 돌리니까 `yarn test`가 멈추는 경우가 있었다.
`rkyv`하고 관계 없는 테스트였는데, 위의 배포 실패 로그를 보니까 멈추는 위치가 `yarn test`가 멈추는 시점과 같았다.

```
PASS unit tests node-swc/__tests__/transform/plugin_test.js
PASS unit tests node-swc/__tests__/dynamic_tsx.mjs
PASS unit tests node-swc/__tests__/preserve_comments.mjs
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

즉, `rkyv`와 관련 없는 `swc` 코드가 깨졌다는 소리다.
rustc의 miscompilation이라는 결론을 내리고 러스트 버전을 `nightly-2023-03-20`로 낮춰봤다.
잘 작동하더라.
하...
나이틀리를 7년 넘게 써왔는데 miscompilation 버그는 처음 밟았봤다.
아무튼 작동하니까 PR 합치고 `rmp-serde` PR은 닫았다.
그리고 여러 PR을 리뷰해서 합친 뒤 새 버전의 `@swc/core`과 새 버전의 플러그인들을 배포했다.

## 검증

한참을 기다린 뒤, 배포가 끝났다.
그해서 `@swc/plugin-jest`을 이용해서 작동하는 걸 확인했다.
그런 뒤 `vercel/turbo`에 보낸 PR 업데이트하고 Undraft 처리했다.
머지되면 `next.js`도 업데이트할 예정이다.

그리고 앞으로는 최대한 안정 버전의 rustc를 쓰게 될 것 같다.

