Skip to content

Commit f185d34

Browse files
committed
Python workers: Replace string Pyodide patches with ast patches
1 parent 6a5e11b commit f185d34

5 files changed

Lines changed: 492 additions & 130 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
"devDependencies": {
1010
"@eslint/js": "^10.0.1",
1111
"@types/node": "^25.9.5",
12+
"acorn": "^8.18.0",
13+
"astring": "^1.9.0",
1214
"capnp-es": "0.0.16",
1315
"chrome-remote-interface": "^0.34.0",
1416
"esbuild": "^0.27.7",

pnpm-lock.yaml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/pyodide/BUILD.bazel

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
load("@aspect_rules_js//js:defs.bzl", "js_binary")
12
load("@bazel_skylib//rules:write_file.bzl", "write_file")
23
load("@rules_python//python:defs.bzl", "py_binary", "py_test")
34
load("//:build/python_metadata.bzl", "BUNDLE_VERSION_INFO")
@@ -19,6 +20,18 @@ py_binary(
1920
visibility = ["//visibility:public"],
2021
)
2122

23+
# Build-time tool that applies workerd's patches to Pyodide's pyodide.asm.js / pyodide.asm.mjs as
24+
# AST rewrites. Used by python_bundles() in helpers.bzl.
25+
js_binary(
26+
name = "patch_pyodide_asm",
27+
data = [
28+
"//:node_modules/acorn",
29+
"//:node_modules/astring",
30+
],
31+
entry_point = "patch_pyodide_asm.mjs",
32+
visibility = ["//visibility:public"],
33+
)
34+
2235
pyodide_extra()
2336

2437
python_bundles()

src/pyodide/helpers.bzl

Lines changed: 32 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild")
2+
load("@aspect_rules_js//js:defs.bzl", "js_run_binary")
23
load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
34
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
45
load("@capnp-cpp//src/capnp:cc_capnp_library.bzl", "cc_capnp_library")
@@ -30,6 +31,18 @@ def _copy_to_generated(src, version = None, out_name = None, name = None):
3031
name += "@" + version
3132
copy_file(name = name, src = src, out = _out_path(out_name, version))
3233

34+
def _bin_relative_path(path):
35+
# js_run_binary runs its tool with the bin directory as the working directory. Returns the
36+
# path, relative to that directory, of a file in the current package. Works whether this
37+
# package is in the main repository or (as when workerd is a dependency) an external one.
38+
package = native.package_name()
39+
if package:
40+
path = package + "/" + path
41+
repo = native.repo_name()
42+
if repo:
43+
path = "external/" + repo + "/" + path
44+
return path
45+
3346
def _copy_and_capnp_embed(src):
3447
out_name = _out_name(src)
3548
_copy_to_generated(src)
@@ -159,132 +172,6 @@ def pyodide_static():
159172
tsconfig_json = "tsconfig.json",
160173
)
161174

