Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

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

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

Emscripten은 완전한 오픈 소스 컴파일러 툴체인입니다. C/C++ 코드를 브라우저와 Node.js를 포함한 JavaScript 런타임에서 사용할 수 있는 WebAssembly/JavaScript 실행 파일로 컴파일합니다. Rust 언어도 Emscripten 대상을 유지 관리합니다.

이 PEP는 2024년 10월 25일 Steering Council에서 승인된 Python 3.14의 Emscripten 지원 Tier 3 추가를 공식화합니다. 목표는 다음과 같습니다.

  1. CPython Emscripten 런타임의 현재 상태를 설명합니다.
  2. Pyodide 런타임의 현재 상태를 설명합니다.
  3. Pyodide 런타임에서 CPython Emscripten 런타임으로 업스트림할 사소한 기능을 식별합니다.

여기에서 식별한 사소한 기능은 모두 PEP 없이 구현할 수 있는 기능입니다. 구현하고자 하는 더 중요한 런타임 기능을 논의하지만, 해당 기능에 대한 결정은 후속 PEP로 미룹니다.

동기

웹 브라우저는 Windows, macOS, Linux 및 모든 스마트폰에서 사용할 수 있는 범용 컴퓨팅 플랫폼입니다.

Pyodide 프로젝트는 2018년부터 Emscripten Python을 지원해 왔습니다. 수십만 명의 학생이 CapytalePyodideU와 같은 프로젝트를 통해 Pyodide로 Python을 배웠습니다. 또한 Python 패키지에서 대화형 문서를 제공하기 위해 Pyodide를 점점 더 많이 사용하고 있습니다. 이는 Emscripten 플랫폼의 중요성과 성숙도를 모두 보여 줍니다.

Emscripten과 WASI는 의미 있는 샌드박싱을 제공하는 지원 플랫폼이기도 합니다.

Emscripten 플랫폼 정보

“Pyodide”와 “Emscripten Python” 비교

이 문서에서는 python/cpython 저장소에서 유지 관리되는 Emscripten Python을 하위 추가 사항 없이 지칭하기 위해 “Emscripten Python”이라는 용어를 사용합니다. Emscripten Python에 있는 기능과 Pyodide에 있는 기능을 대조합니다.

Pyodide는 GitHub에서 유지 관리되며 jsDelivr, npmGitHub releases를 통해 배포됩니다.

Emscripten Python은 배포되지 않지만 개발자 가이드의 지침에 따라 빌드할 수 있습니다

Emscripten 배경

EmscriptenLLVM을 기반으로 하는 C 및 C++ 컴파일러와 링커, 그리고 약간 수정된 musl libc를 기반으로 하는 런타임으로 구성됩니다.

Emscripten은 POSIX 기반 플랫폼입니다. WebAssembly binary formatWebAssembly dynamic linking section을 사용합니다.

emcc 컴파일러는 clang을 감싸는 래퍼입니다. emcc 링커는 wasm-ld를 감싸는 래퍼이며, 이는 LLVM 툴체인의 일부이기도 합니다.

Emscripten의 이식 가능한 C/C++ 코드에 대한 Linux 소스 호환성 지원은 상당히 포괄적이지만, 예상되는 일부 예외는 명시해야 합니다. CPython은 이미 Emscripten으로의 컴파일을 지원하며, 일반적인 Linux 대상에 비해 수정해야 할 사항도 매우 적습니다.

POSIX 준수

Emscripten은 POSIX 플랫폼입니다. 그러나 호출하면 항상 실패하는 POSIX API와 아예 존재하지 않는 POSIX API가 있습니다. 특히 네트워킹 API와 블로킹 I/O에 문제가 있으며, fork()에 대한 지원은 없습니다. Emscripten Portability Guidelines를 참조하십시오.

Emscripten 실행 파일은 스레딩 지원과 함께 링크할 수 있지만, 몇 가지 제한이 따릅니다.

  • 스레딩을 활성화하려면 Spectre-스타일 정보 유출 가능성을 수용한다는 것을 나타내는 특수 보안 헤더와 함께 웹 사이트를 제공해야 합니다. 이러한 헤더는 웹 플랫폼에 매우 익숙하지 않은 사용자에게 사용성 측면의 위험 요소가 됩니다.
  • 실행 파일을 스레딩 및 동적 로더와 함께 링크하면, Emscripten은 동적 로딩과 pthreads를 함께 사용하는 것이 실험적이라는 경고를 출력합니다. 이로 인해 성능 문제가 발생하거나 충돌할 수 있습니다. 이러한 문제를 해결하려면 WebAssembly 표준 작업이 필요할 수 있습니다.

