8000 Merge pull request #24555 from HaoZeke/fixISOCvals · Python-Repository-Hub/numpy@1efe554 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1efe554

Browse files
authored
Merge pull request numpy#24555 from HaoZeke/fixISOCvals
BUG, ENH: Fix `iso_c_binding` type maps and fix `bind(c)` support
2 parents 5785a90 + f4493e2 commit 1efe554

File tree

9 files changed

+165
-5
lines changed

9 files changed

+165
-5
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``iso_c_binding`` support for ``f2py``
2+
--------------------------------------
3+
Previously, users would have to define their own custom ``f2cmap`` file to use
4+
type mappings defined by the Fortran2003 ``iso_c_binding`` intrinsic module.
5+
These type maps are now natively supported by ``f2py``
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
``bind(c)`` support for ``f2py``
2+
--------------------------------
3+
Both functions and subroutines can be annotated with ``bind(c)``. ``f2py`` will
4+
handle both the correct type mapping, and preserve the unique label for other
5+
``C`` interfaces.
6+
7+
**Note:** ``bind(c, name = 'routine_name_other_than_fortran_routine')`` is not
8+
honored by the ``f2py`` bindings by design, since ``bind(c)`` with the ``name``
9+
is meant to guarantee only the same name in ``C`` and ``Fortran``, not in
10+
``Python`` and ``Fortran``.

numpy/f2py/_isocbind.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
iso_c_binding_map = {
2+
'integer': {
3+
'c_int': 'int',
4+
'c_short': 'short int',
5+
'c_long': 'long int',
6+
'c_long_long': 'long long int',
7+
'c_signed_char': 'signed char',
8+
'c_size_t': 'size_t',
9+
'c_int8_t': 'int8_t',
10+
'c_int16_t': 'int16_t',
11+
'c_int32_t': 'int32_t',
12+
'c_int64_t': 'int64_t',
13+
'c_int_least8_t': 'int_least8_t',
14+
'c_int_least16_t': 'int_least16_t',
15+
'c_int_least32_t': 'int_least32_t',
16+
'c_int_least64_t': 'int_least64_t',
17+
'c_int_fast8_t': 'int_fast8_t',
18+
'c_int_fast16_t': 'int_fast16_t',
19+
'c_int_fast32_t': 'int_fast32_t',
20+
'c_int_fast64_t': 'int_fast64_t',
21+
'c_intmax_t': 'intmax_t',
22+
'c_intptr_t': 'intptr_t',
23+
'c_ptrdiff_t': 'intptr_t',
24+
},
25+
'real': {
26+
'c_float': 'float',
27+
'c_double': 'double',
28+
'c_long_double': 'long double'
29+
},
30+
'complex': {
31+
'c_float_complex': 'float _Complex',
32+
'c_double_complex': 'double _Complex',
33+
'c_long_double_complex': 'long double _Complex'
34+
},
35+
'logical': {
36+
'c_bool': '_Bool'
37+
},
38+
'character': {
39+
'c_char': 'char'
40+
}
41+
}
42+
43+
isoc_kindmap = {}
44+
for fortran_type, c_type_dict in iso_c_binding_map.items():
45+
for c_type in c_type_dict.keys():
46+
isoc_kindmap[c_type] = fortran_type

numpy/f2py/auxfuncs.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import sys
1919
import types
2020
from functools import reduce
21+
from copy import deepcopy
2122

2223
from . import __version__
2324
from . import cfuncs
@@ -47,7 +48,8 @@
4748
'isunsigned_chararray', 'isunsigned_long_long',
4849
'isunsigned_long_longarray', 'isunsigned_short',
4950
'isunsigned_shortarray', 'l_and', 'l_not', 'l_or', 'outmess',
50-
'replace', 'show', 'stripcomma', 'throw_error', 'isattr_value'
51+
'replace', 'show', 'stripcomma', 'throw_error', 'isattr_value',
52+
'deep_merge'
5153
]
5254

5355

