Skip to content
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

fix: add inline to prevent multiple definitions #2606

Merged
merged 5 commits into from
Aug 8, 2023

Conversation

ManasviGoyal
Copy link
Collaborator

@ManasviGoyal ManasviGoyal commented Aug 3, 2023

The following changes are made -

  • Some methods were causing multiple definitions when including LayoutBuilder in another C++ header file so inline is added to default_options, type_to_name, and type_to_numpy_like.
  • preprocessor #define is used as a temporary workaround in place of default_options as inline variables are supported starting from C++17. Once the requirements are updated to C++17, default_options can be inlined.
  • Also, two of the tests in test_1494-layout-builder.cpp which included the EmptyArray case were failing due to missing parameter string in the form, so that has been fixed too by removing parameters from the LayoutBuilder code.

@ManasviGoyal ManasviGoyal requested a review from ianna August 3, 2023 13:39
@ManasviGoyal
Copy link
Collaborator Author

ManasviGoyal commented Aug 3, 2023

@ianna I notice that it gives an error - inline variables require at least std:c++17 and LayoutBuilder.h is restricted to std:c++11.

@codecov
Copy link

codecov bot commented Aug 3, 2023

Codecov Report

Merging #2606 (85efce2) into main (ce63bf2) will increase coverage by 0.01%.
Report is 2 commits behind head on main.
The diff coverage is n/a.

Additional details and impacted files

see 2 files with indirect coverage changes

