Skip to content

Commit 441e2e0

Browse files
committed
[GR-13107] Enable building and running scikit-learn examples without CPython support
PullRequest: graalpython/333
2 parents 1557a28 + 9409eb7 commit 441e2e0

File tree

24 files changed

+262
-57
lines changed

24 files changed

+262
-57
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ If all goes well (you'll need to have `clang`, `llvm-link`, `llvm-extract`,
3636
`llvm-nm`, and `opt` in your `PATH` in addition to the normal NumPy build
3737
dependencies), you should be able to `import numpy` afterwards.
3838

39+
Support for more extension modules is high priority for us. We are actively
40+
building out our support for the Python C API to make extensions such as NumPy,
41+
SciPy, Scikit-learn, Tensorflow and the like work. This work means that some
42+
other extensions might also already work, but we're not actively testing other
43+
extensions right now and cannot promise anything. Note that to try extensions on
44+
this implementation, you have to download, build, and install them manually for
45+
now. To do so, we recommend LLVM 6. Other versions might also work, but this
46+
version is what we're testing with in our CI.
47+
3948
### Licensing
4049

4150
This Graal/Truffle-based implementation of Python is copyright (c) 2017, 2018

graalpython/com.oracle.graal.python.cext/src/boolobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
#include <stdarg.h>
4444

45-
PyTypeObject PyBool_Type = PY_TRUFFLE_TYPE("bool", &PyType_Type, Py_TPFLAGS_DEFAULT, sizeof(struct _longobject));
45+
PyTypeObject PyBool_Type = PY_TRUFFLE_TYPE("bool", &PyType_Type, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_LONG_SUBCLASS, sizeof(struct _longobject));
4646

