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(gradient) general cleanup and fix for alignment issues #3036

Merged
merged 5 commits into from
Jan 24, 2022

Conversation

X-Ryl669
Copy link
Contributor

@X-Ryl669 X-Ryl669 commented Jan 23, 2022

Description of the feature or fix

@kisvegabor Please test this PR if it fix the ASAN issue. Cheers!

If ok, we might need to find what is the minimum required alignment for the current platform instead of the hard-coded 8 bytes. Maybe 4 is enough. 2 seems not enough from the last error report.

@embeddedt
Copy link
Member

I have added ASAN support to the CI and added @kisvegabor's reproduction case as a test (so master currently fails). I can confirm that ASAN no longer reports any issues locally with this change.

@X-Ryl669
Copy link
Contributor Author

Does the CI runs on a CPU with alignment constraints (like ARM?)

@embeddedt
Copy link
Member

AFAIK it's x86-64.

@kisvegabor
Copy link
Member

Thanks for the PR!

I've tested it but locally it still failed for me after 2-3 sec.

So I looked deeper into the code and I think I found the problem. In next_in_cache:

if((uint8_t *)item + s > grad_cache_end) return NULL;

should be:

if((uint8_t *)item + s >= grad_cache_end) return NULL;

This way it work well for me but while I was testing with various configs I updated some other parts too:

  • The current version automatically set the cache size to a large value. (Although the user could change it later). I moved the default cache size into lv_conf.h. Ideally it should be 0 by default. Also rearranged the configs a little and updated Kconfig too
  • But the current code assumes the cache is large enough for any gradient. So I added to allocate non cached items if they can't fit into the cache size.
  • Renamed lv_gradient_t -> lv_grad_dsc_t and lv_gradient_cache_t -> lv_grad_t. I found that these names are more consistent with other parts of LVGL and as lv_gradient_cache_t can be non cached too this name is misleading.
  • lv_draw_sw_rect() crashed with NULL pointer errors in some cases so I fixed them too.
  • In test_style() I called lv_timer_handler() in loop the trigger some drawings.

item->map = (lv_color_t *)(grad_cache_end + sizeof(*item));
if(item->not_cached) {
uint8_t * p = (uint8_t *)item;
item->map = (lv_color_t *)(p + (ALIGN(sizeof(*item))));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think this is required. Seems like the PR has shown it isn't.
That's because sizeof(item) is aligned already.

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 not sure. E.g. -Os can pack the structs automatically, so its size can be e.g. 27 bytes

Copy link
Contributor Author

@X-Ryl669 X-Ryl669 Jan 24, 2022

Choose a reason for hiding this comment

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

No that's not possible. Else, if you had an array of those structures, the 2nd element in the array would cause unaligned access. The compiler must align the structure field to at least the most constrained field. It can pad the structure size (so if you do sizeof(*item) on a 27 bytes structure, it'll be 28 not 27, even if only the first 27 bytes are used in the structure).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In all cases, I don't think it's an issue, since if you ALIGN something that's already aligned, it'll give you the same number. It just make the code harder to follow.

Copy link
Member

Choose a reason for hiding this comment

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

No that's not possible. Else, if you had an array of those structures, the 2nd element in the array would cause unaligned access.

GCC is a little too smart sometimes. I don't know if it happens specifically in this case, but if it knows the target CPU (e.g. Cortex-M7) can support unaligned accesses, and you don't explicitly tell it not to use them with -mno-unaligned-access, I have absolutely seen it generate code that uses unaligned accesses like this.

Copy link
Member

Choose a reason for hiding this comment

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

I remember that we had issues with GCC packing structs that it wasn't able to read later. That's why we have introduced LV_ATTRIBUTE_MEM_ALIGN in lv_conf.h.

My experience is that there are too many exotic compilers and hardware so we should do all we can to support special cases too. Especially if it's so simple.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the CPU can support unaligned access, then there's no alignment constraint anymore, right?

Damn, this makes my head hurt. You are right, there's the standard on one side with all its rules and there's GCC that bending them to its will and its strong knowledge of each platform. I'm 99% sure that because of "array like" access, structs must be aligned. Even an empty struct has a non zero sizeof because of this, the second element's address must be different than the former.

I don't know all architecture LVGL builds upon. But on ARM and ESP32 (which are both platform with alignment constraint I know about), their rules are quite simple, to access a 16 bits variable, it must be on (at least) a 16 bits boundary, and so on.
So, on those platform, the ALIGN() above will do nothing since it'll happen for both pointers if the item is aligned (which is ensured by my initial PR's ALIGN usage). The last map (error_acc might be problematic since its 24 bits wide), but since it's the last, it won't push the other map to unaligned positions. This last map might require alignment to 32 bits or 16 bits, I don't know, probably need testing on a platform where alignment is an issue.

Copy link
Contributor Author

@X-Ryl669 X-Ryl669 Jan 24, 2022

Choose a reason for hiding this comment

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

@kisvegabor If I guess correctly, you are using a ARM cpu (a mac ?) so you can assert my claims by removing those ALIGN lines and it should still work.

Please notice that keeping them, at worst, waste memory since any 16bits variable (lv_color_t get pushed to a 32bits or 64bits boundary). So at best, we are wasting 6 bytes per gradient (3 maps x 32 bits alignment) and 12 bytes at worst (3 maps x 64 bits alignment) for this maybe useless alignment constraint. Since we can have many of those when animating/occluding/etc... it might worth double checking to avoid this waste.

Copy link
Member

Choose a reason for hiding this comment

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

I can't test it on an ARM based MAC at this moment but I'd accept the waste for higher reliability.

We could save much more if we didn't allocate error_acc and hmap if LV_DITHER_NONE is used.

Anyway, I'd like to merge this fix as soon as possible to not have broken code in master and we can think about improvement options later.

Copy link
Contributor Author

@X-Ryl669 X-Ryl669 Jan 24, 2022

Choose a reason for hiding this comment

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

Anyway, I'd like to merge this fix as soon as possible to not have broken code in master and we can think about improvement options later.

Sure, feel free to merge. We can change later on to allocate upon requirement (by passing the dither argument to the gradient_get function)

@X-Ryl669
Copy link
Contributor Author

In next_in_cache if((uint8_t *)item + s > grad_cache_end) return NULL;
should be: if((uint8_t *)item + s >= grad_cache_end) return NULL;

Yes, good catch!

@embeddedt embeddedt changed the title Try to fix ASAN behavior fix(gradient) fix alignment issues Jan 24, 2022
@embeddedt embeddedt changed the title fix(gradient) fix alignment issues fix(gradient) general cleanup and fix for alignment issues Jan 24, 2022
Copy link
Member

@embeddedt embeddedt left a comment

Choose a reason for hiding this comment

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

I have an idea for adding arm32 and arm64 CIs - will look into it after this is merged.

@kisvegabor
Copy link
Member

I have an idea for adding arm32 and arm64 CIs - will look into it after this is merged.

Amazing!

@kisvegabor
Copy link
Member

Merging.

@kisvegabor kisvegabor merged commit 923defd into lvgl:master Jan 24, 2022
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.

3 participants