162-
_PRELUDE = """
163-
import {
164-
addEventListener,
165-
getRandomValues,
166-
location,
167-
monotonicDateNow,
168-
newWasmModule,
169-
patchedApplyFunc,
170-
patchedLoadLibData,
171-
reportUndefinedSymbolsPatched,
172-
wasmInstantiate,
173-
patched_PyEM_CountFuncParams,
174-
} from "pyodide-internal:pool/builtin_wrappers";
175-
"""
176-
177-
# pyodide.asm.mjs patches
178-
# TODO: all of these should be fixed by linking our own Pyodide or by upstreaming.
179-
_REPLACEMENTS_COMMON = [
180-
[
181-
"new WebAssembly.Module",
182-
"newWasmModule",
183-
],
184-
[
185-
"WebAssembly.instantiate",
186-
"wasmInstantiate",
187-
],
188-
[
189-
"Date.now",
190-
"monotonicDateNow",
191-
],
192-
[
193-
"reportUndefinedSymbols()",
194-
"reportUndefinedSymbolsPatched(Module)",
195-
],
196-
[
197-
"crypto.getRandomValues(",
198-
"getRandomValues(Module, ",
199-
],
200-
[
201-
# Direct eval disallowed in esbuild, see:
202-
# https://esbuild.github.io/content-types/#direct-eval
203-
"eval(func)",
204-
"(() => {throw new Error('Internal Emscripten code tried to eval, this should not happen, please file a bug report with your requirements.txt file\\'s contents')})()",
205-
],
206-
[
207-
"eval(data)",
208-
"(() => {throw new Error('Internal Emscripten code tried to eval, this should not happen, please file a bug report with your requirements.txt file\\'s contents')})()",
209-
],
210-
[
211-
"eval(UTF8ToString(ptr))",
212-
"(() => {throw new Error('Internal Emscripten code tried to eval, this should not happen, please file a bug report with your requirements.txt file\\'s contents')})()",
213-
],
214-
# Dynamic linking patches:
215-
# library lookup
216-
[
217-
"function loadLibData(){",
218-
"""
219-
function loadLibData(){
220-
var libData = patchedLoadLibData(Module, libName, flags.rpath);
221-
return flags.loadAsync ? Promise.resolve(libData) : libData;
222-
}
223-
function dummiedOutOrigLoadLibData(){
224-
""",
225-
],
226-
# for ensuring memory base of dynlib is stable when restoring snapshots
227-
[
228-
"getMemory(",
229-
"Module.getMemoryPatched(Module, libName, ",
230-
],
231-
[
232-
"function _PyEM_CountFuncParams(func){",
233-
"function _PyEM_CountFuncParams(func){ return patched_PyEM_CountFuncParams(Module, func);",
234-
],
235-
[
236-
"var tableBase=metadata.tableSize?wasmTable.length:0;",
237-
"var tableBase=metadata.tableSize?wasmTable.length:0;" +
238-
"Module.snapshotDebug && console.log('loadWebAssemblyModule', libName, memoryBase, tableBase);",
239-
],
240-
# to ensure we report every fatal error, not just the first one
241-
[
242-
'console.error("Recursive call to fatal_error. Inner error was:");',
243-
'console.error("Recursive call to fatal_error. Inner error was:");\n' +
244-
"try { API.on_fatal?.(e); } catch(e2) { console.error(e2); }\n",
245-
],
246-
]
247-
248-
_REPLACEMENTS_COMMON_0_26_0_28 = [
249-
# for 0.28.2 or earlier, pyodide.asm.js was a commonjs module
250-
[
251-
# Convert pyodide.asm.js into an es6 module.
252-
# When we link our own we can pass `-sES6_MODULE` to the linker and it will do this for us
253-
# automatically.
254-
"var _createPyodideModule",
255-
_PRELUDE + "export const _createPyodideModule",
256-
],
257-
[
258-
"globalThis._createPyodideModule = _createPyodideModule;",
259-
"",
260-
],
261-
# to fix RPC, applies https://github.com/pyodide/pyodide/commit/8da1f38f7
262-
[
263-
"nullToUndefined(func.apply(",
264-
"nullToUndefined(patchedApplyFunc(API, func, ",
265-
],
266-
[
267-
"nullToUndefined(Function.prototype.apply.apply",
268-
"nullToUndefined(API.config.jsglobals.Function.prototype.apply.apply",
269-
],
270-
]
271-
272-
_REPLACEMENTS_COMMON_314 = [
273-
# for 314 or later, pyodide.asm.mjs is es6 module
274-
[
275-
"export default _createPyodideModule;",
276-
# still expose _createPyodideModule for compatibility (import { _createPyodideModule })
277-
_PRELUDE + "export default _createPyodideModule; export { _createPyodideModule };",
278-
],
279-
]
280-
281-
_REPLACEMENTS = {
282-
"0.26.0a2": _REPLACEMENTS_COMMON + _REPLACEMENTS_COMMON_0_26_0_28,
283-
"0.28.2": _REPLACEMENTS_COMMON + _REPLACEMENTS_COMMON_0_26_0_28,
284-
"314.0.4": _REPLACEMENTS_COMMON + _REPLACEMENTS_COMMON_314,
285-
"314.0.6": _REPLACEMENTS_COMMON + _REPLACEMENTS_COMMON_314,
286-
}
287-
288175
def _python_bundle(version, *, pyodide_asm_wasm = None, pyodide_asm_mjs = None, python_stdlib_zip = None, emscripten_setup_override = None):
289176
pyodide_package = "@pyodide-%s//" % version
290177
if not pyodide_asm_wasm:
@@ -304,11 +191,26 @@ def _python_bundle(version, *, pyodide_asm_wasm = None, pyodide_asm_mjs = None,
304191

305192
_copy_to_generated(python_stdlib_zip, version, out_name = "python_stdlib.zip")
306193

307-
expand_template(
194+
# Apply workerd's patches to the Emscripten-generated module (see patch_pyodide_asm.mjs).
195+
# The upstream file is first copied into this package so that both the tool's input and
196+
# output can be addressed relative to the bin directory it runs in.
197+
upstream_asm_mjs = _out_path("pyodide.asm.upstream.mjs", version)
198+
patched_asm_mjs = _out_path("pyodide.asm.mjs", version)
199+
_copy_to_generated(pyodide_asm_mjs, version, out_name = "pyodide.asm.upstream.mjs")
200+
js_run_binary(
308201
name = "pyodide.asm.mjs@rule@" + version,
309-
out = _out_path("pyodide.asm.mjs", version),
310-
substitutions = dict(_REPLACEMENTS[version]),
311-
template = pyodide_asm_mjs,
202+
srcs = [upstream_asm_mjs],
203+
outs = [patched_asm_mjs],
204+
args = [
205+
"--version",
206+
version,
207+
"--input",
208+
_bin_relative_path(upstream_asm_mjs),
209+
"--output",
210+
_bin_relative_path(patched_asm_mjs),
211+
],
212+
mnemonic = "PatchPyodideAsm",
213+
tool = Label("//src/pyodide:patch_pyodide_asm"),
312214
)
313215

314216
js_file(

0 commit comments

Comments
 (0)