이러한 제한으로 인해 Pyodide는 pthreads가 없는 Python 빌드를 표준으로 제공합니다. 수요가 충분하면 동적 로더가 없는 pthreads 빌드를 나중에 추가할 수 있습니다.

개발 도구

Emscripten 개발 도구는 Linux, Windows, macOS에서 동일하게 잘 지원됩니다. 업스트림 도구에는 다음이 포함됩니다.

  • Emscripten 소프트웨어 개발 키트(emsdk)는 Emscripten 컴파일러 도구 모음(emcc)을 설치하는 데 사용할 수 있습니다.
  • 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 코드를 테스트할 수 있습니다.

cibuildwheelpyodide build를 사용하여 Emscripten을 대상으로 하는 휠 빌드를 지원합니다.

단기적으로 Pyodide의 패키징 도구는 Pyodide 저장소에 계속 유지됩니다. 장기적으로 Pyodide의 패키징 도구를 어디에 둘지는 아직 정해지지 않은 문제입니다. 합리적인 선택지는 pyodide 조직 아래에 계속 두거나 GitHub의 pypa 조직으로 옮기는 것입니다.

Emscripten 애플리케이션 수명 주기

Emscripten “바이너리”는 .mjs 파일과 .wasm 파일 한 쌍으로 구성됩니다. .wasm 파일에는 컴파일된 모든 C/C++/Rust 코드가 포함됩니다. .mjs 파일에는 런타임을 설정하고, .wasm 파일을 찾고, 컴파일하고, 인스턴스화하고, main() 함수를 호출하며, 종료 시 런타임을 종료하는 수명 주기 코드가 포함됩니다. 또한 파일 시스템, 동적 로더, JavaScript 런타임의 추가 기능을 C 코드에 노출하는 모든 로직을 비롯한 모든 시스템 호출의 구현도 포함합니다.

.mjs 파일은 런타임을 부트스트랩하고 main() 함수를 호출하며 C 함수를 호출하는 데 사용할 수 있는 API 객체를 반환하는 단일 bootstrapEmscriptenExecutable() JavaScript 함수를 내보냅니다. 이 함수를 호출할 때마다 자체적인 별도 주소 공간을 갖는 런타임의 완전하고 독립적인 복사본이 생성됩니다.

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는 동결 모듈이므로 이러한 문제를 방지합니다. 다른 모든 방식은 JavaScript를 사용하여 표준 라이브러리를 설정하는 방식으로 부트스트래핑 문제를 해결합니다.

서드파티 패키지

필요한 패키지를 Emscripten 파일 시스템에서 사용할 수 있도록 만드는 것도 필요합니다. 현재 Emscripten CPython은 패키지를 지원하지 않습니다. Pyodide는 패키지에 대해 두 가지 서로 다른 방식을 사용합니다.

  • 브라우저에서 Pyodide는 휠을 다운로드하고 MEMFS site-packages 디렉터리에 압축을 풉니다. 그런 다음 휠의 모든 동적 라이브러리를 미리 로드합니다. 모든 패키지를 다운로드하고 설치하는 작업은 런타임이 시작될 때마다 다시 수행됩니다.
  • Pyodide python CLI 진입점은 Python을 부트스트랩하기 전에 호스트 파일 시스템 전체를 NODEFS 디렉터리로 마운트합니다. 이를 통해 일반적인 가상 환경 메커니즘이 작동할 수 있습니다. Pyodide 가상 환경에는 패치된 pip 사본과 사용자 지정 pip.conf가 포함되어 있으므로 pip가 Pyodide 휠을 설치합니다. 시작할 때 Pyodide python CLI는 site-packages 디렉터리에 있는 모든 Emscripten 동적 라이브러리를 미리 로드합니다.

콘솔 및 대화형 사용

stdin은 기본적으로 항상 EOF를 반환하며, stdoutstderr는 각각 console.logconsole.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_sizeprocess.stdout.rowsprocess.stdout.columns와 일관된 결과를 반환하도록 보장합니다. Pyodide는 현재 스택 전환 stdin을 지원하지 않습니다.

현재 Emscripten Python Node.js 실행기는 Emscripten이 제공하는 기본 I/O를 사용합니다. 웹 예제는 stdinAtomics를 사용하고 사용자 지정 stdoutstderr핸들러를 갖지만, 이러한 핸들러는 바람직하지 않은 줄 버퍼링 동작을 보입니다. Pyodide의 표준 스트림 동작을 업스트림에 반영할 예정입니다.

