-
Notifications
You must be signed in to change notification settings - Fork 683
Add #if control around GCC builtin functions so that the code can be compiled using non-GCC compilers. #1483
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
Add #if control around GCC builtin functions so that the code can be compiled using non-GCC compilers. #1483
Conversation
@@ -1,4 +1,5 @@ | |||
/* Copyright JS Foundation and other contributors, http://js.foundation | |||
/* Copyright 2014-2016 Samsung Electronics Co., Ltd. | |||
* Copyright 2016 University of Szeged. | |||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't change the copyrights.
@@ -43,6 +44,10 @@ | |||
/* | |||
* Conditions' likeliness, unlikeliness. | |||
*/ | |||
#ifndef (__GNUC__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't enclose the proceeding identifier with brackets.
@@ -43,6 +44,10 @@ | |||
/* | |||
* Conditions' likeliness, unlikeliness. | |||
*/ | |||
#ifndef (__GNUC__) | |||
#define __builtin_expect(expression, expected_value) (expression) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should wrap the defines in an #ifdef to ensure compilation on other compilers,
instead of define the __builtin_expect()
. Like this.
#ifdef __GNUC__
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#else /* ! __GNUC__ */
#define likely(x) (x)
#define unlikely(x) (x)
#endif /* __GNUC__ */
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No space after !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, turning the macros into a "no op" is preferred over redefining the (non-existant) builtins.
Please fix the DCO in the commit message. Check travis log for details: |
#define JERRY_UNREACHABLE() __builtin_unreachable () | ||
#else /* !__GNUC__ */ | ||
#define JERRY_UNREACHABLE() abort(); /* but what if they haven't included stdlib?!? */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we should change it to JERRY_ASSERT (0);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zherczeg The problem is that it would change the semantics of JERRY_UNREACHABLE() in release builds since JERRY_ASSERT() becomes a "no op" when JerryScript is built in release mode. E.g. today __builtin_unreachable() is called when JERRY_UNREACHABLE() is hit in a release build.
Adding abort() and an include directive for stdlib.h should be fine as this yields the intended behavior no matter which compiler is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this isn't exactly working that way. __builtin_unreachable is NOT a function call, it just notifies the compiler, that the code after that is unreachable, and it can optimize the code that way. The compiler is very serious about it even in debug mode, so we needed to add an assert to capture such issues instead of random crashes.
Hm, perhaps the best would be an empty macro in relase, and an ASSERT in debug. Perhaps the ASSERT is added automatically in debug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's some misunderstanding here :)
__builtin_unreachable()
is a built-in function which causes undefined behavior when called during actual execution. Since it's a function that never returns the compiler can make assumptions based on that (but so can the compiler about abort()
which also never returns). The current behavior of JERRY_UNREACHABLE()
works just fine in debug builds with a non-GCC compiler so we don't need to worry about that. However, for release builds we need an alternative to __builtin_unreachable()
since this is a GCC-specific function.
abort()
provides us exactly that: It terminates the program in a controlled manner (rather than just crashing the program) and since abort()
is part of the C standard library this will work just fine with any C99-compliant compiler.
Makes sense? :)
(BTW assert()
is typically implemented on top of abort()
e.g. it prints some details about the location of the assertion in the code and then just calls abort()
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I forgot to mention: The key difference between __builtin_unreachable()
and abort()
is that calling __builtin_unreachable()
yields undefined behavior while calling abort()
is perfectly valid, e.g. not triggering any kind of undefined behavior. The compiler obviously can optimize much more aggressively when undefined behavior is involved. However, since the goal here is to have a solution that works in a standard C99 environment without relying on any compiler-specific built-in functions, abort()
seems to be the closest match to __builtin_unreachable()
in terms of intended behavior. Losing some optimization opportunities is essentially the price that needs to be paid to get a solution that works in any standard C99 environment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some memories about __builtin_unreachable()
, when a bad code actually reached this point, and no return code was generated by the GCC. Hence the code entered into the next function. Hard to debug. This was the reason while we added an assert before __builtin_unreachable()
, so we can capture these issues in debug mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, GCC can do that since hitting __builtin_unreachable()
triggers undefined behavior :)
|
||
#ifndef (__GNUC__) | ||
#define __builtin_expect(expression, expected_value) (expression) | ||
#endif /* !__GNUC__ */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could remove this if we follow @robertsipka suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this should be removed :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this! Making it easy to build JerryScript with all the different compilers out there is very important for the project.
For more information about the DCO please also have a look at our Contribution Guidelines: https://github.com/jerryscript-project/jerryscript/blob/master/CONTRIBUTING.md
@@ -43,6 +44,10 @@ | |||
/* | |||
* Conditions' likeliness, unlikeliness. | |||
*/ | |||
#ifndef (__GNUC__) | |||
#define __builtin_expect(expression, expected_value) (expression) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, turning the macros into a "no op" is preferred over redefining the (non-existant) builtins.
#define JERRY_UNREACHABLE() __builtin_unreachable () | ||
#else /* !__GNUC__ */ | ||
#define JERRY_UNREACHABLE() abort(); /* but what if they haven't included stdlib?!? */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zherczeg The problem is that it would change the semantics of JERRY_UNREACHABLE() in release builds since JERRY_ASSERT() becomes a "no op" when JerryScript is built in release mode. E.g. today __builtin_unreachable() is called when JERRY_UNREACHABLE() is hit in a release build.
Adding abort() and an include directive for stdlib.h should be fine as this yields the intended behavior no matter which compiler is used.
|
||
#ifndef (__GNUC__) | ||
#define __builtin_expect(expression, expected_value) (expression) | ||
#endif /* !__GNUC__ */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this should be removed :)
#else /* !__GNUC__ */ | ||
#define JERRY_UNREACHABLE() { \ | ||
#include <stdlib.h> \ | ||
abort(); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The question still stands: do we need to do anything here?
extern void __noreturn jerry_unreachable (const char *, const char *, const uint32_t);
In debug mode, the unreachable function calls a real function, which yields an error and terminates the process. In release mode the undefinied behaviour is expected. Hence why we just do nothing here? Why do we increase the binary size by adding abort calls here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I agree, I think based on our previous discussion in this PR JERRY_UNREACHABLE()
can become a "no op" when built with a non-GCC compiler. __builtin_unreachable()
is essentially just a hint to the compiler that the code can never be reached.
@t-harvey Can you please change |
8abf301
to
bc3d01a
Compare
|
||
#ifndef __GNUC__ | ||
#define __builtin_expect(expression, expected_value) (expression) | ||
#endif /* !__GNUC__ */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I see it this is the main reason why this patch hasn't been merge yet. The assert function uses __builtin_expect (below in a condition).
@zherczeg , @tilmannOSG : Should this be remained in this form, or should the assert function be defined differently, or do you have any other ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guys -- I thought the only reason this hadn't yet been merged was b/c I forgot to ping Tillman that I had added the DCO message (sorry, Tillman! :-) -- what am I missing/what can I do to fix this other problem? (I had assumed that if the code passed Travis that it was good to go...?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohh, I missed Tilmann's comment, sorry, this define has been approved earlier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to replace the __builtin_expect() to unlikely() here. The project should not rely on __builtin_expect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @zherczeg, since the only direct use of __builtin_expect()
is in assert.h
this is best fixed by just replacing this invocation with unlikely()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tilmannOSG && @zherczeg -- I'd like to propose a different way of looking at this:
The GCC constructs are useful (to GCC :-); removing them seems like we're getting rid of functionality. Since, with this #def, they behave just fine for every other compiler, why not leave them?
Secondly, "unlikely" is only defined in jrt.h; using it all over the place forces (read: makes it a really good idea :-) us to reorganize things in .h files (for this change, because now we suddenly have to include jrt.h in assert.h, which makes no obvious sense) -- these kinds of reorganizations are required maintenance for an evolving project, but kind of out of the scope for this otherwise simple change (since the bigger fix requires system architectural knowledge, discussion of division, what/when/where to include what, etc... :-).
...so for the record: I'm not trying to get out of work by suggesting that we leave this code as written :-)...but doing this correctly will take a lot of work (and reduce functionality!), so I am actually saying that we should check this in as is...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@t-harvey Good point, I think in this case the pragmatic solution is to just drop the __builtin_expect()
use from assert.h
. Since this is only affects the code in jerry-libc (rather than jerry-core) and most of the assert()
calls seem to be in the printf()
implementation (which is not really performance-critical code) the performance impact should be negligible. Builds with asserts enabled are expected to have a noticeable performance overhead anyway.
@@ -343,7 +343,7 @@ ecma_builtin_global_object_parse_int (ecma_value_t this_arg, /**< this argument | |||
while (string_curr_p > start_p) | |||
{ | |||
ecma_char_t current_char = *(--string_curr_p); | |||
ecma_number_t current_number; | |||
ecma_number_t current_number = ECMA_NUMBER_MINUS_ONE; | |||
|
|||
if ((current_char >= LIT_CHAR_LOWERCASE_A && current_char <= LIT_CHAR_LOWERCASE_Z)) | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would simply add an assert around lit_char_is_decimal_digit (current_char).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zherczeg While that might work to eliminate the compiler warning, it seems explicitly initializing the variable is still the better option (we should initialize all variables by default to avoid any bugs due to uninitialized variables).
|
||
#ifndef __GNUC__ | ||
#define __builtin_expect(expression, expected_value) (expression) | ||
#endif /* !__GNUC__ */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to replace the __builtin_expect() to unlikely() here. The project should not rely on __builtin_expect.
@t-harvey If you just replace the |
Ok. I haven't noticed we are talking about libc. Yes, just drop it. |
@t-harvey So if you just remove the |
a4bd63a
to
9885d07
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM after the endif fixed.
#define JERRY_UNREACHABLE() __builtin_unreachable () | ||
#else /* !__GNUC__ */ | ||
#define JERRY_UNREACHABLE() | ||
#endif /* !__GNUC__ */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#endif /* __GNUC__ */
compiled using non-GCC compilers. This included adding an initialization to a variable that looks(!) like it can reach a use before being initialized (b/c we turned JERRY_UNREACHABLE into a nop) -- an example: int foo; switch (value_can_only_be_one_or_two) case 1: case 2: foo = 5; default: JERRY_UNREACHABLE(); x = foo +1; ...the compiler assumes that the path can go through the default case, which leaves the value of foo undefined. JerryScript-DCO-1.0-Signed-off-by: Timothy Harvey [email protected]
9885d07
to
ede4604
Compare
The header says it all -- only some of the GCC builtin functions are protected by "#ifdef (GNUC)", and this change would protect the builtin functions in jrt.h and assert.h.