PEP 776 – Emscripten 지원
- Author:
- Hood Chatham <roberthoodchatham at gmail.com>
- Sponsor:
- Łukasz Langa <lukasz at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Active
- Type:
- Informational
- Created:
- 18-Mar-2025
- Python-Version:
- 3.14
- Post-History:
- 18-Mar-2025, 28-Mar-2025
- Resolution:
- 04-Apr-2026
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
Emscripten은 완전한 오픈 소스 컴파일러 툴체인입니다. Emscripten은 C/C++ 코드를 브라우저와 Node.js를 포함한 JavaScript 런타임에서 사용할 수 있는 WebAssembly/JavaScript 실행 파일로 컴파일합니다. Rust 언어도 Emscripten 타깃을 유지 관리하고 있습니다.
이 PEP는 2024년 10월 25일 운영 위원회에서 승인된 Python 3.14의 Emscripten 지원 Tier 3 추가를 공식화합니다. 목표는 다음과 같습니다.
- CPython Emscripten 런타임의 현재 상태를 기술하는 것
- Pyodide 런타임의 현재 상태를 기술하는 것
- Pyodide 런타임에서 CPython Emscripten 런타임으로 업스트림할 사소한 기능을 식별하는 것
여기에서 식별한 사소한 기능은 모두 PEP 없이 구현할 수 있는 기능입니다. 구현하고자 하는 더 중요한 런타임 기능도 논의하지만, 그러한 기능에 대한 결정은 후속 PEP로 미룹니다.
동기
웹 브라우저는 Windows, macOS, Linux 및 모든 스마트폰에서 사용할 수 있는 범용 컴퓨팅 플랫폼입니다.
Pyodide 프로젝트는 2018년부터 Emscripten Python을 지원해 왔습니다. 수십만 명의 학생이 Capytale과 PyodideU 같은 프로젝트를 통해 Pyodide로 Python을 배웠습니다. 또한 Pyodide는 대화형 문서를 제공하기 위해 Python 패키지에서 점점 더 많이 사용되고 있습니다. 이는 Emscripten 플랫폼의 중요성과 성숙도를 모두 보여 줍니다.
또한 Emscripten과 WASI는 의미 있는 샌드박싱을 제공하는 유일한 지원 플랫폼입니다.
Emscripten 플랫폼 정보
“Pyodide”와 “Emscripten Python”
이 문서에서는 다운스트림 추가 사항 없이 python/cpython 저장소에서 유지 관리되는 Emscripten Python을 가리켜 “Emscripten Python”이라는 용어를 사용합니다. Emscripten Python에 있는 기능을 Pyodide에 있는 기능과 대조합니다.
Pyodide는 GitHub에서 유지 관리되며 jsDelivr, npm, GitHub 릴리스를 통해 배포됩니다.
Emscripten Python은 배포되지 않지만, devguide의 안내를 따라 빌드할 수 있습니다.
Emscripten 배경
Emscripten은 LLVM 기반의 C 및 C++ 컴파일러와 링커, 그리고 약간 패치된 musl libc 기반의 런타임으로 구성됩니다.
Emscripten은 POSIX 기반 플랫폼입니다. Emscripten은 WebAssembly binary format과 WebAssembly dynamic linking section을 사용합니다.
emcc 컴파일러는 clang의 래퍼입니다. emcc 링커는 (역시 LLVM 툴체인의 일부인) wasm-ld의 래퍼입니다.
Linux와 소스 호환되는 이식 가능한 C/C++ 코드에 대한 Emscripten의 지원은 상당히 포괄적이며, 예상되는 몇 가지 예외는 따로 설명합니다. CPython은 이미 Emscripten으로의 컴파일을 지원하며, 일반적인 Linux 타깃에서 아주 적은 수정만 하면 됩니다.
POSIX 준수
Emscripten은 POSIX 플랫폼입니다. 그러나 존재하기는 하지만 호출하면 항상 실패하는 POSIX API도 있고, 아예 존재하지 않는 POSIX API도 있습니다. 특히 네트워킹 API와 블로킹 I/O에 문제가 있으며, fork()는 지원되지 않습니다. Emscripten 이식성 지침을 참고하십시오.
Emscripten 실행 파일은 스레딩 지원과 함께 링크할 수 있지만, 여기에는 몇 가지 제약이 따릅니다:
- 스레딩을 활성화하려면 웹사이트가 Spectre 방식의 정보 유출 가능성을 수용한다는 것을 나타내는 특수한 보안 헤더와 함께 제공되어야 합니다. 이러한 헤더는 웹 플랫폼에 깊이 익숙하지 않은 사용자에게 사용성 측면의 위험 요소입니다.
- 실행 파일을 스레딩과 동적 로더 모두와 함께 링크하면, Emscripten은 동적 로딩과 pthreads를 함께 사용하는 것이 실험적이라는 경고를 출력합니다. 이로 인해 성능 문제나 크래시가 발생할 수 있습니다. 이러한 문제를 해결하려면 WebAssembly 표준 작업이 필요할 수 있습니다.
이러한 제약 때문에 Pyodide는 pthreads가 없는 Python 빌드를 표준으로 삼습니다. 수요가 충분하다면, 동적 로더가 없는 pthreads 빌드를 나중에 추가할 수 있습니다.
개발 도구
Emscripten 개발 도구는 Linux, Windows, macOS에서 똑같이 잘 지원됩니다. 업스트림 도구는 다음과 같습니다:
- Emscripten 컴파일러 툴체인(emcc)을 설치하는 데 사용할 수 있는 Emscripten 소프트웨어 개발자 키트(emsdk).
- emcc는 C 및 C++ 컴파일러이자 링커이며, 시스템 라이브러리용 헤더를 포함한 sysroot입니다. 시스템 라이브러리 자체는 요청된 ABI에 따라 즉석에서 생성됩니다.
- Node.js를 “에뮬레이터”로 사용하여 명령줄에서 Emscripten 프로그램을 실행할 수 있습니다. 이 에뮬레이션은 Linux에서 가장 잘 동작하며, macOS가 그다음입니다. Node.js는 Emscripten 프로그램을 테스트하는 가장 편리한 방법입니다.
- 어떤 웹 브라우저 안에서도 Emscripten 프로그램을 실행할 수 있습니다. Selenium, Playwright, Puppeteer 같은 브라우저 자동화 도구를 사용하여 브라우저 전용 기능을 테스트할 수 있습니다.
Pyodide의 도구:
pyodide build는 Emscripten에서 실행되도록 Python 패키지를 크로스 컴파일하는 데 사용할 수 있습니다. 크로스 컴파일은 Linux에서 가장 잘 동작하며, macOS에서는 실험적으로 지원되고, Windows에서는 전혀 지원되지 않습니다.pyodide venv는 Pyodide에서 실행되는 가상 환경을 만들 수 있습니다.pytest-pyodide는 다양한 JavaScript 런타임에서 Python 코드를 테스트할 수 있습니다.
cibuildwheel은 pyodide build를 사용하여 Emscripten을 대상으로 하는 휠 빌드를 지원합니다.
당분간 Pyodide의 패키징 도구는 Pyodide 저장소에 계속 남아 있을 것입니다. 장기적으로 Pyodide의 패키징 도구를 어디에 두어야 할지는 아직 정해지지 않은 문제입니다. 합리적인 선택지는 두 가지로, GitHub에서 계속 pyodide 조직 아래에 두거나 pypa 조직으로 옮기는 것입니다.
Emscripten 애플리케이션 생명 주기
Emscripten “바이너리”는 .mjs 파일과 .wasm 파일 한 쌍으로 구성됩니다. .wasm 파일에는 컴파일된 C/C++/Rust 코드가 모두 들어 있습니다. .mjs 파일에는 런타임을 설정하고, .wasm 파일을 찾아 컴파일하고 인스턴스화하며, main() 함수를 호출하고, 종료 시 런타임을 정리하는 생명 주기 코드가 들어 있습니다. 또한 파일 시스템과 동적 로더를 비롯한 모든 시스템 호출의 구현과, JavaScript 런타임의 추가 기능을 C 코드에 노출하는 모든 로직도 포함합니다.
.mjs 파일은 bootstrapEmscriptenExecutable() JavaScript 함수 하나를 내보냅니다. 이 함수는 런타임을 부트스트랩하고, main() 함수를 호출한 뒤, C 함수를 호출하는 데 사용할 수 있는 API 객체를 반환합니다. 이 함수를 호출할 때마다 독자적인 별도의 주소 공간을 가진, 완전하고 독립적인 런타임 사본이 만들어집니다.
bootstrapEmscriptenExecutable() 함수는 많은 런타임 설정을 받습니다. 전체 목록은 이곳의 Emscripten 문서에 설명되어 있습니다. 그중 가장 중요한 설정은 다음과 같습니다.
thisProgram:argv[0]의 값입니다. Python에서는 이 값이sys.executable로 전달됩니다.arguments:main()에 전달할 문자열 인자의 목록입니다.preRun: JavaScript 런타임과 파일 시스템이 부트스트랩된 후,main()을 호출하기 전에 실행되는 콜백의 목록입니다. 파일 시스템, 환경 변수, 표준 스트림을 설정하는 데 유용합니다.print/printErr: stdout과 stderr의 초기 핸들러입니다. 이 핸들러들은 줄 단위로 버퍼링되며, 완성되지 않은 줄에flush()를 수행하면 줄 바꿈이 하나 더 강제로 들어갑니다. tty와 같은 동작이 필요하면preRun()훅에서 표준 스트림 장치를 교체해야 합니다.onExit: 런타임이 종료될 때 호출되는 핸들러입니다.instantiateWasm: WebAssembly 모듈을 인스턴스화하기 위해 호출되는 콜백입니다. WebAssembly 컴파일과 병렬로 수행할 수 있는 다른 사용자 정의 비동기 시작 작업이나 다운로드가 있을 때, 이 함수로 WebAssembly 인스턴스화 절차를 재정의하면 유용합니다. 이 콜백을 구현하면 이 모든 작업을 병렬로 수행할 수 있습니다.
파일 시스템 설정
표준 라이브러리
Python이 실행되려면 Emscripten 파일 시스템에 있는 표준 라이브러리에 접근할 수 있어야 합니다. 이를 위한 방법은 여러 가지가 있습니다.
- Emscripten 링커에는 파일 로딩을 자동으로 처리하는
--preload-file플래그가 있습니다. 동작 방식에 대한 정보는 이곳에서 볼 수 있습니다. 가장 간단한 방법이지만, 표준 도구로 처리할 수 없는 사용자 정의 아카이브 형식에 파일을 넣기 때문에 Pyodide는 이 방법을 더 이상 사용하지 않습니다. - Node.js에서는 NODEFS를 사용해 파일이 있는 네이티브 디렉터리를 Emscripten 파일 시스템에 마운트하십시오. 가장 효율적인 방법이지만 Node에서만 사용할 수 있습니다. 이는 WASI가 하는 방식과 매우 비슷합니다.
- 표준 라이브러리를 zip 아카이브에 넣고
ZipImporter를 사용합니다. 압축하지 않은 zip 파일을 사용하면 웹 서버와 클라이언트가 표준 라이브러리 자체에 더 나은 압축을 적용할 수 있습니다. 또한 덜 효율적인 WebAssembly 압축 해제 대신 브라우저의 더 효율적인 네이티브 압축 해제 알고리즘을 사용합니다. 이 방식의 단점은 메모리 사용량이 더 많다는 점, 그리고inspect및 표준 라이브러리가 이런 방식으로 패키징될 것을 예상하지 않는 여러 테스트가 깨진다는 점입니다. - 표준 라이브러리를 압축하지 않은 tar 아카이브에 넣고, 이 tar 파일을 기반으로 하는 읽기 전용 파일 시스템인 TARFS에 마운트합니다. 이 방식은 브라우저에서 사용할 수 있는 선택지 중 메모리 사용량, 런타임 성능, 전송 크기 면에서 가장 우수합니다. 단점은 Emscripten 자체에 TARFS가 포함되어 있지 않으므로 다운스트림 구현이 필요하다는 점입니다.
Pyodide는 모든 런타임에서 ZipImporter 방식을 사용합니다. Python은 node로 실행할 때는 NODEFS 방식을, 웹 예제에서는 ZipImporter 방식을 사용합니다. 앞으로도 이 방식을 유지할 것입니다.
ZipImporter는 부트스트래핑 문제를 깔끔하게 해결합니다. Python 런타임은 매우 다양한 아카이브 형식을 풀 수 있지만, 표준 라이브러리를 사용할 수 있게 되기 전까지는 Python 런타임을 사용할 준비가 되지 않습니다. zipimport.py는 동결된(frozen) 모듈이므로 이러한 문제를 피할 수 있습니다. 다른 모든 방식은 JavaScript를 사용해 표준 라이브러리를 설정함으로써 부트스트래핑 문제를 해결합니다.
서드파티 패키지
필요한 패키지도 Emscripten 파일 시스템에서 사용할 수 있게 해야 합니다. 현재 Emscripten CPython은 패키지를 지원하지 않습니다. Pyodide는 패키지에 대해 두 가지 다른 방식을 사용합니다:
- 브라우저에서 Pyodide는 휠 패키지를 내려받아 MEMFS site-packages 디렉터리에 압축을 풉니다. 그런 다음 휠에 들어 있는 모든 동적 라이브러리를 미리 로드합니다. 모든 패키지를 다운로드하고 설치하는 작업은 런타임이 시작될 때마다 다시 수행됩니다.
- Pyodide
pythonCLI 진입점은 Python을 부트스트랩하기 전에 호스트 파일 시스템 전체를 NODEFS 디렉터리로 마운트합니다. 이렇게 하면 일반적인 가상 환경 메커니즘이 작동합니다. Pyodide 가상 환경에는 pip가 Pyodide 휠을 설치하도록 패치된 pip 사본과 사용자 정의pip.conf가 들어 있습니다. 시작할 때 PyodidepythonCLI는 site-packages 디렉터리에 있는 패키지의 모든 Emscripten 동적 라이브러리를 미리 로드합니다.
콘솔 및 대화형 사용
stdin은 기본적으로 항상 EOF를 반환하며, stdout과 stderr는 기본적으로 각각 console.log와 console.error를 호출합니다. bootstrapEmscriptenExecutable()에 핸들러를 전달하여 표준 스트림을 구성할 수 있지만, 어떻게 하더라도 I/O 장치에는 플러시할 때 강제로 줄 바꿈을 넣는 바람직하지 않은 줄 버퍼링 동작이 있습니다. 브라우저에서 제대로 동작하는 TTY를 구현하려면 기본 I/O 장치를 제거하고 preRun 훅에서 이를 교체해야 합니다.
브라우저의 메인 스레드에서는 사용자 입력을 기다리며 블록하는 것이 허용되지 않으므로, 브라우저에서 stdin이 올바르게 작동하게 만드는 데에는 추가적인 어려움이 있습니다. Emscripten을 웹 워커에서 실행하고 공유 메모리 헤더와 함께 제공하면, 공유 메모리와 아토믹스를 사용해 입력을 받을 수 있습니다. 또한 실험적인 JavaScript Promise Integration API를 사용한 스택 전환으로 stdin 장치가 더 간단하고 효율적인 방식으로 블록하게 할 수도 있습니다.
Pyodide는 줄 버퍼링 동작을 바로잡기 위해 표준 I/O 장치를 교체합니다. Pyodide를 Node.js에서 실행하면 stdin, stdout, stderr는 기본적으로 각각 process.stdin, process.stdout, process.stderr에 연결되므로, 표준 스트림이 별도의 설정 없이 tty로 동작합니다. Pyodide는 또한 shutil.get_terminal_size가 process.stdout.rows 및 process.stdout.columns와 일치하는 결과를 반환하도록 보장합니다. Pyodide는 현재 스택 전환 stdin을 지원하지 않습니다.
현재 Emscripten Python Node.js 실행기는 Emscripten이 제공하는 기본 I/O를 사용합니다. 웹 예제는 stdin에 Atomics를 사용하고 사용자 정의 stdout 및 stderr 핸들러를 갖추고 있지만, 이 핸들러들은 바람직하지 않은 줄 버퍼링 동작을 보입니다. Pyodide의 표준 스트림 동작을 업스트림에 반영할 것입니다.
장기적으로는 스택 전환 stdin 장치를 구현하기를 희망하지만, 이는 이 PEP의 범위를 벗어납니다.
트랩과 포착되지 않은 예외
WebAssembly 트랩, 처리되지 않은 JavaScript 예외, 또는 포착되지 않은 WebAssembly throw 명령어가 발생하면 C 런타임 상태가 손상된 것으로 간주합니다.
다른 플랫폼과 달리, 트랩이나 그 밖의 복구 불가능한 libc 런타임 손상이 발생했을 때 실행 파일을 종료시켜 줄 운영 체제가 없습니다. 트레이스백을 출력하거나, 메모리를 덤프하거나, 그 밖에 크래시 디버깅에 도움이 되는 작업을 수행하는 코드를 직접 제공해야 합니다. JavaScript API를 노출한다면, 다운스트림 사용자가 일관되지 않은 상태의 Python 런타임을 관찰하지 않도록 복구 불가능한 크래시 이후에는 해당 API가 비활성화되도록 보장해야 합니다.
치명적인 오류를 감지하기 위해 Pyodide는 다음과 같은 방식을 사용합니다. WebAssembly에서 JavaScript로 들어가는 실패할 수 있는 모든 호출을 JavaScript try/catch 블록으로 감쌉니다. 포착된 JavaScript 예외는 모두 Python 예외로 변환됩니다. 이를 통해 복구 가능한 모든 JavaScript 오류는 WebAssembly 프레임을 거쳐 되감기기 전에 포착됩니다. WebAssembly로 들어가는 모든 진입점 또한 JavaScript try/catch 블록으로 감쌉니다. 그곳에서 포착된 예외는 이미 WebAssembly 프레임을 되감은 것이므로 치명적인 오류로 간주됩니다(단, exit()를 처리하기 위한 특수한 경우가 있습니다). 이를 위해서는 Python/JavaScript 외부 함수 인터페이스와의 근본적인 통합이 필요합니다.
Pyodide 런타임은 치명적인 예외를 포착하면 오류를 검사하여 그것이 트랩, 시스템 호출의 논리 오류, longjmp() 없는 setjmp(), 또는 __cxa_throw()에 대한 libcxxabi 호출(포착되지 않은 C++ 예외 또는 Rust 패닉) 중 어디에서 비롯되었는지 판단합니다. 가능한 한 유익한 오류 메시지를 출력합니다. 또한 _Py_DumpTraceback()을 호출하여 JS/WebAssembly 트레이스백과 함께 Python 트레이스백도 표시할 수 있도록 합니다. 또한 JavaScript API를 비활성화하여, 이후 Python을 호출하려는 시도는 런타임이 치명적으로 실패했다는 오류를 발생시키도록 합니다.
일반적으로 WebAssembly 심볼은 제거되어 있으므로 WebAssembly 프레임은 그다지 유용하지 않습니다. -g2 (또는 그보다 높은 디버그 설정)로 컴파일하고 링크하면 WebAssembly 심볼이 포함되어 트레이스백에 나타나게 됩니다.
Emscripten Python에는 현재 JavaScript API도 외부 함수 인터페이스도 없으므로 상황이 훨씬 단순합니다. Python Node.js 실행기는 bootstrapEmscriptenExecutable() 호출을 try/catch 블록으로 감쌉니다. 예외가 포착되면 JavaScript 예외를 표시하고 _Py_DumpTraceback()을 호출합니다. 그런 다음 종료 코드 1로 종료합니다. JavaScript API나 외부 함수 인터페이스를 추가하기 전까지는 이 방식을 유지할 것이며, 그러한 추가는 이 PEP의 범위를 벗어납니다.
명세
작업 범위
Emscripten을 Tier 3 플랫폼으로 추가하려면 패치하지 않은 CPython 소스 코드에서 Emscripten 호환 빌드를 컴파일할 수 있도록 지원을 추가하기만 하면 됩니다. python.org에서 Emscripten 결과물을 반드시 공식 배포해야 하는 것은 아니지만, 향후 추가될 수는 있습니다. 당분간은 계속해서 Pyodide와 함께 다운스트림에서 배포됩니다.
Emscripten은 다른 POSIX 플랫폼과 동일한 configure 및 Makefile 시스템으로 빌드하므로, POSIX 플랫폼에서 빌드해야 합니다. Linux와 macOS를 모두 지원합니다.
Python CLI 진입점도 제공하며, 이 진입점은 테스트 스위트 실행 등에 사용할 수 있습니다.
링크
Python 인터프리터는 정적 링크만 지원합니다. 인터프리터에서는 여러 목적으로 EM_JS 함수를 사용합니다. EM_JS 함수를 포함하는 객체 파일을 동적으로 링크할 수는 있지만, 그 동작은 정적 빌드에서의 동작과 크게 다릅니다. 이러한 이유로 동적 링크를 지원하려면 별도의 작업이 필요합니다. Emscripten에서 인터프리터를 동적으로 링크해야 하는 사용 사례가 생기면, 이를 지원하는 데 얼마나 많은 노력이 필요한지 평가할 수 있습니다.
표준 라이브러리
지원되지 않는 모듈
다음을 참조하십시오: https://pyodide.org/en/stable/usage/wasm-constraints.html#removed-modules.
제거된 모듈
다음 모듈은 다운로드 크기를 줄이기 위해, 그리고 현재 WebAssembly VM에서 작동하지 않기 때문에 표준 라이브러리에서 제거됩니다.
- curses
- dbm
- ensurepip
- fcntl
- grp
- idlelib
- msvcrt
- pwd
- resource
- syslog
- termios
- tkinter
- turtle
- turtledemo
- venv
- winreg
- winsound
포함되어 있지만 작동하지 않는 모듈
다음 모듈은 임포트할 수는 있지만 작동하지 않습니다:
- multiprocessing
- threading
- 소켓
이 모듈들을 필요로 하는 모든 기능도 마찬가지입니다.
다음 모듈은 존재하지만, 제거된 termios 모듈에 대한 의존성 때문에 임포트할 수 없습니다:
- pty
- tty
플랫폼 식별
sys.platform은 "emscripten"을 반환합니다. Emscripten은 Linux와 호환되려고 하지만, 차이가 충분히 크기 때문에 별도의 이름을 사용하는 것이 타당합니다. 이는 os.uname()의 반환값과 일관됩니다.
Emscripten 버전과 런타임(브라우저에서는 navigator.userAgent, Node.js에서는 "Node js" + process.version)을 포함하는 sys._emscripten_info도 있습니다.
시그널 지원
WebAssembly는 시그널을 네이티브로 지원하지 않습니다. 게다가 pthreads를 사용하지 않는 빌드에서는 WebAssembly 모듈의 주소 공간이 공유되지 않으므로, Python 인터프리터가 코드를 실행하는 동안에는 인터럽트를 감지할 수 있는 어떤 스레드도 eval breaker에 값을 쓸 수 없습니다. 이 문제를 우회하기 위해 가능한 해결책은 두 가지입니다:
- Emscripten이 웹 워커에서 실행되고 공유 메모리 헤더와 함께 제공되는 경우, WebAssembly 주소 공간 밖의 공유 메모리를 시그널 버퍼로 사용할 수 있습니다. 시그널을 처리하는 UI 스레드는 원하는 시그널을 시그널 버퍼에 쓸 수 있습니다. 인터프리터는 eval breaker 코드에서 이 시그널 버퍼의 상태를 주기적으로 확인할 수 있습니다. 시그널 버퍼를 확인하는 것은 네이티브 플랫폼에서 eval breaker를 확인하는 것보다 느리므로, eval breaker를 50번 통과할 때마다 한 번씩만 확인합니다. Python/emscripten_signal.c를 참조하십시오.
- 스택 전환을 사용하면 가끔 스택을 전환하여 JavaScript 이벤트 루프가 한 바퀴 돌도록 한 다음, 시그널 버퍼의 상태를 확인할 수 있습니다. 이를 위해서는 실험적인 JavaScript Promise Integration API가 필요하며, 이 글에서 설명하는 긴 작업 최적화 기법과 함께 사용하는 것이 가장 좋습니다.
Emscripten Python은 이미 공유 메모리 기반 해결책을 구현했으며, 이는 Pyodide에서 사용되고 있습니다.
궁극적으로는 스택 전환 기반 시그널을 구현하여 node와 브라우저의 메인 스레드뿐만 아니라 공유 메모리 헤더 없이 제공되는 웹 페이지에서도 시그널을 사용할 수 있게 되기를 바랍니다. 하위 호환성을 위해서, 그리고 가능한 경우에는 더 효율적이기 때문에, 공유 메모리 기반 방식도 유지해야 합니다. 하지만 이는 이 PEP의 범위를 벗어납니다.
함수 포인터 캐스트
C 표준의 6.3.2.3절 8항에는 다음과 같이 적혀 있습니다:
한 타입의 함수를 가리키는 포인터는 다른 타입의 함수를 가리키는 포인터로 변환했다가 다시 원래대로 변환할 수 있으며, 그 결과는 원래 포인터와 비교했을 때 같아야 합니다. 변환된 포인터를 사용하여 가리키는 타입과 호환되지 않는 타입의 함수를 호출하면, 그 동작은 정의되지 않습니다.
그러나 대부분의 플랫폼은 같은 방식으로 동작합니다. 함수를 너무 많은 인자로 호출하면 남는 인자는 무시되고, 너무 적은 인자로 호출하면 모자라는 인자는 쓰레기 값으로 채워집니다.
반면 WebAssembly 명세는 잘못된 시그니처로 함수를 호출하면 트랩이 발생하도록 정의합니다(call_indirect 실행의 18단계를 참조하십시오).
Python 확장 모듈이 함수를 다른 시그니처로 캐스트하여 그 시그니처로 호출하는 일은 흔합니다. 예를 들어 많은 C 확장은 METH_NOARGS 함수가 인자를 0개 또는 1개 받도록 정의합니다. 인터프리터는 이 함수를 두 개의 인자로 호출하는데, 첫 번째 인자는 Python 모듈 객체이고 두 번째 인자는 항상 NULL입니다. 이러한 확장 모듈이 소스 코드를 변경하지 않고도 동작하게 하려면 특별한 처리가 필요합니다.
처음에는 JavaScript를 호출하고 JavaScript가 함수 포인터를 호출하도록 하여 이 문제를 해결했습니다. JavaScript에서 WebAssembly 함수를 호출할 때는 누락된 인자가 0으로 처리되고 남는 인자는 무시됩니다(여기의 7단계를 참조하십시오). 이 방식은 동작하지만, 느리고 스택 전환을 깨뜨린다는 단점이 있습니다. JavaScript 프레임을 거쳐서는 스택 전환을 할 수 없기 때문입니다.
wasm-gc ref.test 명령어를 사용하면 함수 포인터의 타입을 조회하여 인자 목록을 수동으로 바로잡을 수 있습니다.
wasm-gc는 WebAssembly 런타임에서 비교적 새로운 기능이므로, 가능하면 wasm-gc 기반의 함수 포인터 캐스트 트램펄린을 사용하려고 시도하고, 그렇지 않으면 JS 트램펄린으로 대체합니다. 스택 전환을 지원하는 모든 JavaScript 런타임은 wasm-gc도 지원하므로, 이를 통해 스택 전환을 지원하는 모든 플랫폼 런타임에서 스택 전환이 동작하도록 보장됩니다. 한 가지 문제는 iOS 18에 결함이 있는 wasm-gc 구현이 탑재되어 있어서 이를 특별히 처리해야 한다는 점입니다.
함수 포인터 캐스트 처리는 cpython에 완전히 구현되어 있습니다. Pyodide는 업스트림과 정확히 같은 코드를 사용합니다.
CI 자원
Pyodide는 적당히 최신 버전의 Node.js가 있는 모든 Linux에서 빌드하고 테스트할 수 있습니다. Anaconda는 Russell Keith-Magee가 유지 관리하는 Emscripten 빌드봇을 실행할 물리 하드웨어를 제공하겠다고 제안했습니다.
CPython은 현재 GitHub Actions에서 Tier 3 플랫폼을 테스트하지 않지만, 이것이 바뀐다면 GitHub Actions의 Linux 러너로 Emscripten Python을 빌드하고 테스트할 수 있습니다.
PEP 11
PEP 11은 Emscripten, 구체적으로는 wasm32-unknown-emscripten_xx_xx_xx 트리플이 지원된다는 것을 나타내도록 갱신될 것입니다.
Russell Keith-Magee가 이 ABI들에 대한 초기 코어 팀 연락 담당자를 맡을 것입니다.
향후 작업
패키징 생태계의 크로스 빌드 개선
이제 Python은 iOS, Android, WASI, Emscripten의 네 가지 자체 호스팅이 불가능한 플랫폼을 지원합니다. 이 플랫폼들은 모두 크로스 빌드로 패키지를 빌드해야 합니다. 현재 pyodide-build로 매우 많은 Python 패키지를 Emscripten용으로 빌드할 수 있지만, 그 과정이 매우 복잡합니다. 이상적으로는 Python 패키징 생태계에 크로스 빌드를 위한 표준이 있어야 합니다. 이는 어려운 장기 프로젝트입니다. 특히 패키징 시스템이 복잡하고, 처음부터 크로스 컴파일이 일어나지 않는다는 가정하에 설계되었기 때문입니다.
업스트림에 반영할 Pyodide 런타임 기능
다음은 이 PEP와 Python 3.14 개발 주기의 범위에는 포함되지 않지만, 향후 업스트림에 반영하고자 하는 Pyodide 런타임 기능 모음입니다.
부트스트래핑을 위한 JavaScript API
현재 Python 부트스트래핑을 위한 안정적인 API는 제공하지 않습니다. 대신 Node.js CLI 진입점용 설정 모음과 브라우저 데모용 별도 설정 모음을 사용합니다.
Emscripten 실행 파일의 시작 API는 복잡하며, 제대로 동작하지 않는 설정 조합이 많습니다. Pyodide는 Emscripten보다 단순한 옵션 모음을 제공합니다. 이를 통해 다운스트림 사용자에게는 높은 유연성을 제공하면서도, 우리는 테스트된 소수의 설정만 유지 관리할 수 있습니다. 또한 다운스트림의 코드 중복도 줄여 줍니다.
궁극적으로는 Pyodide의 부트스트래핑 API를 업스트림에 반영하고자 합니다. 당분간은 단순함을 유지하기 위해 JavaScript API를 전혀 지원하지 않을 것입니다.
JavaScript 외부 함수 인터페이스(FFI)
Emscripten은 POSIX를 지원하므로, 상당수의 작업은 os 모듈로 수행할 수 있습니다. 그러나 JavaScript 런타임의 여러 기본 동작은 POSIX API로는 수행할 수 없습니다. Pyodide는 JavaScript 객체 모델과 Python 객체 모델 사이의 매핑, 그리고 고수준의 양방향 통합을 가능하게 하는 호출 규약을 정의하는 방식을 택합니다. Pyodide 문서를 참조하십시오.
Asyncio
대부분의 JavaScript 기본 요소는 비동기적입니다. Python이 실행되는 JavaScript 스레드에는 이미 이벤트 루프가 있습니다. 실제 작업을 모두 JavaScript 이벤트 루프에 위임하는 Python 이벤트 루프를 구현하는 것은 그리 어렵지 않으며, Pyodide에서는 여기에 구현되어 있습니다.
JavaScript 이벤트 루프에 작업을 예약하는 유일한 방법은 JavaScript를 호출하는 것이므로, 이 기능은 논리적으로 최소한 제한된 수준의 JavaScript FFI가 있어야 성립합니다.
비호환성의 원인 중 하나는 JavaScript isolate 내부에서 이벤트 루프의 수명 주기를 제어할 수 없다는 점입니다. 이 때문에 asyncio.run() 및 이와 유사한 기능이 동작하지 않습니다.
스택 전환을 사용하면 “동기” Python 프레임으로 코루틴을 만드는 것도 가능합니다. 이러한 스택 전환 코루틴은 일반 Python 코루틴과 같은 이벤트 루프에서 예약되며, 완전히 재진입 가능합니다. 이 기능은 Pyodide에 완전히 구현되어 있습니다.
하위 호환성
새 플랫폼을 추가해도 CPython 자체에는 하위 호환성 문제가 생기지 않습니다. 하지만 Pyodide 사용자에게는 하위 호환성과 관련된 영향이 일부 있을 수 있습니다. Pyodide에는 기존 사용자가 매우 많으므로, Pyodide의 기능을 Python 업스트림에 반영할 때 하위 비호환성을 최소화하도록 주의하는 것이 중요합니다. 또한 Pyodide가 다운스트림에서 더 완전한 버전으로 대체할 수 있도록, 업스트림에 부분적으로 반영된 기능을 비활성화하는 방법도 필요합니다.
보안 영향
새 플랫폼을 추가해도 새로운 보안 영향은 생기지 않습니다.
또한 Emscripten과 WASI는 지원 플랫폼 가운데 샌드박싱을 제공하는 유일한 플랫폼입니다. 사용자가 신뢰할 수 없는 Python 코드나 신뢰할 수 없는 Python 확장 모듈을 실행하려는 경우, Emscripten은 이를 안전하게 실행하는 방법을 제공합니다.
교육 방법
이 PEP와 관련된 교육 요구 사항은 두 개발자 그룹과 관련됩니다.
첫째, 웹 개발자는 Python을 빌드하여 자신의 Python 코드 및 필요한 패키지와 함께 웹사이트에서 사용하는 방법과, 런타임에 이들을 모두 사용하는 방법을 알아야 합니다. 문서는 기존 Windows 임베드 가능한 패키지와 비슷한 형식으로 이 내용을 다룰 것입니다. 당분간은 개발자에게 가능한 한 Pyodide를 사용하도록 권장할 것입니다.
참조 구현
Pyodide.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.