Skip to content

Commit 7181cb3

Browse files
Simplify task function validation
1 parent be767c7 commit 7181cb3

12 files changed

Lines changed: 165 additions & 341 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ Task Dependencies
5353

5454
A task receives its opcodes, captured values, and arguments. It does not inherit the submitting runtime's user-defined functions, classes, or constants.
5555

56-
Load the same function source used by the submitting runtime through the runtime bootstrap, or pass a `Closure` created with `Closure::fromCallable()` as a task argument. Direct function calls are validated against the destination runtime before the task starts. A missing or different function prevents all task code from executing and is reported by `Future::value()`; a `try/catch` inside the task cannot catch this task-contract error.
56+
Statically named function calls in a task are checked against the destination runtime before the task starts. A missing function prevents all task code from executing and is reported by `Future::value()`; a `try/catch` inside the task cannot catch this error.
5757

58-
Do not rely on an `include` inside a task to provide a direct function dependency. If the function existed when the task was compiled, validation happens before the `include` executes. Use the bootstrap for ambient functions and task arguments for explicit dependencies.
58+
Define required functions in the runtime bootstrap, or pass a `Closure` created with `Closure::fromCallable()` as a task argument. Do not rely on an `include` inside the task because validation runs before it. Calls whose names are only known at runtime cannot be checked before execution.
5959

6060
Development
6161
===========

src/cache.c

