Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/wapython
Path: blob/main/core/dylink/src/libpython.ts
1067 views
1
#!/usr/bin/env node
2
3
/*
4
Code that exports function pointers for everything in the Python C API.
5
The resulting .c code should be included in your main wasm binary so that
6
dynamic libraries that are Python extension modules can actually work.
7
8
**This is important:** It makes function calls from extension modules
9
to the Python/C API thousands of times faster, and actually work in
10
general, rather than randomly segfaulting.
11
12
Regarding coverage, we ensure that all functions in the stable ABI are
13
available by reading from Doc/data/stable_abi.dat in the CPython build.
14
We also add a number of additional non-public internal functions that
15
were specifically needed for extension modules that are part of the core
16
Python distribution, partly because we built those as dynamic modules in
17
many cases to minimize the core python.wasm web assembly bundle.
18
19
*/
20
21
import { join } from "path";
22
import spawnAsync from "await-spawn";
23
import wasmExport, { alias } from "./wasm-export";
24
import { readFileSync, statSync } from "fs";
25
26
// I tediously made this list.
27
let omit =
28
"Py_UTF PyOS_CheckStack PyObject_AsCharBuffer PyThread_get_thread_native_id PyUnicode_AsMBCSString PyUnicode_DecodeCodePageStateful PyUnicode_DecodeMBCS PyUnicode_DecodeMBCSStateful PyUnicode_EncodeCodePage _PyAST_Compile _PyAccu_Accumulate _PyAccu_Destroy _PyAccu_Finish _PyAccu_FinishAsList _PyAccu_Init _PyArena_AddPyObject _PyArena_Free _PyArena_Malloc _PyArena_New _PyArgv_AsWstrList _PyCode_New _PyCode_Validate _PyConfig_AsDict _PyConfig_FromDict _PyConfig_InitCompatConfig _PyDict_CheckConsistency _PyErr_ChainStackItem _PyErr_CheckSignalsTstate _PyErr_Clear _PyErr_Display _PyErr_ExceptionMatches _PyErr_Fetch _PyErr_Format _PyErr_FormatFromCauseTstate _PyErr_NoMemory _PyErr_NormalizeException _PyErr_Print _PyErr_Restore _PyErr_SetNone _PyErr_SetObject _PyErr_SetString _PyErr_StackItemToExcInfoTuple _PyErr_WriteUnraisableDefaultHook _PyEval_AddPendingCall _PyEval_SignalAsyncExc _PyEval_SignalReceived _PyExc_CreateExceptionGroup _PyExc_PrepReraiseStar _PyFloat_DebugMallocStats _PyFloat_FormatAdvancedWriter _PyFrame_IsEntryFrame _PyGC_DumpShutdownStats _PyInterpreterID_LookUp _PyInterpreterID_New _PyInterpreterState_Enable _PyInterpreterState_GetIDObject _PyInterpreterState_IDDecref _PyInterpreterState_IDIncref _PyInterpreterState_IDInitref _PyInterpreterState_LookUpID _PyLong_FormatAdvancedWriter _PyLong_FormatBytesWriter _PyLong_FormatWriter _PyMem_GetAllocatorName _PyMem_SetDefaultAllocator _PyMem_SetupAllocators _PyOS_InterruptOccurred _PyOS_SigintEvent _PyObject_Call _PyObject_Call_Prepend _PyObject_DebugMallocStats _PyObject_FastCallDictTstate _PyPathConfig_ClearGlobal _PyPreConfig_InitCompatConfig _PyRuntimeState_Fini _PyRuntimeState_Init _PyRuntime_Finalize _PyRuntime_Initialize _PyState_AddModule _PyStructSequence_InitType _PySys_Audit _PySys_SetAttr _PyThreadState_DeleteCurrent _PyThreadState_DeleteExcept _PyThreadState_Init _PyThreadState_SetCurrent _PyThreadState_Swap _PyTime_As100Nanoseconds _PyTraceBack_FromFrame _PyTraceBack_Print_Indented _PyType_CheckConsistency _PyWarnings_Init _PyWideStringList_AsList _PyWideStringList_CheckConsistency _PyWideStringList_Clear _PyWideStringList_Copy _PyWideStringList_Extend _Py_CheckRecursiveCall _Py_ClearArgcArgv _Py_ClearStandardStreamEncoding _Py_DecodeLocaleEx _Py_DecodeUTF8Ex _Py_DecodeUTF8_surrogateescape _Py_DumpASCII _Py_DumpDecimal _Py_DumpExtensionModules _Py_DumpHexadecimal _Py_DumpTraceback _Py_DumpTracebackThreads _Py_EncodeLocaleEx _Py_EncodeLocaleRaw _Py_EncodeUTF8Ex _Py_ForgetReference _Py_GetAllocatedBlocks _Py_GetConfigsAsDict _Py_GetEnv _Py_GetErrorHandler _Py_GetForceASCII _Py_GetLocaleEncoding _Py_GetLocaleEncodingObject _Py_GetLocaleconvNumeric _Py_GetRefTotal _Py_GetSpecializationStats _Py_GetStdlibDir _Py_Get_Getpath_CodeObject _Py_HandleSystemExit _Py_IsLocaleCoercionTarget _Py_NegativeRefcount _Py_PreInitializeFromConfig _Py_PreInitializeFromPyArgv _Py_ResetForceASCII _Py_UTF8_Edit_Cost _Py_WriteIndent _Py_WriteIndentedMargin _Py_closerange _Py_device_encoding _Py_dg_dtoa _Py_dg_freedtoa _Py_dg_infinity _Py_dg_stdnan _Py_dg_strtod _Py_get_blocking _Py_get_env_flag _Py_get_inheritable _Py_get_osfhandle _Py_get_osfhandle_noraise _Py_get_xoption _Py_hashtable_clear _Py_hashtable_compare_direct _Py_hashtable_destroy _Py_hashtable_foreach _Py_hashtable_get _Py_hashtable_hash_ptr _Py_hashtable_new _Py_hashtable_new_full _Py_hashtable_set _Py_hashtable_size _Py_hashtable_steal _Py_normpath _Py_open _Py_open_noraise _Py_open_osfhandle _Py_open_osfhandle_noraise _Py_read _Py_set_blocking _Py_set_inheritable_async_safe _Py_stat _Py_str_to_int _Py_strhex _Py_strhex_bytes _Py_strhex_bytes_with_sep _Py_strhex_with_sep _Py_wfopen _Py_wgetcwd _Py_wreadlink _Py_wrealpath _Py_write _Py_write_noraise _PyCodec_Forget _PyObject_LookupSpecial _PyInterpreterID_Type _PyNamespace_Type _PyTraceMalloc_Config _Py_HasFileSystemDefaultEncodeErrors _Py_HashSecret_Initialized _Py_RefTotal _Py_UnhandledKeyboardInterrupt _inittab PyObject_AsCharBuffer PyObject_AsReadBuffer PyObject_AsWriteBuffer PySlice_GetIndicesEx PyStructSequence_UnnamedField Py_Version _PyImport_FrozenBootstrap _PyImport_FrozenStdlib _PyImport_FrozenTest _PyLong_DigitValue _PyParser_TokenNames _Py_tracemalloc_config";
29
30
// These are critically needed, but somehow don't get detected below. I found these
31
// because extension modules were visibly slow without them, etc.
32
// TODO: this is caused because in some cases PyAPI appears in a different
33
// line from the function. **MUST FIX**
34
35
// NOTE: We expose more than the public cPython API because we build some of the internal cPython
36
// extension modules as dynamic libraries, instead of building them into the core, in order to
37
// save valuable space.
38
39
const extra =
40
"_PyUnicodeWriter_WriteChar _PyUnicodeWriter_Init _PyUnicodeWriter_Finish _PyUnicodeWriter_Dealloc _PyUnicodeWriter_WriteStr PyCode_NewEmpty PyFrame_New _PyObject_GenericGetAttrWithDict _Py_FatalErrorFunc _PyUnicodeWriter_PrepareInternal _PyBytes_ReverseFind _PyBytes_Find _Py_dup";
41
//_PyArg_VaParseTupleAndKeywords_SizeT _PyArg_ParseStack_SizeT";
42
43
const headers =
44
"Python.h pyframe.h marshal.h frameobject.h structmember.h internal/pycore_unicodeobject.h internal/pycore_namespace.h internal/pycore_bytesobject.h internal/pycore_symtable.h token.h internal/pycore_structseq.h internal/pycore_fileutils.h internal/pycore_runtime.h";
45
46
const aliases = {
47
Py_INCREF: "Py_IncRef",
48
Py_DECREF: "Py_DecRef",
49
_PyArg_ParseTupleAndKeywords_SizeT: "PyArg_ParseTupleAndKeywords",
50
_PyArg_Parse_SizeT: "PyArg_Parse",
51
_PyObject_LookupSpecial: "_PyObject_LookupSpecialId",
52
_PyArg_ParseTuple_SizeT: "PyArg_ParseTuple",
53
};
54
55
/*
56
The stable api data file looks like this:
57
~/python-wasm/packages/cpython/build/wasm/Doc/data$ more stable_abi.dat
58
role,name,added,ifdef_note,struct_abi_kind
59
function,PyAIter_Check,3.10,,
60
function,PyArg_ValidateKeywordArguments,3.2,,
61
var,PyBaseObject_Type,3.2,,
62
...
63
*/
64
function stableABI(pathToPythonSource: string) {
65
const names: string[] = [];
66
const STABLE_ABI_DATA = join(pathToPythonSource, "Doc/data/stable_abi.dat");
67
for (const v of readFileSync(STABLE_ABI_DATA).toString().split("\n")) {
68
const w = v.split(",");
69
if (w.length > 0) {
70
if (
71
w[0] == "function" &&
72
w[3] != "on Windows" &&
73
w[3] != "on platforms with native thread IDs" &&
74
w[3] != "on platforms with USE_STACKCHECK"
75
) {
76
names.push(w[1]);
77
}
78
}
79
}
80
return names;
81
}
82
83
export async function main(pathToPythonSource: string) {
84
if (!statSync(pathToPythonSource).isDirectory()) {
85
throw Error(
86
`${pathToPythonSource} must be the path to the Python source code`
87
);
88
}
89
const exclude = new Set(omit.split(/\s+/));
90
let names: string[] = [];
91
const output = (
92
await spawnAsync("grep", [
93
"--no-filename",
94
"PyAPI",
95
"-r",
96
join(pathToPythonSource, "Include"),
97
])
98
).toString();
99
for (let line of output.split("\n")) {
100
line = line.trim();
101
if (line.includes("DEPRECATED") || line.includes("Windows")) {
102
continue;
103
}
104
if (line.startsWith("PyAPI_FUNC")) {
105
const k = line.lastIndexOf("PyAPI_FUNC");
106
if (k == -1) continue;
107
line = line.slice(k).split(") ")[1];
108
if (line == null) continue;
109
const j = line.indexOf("(");
110
const name = line.slice(0, j);
111
if (name.includes(" ") || exclude.has(name)) continue;
112
names.push(name);
113
} else if (line.startsWith("PyAPI_DATA")) {
114
// EXAMPLES
115
// cpython/pythonrun.h:PyAPI_DATA(char) *(*PyOS_ReadlineFunctionPointer)(FILE *, FILE *, const char *);
116
// PyAPI_DATA(const char *) Py_FileSystemDefaultEncoding;
117
// cpython/unicodeobject.h:PyAPI_DATA(const unsigned char) _Py_ascii_whitespace[];
118
// find first ')' space:
119
const k = line.indexOf(") ");
120
if (k == -1) continue;
121
// find first identifier after that space
122
const name = line.slice(k + 2).match(/([a-zA-Z_$][a-zA-Z\\d_$]*)/)[0];
123
if (name.includes(" ") || exclude.has(name)) continue;
124
names.push(name);
125
}
126
}
127
console.log("#define Py_BUILD_CORE");
128
console.log("#define PY_SSIZE_T_CLEAN");
129
console.log("#undef Py_LIMITED_API");
130
for (const name of new Set(headers.split(/\s+/))) {
131
console.log(`#include <${name}>`);
132
}
133
names = names.filter((name) => {
134
if (name.startsWith("pthread_")) {
135
// python has some conflicting pthread stubs
136
return false;
137
}
138
return true;
139
});
140
names = names.concat(extra.split(/\s+/));
141
// In addition to the above "heuristics", it's critical that we
142
// include the entire stable abi, which we get below:
143
names = names.concat(stableABI(pathToPythonSource));
144
console.log(wasmExport(names));
145
for (const name in aliases) {
146
console.log(alias(name, aliases[name]));
147
}
148
}
149
150
function usage() {
151
process.stderr.write(
152
`Usage: ${process.argv[0]} <path to python source code>\n`
153
);
154
}
155
156
(async () => {
157
if (process.argv.length <= 2) {
158
usage();
159
process.exit(1);
160
} else {
161
try {
162
await main(process.argv[2]);
163
} catch (err) {
164
usage();
165
process.stderr.write(`Error: ${err.message}\n`);
166
process.exit(1);
167
}
168
}
169
})();
170
171