steven

나만의 온라인 C 에디터 만들기

추석 연휴, 우레시노 어딘가에서 찍은 정월 대보름. 연휴를 맞이하여 일본을 다녀왔다. 언어, 문화는 다르지만 같은 개념을 공부하는 사람들도 보아 신기했다. 모처럼 여유로운 휴식 시간을 보냈다.

학교 과정에 C프로그래밍이라는 과목을 듣고 있다. 나는 자바스크립트로 처음 프로그래밍을 접했는데, 이 언어가 c-family 스러운 문법 때문인지 이해하는데 큰 문제는 없었다(main 함수 안에서 아주 작은 프로그램만 만들어서 그런 것일지도 모르겠다). 조금 더 몰입감 있게 공부하고 싶어서 Jupyter Notebook으로 C snippet을 돌리는 방법을 찾아봤었는데 정해진 시간 동안 이 이슈 를 해결하는 방법을 못찾아서 이 방법은 포기했다. 내가 지금 일하고 있는 영역(프론트엔드)의 레버리지를 살려서 alternative으로 해볼 수 있는게 무엇이 있는지 고민하다가 6시간 정도 시간을 들여서 Cnippet을 만들게 되었다. 아직 파일과 관련된 부분을 수강하지 않은 터라 기본적인 기능만 구현하였다.

WASM

만들면서 가장 많이 찾아봤던 부분은 어떻게 C 코드조각을 브라우저에서 실행시킬 수 있는지였다. 백준과 같은 사이트는 만든 코드 스니펫을 서버에 요청으로 보내어 서비스 랜드에서 테스트 코드를 돌리는 것으로 알고 있다. 그런데 나는 철저하게 유저 랜드에서 이걸 실행하고 싶었다. 브라우저에서 C 코드를 실행시키려면 1. C 코드에 대해 컴파일/링킹을 해야하고, 2. 실행 파일을 실행시켜야 한다. 이를 유일하게 구현할 수 있는 방법은 웹 어셈블리 코드를 사용하는 것이다. 이 때 내가 정리한 요구사항은 다음과 같았다.

1. wasm으로 컴파일 된 C 컴파일러를 브라우저에 올린다.
2. C 코드조각 string을 컴파일러에 던져 output 파일을 wasm으로 만든다
3. output wasm 파일을 브라우저에서 실행시킨다.

Wasmer

일단 wasm을 타겟팅 할 수 있는 C 컴파일러를 브라우저에 올리는게 먼저였다. 가장 먼저 눈에 들어왔던 건 tinycc였다. 매우 작고 빠르며 내 상황과 비슷한 예시 레포도 있기 때문이었다. tinycc를 로컬에서 wasm으로 빌드하는 데까지는 성공했지만 가상 파일 시스템 구현이나 stdin, stdout, stderr 같은 걸 WASI 로 만드는 것에는.. 내 한계를 보았다. 이 모든 것들을 학습하는 것도 가치가 있겠지만 내가 정한 시간 내에서 내 목표는 구현하는 것이었다. 그래서 WASM 환경을 관리해주는 라이브러리가 있을까 찾아봤는데, Wasmer를 발견했다.

와, 이 라이브러리 정말 좋다. 내가 필요로 하는 모든 것을 제공해준다. 심지어 레지스트리에 clang이 있었다. 그래서 바로 사용할 수 있었다. wasmer를 통해 내가 위에 작성한 요구사항을 구현하는 건 정말 쉽다.

// 1. fetch wasm file from wasmer registry
await Wasmer.fromRegistry("clang/clang");
// 2. create a new directory
const project = new Directory();
// 3. write file to directory
await project.writeFile("main.c", codeSnippet);

// 4. compile code snippet
const instance = await clangRef.current.entrypoint?.run({
  args: ["/project/main.c", "-o", "/project/main.wasm"],
  mount: { "/project": project },
});

// 5. wait for the process to exit
const compileOutput = await instance.wait();

// 6. read wasm file to the wasm runtime.
const wasm = await project.readFile("main.wasm");
// 7. load executable
const program = await Wasmer.fromFile(wasm);
// 8. run the executable
const result = await program.entrypoint?.run();

Terminal

브라우저에서 실행시킬 때 양방향으로 통신할 수 있는 터미널 비스무리한 게 있으면 아주 멋지지 않을까 생각했다. 그래서 사용한 라이브러리는 XTerm이다. 이걸 사용하면서 경험한 레슨들은 다음과 같다.

우선 stdin을 비스무리하게 보여주려면 wasm executable의 stdin과 터미널의 stdin을 연결해주어야 한다. 터미널의 stdinTerminal.onData 메소드를 통해 구현할 수 있다. 시그니처 를 살펴보면 IDisposable을 리턴한다. 한 터미널을 가지고 여러 번 코드를 실행할 때 구독하고 있는 메소드가 여러개가 되면 메모리 릭은 물론이고 나는 a 한 번 눌렀을 뿐인데 aa 같이 중복해서 키가 입력되는 불상사가 생길 것이다. 그래서 적절한 위치에 언제나 dispose() 하여 리스너를 해제시키는 로직을 넣어야만 했다. 그리고 JS 터미널 랜드에서 output wasm 랜드에 값을 던져주려면 utf-8 인코딩을 해주는 것이 안전하여 TextEncoder를 사용하였다. wasm 랜드에서는 바이트 시퀀스만 받을 수 있기 때문이다.

경험해보면서

안에 만들고 싶은 것을 만들어서 재미 있었지만 동시에 모르는 개념을 빠르게 알아야 하는 부담도 들었다. clang의 파일 크기는 거의 50MB이라.. tinycc로 옮길 수 있으면 참 좋을 것 같다! wasmwasi 와 같은 개념을 딥하게 공부하진 않았지만 적어도 이 단어들이 내 개발자 인생에서 낯설지 않을 거라는 생각이 들었다. 여기서 끝내지 말고 조금씩 개선을 해보고 싶다!