Lines changed: 92 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
static struct {
2424
pthread_mutex_t mutex;
2525
HashTable functions;
26-
HashTable identities;
2726
#if PHP_VERSION_ID < 80200
2827
HashTable statics;
2928
#endif
@@ -57,15 +56,6 @@ typedef struct _php_parallel_cache_entry_t {
5756
#endif
5857
} php_parallel_cache_entry_t;
5958

60-
typedef struct _php_parallel_cache_identity_t {
61-
const zend_op *opcodes;
62-
zend_string *filename;
63-
zif_handler handler;
64-
uint32_t line_start;
65-
uint32_t line_end;
66-
uint8_t type;
67-
} php_parallel_cache_identity_t;
68-
6959
/* {{{ */
7060
static zend_always_inline void *php_parallel_cache_alloc(size_t size)
7161
{
@@ -150,73 +140,6 @@ static zend_always_inline HashTable *php_parallel_cache_statics(HashTable *stati
150140
} /* }}} */
151141
#endif
152142

153-
static zend_always_inline void php_parallel_cache_identities(const zend_function *source, const zend_function *cached)
154-
{ /* {{{ */
155-
const zend_op *source_opline = source->op_array.opcodes;
156-
const zend_op *cached_opline = cached->op_array.opcodes;
157-
const zend_op *end = source_opline + source->op_array.last;
158-
159-
while (source_opline < end) {
160-
if (source_opline->opcode == ZEND_INIT_FCALL) {
161-
zend_function *function = zend_fetch_function(Z_STR_P(RT_CONSTANT(source_opline, source_opline->op2)));
162-
163-
if (function) {
164-
php_parallel_cache_identity_t *identity = php_parallel_cache_alloc(sizeof(*identity));
165-
166-
memset(identity, 0, sizeof(*identity));
167-
identity->type = function->type;
168-
169-
if (ZEND_USER_CODE(function->type)) {
170-
identity->opcodes = function->op_array.opcodes;
171-
identity->filename = function->op_array.filename
172-
? php_parallel_copy_string_interned(function->op_array.filename)
173-
: NULL;
174-
identity->line_start = function->op_array.line_start;
175-
identity->line_end = function->op_array.line_end;
176-
} else {
177-
identity->handler = function->internal_function.handler;
178-
}
179-
180-
zend_hash_index_update_ptr(&PCG(identities), (zend_ulong)cached_opline, identity);
181-
}
182-
}
183-
184-
source_opline++;
185-
cached_opline++;
186-
}
187-
} /* }}} */
188-
189-
bool php_parallel_cache_function_matches(const zend_op *opline, const zend_function *function,
190-
php_parallel_cache_function_source_t *source)
191-
{ /* {{{ */
192-
php_parallel_cache_identity_t *identity;
193-
bool matches = false;
194-
195-
pthread_mutex_lock(&PCG(mutex));
196-
identity = zend_hash_index_find_ptr(&PCG(identities), (zend_ulong)opline);
197-
pthread_mutex_unlock(&PCG(mutex));
198-
199-
if (!identity) {
200-
return false;
201-
}
202-
203-
source->filename = identity->filename;
204-
source->line = identity->line_start;
205-
206-
if (ZEND_USER_CODE(identity->type)) {
207-
matches =
208-
ZEND_USER_CODE(function->type) && (identity->opcodes == function->op_array.opcodes ||
209-
(identity->filename && function->op_array.filename &&
210-
zend_string_equals(identity->filename, function->op_array.filename) &&
211-
identity->line_start == function->op_array.line_start &&
212-
identity->line_end == function->op_array.line_end));
213-
} else {
214-
matches = function->type == identity->type && function->internal_function.handler == identity->handler;
215-
}
216-
217-
return matches;
218-
} /* }}} */
219-
220143
static zend_always_inline void php_parallel_cache_type(zend_type *type)
221144
{ /* {{{ */
222145
zend_type *single;
@@ -252,7 +175,9 @@ static zend_always_inline void php_parallel_cache_type(zend_type *type)
252175
static zend_op_array *php_parallel_cache_create(const zend_function *source PARALLEL_CACHE_STATICS_PARAM)
253176
{
254177
zend_op_array *cached = php_parallel_cache_copy_mem((void *)source, sizeof(zend_op_array));
255-
bool shared = cached->refcount == NULL;
178+
uint32_t *literal_map = NULL;
179+
uint32_t *offset_map = NULL;
180+
uint32_t new_last_literal = cached->last_literal;
256181

257182
cached->fn_flags |= ZEND_ACC_IMMUTABLE;
258183

@@ -285,20 +210,58 @@ static zend_op_array *php_parallel_cache_create(const zend_function *source PARA
285210
}
286211
#endif
287212

288-
if (shared) {
289-
php_parallel_cache_identities(source, (zend_function *)cached);
213+
if (!cached->refcount) {
290214
goto _php_parallel_cached_function_return;
291215
}
292216

293217
cached->refcount = NULL;
294218

295219
if (cached->last_literal) {
296-
zval *literal = source->op_array.literals, *end = literal + cached->last_literal;
297-
zval *slot = php_parallel_cache_alloc(sizeof(zval) * cached->last_literal);
220+
zend_op *src_opline = source->op_array.opcodes;
221+
zend_op *src_end = src_opline + source->op_array.last;
222+
223+
// A map to keep track of which literals are referenced by the
224+
// `ZEND_INIT_FCALL` opcodes we found so that we can expand those later
225+
literal_map = emalloc(sizeof(uint32_t) * cached->last_literal);
226+
memset(literal_map, 0, sizeof(uint32_t) * cached->last_literal);
227+
228+
// Search for `ZEND_INIT_FCALL` opcodes and remember the indexes for the
229+
// literals, as we are rewriting them later to `ZEND_INIT_FCALL_BY_NAME`
230+
// which requires a second, lower cased literal just in the next literal
231+
// slot.
232+
while (src_opline < src_end) {
233+
if (src_opline->opcode == ZEND_INIT_FCALL && src_opline->op2_type == IS_CONST) {
234+
uint32_t idx;
235+
#if ZEND_USE_ABS_CONST_ADDR
236+
idx = (zval *)src_opline->op2.zv - source->op_array.literals;
237+
#else
238+
idx = ((zval *)((char *)src_opline + src_opline->op2.constant) - source->op_array.literals);
239+
#endif
240+
if (idx < cached->last_literal) {
241+
if (literal_map[idx] == 0) {
242+
literal_map[idx] = 1;
243+
new_last_literal++;
244+
}
245+
}
246+
}
247+
src_opline++;
248+
}
249+
}
250+
251+
if (new_last_literal) {
252+
zval *literal = source->op_array.literals;
253+
zval *slot = php_parallel_cache_alloc(sizeof(zval) * new_last_literal);
254+
uint32_t idx = 0;
255+
256+
offset_map = emalloc(sizeof(uint32_t) * cached->last_literal);
298257

299258
cached->literals = slot;
300259

301-
while (literal < end) {
260+
for (uint32_t i = 0; i < cached->last_literal; i++) {
261+
/* Record the mapping from old literal index (i) to new literal index (idx)
262+
so we can update opcode operands later. */
263+
offset_map[i] = idx;
264+
302265
if (Z_TYPE_P(literal) == IS_ARRAY) {
303266
ZVAL_ARR(slot, php_parallel_copy_hash_persistent(Z_ARRVAL_P(literal), php_parallel_copy_string_interned,
304267
php_parallel_cache_copy_mem,
@@ -310,25 +273,62 @@ static zend_op_array *php_parallel_cache_create(const zend_function *source PARA
310273
}
311274

312275
Z_TYPE_FLAGS_P(slot) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE);
276+
277+
/* If this literal was used by INIT_FCALL, insert its lowercased version next. */
278+
if (literal_map[i]) {
279+
zend_string *lower = zend_string_tolower(Z_STR_P(slot));
280+
slot++;
281+
idx++;
282+
ZVAL_STR(slot, php_parallel_copy_string_interned(lower));
283+
zend_string_release(lower);
284+
Z_TYPE_FLAGS_P(slot) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE);
285+
}
286+
313287
literal++;
314288
slot++;
289+
idx++;
290+
}
291+
cached->last_literal = new_last_literal;
292+
}
293+
294+
if (cached->last_var) {
295+
zend_string **vars = cached->vars;
296+
uint32_t it = 0, end = cached->last_var;
297+
zend_string **heap = php_parallel_cache_alloc(cached->last_var * sizeof(zend_string *));
298+
299+
while (it < end) {
300+
heap[it] = php_parallel_copy_string_interned(vars[it]);
301+
it++;
315302
}
303+
cached->vars = heap;
316304
}
317305

318306
if (cached->last) {
319307
zend_op *opcodes = php_parallel_cache_copy_mem(cached->opcodes, sizeof(zend_op) * cached->last);
320308
zend_op *opline = opcodes, *end = opline + cached->last;
321309

322310
while (opline < end) {
311+
/* Replace ZEND_INIT_FCALL with ZEND_INIT_FCALL_BY_NAME.
312+
We must clear op1_type (IS_UNUSED) and op1.var (0) to invalidate the
313+
original thread's cache slot. */
314+
if (opline->opcode == ZEND_INIT_FCALL) {
315+
opline->opcode = ZEND_INIT_FCALL_BY_NAME;
316+
opline->op1_type = IS_UNUSED;
317+
opline->op1.var = 0;
318+
ZEND_VM_SET_OPCODE_HANDLER(opline);
319+
}
320+
321+
/* Remap IS_CONST operands to their new locations in the expanded literal table
322+
using the offset_map we built earlier. */
323323
if (opline->op1_type == IS_CONST) {
324324
uint32_t idx;
325325
zend_op *src_opline = source->op_array.opcodes + (opline - opcodes);
326326
#if ZEND_USE_ABS_CONST_ADDR
327327
idx = (zval *)src_opline->op1.zv - source->op_array.literals;
328-
opline->op1.zv = &cached->literals[idx];
328+
opline->op1.zv = &cached->literals[offset_map[idx]];
329329
#else
330330
idx = ((zval *)((char *)src_opline + src_opline->op1.constant) - source->op_array.literals);
331-
opline->op1.constant = (char *)&cached->literals[idx] - (char *)opline;
331+
opline->op1.constant = (char *)&cached->literals[offset_map[idx]] - (char *)opline;
332332
#endif
333333
if (opline->opcode == ZEND_SEND_VAL || opline->opcode == ZEND_SEND_VAL_EX ||
334334
opline->opcode == ZEND_QM_ASSIGN) {
@@ -340,17 +340,12 @@ static zend_op_array *php_parallel_cache_create(const zend_function *source PARA
340340
zend_op *src_opline = source->op_array.opcodes + (opline - opcodes);
341341
#if ZEND_USE_ABS_CONST_ADDR
342342
idx = (zval *)src_opline->op2.zv - source->op_array.literals;
343-
opline->op2.zv = &cached->literals[idx];
343+
opline->op2.zv = &cached->literals[offset_map[idx]];
344344
#else
345345
idx = ((zval *)((char *)src_opline + src_opline->op2.constant) - source->op_array.literals);
346-
opline->op2.constant = (char *)&cached->literals[idx] - (char *)opline;
346+
opline->op2.constant = (char *)&cached->literals[offset_map[idx]] - (char *)opline;
347347
#endif
348348
}
349-
350-
if (opline->opcode == ZEND_INIT_FCALL) {
351-
Z_EXTRA_P(RT_CONSTANT(opline, opline->op2)) = 0;
352-
ZEND_VM_SET_OPCODE_HANDLER(opline);
353-
}
354349
#if ZEND_USE_ABS_JMP_ADDR
355350
switch (opline->opcode) {
356351
case ZEND_JMP:
@@ -385,18 +380,12 @@ static zend_op_array *php_parallel_cache_create(const zend_function *source PARA
385380
cached->opcodes = opcodes;
386381
}
387382

388-
php_parallel_cache_identities(source, (zend_function *)cached);
389-
390-
if (cached->last_var) {
391-
zend_string **vars = cached->vars;
392-
uint32_t it = 0, end = cached->last_var;
393-
zend_string **heap = php_parallel_cache_alloc(cached->last_var * sizeof(zend_string *));
383+
if (literal_map) {
384+
efree(literal_map);
385+
}
394386

395-
while (it < end) {
396-
heap[it] = php_parallel_copy_string_interned(vars[it]);
397-
it++;
398-
}
399-
cached->vars = heap;
387+
if (offset_map) {
388+
efree(offset_map);
400389
}
401390

402391
if (cached->arg_info) {
@@ -547,7 +536,6 @@ zend_function *php_parallel_cache_function(const zend_function *source)
547536
PHP_MINIT_FUNCTION(PARALLEL_CACHE)
548537
{
549538
zend_hash_init(&PCG(functions), 32, NULL, NULL, 1);
550-
zend_hash_init(&PCG(identities), 32, NULL, NULL, 1);
551539
#if PHP_VERSION_ID < 80200
552540
zend_hash_init(&PCG(statics), 32, NULL, NULL, 1);
553541
#endif
@@ -565,7 +553,6 @@ PHP_MINIT_FUNCTION(PARALLEL_CACHE)
565553
PHP_MSHUTDOWN_FUNCTION(PARALLEL_CACHE)
566554
{
567555
zend_hash_destroy(&PCG(functions));
568-
zend_hash_destroy(&PCG(identities));
569556
#if PHP_VERSION_ID < 80200
570557
zend_hash_destroy(&PCG(statics));
571558
#endif

src/cache.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,6 @@ zend_function *php_parallel_cache_function(const zend_function *source);
3838
*/
3939
zend_function *php_parallel_cache_closure(const zend_function *source, zend_function *destination);
4040

41-
typedef struct _php_parallel_cache_function_source_t {
42-
zend_string *filename;
43-
uint32_t line;
44-
} php_parallel_cache_function_source_t;
45-
46-
bool php_parallel_cache_function_matches(const zend_op *opline, const zend_function *function,
47-
php_parallel_cache_function_source_t *source);
48-
4941
PHP_MINIT_FUNCTION(PARALLEL_CACHE);
5042
PHP_MSHUTDOWN_FUNCTION(PARALLEL_CACHE);
5143
#endif

0 commit comments

Comments
 (0)