@ManasviGoyal ManasviGoyal temporarily deployed to docs-preview August 3, 2023 13:59 — with GitHub Actions Inactive
@@ -19,7 +19,7 @@ namespace awkward {

/// @brief Object of {@link BuilderOptions BuilderOptions} which sets the
/// values of the default options.
awkward::BuilderOptions default_options(1024, 1);
inline awkward::BuilderOptions default_options(1024, 1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is an inlined variable - does it need to be?

Copy link
Collaborator Author

@ManasviGoyal ManasviGoyal Aug 3, 2023

Choose a reason for hiding this comment

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

When we include LayoutBuilder.h in multiple source files, it results in multiple definitions of default_options leading to linker errors. So yes it needs to be inline.

test-fake.cpp:(.text+0x360): multiple definition of `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const awkward::type_to_numpy_like<long>()'; /tmp/ccmHoZHl.o:fake.cpp:(.text+0x360): first defined here
/usr/bin/ld: /tmp/cculuiVU.o:(.bss+0x0): multiple definition of `awkward::LayoutBuilder::default_options'; /tmp/ccmHoZHl.o:(.bss+0x0): first defined here

@ManasviGoyal
Copy link
Collaborator Author

@jpivarski default_options is an inline variable that's why it gives an error that it requires C++17.

D:\a\awkward\awkward\header-only\layout-builder\awkward/LayoutBuilder.h(22,36): error C7525: inline variables require at least '/std:c++17' [D:\a\awkward\awkward\build\tests\test_1494-layout-builder.vcxproj]

@jpivarski
Copy link
Member

Ah, right! I missed that default_options is a variable.

If you want a quick-and-dirty way to ensure that this PR does not depend on #2608, you could replace the inline variable with a preprocessor #define. That name will leak into any code that #includes this file, without namespace qualifications, so the name would need to be qualified:

#define AWKWARD_LAYOUTBUILDER_DEFAULT_OPTIONS awkward::BuilderOptions(1024, 1)

and then use AWKWARD_LAYOUTBUILDER_DEFAULT_OPTIONS in place of awkward::LayoutBuilder::default_options everywhere.


For completeness, all of the functions ought to be inline, as @henryiii pointed out. In LayoutBuilder.h, I see:

  • default_options (already addressed)
  • class Empty is not a template, but I'm not sure if the always-inline rule for header-only libraries applies to classes...

In utils.h, I see:

  • type_to_name is a templated function, but it has been instantiated for some concrete types, so I guess that needs to be inlined (already addressed)
  • type_to_numpy_like, same thing
  • type_to_form is a templated function, and I don't see any concrete instantiations, so I guess it's fine to leave it non-inlined
  • visit_impl is implemented in some concrete cases; maybe that needs to be inlined. It hasn't been: does anything bad happen if you try inlining it? (Do you know what it's used for, and do you have any tests that would verify that it's okay to inline?)

@ManasviGoyal
Copy link
Collaborator Author

If you want a quick-and-dirty way to ensure that this PR does not depend on #2608, you could replace the inline variable with a preprocessor #define. That name will leak into any code that #includes this file, without namespace qualifications, so the name would need to be qualified:

#define AWKWARD_LAYOUTBUILDER_DEFAULT_OPTIONS awkward::BuilderOptions(1024, 1)

and then use AWKWARD_LAYOUTBUILDER_DEFAULT_OPTIONS in place of awkward::LayoutBuilder::default_options everywhere.

Yes, this can be a temporary workaround until the version isn't updated to C++17. Should I add these changes to this PR?

  • visit_impl is implemented in some concrete cases; maybe that needs to be inlined. It hasn't been: does anything bad happen if you try inlining it? (Do you know what it's used for, and do you have any tests that would verify that it's okay to inline?)

visit_impl is a struct so it can't be inlined. default_options, type_to_name, and type_to_numpy_like are the ones that give linker errors so I think those might be the only one that needs to be inlined.

@jpivarski
Copy link
Member

Okay, then it sounds good. Let's use the preprocessor as a temporary workaround for not requiring C++17 yet. (Our next opportunity to do that is September 1.)

@ManasviGoyal ManasviGoyal self-assigned this Aug 3, 2023
@ManasviGoyal ManasviGoyal temporarily deployed to docs-preview August 3, 2023 20:58 — with GitHub Actions Inactive
@ManasviGoyal ManasviGoyal requested a review from ianna August 3, 2023 21:05
Copy link
Collaborator

@ianna ianna left a comment

Choose a reason for hiding this comment

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

Thanks, @ManasviGoyal! Looks good to me.

@@ -531,7 +532,8 @@ test_ListOffset_Empty() {
"\"class\": \"ListOffsetArray\", "
"\"offsets\": \"i64\", "
"\"content\": { "
"\"class\": \"EmptyArray\" "
"\"class\": \"EmptyArray\", "
"\"parameters\": {} "
Copy link
Collaborator

Choose a reason for hiding this comment

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

@ManasviGoyal why does this test change? Was it not being run before?

Copy link
Collaborator Author

@ManasviGoyal ManasviGoyal Aug 4, 2023

Choose a reason for hiding this comment

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

When I run the the LayoutBuilder tests, the assertion is failing due to the missing "parameters: {}" described in the EmptyArray form. If this test is run while running all the tests, it's strange that it doesn't give any error despite the incomplete form.

Copy link
Member

Choose a reason for hiding this comment

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

None of the Forms should explicitly require a parameters key when reading from JSON. The parameters key is always extracted with get:

def from_dict(input: Mapping) -> Form:
assert input is not None
if isinstance(input, str):
return ak.forms.NumpyForm(primitive=input)
assert isinstance(input, Mapping)
parameters = input.get("parameters", None)

(ak.forms.from_json immediately calls ak.forms.from_dict. When parameters are None, it's the same as if they're {}.)

On top of that, EmptyArray is required to not have any parameters. Explicitly providing None or {} is benign, though:

class EmptyForm(Form):
is_numpy = True
is_unknown = True
def __init__(self, *, parameters: JSONMapping | None = None, form_key=None):
if not (parameters is None or len(parameters) == 0):
raise TypeError(f"{type(self).__name__} cannot contain parameters")

If these are C++ tests that are failing, and the C++ tests are more restrictive than what the Python tests require, then it's the test that should change.

Copy link
Collaborator Author

@ManasviGoyal ManasviGoyal Aug 4, 2023

Choose a reason for hiding this comment

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

The C++ tests were failing because of the mismatch between the EmptyArray form definition in the code and the tests. If the parameters are not needed in this case, then the parameters part from the EmptyArray form in the Layoutbuilder.h code can be removed then we don't have to change the tests

std::string
form() const noexcept {
return "{ \"class\": \"EmptyArray\", \"parameters\": {} }";
}

Copy link
Member

Choose a reason for hiding this comment

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

Correct. It's also the only node type that sets it to a constant; all of the other LayoutBuilder node types take a parameters argument to pass on here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes exactly. I'll update the PR with the required changes.

@ManasviGoyal ManasviGoyal temporarily deployed to docs-preview August 4, 2023 17:44 — with GitHub Actions Inactive
@agoose77 agoose77 enabled auto-merge (squash) August 6, 2023 16:58
@agoose77 agoose77 temporarily deployed to docs-preview August 6, 2023 17:04 — with GitHub Actions Inactive
@agoose77 agoose77 disabled auto-merge August 8, 2023 12:42
@agoose77 agoose77 merged commit 0628b7f into main Aug 8, 2023
30 checks passed
@agoose77 agoose77 deleted the ManasviGoyal/fix-cpp-header-files branch August 8, 2023 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants