Skip to content

Commit f60f9dd

Browse files
committed
Implement native clang launcher
Implement Phase 1 of the native launcher design document. Provides high-performance native C++ compiler drivers (emcc, em++) using C++20 that directly invoke Clang for compile steps (-c, -S, -E) and seamlessly fall back to python (emcc.py / em++.py) for link steps or complex post-processing options. Output executables are placed in ./bin using CMake and Ninja. Adds CI matrix testing for Linux, macOS, and Windows. These new binaries are currently 100% optional and the python versions can still be used without building them. Tested using `embuilder build libc --force` on Linux CI (compiling 1,075 files sequentially with `EMCC_CORES=1`, `EMCC_USE_NINJA=0`, and `EMCC_BATCH_BUILD=0`): | Metric | Before (Python Baseline) | After (Native Launcher) | Improvement | | :--- | :---: | :---: | :---: | | **Total Time** | **181.96 s** (3m 2s) | **64.82 s** (1m 5s) | **-117.14 s (64.4% faster)** | | **Time per File** | 169.3 ms | 60.3 ms | **-109.0 ms / invocation** | | **Speedup Factor** | 1.0x | **2.81x** | — | **Key Takeaways:** * **64.4% Reduction in Build Time:** Bypassing Python interpreter startup shaves off **~109.0 ms of overhead per compiler invocation** on Linux CI (a **2.81x speedup**). * **Windows Impact:** Because process spawning and `python.exe` startup carry significantly higher overhead on Windows than on Linux, the percentage speedup on Windows CI is expected to be even larger. See: #26453
1 parent 86ea17d commit f60f9dd

30 files changed

Lines changed: 2742 additions & 414 deletions

‎.circleci/config.yml‎

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ commands:
5353
bootstrap:
5454
description: "bootstrap"
5555
steps:
56+
- run:
57+
name: Install dependencies (Linux)
58+
command: |
59+
if command -v apt-get >/dev/null 2>&1; then
60+
apt-get install -q -y cmake ninja-build
61+
fi
5662
- run: "$EMSDK_PYTHON ./bootstrap.py"
5763
pip-install:
5864
description: "pip install"
@@ -1182,30 +1188,6 @@ jobs:
11821188
steps:
11831189
- test-sockets-chrome
11841190

1185-
build-windows-launcher:
1186-
executor:
1187-
name: win/server-2022
1188-
shell: bash.exe -eo pipefail
1189-
steps:
1190-
- checkout
1191-
- run:
1192-
name: "build pylauncher"
1193-
shell: cmd.exe
1194-
command: .circleci\setup_vs2022.bat && cd tools\pylauncher && call build.bat
1195-
- store_artifacts:
1196-
path: tools/pylauncher/pylauncher.exe
1197-
destination: pylauncher.exe
1198-
- install-emsdk
1199-
- pip-install:
1200-
python: "$EMSDK_PYTHON"
1201-
- run:
1202-
name: "create_entry_points"
1203-
command: $EMSDK_PYTHON tools/maint/create_entry_points.py --exe-files
1204-
- run:
1205-
name: "crossplatform tests"
1206-
command: test/runner.exe core0.test_hello_world
1207-
1208-
12091191
# windows and mac do not have separate build and test jobs, as they only run
12101192
# a limited set of tests; it is simpler and faster to do it all in one job.
12111193
test-windows:
@@ -1233,10 +1215,6 @@ jobs:
12331215
EMTEST_BROWSER: "0"
12341216
steps:
12351217
- checkout
1236-
- run:
1237-
name: Build launcher
1238-
command: call .circleci\setup_vs2019.bat && cd tools\pylauncher && call build.bat
1239-
shell: cmd.exe
12401218
- run:
12411219
name: Install packages
12421220
command: |
@@ -1257,9 +1235,7 @@ jobs:
12571235
- upload-test-results
12581236
- run:
12591237
name: "check clean"
1260-
command: |
1261-
git checkout tools/pylauncher
1262-
$EMSDK_PYTHON test/check_clean.py
1238+
command: $EMSDK_PYTHON test/check_clean.py
12631239

