Verified Commit 74759639 authored by Camil Staps's avatar Camil Staps 🚀

Add POSIX implementation of optional timeouts in hyperstrict evaluation using pthreads

parent 257228f0
Pipeline #43425 passed with stages
in 16 minutes and 12 seconds
......@@ -8,7 +8,6 @@ stages:
image: camilstaps/abc-interpreter
before_script:
- for bw in 32 64; do for tool in abcopt bcgen bclink bcprelink bcstrip; do ln -s "$PWD/src/$tool" /opt/clean-$bw/lib/exe; done; done
- sed -i '/^import code from library/d' lib/ABC/Interpreter/Util.icl
- ulimit -a
- gcc --version
......
......@@ -34,6 +34,7 @@ from symbols_in_program import :: Symbol
{ heap_size :: !Int //* Heap size for the interpreter, in bytes (default: 2M)
, stack_size :: !Int //* Stack size for the interpreter, in bytes (default: 1M in total; 500k for A and 500k for BC stack)
, file_io :: !Bool //* Whether file I/O is allowed (default: False)
, timeout :: !Maybe Int //* Optional timeout per evaluation, in microseconds (default: Nothing)
}
defaultDeserializationSettings :: DeserializationSettings
......@@ -103,6 +104,8 @@ deserialize :: !DeserializationSettings !SerializedGraph !String !*World -> *(!M
//* File I/O was attempted while the interpreter was started with file_io=False.
| DV_HostHeapFull
//* The heap of the host application has not enough space to copy the result.
| DV_Timeout
//* Interpretation timed out.
| DV_Ok !a
//* Deserialization succeeded.
......
......@@ -22,6 +22,7 @@ defaultDeserializationSettings =
{ heap_size = 2 << 20
, stack_size = (512 << 10) * 2
, file_io = False
, timeout = Nothing
}
:: *SerializedGraph =
......@@ -118,7 +119,7 @@ deserialize` strict dsets {graph,descinfo,modules,bytecode} thisexe w
pgm
heap dsets.heap_size stack dsets.stack_size
asp bsp csp heap
strict dsets.file_io
strict dsets.file_io (if (isJust dsets.timeout) (fromJust dsets.timeout) -1)
# graph = replace_desc_numbers_by_descs 0 graph int_syms 0 0
# graph_node = string_to_interpreter graph ie_settings
#! (ie,_) = make_finalizer ie_settings
......@@ -215,7 +216,7 @@ get_start_rule strict dsets filename prog w
pgm
heap dsets.heap_size stack dsets.stack_size
asp bsp csp heap
strict dsets.file_io
strict dsets.file_io (if (isJust dsets.timeout) (fromJust dsets.timeout) -1)
# start_node = build_start_node ie_settings
#! (ie,_) = make_finalizer ie_settings
# ie = {ie_finalizer=ie, ie_snode_ptr=0, ie_snodes=create_array_ 1}
......@@ -225,9 +226,14 @@ get_start_rule strict dsets filename prog w
// it to the finalizer_list anyway. This is just to ensure that the first
// call to interpret gets the right argument.
build_interpretation_environment :: !Pointer !Pointer !Int !Pointer !Int !Pointer !Pointer !Pointer !Pointer !Bool !Bool -> Pointer
build_interpretation_environment pgm heap hsize stack ssize asp bsp csp hp strict file_io = code {
ccall build_interpretation_environment "ppIpIppppII:p"
build_interpretation_environment ::
!Pointer
!Pointer !Int !Pointer !Int
!Pointer !Pointer !Pointer !Pointer
!Bool !Bool !Int
-> Pointer
build_interpretation_environment pgm heap hsize stack ssize asp bsp csp hp strict file_io timeout = code {
ccall build_interpretation_environment "ppIpIppppIII:p"
}
build_start_node :: !Pointer -> Pointer
......
......@@ -24,6 +24,7 @@ import code from "util."
import code from library "msvcrt_library"
import code from library "kernel32_library"
import code from library "ucrtbase_library"
import code from library "-lpthread"
OFFSET_PARSER_PROGRAM :== IF_INT_64_OR_32 8 4 // Offset to the program field in the parser struct (parse.h)
......
......@@ -33,7 +33,7 @@ endif
ifeq ($(OS),Windows_NT)
override CFLAGS+=-DWINDOWS
else
override CFLAGS+=-DPOSIX -D_POSIX_C_SOURCE=199309L -D_XOPEN_SOURCE=500 -D_C99_SOURCE
override CFLAGS+=-DPOSIX -D_POSIX_C_SOURCE=200801L -D_XOPEN_SOURCE=500 -D_C99_SOURCE
endif
ifeq ($(USING_CLANG),1)
override CFLAGS+=-Wno-parentheses-equality
......
......@@ -52,7 +52,8 @@ struct interpretation_environment *build_interpretation_environment(
struct program *program,
BC_WORD *heap, BC_WORD heap_size, BC_WORD *stack, BC_WORD stack_size,
BC_WORD *asp, BC_WORD *bsp, BC_WORD *csp, BC_WORD *hp,
int hyperstrict, int allow_file_io) {
int hyperstrict, int allow_file_io, int timeout)
{
struct interpretation_environment *ie = safe_malloc(sizeof(struct interpretation_environment));
ie->host = safe_malloc(sizeof(struct host_status));
ie->program = program;
......@@ -68,6 +69,7 @@ struct interpretation_environment *build_interpretation_environment(
ie->hp_end = hp+(heap_size>>1);
ie->caf_list[0] = 0;
ie->caf_list[1] = &ie->caf_list[1];
ie->options.timeout=timeout;
ie->options.in_first_semispace=1;
ie->options.hyperstrict=hyperstrict!=0;
ie->options.allow_file_io=allow_file_io!=0;
......
......@@ -255,19 +255,47 @@ void* __interpreter_indirection[9] = {
#endif
#ifdef LINK_CLEAN_RUNTIME
# include <errno.h>
# include <pthread.h>
# include <signal.h>
struct segfault_restore_points {
struct interpretation_environment *interpretation_environment;
jmp_buf restore_point;
BC_WORD *host_a_ptr;
pthread_t timer_thread;
pthread_mutex_t timer_mutex;
pthread_cond_t timer_cond;
struct segfault_restore_points *prev;
};
static struct segfault_restore_points *segfault_restore_points=NULL;
static inline void pop_segfault_restore_point (void)
{
struct segfault_restore_points *old=segfault_restore_points;
segfault_restore_points=old->prev;
if (old->interpretation_environment->options.timeout>=0){
pthread_mutex_trylock (&old->timer_mutex);
pthread_cancel (old->timer_thread);
pthread_cond_signal (&old->timer_cond);
pthread_mutex_unlock (&old->timer_mutex);
pthread_join (old->timer_thread,NULL);
}
free(old);
}
static pthread_t main_thread;
static pthread_condattr_t pthread_cond_monotonic_clock;
#endif
#ifdef POSIX
# ifdef LINK_CLEAN_RUNTIME
static struct sigaction old_segv_handler;
# endif
static void handle_segv(int sig, siginfo_t *info, void *context) {
static void handle_signal (int sig,siginfo_t *info,void *context)
{
# ifdef LINK_CLEAN_RUNTIME
if (segfault_restore_points==NULL) {
if (old_segv_handler.sa_handler!=SIG_DFL && old_segv_handler.sa_handler!=SIG_IGN) {
......@@ -285,6 +313,11 @@ static void handle_segv(int sig, siginfo_t *info, void *context) {
interpret_error=&e__ABC_PInterpreter__dDV__FloatingPointException;
# endif
EPRINTF("Floating point exception during interpretation\n");
} else if (sig==SIGUSR1){
# ifdef LINK_CLEAN_RUNTIME
interpret_error=&e__ABC_PInterpreter__dDV__Timeout;
# endif
EPRINTF("Interpretation timed out\n");
} else {
# ifdef LINK_CLEAN_RUNTIME
interpret_error=&e__ABC_PInterpreter__dDV__StackOverflow;
......@@ -298,7 +331,8 @@ static void handle_segv(int sig, siginfo_t *info, void *context) {
# endif
}
#elif defined(WINDOWS)
static LONG WINAPI handle_segv(struct _EXCEPTION_POINTERS *exception) {
static LONG WINAPI handle_signal (struct _EXCEPTION_POINTERS *exception)
{
# ifdef LINK_CLEAN_RUNTIME
interpret_error=&e__ABC_PInterpreter__dDV__StackOverflow;
if (segfault_restore_points!=NULL)
......@@ -317,22 +351,24 @@ static void install_signal_handlers(void) {
if (sigaltstack(&signal_stack,NULL) == -1)
perror("sigaltstack");
struct sigaction segv_handler;
segv_handler.sa_sigaction=handle_segv;
sigemptyset(&segv_handler.sa_mask);
segv_handler.sa_flags=SA_ONSTACK | SA_SIGINFO | SA_RESTART;
if (sigaction(SIGSEGV, &segv_handler,
struct sigaction signal_handler;
signal_handler.sa_sigaction=handle_signal;
sigemptyset(&signal_handler.sa_mask);
signal_handler.sa_flags=SA_ONSTACK | SA_SIGINFO | SA_RESTART;
if (sigaction (SIGSEGV,&signal_handler,
# ifdef LINK_CLEAN_RUNTIME
&old_segv_handler
# else
NULL
# endif
) == -1)
perror("sigaction");
if (sigaction(SIGFPE, &segv_handler, NULL) == -1)
)!=0)
perror("sigaction");
if (sigaction (SIGFPE,&signal_handler,NULL)!=0)
perror ("sigaction");
if (sigaction (SIGUSR1,&signal_handler,NULL)!=0)
perror ("sigaction");
#elif defined(WINDOWS)
SetUnhandledExceptionFilter(&handle_segv);
SetUnhandledExceptionFilter(&handle_signal);
#else
EPRINTF("warning: interpreter does not recover from segfaults on this platform\n");
#endif
......@@ -349,6 +385,12 @@ int ensure_interpreter_init(void) {
install_signal_handlers();
#ifdef LINK_CLEAN_RUNTIME
main_thread=pthread_self();
pthread_condattr_init (&pthread_cond_monotonic_clock);
pthread_condattr_setclock (&pthread_cond_monotonic_clock,CLOCK_MONOTONIC);
#endif
prepare_static_nodes();
#ifdef LINK_CLEAN_RUNTIME
build_host_nodes();
......@@ -388,6 +430,69 @@ int ensure_interpreter_init(void) {
return 1;
}
#ifdef LINK_CLEAN_RUNTIME
/* This function runs in a separate thread. It waits for the timeout specified
* in the options of the interpretation environment, and then sends SIGUSR1 to
* the main thread. When signaled using the timer_cond, it pauses the timer. */
static void *timed_interpret_watch (void *arg)
{
struct segfault_restore_points *segfault_restore_points=(struct segfault_restore_points*)arg;
struct timespec start_time,wait_time,end_time;
wait_time.tv_sec=0;
wait_time.tv_nsec=1000*segfault_restore_points->interpretation_environment->options.timeout;
clock_gettime (CLOCK_MONOTONIC,&start_time);
end_time.tv_sec=start_time.tv_sec+wait_time.tv_sec;
end_time.tv_nsec=start_time.tv_nsec+wait_time.tv_nsec;
pthread_mutex_lock (&segfault_restore_points->timer_mutex);
for (;;)
{
if (end_time.tv_nsec>=1000000000){
end_time.tv_sec+=end_time.tv_nsec/1000000000;
end_time.tv_nsec%=1000000000;
}
pthread_cond_signal (&segfault_restore_points->timer_cond);
int err=pthread_cond_timedwait (&segfault_restore_points->timer_cond,&segfault_restore_points->timer_mutex,&end_time);
if (!err){
clock_gettime (CLOCK_MONOTONIC,&end_time);
wait_time.tv_sec=wait_time.tv_sec-(end_time.tv_sec-start_time.tv_sec);
wait_time.tv_nsec=wait_time.tv_nsec-(end_time.tv_nsec-start_time.tv_nsec);
pthread_cond_signal (&segfault_restore_points->timer_cond);
pthread_cond_wait (&segfault_restore_points->timer_cond,&segfault_restore_points->timer_mutex);
clock_gettime (CLOCK_MONOTONIC,&start_time);
end_time.tv_sec=start_time.tv_sec+wait_time.tv_sec;
end_time.tv_nsec=start_time.tv_nsec+wait_time.tv_nsec;
continue;
}
if (err==ETIMEDOUT){
pthread_kill (main_thread,SIGUSR1);
pthread_mutex_unlock (&segfault_restore_points->timer_mutex);
} else {
fprintf (stderr,"pthread_cond_timedwait failed (%d)\n",err);
exit (1);
}
return NULL;
}
}
static void enter_or_exit_untimed_section (void)
{
pthread_mutex_lock (&segfault_restore_points->timer_mutex);
pthread_cond_signal (&segfault_restore_points->timer_cond);
pthread_cond_wait (&segfault_restore_points->timer_cond,&segfault_restore_points->timer_mutex);
pthread_mutex_unlock (&segfault_restore_points->timer_mutex);
}
#endif
#ifdef LINK_CLEAN_RUNTIME
static BC_WORD *hp;
#endif
......@@ -445,10 +550,14 @@ int interpret(
#endif
#ifdef LINK_CLEAN_RUNTIME
int interpretation_with_timeout=create_restore_point && ie->options.timeout>=0;
if (create_restore_point) {
struct segfault_restore_points *new=safe_malloc(sizeof(struct segfault_restore_points));
new->prev=segfault_restore_points;
new->host_a_ptr=ie->host->host_a_ptr;
new->interpretation_environment=ie;
segfault_restore_points=new;
# ifdef POSIX
if (sigsetjmp(new->restore_point, 1) != 0) {
......@@ -458,6 +567,24 @@ int interpret(
ie->host->host_a_ptr=segfault_restore_points->host_a_ptr;
goto eval_to_hnf_return_failure;
}
if (ie->options.timeout>=0){
pthread_cond_init (&new->timer_cond,&pthread_cond_monotonic_clock);
pthread_mutex_init (&new->timer_mutex,NULL);
pthread_mutex_lock (&new->timer_mutex);
pthread_create (&new->timer_thread,NULL,timed_interpret_watch,(void*)new);
pthread_cond_wait (&new->timer_cond,&new->timer_mutex);
pthread_mutex_unlock (&new->timer_mutex);
}
} else if (ie->options.timeout>=0){
/* This occurs when an indirection to the interpreter is created as
* (part of) the argument of a function in the host. The result should
* not be wrapped in a DeserializedValue, so we cannot create a restore
* point, but the time spent here must be counted towards the timeout
* of the calling interpreter function, that is, the top of the restore
* point stack. */
enter_or_exit_untimed_section();
}
#endif
......@@ -472,20 +599,17 @@ int interpret(
pc=_pc;
if (0) {
#ifdef LINK_CLEAN_RUNTIME
struct segfault_restore_points *old;
#endif
eval_to_hnf_return:
#ifdef LINK_CLEAN_RUNTIME
ie->asp = asp;
ie->bsp = bsp;
ie->csp = csp;
ie->hp = hp;
if (create_restore_point) {
old=segfault_restore_points;
segfault_restore_points=old->prev;
free(old);
}
if (create_restore_point)
pop_segfault_restore_point();
else if (ie->options.timeout>=0)
enter_or_exit_untimed_section();
#endif
return 0;
#ifdef LINK_CLEAN_RUNTIME
......@@ -494,11 +618,12 @@ eval_to_hnf_return_failure:
ie->bsp = bsp;
ie->csp = csp;
ie->hp = hp;
if (create_restore_point) {
old=segfault_restore_points;
segfault_restore_points=old->prev;
free(old);
}
if (create_restore_point)
pop_segfault_restore_point();
else if (ie->options.timeout>=0)
enter_or_exit_untimed_section();
if (stack[stack_size/2-1]!=A_STACK_CANARY) {
stack[stack_size/2-1]=A_STACK_CANARY;
interpret_error=&e__ABC_PInterpreter__dDV__StackOverflow;
......
......@@ -7,6 +7,7 @@ struct interpretation_options {
int allow_file_io:1;
#ifdef LINK_CLEAN_RUNTIME
int hyperstrict:1;
int timeout;
#endif
};
......@@ -61,6 +62,7 @@ extern void *e__ABC_PInterpreter__dDV__IllegalInstruction;
extern void *e__ABC_PInterpreter__dDV__FileIOAttempted;
extern void *e__ABC_PInterpreter__dDV__SegmentationFault;
extern void *e__ABC_PInterpreter__dDV__HostHeapFull;
extern void *e__ABC_PInterpreter__dDV__Timeout;
extern void *e__ABC_PInterpreter__kDV__Ok;
extern void **interpret_error;
......
......@@ -7,6 +7,10 @@ INSTRUCTION_BLOCK(jsr_eval_host_node):
#if DEBUG_CLEAN_LINKS > 1
EPRINTF("\t%p -> [%d; %p -> %p]\n",n,host_nodeid,host_node,(void*)*host_node);
#endif
if (interpretation_with_timeout)
enter_or_exit_untimed_section();
if (!(host_node[0] & 2)) {
ie->asp = asp;
ie->bsp = bsp;
......@@ -45,6 +49,9 @@ INSTRUCTION_BLOCK(jsr_eval_host_node):
hp=ie->hp+words_used;
heap_free=ie->hp_end-hp;
if (interpretation_with_timeout)
enter_or_exit_untimed_section();
pc=(BC_WORD*)*csp--;
END_INSTRUCTION_BLOCK;
}
......@@ -189,6 +196,10 @@ jsr_eval_host_node_with_args:
ie->bsp = bsp;
ie->csp = csp;
ie->hp = hp;
if (interpretation_with_timeout)
enter_or_exit_untimed_section();
if (instr_arg >= 2) {
#ifdef WINDOWS
host_node = __interpret__evaluate__host_with_args(arg2, arg1, host_node, ap_addresses[instr_arg-2], ie);
......@@ -248,6 +259,9 @@ jsr_eval_host_node_with_args:
hp=ie->hp+words_used;
heap_free=ie->hp_end-hp;
if (interpretation_with_timeout)
enter_or_exit_untimed_section();
pc=(BC_WORD*)*csp--;
END_INSTRUCTION_BLOCK;
}
......
......@@ -18,7 +18,9 @@ where
DV_StackOverflow -> DV_StackOverflow
DV_Halt -> DV_Halt
DV_IllegalInstruction -> DV_IllegalInstruction
DV_FileIOAttempted -> DV_FileIOAttempted
DV_HostHeapFull -> DV_HostHeapFull
DV_Timeout -> DV_Timeout
Start w
# (graph,w) = serialize graph "GraphTest.bc" w
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment