Skip to content

Commit

Permalink
ENH Add Ctypes support (pyodide#1656)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hood Chatham authored Jun 26, 2021
1 parent 450a2d2 commit 653891b
Show file tree
Hide file tree
Showing 22 changed files with 237 additions and 191 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 2
defaults: &defaults
working_directory: ~/repo
docker:
- image: pyodide/pyodide-env:17
- image: pyodide/pyodide-env:18
environment:
- EMSDK_NUM_CORES: 3
EMCC_CORES: 3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ build
ccache
cpython/downloads
cpython/installs
cpython/build.log
docs/_build/
emsdk/emsdk
geckodriver.log
Expand Down
12 changes: 7 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ FROM python:3.9.5-slim-buster

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
# building packages
bzip2 ccache clang-format-6.0 cmake f2c g++ gfortran git make \
patch pkg-config swig unzip wget xz-utils \
# testing packages: libgconf-2-4 is necessary for running chromium
libgconf-2-4 "chromium=90.*" \
# building packages
bzip2 ccache clang-format-6.0 cmake f2c g++ gfortran git make \
patch pkg-config swig unzip wget xz-utils \
autoconf autotools-dev automake texinfo dejagnu \
build-essential prelink autoconf libtool libltdl-dev \
# testing packages: libgconf-2-4 is necessary for running chromium
libgconf-2-4 "chromium=90.*" \
&& rm -rf /var/lib/apt/lists/*

RUN pip3 --no-cache-dir install \
Expand Down
11 changes: 7 additions & 4 deletions Makefile.envs
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,24 @@ export LDFLAGS_BASE=\
-s WASM=1 \
-std=c++14 \
-s LZ4=1 \
-L $(CPYTHONROOT)/installs/python-$(PYVERSION)/lib/ \
$(EXTRA_LDFLAGS)

export CXXFLAGS_BASE=

export SIDE_MODULE_LDFLAGS= $(LDFLAGS_BASE) -s SIDE_MODULE=1
export MAIN_MODULE_LDFLAGS= $(LDFLAGS_BASE) -s MAIN_MODULE=1 \
-s EXPORTED_FUNCTIONS='["___cxa_guard_acquire", "__ZNSt3__28ios_base4initEPv", "_main"]' \
$(CPYTHONROOT)/installs/python-$(PYVERSION)/lib/libpython$(PYMINOR).a \
-lpython$(PYMINOR) \
-lffi \
-lsqlite3 \
-lbz2 \
-lstdc++ \
-s TOTAL_MEMORY=20971520 \
-s ALLOW_MEMORY_GROWTH=1 \
--use-preload-plugins \
-s USE_FREETYPE=1 \
-s USE_LIBPNG=1 \
-L$(wildcard $(CPYTHONROOT)/build/sqlite*/.libs) -lsqlite3 \
$(wildcard $(CPYTHONROOT)/build/bzip2*/libbz2.a) \
-lstdc++ \
--memory-init-file 0 \

export SIDE_MODULE_CXXFLAGS = $(CXXFLAGS_BASE)
Expand All @@ -73,5 +75,6 @@ export MAIN_MODULE_CFLAGS= $(CFLAGS_BASE) \
-I$(PYTHONINCLUDE)



.output_vars:
set
34 changes: 26 additions & 8 deletions cpython/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ BZIP2TARBALL=$(ROOT)/downloads/bzip2-1.0.2.tar.gz
BZIP2BUILD=$(ROOT)/build/bzip2-1.0.2
BZIP2URL=https://sourceware.org/pub/bzip2/bzip2-1.0.2.tar.gz

FFIBUILD=$(ROOT)/build/libffi
LIBFFIREPO=https://github.com/hoodmane/libffi-emscripten
LIBFFI_COMMIT=dd8318b543b92d038da4914fac42d34056a80c7d

all: $(INSTALL)/lib/$(LIB)
all: $(INSTALL)/lib/$(LIB) $(INSTALL)/lib/libffi.a


$(INSTALL)/lib/$(LIB): $(BUILD)/$(LIB) remove_modules.txt
Expand Down Expand Up @@ -86,7 +89,7 @@ $(ZLIBBUILD)/.configured: $(ZLIBTARBALL)
touch $@