장기적으로는 스택 전환 stdin장치를 구현하고자 하지만, 이는 이 PEP의 범위를 벗어납니다.

트랩 및 포착되지 않은 예외

WebAssembly 트랩, 처리되지 않은 JavaScript 예외 또는 포착되지 않은 WebAssembly throw 명령어가 발생하면 C 런타임 상태가 손상된 것으로 간주합니다.

다른 플랫폼과 달리 트랩이나 libc 런타임의 복구할 수 없는 손상이 발생했을 때 실행 파일을 종료할 운영 체제가 없습니다. 트레이스백을 출력하고 메모리를 덤프하며 충돌 디버깅에 도움이 되는 기타 작업을 수행하는 자체 코드를 제공해야 합니다. JavaScript API를 노출하는 경우, 복구할 수 없는 충돌 이후 해당 API를 비활성화하여 하위 사용자가 일관되지 않은 상태의 Python 런타임을 관찰하지 못하도록 해야 합니다.

치명적 오류를 감지하기 위해 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
  • sockets

이러한 기능을 필요로 하는 모든 기능도 마찬가지입니다.

다음 항목은 제거된 termios 모듈에 의존하기 때문에 존재하지만 가져올 수 없습니다:

  • pty
  • tty

플랫폼 식별

sys.platform"emscripten"을 반환합니다. Emscripten은 Linux와 호환되도록 시도하지만 차이가 충분히 크므로 별도의 이름을 사용하는 것이 타당합니다. 이는 os.uname()의 반환값과 일치합니다.

또한 sys._emscripten_info도 있으며, 여기에는 Emscripten 버전과 런타임이 포함됩니다. 런타임은 브라우저에서는 navigator.userAgent이고 Node.js에서는 "Node js" + process.version입니다.

시그널 지원

WebAssembly는 시그널을 네이티브 방식으로 지원하지 않습니다. 또한 non-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 표준의 Section 6.3.2.3, paragraph 8은 다음과 같이 규정합니다:

한 유형의 함수에 대한 포인터는 다른 유형의 함수에 대한 포인터로 변환한 다음 다시 되돌릴 수 있으며, 그 결과는 원래 포인터와 같아야 합니다. 변환된 포인터를 사용하여 가리키는 유형과 호환되지 않는 유형의 함수를 호출하면 동작은 정의되지 않습니다.

그러나 대부분의 플랫폼은 동일하게 동작합니다. 함수가 너무 많은 인자와 함께 호출되면 초과 인자는 무시되며, 너무 적은 인자와 함께 호출되면 나머지 인자는 쓰레기 값으로 채워집니다.

반면 WebAssembly 사양에서는 잘못된 시그니처로 함수를 호출하면 트랩이 발생하도록 정의합니다 (see step 18 in the execution of call_indirect).

Python 확장 모듈에서는 함수를 다른 시그니처로 캐스팅한 다음 해당 시그니처로 호출하는 일이 흔합니다. 예를 들어 많은 C 확장에서는 METH_NOARGS 함수를 인자를 0개 또는 1개 받도록 정의합니다. 인터프리터는 두 개의 인자와 함께 이를 호출하며, 첫 번째 인자는 Python 모듈 객체이고 두 번째 인자는 항상 NULL입니다. 이러한 확장 모듈이 소스 코드를 변경하지 않고 동작하도록 하려면 특별한 처리가 필요합니다.

처음에는 JavaScript를 호출한 다음 JavaScript에서 함수 포인터를 호출하도록 하여 이 문제를 해결했습니다. JavaScript에서 WebAssembly 함수를 호출할 때 누락된 인자는 0으로 처리되고 초과 인자는 무시됩니다 (see step 7 here). 이 방식은 동작하지만 느리고 스택 전환을 방해한다는 단점이 있습니다. JavaScript 프레임을 거쳐 스택을 전환할 수 없기 때문입니다.

wasm-gc의 ref.test명령어를 사용하면 함수 포인터의 유형을 조회하고 인자 목록을 수동으로 수정할 수 있습니다.

