Skip to content

Commit e6a6ac9

Browse files
committed
Make vm_interrupt and timed_out atomic
This is done by adding a new zend_atomic_bool type. The type definition is only available for compiler alignment and size info; it should be treated as opaque and only the zend_atomic_bool_* family of functions should be used. Note that directly using atomic_bool is complicated. All C++ compilers stdlibs that I checked typedef atomic_bool to std::atomic<bool>, which can't be used in an extern "C" section, and there's at least one usage of this in core, and probably more outside of it. So, instead check that atomic_bool has the same size and alignment as native bool and if so we'll cast it.
1 parent 4117063 commit e6a6ac9

File tree

14 files changed

+141
-27
lines changed

14 files changed

+141
-27
lines changed

Zend/Zend.m4

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,32 @@ AC_LINK_IFELSE([AC_LANG_PROGRAM([[
389389
if test "$ac_cv_cpuid_count_available" = "yes"; then
390390
AC_DEFINE([HAVE_CPUID_COUNT], 1, [whether __cpuid_count is available])
391391
fi
392+
393+
dnl Check whether <stdatomic.h> is available.
394+
AC_CACHE_CHECK(
395+
[whether <stdatomic.h> is available],
396+
ac_cv_stdatomic_h,
397+
[AC_RUN_IFELSE(
398+
[AC_LANG_SOURCE(
399+
[[
400+
#include <stdatomic.h>
401+
#include <stdbool.h>
402+
int main(void) {
403+
atomic_bool val = false;
404+
(void)atomic_load(&val);
405+
atomic_store(&val, true);
406+
(void)atomic_exchange(&val, false);
407+
408+
if (sizeof(atomic_bool) != sizeof(bool)) return 1;
409+
if (_Alignof(atomic_bool) != _Alignof(bool)) return 1;
410+
return 0;
411+
}
412+
]]
413+
)],
414+
[ac_cv_stdatomic_h_available=yes],
415+
[ac_cv_stdatomic_h_available=no]
416+
)]
417+
)
418+
if test "$ac_cv_stdatomic_h_available" = "yes"; then
419+
AC_DEFINE([HAVE_STDATOMIC_H], 1, [whether <stdatomic.h> is available])
420+
fi

Zend/zend_atomic.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// TODO: license
2+
3+
#include "zend_atomic.h"
4+
5+
/* The general strategy here is to use C11's stdatomic.h functions when they
6+
* are available, and then fall-back to non-atomic operations, except for
7+
* Windows, which has C++ std::atomic_bool but not C atomic_bool.
8+
*
9+
* The defines HAVE_ATOMIC_BOOL should only be set when it is determined that
10+
* representations of atomic_bool and bool are the same, as this is relied on
11+
* in the implementation.
12+
*/
13+
14+
#if HAVE_STDATOMIC_H
15+
#include <stdatomic.h>
16+
#define HAVE_ATOMIC_BOOL 1
17+
#elif WIN32
18+
#include <atomic>
19+
using std::atomic_bool;
20+
using std::atomic_exchange;
21+
using std::atomic_load;
22+
using std::atomic_store;
23+
static_assert(sizeof(atomic_bool) == sizeof(bool), "Repr of atomic_bool and bool must match");
24+
static_assert(alignof(atomic_bool) == alignof(bool), "Repr of atomic_bool and bool must match");
25+
#define HAVE_ATOMIC_BOOL 1
26+
#else
27+
#define HAVE_ATOMIC_BOOL 0
28+
#endif
29+
30+
#if HAVE_ATOMIC_BOOL
31+
32+
ZEND_API bool zend_atomic_bool_exchange(zend_atomic_bool *obj, bool desired) {
33+
return atomic_exchange((atomic_bool *)obj, desired);
34+
}
35+
36+
ZEND_API bool zend_atomic_bool_load(const zend_atomic_bool *obj) {
37+
return atomic_load((const atomic_bool *)obj);
38+
}
39+
40+
ZEND_API void zend_atomic_bool_store(zend_atomic_bool *obj, bool desired) {
41+
atomic_store((atomic_bool *)obj, desired);
42+
}
43+
44+
#else
45+
46+
/* Yes, these are not guaranteed to be atomic. Understand that previously
47+
* atomics were never used, so the fact they are sometimes used is an
48+
* improvement. As more platforms support C11 atomics, or as we add support
49+
* for more platforms through intrinsics/asm, this should be used less and
50+
* less until it can be removed.
51+
*/
52+
53+
ZEND_API bool zend_atomic_bool_exchange(zend_atomic_bool *obj, bool desired) {
54+
bool previous = obj->bytes;
55+
obj->bytes = desired;
56+
return previous;
57+
}
58+
59+
ZEND_API bool zend_atomic_bool_load(const zend_atomic_bool *obj) {
60+
return obj->bytes;
61+
}
62+
63+
ZEND_API void zend_atomic_bool_store(zend_atomic_bool *obj, bool desired) {
64+
obj->bytes = desired;
65+
}
66+
67+
#endif

Zend/zend_atomic.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// TODO: license
2+
#include "zend_portability.h"
3+
4+
/* These functions correspond to C11's stdatomic.h functions but are type-
5+
* specialized because it's difficult to provide portable routines such as
6+
* exchange without making functions.
7+
*
8+
* Treat zend_atomic_* types as opaque. They have definitions only for size
9+
* and alignment purposes.
10+
*/
11+
12+
typedef struct zend_atomic_bool_s {
13+
volatile bool bytes;
14+
} zend_atomic_bool;
15+
16+
ZEND_API bool zend_atomic_bool_load(const zend_atomic_bool *obj);
17+
ZEND_API void zend_atomic_bool_store(zend_atomic_bool *obj, bool desired);
18+
ZEND_API bool zend_atomic_bool_exchange(zend_atomic_bool *obj, bool desired);

Zend/zend_execute.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3726,13 +3726,13 @@ ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *exec
37263726
/* }}} */
37273727

37283728
#define ZEND_VM_INTERRUPT_CHECK() do { \
3729-
if (UNEXPECTED(EG(vm_interrupt))) { \
3729+
if (UNEXPECTED(zend_atomic_bool_load(&EG(vm_interrupt)))) { \
37303730
ZEND_VM_INTERRUPT(); \
37313731
} \
37323732
} while (0)
37333733

37343734
#define ZEND_VM_LOOP_INTERRUPT_CHECK() do { \
3735-
if (UNEXPECTED(EG(vm_interrupt))) { \
3735+
if (UNEXPECTED(zend_atomic_bool_load(&EG(vm_interrupt)))) { \
37363736
ZEND_VM_LOOP_INTERRUPT(); \
37373737
} \
37383738
} while (0)

Zend/zend_execute_API.c

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ void init_executor(void) /* {{{ */
168168
zend_objects_store_init(&EG(objects_store), 1024);
169169

170170
EG(full_tables_cleanup) = 0;
171-
EG(vm_interrupt) = 0;
172-
EG(timed_out) = 0;
171+
zend_atomic_bool_store(&EG(vm_interrupt), false);
172+
zend_atomic_bool_store(&EG(timed_out), false);
173173

174174
EG(exception) = NULL;
175175
EG(prev_exception) = NULL;
@@ -960,9 +960,8 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
960960

961961
/* This flag is regularly checked while running user functions, but not internal
962962
* So see whether interrupt flag was set while the function was running... */
963-
if (EG(vm_interrupt)) {
964-
EG(vm_interrupt) = 0;
965-
if (EG(timed_out)) {
963+
if (zend_atomic_bool_exchange(&EG(vm_interrupt), false)) {
964+
if (zend_atomic_bool_load(&EG(timed_out))) {
966965
zend_timeout();
967966
} else if (zend_interrupt_function) {
968967
zend_interrupt_function(EG(current_execute_data));
@@ -1337,7 +1336,7 @@ ZEND_API ZEND_NORETURN void ZEND_FASTCALL zend_timeout(void) /* {{{ */
13371336
}
13381337
# endif
13391338
#else
1340-
EG(timed_out) = 0;
1339+
zend_atomic_bool_store(&EG(timed_out), false);
13411340
zend_set_timeout_ex(0, 1);
13421341
#endif
13431342

@@ -1349,7 +1348,7 @@ ZEND_API ZEND_NORETURN void ZEND_FASTCALL zend_timeout(void) /* {{{ */
13491348
static void zend_timeout_handler(int dummy) /* {{{ */
13501349
{
13511350
#ifndef ZTS
1352-
if (EG(timed_out)) {
1351+
if (zend_atomic_bool_load(&EG(timed_out))) {
13531352
/* Die on hard timeout */
13541353
const char *error_filename = NULL;
13551354
uint32_t error_lineno = 0;
@@ -1384,8 +1383,8 @@ static void zend_timeout_handler(int dummy) /* {{{ */
13841383
zend_on_timeout(EG(timeout_seconds));
13851384
}
13861385

1387-
EG(timed_out) = 1;
1388-
EG(vm_interrupt) = 1;
1386+
zend_atomic_bool_store(&EG(timed_out), true);
1387+
zend_atomic_bool_store(&EG(vm_interrupt), true);
13891388

13901389
#ifndef ZTS
13911390
if (EG(hard_timeout) > 0) {
@@ -1409,8 +1408,8 @@ VOID CALLBACK tq_timer_cb(PVOID arg, BOOLEAN timed_out)
14091408
}
14101409

14111410
eg = (zend_executor_globals *)arg;
1412-
eg->timed_out = 1;
1413-
eg->vm_interrupt = 1;
1411+
zend_atomic_bool_store(&eg->timed_out, true);
1412+
zend_atomic_bool_store(&eg->vm_interrupt, true);
14141413
}
14151414
#endif
14161415

@@ -1496,7 +1495,7 @@ void zend_set_timeout(zend_long seconds, bool reset_signals) /* {{{ */
14961495

14971496
EG(timeout_seconds) = seconds;
14981497
zend_set_timeout_ex(seconds, reset_signals);
1499-
EG(timed_out) = 0;
1498+
zend_atomic_bool_store(&EG(timed_out), false);
15001499
}
15011500
/* }}} */
15021501

@@ -1525,7 +1524,7 @@ void zend_unset_timeout(void) /* {{{ */
15251524
# endif
15261525
}
15271526
#endif
1528-
EG(timed_out) = 0;
1527+
zend_atomic_bool_store(&EG(timed_out), false);
15291528
}
15301529
/* }}} */
15311530

Zend/zend_globals.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
#include "zend_globals_macros.h"
2727

28+
#include "zend_atomic.h"
2829
#include "zend_stack.h"
2930
#include "zend_ptr_stack.h"
3031
#include "zend_hash.h"
@@ -189,8 +190,8 @@ struct _zend_executor_globals {
189190
/* for extended information support */
190191
bool no_extensions;
191192

192-
bool vm_interrupt;
193-
bool timed_out;
193+
zend_atomic_bool vm_interrupt;
194+
zend_atomic_bool timed_out;
194195
zend_long hard_timeout;
195196

196197
#ifdef ZEND_WIN32

Zend/zend_vm_def.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9892,9 +9892,9 @@ ZEND_VM_DEFINE_OP(137, ZEND_OP_DATA);
98929892

98939893
ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY)
98949894
{
9895-
EG(vm_interrupt) = 0;
9895+
zend_atomic_bool_store(&EG(vm_interrupt), false);
98969896
SAVE_OPLINE();
9897-
if (EG(timed_out)) {
9897+
if (zend_atomic_bool_load(&EG(timed_out))) {
98989898
zend_timeout();
98999899
} else if (zend_interrupt_function) {
99009900
zend_interrupt_function(execute_data);

Zend/zend_vm_execute.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3537,9 +3537,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_JMP_FORWARD_SPEC_H
35373537

35383538
static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS)
35393539
{
3540-
EG(vm_interrupt) = 0;
3540+
zend_atomic_bool_store(&EG(vm_interrupt), false);
35413541
SAVE_OPLINE();
3542-
if (EG(timed_out)) {
3542+
if (zend_atomic_bool_load(&EG(timed_out))) {
35433543
zend_timeout();
35443544
} else if (zend_interrupt_function) {
35453545
zend_interrupt_function(execute_data);

build/php.m4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2152,7 +2152,7 @@ EOF
21522152
else
21532153
break
21542154
fi
2155-
$as_echo "$CURRENT_ARG \\" >>$1
2155+
AS_ECHO(["$CURRENT_ARG \\"]) >>$1
21562156
CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS $CURRENT_ARG"
21572157
done
21582158
echo '"[$]@"' >> $1

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1637,7 +1637,7 @@ PHP_ADD_SOURCES(Zend, \
16371637
zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.c \
16381638
zend_virtual_cwd.c zend_ast.c zend_objects.c zend_object_handlers.c zend_objects_API.c \
16391639
zend_default_classes.c zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_gdb.c \
1640-
zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c \
1640+
zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_atomic.c \
16411641
Optimizer/zend_optimizer.c \
16421642
Optimizer/pass1.c \
16431643
Optimizer/pass3.c \

ext/opcache/jit/zend_jit_trace.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8023,7 +8023,7 @@ int ZEND_FASTCALL zend_jit_trace_exit(uint32_t exit_num, zend_jit_registers_buf
80238023
EX(opline) = opline;
80248024
}
80258025

8026-
if (EG(vm_interrupt) || JIT_G(tracing)) {
8026+
if (zend_atomic_bool_load(&EG(vm_interrupt)) || JIT_G(tracing)) {
80278027
return 1;
80288028
/* Lock-free check if the side trace was already JIT-ed or blacklist-ed in another process */
80298029
} else if (t->exit_info[exit_num].flags & (ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED)) {

ext/pcntl/pcntl.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1383,7 +1383,7 @@ static void pcntl_signal_handler(int signo)
13831383
PCNTL_G(tail) = psig;
13841384
PCNTL_G(pending_signals) = 1;
13851385
if (PCNTL_G(async_signals)) {
1386-
EG(vm_interrupt) = 1;
1386+
zend_atomic_bool_store(&EG(vm_interrupt), true);
13871387
}
13881388
}
13891389

win32/build/config.w32

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \
238238
zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.c \
239239
zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \
240240
zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \
241-
zend_enum.c zend_fibers.c");
241+
zend_enum.c zend_fibers.c zend_atomic.h");
242242
ADD_SOURCES("Zend\\Optimizer", "zend_optimizer.c pass1.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c escape_analysis.c compact_vars.c dce.c sccp.c scdf.c");
243243

244244
var FIBER_ASSEMBLER = X64 ? PATH_PROG('ML64') : PATH_PROG('ML');

win32/signal.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
/* true globals; only used from main thread and from kernel callback */
2323
static zval ctrl_handler;
2424
static DWORD ctrl_evt = (DWORD)-1;
25-
static bool *vm_interrupt_flag = NULL;
25+
static volatile bool *vm_interrupt_flag = NULL;
2626

2727
static void (*orig_interrupt_function)(zend_execute_data *execute_data);
2828

0 commit comments

Comments
 (0)