$(SQLITEBUILD)/libsqlite3.la: $(SQLITETARBALL)
$(INSTALL)/lib/libsqlite3.a: $(SQLITETARBALL)
[ -d $(ROOT)/build ] || (mkdir $(ROOT)/build)
tar -C $(ROOT)/build/ -xf $(SQLITETARBALL)
# sqlite fails to detect that popen is not available. We have to set it
Expand All @@ -96,18 +99,30 @@ $(SQLITEBUILD)/libsqlite3.la: $(SQLITETARBALL)
emconfigure ./configure CFLAGS="$(PYTHON_CFLAGS)" CPPFLAGS="-DSQLITE_OMIT_POPEN"; \
emmake make -j $${PYODIDE_JOBS:-3}; \
)
mkdir -p $(INSTALL)/lib/
cp $(SQLITEBUILD)/.libs/libsqlite3.a $(INSTALL)/lib/libsqlite3.a


$(BZIP2BUILD)/libbz2.a: $(BZIP2TARBALL)
$(INSTALL)/lib/libbz2.a: $(BZIP2TARBALL)
[ -d $(ROOT)/build ] || (mkdir $(ROOT)/build)
tar -C $(ROOT)/build/ -xf $(BZIP2TARBALL)
( \
cd $(BZIP2BUILD); \
emmake make -j $${PYODIDE_JOBS:-3} CC=emcc CFLAGS="$(PYTHON_CFLAGS) -D_FILE_OFFSET_BITS=64" AR=emar RANLIB=emranlib libbz2.a; \
)


$(BUILD)/Makefile: $(BUILD)/.patched $(ZLIBBUILD)/.configured $(SQLITEBUILD)/libsqlite3.la $(BZIP2BUILD)/libbz2.a
mkdir -p $(INSTALL)/lib
cp $(BZIP2BUILD)/libbz2.a $(INSTALL)/lib/libbz2.a

$(INSTALL)/lib/libffi.a :
rm -rf $(FFIBUILD)
git clone $(LIBFFIREPO) $(FFIBUILD) --shallow-exclude upstream-base
source $(PYODIDE_ROOT)/emsdk/emsdk/emsdk_env.sh
cd $(FFIBUILD) && git checkout $(LIBFFI_COMMIT) && bash ./build.sh ; make install
cp $(FFIBUILD)/target/include/*.h $(BUILD)/Include/
mkdir -p $(INSTALL)/lib
cp $(FFIBUILD)/target/lib/libffi.a $(INSTALL)/lib/

$(BUILD)/Makefile: $(BUILD)/.patched $(ZLIBBUILD)/.configured $(INSTALL)/lib/libsqlite3.a $(INSTALL)/lib/libbz2.a
cp config.site $(BUILD)/
( \
cd $(BUILD); \
Expand All @@ -118,14 +133,17 @@ $(BUILD)/Makefile: $(BUILD)/.patched $(ZLIBBUILD)/.configured $(SQLITEBUILD)/lib
--without-pymalloc \
--disable-shared \
--disable-ipv6 \
--without-gcc \
--enable-optimizations \
--host=wasm32-unknown-emscripten\
--build=$(shell $(BUILD)/config.guess) \
--prefix=$(INSTALL) ; \
)


$(BUILD)/$(LIB): $(BUILD)/Makefile Setup.local
$(BUILD)/Modules/Setup.local : Setup.local
cp Setup.local $(BUILD)/Modules/

$(BUILD)/$(LIB): $(BUILD)/Makefile $(BUILD)/pyconfig.h $(BUILD)/Modules/Setup.local $(INSTALL)/lib/libffi.a
cp Setup.local $(BUILD)/Modules/
cat pyconfig.undefs.h >> $(BUILD)/pyconfig.h
( \
Expand Down
5 changes: 5 additions & 0 deletions cpython/Setup.local
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ _datetime _datetimemodule.c
_heapq _heapqmodule.c
_json _json.c
_csv _csv.c

CTYPES_FLAGS=-DHAVE_FFI_PREP_CIF_VAR=1 -DHAVE_FFI_PREP_CLOSURE_LOC=1 -DHAVE_FFI_CLOSURE_ALLOC=1
_ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/cfield.c _ctypes/stgdict.c $(CTYPES_FLAGS)
_ctypes_test _ctypes/_ctypes_test.c

unicodedata unicodedata.c
_pickle _pickle.c
parser parsermodule.c
Expand Down
35 changes: 35 additions & 0 deletions cpython/patches/ctypes-dont-deref-function-pointer.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
From 3f11694d7587e782530118d176306eeb6eebf5d1 Mon Sep 17 00:00:00 2001
From: Hood <hood@mit.edu>
Date: Wed, 23 Jun 2021 13:47:30 -0700
Subject: [PATCH] Don't dereference function pointer

Ctypes thinks that the result of dlsym is a pointer to the function pointer, so
it should call it like `result = (*f)(args)`. Probably this is true for the
native dlsym, but our dlsym returns an index into the indirect call table
"wasmTable", in particular it isn't even aligned like a pointer should be.
This patch fixes it so that it calls it like `result = f(args)` instead.

---
Modules/_ctypes/_ctypes.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index ceae67e..44f2d76 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -771,7 +771,11 @@ CDataType_in_dll(PyObject *type, PyObject *args)
return NULL;
}
#endif
- return PyCData_AtAddress(type, address);
+ CDataObject *ob = (CDataObject *)GenericPyCData_new(type, NULL, NULL);
+ if (ob == NULL)
+ return NULL;
+ *(void **)ob->b_ptr = address;
+ return (PyObject*)ob;
}

static const char from_param_doc[] =
--
2.17.1

49 changes: 49 additions & 0 deletions cpython/patches/remove-duplicate-symbols-from-cfield.c.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
From fc3b69f9afa779185a60834cf1817c22706edcd1 Mon Sep 17 00:00:00 2001
From: Hood <hood@mit.edu>
Date: Tue, 22 Jun 2021 20:12:45 -0700
Subject: [PATCH] Remove duplicate symbols from cfield.c

These symbols are already defined by libffi.
---
Modules/_ctypes/cfield.c | 26 --------------------------
1 file changed, 26 deletions(-)

diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c
index 7ebd4ba..7a63ab7 100644
--- a/Modules/_ctypes/cfield.c
+++ b/Modules/_ctypes/cfield.c
@@ -1635,31 +1635,5 @@ typedef struct _ffi_type
} ffi_type;
*/