wasm-gc는 WebAssembly 런타임에서 비교적 새로운 기능이므로, 가능하면 wasm-gc 기반 함수 포인터 캐스트 트램펄린을 사용하고 그렇지 않으면 JS 트램펄린으로 대체하려고 합니다. 스택 전환을 지원하는 모든 JavaScript 런타임은 wasm-gc도 지원하므로, 이를 통해 스택 전환을 지원하는 모든 플랫폼 런타임에서 스택 전환이 작동하도록 보장할 수 있습니다. 한 가지 문제는 iOS 18이 wasm-gc의 손상된 구현을 제공하므로 이를 별도로 처리해야 한다는 점입니다.

See here for the full implementation details.

함수 포인터 캐스트 처리는 cpython에 완전히 구현되어 있습니다. Pyodide는 업스트림과 정확히 동일한 코드를 사용합니다.

CI 리소스

상당히 최신 버전의 Node.js가 설치된 모든 Linux에서 Pyodide를 빌드하고 테스트할 수 있습니다. Anaconda는 Russell Keith-Magee가 유지 관리하는 Emscripten 빌드봇을 실행할 물리적 하드웨어를 제공하겠다고 제안했습니다.

CPython은 현재 GitHub Actions에서 Tier 3 플랫폼을 테스트하지 않지만, 향후 변경되면 해당 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는 Emscripten용 Python 패키지를 매우 많이 빌드할 수 있지만, 매우 복잡합니다. 이상적으로는 Python 패키징 생태계에 크로스 빌드를 위한 표준이 있어야 합니다. 이는 어려운 장기 프로젝트입니다. 특히 패키징 시스템이 복잡하고 크로스 컴파일이 일어나지 않을 것이라는 가정에서 처음부터 설계되었기 때문입니다.

업스트림에 반영할 Pyodide 런타임 기능

이는 이 PEP와 Python 3.14 개발 주기의 범위에는 포함되지 않지만, 향후 업스트림에 반영하고자 하는 Pyodide 런타임 기능 모음입니다.

부트스트래핑을 위한 JavaScript API

현재 Python 부트스트래핑을 위한 안정적인 API는 제공하지 않습니다. 대신 one collection of settings for the Node.js CLI entrypointa separate collection of settings for the browser demo를 사용합니다.

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 FFI가 있어야 가능하다는 논리적 의존성이 있습니다. JavaScript 이벤트 루프에 작업을 예약하는 유일한 방법은 JavaScript를 호출하는 것이기 때문입니다.

호환성이 떨어지는 한 가지 원인은 JavaScript 격리 영역 내부에서는 이벤트 루프의 수명 주기를 제어할 수 없다는 점입니다. 이로 인해 asyncio.run()및 이와 유사한 기능이 작동하지 않습니다.

스택 전환을 사용하면 “동기식” Python 프레임으로부터 코루틴을 만드는 것도 가능합니다. 이러한 스택 전환 코루틴은 일반 Python 코루틴과 동일한 이벤트 루프에서 예약되며 완전히 재진입 가능합니다. 이는 Pyodide에 완전히 구현되어 있습니다.

하위 호환성

새로운 플랫폼을 추가해도 CPython 자체에는 하위 호환성 문제가 발생하지 않습니다. 그러나 Pyodide 사용자에게는 일부 하위 호환성 영향이 있을 수 있습니다. Pyodide를 사용하는 기존 사용자가 매우 많으므로, Pyodide의 기능을 Python으로 업스트림할 때 하위 호환성 문제를 최소화하도록 주의하는 것이 중요합니다. 또한 Pyodide가 다운스트림에서 이를 더 완전한 버전으로 대체할 수 있도록, 부분적으로 업스트림된 기능을 비활성화하는 방법도 필요합니다.

보안 관련 영향

새로운 플랫폼을 추가해도 새로운 보안 관련 영향은 발생하지 않습니다.

Emscripten과 WASI는 샌드박싱을 제공하는 지원 플랫폼이기도 합니다. 사용자가 신뢰할 수 없는 Python 코드나 신뢰할 수 없는 Python 확장 모듈을 실행하려는 경우, Emscripten은 이를 안전하게 실행할 방법을 제공합니다.

교육 방법

이 PEP와 관련된 교육 요구 사항은 두 개발자 그룹과 관련이 있습니다.

첫째, 웹 개발자는 Python을 빌드하여 웹사이트에서 사용하는 방법과 자신의 Python 코드 및 지원 패키지를 함께 사용하고, 런타임에 이들을 모두 사용하는 방법을 알아야 합니다. 문서에서는 기존 Windows 임베더블 패키지와 유사한 형식으로 이를 다룹니다. 단기적으로는 개발자가 가능하면 Pyodide를 사용하도록 권장합니다.

참조 구현

Pyodide입니다.