@@ -888,3 +890,25 @@ def applyrules(rules, d, var={}):
888890
if ret[k] == []:
889891
del ret[k]
890892
return ret
893+
894+
def deep_merge(dict1, dict2):
895+
"""Recursively merge two dictionaries into a new dictionary.
896+
897+
Parameters:
898+
- dict1: The base dictionary.
899+
- dict2: The dictionary to merge into a copy of dict1.
900+
If a key exists in both, the dict2 value will take precedence.
901+
902+
Returns:
903+
- A new merged dictionary.
904+
"""
905+
merged_dict = deepcopy(dict1)
906+
for key, value in dict2.items():
907+
if key in merged_dict:
908+
if isinstance(merged_dict[key], dict) and isinstance(value, dict):
909+
merged_dict[key] = deep_merge(merged_dict[key], value)
910+
else:
911+
merged_dict[key] = value
912+
else:
913+
merged_dict[key] = value
914+
return merged_dict

numpy/f2py/capi_maps.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import os
2020
from .crackfortran import markoutercomma
2121
from . import cb_rules
22+
from ._isocbind import iso_c_binding_map
2223

2324
# The environment provided by auxfuncs.py is needed for some calls to eval.
2425
# As the needed functions cannot be determined by static inspection of the
@@ -130,6 +131,7 @@
130131
'byte': {'': 'char'},
131132
}
132133

134+
f2cmap_all = deep_merge(f2cmap_all, iso_c_binding_map)
133135
f2cmap_default = copy.deepcopy(f2cmap_all)
134136

135137
f2cmap_mapped = []

numpy/f2py/crackfortran.py

Lines changed: 23 additions & 4 deletions
10000
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,8 @@ def _simplifyargs(argsline):
696696
return ','.join(a)
697697

698698
crackline_re_1 = re.compile(r'\s*(?P<result>\b[a-z]+\w*\b)\s*=.*', re.I)
699-
699+
crackline_bind_1 = re.compile(r'\s*(?P<bind>\b[a-z]+\w*\b)\s*=.*', re.I)
700+
crackline_bindlang = re.compile(r'\s*bind\(\s*(?P<lang>[^,]+)\s*,\s*name\s*=\s*"(?P<lang_name>[^"]+)"\s*\)', re.I)
700701