4747
// taken from CPython "Python/Objects/boolobject.c"
4848
PyObject *PyBool_FromLong(long ok) {

graalpython/com.oracle.graal.python.cext/src/capi.c

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -235,13 +235,19 @@ void* get_ob_type(PyObject* obj) {
235235
if (!truffle_cannot_be_handle(type)) {
236236
return resolve_handle(cache, (uint64_t)type);
237237
} else {
238-
// we have stored a handle to the Java class in ob_refcnt
239-
void* handle = (void*)((PyObject*)type)->ob_refcnt;
240-
if (!truffle_cannot_be_handle(handle)) {
241-
return resolve_handle(cache, (uint64_t)handle);
238+
PyObject* cast_type = ((PyObject*)type);
239+
if (!polyglot_is_value(cast_type)) {
240+
// we have stored a handle to the Java class in ob_refcnt
241+
void* handle = (void*)(cast_type->ob_refcnt);
242+
if (!truffle_cannot_be_handle(handle)) {
243+
return resolve_handle(cache, (uint64_t)handle);
244+
} else {
245+
// assume handle is a TruffleObject
246+
return handle;
247+
}
242248
} else {
243-
// assume handle is a TruffleObject
244-
return handle;
249+
// the type is already the right value (e.g. on sandboxed it's a managed pointer)
250+
return cast_type;
245251
}
246252
}
247253
}
@@ -649,6 +655,21 @@ void* wrap_unsupported(void *fun, ...) {
649655
return NULL;
650656
}
651657

652-
int truffle_ptr_compare(void* x, void* y) {
653-
return x == y;
658+
int truffle_ptr_compare(void* x, void* y, int op) {
659+
switch (op) {
660+
case Py_LT:
661+
return x < y;
662+
case Py_LE:
663+
return x <= y;
664+
case Py_EQ:
665+
return x == y;
666+
case Py_NE:
667+
return x != y;
668+
case Py_GT:
669+
return x > y;
670+
case Py_GE:
671+
return x >= y;
672+
default:
673+
return -1;
674+
}
654675
}

graalpython/com.oracle.graal.python.cext/src/capi.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ void* wrap_keywords(PyCFunctionWithKeywords fun, PyObject *module, PyObject *var
211211
void* wrap_fastcall(_PyCFunctionFast fun, PyObject * self, PyObject **args, PyObject *nargs, PyObject *kwnames);
212212
void* wrap_unsupported(void *fun, ...);
213213

214-
#define TDEBUG __asm__("int $3")
214+
#define TDEBUG __builtin_debugtrap()
215215
#define get_method_flags_wrapper(flags) \
216216
(((flags) < 0) ? \
217217
truffle_read(PY_TRUFFLE_CEXT, "METH_DIRECT") : \

graalpython/com.oracle.graal.python.cext/src/exceptions.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ PyObject * PyExc_UnboundLocalError = NULL;
8282
PyObject * PyExc_NotImplementedError = NULL;
8383
PyObject * PyExc_RecursionError = NULL;
8484
PyObject * PyExc_UnicodeEncodeError = NULL;
85+
PyObject * PyExc_GeneratorExit = NULL;
8586

8687
void initialize_exceptions() {
8788
PyExc_AttributeError = PY_EXCEPTION("AttributeError");
@@ -120,6 +121,7 @@ void initialize_exceptions() {
120121
PyExc_RecursionError = PY_EXCEPTION("RecursionError");
121122
PyExc_NotImplementedError = PY_EXCEPTION("NotImplementedError");
122123
PyExc_UnicodeEncodeError = PY_EXCEPTION("UnicodeEncodeError");
124+
PyExc_GeneratorExit = PY_EXCEPTION("GeneratorExit");
123125
}
124126

125127

graalpython/com.oracle.graal.python.cext/src/obmalloc.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ int _PyTraceMalloc_Track(_PyTraceMalloc_domain_t domain, uintptr_t ptr, size_t s
7676
return -2; // we do not track
7777
}
7878

79+
int _PyTraceMalloc_Untrack(_PyTraceMalloc_domain_t domain, uintptr_t ptr) {
80+
return -2; // we do not track
81+
}
82+
7983
void * PyMem_Realloc(void *ptr, size_t new_size) {
8084
return PyMem_RawRealloc(ptr, new_size);
8185
}

graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonCC.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class GraalPythonCC extends GraalPythonCompiler {
6060
private List<String> clangArgs;
6161
private List<String> execLinkArgs;
6262
private List<String> fileInputs;
63+
private boolean isCpp;
6364

6465
GraalPythonCC() {
6566
}
@@ -103,6 +104,20 @@ static void main(String[] args) {
103104

104105
private void run(String[] args) {
105106
parseOptions(args);
107+
if (isCpp) {
108+
// cannot use streaming API anyMatch for this on SVM
109+
for (String s : clangArgs) {
110+
if (s.contains("--sysroot")) {
111+
// nasty, nasty
112+
logV("Refusing to compile C++ code in sandboxed mode, because we cannot actually do it");
113+
try {
114+
Files.createFile(Paths.get(outputFilename));
115+
} catch (IOException e) {
116+
}
117+
return;
118+
}
119+
}
120+
}
106121
launchCC();
107122
}
108123

@@ -116,6 +131,7 @@ private void parseOptions(String[] args) {
116131
clangArgs = new ArrayList<>(clangPrefix);
117132
execLinkArgs = new ArrayList<>(execLinkPrefix);
118133
fileInputs = new ArrayList<>();
134+
isCpp = false;
119135
for (int i = 0; i < args.length; i++) {
120136
String arg = args[i];
121137
switch (arg) {
@@ -152,6 +168,9 @@ private void parseOptions(String[] args) {
152168
}
153169
fileInputs.add(arg);
154170
} else if (arg.endsWith(".c") || arg.endsWith(".cpp") || arg.endsWith(".cxx")) {
171+
if (arg.endsWith(".cpp") || arg.endsWith(".cxx")) {
172+
isCpp = true;
173+
}
155174
if (compile == null) {
156175
compile = true;
157176
} else if (compile != true) {
@@ -168,6 +187,9 @@ private void parseOptions(String[] args) {
168187
if (targetFlags != null) {
169188
clangArgs.addAll(Arrays.asList(targetFlags.split(" ")));
170189
}
190+
if (isCpp) {
191+
clangArgs.add("-stdlib=libc++");
192+
}
171193
}
172194

173195
private void launchCC() {

graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonLD.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,13 @@ private Collection<? extends String> arMembers(String path) throws IOException,
244244
// in the build process, because such a smart linker should not be assumed for POSIX, but it
245245
// seems ok to emulate this at least for the very common case of ar archives with symbol
246246
// definitions that overlap what's defined in explicitly include .o files
247-
for (String f : members) {
247+
outer: for (String f : members) {
248248
if (Files.probeContentType(Paths.get(f)).contains(LLVM_IR_BITCODE)) {
249-
HashSet<String> definedHere = new HashSet<>();
249+
HashSet<String> definedFuncs = new HashSet<>();
250+
HashSet<String> definedGlobals = new HashSet<>();
251+
250252
ProcessBuilder nm = new ProcessBuilder();
251-
nm.command(LLVM_NM, "-g", "--defined-only", f);
253+
nm.command(LLVM_NM, "--defined-only", f);
252254
nm.redirectInput(Redirect.INHERIT);
253255
nm.redirectError(Redirect.INHERIT);
254256
nm.redirectOutput(Redirect.PIPE);
@@ -257,23 +259,40 @@ private Collection<? extends String> arMembers(String path) throws IOException,
257259
String line = null;
258260
while ((line = buffer.readLine()) != null) {
259261
String[] symboldef = line.split(" ");
260-
if (symboldef.length >= 2) {
261-
definedHere.add(symboldef[symboldef.length - 1]);
262+
if (symboldef.length == 3) {
263+
// format is ------- CHAR FUNCNAME
264+
if (symboldef[1].toLowerCase().equals("t")) {
265+
definedFuncs.add(symboldef[2].trim());
266+
} else if (symboldef[1].toLowerCase().equals("d")) {
267+
definedGlobals.add(symboldef[2].trim());
268+
} else {
269+
// keep all if we have symbols that we wouldn't know what to do with
270+
logV("Not extracting from ", f, " because there are non-strong function or global symbols");
271+
continue outer;
272+
}
262273
}
263274
}
264275
}
265276
nmProc.waitFor();
266277

267278
ArrayList<String> extractCmd = new ArrayList<>();
268279
extractCmd.add("llvm-extract");
269-
for (String def : definedHere) {
270-
if (undefinedSymbols.contains(def)) {
280+
for (String def : definedFuncs) {
281+
if (!definedSymbols.contains(def)) {
271282
definedSymbols.add(def);
272283
undefinedSymbols.remove(def);
273284
extractCmd.add("-func");
274285
extractCmd.add(def);
275286
}
276287
}
288+
for (String def : definedGlobals) {
289+
if (!definedSymbols.contains(def)) {
290+
definedSymbols.add(def);
291+
undefinedSymbols.remove(def);
292+
extractCmd.add("-glob");
293+
extractCmd.add(def);
294+
}
295+
}
277296
extractCmd.add(f);
278297
extractCmd.add("-o");
279298
extractCmd.add(f);

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abstract.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,15 @@ def compile_module(self, name):
240240
cmpfunc=unhandled_error_compare
241241
)
242242

243+
test_PyLong_Check = CPyExtFunction(
244+
lambda args: isinstance(args[0], int),
245+
_default_unarop_args,
246+
resultspec="i",
247+
argspec='O',
248+
arguments=["PyObject* v"],
249+
cmpfunc=unhandled_error_compare
250+
)
251+
243252
test_PyNumber_Add = CPyExtFunction(
244253
lambda args: args[0] + args[1],
245254
lambda: (

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_long.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,33 @@ def compile_module(self, name):
271271
cmpfunc=unhandled_error_compare
272272
)
273273

274+
# We get a pattern like this in Cython generated code
275+
test_PyLong_FromAndToVoidPtrAllocated = CPyExtFunction(
276+
lambda args: True,
277+
lambda: ((None,),),
278+
code="""PyObject* PyLong_FromAndToVoidPtrAllocated(PyObject* none) {
279+
unsigned long l = 0;
280+
void* unwrappedPtr;
281+
PyObject* result;
282+
void* dummyPtr = malloc(sizeof(size_t));
283+
PyObject* obj = PyLong_FromVoidPtr(dummyPtr);
284+
int r = PyObject_RichCompareBool(obj, Py_False, Py_LT);
285+
if (r < 0) {
286+
return Py_None;
287+
}
288+
l = PyLong_AsUnsignedLong(obj);
289+
unwrappedPtr = (void*)l;
290+
result = unwrappedPtr == dummyPtr ? Py_True : Py_False;
291+
free(dummyPtr);
292+
return result;
293+
}
294+
""",
295+
resultspec="O",
296+
argspec='O',
297+
arguments=["PyObject* none"],
298+
cmpfunc=unhandled_error_compare
299+
)
300+
274301
test_PyLong_Check = CPyExtFunction(
275302
lambda args: isinstance(args[0], int),
276303
lambda: (

0 commit comments

Comments
 (0)