Files

281 lines
9.1 KiB
C

/* Native stack exhaustion must return an error, unwind, and permit reuse.
* Parse/open on the main thread, then execute the same rtx on smaller stacks. */
#include <hawk.h>
#include <hawk-utl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tap.h"
#if defined(_WIN32)
#define TEST_CSTACK
#elif defined(__HAIKU__) && defined(HAVE_FIND_THREAD) && defined(HAVE_GET_THREAD_INFO)
#define TEST_CSTACK
#elif defined(__APPLE__) && defined(HAVE_PTHREAD) && defined(HAVE_PTHREAD_GET_STACKADDR_NP) && defined(HAVE_PTHREAD_GET_STACKSIZE_NP)
#define TEST_CSTACK
#elif defined(__OpenBSD__) && defined(HAVE_PTHREAD) && defined(HAVE_PTHREAD_STACKSEG_NP)
#define TEST_CSTACK
#elif defined(__linux__) && defined(HAVE_PTHREAD) && defined(HAVE_PTHREAD_GETATTR_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK) && defined(HAVE_PTHREAD_ATTR_GETGUARDSIZE)
#define TEST_CSTACK
#elif (defined(__FreeBSD__) || defined(__NetBSD__)) && defined(HAVE_PTHREAD) && defined(HAVE_PTHREAD_ATTR_GET_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK) && defined(HAVE_PTHREAD_ATTR_GETGUARDSIZE)
#define TEST_CSTACK
#endif
#if defined(TEST_CSTACK)
# if defined(_WIN32)
# include <windows.h>
# else
# include <pthread.h>
# include <sys/mman.h>
# include <unistd.h>
# if !defined(MAP_ANONYMOUS)
# define MAP_ANONYMOUS MAP_ANON
# endif
# endif
#endif
#if defined(TEST_CSTACK)
static int reenter (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
hawk_val_t* v;
(void)fi;
v = hawk_rtx_callwithbcstr(rtx, "callback", HAWK_NULL, 0);
if (!v) return -1;
hawk_rtx_setretval(rtx, v);
hawk_rtx_refdownval(rtx, v);
return 0;
}
struct job_t
{
hawk_rtx_t* rtx;
const char* name; /* NULL exercises hawk_rtx_loop() */
int require_native;
int passed;
};
static void* exhaust (void* data)
{
struct job_t* job = (struct job_t*)data;
hawk_val_t* v;
v = job->name? hawk_rtx_callwithbcstr(job->rtx, job->name, HAWK_NULL, 0): hawk_rtx_loop(job->rtx);
job->passed = !v && hawk_rtx_geterrnum(job->rtx) == HAWK_ESTACK &&
(!job->require_native || strstr(hawk_rtx_geterrbmsg(job->rtx), "native C stack limit reached") != HAWK_NULL);
if (!job->passed) printf("# unexpected result: %s\n", hawk_rtx_geterrbmsg(job->rtx));
if (v) hawk_rtx_refdownval(job->rtx, v);
return HAWK_NULL;
}
#if defined(_WIN32)
static DWORD WINAPI thread_main (LPVOID data)
{
exhaust(data);
return 0;
}
#endif
static int on_stack (struct job_t* job, size_t size)
{
#if defined(_WIN32)
HANDLE thread = CreateThread(HAWK_NULL, size, thread_main, job, STACK_SIZE_PARAM_IS_A_RESERVATION, HAWK_NULL);
DWORD n;
if (!thread) return -1;
n = WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
return n == WAIT_OBJECT_0? 0: -1;
#elif defined(__HAIKU__)
pthread_attr_t attr;
pthread_t thread;
int n, started = 0;
n = pthread_attr_init(&attr);
if (n == 0)
{
/* On Haiku, passing an existing mmap() area to pthread_attr_setstack()
* prevents the thread entry point from running. Let the kernel allocate
* an exact-sized stack instead.
*
* [NOTE]
* This branch may work on other platforms that support pthread_attr_setstacksize.
* In posix specification, the given size to pthread_attr_setstacksize
* is the minimum size. so the actual stack may be larger than requested.
* If the exact-sized stack is allocated, the test becomes less deterministic */
n = pthread_attr_setstacksize(&attr, size);
if (n == 0)
{
n = pthread_create(&thread, &attr, exhaust, job);
started = (n == 0);
}
pthread_attr_destroy(&attr);
if (n == 0) n = pthread_join(thread, HAWK_NULL);
}
return started && n == 0? 0: -1;
#else
pthread_attr_t attr;
pthread_t thread;
void* stack;
long page = sysconf(_SC_PAGESIZE);
int n, started = 0;
if (page <= 0) return -1;
/* Supply the exact allocation: pthreads may otherwise reuse a cached,
* larger stack and turn the small-stack regression into a false pass. */
stack = mmap(HAWK_NULL, size + 2 * page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (stack == MAP_FAILED) return -1;
if (mprotect((char*)stack + page, size, PROT_READ | PROT_WRITE) != 0)
{
munmap(stack, size + 2 * page);
return -1;
}
n = pthread_attr_init(&attr);
if (n == 0)
{
n = pthread_attr_setstack(&attr, (char*)stack + page, size);
if (n == 0)
{
n = pthread_create(&thread, &attr, exhaust, job);
started = (n == 0);
}
pthread_attr_destroy(&attr);
if (n == 0) n = pthread_join(thread, HAWK_NULL);
}
/* A failed join cannot prove that the worker stopped using this stack. */
if (!started || n == 0) munmap(stack, size + 2 * page);
return n == 0? 0: -1;
#endif
}
static void check_reuse (hawk_rtx_t* rtx)
{
hawk_val_t* v;
hawk_int_t n = 0;
v = hawk_rtx_callwithbcstr(rtx, "answer", HAWK_NULL, 0);
OK(v && hawk_rtx_valtoint(rtx, v, &n) == 0 && n == 42, "rtx reusable on main thread after exhaustion");
if (v) hawk_rtx_refdownval(rtx, v);
}
#endif
int main (void)
{
#if defined(TEST_CSTACK)
static const char prefix[] =
"@pragma stack_limit 1048576\n"
"function direct() { @local s; s = sprintf(\"value-%d\", 1); return 1 + direct(); }\n"
"function mutual() { return other(); }\n"
"function other() { return mutual(); }\n"
"function callback() { return host_reenter(); }\n"
"function answer() { return 42; }\n"
"BEGIN { direct(); }\n"
"function expression() { @local n; n = 1; return n";
static const char* names[] = { "direct", "mutual", "callback", "expression", HAWK_NULL };
static const size_t sizes[] = { 128 * 1024, 256 * 1024, 512 * 1024 };
hawk_t* hawk;
hawk_rtx_t* rtx = HAWK_NULL;
hawk_parsestd_t in[2];
hawk_fnc_bspec_t spec;
hawk_oow_t unlimited = 0;
struct job_t job;
char* src;
size_t i, j, len;
int n;
no_plan();
hawk = hawk_openstd(0, HAWK_NULL);
OK(hawk != HAWK_NULL, "open interpreter");
if (!hawk) return exit_status();
/* Make depth/value-stack limits incapable of masking native exhaustion. */
hawk_setopt(hawk, HAWK_OPT_DEPTH_RECURS_RUN, &unlimited);
memset(&spec, 0, sizeof(spec));
spec.impl = reenter;
OK(hawk_addfncwithbcstr(hawk, "host_reenter", &spec) != HAWK_NULL, "register host callback");
src = (char*)malloc(sizeof(prefix) + 12000);
OK(src != HAWK_NULL, "allocate expression source");
if (!src) goto done;
strcpy(src, prefix);
len = strlen(src);
/* Not constant-foldable; evaluates recursively without function calls. */
for (i = 0; i < 5000; i++) { src[len++] = '+'; src[len++] = 'n'; }
strcpy(src + len, "; }\n");
memset(in, 0, sizeof(in));
in[0].type = HAWK_PARSESTD_BCS;
in[0].u.bcs.ptr = src;
in[0].u.bcs.len = strlen(src);
in[1].type = HAWK_PARSESTD_NULL;
n = hawk_parsestd(hawk, in, HAWK_NULL);
OK(n >= 0, "parse recursive functions and long expression");
free(src);
if (n < 0) { printf("# %s\n", hawk_geterrbmsg(hawk)); goto done; }
rtx = hawk_rtx_openstd(hawk, 0, HAWK_T("t-011"), HAWK_NULL, HAWK_NULL, HAWK_NULL);
OK(rtx != HAWK_NULL, "open runtime on main thread");
if (!rtx) goto done;
job.rtx = rtx;
job.name = "direct";
job.require_native = 0;
job.passed = 0;
exhaust(&job);
OK(job.passed, "stack exhaustion on main thread");
check_reuse(rtx);
job.require_native = 1;
for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++)
{
for (j = 0; j < sizeof(names) / sizeof(names[0]); j++)
{
job.name = names[j];
job.passed = 0;
printf("# stack=%lu entry=%s\n", (unsigned long)sizes[i], job.name? job.name: "BEGIN");
n = on_stack(&job, sizes[i]);
OK(n == 0, "execute on bounded worker stack");
OK(n == 0 && job.passed, "native stack exhausted gracefully");
check_reuse(rtx);
}
}
/* An entry with less than the reserve must refuse even trivial work. */
job.name = "answer";
job.passed = 0;
OK(on_stack(&job, 64 * 1024) == 0 && job.passed, "reject entry without stack headroom");
check_reuse(rtx);
done:
if (rtx) hawk_rtx_close(rtx);
hawk_close(hawk);
return exit_status();
#else
static char src[] =
"function direct() { return 1 + direct(); }\n"
"function answer() { return 42; }\n";
hawk_t* hawk;
hawk_rtx_t* rtx = HAWK_NULL;
hawk_parsestd_t in[2];
hawk_val_t* v;
hawk_oow_t limit = 32;
hawk_int_t answer = 0;
int n;
no_plan();
hawk = hawk_openstd(0, HAWK_NULL);
OK(hawk != HAWK_NULL, "open interpreter for logical-depth fallback");
if (!hawk) return exit_status();
OK(hawk_setopt(hawk, HAWK_OPT_DEPTH_RECURS_RUN, &limit) == 0, "set logical recursion limit");
memset(in, 0, sizeof(in));
in[0].type = HAWK_PARSESTD_BCS;
in[0].u.bcs.ptr = src;
in[0].u.bcs.len = sizeof(src) - 1;
in[1].type = HAWK_PARSESTD_NULL;
n = hawk_parsestd(hawk, in, HAWK_NULL);
OK(n >= 0, "parse fallback test functions");
if (n < 0) goto fallback_done;
rtx = hawk_rtx_openstd(hawk, 0, HAWK_T("t-011"), HAWK_NULL, HAWK_NULL, HAWK_NULL);
OK(rtx != HAWK_NULL, "open runtime for logical-depth fallback");
if (!rtx) goto fallback_done;
v = hawk_rtx_callwithbcstr(rtx, "direct", HAWK_NULL, 0);
OK(!v && hawk_rtx_geterrnum(rtx) == HAWK_EBLKNST, "logical recursion limit stops recursive evaluation");
if (v) hawk_rtx_refdownval(rtx, v);
v = hawk_rtx_callwithbcstr(rtx, "answer", HAWK_NULL, 0);
OK(v && hawk_rtx_valtoint(rtx, v, &answer) == 0 && answer == 42, "runtime reusable after logical-depth exhaustion");
if (v) hawk_rtx_refdownval(rtx, v);
fallback_done:
if (rtx) hawk_rtx_close(rtx);
hawk_close(hawk);
return exit_status();
#endif
}