-/* align and size are bogus for void, but they must not be zero */
-ffi_type ffi_type_void = { 1, 1, FFI_TYPE_VOID };
-
-ffi_type ffi_type_uint8 = { 1, 1, FFI_TYPE_UINT8 };
-ffi_type ffi_type_sint8 = { 1, 1, FFI_TYPE_SINT8 };
-
-ffi_type ffi_type_uint16 = { 2, 2, FFI_TYPE_UINT16 };
-ffi_type ffi_type_sint16 = { 2, 2, FFI_TYPE_SINT16 };
-
-ffi_type ffi_type_uint32 = { 4, INT_ALIGN, FFI_TYPE_UINT32 };
-ffi_type ffi_type_sint32 = { 4, INT_ALIGN, FFI_TYPE_SINT32 };
-
-ffi_type ffi_type_uint64 = { 8, LONG_LONG_ALIGN, FFI_TYPE_UINT64 };
-ffi_type ffi_type_sint64 = { 8, LONG_LONG_ALIGN, FFI_TYPE_SINT64 };
-
-ffi_type ffi_type_float = { sizeof(float), FLOAT_ALIGN, FFI_TYPE_FLOAT };
-ffi_type ffi_type_double = { sizeof(double), DOUBLE_ALIGN, FFI_TYPE_DOUBLE };
-
-#ifdef ffi_type_longdouble
-#undef ffi_type_longdouble
-#endif
- /* This is already defined on OSX */
-ffi_type ffi_type_longdouble = { sizeof(long double), LONGDOUBLE_ALIGN,
- FFI_TYPE_LONGDOUBLE };
-
-ffi_type ffi_type_pointer = { sizeof(void *), VOID_P_ALIGN, FFI_TYPE_POINTER };

/*---------------- EOF ----------------*/
--
2.17.1

65 changes: 65 additions & 0 deletions cpython/patches/xfail-core-ctypes-tests-that-don-t-work.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
From a4aec920c76ebcb8360350ff0046c7a0c74c0cf2 Mon Sep 17 00:00:00 2001
From: Hood <hood@mit.edu>
Date: Thu, 24 Jun 2021 14:55:10 -0700
Subject: [PATCH] xfail core ctypes tests that don't work

PyCode_SetExtra doesn't work and ctypes doesn't seem to work with variadic functions including PyUnicode_FromFormat/
---
Lib/test/test_code.py | 7 +++++--
Lib/test/test_unicode.py | 1 +
2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py
index ac3dde7..d91a350 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -389,6 +389,7 @@ if check_impl_detail(cpython=True) and ctypes is not None:
ctypes.c_voidp(100)), 0)

