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 gcc 8 function warning #899

Merged
merged 6 commits into from
Oct 9, 2020
Merged

fix gcc 8 function warning #899

merged 6 commits into from
Oct 9, 2020

Conversation

mmomtchev
Copy link
Contributor

How about this fix for #807 ? It is very clean, but needs testing on different compilers

Copy link
Collaborator

@agnat agnat left a comment

Choose a reason for hiding this comment

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

Hi @mmomtchev,
thanks for looking into this. I agree with @kkoopa's comment from the other thread. I'd just use the cast. Unless I'm missing something?

Now that I think about it, I'm not even sure why this works... if it works... very sneaky.

nan.h Outdated
@@ -2280,7 +2280,7 @@ inline void AsyncExecute (uv_work_t* req) {
worker->Execute();
}

inline void AsyncExecuteComplete (uv_work_t* req) {
inline void AsyncExecuteComplete(uv_work_t* req, int status = 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hehe, to be honest, I think it is a bit too subtle. But it is kind of pretty ... in a sneaky way. Can we drop the default value and just use a hard cast a couple of lines below?

reinterpret_cast<(void (*)(void))>(AsyncExecuteComplete)

... or similar?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure we can, but for me this is the root cause of the problem - AsyncExecuteComplete can be called with 1 or with 2 arguments - not depending on the circumstances like most optional arguments, but depending on the libuv version
So this is, in a way, a variable argument function 😃

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hehe, yes. I do understand the idea. But it's not really. I'm a bit rusty, but IIRC a function with a default argument value behaves exactly like an overloaded function. There are two valid signatures. The compiler has to choose at compile-time. So, how is this not ambiguous? Anyway, since your solution does not get rid of the hard reinterpret_cast<>() either, I don't see a real advantage of doing it this way.

It's not that I'm totally opposed here, but to merge this I need a thorough explanation how and why this works. At the very least it needs a source code comment. In two weeks nobody knows why this thing has a default argument. You have to admit, considering that it just side-steps a warning it is rather obscure...

Copy link
Collaborator

Choose a reason for hiding this comment

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

Here is a meta function that avoids the warning and gets rid of the cast. I didn't really test it, but this should do it:

inline void AsyncExecuteComplete (uv_work_t* req) {
  AsyncWorker* worker = static_cast<AsyncWorker*>(req->data);
  worker->WorkComplete();
  worker->Destroy();
}

template <typename CB> class UVAfterWorkAdapter;

template <>
class UVAfterWorkAdapter<void (*)(uv_work_t *)> {
  void invoke(uv_work_t * req) { AsyncExecuteComplete(req); }
};

template <>
class UVAfterWorkAdapter<void (*)(uv_work_t *, int)> {
  void invoke(uv_work_t * req, int status) { AsyncExecuteComplete(req); }
};

inline void AsyncQueueWorker (AsyncWorker* worker) {
  uv_queue_work(
      GetCurrentEventLoop()
    , &worker->request
    , AsyncExecute
    , UVAfterWorkAdapter<uv_after_work_cb>::invoke
  );
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very good point, the cast is not needed with the optional parameter at all
No need for an intermediate template

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, nice! Yes, that I can understand. How did I miss that? 🤔 Anyway... could you add a little comment on AsyncExecuteComplete explaining the default argument?

@agnat
Copy link
Collaborator

agnat commented Oct 9, 2020

Hm, this is odd. I still had a non-good feeling about this and I tried to reproduce this kind of "variable argument function". I can not get it to work. Could you take a look?

extern "C" {
  struct req { int dummy; };
  typedef void (*old_cb)(req * r);
  typedef void (*new_cb)(req * r, int status);

  void old_api(old_cb) {};
  void new_api(new_cb) {};
};

inline void complete(req * r, int status = 0) {}

int main(void) {
  old_api(complete);
  new_api(complete);
}

This is the same thing, right? Well, it does not compile:

$ c++ --std c++17 ttt.cpp -o ttt
ttt.cpp:14:3: error: no matching function for call to 'old_api'
  old_api(complete);
  ^~~~~~~
ttt.cpp:8:6: note: candidate function not viable: no known conversion from 'void (req *, int)' to 'old_cb'
      (aka 'void (*)(req *)') for 1st argument
void old_api(old_cb){};
     ^
1 error generated.

This is on OSX using clang. Am I missing something? According to your research this should work, right?

@kkoopa
Copy link
Collaborator

kkoopa commented Oct 9, 2020 via email

@agnat
Copy link
Collaborator

agnat commented Oct 9, 2020

Thanks! So, I did recall correctly in this this comment. It is ambiguous. Ok, meta function it is ... or #ifdef's. 😜

Why we don't hit this in testing is another matter. We have tests for async workers, right? 🤔

@kkoopa
Copy link
Collaborator

kkoopa commented Oct 9, 2020 via email

@agnat
Copy link
Collaborator

agnat commented Oct 9, 2020

Yes, but this PR should have tripped the osx build, which uses clang, no?

@kkoopa
Copy link
Collaborator

kkoopa commented Oct 9, 2020 via email

@addaleax
Copy link
Member

addaleax commented Oct 9, 2020

This PR currently creates only a two-argument version of the function. The fact that there is a default value for status only affects direct calls to the function, not calls made to it through the function pointer, because as a function pointer it always has the type void (*)(uv_work_t*, int) now.

That is, if you want to have a function that can be used as a function pointer which receives either 1 or 2 arguments, you need to use overloads instead of default arguments:

// Variant 1: Previous code, only 1-argument version, needs cast
void AsyncExecuteComplete (uv_work_t* req) { ... }

// Variant 2: Previous code, only 2-argument version, will not work as a 1-argument version (!)
// `= 0` can be dropped, it doesn’t change anything in this context
void AsyncExecuteComplete (uv_work_t* req, int status = 0) { ... }

// Variant 3: Both variants, one implicitly calls the other, works everywhere
void AsyncExecuteComplete (uv_work_t* req) { ... }
void AsyncExecuteComplete (uv_work_t* req, int status) {
  AsyncExecuteComplete(req);
}

With Variant 3, the compiler should automatically pick the correct one to pass to uv_queue_work().

@kkoopa
Copy link
Collaborator

kkoopa commented Oct 9, 2020 via email

@agnat
Copy link
Collaborator

agnat commented Oct 9, 2020

@addaleax is right. Variant 3 works:

#include <iostream>
extern "C" {
  struct req { char dummy; };
  typedef void (*old_cb)(req * r);
  typedef void (*new_cb)(req * r, int status);

  void old_api(old_cb f){ f(0); };
  void new_api(new_cb f){ f(0, 0); };

};

inline void complete(req * r) { std::cerr << "0\n"; }
inline void complete(req * r, int status) { std::cerr << "1\n"; }

int main(void) {
  old_api(complete);
  new_api(complete);
}

I like it. Overloads don't scale, but we only need two. And libuv is not exactly a moving target.

@agnat
Copy link
Collaborator

agnat commented Oct 9, 2020

Exactly! Way too subtle. 😜

Copy link
Collaborator

@agnat agnat left a comment

Choose a reason for hiding this comment

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

Almost there. One bug, one performance nit...

nan.h Outdated
AsyncWorker* worker = static_cast<AsyncWorker*>(req->data);
worker->WorkComplete();
worker->Destroy();
}
void AsyncExecuteComplete (uv_work_t* req, int status) {
AsyncExecute(req);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should be AsyncExecuteComplete(req), right?

nan.h Outdated
@@ -2284,11 +2284,14 @@ inline void AsyncExecute (uv_work_t* req) {
* 2 arguments since node-v0.9.4
* https://github.com/libuv/libuv/commit/92fb84b751e18f032c02609467f44bfe927b80c5
*/
inline void AsyncExecuteComplete(uv_work_t* req, int status = 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Better keep it inline. It doesn't matter for the old signature, but it does for the new one with status below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

doesn't build with g++-8
I wonder if you can take the address of an overloaded function by resolving the fingerprint? I think this works only for direct calls too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

@addaleax addaleax Oct 9, 2020

Choose a reason for hiding this comment

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

This is in a header file, so not keeping it inline has the potential to lead to linking errors if an addon happens to include nan twice (I know this wouldn’t be a common scenario, but it is a realistic one)

I don’t see why keeping it inline wouldn’t work, though, that surprises me – this should generally be possible with GCC 8:

https://godbolt.org/z/qx9szb

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is not about the inline:

/home/mmom/src/node-gdal-async/build/../node_modules/nan/nan.h:2292: multiple definition of `Nan::AsyncExecuteComplete(uv_work_s*, int)'; Debug/obj.target/gdal/src/utils/typed_array.o:/home/mmom/src/node-gdal-async/build/../node_modules/nan/nan.h:2292: first defined here

I get a weird error for multiple definitions, both of them on the same line 😄

Copy link
Collaborator

Choose a reason for hiding this comment

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

Both need to be inline.
This works fine with
Apple clang version 12.0.0 (clang-1200.0.32.2)

diff --git a/nan.h b/nan.h
index 580d3b5..6135f73 100644
--- a/nan.h
+++ b/nan.h
@@ -2284,12 +2284,12 @@ inline void AsyncExecute (uv_work_t* req) {
  * 2 arguments since node-v0.9.4
  * https://github.com/libuv/libuv/commit/92fb84b751e18f032c02609467f44bfe927b80c5
  */
-void AsyncExecuteComplete(uv_work_t *req) {
+inline void AsyncExecuteComplete(uv_work_t *req) {
   AsyncWorker* worker = static_cast<AsyncWorker*>(req->data);
   worker->WorkComplete();
   worker->Destroy();
 }
-void AsyncExecuteComplete (uv_work_t* req, int status) {
+inline void AsyncExecuteComplete (uv_work_t* req, int status) {
   AsyncExecuteComplete(req);
 }

Copy link
Contributor Author

@mmomtchev mmomtchev Oct 9, 2020

Choose a reason for hiding this comment

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

does anyone understand why the functions need to be inline?
@agnat's example works without it

Copy link
Collaborator

Choose a reason for hiding this comment

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

The multifile test successfully detects the aforementioned multiple definition problem at the link stage. As @addaleax mentioned, this is a header library. In a header, ordinary, free functions may only be defined if marked inline, which, in C++ (at least from 98 onwards), has nothing to do with actual inlining of code (that is an implementation optional detail of the compiler, which a modern compiler mostly ignores. In g++, I think it might affect the inlinability-score slightly) . What the keyword actually means is that multiple definitions are allowed, with more leeway since C++17.

Copy link
Member

Choose a reason for hiding this comment

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

It’s the “realistic scenario” I talked above – if an addon includes nan.h twice, i.e. from two different files, then both of those addons will contain definitions for these functions. For inline functions that’s allowed, for regular functions not (because one can’t know in advance whether inline functions will have an explicit definition after compiling or not, because they might have been, well, inlined 🙂 ).

Copy link
Collaborator

Choose a reason for hiding this comment

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

As an addendum, the C++17 thing I mentioned applies to inline variables, I misremembered that it had anything to do with functions. Note that for non-free functions (member functions), inline is implicit if entirely defined in the class declaration. If the definition is outside the class declaration, inline is not implicit.

Copy link
Collaborator

@agnat agnat left a comment

Choose a reason for hiding this comment

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

LGTM.

@kkoopa kkoopa merged commit 35f0fab into nodejs:master Oct 9, 2020
@kkoopa
Copy link
Collaborator

kkoopa commented Oct 9, 2020

Thank you all for finally getting this addressed. Since it is weekend, I will make a release during next week.

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