Skip to content

BUG: Handle iso_c_type mappings more consistently #25226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 18, 2023
Merged

Conversation

HaoZeke
Copy link
Member

@HaoZeke HaoZeke commented Nov 22, 2023

Closes #25207. This one could use a backport. Needs:

  • Scoping TODOs (can't rewrite the entire cfuncs here)
  • Tests
  • Check that SciPy builds

As discussed below, there were basically two deficiencies:

  • Type maps were not propagated
  • Fortran types (iso_c_binding) were mapped to ignored C types
    • This is the default behavior, but is silent and the maps are done to basic (lowest precision) corresponding types

This PR fixes (pragmatically) both these deficiences, for the first problem, the fix is a simple refactor and cleanup.

The second issue is a little trickier. For now, the pragmatic solution (as done by users anyway) is to map the iso_c_binding kinds to supported f2py types, i.e. to one of:

c2py_map = {'double': 'float',
            'float': 'float',                          # forced casting
            'long_double': 'float',                    # forced casting
            'char': 'int',                             # forced casting
            'signed_char': 'int',                      # forced casting
            'unsigned_char': 'int',                    # forced casting
            'short': 'int',                            # forced casting
            'unsigned_short': 'int',                   # forced casting
            'int': 'int',                              # forced casting
            'long': 'int',
            'long_long': 'long',
            'unsigned': 'int',                         # forced casting
            'complex_float': 'complex',                # forced casting
            'complex_double': 'complex',
            'complex_long_double': 'complex',          # forced casting
            'string': 'string',
            'character': 'bytes',
            }

Pending a more complete overhaul of the generated bindings (tracked in #25229, which @Pranavchiku is also looking into) this is the optimal bugfix for #25207.

@mattip
Copy link
Member

mattip commented Nov 26, 2023

I'm not sure I can review this without some context.

@HaoZeke
Copy link
Member Author

HaoZeke commented Nov 26, 2023

I'm not sure I can review this without some context.

Sorry, so the reported issue is that as currently implemented, the iso_c_binding types introduced in #24555 are not handled the same way as manually adding .f2cmap files. So this PR fixes that, instead of just merging the type dictionaries, the mapping is considered to be the same as if the user had provided it in a .f2cmap file.

I will leave comments in the code.

Comment on lines -896 to -917
def deep_merge(dict1, dict2):
"""Recursively merge two dictionaries into a new dictionary.

Parameters:
- dict1: The base dictionary.
- dict2: The dictionary to merge into a copy of dict1.
If a key exists in both, the dict2 value will take precedence.

Returns:
- A new merged dictionary.
"""
merged_dict = deepcopy(dict1)
for key, value in dict2.items():
if key in merged_dict:
if isinstance(merged_dict[key], dict) and isinstance(value, dict):
merged_dict[key] = deep_merge(merged_dict[key], value)
else:
merged_dict[key] = value
else:
merged_dict[key] = value
return merged_dict

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer required since it is handled in process_f2cmap_dict

Comment on lines -154 to +158
for k, d1 in d.items():
for k1 in d1.keys():
d1[k1.lower()] = d1[k1]
d[k.lower()] = d[k]
for k in d.keys():
if k not in f2cmap_all:
f2cmap_all[k] = {}
for k1 in d[k].keys():
if d[k][k1] in c2py_map:
if k1 in f2cmap_all[k]:
outmess(
"\tWarning: redefinition of {'%s':{'%s':'%s'->'%s'}}\n" % (k, k1, f2cmap_all[k][k1], d[k][k1]))
f2cmap_all[k][k1] = d[k][k1]
outmess('\tMapping "%s(kind=%s)" to "%s"\n' %
(k, k1, d[k][k1]))
f2cmap_mapped.append(d[k][k1])
else:
errmess("\tIgnoring map {'%s':{'%s':'%s'}}: '%s' must be in %s\n" % (
k, k1, d[k][k1], d[k][k1], list(c2py_map.keys())))
f2cmap_all, f2cmap_mapped = process_f2cmap_dict(f2cmap_all, d, c2py_map, True)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is used to handle user provided f2cmap files, now refactored into a function, process_f2cmap_dict which is also used for the iso_c_binding types.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially, the bug in #24555 was that I didn't update f2cmap_mapped.

Comment on lines -129 to 134
f2cmap_all = deep_merge(f2cmap_all, iso_c_binding_map)
# Add ISO_C handling
c2pycode_map.update(isoc_c2pycode_map)
c2py_map.update(iso_c2py_map)
f2cmap_all, _ = process_f2cmap_dict(f2cmap_all, iso_c_binding_map, c2py_map)
# End ISO_C handling
f2cmap_default = copy.deepcopy(f2cmap_all)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with the old handling is that updating the f2cmap_all doesn't preserve / update the mapping basically.

Comment on lines +987 to +988

return f2cmap_all, f2cmap_mapped
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return here is key, previously only the f2cmap dictionary was being updated, now we ensure the mapped types are tracked as well.

@HaoZeke
Copy link
Member Author

HaoZeke commented Nov 26, 2023

@mattip I rescoped this into a smaller minimal fix and added comments now, the long and short of it is I forgot to consider propagating the kind map for variables my older PR (#25226 (comment)).

@HaoZeke
Copy link
Member Author

HaoZeke commented Nov 26, 2023

Actually making this a draft to check the subroutine mentioned in the original issue.

@HaoZeke HaoZeke marked this pull request as draft November 26, 2023 19:57
@HaoZeke HaoZeke marked this pull request as ready for review November 26, 2023 20:11
@HaoZeke HaoZeke marked this pull request as draft November 26, 2023 20:21
Fixes numpygh-25226

MAINT: Cleanup and scope fix better [f2py]
Comment on lines -15 to +20
'c_short': 'short int',
'c_long': 'long int',
'c_long_long': 'long long int',
'c_signed_char': 'signed char',
'c_size_t': 'size_t',
'c_int8_t': 'int8_t',
'c_int16_t': 'int16_t',
'c_int32_t': 'int32_t',
'c_int64_t': 'int64_t',
'c_int_least8_t': 'int_least8_t',
'c_int_least16_t': 'int_least16_t',
'c_int_least32_t': 'int_least32_t',
'c_int_least64_t': 'int_least64_t',
'c_int_fast8_t': 'int_fast8_t',
'c_int_fast16_t': 'int_fast16_t',
'c_int_fast32_t': 'int_fast32_t',
'c_int_fast64_t': 'int_fast64_t',
'c_intmax_t': 'intmax_t',
'c_intptr_t': 'intptr_t',
'c_ptrdiff_t': 'intptr_t',
'c_short': 'short', # 'short' <=> 'int' for now
'c_long': 'long', # 'long' <=> 'int' for now
'c_long_long': 'long_long',
'c_signed_char': 'signed_char',
'c_size_t': 'unsigned', # size_t <=> 'unsigned' for now
Copy link
Member Author

@HaoZeke HaoZeke Nov 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on here is that the original PR for ISO_C_BINDINGS was overly optimistic. F2PY can only support maps which are defined as part of the c2py keys, and then those need to have corresponding cfuncs snippets to generate binding code. For now, this performs forced casting to known types to get around that.

Eventually, the various maps in capi_maps will be updated to get more correct bindings #25229. However, for now this is exactly the handling done typically at the user end via the f2cmap files.

@HaoZeke HaoZeke marked this pull request as ready for review November 26, 2023 20:55
@HaoZeke
Copy link
Member Author

HaoZeke commented Nov 26, 2023

@mattip, let me know if this is clearer now :)

@Pranavchiku
Copy link

I have a potential fix for #25229 ready, do you wish me to open a PR or wait to get this merged?

@HaoZeke
Copy link
Member Author

HaoZeke commented Nov 27, 2023

I have a potential fix for #25229 ready, do you wish me to open a PR or wait to get this merged?

We should wait for this to get merged, but feel free to open a draft PR, we can start iterating over it.

@HaoZeke
Copy link
Member Author

HaoZeke commented Dec 18, 2023

@mattip does the extended rationale make sense?

@charris charris added the 09 - Backport-Candidate PRs tagged should be backported label Dec 18, 2023
@mattip mattip merged commit 003c846 into numpy:main Dec 18, 2023
@mattip
Copy link
Member

mattip commented Dec 18, 2023

Let's try this, hoepfully we will get some end-user feedback before the release.

Thanks @HaoZeke

@HaoZeke HaoZeke deleted the gh25207 branch December 19, 2023 00:25
charris pushed a commit to charris/numpy that referenced this pull request Dec 23, 2023
Fixes numpygh-25226

MAINT: Cleanup and scope fix better [f2py]
@charris charris changed the title BUG: Handle iso_c_type mappings more consistently BUG: Handle iso_c_type mappings more consistently Dec 23, 2023
@charris charris removed the 09 - Backport-Candidate PRs tagged should be backported label Dec 23, 2023
@HaoZeke HaoZeke restored the gh25207 branch June 15, 2024 23:40
@HaoZeke HaoZeke deleted the gh25207 branch January 18, 2025 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BUG: c2capi_map entries missing for iso_c_binding in f2py
4 participants