def test_free_called(self):
+ raise unittest.SkipTest("PyCode_SetExtra is broken")
# Verify that the provided free function gets invoked
# when the code object is cleaned up.
f = self.get_func()
@@ -398,6 +399,7 @@ if check_impl_detail(cpython=True) and ctypes is not None:
self.assertEqual(LAST_FREED, 100)

def test_get_set(self):
+ raise unittest.SkipTest("PyCode_SetExtra is broken")
# Test basic get/set round tripping.
f = self.get_func()

@@ -414,6 +416,7 @@ if check_impl_detail(cpython=True) and ctypes is not None:
del f

def test_free_different_thread(self):
+ raise unittest.SkipTest("Requires threading")
# Freeing a code object on a different thread then
# where the co_extra was set should be safe.
f = self.get_func()
@@ -438,8 +441,8 @@ def test_main(verbose=None):
from test import test_code
run_doctest(test_code, verbose)
tests = [CodeTest, CodeConstsTest, CodeWeakRefTest]
- if check_impl_detail(cpython=True) and ctypes is not None:
- tests.append(CoExtra)
+ # if check_impl_detail(cpython=True) and ctypes is not None:
+ # tests.append(CoExtra)
run_unittest(*tests)

if __name__ == "__main__":
diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py
index 23508c5..a618e25 100644
--- a/Lib/test/test_unicode.py
+++ b/Lib/test/test_unicode.py
@@ -2521,6 +2521,7 @@ class CAPITest(unittest.TestCase):

# Test PyUnicode_FromFormat()
def test_from_format(self):
+ raise unittest.SkipTest("ctypes don't work with variadic C functions like PyUnicode_FromFormat")
support.import_module('ctypes')
from ctypes import (
c_char_p,
--
2.17.1

1 change: 0 additions & 1 deletion cpython/remove_modules.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
_osx_support.py
ctypes
curses
dbm
ensurepip
Expand Down
3 changes: 3 additions & 0 deletions docs/project/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ substitutions:
- The standard library module `audioop` is now included, making the `wave`,
`sndhdr`, `aifc`, and `sunau` modules usable. {pr}`1623`

- Added support for `ctypes`.
{pr}`1656`

### Python / JS type conversions

- {{ API }} {any}`pyodide.runPythonAsync` no longer automatically calls
Expand Down
36 changes: 36 additions & 0 deletions emsdk/patches/0001-Throw-away-errors-in-minify_wasm_js.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
From 310269ed630ead73e89e6bfc15e54e4c90959c95 Mon Sep 17 00:00:00 2001
From: Hood <hood@mit.edu>
Date: Thu, 24 Jun 2021 04:08:02 -0700
Subject: [PATCH] Throw away errors in minify_wasm_js

---
emcc.py | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/emsdk/upstream/emscripten/emcc.py b/emsdk/upstream/emscripten/emcc.py
index 839f791b3..5653470dd 100755
--- a/emsdk/upstream/emscripten/emcc.py
+++ b/emsdk/upstream/emscripten/emcc.py
@@ -2901,11 +2901,14 @@ def do_binaryen(target, options, wasm_target):
# Closure can print out readable error messages (Closure will then
# minify whitespace afterwards)
save_intermediate_with_wasm('preclean', wasm_target)
- final_js = building.minify_wasm_js(js_file=final_js,
- wasm_file=wasm_target,
- expensive_optimizations=will_metadce(),
- minify_whitespace=minify_whitespace() and not options.use_closure_compiler,
- debug_info=intermediate_debug_info)
+ try:
+ final_js = building.minify_wasm_js(js_file=final_js,
+ wasm_file=wasm_target,
+ expensive_optimizations=will_metadce(),
+ minify_whitespace=minify_whitespace() and not options.use_closure_compiler,
+ debug_info=intermediate_debug_info)
+ except:
+ pass
save_intermediate_with_wasm('postclean', wasm_target)

if shared.Settings.ASYNCIFY_LAZY_LOAD_CODE:
--
2.17.1

2 changes: 0 additions & 2 deletions packages/imageio/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ package:
source:
sha256: 52ddbaeca2dccf53ba2d6dec5676ca7bc3b2403ef8b37f7da78b7654bb3e10f0
url: https://files.pythonhosted.org/packages/c3/73/f37f428748c4f10a7991ac5bff00f113a34bcc0d0a78957d6e1cdc29a94e/imageio-2.9.0.tar.gz
patches:
- patches/setitup.patch
requirements:
run:
- numpy
Expand Down
Loading

0 comments on commit 653891b

Please sign in to comment.