-
Notifications
You must be signed in to change notification settings - Fork 7.9k
[Zend]: Fix unnecessary alignment in ZEND_CALL_FRAME_SLOT macro #10988
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
Conversation
@iluuu1994 I believe my reasoning is logically correct, but not 100% confident. |
I just checked this, because if the So there isn't a bug. Whether the alignment is necessary for the call frame, I have not checked that. |
@nielsdos You're right. Alignment is actually make address bigger, rather than smaller. #define ZEND_CALL_FRAME_SLOT \
((int)((ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval)) - 1) \
/ ZEND_MM_ALIGNED_SIZE(sizeof(zval)))) let's say |
@stkeke I'm not completely sure but the alignment might be there to avoid integer division with a remainder. E.g. if |
You do get the result 5 as expected: https://godbolt.org/z/aErqT9n7P |
The numbers are unrealistic but I think it does make sense. If we're skipping |
Hmm, looking at the macro ZEND_CALL_VAR_NUM: #define ZEND_CALL_VAR_NUM(call, n) \
(((zval*)(call)) + (ZEND_CALL_FRAME_SLOT + ((int)(n)))) It does not align |
Let's still assume static zend_always_inline uint32_t zend_vm_calc_used_stack(uint32_t num_args, zend_function *func)
{
uint32_t used_stack = ZEND_CALL_FRAME_SLOT + num_args + func->common.T;
if (EXPECTED(ZEND_USER_CODE(func->type))) {
used_stack += func->op_array.last_var - MIN(func->op_array.num_args, num_args);
}
return used_stack * sizeof(zval);
} |
Zvals are 16 bytes (edit: oh sorry you meant in the example), but yes it seems that we should be consistent and use alignment in both or neither place. If we make the assumption that zvals are properly aligned in many places we could add a static assert that there's no remainder without alignment. |
@iluuu1994 @nielsdos I think I started to understand why we need to make alignment in |
I think it's still worth considering to remove the alignment and replace it with a static assert, since we make the assumption that |
Yes, that's the assumption I just realized. |
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 31ed95f675..0e96e76771 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -606,8 +606,9 @@ struct _zend_execute_data {
#define ZEND_CALL_NUM_ARGS(call) \
(call)->This.u2.num_args
+ZEND_STATIC_ASSERT(ZEND_MM_ALIGNED_SIZE(sizeof(zval)) == sizeof(zval), "zval must be aligned by ZEND_MM_ALIGNMENT");
#define ZEND_CALL_FRAME_SLOT \
- ((int)((ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval)) - 1) / ZEND_MM_ALIGNED_SIZE(sizeof(zval))))
+ ((int)((ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)) + sizeof(zval) - 1) / sizeof(zval)))
#define ZEND_CALL_VAR(call, n) \
((zval*)(((char*)(call)) + ((int)(n))))
diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h
index 323b6269ee..0f2939ae34 100644
--- a/Zend/zend_execute.h
+++ b/Zend/zend_execute.h
@@ -195,8 +195,9 @@ struct _zend_vm_stack {
zend_vm_stack prev;
};
+ZEND_STATIC_ASSERT(ZEND_MM_ALIGNED_SIZE(sizeof(zval)) == sizeof(zval), "zval must be aligned by ZEND_MM_ALIGNMENT");
#define ZEND_VM_STACK_HEADER_SLOTS \
- ((ZEND_MM_ALIGNED_SIZE(sizeof(struct _zend_vm_stack)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval)) - 1) / ZEND_MM_ALIGNED_SIZE(sizeof(zval)))
+ ((ZEND_MM_ALIGNED_SIZE(sizeof(struct _zend_vm_stack)) + sizeof(zval) - 1) / sizeof(zval))
#define ZEND_VM_STACK_ELEMENTS(stack) \
(((zval*)(stack)) + ZEND_VM_STACK_HEADER_SLOTS)
diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h
index 48e648ce4b..c098d29d51 100644
--- a/Zend/zend_portability.h
+++ b/Zend/zend_portability.h
@@ -750,4 +750,10 @@ extern "C++" {
# define ZEND_CGG_DIAGNOSTIC_IGNORED_END
#endif
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */
+# define ZEND_STATIC_ASSERT(c, m) _Static_assert((c), m)
+#else
+# define ZEND_STATIC_ASSERT(c, m)
+#endif
+
#endif /* ZEND_PORTABILITY_H */ Something like this should be fine I think. |
The patch is clear enough to make sure the assumption. |
Hmm, I think I might be wrong. On second thought, your original commit might already be correct. #define ZEND_CALL_FRAME_SLOT \
((int)((sizeof(zend_execute_data) + sizeof(zval) - 1) / sizeof(zval))) The |
It looks like the implementation of |
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 original code was correct, but ZEND_MM_ALIGNED_SIZE()
is really not necessary since sizeof(zval) is already aligned.
I would change the comments to "A number of call frame slots (zvals) reserved for zend_execute_data. CV and TMP/VAR slots lay down the stack".
May be some native speaker would propose a better comment.
@dstogov Thanks for the approval. I count on @iluuu1994 for this PR, who has good |
Alignment is not necessary while calculating slots reserved for zend_execute_data and _zend_vm_stack. ZEND_STATIC_ASSERT ensures the correct alignment while code compilation. Credit is to Ilija Tovilo. PR: #10988 Signed-off-by: Tony Su <[email protected]> Reviewed-by : Ilija Tovilo Reviewed-by : Dmitry Stogov Reviewed-by : Niels Dossche
Removed the accidential backslash character (\) in zend_compile.h file. Signed-off-by: Tony Su <[email protected]>
@iluuu1994 I consolidated all the ideas and updated commit message, rebased for merge ready. |
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 don't see any problems.
Thanks everyone! |
@iluuu1994 @dstogov I learned something more ... Thanks. |
I spent almost one hour trying to understand the purpose of alignment while calculating ZEND_CALL_FRAME_SLOT and finally found that it might not be necessary. Here is my arguments:
ZEND_MM_ALIGNED_SIZE(sizeof(zval)) cleans the last 3 bits of
sizeof(zval) and actually makes it smaller.
If sizeof(zval) < 8, there will be a divied by zero error.
Aligning of the result of sizeof() opration is logically hard to
understand and does not make sense for code beginners like me.
(address can be aligned, but size should not.)
Why we have not got trouble so far?
Because sizeof(zend_execute_data) is 80 and sizeof(zval) is 16 in current code. Therefore, Alignment operations take no effect. If size changed in future, we might get logic trouble.
This patch cleans this macro and makes it logically correct and code beginner friendly.