12641240
test-mac-arm64:
12651241
executor: mac-arm64
@@ -1357,7 +1333,6 @@ workflows:
13571333
- test-node-compat
13581334
- test-windows
13591335
- test-windows-browser-firefox
1360-
- build-windows-launcher
13611336
- test-mac-arm64:
13621337
requires:
13631338
- build-linux

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ jobs:
6666
echo "Be sure that you have installed the current emsdk version. See test/emsdk_version.txt ($(cat test/emsdk_version.txt))."
6767
exit 1
6868
fi
69+
- name: Check emcc_native generated settings
70+
run: |
71+
./tools/emcc_native/gen_settings.py --check
6972
7073
clang-format-diff:
7174
# This job is disabled until we can make it more precise
@@ -83,22 +86,3 @@ jobs:
8386
sudo apt-get install clang-format-19
8487
sudo update-alternatives --install /usr/bin/git-clang-format git-clang-format /usr/bin/git-clang-format-19 100
8588
- run: tools/maint/clang-format-diff.sh origin/$GITHUB_BASE_REF
86-
87-
build-pylauncher-arm64:
88-
name: Build pylauncher (ARM64)
89-
runs-on: windows-11-arm
90-
steps:
91-
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
92-
- name: Setup MSVC
93-
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
94-
with:
95-
arch: arm64
96-
- name: Build pylauncher
97-
run: |
98-
cd tools\pylauncher
99-
call build.bat arm64
100-
shell: cmd
101-
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
102-
with:
103-
name: pylauncher-arm64.exe
104-
path: tools/pylauncher/pylauncher-arm64.exe

‎Makefile‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,18 @@ install:
1313
./tools/install.py $(DESTDIR)
1414
npm install --omit=dev --prefix $(DESTDIR)
1515

16+
out/build_emcc_native/native_tests emcc_native:
17+
cmake -B out/build_emcc_native -S tools/emcc_native -G Ninja -DCMAKE_BUILD_TYPE=Release
18+
cmake --build out/build_emcc_native --config Release
19+
cmake --install out/build_emcc_native --config Release
20+
21+
test: out/build_emcc_native/native_tests
22+
ctest --test-dir out/build_emcc_native --output-on-failure
23+
1624
# Create an distributable archive of emscripten suitable for use
1725
# by end users. This archive excludes node_modules as it can include native
1826
# modules which can't be safely pre-packaged.
1927
$(DISTFILE): install
2028
tar cf $@ --exclude=node_modules -C `dirname $(DESTDIR)` `basename $(DESTDIR)`
2129