701702
def crackline(line, reset=0):
702703
"""
@@ -967,12 +968,22 @@ def _resolvetypedefpattern(line):
967968
return m1.group('name'), attrs, m1.group('params')
968969
return None, [], None
969970

971+
def parse_name_for_bind(line):
972+
pattern = re.compile(r'bind\(\s*(?P<lang>[^,]+)(?:\s*,\s*name\s*=\s*["\'](?P<name>[^"\']+)["\']\s*)?\)', re.I)
973+
match = pattern.search(line)
974+
bind_statement = None
975+
if match:
976+
bind_statement = match.group(0)
977+
# Remove the 'bind' construct from the line.
978+
line = line[:match.start()] + line[match.end():]
979+
return line, bind_statement
970980

971981
def _resolvenameargspattern(line):
982+
line, bind_cname = parse_name_for_bind(line)
972983
line = markouterparen(line)
973984
m1 = nameargspattern.match(line)
974985
if m1:
975-
return m1.group('name'), m1.group('args'), m1.group('result'), m1.group('bind')
986+
return m1.group('name'), m1.group('args'), m1.group('result'), bind_cname
976987
m1 = operatorpattern.match(line)
977988
if m1:
978989
name = m1.group('scheme') + '(' + m1.group('name') + ')'
@@ -1022,7 +1033,7 @@ def analyzeline(m, case, line):
10221033
args = []
10231034
result = None
10241035
else:
1025-
name, args, result, _ = _resolvenameargspattern(m.group('after'))
1036+
name, args, result, bindcline = _resolvenameargspattern(m.group('after'))
10261037
if name is None:
10271038
if block == 'block data':
10281039
name = '_BLOCK_DATA_'
@@ -1140,6 +1151,14 @@ def analyzeline(m, case, line):
11401151
except Exception:
11411152
pass
11421153
if block in ['function', 'subroutine']: # set global attributes
1154+
# name is fortran name
1155+
if bindcline:
1156+
bindcdat = re.search(crackline_bindlang, bindcline)
1157+
if bindcdat:
1158+
groupcache[groupcounter]['bindlang'] = {name : {}}
1159+
groupcache[groupcounter]['bindlang'][name]["lang"] = bindcdat.group('lang')
1160+
if bindcdat.group('lang_name'):
1161+
groupcache[groupcounter]['bindlang'][name]["name"] = bindcdat.group('lang_name')
11431162
try:
11441163
groupcache[groupcounter]['vars'][name] = appenddecl(
11451164
groupcache[groupcounter]['vars'][name], groupcache[groupcounter - 2]['vars'][''])
@@ -1173,7 +1192,7 @@ def analyzeline(m, case, line):
11731192
groupcounter = groupcounter - 1 # end interface
11741193

11751194
elif case == 'entry':
1176-
name, args, result, bind = _resolvenameargspattern(m.group('after'))
1195+
name, args, result, _= _resolvenameargspattern(m.group('after'))
11771196
if name is not None:
11781197
if args:
11791198
args = rmbadname([x.strip()

numpy/f2py/func2subr.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
issubroutine, issubroutine_wrap, outmess, show
2222
)
2323

24+
from ._isocbind import isoc_kindmap
2425

2526
def var2fixfortran(vars, a, fa=None, f90mode=None):
2627
if fa is None:
@@ -70,6 +71,13 @@ def var2fixfortran(vars, a, fa=None, f90mode=None):
7071
vardef = '%s(%s)' % (vardef, ','.join(vars[a]['dimension']))
7172
return vardef
7273

74+
def useiso_c_binding(rout):
75+
useisoc = False
76+
for key, value in rout['vars'].items():
77+
kind_value = value.get('kindselector', {}).get('kind')
78+
if kind_value in isoc_kindmap:
79+
return True
80+
return useisoc
7381

7482
def createfuncwrapper(rout, signature=0):
7583
assert isfunction(rout)
@@ -117,6 +125,7 @@ def add(line, ret=ret):
117125
l1 = l_tmpl.replace('@@@NAME@@@', newname)
118126
rl = None
119127

128+
useisoc = useiso_c_binding(rout)
120129
sargs = ', '.join(args)
121130
if f90mode:
122131
# gh-23598 fix warning
@@ -129,8 +138,12 @@ def add(line, ret=ret):
129138
(rout['modulename'], name, sargs))
130139
if not signature:
131140
add('use %s, only : %s' % (rout['modulename'], fortranname))
141+
if useisoc:
142+
add('use iso_c_binding')
132143
else:
133144
add('subroutine f2pywrap%s (%s)' % (name, sargs))
145+
if useisoc:
146+
add('use iso_c_binding')
134147
if not need_interface:
135148
add('external %s' % (fortranname))
136149
rl = l_tmpl.replace('@@@NAME@@@', '') + ' ' + fortranname
@@ -218,14 +231,19 @@ def add(line, ret=ret):
218231

219232
args = rout['args']
220233

234+
useisoc = useiso_c_binding(rout)
221235
sargs = ', '.join(args)
222236
if f90mode:
223237
add('subroutine f2pywrap_%s_%s (%s)' %
224238
(rout['modulename'], name, sargs))
239+
if useisoc:
240+
add('use iso_c_binding')
225241
if not signature:
226242
add('use %s, only : %s' % (rout['modulename'], fortranname))
227243
else:
228244
add('subroutine f2pywrap%s (%s)' % (name, sargs))
245+
if useisoc:
246+
add('use iso_c_binding')
229247
if not need_interface:
230248
add('external %s' % (fortranname))
231249

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module coddity
2+
use iso_c_binding, only: c_double, c_int
3+
implicit none
4+
contains
5+
subroutine c_add(a, b, c) bind(c, name="c_add")
6+
real(c_double), intent(in) :: a, b
7+
real(c_double), intent(out) :: c
8+
c = a + b
9+
end subroutine c_add
10+
! gh-9693
11+
function wat(x, y) result(z) bind(c)
12+
integer(c_int), intent(in) :: x, y
13+
integer(c_int) :: z
14+
15+
z = x + 7
16+
end function wat
17+
end module coddity

numpy/f2py/tests/test_isoc.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from . import util
2+
import numpy as np
3+
4+
class TestISOC(util.F2PyTest):
5+
sources = [
6+
util.getpath("tests", "src", "isocintrin", "isoCtests.f90"),
7+
]
8+
9+
# gh-24553
10+
def test_c_double(self):
11+
out = self.module.coddity.c_add(1, 2)
12+
exp_out = 3
13+
assert out == exp_out
14+
15+
# gh-9693
16+
def test_bindc_function(self):
17+
out = self.module.coddity.wat(1, 20)
18+
exp_out = 8
19+
assert out == exp_out

0 commit comments

Comments
 (0)
0