Skip to content

Commit e409c04

Browse files
committed
Exception handling generated code is now entirely python-object free.
1 parent 877c89a commit e409c04

File tree

7 files changed

+227
-103
lines changed

7 files changed

+227
-103
lines changed

typed_python/_runtime.cpp

Lines changed: 89 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,16 +1021,14 @@ extern "C" {
10211021
return PythonObjectOfType::createLayout(p);
10221022
}
10231023

1024-
void np_initialize_exception(PythonObjectOfType::layout_type* layout) {
1024+
// set the python exception state and return (without throwing)
1025+
void setExceptionState(PyObject* exception, PyObject* cause) {
10251026
PyEnsureGilAcquired getTheGil;
10261027

1027-
PyObject* prevType;
1028-
PyObject* prevValue;
1029-
PyObject* prevTraceback;
1030-
PyErr_GetExcInfo(&prevType, &prevValue, &prevTraceback);
1031-
1032-
if (layout) {
1033-
PyTypeObject* tp = layout->pyObj->ob_type;
1028+
// check if the exception is actually an exception. If not,
1029+
// we can't even raise it.
1030+
if (exception) {
1031+
PyTypeObject* tp = exception->ob_type;
10341032
bool hasBaseE = false;
10351033

10361034
while (tp) {
@@ -1044,81 +1042,64 @@ extern "C" {
10441042
PyErr_Format(
10451043
PyExc_TypeError,
10461044
"exceptions must derive from BaseException, not %S",
1047-
(PyObject*)layout->pyObj->ob_type
1045+
(PyObject*)exception->ob_type
10481046
);
10491047

10501048
return;
10511049
}
1050+
}
1051+
1052+
if (exception && cause) {
1053+
// if we have an exception and a cause, just raise directly
1054+
PyException_SetCause(exception, incref(cause));
1055+
PyErr_Restore((PyObject*)incref(exception->ob_type), incref(exception), nullptr);
1056+
}
1057+
else if (exception) {
1058+
PyObject* prevType;
1059+
PyObject* prevValue;
1060+
PyObject* prevTraceback;
1061+
PyErr_GetExcInfo(&prevType, &prevValue, &prevTraceback);
10521062

10531063
if (prevValue) {
1054-
PyException_SetContext(layout->pyObj, prevValue);
1064+
PyException_SetContext(exception, prevValue);
10551065
}
10561066
decref(prevType);
10571067
decref(prevTraceback);
10581068

1059-
PyErr_SetObject(
1060-
(PyObject*)layout->pyObj->ob_type,
1061-
layout->pyObj
1062-
);
1069+
PyErr_SetObject((PyObject*)exception->ob_type, exception);
10631070
}
10641071
else {
1072+
PyObject* prevType;
1073+
PyObject* prevValue;
1074+
PyObject* prevTraceback;
1075+
PyErr_GetExcInfo(&prevType, &prevValue, &prevTraceback);
1076+
10651077
if (!prevValue) {
10661078
decref(prevType);
10671079
decref(prevValue);
10681080
decref(prevTraceback);
10691081
PyErr_SetString(PyExc_RuntimeError, "No active exception to reraise");
10701082
throw PythonExceptionSet();
10711083
}
1084+
if (cause) {
1085+
PyException_SetCause(prevValue, incref(cause));
1086+
}
10721087
PyErr_Restore(prevType, prevValue, prevTraceback);
10731088
}
10741089
}
10751090

1091+
void np_initialize_exception(PythonObjectOfType::layout_type* layout) {
1092+
setExceptionState(layout ? layout->pyObj : nullptr, nullptr);
1093+
}
1094+
10761095
void np_initialize_exception_w_cause(
10771096
PythonObjectOfType::layout_type* layoutExc,
10781097
PythonObjectOfType::layout_type* layoutCause
10791098
) {
1080-
PyEnsureGilAcquired getTheGil;
1081-
1082-
if (layoutExc) {
1083-
PyTypeObject* tp = layoutExc->pyObj->ob_type;
1084-
bool hasBaseE = false;
1085-
1086-
while (tp) {
1087-
if (tp == (PyTypeObject*)PyExc_BaseException) {
1088-
hasBaseE = true;
1089-
}
1090-
tp = tp->tp_base;
1091-
}
1092-
1093-
if (!hasBaseE) {
1094-
PyErr_Format(
1095-
PyExc_TypeError,
1096-
"exceptions must derive from BaseException, not %S",
1097-
(PyObject*)layoutExc->pyObj->ob_type
1098-
);
1099-
1100-
return;
1101-
}
1102-
1103-
PyException_SetCause(layoutExc->pyObj, layoutCause ? incref(layoutCause->pyObj) : NULL);
1104-
PyErr_Restore((PyObject*)incref(layoutExc->pyObj->ob_type), incref(layoutExc->pyObj), nullptr);
1105-
}
1106-
else {
1107-
PyObject* prevType;
1108-
PyObject* prevValue;
1109-
PyObject* prevTraceback;
1110-
PyErr_GetExcInfo(&prevType, &prevValue, &prevTraceback);
1111-
1112-
if (!prevValue) {
1113-
decref(prevType);
1114-
decref(prevValue);
1115-
decref(prevTraceback);
1116-
PyErr_SetString(PyExc_RuntimeError, "No active exception to reraise");
1117-
throw PythonExceptionSet();
1118-
}
1119-
PyException_SetCause(prevValue, layoutCause ? incref(layoutCause->pyObj) : NULL);
1120-
PyErr_Restore(prevType, prevValue, prevTraceback);
1121-
}
1099+
setExceptionState(
1100+
layoutExc ? layoutExc->pyObj : nullptr,
1101+
layoutCause ? layoutCause->pyObj : nullptr
1102+
);
11221103
}
11231104

11241105
void np_clear_exception() {
@@ -1353,6 +1334,58 @@ extern "C" {
13531334
return PythonObjectOfType::stealToCreateLayout(res);
13541335
}
13551336

1337+
void nativepython_runtime_call_pyobj_and_raise(
1338+
int argCount,
1339+
int kwargCount,
1340+
...
1341+
) {
1342+
PyEnsureGilAcquired getTheGil;
1343+
1344+
// each of 'argCount' arguments is a PyObject* followed by a const char*
1345+
va_list va_args;
1346+
va_start(va_args, kwargCount);
1347+
1348+
PyObjectHolder toCall;
1349+
1350+
PyObjectStealer args(PyTuple_New(argCount - 1));
1351+
PyObjectStealer kwargs(PyDict_New());
1352+
1353+
for (int i = 0; i < argCount; ++i) {
1354+
instance_ptr data = va_arg(va_args, instance_ptr);
1355+
Type* typ = va_arg(va_args, Type*);
1356+
1357+
if (i == 0) {
1358+
toCall.steal(
1359+
PyInstance::extractPythonObject(data, typ)
1360+
);
1361+
} else {
1362+
PyTuple_SetItem((PyObject*)args, i - 1, PyInstance::extractPythonObject(data, typ));
1363+
}
1364+
}
1365+
1366+
for (int i = 0; i < kwargCount; ++i) {
1367+
instance_ptr data = va_arg(va_args, instance_ptr);
1368+
Type* typ = va_arg(va_args, Type*);
1369+
const char* kwargName = va_arg(va_args, const char*);
1370+
1371+
PyObjectStealer kwargVal(PyInstance::extractPythonObject(data, typ));
1372+
1373+
PyDict_SetItemString((PyObject*)kwargs, kwargName, (PyObject*)kwargVal);
1374+
}
1375+
1376+
va_end(va_args);
1377+
1378+
PyObjectStealer res(PyObject_Call((PyObject*)toCall, args, kwargs));
1379+
1380+
if (!res) {
1381+
throw PythonExceptionSet();
1382+
}
1383+
1384+
setExceptionState((PyObject*)res, nullptr);
1385+
1386+
throw PythonExceptionSet();
1387+
}
1388+
13561389
PythonObjectOfType::layout_type* nativepython_runtime_call_pyobj(
13571390
PythonObjectOfType::layout_type* toCall,
13581391
int argCount,

typed_python/compiler/expression_conversion_context.py

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,53 +1286,44 @@ def pushException(self, excType, *args, **kwargs):
12861286
Returns:
12871287
None
12881288
"""
1289-
if len(args) == 0 and isinstance(excType, type):
1290-
self.pushEffect(
1291-
runtime_functions.np_raise_exception_str.call(
1292-
self.constantPyObject(excType).nonref_expr.cast(native_ast.VoidPtr),
1293-
native_ast.UInt8.pointer().zero(),
1294-
)
1295-
)
1296-
return None
1297-
1298-
if len(args) == 1 and isinstance(args[0], str) and isinstance(excType, type):
1299-
# this is the most common pathway
1300-
self.pushEffect(
1301-
runtime_functions.np_raise_exception_str.call(
1302-
self.constantPyObject(excType).nonref_expr.cast(native_ast.VoidPtr),
1303-
native_ast.const_utf8_cstr(args[0]),
1304-
)
1305-
)
1306-
return None
1307-
13081289
def toTyped(x):
13091290
if isinstance(x, TypedExpression):
13101291
return x
13111292
return self.constant(x)
13121293

1313-
origExcType = excType
1314-
excType = toTyped(excType)
1315-
args = [toTyped(x) for x in args]
1294+
args = [toTyped(excType)] + [toTyped(x) for x in args]
13161295
kwargs = {k: toTyped(v) for k, v in kwargs.items()}
13171296

1318-
if len(args) == 1 and isinstance(args[0].constantValue, str) and isinstance(origExcType, type):
1319-
self.pushEffect(
1320-
runtime_functions.np_raise_exception_str.call(
1321-
self.constantPyObject(origExcType).nonref_expr.cast(native_ast.VoidPtr),
1322-
native_ast.const_utf8_cstr(args[0].constantValue),
1323-
)
1324-
)
1325-
return None
1297+
args = [a.demasquerade() for a in args]
1298+
kwargs = {k: v.demasquerade() for k, v in kwargs.items()}
13261299

1327-
if excType is None:
1328-
return None
1300+
# we converted everything to python objects. We need to pass this
1301+
# ensemble to the interpreter. We use c-style variadic arguments here
1302+
# since everything is a pointer.
1303+
arguments = []
1304+
kwarguments = []
13291305

1330-
exceptionVal = excType.convert_call(args, kwargs)
1306+
for a in args:
1307+
a = a.ensureIsReference()
13311308

1332-
if exceptionVal is None:
1333-
return None
1309+
arguments.append(a.expr.cast(native_ast.VoidPtr))
1310+
arguments.append(self.getTypePointer(a.expr_type.typeRepresentation))
13341311

1335-
return self.pushExceptionObject(exceptionVal)
1312+
for kwargName, kwargVal in kwargs.items():
1313+
kwargVal = kwargVal.ensureIsReference()
1314+
1315+
kwarguments.append(kwargVal.expr.cast(native_ast.VoidPtr))
1316+
kwarguments.append(self.getTypePointer(kwargVal.expr_type.typeRepresentation))
1317+
kwarguments.append(native_ast.const_utf8_cstr(kwargName))
1318+
1319+
return self.pushEffect(
1320+
runtime_functions.call_pyobj_and_raise.call(
1321+
native_ast.const_int_expr(len(args)),
1322+
native_ast.const_int_expr(len(kwargs)),
1323+
*arguments,
1324+
*kwarguments,
1325+
)
1326+
)
13361327

13371328
def pushExceptionClear(self):
13381329
nativeExpr = (
@@ -1368,8 +1359,7 @@ def pushExceptionObjectWithCause(self, exceptionObject, causeObject, deferred=Fa
13681359
if exceptionObject is None:
13691360
exceptionObject = self.zero(object)
13701361

1371-
if causeObject.expr_type.typeRepresentation is type(None): # noqa
1372-
causeObject = self.zero(object)
1362+
causeObject = causeObject.toPyObj()
13731363

13741364
nativeExpr = (
13751365
runtime_functions.initialize_exception_w_cause.call(

typed_python/compiler/function_conversion_context.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,6 +1950,32 @@ def _convert_statement_ast(self, ast, variableStates: FunctionStackState, contro
19501950
if ast.exc is None:
19511951
toThrow = None # means reraise
19521952
else:
1953+
if (
1954+
ast.exc.matches.Call
1955+
and len(ast.exc.args) == 1
1956+
and not ast.exc.keywords
1957+
and not ast.exc.args[0].matches.Starred
1958+
and ast.cause is None
1959+
):
1960+
exceptionT = expr_context.convert_expression_ast(ast.exc.func)
1961+
if exceptionT is None:
1962+
return (
1963+
expr_context.finalize(None, exceptionsTakeFrom=None if ast.exc is None else ast), False
1964+
)
1965+
1966+
exceptionArg = expr_context.convert_expression_ast(ast.exc.args[0])
1967+
if exceptionArg is None:
1968+
return (
1969+
expr_context.finalize(None, exceptionsTakeFrom=None if ast.exc is None else ast), False
1970+
)
1971+
1972+
expr_context.pushException(
1973+
exceptionT,
1974+
exceptionArg
1975+
)
1976+
1977+
return expr_context.finalize(None, exceptionsTakeFrom=None if ast.exc is None else ast), False
1978+
19531979
toThrow = expr_context.convert_expression_ast(ast.exc)
19541980

19551981
if toThrow is None:

typed_python/compiler/python_to_native_converter.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -916,20 +916,19 @@ def _installInflightFunctions(self, name):
916916
self._currentlyConverting = None
917917

918918
for identifier, functionConverter in self._inflight_function_conversions.items():
919+
outboundTargets = []
920+
for outboundFuncId in self._dependencies.getNamesDependedOn(identifier):
921+
name = self._link_name_for_identity[outboundFuncId]
922+
outboundTargets.append(self._targets[name])
923+
924+
nativeFunction, actual_output_type = self._inflight_definitions.get(identifier)
925+
919926
if identifier in self._identifier_to_pyfunc:
920927
for v in self._visitors:
921-
922928
funcName, funcCode, funcGlobals, closureVars, input_types, output_type, conversionType = (
923929
self._identifier_to_pyfunc[identifier]
924930
)
925931

926-
nativeFunction, actual_output_type = self._inflight_definitions.get(identifier)
927-
928-
outboundTargets = []
929-
for outboundFuncId in self._dependencies.getNamesDependedOn(identifier):
930-
name = self._link_name_for_identity[outboundFuncId]
931-
outboundTargets.append(self._targets[name])
932-
933932
try:
934933
v.onNewFunction(
935934
identifier,
@@ -948,6 +947,17 @@ def _installInflightFunctions(self, name):
948947
)
949948
except Exception:
950949
logging.exception("event handler %s threw an unexpected exception", v.onNewFunction)
950+
else:
951+
for v in self._visitors:
952+
v.onNewNonpythonFunction(
953+
identifier,
954+
self._link_name_for_identity[identifier],
955+
functionConverter,
956+
nativeFunction,
957+
outboundTargets
958+
)
959+
960+
951961

952962
if identifier not in self._inflight_definitions:
953963
raise Exception(

0 commit comments

Comments
 (0)