Relative computed goto - #533
Conversation
|
Great I'll give a try.. |
|
unfortunately gnu AS seems to choke on it.. I tried with the PHP parsers.. chokes on var_unserializer parser..I'll see if it is worth reporting.. |
|
Hi @crrodriguez - thanks, what sort of errors do you see (are they parsing errors or memory errors)? I'll have some time tomorrow to test it further. I haven't regenerated the test output, there's some output differences with current master, but semantically equivalent - is that what you are seeing when you run the tests? |
|
This seems to have an interesting effect on executable size (gcc 14.2.0 with -Os): 810592 Feb 21 07:51 uri_rfc3986 (re2c 4.0.2 -g) Maybe this is really worth investigating further? |
| buf.cstr("(typeof(*").cstr(name).cstr("))((char *)&&").str(opts->label_prefix).u32(go->table[i]->label->index); | ||
| if (opts->computed_gotos_relative) { | ||
| buf.cstr(" - (char *)&&").str(opts->label_prefix).u32(min_index); | ||
| } | ||
| elems[i] = buf.cstr(")").flush(); |
There was a problem hiding this comment.
Don't change code generation for the absolute case (you can see the changes if you run ./run_tests.py or make check).
There was a problem hiding this comment.
I need to test this still, so I'll leave this open for tracking.
It is! Since we know all the offsets, we should be able to find the smallest integer type for the table. The change in binary size seems drastic, I'm surprized to see it. |
The same thought came to mind but I think that would only work with C++ if at all, unfortunately. But for most small/medium-sized parsers uint16_t should be sufficient if the compiler doesn't decide to rearrange the earliest label, otherwise int16_t is another reasonable choice. |
Ah, right, I forgot we're not on the assembly level and we don't really know the size of the generated code and how far the labels will be. Other options that we can consider:
This is all follow-up work that should not get in the way of the initial implementation, though. BTW why is it different between C and C++? |
I was wondering if it was possible to use C++'s constexpr magic to determine the size of the offset values but it isn't possible after some tests - it doesn't treat && as constexpr values. |
It simply fails to assemble var_unserializer PHP parser..
I need to check if it is a thing that happens with one of the CFLAGS or something. The binary size change is actually pretty nice, what is surprising is the amount. |
|
Regenerating the zend_language_parser.l from PHP with this options results in a parser that compiles but crashes. .. needs further investigation. |
Thanks for testing! Could you give me the version of PHP + compiler you used to build it and any other environment information that could potentially affect it? And some commands that can reproduce the crash - I'm not terribly familiar with php. |
Im using the latest php-src master branch + this re2c PR + gcc 15 |
|
Some work in progress - I've generalised some of the code, but should be functionally the same so far (barring bugs I've introduced).
|
06e10ff to
1a92b05
Compare
1a92b05 to
9a2c785
Compare
skvadrik
left a comment
There was a problem hiding this comment.
Looks good, only a few minor issues, plus I think you forgot to include regenerated bootstrap files (syntax files, lexers, docs).
| buf.cstr("(int)("); // TODO: port to syntax file | ||
| buf.cstr("(char*)"); | ||
| buf.cstr("&&").str(opts->label_prefix).u32(go->table[i]->label->index); | ||
| buf.cstr(" - (char*)"); | ||
| buf.cstr("&&").str(opts->label_prefix).u32(min_index); | ||
| buf.cstr(")"); |
There was a problem hiding this comment.
Formatting issue: can you just glue it together into one statement, breaking the lines at 100 char limit (the way it's formatted everywhere else)? The comment should go on the line before the statement and say something like TODO: port this to syntax files and replace hardcoded `int` with `code:type_yytarget`.
There was a problem hiding this comment.
Are the following lines just indented by 4 or aligned?
There was a problem hiding this comment.
Use 4-space indent (the way you've done now is good). I'm not sure what you mean by aligned though.
There was a problem hiding this comment.
Great, thanks! I meant if the '.' on each line were aligned.
| buf.cstr("(int)("); // TODO: port to syntax file | ||
| buf.cstr("(char*)"); | ||
| buf.cstr("&&").str(opts->label_prefix).u32(acc[i].state->label->index); | ||
| buf.cstr(" - (char*)"); | ||
| buf.cstr("&&").str(opts->label_prefix).u32(min_index); | ||
| buf.cstr(")"); |
There was a problem hiding this comment.
Same comment as for lines 766-771.
| buf.cstr("(int)("); // TODO: port to syntax file | ||
| buf.cstr("(char*)"); | ||
| buf.cstr("&&").str(opts->cond_label_prefix).str(conds[i].name); | ||
| buf.cstr(" - (char*)").str(opts->cond_label_prefix).str(conds.front().name); | ||
| buf.cstr(")"); |
There was a problem hiding this comment.
Same comment as for lines 766-771.
Instead of using absolute pointers are used in the cgoto tables, we generate relative offsets from the earliest label offset within the code. This offset type is hardcoded as `int' currently.
9a2c785 to
e2c84f7
Compare
|
Thanks for your patience addressing all the comments @caffe3 ! |
I took a look at this again.. it is a bad interaction between this code generation and always_inline functions.. alays_inline function gets ..well..always inlined and vanishes from the resulting asm.. |
@crrodriguez Did you try with the final state of this PR that has been merged? I think @caffe3 fixed some error and tested that it works with php. |
I haven't found the time to test with PHP yet - PHP's build system looked more complicated than I originally thought rather than just But @crrodriguez - if you haven't already pulled the latest, please do, because there was a bug in the initial implementation which would have caused a segfault. |
|
Hi @crrodriguez - I reproduced your issue. I strongly suspect GCC has a bug where it is still reordering functions into new sections despite being used within relative gotos. I fixed it by disabling reordering with diff --git a/configure.ac b/configure.ac
index 01d9ded69b..6675e802b7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -155,13 +155,13 @@ PHP_PROG_PHP([7.4])
PHP_ARG_ENABLE([re2c-cgoto],
[whether to enable computed goto extension with re2c],
[AS_HELP_STRING([--enable-re2c-cgoto],
- [Enable re2c -g flag to optimize conditional jumps using computed goto
+ [Enable re2c --computed-gotos-relative flag to optimize conditional jumps using computed goto
extension, if supported by the compiler])],
[no],
[no])
AS_VAR_IF([PHP_RE2C_CGOTO], [no],,
-[AC_CACHE_CHECK([whether re2c -g works], [php_cv_have_re2c_cgoto],
+[AC_CACHE_CHECK([whether re2c --computed-gotos-relative works], [php_cv_have_re2c_cgoto],
[AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
int main(void)
{
@@ -176,7 +176,7 @@ label2:
[php_cv_have_re2c_cgoto=yes],
[php_cv_have_re2c_cgoto=no])])
AS_VAR_IF([php_cv_have_re2c_cgoto], [yes],
- [AS_VAR_APPEND([RE2C_FLAGS], [" -g"])])
+ [AS_VAR_APPEND([RE2C_FLAGS], [" --computed-gotos-relative"])])
])
dnl Platform-specific compile settings.
@@ -201,6 +201,9 @@ esac
dnl See https://github.com/php/php-src/issues/14140
AX_CHECK_COMPILE_FLAG([-ffp-contract=off], [CFLAGS="$CFLAGS -ffp-contract=off"])
+dnl Because it breaks re2c --computed-gotos-relative otherwise
+AX_CHECK_COMPILE_FLAG([-fno-reorder-functions], [CFLAGS="$CFLAGS -fno-reorder-functions"])
+
dnl Mark symbols hidden by default if the compiler (for example, gcc >= 4)
dnl supports it. This can help reduce the binary size and startup time.
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],I ran =====================================================================
TIME END 2025-03-05 08:03:47
=====================================================================
TEST RESULT SUMMARY
---------------------------------------------------------------------
Exts skipped : 43 (bcmath, bz2, calendar, com_dotnet, curl, dba, dl_test, enchant, exif, ffi, ftp, gd, gettext, gmp, intl, ldap, mbstring, mysqli, mysqlnd, odbc, openssl, pcntl, pdo_dblib, pdo_firebird, pdo_mysql, pdo_odbc, pdo_pgsql, pgsql, readline, shmop, skeleton, snmp, soap, sockets, sodium, sysvmsg, sysvsem, sysvshm, tidy, xsl, zend_test, zip, zlib)
Exts tested : 26
---------------------------------------------------------------------
Number of tests : 20449 14604
Tests skipped : 5845 ( 28.6%) --------
Tests warned : 0 ( 0.0%) ( 0.0%)
Tests failed : 0 ( 0.0%) ( 0.0%)
Expected fail : 8 ( 0.0%) ( 0.1%)
Tests passed : 14596 ( 71.4%) ( 99.9%)
---------------------------------------------------------------------
Time taken : 338.225 seconds
===================================================================== |
I thought relative gotos are good precisely because they don't care about the absolute location of the function, as they are relative to the start of the function. So why would function order matter here? If you suspect a compiler bug, can you get a self-contained C/C++ file so we can compile it with/without |
I think the flag is a misnomer - GCC puts different parts of the code into different .text sections to optimise whether its a hot/cold/unlikely path. The changes in the assembly are very small: https://gist.github.com/caffe3/dccc0f1908dcdc9b2f39c371c102ac46
Yes, I think that is possible - the assembly currently stands at around 40k lines... and I think only gets triggered if the code of the function is big enough though. |
|
Unfortunately there's no per-function flag that I can see helps with this instance. I tried
|
Yes, I tried with the revision that was merged or master branch. not using zend_always_inline worked around the problem for me with gcc15, I also believe this to be a compiler bug. |
|
The GCC documentation states:
What did you do to avoid |
We don't rely on |
|
Removing __attribute__((cold)) php_error_docref();
php_var_unserialize() {
static yytarget_0 = (char *) && yy14 - (char *) && yy1;
goto *&&yy1 + yytarget_0;
yy1:
yy2:
goto yy2;
yy14:
php_error_docref();
} |
|
Link to the GCC bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119158 for the record. |
I noticed #531 and I thought I'd give it a go because I've been working on some of my own code generation improvements. This is purely experimental for review/feedback.
I have only done the lightest amount of testing so far (it compiles and the c++98.re seemed to work) and it has some caveats both in my current implementation and compiler support:
@crrodriguez would you like to give this a spin to see if it works in your use-case?
To use this, add to a block, and enable
-gfor re2c:re2c:cgoto:relative = 1