Skip to content

Expand php-wasm/node multi-worker support to Asyncify builds #2317

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

Draft
wants to merge 10 commits into
base: trunk
Choose a base branch
from

Conversation

brandonpayton
Copy link
Member

Motivation for the change, related issues

We want to be able to use multiple workers in places that don't have JSPI support.

Implementation details

TBD

Testing Instructions (or ideally a Blueprint)

TBD

@brandonpayton brandonpayton requested a review from adamziel July 2, 2025 21:22
@brandonpayton brandonpayton self-assigned this Jul 2, 2025
@brandonpayton
Copy link
Member Author

I am encountering a null-or-signature-mismatch error here:
Screenshot 2025-07-02 at 7 00 17 PM

The line is this, the zend_rc_dtor_func call in this function:

ZEND_API void ZEND_FASTCALL rc_dtor_func(zend_refcounted *p)
{
	ZEND_ASSERT(GC_TYPE(p) <= IS_CONSTANT_AST);
	zend_rc_dtor_func[GC_TYPE(p)](p);
}

I was able to find the GC_TYPE by expanding the refcounted pointer struct while debugging in Cursor. The GH type num was 7, which is an offset in this array:

static const zend_rc_dtor_func_t zend_rc_dtor_func[] = {
	[IS_UNDEF] =        (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_NULL] =         (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_FALSE] =        (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_TRUE] =         (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_LONG] =         (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_DOUBLE] =       (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_STRING] =       (zend_rc_dtor_func_t)zend_string_destroy,
	[IS_ARRAY] =        (zend_rc_dtor_func_t)zend_array_destroy,
	[IS_OBJECT] =       (zend_rc_dtor_func_t)zend_objects_store_del,
	[IS_RESOURCE] =     (zend_rc_dtor_func_t)zend_list_free,
	[IS_REFERENCE] =    (zend_rc_dtor_func_t)zend_reference_destroy,
	[IS_CONSTANT_AST] = (zend_rc_dtor_func_t)zend_ast_ref_destroy
};

If the GC type is indeed 7, then it looks like the dtor is zend_array_destroy(). Its signature doesn't look that different from the other dtor signatures I've examined, but I can instrument that function and its caller above to see if we can learn more.

@adamziel
Copy link
Collaborator

adamziel commented Jul 3, 2025

Any chance it would work on trunk with @mho22 wrapper? Or was that a completely different code path?

@brandonpayton
Copy link
Member Author

Any chance it would work on trunk with @mho22 wrapper? Or was that a completely different code path?

It might! I think it is a different path, but it feels like it could be a similar thing. Will try shortly. :)

@brandonpayton
Copy link
Member Author

After merging trunk into this branch, I'm still seeing the same issue. Will instrument php-src and see what can be found.

@mho22
Copy link
Collaborator

mho22 commented Jul 3, 2025

@adamziel @brandonpayton For the record, my wrapper is only targeting PHP7.4 and PHP7.3 to replace the old EMULATE_FUNCTION_POINTER_CASTS option applied to PHP versions lower than 8 :

RUN if [ "${PHP_VERSION:0:1}" -lt "8" ]; then \
	echo -n ' -s EMULATE_FUNCTION_POINTER_CASTS=1' >> /root/.emcc-php-wasm-flags; \
fi

It may be the same issue but Brandon is right, that's a new one. And the zend_array_destroy function seems to have a correct signature :

ZEND_API void ZEND_FASTCALL zend_array_destroy(HashTable *ht)

But maybe not, I think HashTable *ht should be replaced with zend_array to fit with the zend_refcounted *p from :

typedef void (ZEND_FASTCALL *zend_rc_dtor_func_t)(zend_refcounted *p);

So maybe you'll need to apply a new patch [ to PHP8.4 and PHP8.3 at least ] like this :

+ static void zend_array_destroy_wrapper(zend_refcounted *p) {
+    HashTable *ht = (HashTable*)p;
+    zend_array_destroy(ht);
+}

static const zend_rc_dtor_func_t zend_rc_dtor_func[] = {
	[IS_UNDEF] =        (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_NULL] =         (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_FALSE] =        (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_TRUE] =         (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_LONG] =         (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_DOUBLE] =       (zend_rc_dtor_func_t)zend_empty_destroy,
	[IS_STRING] =       (zend_rc_dtor_func_t)zend_string_destroy,
-	[IS_ARRAY] =        (zend_rc_dtor_func_t)zend_array_destroy,
+	[IS_ARRAY] =        zend_array_destroy_wrapper
	[IS_OBJECT] =       (zend_rc_dtor_func_t)zend_objects_store_del,
	[IS_RESOURCE] =     (zend_rc_dtor_func_t)zend_list_free,
	[IS_REFERENCE] =    (zend_rc_dtor_func_t)zend_reference_destroy,
	[IS_CONSTANT_AST] = (zend_rc_dtor_func_t)zend_ast_ref_destroy
};

I hope this helps.

@brandonpayton
Copy link
Member Author

But maybe not, I think HashTable *ht should be replaced with zend_array to fit with the zend_refcounted *p from :

Thank you, @mho22. 🙇 This discussion is helpful. Hopefully, we are close to where the problem is.

I took a look at the HashTable and zend_array types, and they are both type aliases for the same struct like:

typedef struct _zend_array HashTable;
typedef struct _zend_array      zend_array;

My mental model suggests those types are equivalent and would not conflict, but it would be good to try a wrapper to confirm. Also, maybe there is some other type that is marked with IS_ARRAY that is not a HashTable or zend_array... We'll see!

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