22-
.PHONY: dist install
30+
.PHONY: dist install emcc_native test

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ There are two primary ways to install Emscripten:
3939
The easiest way to get started is by using the Emscripten SDK. Follow the instructions on the [downloads page](https://emscripten.org/docs/getting_started/downloads.html) to install it.
4040

4141
2. **From a Git Checkout (Manual Installation)**
42-
If you have cloned the repository from Git, you can install the dependencies manually and then run the bootstrap script:
42+
If you have cloned the repository from Git, run `bootstrap.py` to build the native launcher (`emcc_native`) and set up dependencies:
4343
```bash
4444
./bootstrap.py
4545
```
46-
For more details, see the [developer guide](https://emscripten.org/docs/contributing/developers_guide.html).
46+
Building `emcc_native` requires CMake 3.20+ and a C++20 host compiler toolchain. Alternatively, setting `EMCC_NATIVE=0` in your environment before running `./bootstrap.py` will generate legacy Python launcher scripts (e.g. `.bat` / `.ps1` files on Windows) via `./tools/maint/create_entry_points.py`. For more details, see the [developer guide](https://emscripten.org/docs/contributing/developers_guide.html).
4747

4848
## Using the compiler
4949

‎bootstrap.py‎

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,22 @@ def run_cmd(cmd):
9595
subprocess.run(cmd, check=True, text=True, encoding='utf-8', cwd=utils.path_from_root())
9696

9797

98+
def build_emcc_native():
99+
build_dir = utils.path_from_root('out/build_emcc_native')
100+
source_dir = utils.path_from_root('tools/emcc_native')
101+
cmd = ['cmake', '-B', build_dir, source_dir, '-DCMAKE_BUILD_TYPE=Release']
102+
if not utils.WINDOWS and shutil.which('ninja'):
103+
cmd.extend(['-G', 'Ninja'])
104+
run_cmd(cmd)
105+
run_cmd(['cmake', '--build', build_dir, '--config', 'Release'])
106+
run_cmd(['cmake', '--install', build_dir, '--config', 'Release'])
107+
108+
98109
actions = [
99110
('npm packages', [
100111
'package.json',
101112
'package-lock.json',
102113
], ['npm', 'ci']),
103-
('create entry points', [
104-
'tools/maint/create_entry_points.py',
105-
'tools/pylauncher/pylauncher.exe',
106-
'tools/maint/run_python.bat',
107-
'tools/maint/run_python.sh',
108-
'tools/maint/run_python.ps1',
109-
], [sys.executable, 'tools/maint/create_entry_points.py']),
110114
('git submodules', [
111115
'test/third_party/posixtestsuite/',
112116
'test/third_party/googletest',
@@ -118,6 +122,28 @@ def run_cmd(cmd):
118122
], maybe_install_hooks),
119123
]
120124

125+
if os.environ.get('EMCC_NATIVE') == '0':
126+
actions.append(('legacy entry points', [
127+
'tools/maint/create_entry_points.py',
128+
'tools/maint/run_python.sh',
129+
'tools/maint/run_python.bat',
130+
'tools/maint/run_python.ps1',
131+
'tools/maint/run_python_compiler.sh',
132+
'tools/maint/run_python_compiler.bat',
133+
'tools/maint/run_python_compiler.ps1',
134+
], [sys.executable, utils.path_from_root('tools/maint/create_entry_points.py')]))
135+
else:
136+
actions.append(('build emcc_native', [
137+
'tools/emcc_native/CMakeLists.txt',
138+
'tools/emcc_native/main.cpp',
139+
'tools/emcc_native/driver.cpp',
140+
'tools/emcc_native/driver.h',
141+
'tools/emcc_native/exec.cpp',
142+
'tools/emcc_native/exec.h',
143+
'tools/emcc_native/config.cpp',
144+
'tools/emcc_native/config.h',
145+
], build_emcc_native))
146+
121147

122148
def main(args):
123149
parser = argparse.ArgumentParser(description=__doc__)

‎docs/design/03-native-clang-frontend.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Design Doc: Native Launcher / Clang Frontend
22

3-
- **Status**: Draft
3+
- **Status**: Phase 1 Completed
44
- **Bug**: https://github.com/emscripten-core/emscripten/issues/26453
55

66
## Context

‎site/source/docs/building_from_source/index.rst‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@ Building Emscripten from Source
77
Building Emscripten yourself is an alternative to getting binaries using the
88
emsdk.
99

10-
Emscripten itself is written in Python and JavaScript so it does not need to be
11-
compiled. However, after checkout you will need to run the top level
12-
``bootstrap.py`` script before the toolchain is usable. This performs
13-
various steps including ``npm install`` and the creation of compiler entry
14-
points (e.g. `.bat` files on windows).
10+
Emscripten itself is primarily written in Python and JavaScript. However,
11+
after checkout you will need to run the top-level ``bootstrap.py`` script
12+
before the toolchain is usable. This performs various steps including ``npm
13+
install`` and building the native compiler frontend launcher (``emcc_native``),
14+
which provides high-performance ``emcc`` and ``em++`` binaries.
15+
16+
Building ``emcc_native`` requires CMake 3.20+ and a C++20 host compiler
17+
toolchain (which you already need for building LLVM and Binaryen). If you prefer
18+
not to perform a native build, setting ``EMCC_NATIVE=0`` in your environment
19+
before running ``./bootstrap.py`` instructs it to generate legacy Python launcher
20+
scripts (e.g., ``.bat`` / ``.ps1`` files on Windows) via
21+
``./tools/maint/create_entry_points.py``.
1522

1623
Emscripten comes with its own versions of some C/C++ system libraries which
1724
``emcc`` builds automatically as and when needed (in the emsdk builds, these are

‎site/source/docs/building_from_source/toolchain_what_is_needed.rst‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ In general a complete Emscripten environment requires the following tools. First
3939
Compiler toolchain
4040
------------------
4141

42-
When building LLVM and Binaryen from source code, whether "manually" or using the SDK, you will need a *compiler toolchain*:
42+
When building Emscripten from source (via ``bootstrap.py`` to build
43+
``emcc_native``), or when building LLVM and Binaryen from source, you will need
44+
a C++20 host *compiler toolchain* and CMake 3.20+:
4345

4446
- Windows: You will need `Visual Studio <https://visualstudio.microsoft.com/>`_ (2019 or above) and `cmake <http://www.cmake.org/cmake/resources/software.html>`_ (3.20 or above).
4547

‎site/source/docs/contributing/developers_guide.rst‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,22 @@ interested in helping out!
1616
Setting up
1717
==========
1818

19-
For contributing to core Emscripten code, such as ``emcc.py``, you don't need to
20-
build any binaries as ``emcc.py`` is in Python, and the core JS generation is
21-
in JavaScript. You do still need binaries for LLVM and Binaryen, which you can
22-
get using the emsdk.
19+
When setting up a Git checkout of Emscripten, run the top-level ``bootstrap.py``
20+
script to set up dependencies (such as ``npm install``) and build the native
21+
compiler frontend launcher (``emcc_native``):
22+
23+
::
24+
25+
./bootstrap.py
26+
27+
Building ``emcc_native`` requires CMake 3.20+ and a C++20 host compiler
28+
toolchain. If you prefer not to build the native launcher, setting ``EMCC_NATIVE=0``
29+
in your environment before running ``./bootstrap.py`` will generate legacy Python
30+
launcher scripts (e.g., ``.bat`` / ``.ps1`` files on Windows) via
31+
``./tools/maint/create_entry_points.py``.
32+
33+
For LLVM and Binaryen binaries, you don't need to build them from source if you
34+
are only contributing to Emscripten; you can get them using the emsdk.
2335

2436
If you want to contribute back to Emscripten, it is recommended that you install
2537
the precise version of the emsdk binaries that are used by Emscripten CI when

‎test/test_other.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12278,9 +12278,9 @@ def test_xclang_flag(self):
1227812278
self.run_process([EMCC, '-c', '-o', 'out.o', '-Xclang', '-include', '-Xclang', 'foo.h', test_file('hello_world.c')])
1227912279

1228012280
def test_emcc_size_parsing(self):
12281-
create_file('foo.h', ' ')
12282-
self.assert_fail([EMCC, '-sTOTAL_MEMORY=X', 'foo.h'], 'error: invalid byte size `X`. Valid suffixes are: kb, mb, gb, tb')
12283-
self.assert_fail([EMCC, '-sTOTAL_MEMORY=11PB', 'foo.h'], 'error: invalid byte size `11PB`. Valid suffixes are: kb, mb, gb, tb')
12281+
create_file('foo.c', ' ')
12282+
self.assert_fail([EMCC, '-sTOTAL_MEMORY=X', 'foo.c'], 'error: invalid byte size `X`. Valid suffixes are: kb, mb, gb, tb')
12283+
self.assert_fail([EMCC, '-sTOTAL_MEMORY=11PB', 'foo.c'], 'error: invalid byte size `11PB`. Valid suffixes are: kb, mb, gb, tb')
1228412284

1228512285
def test_native_call_before_init(self):
1228612286
self.set_setting('ASSERTIONS')

0 commit comments

Comments
 (0)