Skip to content

Zend: Preallocate error buffer with capacity tracking#20565

Open
devnexen wants to merge 4 commits intophp:masterfrom
devnexen:zend_little_upd
Open

Zend: Preallocate error buffer with capacity tracking#20565
devnexen wants to merge 4 commits intophp:masterfrom
devnexen:zend_little_upd

Conversation

@devnexen
Copy link
Copy Markdown
Member

@devnexen devnexen commented Nov 23, 2025

Replace separate num_errors/errors fields with a single struct containing size, capacity, and a flexible array. The buffer grows by 50% when needed instead of reallocating on every recorded error.

  • Add ZEND_ERR_BUF_SIZE macro and zend_init_errors_buf() helper
  • Allocate error buffer in zend_startup for NTS builds
  • Remove redundant NULL checks since buffer is always allocated

@ndossche
Copy link
Copy Markdown
Member

It's often a good idea to choose a factor of 1.5, there's a mathematical reason behind it (https://github.com/facebook/folly/blob/main/folly/docs/FBVector.md#memory-handling).

@devnexen
Copy link
Copy Markdown
Member Author

yes will try ; even though usage and growth might not be that as critical as your (nice) link contexts.

@Girgias
Copy link
Copy Markdown
Member

Girgias commented Nov 23, 2025

It's often a good idea to choose a factor of 1.5, there's a mathematical reason behind it (https://github.com/facebook/folly/blob/main/folly/docs/FBVector.md#memory-handling).

Interesting, we currently extend arrays by a factor of 2, I wonder what would happen if we do 1.5 instead.

@ndossche
Copy link
Copy Markdown
Member

Interesting, we currently extend arrays by a factor of 2, I wonder what would happen if we do 1.5 instead.

Won't work because the hash masking depends on it being a power of 2.

@devnexen devnexen marked this pull request as ready for review November 23, 2025 13:55
@devnexen devnexen requested a review from dstogov as a code owner November 23, 2025 13:55
@TimWolla
Copy link
Copy Markdown
Member

Can we get a clearer PR title?

@devnexen devnexen changed the title Zend little upd Zend internal changes: error handling and zend_make_compiled_string_description() takes in account lineno is uint32_t Nov 24, 2025
@devnexen devnexen requested a review from ndossche December 5, 2025 23:35
Zend/zend.c Outdated
* Use pow2 realloc if it becomes a problem. */
EG(num_errors)++;
EG(errors) = erealloc(EG(errors), sizeof(zend_error_info*) * EG(num_errors));
// pondering if uint32_t is more appropriate but would need to align the allocation size then
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Storing the size like this is very ugly in my opinion. What you could do is make a struct with a size field and a flexible array member at the end.

@devnexen devnexen marked this pull request as draft January 4, 2026 20:09
@devnexen devnexen force-pushed the zend_little_upd branch 8 times, most recently from f38b0e3 to 078130f Compare January 5, 2026 07:15
@devnexen devnexen force-pushed the zend_little_upd branch 7 times, most recently from 21c2fdc to 5111ca5 Compare January 17, 2026 15:11
@devnexen devnexen changed the title Zend internal changes: error handling and zend_make_compiled_string_description() takes in account lineno is uint32_t Zend: Preallocate error buffer with capacity tracking 5111ca5 Replace separate num_errors/errors fields with a single struct containing size, capacity, and a flexible array. The buffer grows by 50% when needed instead of reallocating on every recorded error. - Add ZEND_ERR_BUF_SIZE macro and zend_init_errors_buf() helper - Allocate error buffer in zend_startup for NTS builds - Remove redundant NULL checks since buffer is always allocated Jan 17, 2026
@devnexen devnexen changed the title Zend: Preallocate error buffer with capacity tracking 5111ca5 Replace separate num_errors/errors fields with a single struct containing size, capacity, and a flexible array. The buffer grows by 50% when needed instead of reallocating on every recorded error. - Add ZEND_ERR_BUF_SIZE macro and zend_init_errors_buf() helper - Allocate error buffer in zend_startup for NTS builds - Remove redundant NULL checks since buffer is always allocated Zend: Preallocate error buffer with capacity tracking Jan 17, 2026
@devnexen devnexen marked this pull request as ready for review January 17, 2026 15:48
@devnexen devnexen requested a review from ndossche January 17, 2026 15:49
Copy link
Copy Markdown
Member

@ndossche ndossche left a comment

Choose a reason for hiding this comment

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

When dealing with nested fields in module globals, we normally use the EG(errors)->size etc syntax instead of EG(errors->size). Please change this.

I'm not sure how I feel about unconditionally allocating a buffer in the executor globals. I understand this is to allow unconditional access to fields like EG(errors)->size ? I see previously it was always safe to access EG(num_errors).

HashTable callable_convert_cache;

void *reserved[ZEND_MAX_RESERVED_RESOURCES];
struct zend_err_buf *errors;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't believe this should be at the end, please place this under record_errors

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

no it does not need to.

Zend/zend.c Outdated
EG(errors)[EG(num_errors)-1] = info;
EG(errors->size)++;
if (EG(errors->size) >= EG(errors->capacity)) {
// not sure we can get high number of errors so safe `might be` over cautious here
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please just drop this comment

Zend/zend.c Outdated

static void zend_init_errors_buf(zend_executor_globals *eg)
{
eg->errors = pemalloc(ZEND_ERR_BUF_SIZE(2), true);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is this persistent?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

well it needs to live long enough. I m not yet that familiar with Zend engine (why I m doing this PR) though. I ll try in non persistent mode just in case..

@devnexen
Copy link
Copy Markdown
Member Author

devnexen commented Jan 24, 2026

I'm not sure how I feel about unconditionally allocating a buffer in the executor globals. I understand this is to allow unconditional access to fields like EG(errors)->size ? I see previously it was always safe to access EG(num_errors).

Yes this is the reason, remember we re dealing with a "flexible" array now.

@devnexen devnexen force-pushed the zend_little_upd branch 3 times, most recently from ecc36fc to 3ac9616 Compare January 24, 2026 23:12
@devnexen
Copy link
Copy Markdown
Member Author

ping :)

@devnexen devnexen requested a review from arnaud-lb March 30, 2026 09:15
Copy link
Copy Markdown
Member

@arnaud-lb arnaud-lb left a comment

Choose a reason for hiding this comment

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

This is a good idea.

I've added some comments. I also noticed that Nora's comments weren't addressed yet (factor of 1.5, EG(errors)->size instead of EG(errors->size)).

bool record_errors;
uint32_t num_errors;
zend_error_info **errors;
struct zend_err_buf *errors;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggestion:

Suggested change
struct zend_err_buf *errors;
struct zend_err_buf errors;

And change zend_error_info *buf[1] to zend_error_info **errors in struct zend_err_buf.

This would simplify the realloc logic (we only need to reallocate EG(errors).errors), as well as initialization (EG(errors) can be zeroed).

Zend/zend.c Outdated
Comment on lines +2035 to +2040
#define COMPILED_STRING_DESCRIPTION_FORMAT "%s(%u) : %s"

ZEND_API char *zend_make_compiled_string_description(const char *name) /* {{{ */
{
const char *cur_filename;
int cur_lineno;
uint32_t cur_lineno;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please avoid unrelated changes

Replace separate num_errors/errors fields with a single struct containing
size, capacity, and a flexible array. The buffer grows by 50% when needed
instead of reallocating on every recorded error.

- Add ZEND_ERR_BUF_SIZE macro and zend_init_errors_buf() helper
- Allocate error buffer in zend_startup for NTS builds
- Remove redundant NULL checks since buffer is always allocated
@devnexen
Copy link
Copy Markdown
Member Author

This is a good idea.

I've added some comments. I also noticed that Nora's comments weren't addressed yet (factor of 1.5, EG(errors)->size instead of EG(errors->size)).

note factor of 1.5 was already addressed for a while

EG(errors).capacity + (EG(errors).capacity >> 1

@devnexen devnexen marked this pull request as draft March 30, 2026 11:28
- Change EG(errors) from pointer to embedded struct (arnaud-lb)
- Replace flexible array member with plain pointer (arnaud-lb)
- Use EG(errors).field syntax instead of EG(errors->field) (ndossche)
- Remove persistent allocation, use erealloc for the inner array
- Remove zend_init_errors_buf/ZEND_ERR_BUF_SIZE, zeroing suffices
- Free EG(errors).errors buffer in zend_free_recorded_errors
- Save/restore full EG(errors) state in error handler paths
- Revert unrelated int->uint32_t change (arnaud-lb)
@devnexen devnexen marked this pull request as ready for review March 30, 2026 12:43
Zend/zend.c Outdated
}
/* }}} */


Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unexpected white space change

Zend/zend.c Outdated
Comment on lines +1449 to +1451
uint32_t orig_num_errors = 0;
uint32_t orig_cap_errors = 0;
zend_error_info **orig_errors = NULL;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here it maybe possible to replace these 3 variables by a single zend_err_buf variable

Zend/zend.c Outdated
Comment on lines +1464 to +1466
uint32_t num_errors = EG(errors).size;
uint32_t cap_errors = EG(errors).capacity;
zend_error_info **errors = EG(errors).errors;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto

Comment on lines -1803 to -1805
if (!EG(num_errors)) {
return;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We could keep this check to avoid the memset() and other checks when there are not errors

- Save/restore EG(errors) with struct copy instead of 3 separate vars
- Remove extra blank line left from zend_init_errors_buf removal
- Restore early return in zend_free_recorded_errors to skip memset
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants