정말 대단한 발견임을 느꼈고, 어셈블리 코드를 보는 순간 디버깅 경로를 따라가게 됨, 사실 이 방식은 꼭 어셈블리에서만 가능한 게 아님, IR 단계에서도 가능할 수 있지만 여러 이유로 그렇지 않음, ARM 어셈블리를 읽을 수 있다는 점에서 큰 장점임, 명령 줄을 줄이기 위해 스택 크기를 push나 pop해보는 방식도 고려해봤는데 GC가 정확히 뭘 확인하는지 몰라서 확신하진 못함, 다른 의견을 듣고 싶음
일반적으로는 “LDR Rd, =expr”라는 ARM의 의사명령어를 사용함, 바로 만들 수 없는 상수의 경우 PC-relative 위치에 상수를 두고 PC를 기준으로 레지스터로 로드함, 이를 통해 “SP에 상수 더하기” 과정을 2개의 실행 명령으로 바꿀 수 있고 8바이트 코드와 4바이트 데이터 영역(17비트 상수용)으로 총 12바이트가 필요함, 관련 문서: LDR pseudo-instruction 설명
즉시값을 RSP에 더하는 특이 케이스로 어셈블러에서 이 버그가 특수 처리되지 않은 게 의외임, 패치가 컴파일러 쪽에만 적용됐다면 aarch64 어셈블리 다른 곳에도 같은 문제가 남아있을 수 있음
ARM 어셈블리 문법에 달러 기호가 들어간 이상한 표현은 AArch64 표준 어셈블리가 아니고, 글에서 “스택은 한 번만 이동해야 한다”는 규칙도 같이 언급했으면 좋았을 것 같음
Java나 .NET 같은 런타임에서는 safepoint를 명확하게 두어 명령어 집합 중간에 컨텍스트가 바뀌지 않도록 방지함
컴파일러가 상수를 두 번에 나눠서 레지스터에 넣은 뒤 add 한 번으로 원자적으로 SP를 조정하는 게 맞는 해결책인 것 같음, 물론 명령어 하나가 늘어나지만 원자성이 확보됨, 아니면 임시 레지스터로 연산 후 다시 옮기는 방법도 있음
이슈에서 살펴봤을 때, Go 팀이 자연어 봇을 쓰는 건지, 아니면 댓글에서 단순히 “backport”라는 키워드만 체크하는 건지 궁금증이 생김, 관련 코멘트: github issue comment
기술적으로 아주 훌륭한 블로그로, 설명이 워낙 명확해 이해하기 쉬워 오히려 더 똑똑해진 느낌임, x86 어셈블리 이후 오랜만에 어셈블리를 접했음에도 따라가기 쉬웠음, 그리고 이런 팀이라면 언제든 이런 이슈를 해결할 능력과 품질 관리가 있다는 신뢰까지 생김, 서버 확장을 위해 Ampere Altra도 고려했었는데 공간이 넉넉해 결국 Epyc을 사용함
Go에서 모든 명령을 싱글스텝하며 명령마다 GC 인터럽트를 발생하도록 하는 모드가 있다면, 이런 버그를 더 쉽게 찾을 수 있을 거라 생각함
ARM64 서버를 어디에 사용하고 있는지 궁금함, 작년에는 AMD EPYC 기반 Gen 12 서버를 출시한다고 했지만 ARM64 언급은 없었는데, 현재는 ARM64가 프로덕션에 쓰이고 있음
나는 Cloudflare 직원은 아니지만 블로그를 많이 읽어서 아는 바, 보안 부팅 등을 고려했을 때 이미 몇 년 전부터 Ampere를 AMD와 병행 배치 중이었음, 운영 목적은 엣지 효율성 때문으로 보이나 다른 용도도 있을 수 있음, 더 많은 정보는 엣지 서버 설계 글과 Ampere Altra vs AWS Graviton2 그리고 퀄컴 ARM 평가 등에서 확인 가능함
Cloudflare가 일부 non-edge 컴퓨팅을 퍼블릭 클라우드에서 호스팅한다는 얘기가 기억남, 예를 들어 컨트롤 플레인 등, 그럴 수도 있음
Cloudflare가 요즘 100% Rust와 x86(EPYC)만 쓴다고 생각했었음, Go와 ARM 사용 중이라는 점이 흥미로움
매번 클라우드플레어 블로그 글은 인프라나 ML 마법 없이 엔지니어링의 본질을 담아내는 멋진 콘텐츠라 생각함, 언젠가는 이곳에 지원해보고 싶음, 컴파일러 버그는 생각보다 흔한데(과거엔 gcc에서 매년 몇 개씩 찾음), 글에서처럼 대규모에서야 드러나는 희귀한 경우가 많음, 대부분은 그 정도 규모까지 들어가 보는 일이 없음
오늘 지원하지 않는 이유가 궁금함
스택 포인터는 항상 원자적으로 조정해야 함을 강조함
프리엠션을 작성한 이들이 x86 기준(여기선 명령어가 상수를 담을 수 있어 원자적으로 이뤄짐)에서 코드를 만들었고, ARM 포팅 과정에서 고수준에서 자동 분할이 되어 이런 버그가 생긴 듯함, 누구 잘못은 아니지만 좋지 않은 결과임
이 생각이 바로 떠올랐음
기계 쓰레드가 어떻게 두 개의 명령 중간에 멈추게 됐는지 잘 이해가지 않음, 베어메탈에서 이런 일이 가능한지 의문임
go는 GC 노티를 위해 인터럽트를 사용함
신호(signals)
“아주 재미있던 문제였다”는 글에 대해, 이런 근본적 문제를 해결한 건 분명 시원했을 테지만, 미해결 상황에서는 전혀 즐겁지 않았을 거라 생각함, 이런 버그는 모든 신경을 잡아먹는 경험임, 표준 라이브러리나 컴파일러가 문제일 거라 아무도 생각하지 않게 되어 개발자가 본인 코드만 계속 의심하는 문화가 있음, 나도 한 번 표준 라이브러리 버그를 찾은 적 있는데, SDK 쪽이 문제라는 건 가장 마지막에 의심하게 됨, 덕분에 엉뚱한 곳에서 시간 다 쓰게 되고, 게다가 이번 문제처럼 레이스 컨디션이면 재현도 어려워서 항상 사라진 줄 알았다가 다시 튀어나온다는 것임
이 코멘트가 본인의 비슷한 경험담을 덧붙이면서도, 굳이 저자가 느끼는 즐거움에 대해 반박을 내세워 오히려 감동이 반감된 느낌임, 사람마다 다른 걸 재미로 느낄 수 있음
어떤 사람들은 남들이 괴로워할 만한 아주 특이한 디버깅을 오히려 기쁘게 여김, 누군가에겐 좌절이 남에겐 즐거움이 됨
아마 저자가 말하고 싶었던 건 “재미(funny)”가 아니라 “만족(satisfying)”이었다고 생각함, 나도 마감에 쫓기며 Ubuntu GCC ARM 툴체인의 sscanf 버그를 잡은 적 있는데, 그때는 즐겁진 않았지만, 문제를 정확하게 잡고 회귀 테스트까지 쓴 뒤에는 정말 만족스러웠음
깊은 결함을 해결하는 게 풀렸을 땐 엄청난 해방감임, 나도 컴파일러나 CPU 쪽 버그를 해결할 때 가장 큰 즐거움을 느꼈던 적이 많음
관리형 언어에서 Unsafe 류를 전혀 쓰지 않고도 세그폴트가 나면 내 코드 문제가 아닐 가능성이 높다는 신호로 삼는 편임
Hacker News 의견