Lone Lisp를 만든 Matheus Moreira 인터뷰
(alexalejandre.com)- Lone Lisp는
libc없이 Linux 시스템 호출 위에서 직접 실행되는 Lisp로, Matheus Moreira가 freestanding C만으로 온전한 언어와 사용자 공간 프로그램을 만들 수 있는지 확인하려 시작함 - 안정적인 Linux 사용자 공간 ABI를 직접 이용해
errno, 로케일, 암묵적 파일 버퍼링 같은 C 라이브러리의 전역 상태와 레거시 API를 제거하고 런타임·메모리 할당기·테스트 도구를 직접 구현함 - FEXPR와 적절한 꼬리 호출 최적화를 비롯해 생성기, 구분된 연속체, 재개 가능한 오류 처리를 지원하며 벡터·테이블·연속체도 같은 함수 호출 방식으로 다룸
- 시작은 빠르지만 임시 벤치마크에서 Python보다 10~100배 느린 리스트 수준 인터프리터이며, C 인터프리터를 부트스트랩 기준으로 남기고 장기적으로 Lone 자체에 JIT 컴파일러를 구현할 계획임
- Moreira는 Claude를 코드 검토와 프로젝트 관리에 활용하되 Lone 코드는 직접 작성·검토하며, 정적 사이트 생성기·셸·유틸리티를 만들어 전통적인 Linux 사용자 공간을 재구축하려 함
게임에서 C++·Ruby·Lisp로
- 프로그래밍에 관심을 갖게 된 계기는 Mega Man Battle Network였으며, 관련 포럼에서 교류하려 영어를 익히고 첫 언어인 C++도 배움
- 브라질의 기술 고등학교에서 13~14세 무렵 정보학 과정을 시작함
- Dev-C++와 cplusplus.com 튜토리얼을 이용함
- 6개월 만에 학교 교육과정을 모두 익힌 뒤 다른 학생을 돕는 조교로 채용됨
- 게임 제작도 시도했지만 필요한 물리 지식이 부족해 성공하지 못함
- C++ 이후 Java, Ruby, Python을 거쳐 Lisp와 Scheme을 접했으며, Scheme의 언어적 우아함에 끌림
- Sun의 Java 튜토리얼을 거의 전부 읽으며 객체지향 프로그래밍을 배움
- Java Swing과 Nimbus 디자인을 좋아해 수학 교수를 위한 원·호 그리기 앱도 제작함
- Ruby에서는 Git 형태의 명령 기반 인자 파서 Acclaim을 포함한 gem을 작성함
- 여러 언어를 탐색한 동력은 학교 프로젝트에서 출발했지만, 이후에는 호기심과 자신에게 맞는 올바른 언어를 찾으려는 욕구가 중심이 됨
Ruby의 표현력과 C의 투명성
- Ruby는 메서드 이름을 추측해도 맞는 경우가 많고 단수·복수형과 동의어를 지원해 자신의 사고방식에 잘 맞았음
items.each.with_index.map처럼 셸 파이프라인에 가까운 조합으로 적은 코드에서 많은 작업을 수행할 수 있음- 지금도 Ruby를 항상 설치해 두며 최근에는 Rails 프로젝트도 시작함
- C는 함수가 ELF 심볼과 어셈블리 코드로 직접 대응해 컴파일 결과를 이해하기 쉬웠음
- 현대 C++은 과거와 사실상 다른 언어가 됐다고 보고 더는 자신이 C++을 안다고 말하지 않음
- Rust로 옮기려 했지만 정착하지 못했고, C로 재작성하는 이유를 속도와 ABI에서 찾는 Zig와 그 창시자에게도 관심을 가짐
- 십 대 때 CRuby VM 소스를 읽으며 Stack Overflow의 “왜 Ruby가 이렇게 동작하는가”라는 질문에 구현으로 답을 찾음
- 이후 CRuby, CPython, JVM, Guile, V8 코드를 탐색했고, 특히 Linux 커널 소스를 전문 C 프로그래밍의 기준으로 평가함
다른 사람의 코드를 읽는 방식
- 라이브러리를 사용하는 데 그치지 않고 내부 구현, 드라이버와 하드웨어의 통신, 숨겨진 계층을 이해하려 꾸준히 소스를 읽음
- 실제 애플리케이션을 만들다가도 기반 기술 탐구로 빠지는 일이 많았고, 이미 해결된 문제를 더 낫게 다시 만들려는 성향도 생김
- 이런 성향이 일반적인 소프트웨어 개발 경력과 맞지 않는다고 판단해 프로그래밍을 직업으로 선택하지 않고 취미로 유지함
- 처음부터 드라이버 코드를 이해한 것은 아니며, 좌절하면서도 계속 읽어 일부 동작을 파악할 수 있게 됨
RGB 키보드 역공학
- Clevo 베어본 노트북의 RGB 키보드 제어용 Windows 앱이 창을 띄우는 데 1분 넘게 걸릴 정도로 느려 직접 대체함
- Wireshark로 앱이 키보드에 보내는 USB 패킷을 캡처하고 구조를 분석해 Linux 사용자 공간 C 드라이버를 작성함
- GitHub에 올린 뒤 잊고 있었지만 실제 사용자들이 생겼고, 한 사용자는 GUI까지 제작함
- 이어 ACPI 전원 관리 등 노트북의 다른 기능도 역공학했으나 모든 기능을 알아내지는 못함
libc를 우회한 liblinux
- Linux 시스템 호출은 일반 함수 호출과 비슷할 만큼 코드가 단순하며,
libc를 거치지 않고 Linux를 직접 대상으로 삼을 수 있음 - Linux는 사용자 공간 ABI 안정성으로 알려졌지만 사용자 공간 구성 요소는 그렇지 않다고 봄
- 매뉴얼에서 일부 시스템 호출을 glibc가 지원하지 않는다는 문구를 접함
getrandom이 glibc에 들어가기까지 약 2년이 걸렸다는 LWN 사례도 영향을 줌
- 시스템 호출만 담은
liblinux.a를 만들어-llinux로 연결하자는 발상에서 liblinux를 개발함- freestanding C용 Linux 시스템 호출 래퍼 모음이었음
- C 런타임(CRT)은 glibc, musl, uclibc 같은 표준 C 라이브러리를 뜻함
Freestanding C에서 사라지는 문제
- Linux는 오류를 일반 반환값으로 돌려주지만 C 라이브러리는 이를 전역 변수인
errno에 저장함 libc를 제거하면 여러 전역 상태와 오래된 API를 함께 피할 수 있음errno,rand/srand, 로케일 상태- 재진입 가능하지 않은
strtok - 예기치 않은 문제를 일으킬 수 있는 암묵적 파일 버퍼링
gets,atexit,signal,malloc- NaN 및
errno와 결합하면서 복잡해지는math.h
- 반면 정수 승격 규칙과 오버플로 검사 부재는 C 자체의 문제라 그대로 남음
- 오버플로에는 컴파일러 내장 기능을 사용함
- 자신이 새로운 결함을 만들 가능성도 경계함
- 표준 라이브러리 없이 바이트 복사부터 정수→문자열 변환까지 직접 작성해야 했지만 예상만큼 불가능한 작업은 아니었음
- glibc 저장소는 해석하기 어려웠으나
musl libc소스는 깨끗하고 탐색하기 쉬워 큰 도움이 됨 - C 라이브러리와 동적 메모리 할당 없이 정적 메모리만 사용하는 자동 테스트 스위트도 Lone에서 구현함
- glibc 저장소는 해석하기 어려웠으나
nolibc 발견과 Lone Lisp의 시작
- Greg Kroah-Hartman에게 Linux에 공식 C 라이브러리가 없는 이유를 물었고, 과거 klibc 프로젝트가 있었다는 답을 받음
- 이후 Linux 커널 개발자들이 내부 도구용으로 nolibc.h를 만들었다는 사실을 발견함
- 자신의 liblinux보다 완성도와 사용성이 낫다고 판단해 프로젝트를 보관 처리함
- 현재
nolibc.h는 커널 트리 안에서 임시libc에 가까운 디렉터리로 커졌지만 외부 안정성 보장은 확실하지 않음
- 다음 프로젝트로 Lone Lisp를 시작함
- Linux에서 직접 실행되는 freestanding Lisp를 만들 수 있다면 어떤 프로그램도 같은 방식으로 작성할 수 있다고 봄
- 복잡한 현대 Linux 사용자 공간을 걷어내고 안정적인 시스템 호출 바이너리 인터페이스 위에서 다시 시작하려는 실험임
- Cosmopolitan에도 부동소수점 파싱 구현 등을 위한 패치를 보냄
- 여러 플랫폼의 실행 파일 형식으로 동시에 해석되는 APE 형식과 실행 파일 내 임베디드 아카이브 기능을 높이 평가함
오류 처리와 구분된 연속체
- 초기 Lone의 모든 오류 경로는 인터프리터에서
exit(255)로 종료됐으나, Common Lisp 계열의 재개 가능한 조건 시스템을 구현함 - 오류 처리의 기반은 구분된 연속체임
- 연속체를 스택 프레임 복사와 다른 위치에서의 재생으로 이해함
- 예외도 값을 캡처하지 않는 연속체의 한 형태로 봄
- 오류 종류를
'not-found같은 심볼에서 lambda로 매핑함- 인자 하나인
(lambda (error) ...)는 일반적인 예외 처리기처럼 동작함 - 인자 둘인
(lambda (error continuation) ...)는 연속체를 호출해 오류가 난 코드를 값과 함께 재개할 수 있음
- 인자 하나인
- Common Lisp의
use-value처럼 신호 발생 코드가 재시작 선택지를 열거하는 restart API는 의도적으로 채택하지 않음signal,throw,transfer가 단순히 값을 반환한 것처럼 동작하도록 평가기 내부의 기본 메커니즘으로 구성함
FEXPR와 호출 가능한 언어 요소
- 전통적인 Lisp 평가기가
if같은 특수 형식을 직접 처리하는 방식도 피함 - Lone은 평가기 플러그인에 해당하는 FEXPR를 지원해 특수 형식을 평가기 밖으로 분리함
- 이 설계로 순수 Lisp 함수의 꼬리 호출 최적화는 먼저 가능했지만 primitive에는 한동안 버그가 있었음
- primitive가 꼬리 위치에서 반환할 수 있도록 별도 메커니즘을 추가해 해결함
- 벡터와 테이블도 호출 가능함
([1 2 3] 0)은1을 반환함([1 2 3] 0 4)는 벡터를[4 2 3]으로 변경함
- 연속체와 생성기도 같은 호출 규약을 따름
control은 연속체를 지원하는begin과 비슷함transfer는 가장 가까운control블록을 빠져나오며 중단 지점을 나타내는 호출 가능한 연속체를 반환함- 해당 연속체를 값과 함께 호출하면 중단된 계산을 다른 반환값으로 재개할 수 있음
시스템 호출과 실행 구조
- Lone 시작 전 부모 프로세스가
execve를 호출하고, Lone은 내부 메모리 관리를 위해 약 12회의mmap을 수행함 - 표준 입력에서 스크립트를 읽어 실행한 뒤 종료하며, 실행 중
mmap,munmap,mremap이 추가로 발생할 수 있음 - 모듈 시스템은
openat과close를 사용함 - Linux의 300개 이상 시스템 호출 중 자체적으로 쓰는 것은 일부지만, Lisp 수준에 system-call primitive를 제공해 프로그램에서 나머지도 호출할 수 있음
ioctl처럼 커널 C 구조체가 필요한 호출을 위해 구조체 생성·조작 기능을 추가해야 함- 터미널 크기 조회도 이런 지원이 필요한 사례임
Lone으로 만들려는 사용자 공간
- 언어 수준에서는 오류 처리, 적절한 꼬리 호출 최적화, 생성기, 구분된 연속체를 구현했으며 장기적으로 Erlang/BEAM의 아이디어도 도입하려 함
- 여러 인터프리터가 Lisp 코드를 병렬 실행할 수 있도록 구조를 준비함
- Lone으로 만들 첫 프로그램은 현재 사용 중인 PugJS 포크 기반 정적 사이트 생성기의 대체품임
- 이후 자체 셸과 유틸리티를 작성해 전통적인 Linux 사용자 공간을 자신의 방식으로 다시 만들 계획임
env를 Lone Lisp 테이블로 내보내는 구현도 만들었으며, 당시에는 순회 기능이 없었지만 현재는 생성기를 지원함- 인터프리터가 특수 ELF 세그먼트의 Lisp 코드를 읽어 실행하는 기능도 있음
- 인터프리터 바이너리를 복사하고 Lisp 코드를 삽입하면 독립 실행 프로그램을 만들 수 있음
Claude를 통한 검토와 프로젝트 관리
- 전체 작업량에는 제한이 없지만 언어 자체의 기능 집합은 완성에 가까워지고 있다고 봄
- Claude를 일종의 관리자처럼 사용하며 코드 검토와 프로젝트 관리에서 가장 큰 도움을 받음
- 전체 코드베이스를 대상으로 문제, 미개발 영역, 향후 방향을 엄격하게 찾도록 요청함
- 거부한 접근법과 나중에 처리할 기능을 문서와 프로젝트 메모리로 관리함
- 쓰레기 수집기와 메모리 할당기는 Claude가 더 나은 알고리듬을 알려줘 크게 개선함
- Lone에 들어가는 모든 줄을 직접 검토하며 코드 작성 자체도 교육적이라고 보기 때문에 AI가 Lone 코드를 대신 작성하도록 신뢰하지는 않음
- 반면 JavaScript 정적 사이트 생성기에서는 Claude가 기능 전체를 작성하게 한 적이 있음
- 예정된 기능에는 GCC/Clang 컴파일러 API와 스택 보호 함수 지원이 있음
Ruby와 Scheme에서 가져온 문법
- Ruby의 표현력과 Scheme의 간결함을 결합하려 했으며, Lisp의 파서가 간단해 직접 작성한 뒤 중첩 리스트를 바로 다룰 수 있었음
- Lisp를 제대로 이해한 계기는 함수가 평가된 값이 아니라 리스트 자체를 받는 FEXPR를 구현했을 때였음
- 배열은
[1 2 3], 사전은{k v}문법을 사용함- Clojure와 newLisp뿐 아니라 Ruby, Python, JavaScript 사용자에게도 익숙해 불필요한 진입 장벽을 줄임
- C 코딩 스타일은 Linux 커널 스타일을 수정해 사용하되 정렬과 장식을 더 선호함
- Ruby에서는
method('arg')보다method 'arg'처럼 구두점을 줄여 영어 문장에 가깝게 쓰는 방식을 선호함 - Lisp에서도 구문 중첩을 줄임
- Lone의
let은(let (a b c d ...) body...)형태임 - 전통적인
(let ((a b) (c d) ...) body...)보다 이름과 값이 순서대로 나오는 형태가 함수 적용 방식에 더 자연스럽다고 판단함
- Lone의
성능과 부트스트랩 계획
- Lone이 앞서는 벤치마크는 시작 속도로, Ruby가 Rubygems를 읽는 시간보다 빠르게 프로그램을 시작하고 끝낼 수 있음
- 임시 측정에서는 Python보다 약 10~100배 느림
- Lone은 SICP 5.4장의 형태와 비슷한 리스트 수준 인터프리터임
- Python과 Ruby는 바이트코드 인터프리터라 성능 범주가 다름
- 리스트를 바이트코드로 바꾸면 Lisp의 본질을 잃는다고 느껴 바이트코드 컴파일을 피하고 있음
- 장기적으로 C 인터프리터는 최종 부트스트랩 기준으로 남기고, Lone 내부에서 JIT 컴파일러를 작성할 계획임
- 실행 가능한 페이지를
mmap으로 확보한 뒤 명령어를 생성하는 방식임
- 실행 가능한 페이지를
- Bash와의 재귀 Fibonacci 임시 비교에서는 Lone이 훨씬 빨랐지만, Bash가 반복마다 프로세스를 생성했을 가능성 등 측정 왜곡을 인정함
- 스크립트에서는 시작 시간이 중요하므로 Lone의 빠른 부트스트랩이 유용하다고 봄
- 해시 테이블을 빌드 단계에서 미리 계산해 C 초기화 데이터로 만들면 실행 시 초기화 비용을 더 줄일 수 있음
메모리와 최소 환경
mmap은 커널에 메모리 페이지를 요청하며, 일반적인 페이지 크기는 4,096바이트임- 읽기·쓰기·실행 권한을 지정할 수 있고 JIT는 실행 가능한 페이지를 이용함
- 16바이트만 필요해도 4KiB를 요청하면 낭비가 크므로 일반 할당기는 128MB 같은 큰 영역을 받은 뒤 작은 조각으로 나눔
- Lone의 최상위 환경에는
import와export두 심볼만 있음- 리스트, 리터럴, 함수 호출 메커니즘 외에는
if,quote까지 모두 가져와야 함 - 사용자가 환경의 모든 이름을 직접 통제하도록 한 설계임
- 리스트, 리터럴, 함수 호출 메커니즘 외에는
- 현재 함수는 약 50개이며
if,let,begin,control/transfer,intercept/signal,flatten등이 중심임
표준 라이브러리와 문서화
- 언어 문서는 아직 불완전하지만 언어 기능보다 표준 라이브러리 작업이 가까워지고 있음
- Ruby의 표준 라이브러리와 Scheme의 명명 방식을 참고할 계획임
- 다음 작업은 순회 프로토콜임
(for (each collection) (lambda (item) ...))형태를 고려함- 기존
each함수들은 모두 생성기를 반환하도록 재설계할 예정임
- 이후 문자열 조작과 효율적인 연결을 위한 문자열 빌더를 구현하려 함
- 순회와 문자열 기능이 완성되면 Hiccup 형태의 웹 프레임워크도 작성할 수 있다고 봄
- 저수준 기능이 끝나고 반복적인 표준 라이브러리 작업으로 넘어가면 집중력이 떨어질 가능성을 걱정하며 Claude의 관리 도움을 활용할 계획임
전체 스택을 직접 소유한다는 목표
- 연속체처럼 신비해 보이던 기능도 스택 프레임 복사로 이해하면 평범한 구현 문제가 되며, Lone은 다른 사람도 자신의 시스템을 만들고 탐색하도록 자극하려는 목적을 가짐
- 라이브러리 소비자에 머무르지 않고 라이브러리를 만드는 사람이 되려 하며, 일반 개발자에게 금지된 것처럼 취급되는 시스템 호출도 직접 사용하려 함
- 자신만의 시스템을 가지려면 고장 났을 때 직접 고쳐야 하지만, 더 많은 사람이 그 책임과 보상을 선택하기를 바람
- 컴퓨팅에 축적된 오래된 코드를 두려워하지 말고 불필요한 계층을 제거할 수 있다고 봄
- Linux 사용자 공간을 다시 만드는 주체가 자신이 아니라 Rust나 Zig 개발자일 수도 있음
- Lone이 성공하지 못하더라도 이런 시도를 고무한다면 만족할 수 있음
자유 소프트웨어와 Android
- 자유 소프트웨어가 추구하는 사용자 권한 강화를 중시하며, 범용 컴퓨터가 기업이나 정부가 관리하는 가전제품처럼 축소되는 상황을 우려함
- systemd는 무겁게 접근한 측면이 있어도 전통적인 Linux 사용자 공간을 다시 상상하고 재구축한 성공적인 사례로 평가함
- 모든 계층을 직접 만들기보다 싸움을 선택해야 한다고 봄
- Lone을 베어메탈 Lisp로 만들었다면 부팅 단계에도 도달하지 못했을 수 있음
- 512바이트 미만 부트 섹터 Lisp인 jart의 Sector Lisp를 사례로 듦
- Lone은 Termux 안에서 시작됐고 상당 부분을 휴대전화에서 작성함
- Android가
statx를 포함한 일부 시스템 호출을 차단하지만 Lone은 Android에서 실행되며 테스트 스위트가 100% 통과함
의사이자 비전문 프로그래머
- Moreira는 2019년 의대를 졸업한 의사임
- 고등학교에서 신경망 프로그래밍을 읽다가 생물학에 관심을 갖게 됨
- 어떤 날은 10~14시간 진료하고 다른 날은 쉬는 일정으로 프로그래밍 시간을 확보함
- 전자 의무기록을 사용하는 병원에서 템플릿과 타자 능력으로 업무를 크게 단축함
- 환자와 눈을 맞춘 채 기록할 수 있음
- 최근 시작한 Rails 프로젝트는 자신의 필요를 해결하기 위한 전자 환자 기록 시스템임
- 전문 개발자들이 개인적 문제를 직접 해결하지 않는 이유 중 하나로 컴퓨터 자체에 대한 관심 부족을 꼽음
- 결과만 얻으려는 태도보다 호기심이 있어야 구현 내부를 파고들 수 있으며, 사람들에게 그 호기심을 어떻게 불러일으킬지는 아직 알지 못함
댓글과 토론
Lobste.rs 의견들
-
인터뷰 시리즈를 빠짐없이 읽을 만큼 좋아하며, 각자가 컴퓨팅과 맺어 온 개인적 관계를 접하는 재미가 큼
- 컴퓨터를 단순한 도구 이상으로 아끼게 된 입문 계기를 모아 서로 비교해 보는 일이 특히 흥미로움
GBE+ GBA 에뮬레이터의 저자 Shonumi도 나처럼 Mega Man Battle Network 게임에 영향을 받았고, 언젠가 그의 인터뷰도 읽어보고 싶음
- 컴퓨터를 단순한 도구 이상으로 아끼게 된 입문 계기를 모아 서로 비교해 보는 일이 특히 흥미로움
-
재시작 가능한 예외에 일급 연속(first-class continuation)이 반드시 필요한 것은 아님
Common Lisp는 연속이 없어도 재시작 가능한 예외를 제공하며, 핵심은 처리기를 호출하기 전에 스택을 되감지 않는 데 있음
따라서 예외가 발생한 스택과 상태가 그대로 유지되어 처리기가 정상 반환하거나, Common Lisp처럼 스택 어딘가의 재시작 처리기로 점프할 수 있음- 필수라는 인상을 줬다면 내 깨달음에 대한 흥분이 과했던 탓임
다만 Lone Lisp에서는 반환된 연속이 여러 번 호출 가능하고 신호 처리기 밖으로 탈출할 수 있어 실제로 필요함
생성기는 반복 처리의 기반이라 성능이 더 중요하므로, 제한된 연속 대신 별도 스택을 사용해 복사를 없앴음(import (lone print set quote lambda intercept signal)) (set continuation (intercept (('return (lambda (value continuation) continuation))) (signal 'return 'value))) (print (continuation 1)); 1 (print (continuation 2)); 2 (print (continuation 3)); 3
오류 처리는 상대적으로 드문 경로라 기능을 온전히 제공하기로 했음
- 필수라는 인상을 줬다면 내 깨달음에 대한 흥분이 과했던 탓임
-
많은 사람에게 가장 중요한 작업이나 프로젝트는 여가 시간에 보수 없이 재미로 하는 일일지도 모름
내 의사도 남몰래 자신만의 Lisp 언어를 만드는 이중생활을 할 수 있겠다는 상상이 듦- 알고 보니 나만 그런 것도 아니었음
Dr. Cameron Kaiser도 굵직한 프로젝트를 진행했고, 기계 소유권에 대한 철학까지 공유하며 완전 개방형 POWER9 워크스테이션을 일상적으로 사용하고 있음
- 알고 보니 나만 그런 것도 아니었음
-
Lone Lisp를 이번에 처음 알게 됐으며 멋진 프로젝트로 보임
나도 13~14세 무렵 정보 수업에서 오래된 Dev-C++ 로 C++를 배우기 시작했는데, 정말 오랜만에 떠올라 향수를 자극함- Dev-C++가 아직도 명맥을 이어 가며 2021년에도 릴리스됐다는 사실이 놀라움
-
의사일 줄은 전혀 예상하지 못했으며 Con Kolivas가 떠오름
이런 의사 개발자가 의외로 꽤 많은 모양임
https://en.wikipedia.org/wiki/Con_Kolivas