/
callconv.py
956 lines (805 loc) · 36.3 KB
/
callconv.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
"""
Calling conventions for Numba-compiled functions.
"""
from collections import namedtuple
from collections.abc import Iterable
import itertools
import hashlib
from llvmlite import ir
from numba.core import types, cgutils, errors
from numba.core.base import PYOBJECT, GENERIC_POINTER
TryStatus = namedtuple('TryStatus', ['in_try', 'excinfo'])
Status = namedtuple("Status",
("code",
# If the function returned ok (a value or None)
"is_ok",
# If the function returned None
"is_none",
# If the function errored out (== not is_ok)
"is_error",
# If the generator exited with StopIteration
"is_stop_iteration",
# If the function errored with an already set exception
"is_python_exc",
# If the function errored with a user exception
"is_user_exc",
# The pointer to the exception info structure (for user
# exceptions)
"excinfoptr",
))
int32_t = ir.IntType(32)
int64_t = ir.IntType(64)
errcode_t = int32_t
def _const_int(code):
return ir.Constant(errcode_t, code)
RETCODE_OK = _const_int(0)
RETCODE_EXC = _const_int(-1)
RETCODE_NONE = _const_int(-2)
# StopIteration
RETCODE_STOPIT = _const_int(-3)
FIRST_USEREXC = 1
RETCODE_USEREXC = _const_int(FIRST_USEREXC)
class BaseCallConv(object):
def __init__(self, context):
self.context = context
def return_optional_value(self, builder, retty, valty, value):
if valty == types.none:
# Value is none
self.return_native_none(builder)
elif retty == valty:
# Value is an optional, need a runtime switch
optval = self.context.make_helper(builder, retty, value=value)
validbit = cgutils.as_bool_bit(builder, optval.valid)
with builder.if_then(validbit):
retval = self.context.get_return_value(builder, retty.type,
optval.data)
self.return_value(builder, retval)
self.return_native_none(builder)
elif not isinstance(valty, types.Optional):
# Value is not an optional, need a cast
if valty != retty.type:
value = self.context.cast(builder, value, fromty=valty,
toty=retty.type)
retval = self.context.get_return_value(builder, retty.type, value)
self.return_value(builder, retval)
else:
raise NotImplementedError("returning {0} for {1}".format(valty,
retty))
def return_native_none(self, builder):
self._return_errcode_raw(builder, RETCODE_NONE)
def return_exc(self, builder):
self._return_errcode_raw(builder, RETCODE_EXC)
def return_stop_iteration(self, builder):
self._return_errcode_raw(builder, RETCODE_STOPIT)
def get_return_type(self, ty):
"""
Get the actual type of the return argument for Numba type *ty*.
"""
restype = self.context.data_model_manager[ty].get_return_type()
return restype.as_pointer()
def init_call_helper(self, builder):
"""
Initialize and return a call helper object for the given builder.
"""
ch = self._make_call_helper(builder)
builder.__call_helper = ch
return ch
def _get_call_helper(self, builder):
return builder.__call_helper
def unpack_exception(self, builder, pyapi, status):
return pyapi.unserialize(status.excinfoptr)
def raise_error(self, builder, pyapi, status):
"""
Given a non-ok *status*, raise the corresponding Python exception.
"""
bbend = builder.function.append_basic_block()
with builder.if_then(status.is_user_exc):
# Unserialize user exception.
# Make sure another error may not interfere.
pyapi.err_clear()
exc = self.unpack_exception(builder, pyapi, status)
with cgutils.if_likely(builder,
cgutils.is_not_null(builder, exc)):
pyapi.raise_object(exc) # steals ref
builder.branch(bbend)
with builder.if_then(status.is_stop_iteration):
pyapi.err_set_none("PyExc_StopIteration")
builder.branch(bbend)
with builder.if_then(status.is_python_exc):
# Error already raised => nothing to do
builder.branch(bbend)
pyapi.err_set_string("PyExc_SystemError",
"unknown error when calling native function")
builder.branch(bbend)
builder.position_at_end(bbend)
def decode_arguments(self, builder, argtypes, func):
"""
Get the decoded (unpacked) Python arguments with *argtypes*
from LLVM function *func*. A tuple of LLVM values is returned.
"""
raw_args = self.get_arguments(func)
arginfo = self._get_arg_packer(argtypes)
return arginfo.from_arguments(builder, raw_args)
def _get_arg_packer(self, argtypes):
"""
Get an argument packer for the given argument types.
"""
return self.context.get_arg_packer(argtypes)
class MinimalCallConv(BaseCallConv):
"""
A minimal calling convention, suitable for e.g. GPU targets.
The implemented function signature is:
retcode_t (<Python return type>*, ... <Python arguments>)
The return code will be one of the RETCODE_* constants or a
function-specific user exception id (>= RETCODE_USEREXC).
Caller is responsible for allocating a slot for the return value
(passed as a pointer in the first argument).
"""
def _make_call_helper(self, builder):
return _MinimalCallHelper()
def return_value(self, builder, retval):
retptr = builder.function.args[0]
assert retval.type == retptr.type.pointee, \
(str(retval.type), str(retptr.type.pointee))
builder.store(retval, retptr)
self._return_errcode_raw(builder, RETCODE_OK)
def return_user_exc(self, builder, exc, exc_args=None, loc=None,
func_name=None):
if exc is not None and not issubclass(exc, BaseException):
raise TypeError("exc should be None or exception class, got %r"
% (exc,))
if exc_args is not None and not isinstance(exc_args, tuple):
raise TypeError("exc_args should be None or tuple, got %r"
% (exc_args,))
# Build excinfo struct
if loc is not None:
fname = loc._raw_function_name()
if fname is None:
# could be exec(<string>) or REPL, try func_name
fname = func_name
locinfo = (fname, loc.filename, loc.line)
if None in locinfo:
locinfo = None
else:
locinfo = None
call_helper = self._get_call_helper(builder)
exc_id = call_helper._add_exception(exc, exc_args, locinfo)
self._return_errcode_raw(builder, _const_int(exc_id))
def return_status_propagate(self, builder, status):
self._return_errcode_raw(builder, status.code)
def _return_errcode_raw(self, builder, code):
if isinstance(code, int):
code = _const_int(code)
builder.ret(code)
def _get_return_status(self, builder, code):
"""
Given a return *code*, get a Status instance.
"""
norm = builder.icmp_signed('==', code, RETCODE_OK)
none = builder.icmp_signed('==', code, RETCODE_NONE)
ok = builder.or_(norm, none)
err = builder.not_(ok)
exc = builder.icmp_signed('==', code, RETCODE_EXC)
is_stop_iteration = builder.icmp_signed('==', code, RETCODE_STOPIT)
is_user_exc = builder.icmp_signed('>=', code, RETCODE_USEREXC)
status = Status(code=code,
is_ok=ok,
is_error=err,
is_python_exc=exc,
is_none=none,
is_user_exc=is_user_exc,
is_stop_iteration=is_stop_iteration,
excinfoptr=None)
return status
def get_function_type(self, restype, argtypes):
"""
Get the implemented Function type for *restype* and *argtypes*.
"""
arginfo = self._get_arg_packer(argtypes)
argtypes = list(arginfo.argument_types)
resptr = self.get_return_type(restype)
fnty = ir.FunctionType(errcode_t, [resptr] + argtypes)
return fnty
def decorate_function(self, fn, args, fe_argtypes, noalias=False):
"""
Set names and attributes of function arguments.
"""
assert not noalias
arginfo = self._get_arg_packer(fe_argtypes)
arginfo.assign_names(self.get_arguments(fn),
['arg.' + a for a in args])
fn.args[0].name = ".ret"
def get_arguments(self, func):
"""
Get the Python-level arguments of LLVM *func*.
"""
return func.args[1:]
def call_function(self, builder, callee, resty, argtys, args):
"""
Call the Numba-compiled *callee*.
"""
retty = callee.args[0].type.pointee
retvaltmp = cgutils.alloca_once(builder, retty)
# initialize return value
builder.store(cgutils.get_null_value(retty), retvaltmp)
arginfo = self._get_arg_packer(argtys)
args = arginfo.as_arguments(builder, args)
realargs = [retvaltmp] + list(args)
code = builder.call(callee, realargs)
status = self._get_return_status(builder, code)
retval = builder.load(retvaltmp)
out = self.context.get_returned_value(builder, resty, retval)
return status, out
class _MinimalCallHelper(object):
"""
A call helper object for the "minimal" calling convention.
User exceptions are represented as integer codes and stored in
a mapping for retrieval from the caller.
"""
def __init__(self):
self.exceptions = {}
def _add_exception(self, exc, exc_args, locinfo):
"""
Add a new user exception to this helper. Returns an integer that can be
used to refer to the added exception in future.
Parameters
----------
exc :
exception type
exc_args : None or tuple
exception args
locinfo : tuple
location information
"""
exc_id = len(self.exceptions) + FIRST_USEREXC
self.exceptions[exc_id] = exc, exc_args, locinfo
return exc_id
def get_exception(self, exc_id):
"""
Get information about a user exception. Returns a tuple of
(exception type, exception args, location information).
Parameters
----------
id : integer
The ID of the exception to look up
"""
try:
return self.exceptions[exc_id]
except KeyError:
msg = "unknown error %d in native function" % exc_id
exc = SystemError
exc_args = (msg,)
locinfo = None
return exc, exc_args, locinfo
# The structure type constructed by PythonAPI.serialize_uncached()
# i.e a {i8* pickle_buf, i32 pickle_bufsz, i8* hash_buf, i8* fn, i32 alloc_flag}
PICKLE_BUF_IDX = 0
PICKLE_BUFSZ_IDX = 1
HASH_BUF_IDX = 2
UNWRAP_FUNC_IDX = 3
ALLOC_FLAG_IDX = 4
excinfo_t = ir.LiteralStructType(
[GENERIC_POINTER, int32_t, GENERIC_POINTER, GENERIC_POINTER, int32_t])
excinfo_ptr_t = ir.PointerType(excinfo_t)
class CPUCallConv(BaseCallConv):
"""
The calling convention for CPU targets.
The implemented function signature is:
retcode_t (<Python return type>*, excinfo **, ... <Python arguments>)
The return code will be one of the RETCODE_* constants.
If RETCODE_USEREXC, the exception info pointer will be filled with
a pointer to a constant struct describing the raised exception.
Caller is responsible for allocating slots for the return value
and the exception info pointer (passed as first and second arguments,
respectively).
"""
_status_ids = itertools.count(1)
def _make_call_helper(self, builder):
return None
def return_value(self, builder, retval):
retptr = self._get_return_argument(builder.function)
assert retval.type == retptr.type.pointee, \
(str(retval.type), str(retptr.type.pointee))
builder.store(retval, retptr)
self._return_errcode_raw(builder, RETCODE_OK)
def build_excinfo_struct(self, exc, exc_args, loc, func_name):
# Build excinfo struct
if loc is not None:
fname = loc._raw_function_name()
if fname is None:
# could be exec(<string>) or REPL, try func_name
fname = func_name
locinfo = (fname, loc.filename, loc.line)
if None in locinfo:
locinfo = None
else:
locinfo = None
exc = (exc, exc_args, locinfo)
return exc
def set_static_user_exc(self, builder, exc, exc_args=None, loc=None,
func_name=None):
if exc is not None and not issubclass(exc, BaseException):
raise TypeError("exc should be None or exception class, got %r"
% (exc,))
if exc_args is not None and not isinstance(exc_args, tuple):
raise TypeError("exc_args should be None or tuple, got %r"
% (exc_args,))
# None is indicative of no args, set the exc_args to an empty tuple
# as PyObject_CallObject(exc, exc_args) requires the second argument to
# be a tuple (or nullptr, but doing this makes it consistent)
if exc_args is None:
exc_args = tuple()
# An exception in Numba is defined as the excinfo_t struct defined
# above. Some arguments in this struct are not used, depending on
# which kind of exception is being raised. A static exception uses
# only the first three members whilst a dynamic exception uses all
# members:
#
# static exc - last 2 args are NULL and 0
# vvv vvv vvv
# excinfo_t: {i8*, i32, i8*, i8*, i32}
# ^^^ ^^^ ^^^
# dynamic exc only - first 2 args are used for
# static info
#
# Comment below details how the struct is used in the case of a dynamic
# exception. For dynamic exceptions, see
# CPUCallConv::set_dynamic_user_exc
#
# {i8*, ___, ___, ___, ___}
# ^ serialized info about the exception (loc, kind, compile time
# args)
#
# {___, i32, ___, ___, ___}
# ^ len(serialized_exception)
#
# {___, ___, i8*, ___, ___}
# ^ Store a list of native values in a dynamic exception.
# Or a hash(serialized_exception) in a static exc.
#
# {___, ___, ___, i8*, ___}
# ^ "NULL" as this member is not used in a static exc
#
# {___, ___, ___, ___, i32}
# ^ Number of dynamic args in the exception. For
# static exceptions, this value is "0"
pyapi = self.context.get_python_api(builder)
exc = self.build_excinfo_struct(exc, exc_args, loc, func_name)
struct_gv = pyapi.serialize_object(exc)
excptr = self._get_excinfo_argument(builder.function)
store = builder.store(struct_gv, excptr)
md = builder.module.add_metadata([ir.IntType(1)(1)])
store.set_metadata("numba_exception_output", md)
def return_user_exc(self, builder, exc, exc_args=None, loc=None,
func_name=None):
try_info = getattr(builder, '_in_try_block', False)
self.set_static_user_exc(builder, exc, exc_args=exc_args,
loc=loc, func_name=func_name)
self.check_try_status(builder)
if try_info:
# This is a hack for old-style impl.
# We will branch directly to the exception handler.
builder.branch(try_info['target'])
else:
# Return from the current function
self._return_errcode_raw(builder, RETCODE_USEREXC)
def unpack_dynamic_exception(self, builder, pyapi, status):
excinfo_ptr = status.excinfoptr
# load the serialized exception buffer from the module and create
# a python bytes object
picklebuf = builder.extract_value(
builder.load(excinfo_ptr), PICKLE_BUF_IDX)
picklebuf_sz = builder.extract_value(
builder.load(excinfo_ptr), PICKLE_BUFSZ_IDX)
static_exc_bytes = pyapi.bytes_from_string_and_size(
picklebuf, builder.sext(picklebuf_sz, pyapi.py_ssize_t))
# Load dynamic args (i8*) and the unwrap function
dyn_args = builder.extract_value(
builder.load(excinfo_ptr), HASH_BUF_IDX)
func_ptr = builder.extract_value(
builder.load(excinfo_ptr), UNWRAP_FUNC_IDX)
# Convert the unwrap function to a function pointer and call it.
# Function returns a python tuple with dynamic arguments converted to
# CPython objects
fnty = ir.FunctionType(PYOBJECT, [GENERIC_POINTER])
fn = builder.bitcast(func_ptr, fnty.as_pointer())
py_tuple = builder.call(fn, [dyn_args])
# We check at this stage if creating the Python tuple was successful
# or not. Note the exception is raised by calling PyErr_SetString
# directly as the current function is the CPython wrapper.
failed = cgutils.is_null(builder, py_tuple)
with cgutils.if_unlikely(builder, failed):
msg = ('Error creating Python tuple from runtime exception '
'arguments')
pyapi.err_set_string("PyExc_RuntimeError", msg)
# Return NULL to indicate an error was raised
fnty = builder.function.function_type
if not isinstance(fnty.return_type, ir.VoidType):
# in some ufuncs, the return type is void
builder.ret(cgutils.get_null_value(fnty.return_type))
else:
builder.ret_void()
# merge static and dynamic variables
excinfo = pyapi.build_dynamic_excinfo_struct(static_exc_bytes, py_tuple)
# At this point, one can free the entire excinfo_ptr struct
if self.context.enable_nrt:
# One can safely emit a free instruction as it is only executed
# if its in a dynamic exception branch
self.context.nrt.free(
builder, builder.bitcast(excinfo_ptr, pyapi.voidptr))
return excinfo
def unpack_exception(self, builder, pyapi, status):
# Emit code that checks the alloc flag (last excinfo member)
# if alloc_flag > 0:
# (dynamic) unpack the exception to retrieve runtime information
# and merge with static info
# else:
# (static) unserialize the exception using pythonapi.unserialize
excinfo_ptr = status.excinfoptr
alloc_flag = builder.extract_value(builder.load(excinfo_ptr),
ALLOC_FLAG_IDX)
gt = builder.icmp_signed('>', alloc_flag, int32_t(0))
with builder.if_else(gt) as (then, otherwise):
with then:
dyn_exc = self.unpack_dynamic_exception(builder, pyapi, status)
bb_then = builder.block
with otherwise:
static_exc = pyapi.unserialize(excinfo_ptr)
bb_else = builder.block
phi = builder.phi(static_exc.type)
phi.add_incoming(dyn_exc, bb_then)
phi.add_incoming(static_exc, bb_else)
return phi
def emit_unwrap_dynamic_exception_fn(self, module, st_type, nb_types):
# Create a function that converts a list of runtime arguments to a tuple
# of PyObjects. i.e.:
#
# @njit('void(float, int64)')
# def func(a, b):
# raise ValueError(a, 123, b)
#
# The last three arguments of the exception info structure will hold:
# {___, ___, i8*, i8*, i32}
# ^ A ptr to a {f32, i64} struct
# ^ function ptr that converts i8* -> {f32, i64}* ->
# python tuple
# ^ Number of dynamic arguments = 2
#
_hash = hashlib.sha1(str(st_type).encode()).hexdigest()
name = f'__excinfo_unwrap_args{_hash}'
if name in module.globals:
return module.globals.get(name)
fnty = ir.FunctionType(GENERIC_POINTER, [GENERIC_POINTER])
fn = ir.Function(module, fnty, name)
# prevent the function from being inlined
fn.attributes.add('nounwind')
fn.attributes.add('noinline')
bb_entry = fn.append_basic_block('')
builder = ir.IRBuilder(bb_entry)
pyapi = self.context.get_python_api(builder)
# i8* -> {native arg1 type, native arg2 type, ...}
st_type_ptr = st_type.as_pointer()
st_ptr = builder.bitcast(fn.args[0], st_type_ptr)
# compile time values are stored as None
nb_types = [typ for typ in nb_types if typ is not None]
# convert native values into CPython objects
objs = []
env_manager = self.context.get_env_manager(builder,
return_pyobject=True)
for i, typ in enumerate(nb_types):
val = builder.extract_value(builder.load(st_ptr), i)
obj = pyapi.from_native_value(typ, val, env_manager=env_manager)
# If object cannot be boxed, raise an exception
if obj == cgutils.get_null_value(obj.type):
# When not supported, abort compilation
msg = f'Cannot convert native {typ} to a Python object.'
raise errors.TypingError(msg)
objs.append(obj)
# at this point, a pointer to the list of runtime values can be freed
self.context.nrt.free(builder,
self._get_return_argument(builder.function))
# Create a tuple of CPython objects
tup = pyapi.tuple_pack(objs)
builder.ret(tup)
return fn
def emit_wrap_args_insts(self, builder, pyapi, struct_type, exc_args):
"""
Create an anonymous struct containing the given LLVM *values*.
"""
st_size = pyapi.py_ssize_t(self.context.get_abi_sizeof(struct_type))
st_ptr = builder.bitcast(
self.context.nrt.allocate(builder, st_size),
struct_type.as_pointer())
# skip compile-time values
exc_args = [arg for arg in exc_args if isinstance(arg, ir.Value)]
zero = int32_t(0)
for idx, arg in enumerate(exc_args):
builder.store(arg, builder.gep(st_ptr, [zero, int32_t(idx)]))
return st_ptr
def set_dynamic_user_exc(self, builder, exc, exc_args, nb_types, loc=None,
func_name=None):
"""
Compute the required bits to emit an exception with dynamic (runtime)
values
"""
if not issubclass(exc, BaseException):
raise TypeError("exc should be an exception class, got %r"
% (exc,))
if exc_args is not None and not isinstance(exc_args, tuple):
raise TypeError("exc_args should be None or tuple, got %r"
% (exc_args,))
# An exception in Numba is defined as the excinfo_t struct defined
# above. Some arguments in this struct are not used, depending on
# which kind of exception is being raised. A static exception uses
# only the first three members whilst a dynamic exception uses all
# members:
#
# static exc - last 2 args are NULL and 0
# vvv vvv vvv
# excinfo_t: {i8*, i32, i8*, i8*, i32}
# ^^^ ^^^ ^^^
# dynamic exc only - first 2 args are used for
# static info
#
# Comment below details how the struct is used in the case of a dynamic
# exception. For static exception, see CPUCallConv::set_static_user_exc
#
# {i8*, ___, ___, ___, ___}
# ^ serialized info about the exception (loc, kind, compile time
# args)
#
# {___, i32, ___, ___, ___}
# ^ len(serialized_exception)
#
# {___, ___, i8*, ___, ___}
# ^ Store a list of native values in a dynamic exception.
# Or a hash(serialized_exception) in a static exc.
#
# {___, ___, ___, i8*, ___}
# ^ Pointer to function that convert native values
# into PyObject*. NULL in the case of a static
# exception
#
# {___, ___, ___, ___, i32}
# ^ Number of dynamic args in the exception.
# Default is "0"
#
# The following code will:
# 1) Serialize compile time information and store them in the first
# two args {i8*, i32, ___, ___, ___} of excinfo_t
# 2) Emit the required code for converting native values to CPython
# objects. Those objects are stored in the last three args
# {___, ___, i8*, i8*, i32} of excinfo_t
# 3) Allocate a new excinfo_t struct
# 4) Fill excinfo_t struct and copy the pointer to the excinfo** arg
# serialize comp. time args
pyapi = self.context.get_python_api(builder)
exc = self.build_excinfo_struct(exc, exc_args, loc, func_name)
excinfo_pp = self._get_excinfo_argument(builder.function)
struct_gv = builder.load(pyapi.serialize_object(exc))
# Create the struct for runtime args and emit a function to convert it
# into a Python tuple
struct_type = ir.LiteralStructType([arg.type for arg in exc_args if
isinstance(arg, ir.Value)])
st_ptr = self.emit_wrap_args_insts(builder, pyapi, struct_type,
exc_args)
unwrap_fn = self.emit_unwrap_dynamic_exception_fn(
builder.module, struct_type, nb_types)
# allocate the excinfo struct
exc_size = pyapi.py_ssize_t(self.context.get_abi_sizeof(excinfo_t))
excinfo_p = builder.bitcast(
self.context.nrt.allocate(builder, exc_size),
excinfo_ptr_t)
# fill the args
zero = int32_t(0)
exc_fields = (builder.extract_value(struct_gv, PICKLE_BUF_IDX),
builder.extract_value(struct_gv, PICKLE_BUFSZ_IDX),
builder.bitcast(st_ptr, GENERIC_POINTER),
builder.bitcast(unwrap_fn, GENERIC_POINTER),
int32_t(len(struct_type)))
for idx, arg in enumerate(exc_fields):
builder.store(arg, builder.gep(excinfo_p, [zero, int32_t(idx)]))
builder.store(excinfo_p, excinfo_pp)
def return_dynamic_user_exc(self, builder, exc, exc_args, nb_types,
loc=None, func_name=None):
"""
Same as ::return_user_exc but for dynamic exceptions
"""
self.set_dynamic_user_exc(builder, exc, exc_args, nb_types,
loc=loc, func_name=func_name)
self._return_errcode_raw(builder, RETCODE_USEREXC)
def _get_try_state(self, builder):
try:
return builder.__eh_try_state
except AttributeError:
ptr = cgutils.alloca_once(
builder, cgutils.intp_t, name='try_state', zfill=True,
)
builder.__eh_try_state = ptr
return ptr
def check_try_status(self, builder):
try_state_ptr = self._get_try_state(builder)
try_depth = builder.load(try_state_ptr)
# try_depth > 0
in_try = builder.icmp_unsigned('>', try_depth, try_depth.type(0))
excinfoptr = self._get_excinfo_argument(builder.function)
excinfo = builder.load(excinfoptr)
return TryStatus(in_try=in_try, excinfo=excinfo)
def set_try_status(self, builder):
try_state_ptr = self._get_try_state(builder)
# Increment try depth
old = builder.load(try_state_ptr)
new = builder.add(old, old.type(1))
builder.store(new, try_state_ptr)
def unset_try_status(self, builder):
try_state_ptr = self._get_try_state(builder)
# Decrement try depth
old = builder.load(try_state_ptr)
new = builder.sub(old, old.type(1))
builder.store(new, try_state_ptr)
# Needs to reset the exception state so that the exception handler
# will run normally.
excinfoptr = self._get_excinfo_argument(builder.function)
null = cgutils.get_null_value(excinfoptr.type.pointee)
builder.store(null, excinfoptr)
def return_status_propagate(self, builder, status):
trystatus = self.check_try_status(builder)
excptr = self._get_excinfo_argument(builder.function)
builder.store(status.excinfoptr, excptr)
with builder.if_then(builder.not_(trystatus.in_try)):
self._return_errcode_raw(builder, status.code)
def _return_errcode_raw(self, builder, code):
builder.ret(code)
def _get_return_status(self, builder, code, excinfoptr):
"""
Given a return *code* and *excinfoptr*, get a Status instance.
"""
norm = builder.icmp_signed('==', code, RETCODE_OK)
none = builder.icmp_signed('==', code, RETCODE_NONE)
exc = builder.icmp_signed('==', code, RETCODE_EXC)
is_stop_iteration = builder.icmp_signed('==', code, RETCODE_STOPIT)
ok = builder.or_(norm, none)
err = builder.not_(ok)
is_user_exc = builder.icmp_signed('>=', code, RETCODE_USEREXC)
excinfoptr = builder.select(is_user_exc, excinfoptr,
ir.Constant(excinfo_ptr_t, ir.Undefined))
status = Status(code=code,
is_ok=ok,
is_error=err,
is_python_exc=exc,
is_none=none,
is_user_exc=is_user_exc,
is_stop_iteration=is_stop_iteration,
excinfoptr=excinfoptr)
return status
def get_function_type(self, restype, argtypes):
"""
Get the implemented Function type for *restype* and *argtypes*.
"""
arginfo = self._get_arg_packer(argtypes)
argtypes = list(arginfo.argument_types)
resptr = self.get_return_type(restype)
fnty = ir.FunctionType(errcode_t,
[resptr, ir.PointerType(excinfo_ptr_t)]
+ argtypes)
return fnty
def decorate_function(self, fn, args, fe_argtypes, noalias=False):
"""
Set names of function arguments, and add useful attributes to them.
"""
arginfo = self._get_arg_packer(fe_argtypes)
arginfo.assign_names(self.get_arguments(fn),
['arg.' + a for a in args])
retarg = self._get_return_argument(fn)
retarg.name = "retptr"
retarg.add_attribute("nocapture")
retarg.add_attribute("noalias")
excarg = self._get_excinfo_argument(fn)
excarg.name = "excinfo"
excarg.add_attribute("nocapture")
excarg.add_attribute("noalias")
if noalias:
args = self.get_arguments(fn)
for a in args:
if isinstance(a.type, ir.PointerType):
a.add_attribute("nocapture")
a.add_attribute("noalias")
# Add metadata to mark functions that may need NRT
# thus disabling aggressive refct pruning in removerefctpass.py
def type_may_always_need_nrt(ty):
# Returns True if it's a non-Array type that is contains MemInfo
if not isinstance(ty, types.Array):
dmm = self.context.data_model_manager
if dmm[ty].contains_nrt_meminfo():
return True
return False
args_may_always_need_nrt = any(
map(type_may_always_need_nrt, fe_argtypes)
)
if args_may_always_need_nrt:
nmd = fn.module.add_named_metadata(
'numba_args_may_always_need_nrt',
)
nmd.add(fn.module.add_metadata([fn]))
def get_arguments(self, func):
"""
Get the Python-level arguments of LLVM *func*.
"""
return func.args[2:]
def _get_return_argument(self, func):
return func.args[0]
def _get_excinfo_argument(self, func):
return func.args[1]
def call_function(self, builder, callee, resty, argtys, args,
attrs=None):
"""
Call the Numba-compiled *callee*.
Parameters:
-----------
attrs: LLVM style string or iterable of individual attributes, default
is None which specifies no attributes. Examples:
LLVM style string: "noinline fast"
Equivalent iterable: ("noinline", "fast")
"""
# XXX better fix for callees that are not function values
# (pointers to function; thus have no `.args` attribute)
retty = self._get_return_argument(callee.function_type).pointee
retvaltmp = cgutils.alloca_once(builder, retty)
# initialize return value to zeros
builder.store(cgutils.get_null_value(retty), retvaltmp)
excinfoptr = cgutils.alloca_once(builder, ir.PointerType(excinfo_t),
name="excinfo")
arginfo = self._get_arg_packer(argtys)
args = list(arginfo.as_arguments(builder, args))
realargs = [retvaltmp, excinfoptr] + args
# deal with attrs, it's fine to specify a load in a string like
# "noinline fast" as per LLVM or equally as an iterable of individual
# attributes.
if attrs is None:
_attrs = ()
elif isinstance(attrs, Iterable) and not isinstance(attrs, str):
_attrs = tuple(attrs)
else:
raise TypeError("attrs must be an iterable of strings or None")
code = builder.call(callee, realargs, attrs=_attrs)
status = self._get_return_status(builder, code,
builder.load(excinfoptr))
retval = builder.load(retvaltmp)
out = self.context.get_returned_value(builder, resty, retval)
return status, out
class ErrorModel(object):
def __init__(self, call_conv):
self.call_conv = call_conv
def fp_zero_division(self, builder, exc_args=None, loc=None):
if self.raise_on_fp_zero_division:
self.call_conv.return_user_exc(builder, ZeroDivisionError, exc_args,
loc)
return True
else:
return False
class PythonErrorModel(ErrorModel):
"""
The Python error model. Any invalid FP input raises an exception.
"""
raise_on_fp_zero_division = True
class NumpyErrorModel(ErrorModel):
"""
In the Numpy error model, floating-point errors don't raise an
exception. The FPU exception state is inspected by Numpy at the
end of a ufunc's execution and a warning is raised if appropriate.
Note there's no easy way to set the FPU exception state from LLVM.
Instructions known to set an FP exception can be optimized away:
https://llvm.org/bugs/show_bug.cgi?id=6050
http://lists.llvm.org/pipermail/llvm-dev/2014-September/076918.html
http://lists.llvm.org/pipermail/llvm-commits/Week-of-Mon-20140929/237997.html
"""
raise_on_fp_zero_division = False
error_models = {
'python': PythonErrorModel,
'numpy': NumpyErrorModel,
}
def create_error_model(model_name, context):
"""
Create an error model instance for the given target context.
"""
return error_models[model_name](context.call_conv)