Compare commits

..
7 Commits
Author SHA1 Message Date
hyung-hwan c21623e5a5 refactored the ticker code using a global variable. - hawk_raise_gtick().
also implemented the instance_specific tick - hawk_raise_tick()
2026-08-31 01:30:25 +09:00
hyung-hwan 8267a20766 port spinlock(hak-spl.h) from qse
enhanced set_signal_handler with multiple methods
2026-08-30 21:12:31 +09:00
hyung-hwan 46dd217591 added the catchsig callback to vm for system-catch-sig and implementation of process handling
various bug fixes in dealing with multiple instances - still more to fix
more methods to dictionary access
files to access cons cell to the core module
2026-08-29 14:40:35 +09:00
hyung-hwan a7d245a92d added hak_pio_free
added pwait and pkill to the sys module
2026-08-26 18:33:10 +09:00
hyung-hwan fae68834cd added dtor to hak_hnd_t for non-fd based handles.
updated to return the handle table index from pf_system_get_sigfd
2026-08-26 18:13:43 +09:00
hyung-hwan 87a5086dba some ip functions added to the system module 2026-08-26 01:15:57 +09:00
hyung-hwan 234b031fcb added handle table routines 2026-08-26 00:52:16 +09:00
40 changed files with 4712 additions and 508 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ ACLOCAL_AMFLAGS = -I m4
gosrcdir = $(datadir)/hak/go
gosrc_DATA = go.mod hak.go hak-inst.go hak-cb.go
EXTRA_DIST = t t/test-bi.hak
EXTRA_DIST = t t/test-bi.hak src
SUBDIRS =
+1 -1
View File
@@ -386,7 +386,7 @@ AUTOMAKE_OPTION = foreign
ACLOCAL_AMFLAGS = -I m4
gosrcdir = $(datadir)/hak/go
gosrc_DATA = go.mod hak.go hak-inst.go hak-cb.go
EXTRA_DIST = t t/test-bi.hak
EXTRA_DIST = t t/test-bi.hak src
SUBDIRS = $(am__append_1) $(am__append_2)
DIST_SUBDIRS = $(SUBDIRS) pas
all: all-recursive
+55 -5
View File
@@ -360,6 +360,24 @@ static void set_signal_to_default (int sig)
#endif
}
static void set_signal_to_ignore (int sig)
{
#if defined(_WIN32) || defined(__DOS__) || defined(__OS2__)
signal (sig, SIG_IGN);
#elif defined(macintosh)
/* TODO: implement this */
#else
struct sigaction sa;
memset (&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
sigemptyset (&sa.sa_mask);
sigaction (sig, &sa, NULL);
#endif
}
/* ========================================================================= */
static void print_info (void)
@@ -464,7 +482,10 @@ static hak_oop_t execute_in_interactive_mode (hak_t* hak)
hak_decode (hak, hak_getcode(hak), 0, hak_getbclen(hak));
HAK_LOG0 (hak, HAK_LOG_MNEMONIC, "------------------------------------------\n");
g_hak = hak;
/*setup_tick ();*/
hak_catch_termreq();
hak_start_ticker();
hak_rcvtick(hak, 1);
retv = hak_execute(hak);
@@ -490,7 +511,11 @@ static hak_oop_t execute_in_interactive_mode (hak_t* hak)
}
*/
}
/*cancel_tick();*/
hak_rcvtick(hak, 0);
hak_stop_ticker();
hak_uncatch_termreq();
g_hak = HAK_NULL;
return retv;
@@ -503,8 +528,10 @@ static hak_oop_t execute_in_batch_mode(hak_t* hak, int verbose)
hak_decode(hak, hak_getcode(hak), 0, hak_getbclen(hak));
HAK_LOG3(hak, HAK_LOG_MNEMONIC, "BYTECODES bclen=%zu lflen=%zu ngtmprs=%zu\n", hak_getbclen(hak), hak_getlflen(hak), hak_getngtmprs(hak));
g_hak = hak;
/*setup_tick ();*/
hak_catch_termreq();
hak_start_ticker();
hak_rcvtick(hak, 1);
/* TESTING */
#if 0
@@ -531,10 +558,15 @@ static hak_oop_t execute_in_batch_mode(hak_t* hak, int verbose)
if (!retv) print_error(hak, "execute");
else if (verbose) hak_logbfmt(hak, HAK_LOG_STDERR, "EXECUTION OK - EXITED WITH %O\n", retv);
hak_rcvtick(hak, 0);
hak_stop_ticker();
hak_uncatch_termreq();
/*cancel_tick();*/
g_hak = HAK_NULL;
/*hak_dumpsymtab (hak);*/
return retv;
}
@@ -808,7 +840,7 @@ static int feed_loop (hak_t* hak, xtn_t* xtn, int verbose)
}
fclose (fp);
if (!is_tty && hak_getbclen(hak) > 0) execute_in_batch_mode (hak, verbose);
if (!is_tty && hak_getbclen(hak) > 0) execute_in_batch_mode(hak, verbose);
return 0;
oops:
@@ -996,7 +1028,7 @@ int main (int argc, char* argv[])
hakcb.vm_startup = vm_startup;
hakcb.vm_cleanup = vm_cleanup;
/*hakcb.vm_checkbc = vm_checkbc;*/
hak_regcb (hak, &hakcb);
hak_regcb(hak, &hakcb);
if (logopt && handle_logopt(hak, logopt) <= -1) goto oops;
@@ -1037,9 +1069,21 @@ int main (int argc, char* argv[])
goto oops;
}
/* -- from this point onward, any failure leads to jumping to the oops label
* -- instead of returning -1 immediately. --*/
set_signal(SIGINT, handle_sigint);
#if defined(SIGPIPE)
/* Writing to a pipe whose reader has gone - `hak foo.hak | head -1', or a
* child spawned by sys.popen exiting early - raises SIGPIPE, which kills
* the process by default before write() ever returns EPIPE. Ignore it here
* so the failing write reports an error the script can act on instead.
*
* This belongs to the application, not to the library: the disposition is
* process-wide, and a program that would rather die quietly on a broken
* pipe is making a legitimate choice that hak must not overrule. */
set_signal_to_ignore(SIGPIPE);
#endif
#if 0
// TODO: change the option name
@@ -1056,12 +1100,18 @@ int main (int argc, char* argv[])
if (feed_loop(hak, xtn, verbose) <= -1) goto oops;
set_signal_to_default(SIGINT);
#if defined(SIGPIPE)
set_signal_to_default(SIGPIPE);
#endif
hak_close(hak);
return 0;
oops:
set_signal_to_default(SIGINT); /* harmless to call multiple times without set_signal() */
#if defined(SIGPIPE)
set_signal_to_default(SIGPIPE);
#endif
if (hak) hak_close(hak);
return -1;
}
+3
View File
@@ -55,7 +55,9 @@ pkginclude_HEADERS = \
hak-htb.h \
hak-opt.h \
hak-pac1.h \
hak-hnd.h \
hak-pio.h \
hak-spl.h \
hak-rbt.h \
hak-str.h \
hak-upac.h \
@@ -80,6 +82,7 @@ libhak_la_SOURCES = \
gc.c \
hak.c \
heap.c \
hnd.c \
htb.c \
mb8.c \
number.c \
+35 -21
View File
@@ -155,12 +155,13 @@ am_libhak_la_OBJECTS = libhak_la-bigint.lo libhak_la-chr.lo \
libhak_la-debug.lo libhak_la-decode.lo libhak_la-dic.lo \
libhak_la-err.lo libhak_la-exec.lo libhak_la-fmt.lo \
libhak_la-gc.lo libhak_la-hak.lo libhak_la-heap.lo \
libhak_la-htb.lo libhak_la-mb8.lo libhak_la-number.lo \
libhak_la-obj.lo libhak_la-opt.lo libhak_la-pio.lo \
libhak_la-prim.lo libhak_la-print.lo libhak_la-rbt.lo \
libhak_la-read.lo libhak_la-std.lo libhak_la-str.lo \
libhak_la-sym.lo libhak_la-utf16.lo libhak_la-utf8.lo \
libhak_la-utl.lo libhak_la-xchg.lo libhak_la-xma.lo
libhak_la-hnd.lo libhak_la-htb.lo libhak_la-mb8.lo \
libhak_la-number.lo libhak_la-obj.lo libhak_la-opt.lo \
libhak_la-pio.lo libhak_la-prim.lo libhak_la-print.lo \
libhak_la-rbt.lo libhak_la-read.lo libhak_la-std.lo \
libhak_la-str.lo libhak_la-sym.lo libhak_la-utf16.lo \
libhak_la-utf8.lo libhak_la-utl.lo libhak_la-xchg.lo \
libhak_la-xma.lo
libhak_la_OBJECTS = $(am_libhak_la_OBJECTS)
AM_V_lt = $(am__v_lt_@AM_V@)
am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
@@ -206,16 +207,17 @@ am__depfiles_remade = ./$(DEPDIR)/libhak_la-bigint.Plo \
./$(DEPDIR)/libhak_la-err.Plo ./$(DEPDIR)/libhak_la-exec.Plo \
./$(DEPDIR)/libhak_la-fmt.Plo ./$(DEPDIR)/libhak_la-gc.Plo \
./$(DEPDIR)/libhak_la-hak.Plo ./$(DEPDIR)/libhak_la-heap.Plo \
./$(DEPDIR)/libhak_la-htb.Plo ./$(DEPDIR)/libhak_la-mb8.Plo \
./$(DEPDIR)/libhak_la-number.Plo ./$(DEPDIR)/libhak_la-obj.Plo \
./$(DEPDIR)/libhak_la-opt.Plo ./$(DEPDIR)/libhak_la-pio.Plo \
./$(DEPDIR)/libhak_la-prim.Plo ./$(DEPDIR)/libhak_la-print.Plo \
./$(DEPDIR)/libhak_la-rbt.Plo ./$(DEPDIR)/libhak_la-read.Plo \
./$(DEPDIR)/libhak_la-std.Plo ./$(DEPDIR)/libhak_la-str.Plo \
./$(DEPDIR)/libhak_la-sym.Plo ./$(DEPDIR)/libhak_la-utf16.Plo \
./$(DEPDIR)/libhak_la-utf8.Plo ./$(DEPDIR)/libhak_la-utl.Plo \
./$(DEPDIR)/libhak_la-xchg.Plo ./$(DEPDIR)/libhak_la-xma.Plo \
./$(DEPDIR)/libhakx_la-json.Plo ./$(DEPDIR)/libhakx_la-tmr.Plo \
./$(DEPDIR)/libhak_la-hnd.Plo ./$(DEPDIR)/libhak_la-htb.Plo \
./$(DEPDIR)/libhak_la-mb8.Plo ./$(DEPDIR)/libhak_la-number.Plo \
./$(DEPDIR)/libhak_la-obj.Plo ./$(DEPDIR)/libhak_la-opt.Plo \
./$(DEPDIR)/libhak_la-pio.Plo ./$(DEPDIR)/libhak_la-prim.Plo \
./$(DEPDIR)/libhak_la-print.Plo ./$(DEPDIR)/libhak_la-rbt.Plo \
./$(DEPDIR)/libhak_la-read.Plo ./$(DEPDIR)/libhak_la-std.Plo \
./$(DEPDIR)/libhak_la-str.Plo ./$(DEPDIR)/libhak_la-sym.Plo \
./$(DEPDIR)/libhak_la-utf16.Plo ./$(DEPDIR)/libhak_la-utf8.Plo \
./$(DEPDIR)/libhak_la-utl.Plo ./$(DEPDIR)/libhak_la-xchg.Plo \
./$(DEPDIR)/libhak_la-xma.Plo ./$(DEPDIR)/libhakx_la-json.Plo \
./$(DEPDIR)/libhakx_la-tmr.Plo \
./$(DEPDIR)/libhakx_la-x-client.Plo \
./$(DEPDIR)/libhakx_la-x-proto.Plo \
./$(DEPDIR)/libhakx_la-x-server.Plo \
@@ -248,9 +250,9 @@ am__can_run_installinfo = \
*) (install-info --version) >/dev/null 2>&1;; \
esac
am__pkginclude_HEADERS_DIST = hak.h hak-chr.h hak-cmgr.h hak-cmn.h \
hak-fmt.h hak-htb.h hak-opt.h hak-pac1.h hak-pio.h hak-rbt.h \
hak-str.h hak-upac.h hak-utl.h hak-xma.h hak-x.h hak-tmr.h \
hak-json.h
hak-fmt.h hak-htb.h hak-opt.h hak-pac1.h hak-hnd.h hak-pio.h \
hak-spl.h hak-rbt.h hak-str.h hak-upac.h hak-utl.h hak-xma.h \
hak-x.h hak-tmr.h hak-json.h
HEADERS = $(pkginclude_HEADERS)
am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) \
hak-cfg.h.in
@@ -442,8 +444,9 @@ LIBADD_LIB_COMMON = $(LIBM) $(am__append_1) $(am__append_2) \
@WIN32_TRUE@ -DHAK_DEFAULT_PFMODPOSTFIX=\"-1.dll\" \
@WIN32_TRUE@ $(am__append_4) $(am__append_5)
pkginclude_HEADERS = hak.h hak-chr.h hak-cmgr.h hak-cmn.h hak-fmt.h \
hak-htb.h hak-opt.h hak-pac1.h hak-pio.h hak-rbt.h hak-str.h \
hak-upac.h hak-utl.h hak-xma.h $(am__append_8)
hak-htb.h hak-opt.h hak-pac1.h hak-hnd.h hak-pio.h hak-spl.h \
hak-rbt.h hak-str.h hak-upac.h hak-utl.h hak-xma.h \
$(am__append_8)
pkglib_LTLIBRARIES = libhak.la $(am__append_7)
libhak_la_SOURCES = \
hak-prv.h \
@@ -462,6 +465,7 @@ libhak_la_SOURCES = \
gc.c \
hak.c \
heap.c \
hnd.c \
htb.c \
mb8.c \
number.c \
@@ -612,6 +616,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhak_la-gc.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhak_la-hak.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhak_la-heap.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhak_la-hnd.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhak_la-htb.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhak_la-mb8.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhak_la-number.Plo@am__quote@ # am--include-marker
@@ -766,6 +771,13 @@ libhak_la-heap.lo: heap.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhak_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libhak_la-heap.lo `test -f 'heap.c' || echo '$(srcdir)/'`heap.c
libhak_la-hnd.lo: hnd.c
@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhak_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libhak_la-hnd.lo -MD -MP -MF $(DEPDIR)/libhak_la-hnd.Tpo -c -o libhak_la-hnd.lo `test -f 'hnd.c' || echo '$(srcdir)/'`hnd.c
@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhak_la-hnd.Tpo $(DEPDIR)/libhak_la-hnd.Plo
@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hnd.c' object='libhak_la-hnd.lo' libtool=yes @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhak_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libhak_la-hnd.lo `test -f 'hnd.c' || echo '$(srcdir)/'`hnd.c
libhak_la-htb.lo: htb.c
@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhak_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libhak_la-htb.lo -MD -MP -MF $(DEPDIR)/libhak_la-htb.Tpo -c -o libhak_la-htb.lo `test -f 'htb.c' || echo '$(srcdir)/'`htb.c
@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhak_la-htb.Tpo $(DEPDIR)/libhak_la-htb.Plo
@@ -1110,6 +1122,7 @@ distclean: distclean-am
-rm -f ./$(DEPDIR)/libhak_la-gc.Plo
-rm -f ./$(DEPDIR)/libhak_la-hak.Plo
-rm -f ./$(DEPDIR)/libhak_la-heap.Plo
-rm -f ./$(DEPDIR)/libhak_la-hnd.Plo
-rm -f ./$(DEPDIR)/libhak_la-htb.Plo
-rm -f ./$(DEPDIR)/libhak_la-mb8.Plo
-rm -f ./$(DEPDIR)/libhak_la-number.Plo
@@ -1195,6 +1208,7 @@ maintainer-clean: maintainer-clean-am
-rm -f ./$(DEPDIR)/libhak_la-gc.Plo
-rm -f ./$(DEPDIR)/libhak_la-hak.Plo
-rm -f ./$(DEPDIR)/libhak_la-heap.Plo
-rm -f ./$(DEPDIR)/libhak_la-hnd.Plo
-rm -f ./$(DEPDIR)/libhak_la-htb.Plo
-rm -f ./$(DEPDIR)/libhak_la-mb8.Plo
-rm -f ./$(DEPDIR)/libhak_la-number.Plo
+68 -15
View File
@@ -50,6 +50,12 @@ static hak_ooch_t oocstr_colon[2] = { ':', '\0' };
static hak_ooch_t oocstr_dash[2] = { '-', '\0' };
static hak_ooch_t oocstr_none[1] = { '\0' };
/* global tick counter - all instances configured to receive ticks
* switch processes based on this counter.
* sig_atomic_t is more strict choice but hak_uint32_t is believed to be safe enough. */
/* static volatile sig_atomic_t gtick = 0; */
static volatile hak_uint32_t gtick = 0;
#define PROC_MAP_INC 64
/* TODO: adjust these max semaphore pointer buffer capacity,
@@ -1739,7 +1745,7 @@ static void update_sem_heap (hak_t* hak, hak_ooi_t index, hak_oop_semaphore_t ne
}
#endif
static int add_sem_to_sem_io_tuple (hak_t* hak, hak_oop_semaphore_t sem, hak_ooi_t io_handle, hak_semaphore_io_type_t io_type)
int hak_add_sem_to_sem_io_tuple (hak_t* hak, hak_oop_semaphore_t sem, hak_ooi_t io_handle, hak_semaphore_io_type_t io_type)
{
hak_ooi_t index;
hak_ooi_t new_mask;
@@ -3212,24 +3218,50 @@ static HAK_INLINE int switch_process_if_needed (hak_t* hak)
switch_to_next:
/* TODO: implement different process switching scheme - time-slice or clock based??? */
#if defined(HAK_EXTERNAL_PROCESS_SWITCH)
if (hak->switch_proc)
#if 0
if (hak->rcv_tick && (hak->last_tick != hak->tick || hak->last_gtick != gtick))
{
#endif
if (!hak->proc_switched)
{
switch_to_next_runnable_process(hak);
hak->proc_switched = 0;
}
#if defined(HAK_EXTERNAL_PROCESS_SWITCH)
hak->switch_proc = 0;
}
else hak->proc_switched = 0;
#endif
/* clear the state regardless of the actual switching */
hak->last_gtick = gtick;
hak->last_tick = hak->tick;
/* switching happens only if it didn't happen yet */
if (!hak->proc_switched) switch_to_next_runnable_process(hak);
}
#else
if (hak->rcv_tick && !hak->proc_switched && (hak->last_tick != hak->tick || hak->last_gtick != gtick))
{
/* clear the state only if the actual switching would happen */
hak->last_gtick = gtick;
hak->last_tick = hak->tick;
switch_to_next_runnable_process(hak); /* actual switching */
}
#endif
hak->proc_switched = 0;
return 1;
}
void hak_rcvtick (hak_t* hak, int enabled)
{
if (enabled)
{
hak->last_gtick = gtick;
hak->last_tick = hak->tick;
}
hak->rcv_tick = !!enabled;
}
void hak_raisetick (hak_t* hak)
{
hak->tick++;
}
void hak_raise_gtick (int unused)
{
/* this function is global and not bound to a specific instance. */
gtick++;
}
/* ------------------------------------------------------------------------- */
static HAK_INLINE int do_return_from_block (hak_t* hak)
@@ -5705,7 +5737,11 @@ hak_pfrc_t hak_pf_semaphore_signal (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
static hak_pfrc_t __semaphore_signal_on_io (hak_t* hak, hak_ooi_t nargs, hak_semaphore_io_type_t io_type)
{
hak_oop_semaphore_t sem;
#if 0
hak_oop_t fd;
#else
hak_hnd_t* hnd;
#endif
sem = (hak_oop_semaphore_t)HAK_STACK_GETARG(hak, nargs, 0);
if (!HAK_IS_SEMAPHORE(hak, sem))
@@ -5714,13 +5750,22 @@ static hak_pfrc_t __semaphore_signal_on_io (hak_t* hak, hak_ooi_t nargs, hak_sem
return HAK_PF_FAILURE;
}
#if 0
fd = HAK_STACK_GETARG(hak, nargs, 1);
if (!HAK_OOP_IS_SMOOI(fd))
{
hak_seterrbfmt(hak, HAK_EINVAL, "handle not a small integer - %O", fd);
return HAK_PF_FAILURE;
}
#else
/* the second argument is a system handle id, not a raw descriptor.
* resolving it through the handle table is what stops hak code from
* naming a descriptor it never opened - hak's own multiplexer, signal
* and io-thread descriptors among them - and what guarantees that
* hak_closehnd() will later unbind whatever we register here. */
hnd = hak_gethndwithoop(hak, HAK_STACK_GETARG(hak, nargs, 1), HAK_HND_TYPE_ALL_MUXABLE);
if (HAK_UNLIKELY(!hnd)) return HAK_PF_FAILURE;
#endif
if (sem->subtype != hak->_nil)
{
@@ -5743,10 +5788,18 @@ static hak_pfrc_t __semaphore_signal_on_io (hak_t* hak, hak_ooi_t nargs, hak_sem
return HAK_PF_FAILURE;
}
#if 0
if (add_sem_to_sem_io_tuple(hak, sem, HAK_OOP_TO_SMOOI(fd), io_type) <= -1)
#else
if (hak_bindhnd(hak, hnd, sem, io_type) <= -1)
#endif
{
const hak_ooch_t* oldmsg = hak_backuperrmsg(hak);
#if 0
hak_seterrbfmt(hak, hak->errnum, "unable to add the handle %zd to the multiplexer for %hs - %js", HAK_OOP_TO_SMOOI(fd), io_type_str[io_type], oldmsg);
#else
hak_seterrbfmt(hak, hak->errnum, "unable to add the handle %zd to the multiplexer for %hs - %js", hnd->id, io_type_str[io_type], oldmsg);
#endif
return HAK_PF_FAILURE;
}
+3 -3
View File
@@ -1137,7 +1137,7 @@ void hak_gc (hak_t* hak, int full)
{
if (hak->gci.lazy_sweep) hak_gc_ms_sweep_lazy(hak, HAK_TYPE_MAX(hak_oow_t));
HAK_LOG1 (hak, HAK_LOG_GC | HAK_LOG_INFO, "Starting GC (mark-sweep) - gci.bsz = %zu\n", hak->gci.bsz);
HAK_LOG1(hak, HAK_LOG_GC | HAK_LOG_INFO, "Starting GC (mark-sweep) - gci.bsz = %zu\n", hak->gci.bsz);
hak->gci.stack.len = 0;
/*hak->gci.stack.max = 0;*/
@@ -1156,7 +1156,7 @@ void hak_gc (hak_t* hak, int full)
gc_ms_sweep(hak);
}
HAK_LOG2 (hak, HAK_LOG_GC | HAK_LOG_INFO, "Finished GC (mark-sweep) - gci.bsz = %zu, gci.stack.max %zu\n", hak->gci.bsz, hak->gci.stack.max);
HAK_LOG2(hak, HAK_LOG_GC | HAK_LOG_INFO, "Finished GC (mark-sweep) - gci.bsz = %zu, gci.stack.max %zu\n", hak->gci.bsz, hak->gci.stack.max);
}
hak_oop_t hak_moveoop (hak_t* hak, hak_oop_t oop)
@@ -1314,7 +1314,7 @@ void hak_gc (hak_t* hak)
{
if ((hak_oop_t)buc->slot[index] != hak->_nil)
{
HAK_LOG1 (hak, HAK_LOG_GC | HAK_LOG_DEBUG, "\t%O\n", buc->slot[index]);
HAK_LOG1(hak, HAK_LOG_GC | HAK_LOG_DEBUG, "\t%O\n", buc->slot[index]);
}
}
HAK_LOG0 (hak, HAK_LOG_GC | HAK_LOG_DEBUG, "--------------------------------------------\n");
+347
View File
@@ -0,0 +1,347 @@
/*
Copyright (c) 2016-2018 Chung, Hyung-Hwan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _HAK_HND_H_
#define _HAK_HND_H_
#include <hak.h>
/** \file
* This file provides the system handle table - the single place where hak
* keeps track of operating system resources handed out to hak code.
*
* hak code never sees a raw file descriptor or a pointer. It sees a small
* non-negative integer id which is resolved against this table on every use.
* That resolution is what makes the following impossible:
*
* - naming a descriptor the script never opened (including hak's own
* multiplexer, signal and io-thread descriptors, and any descriptor the
* host application that embeds hak happens to hold open),
* - aiming hak_releaseiohandle() at a descriptor hak does not own, which
* would delete the VM's own multiplexer registration for it,
* - keeping a multiplexer registration alive across a close(), where the
* descriptor number may be recycled for something unrelated.
*
* There is exactly one table per #hak_t, shared by every module, so ids are
* unique across subsystems and the close protocol lives in one place.
*
* A node is one waitable operating system handle. A resource made of several
* handles (a child process with its pipes, say) is represented as several
* nodes tied together by \a owner, so that "which handle am I waiting on"
* never becomes a parameter of read, write or bind.
*/
/**
* The hak_hnd_type_t type enumerates the kinds of resource a node may hold.
* The values are bit flags so that hak_gethnd() can be given the set of
* types a caller is prepared to accept.
*/
enum hak_hnd_type_t
{
HAK_HND_TYPE_FILE = (1 << 0), /**< regular file, directory or block device - not muxable */
HAK_HND_TYPE_PIPE = (1 << 1), /**< pipe or fifo end */
HAK_HND_TYPE_SCK = (1 << 2), /**< socket */
HAK_HND_TYPE_CHR = (1 << 3), /**< terminal or character device */
HAK_HND_TYPE_DIR = (1 << 4), /**< directory stream - a pointer, not a descriptor */
HAK_HND_TYPE_PROC = (1 << 5), /**< child process - a pointer, not a descriptor */
HAK_HND_TYPE_EVT = (1 << 6) /**< anonymous-inode descriptor: pidfd, eventfd,
* timerfd, signalfd. pollable but carries no
* file type bits, and generally not readable
* as a byte stream. */
};
typedef enum hak_hnd_type_t hak_hnd_type_t;
/** every type that is backed by a file descriptor */
#define HAK_HND_TYPE_ALL_FD \
(HAK_HND_TYPE_FILE | HAK_HND_TYPE_PIPE | HAK_HND_TYPE_SCK | \
HAK_HND_TYPE_CHR | HAK_HND_TYPE_EVT)
/** every type that moves bytes, and so may be given to hak_readhnd() */
#define HAK_HND_TYPE_ALL_STREAM \
(HAK_HND_TYPE_FILE | HAK_HND_TYPE_PIPE | HAK_HND_TYPE_SCK | HAK_HND_TYPE_CHR)
/** every type the multiplexer can accept */
#define HAK_HND_TYPE_ALL_MUXABLE \
(HAK_HND_TYPE_PIPE | HAK_HND_TYPE_SCK | HAK_HND_TYPE_CHR | HAK_HND_TYPE_EVT)
enum hak_hnd_flag_t
{
/** the multiplexer accepts this handle. set by hak_wrapfd() from the
* probed type; a regular file never gets it because epoll refuses one
* outright and poll() would report it permanently ready. */
HAK_HND_FLAG_MUXABLE = (1 << 0),
/** O_NONBLOCK is set on the handle */
HAK_HND_FLAG_NONBLOCK = (1 << 1),
/** a semaphore is currently bound to this handle. hak_closehnd() uses
* this to know it must call hak_releaseiohandle() first. */
HAK_HND_FLAG_IN_MUX = (1 << 2),
/** release the node without closing the underlying handle. useful for a
* descriptor owned by someone else that was only wrapped for the ride. */
HAK_HND_FLAG_KEEPOPEN = (1 << 3)
};
typedef enum hak_hnd_flag_t hak_hnd_flag_t;
/** hak_wrapfd() should put the descriptor into non-blocking mode */
#define HAK_HND_OPEN_NONBLOCK HAK_HND_FLAG_NONBLOCK
/** force #HAK_HND_FLAG_MUXABLE regardless of what the probe concluded, for a
* caller that knows the descriptor is pollable */
#define HAK_HND_OPEN_MUXABLE HAK_HND_FLAG_MUXABLE
/** hak_closehnd() should not close the underlying handle */
#define HAK_HND_OPEN_KEEPOPEN HAK_HND_FLAG_KEEPOPEN
typedef struct hak_hnd_t hak_hnd_t;
/**
* The hak_hnd_dtor_t type defines how a node's underlying resource is
* released. It is what lets a pointer-shaped resource - a directory stream, a
* child process - be disposed of correctly even when hak code never closed it
* and hak_finihndtab() is doing the closing at teardown.
*
* The node itself is still returned to the free list afterwards; a destructor
* only has to deal with what \a hnd points at.
*/
typedef void (*hak_hnd_dtor_t) (
hak_t* hak,
hak_hnd_t* hnd
);
struct hak_hnd_t
{
/* the id is what hak code holds. it is always >= 0 and always within
* HAK_SMOOI_MAX so that it can be handed over as a small integer. */
hak_ooi_t id;
hak_hnd_type_t type;
int flags;
/* id of the node that owns this one, or -1. closing an owner closes
* everything it owns. */
hak_ooi_t owner;
union
{
int fd; /**< HAK_HND_TYPE_FILE, _PIPE, _SCK, _CHR */
void* ptr; /**< HAK_HND_TYPE_DIR, _PROC */
} u;
/* how to release u.ptr, or a descriptor needing more than close().
* HAK_NULL means the default: close() for a descriptor, nothing for a
* pointer - which is why a pointer-shaped node without one leaks. */
hak_hnd_dtor_t dtor;
/* house keeping. do not touch from outside hnd.c */
hak_hnd_t* prev;
hak_hnd_t* next;
};
typedef struct hak_hndtab_t hak_hndtab_t;
/* ========================================================================= */
/* THE UNIFORM I/O CONTRACT */
/* ========================================================================= */
/**
* hak_readhnd() and hak_writehnd() never block and never raise. They return
* the number of bytes transferred, 0 at end of file, or one of the two values
* below. #HAK_HND_IO_WOULDBLOCK is an ordinary outcome that the caller is
* expected to hand back to hak code so that it can wait on a semaphore and
* retry; only #HAK_HND_IO_ERROR means the hak error has been set and the
* primitive should fail.
*/
#define HAK_HND_IO_WOULDBLOCK ((hak_ooi_t)-1)
#define HAK_HND_IO_ERROR ((hak_ooi_t)-2)
#if defined(__cplusplus)
extern "C" {
#endif
/* ========================================================================= */
/* TABLE LIFECYCLE - called by hak_init()/hak_fini(), not by modules */
/* ========================================================================= */
int hak_inithndtab (hak_t* hak);
/**
* Closes every handle still open and frees the table. Handles that outlive
* the hak instance would otherwise leak descriptors and child processes.
*/
void hak_finihndtab (hak_t* hak);
/* ========================================================================= */
/* CREATION */
/* ========================================================================= */
/**
* Wraps the descriptor \a fd into a new node and returns it.
*
* The kind of descriptor is probed with fstat() and recorded in \a type,
* together with #HAK_HND_FLAG_MUXABLE when the multiplexer can accept it.
* Pass 0 for \a type_hint to take whatever the probe finds, or a mask of
* acceptable #hak_hnd_type_t values to require one of them.
*
* \a flags may carry #HAK_HND_OPEN_NONBLOCK and #HAK_HND_OPEN_KEEPOPEN.
*
* On success the table owns \a fd. On failure \a fd is left alone, so the
* caller remains responsible for closing it.
*
* \return node pointer on success, #HAK_NULL on failure
*/
hak_hnd_t* hak_wrapfd (
hak_t* hak,
int fd,
hak_hnd_type_t type_hint,
int flags
);
/**
* Wraps a pointer-shaped resource, such as a directory stream or a child
* process object. Such a node is never muxable.
*
* \a dtor is how \a ptr gets released, and is called by hak_closehnd() -
* including the closes that hak_finihndtab() performs at teardown. Passing
* #HAK_NULL means the pointer is owned elsewhere and this node must not
* release it.
*/
hak_hnd_t* hak_wrapptr (
hak_t* hak,
void* ptr,
hak_hnd_type_t type,
int flags,
hak_hnd_dtor_t dtor
);
/**
* Like hak_wrapfd() but idempotent: if \a fd already has a node, that node is
* returned instead of failing the way hak_wrapfd() does. Use it for a
* descriptor the VM owns and hands out repeatedly - the signal descriptor, for
* instance - so that hak code always sees the same id for it.
*
* Pass #HAK_HND_OPEN_KEEPOPEN for anything the handle table must not close.
*/
hak_hnd_t* hak_wrapfd_once (
hak_t* hak,
int fd,
hak_hnd_type_t type_hint,
int flags
);
/**
* Makes \a hnd owned by \a owner, so that closing \a owner closes \a hnd too.
*/
void hak_ownhnd (
hak_t* hak,
hak_hnd_t* hnd,
hak_hnd_t* owner
);
/* ========================================================================= */
/* LOOKUP - the validation gate */
/* ========================================================================= */
/**
* Resolves \a id to a node, requiring its type to be among
* \a acceptable_types. Sets the hak error and returns #HAK_NULL when the id
* is out of range, refers to no live node, or refers to a node of the wrong
* kind - so a caller can simply return HAK_PF_FAILURE.
*/
hak_hnd_t* hak_gethnd (
hak_t* hak,
hak_ooi_t id,
int acceptable_types
);
/**
* Like hak_gethnd() but takes the id as an object, which is what a primitive
* has at hand. Rejects anything that is not a small integer.
*/
hak_hnd_t* hak_gethndwithoop (
hak_t* hak,
hak_oop_t id,
int acceptable_types
);
/* ========================================================================= */
/* DESTRUCTION */
/* ========================================================================= */
/**
* Closes \a hnd. Handles owned by it are closed first, any multiplexer
* registration is dropped through hak_releaseiohandle() before the underlying
* handle goes away, the resource is released through the node's
* #hak_hnd_dtor_t (or close() when it has none and is a descriptor), and the
* node is returned to the free list.
* \return 0 on success, -1 if the underlying close failed (the node is
* released either way)
*/
int hak_closehnd (
hak_t* hak,
hak_hnd_t* hnd
);
/* ========================================================================= */
/* MULTIPLEXER BINDING - the only path into the io semaphore tuples */
/* ========================================================================= */
/**
* Binds \a sem to \a hnd so that the semaphore is signalled when the handle
* becomes ready for \a io_type. Fails if the handle is not muxable.
*
* Unbinding is done with hak_pf_semaphore_unsignal() from hak code, which
* works from the semaphore rather than from the handle and so needs no
* handle-side counterpart.
*
* \return 0 on success, -1 on failure
*/
int hak_bindhnd (
hak_t* hak,
hak_hnd_t* hnd,
hak_oop_semaphore_t sem,
hak_semaphore_io_type_t io_type
);
/* ========================================================================= */
/* I/O */
/* ========================================================================= */
hak_ooi_t hak_readhnd (
hak_t* hak,
hak_hnd_t* hnd,
void* buf,
hak_oow_t len
);
hak_ooi_t hak_writehnd (
hak_t* hak,
hak_hnd_t* hnd,
const void* buf,
hak_oow_t len
);
#if defined(__cplusplus)
}
#endif
#endif
+16
View File
@@ -286,6 +286,22 @@ HAK_EXPORT void hak_pio_fini (
hak_pio_t* pio /**< pio object */
);
/**
* The hak_pio_free() function closes the pipes, makes sure the child is
* reaped, and frees the #hak_pio_t structure.
*
* Unlike hak_pio_close() it never waits indefinitely: the child is looked at
* without blocking first, and only if it is still running is it killed, which
* bounds the wait that follows because SIGKILL cannot be caught. Use this
* wherever an unbounded wait is unacceptable - inside a scheduler that has
* other work to run, or while tearing down a #hak_t.
*
* The error currently set on \a hak is preserved across the call.
*/
HAK_EXPORT void hak_pio_free (
hak_pio_t* pio /**< pio object */
);
#if defined(HAK_HAVE_INLINE)
static HAK_INLINE void* hak_pio_getxtn (hak_pio_t* pio) { return (void*)(pio + 1); }
#else
+14 -4
View File
@@ -29,6 +29,7 @@
#include <hak-chr.h>
#include <hak-cmgr.h>
#include <hak-fmt.h>
#include <hak-hnd.h>
#include <hak-str.h>
#include <hak-utl.h>
@@ -200,10 +201,6 @@ do { \
/*#define HAK_PROFILE_VM 1*/
#endif
/* allow the caller to drive process switching by calling
* stix_switchprocess(). */
#define HAK_EXTERNAL_PROCESS_SWITCH
/* limit the maximum object size such that:
* 1. an index to an object field can be represented in a small integer.
* 2. the maximum number of bits including bit-shifts can be represented
@@ -1652,6 +1649,19 @@ hak_heap_t* hak_makeheap (
/**
* The hak_killheap() function destroys the heap pointed to by \a heap.
*/
/* --------------------------------------------------------------------------
* IO SEMAPHORE TUPLES (exec.c)
* -------------------------------------------------------------------------- */
/* hak_bindhnd() in hnd.c is the only intended caller. binding is kept behind
* the handle table so that a raw descriptor can never be named from hak code. */
int hak_add_sem_to_sem_io_tuple (
hak_t* hak,
hak_oop_semaphore_t sem,
hak_ooi_t io_handle,
hak_semaphore_io_type_t io_type
);
void hak_killheap (
hak_t* hak,
hak_heap_t* heap
+273
View File
@@ -0,0 +1,273 @@
/*
Copyright (c) 2016-2018 Chung, Hyung-Hwan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _HAK_SPL_H_
#define _HAK_SPL_H_
#include <hak-cmn.h>
/** \file
* This file provides a spinlock.
*
* A spinlock is the lock of last resort: it burns processor time while it
* waits, so a real mutex is preferable wherever one exists. What it has that
* a mutex does not is that zero is a valid unlocked state. HAK_SPL_INIT is a
* constant, so a spinlock can be a statically initialised global that is ready
* before main() runs and needs no library initialisation call to construct it.
*
* It must never be taken from a signal handler or an interrupt handler. The
* atomics it is built from are async-signal-safe, but that is not the binding
* constraint: a signal delivered to the thread already holding the lock would
* spin forever on a lock that thread can no longer reach the end of.
*
* Keep the guarded region to plain memory access. Spinning while the holder
* sits in a system call wastes exactly the time the spinlock was chosen to
* save.
*
* HAK_SUPPORT_SPL is left undefined if no implementation fits the target, so
* a caller can fall back to something else:
*
* \code
* #if defined(HAK_SUPPORT_SPL)
* static hak_spl_t lck = HAK_SPL_INIT;
* hak_spl_lock(&lck);
* ...
* hak_spl_unlock(&lck);
* #endif
* \endcode
*
* Define HAK_SPL_UNSUPPORTED_ERROR to turn an unsupported target into a
* compile-time error instead.
*/
#define HAK_SUPPORT_SPL
typedef volatile hak_uint32_t hak_spl_t;
#define HAK_SPL_INIT (0)
#if defined(HAK_HAVE_INLINE)
static HAK_INLINE void hak_spl_init (hak_spl_t* spl) { *spl = HAK_SPL_INIT; }
#else
# define hak_spl_init(spl) ((*(spl)) = HAK_SPL_INIT)
#endif
/* hint to the processor that this is a spin-wait, and give up the timeslice
* where that is cheap to do. without it a waiter can starve the holder on a
* single processor. */
#if defined(_WIN32)
# define HAK_SPL_RELAX() Sleep(0)
#elif defined(__OS2__)
# define HAK_SPL_RELAX() DosSleep(0)
#elif defined(__GNUC__) && (defined(__x86_64) || defined(__amd64) || defined(__i386) || defined(i386))
/* "rep; nop" is the pause instruction, and decodes as a plain nop on
* processors that predate it, so it is safe unconditionally. */
# define HAK_SPL_RELAX() __asm__ __volatile__ ("rep; nop" : : : "memory")
#else
# define HAK_SPL_RELAX() ((void)0)
#endif
/* __sync_lock_test_and_set()/__sync_lock_release() are the pair gcc documents
* for building a spinlock: the first is an acquire-barrier exchange, the second
* a release-barrier store. hak-cmn.h probes them with __has_builtin, which is
* itself newer than the builtins are, so accept any gcc from 4.1 as well. That
* covers every architecture gcc targets, leaving the hand-written arms below
* for compilers older than that. */
#if defined(HAK_HAVE_SYNC_LOCK_TEST_AND_SET) && defined(HAK_HAVE_SYNC_LOCK_RELEASE)
# define HAK_SPL_USE_SYNC_BUILTINS
#elif defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1))
# define HAK_SPL_USE_SYNC_BUILTINS
#endif
#if defined(HAK_SPL_USE_SYNC_BUILTINS)
/* =======================================================================
* COMPILERS WITH BUILTIN ATOMICS
* ======================================================================= */
#if defined(HAK_HAVE_INLINE)
static HAK_INLINE_ALWAYS int hak_spl_trylock (hak_spl_t* spl) { return !__sync_lock_test_and_set(spl, 1); }
static HAK_INLINE_ALWAYS void hak_spl_lock (hak_spl_t* spl) { while (__sync_lock_test_and_set(spl, 1)) HAK_SPL_RELAX(); }
static HAK_INLINE_ALWAYS void hak_spl_unlock (hak_spl_t* spl) { __sync_lock_release(spl); }
#else
# define hak_spl_trylock(spl) (!__sync_lock_test_and_set(spl, 1))
# define hak_spl_lock(spl) do { while (__sync_lock_test_and_set(spl, 1)) HAK_SPL_RELAX(); } while(0)
# define hak_spl_unlock(spl) (__sync_lock_release(spl))
#endif
#elif defined(_WIN32)
/* =======================================================================
* WIN32 WITHOUT GCC - MSVC AND FRIENDS
*
* InterlockedCompareExchange() has been available since NT 3.51 and
* carries a full barrier, so this arm holds for the whole range this
* file targets. It is the reason the header exists: _WIN32 runs a
* ticker thread but has no pthreads, and CRITICAL_SECTION cannot be
* initialised statically.
* ======================================================================= */
#if defined(HAK_HAVE_INLINE)
static HAK_INLINE_ALWAYS int hak_spl_trylock (hak_spl_t* spl) { return InterlockedCompareExchange((LONG volatile*)spl, 1, 0) == 0; }
static HAK_INLINE_ALWAYS void hak_spl_lock (hak_spl_t* spl) { while (InterlockedCompareExchange((LONG volatile*)spl, 1, 0)) HAK_SPL_RELAX(); }
static HAK_INLINE_ALWAYS void hak_spl_unlock (hak_spl_t* spl) { InterlockedExchange((LONG volatile*)spl, 0); }
#else
# define hak_spl_trylock(spl) (InterlockedCompareExchange((LONG volatile*)(spl), 1, 0) == 0)
# define hak_spl_lock(spl) do { while (InterlockedCompareExchange((LONG volatile*)(spl), 1, 0)) HAK_SPL_RELAX(); } while(0)
# define hak_spl_unlock(spl) (InterlockedExchange((LONG volatile*)(spl), 0))
#endif
#elif defined(_SCO_DS)
/* =======================================================================
* SCO DEVELOPEMENT SYSTEM
*
* NOTE: when the asm macros were indented, the compiler/linker ended up
* with undefined symbols. never indent hak_spl_xxx macros.
* ======================================================================= */
asm int hak_spl_trylock (hak_spl_t* spl)
{
%reg spl
movl $1, %eax
xchgl (spl), %eax
xorl $1, %eax / return zero on failure, non-zero on success
%mem spl
movl spl, %ecx
movl $1, %eax
xchgl (%ecx), %eax
xorl $1, %eax / return zero on failure, non-zero on success
}
/* jump labels cannot be made unique across multiple occurrences of an asm
* macro, so the loop lives in C instead. */
#define hak_spl_lock(x) do { while (!hak_spl_trylock(x)) HAK_SPL_RELAX(); } while(0)
asm void hak_spl_unlock (hak_spl_t* spl)
{
/* don't need xchg as movl on an aligned data is atomic */
/* mfence is 0F AE F0 */
%reg spl
.byte 0x0F
.byte 0xAE
.byte 0xF0
movl $0, (spl)
%mem spl
.byte 0x0F
.byte 0xAE
.byte 0xF0
movl spl, %ecx
movl $0, (%ecx)
}
#elif defined(__GNUC__) && (defined(__x86_64) || defined(__amd64) || defined(__i386) || defined(i386))
/* =======================================================================
* GCC OLDER THAN 4.1 ON X86
* ======================================================================= */
static HAK_INLINE int hak_spl_trylock (hak_spl_t* spl)
{
int x = 1;
__asm__ volatile (
"xchgl %0, (%2)\n"
: "=r"(x)
: "0"(x), "r"(spl)
: "memory"
);
return !x;
}
static HAK_INLINE void hak_spl_lock (hak_spl_t* spl)
{
while (!hak_spl_trylock(spl)) HAK_SPL_RELAX();
}
static HAK_INLINE void hak_spl_unlock (hak_spl_t* spl)
{
#if defined(__x86_64) || defined(__amd64)
__asm__ volatile (
"mfence\n\t"
"movl $0, (%0)\n"
:
:"r"(spl)
:"memory"
);
#else
__asm__ volatile (
"movl $0, (%0)\n"
:
:"r"(spl)
:"memory"
);
#endif
}
#elif defined(__GNUC__) && (defined(__POWERPC__) || defined(__powerpc) || defined(__powerpc__) || defined(__ppc))
/* =======================================================================
* GCC OLDER THAN 4.1 ON POWERPC
*
* lwarx loads the word and reserves the location; the paired stwcx.
* stores only if the reservation still holds.
* ======================================================================= */
static HAK_INLINE int hak_spl_trylock (hak_spl_t* spl)
{
unsigned int rc;
__asm__ volatile (
"1:\n"
"lwarx %0,0,%1\n" /* load and reserve. rc(%0) = *spl(%1) */
"cmpwi cr0,%0,0\n" /* cr0 = (rc compare-with 0) */
"li %0,0\n" /* rc = 0(failure) */
"bne cr0,2f\n" /* if cr0 != 0, goto 2; */
"li %0,1\n" /* rc = 1(success) */
"stwcx. %0,0,%1\n" /* *spl(%1) = 1(value in rc) if reserved */
"bne cr0,1b\n" /* if reservation is lost, goto 1 */
"lwsync\n"
"2:\n"
: "=&r"(rc)
: "r"(spl)
: "cr0", "memory"
);
return rc;
}
static HAK_INLINE void hak_spl_lock (hak_spl_t* spl)
{
while (!hak_spl_trylock(spl)) HAK_SPL_RELAX();
}
static HAK_INLINE void hak_spl_unlock (hak_spl_t* spl)
{
__asm__ volatile ("lwsync\n" : : : "memory");
*spl = 0;
}
#else
/* no implementation fits. leave HAK_SUPPORT_SPL undefined so the caller
* can choose something else - hak's own targets that land here, __DOS__
* and EMSCRIPTEN, cannot run a second thread and need no lock at all. */
# undef HAK_SUPPORT_SPL
# if defined(HAK_SPL_UNSUPPORTED_ERROR)
# error UNSUPPORTED
# endif
#endif
#endif
+11
View File
@@ -23,6 +23,7 @@
*/
#include "hak-prv.h"
#include <hak-hnd.h>
hak_t* hak_open (hak_mmgr_t* mmgr, hak_oow_t xtnsize, const hak_vmprim_t* vmprim, hak_errinf_t* errinf)
{
@@ -118,6 +119,7 @@ int hak_init (hak_t* hak, hak_mmgr_t* mmgr, const hak_vmprim_t* vmprim)
{
int static_mods_inited = 0;
int modtab_inited = 0;
int hndtab_inited = 0;
int n;
if (!vmprim->syserrstrb && !vmprim->syserrstru)
@@ -175,6 +177,10 @@ int hak_init (hak_t* hak, hak_mmgr_t* mmgr, const hak_vmprim_t* vmprim)
modtab_inited = 1;
hak_rbt_setstyle(&hak->modtab, hak_get_rbt_style(HAK_RBT_STYLE_INLINE_COPIERS));
n = hak_inithndtab(hak);
if (HAK_UNLIKELY(n <= -1)) goto oops;
hndtab_inited = 1;
fill_bigint_tables(hak);
hak->tagged_brands[HAK_OOP_TAG_SMOOI] = HAK_BRAND_SMOOI;
@@ -199,6 +205,7 @@ int hak_init (hak_t* hak, hak_mmgr_t* mmgr, const hak_vmprim_t* vmprim)
return 0;
oops:
if (hndtab_inited) hak_finihndtab(hak);
if (modtab_inited) hak_rbt_fini(&hak->modtab);
if (static_mods_inited) hak_htb_fini(&hak->static_mods);
if (hak->gci.stack.ptr)
@@ -234,6 +241,10 @@ void hak_fini (hak_t* hak)
hak_rbt_fini(&hak->modtab);
hak_htb_fini(&hak->static_mods);
/* after the modules, so that a module's unload can close its own handles
* first; whatever hak code leaked is closed here. */
hak_finihndtab(hak);
if (hak->log.len > 0)
{
/* flush pending log messages just in case. */
+68 -10
View File
@@ -119,6 +119,9 @@ enum hak_errnum_t
HAK_EUNDEFVAR /**< runtime error - undefined variable access */
};
typedef enum hak_errnum_t hak_errnum_t;
/* defined in hak-hnd.h */
typedef struct hak_hndtab_t hak_hndtab_t;
/**/
enum hak_synerrnum_t
@@ -1261,6 +1264,22 @@ typedef int (*hak_vmprim_getsig_t) (
hak_uint8_t* sig
);
/**
* The hak_vmprim_catchsig_t type defines how an operating system signal is
* routed into hak. With \a enable non-zero the signal is caught and its number
* posted to the signal descriptor returned by \a vm_getsigfd, so that hak code
* can wait on that descriptor and read the number with \a vm_getsig. With
* \a enable zero the signal is released back to its previous disposition.
*
* Signals that cannot be caught, that indicate a crash, or that hak uses for
* process switching are refused.
*/
typedef int (*hak_vmprim_catchsig_t) (
hak_t* hak,
int signo,
int enable
);
typedef int (*hak_vmprim_setsig_t) (
hak_t* hak,
hak_uint8_t sig
@@ -1301,6 +1320,7 @@ struct hak_vmprim_t
hak_vmprim_getsigfd_t vm_getsigfd;
hak_vmprim_getsig_t vm_getsig;
hak_vmprim_setsig_t vm_setsig;
hak_vmprim_catchsig_t vm_catchsig; /* optional. may be HAK_NULL */
};
typedef struct hak_vmprim_t hak_vmprim_t;
@@ -1868,6 +1888,10 @@ struct hak_t
hak_oow_t sem_io_map_capa;
/* ============================================================================= */
/* the system handle table. see hak-hnd.h. opaque here so that hak.h need
* not pull in the handle definitions. */
hak_hndtab_t* hndtab;
hak_oop_t* proc_map;
hak_oow_t proc_map_capa;
hak_oow_t proc_map_used;
@@ -1891,10 +1915,14 @@ struct hak_t
hak_oob_t* active_code;
hak_ooi_t sp;
hak_ooi_t ip;
int no_proc_switch; /* process switching disabled */
int proc_switched; /* TODO: this is temporary. implement something else to skip immediate context switching */
int switch_proc;
int abort_req;
hak_uint8_t abort_req;
hak_uint8_t no_proc_switch; /* process switching disabled */
hak_uint8_t proc_switched; /* TODO: this is temporary. implement something else to skip immediate context switching */
hak_uint8_t rcv_tick; /* whether to receive tick or not */
hak_uint32_t tick; /* instance specific tick */
hak_uint32_t last_tick; /* last instance specific tick this instance acted on */
hak_uint32_t last_gtick; /* last global tick this instance acted on */
hak_ntime_t exec_start_time;
hak_ntime_t exec_end_time;
hak_oop_t last_retv;
@@ -2275,6 +2303,20 @@ HAK_EXPORT void hak_seterrumsg (
const hak_uch_t* errmsg
) HAK_NONNULL_1(1);
/**
* The hak_releaseiohandle() function deletes the resources associated with
* the given IO handle, the IO semaphores among them. It must be called before
* closing an IO handle, or the multiplexer keeps watching a handle number that
* may since have been recycled for something unrelated.
*
* hak_closehnd() in hak-hnd.h does this for you; call it directly only for a
* handle the handle table does not own.
*/
HAK_EXPORT void hak_releaseiohandle (
hak_t* hak,
hak_ooi_t io_handle
);
HAK_EXPORT void hak_seterrwithsyserr (
hak_t* hak,
int syserr_type,
@@ -2519,12 +2561,25 @@ HAK_EXPORT void hak_abort (
hak_t* hak
);
HAK_EXPORT void hak_rcvtick (
hak_t* hak,
int enabled
);
#if defined(HAK_HAVE_INLINE)
static HAK_INLINE void hak_switchprocess (hak_t* hak) { hak->switch_proc = 1; }
#else
# define hak_switchprocess(hak) ((hak)->switch_proc = 1)
#endif
/**
* The hak_raisetick() function raises a tick for the given instance only.
* hak_raise_gtick(), on the other hand, raises one for every instance in the
* process; it is safe to call from a signal handler, as it only increments a
* counter.
*
* A raised tick makes the instance switch to the next runnable process at its
* next opportunity. Reception must be enabled first: an instance ignores both
* kinds of tick until hak_rcvtick(hak, 1) has been called on it, and a global
* tick reaches only those instances that have enabled it.
*/
HAK_EXPORT void hak_raisetick (
hak_t* hak
);
HAK_EXPORT void hak_setbasesrloc (
hak_t* hak,
@@ -2728,7 +2783,6 @@ void* hak_getxtn (
hak_t* hak
);
#if defined(HAK_HAVE_INLINE)
static HAK_INLINE hak_code_t* hak_getcode (hak_t* hak) { return &hak->code; }
static HAK_INLINE hak_oob_t* hak_getbcptr (hak_t* hak) { return hak->code.bc.ptr; }
@@ -3505,6 +3559,10 @@ HAK_EXPORT void hak_uncatch_termreq (
void
);
HAK_EXPORT void hak_raise_gtick (
int unused
);
#if defined(__cplusplus)
}
#endif
+630
View File
@@ -0,0 +1,630 @@
/*
Copyright (c) 2016-2018 Chung, Hyung-Hwan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !defined(_GNU_SOURCE)
# define _GNU_SOURCE
#endif
#include <hak-hnd.h>
#include "hak-prv.h"
#if defined(_WIN32)
# include <windows.h>
#else
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
# include <fcntl.h>
# include <errno.h>
#endif
/* how much the id map and the descriptor reverse map grow by */
#define MAP_ALIGN 64
/* the id map is indexed by id, which is dense, so an int-sized ceiling is
* plenty and keeps every id comfortably inside HAK_SMOOI_MAX */
#define MAP_CAPA_MAX HAK_TYPE_MAX(int)
struct hak_hndtab_t
{
/* circular lists with real nodes as sentinels. a cast-a-2-pointer-struct
* sentinel would silently depend on prev/next sitting at the very front
* of hak_hnd_t; spending two nodes here removes that coupling. */
hak_hnd_t used;
hak_hnd_t free;
/* id -> node */
struct
{
hak_hnd_t** tab;
hak_ooi_t capa;
hak_ooi_t high; /* the next id to hand out */
} map;
/* descriptor -> id, so that a descriptor cannot be wrapped twice.
* two nodes over one descriptor would make hak_closehnd() on either of
* them drop the other's multiplexer registration. */
struct
{
hak_ooi_t* tab;
hak_ooi_t capa;
} fdmap;
};
/* ------------------------------------------------------------------------- */
static HAK_INLINE void chain_to_free (hak_hndtab_t* tab, hak_hnd_t* node)
{
node->next = &tab->free;
node->prev = tab->free.prev;
node->prev->next = node;
tab->free.prev = node;
}
static HAK_INLINE void chain_to_used (hak_hndtab_t* tab, hak_hnd_t* node)
{
node->next = &tab->used;
node->prev = tab->used.prev;
node->prev->next = node;
tab->used.prev = node;
}
static HAK_INLINE void unchain (hak_hnd_t* node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
/* ------------------------------------------------------------------------- */
int hak_inithndtab (hak_t* hak)
{
hak_hndtab_t* tab;
tab = (hak_hndtab_t*)hak_callocmem(hak, HAK_SIZEOF(*tab));
if (HAK_UNLIKELY(!tab)) return -1;
tab->used.prev = tab->used.next = &tab->used;
tab->free.prev = tab->free.next = &tab->free;
hak->hndtab = tab;
return 0;
}
void hak_finihndtab (hak_t* hak)
{
hak_hndtab_t* tab = hak->hndtab;
hak_hnd_t* node;
if (!tab) return;
/* close whatever hak code left open. an owner closes the handles it owns,
* so walk from the head each time rather than caching a next pointer. */
while ((node = tab->used.next) != &tab->used)
{
hak_closehnd(hak, node);
}
while ((node = tab->free.next) != &tab->free)
{
unchain(node);
hak_freemem(hak, node);
}
if (tab->map.tab) hak_freemem(hak, tab->map.tab);
if (tab->fdmap.tab) hak_freemem(hak, tab->fdmap.tab);
hak_freemem(hak, tab);
hak->hndtab = HAK_NULL;
}
/* ------------------------------------------------------------------------- */
static hak_hnd_t* alloc_node (hak_t* hak)
{
hak_hndtab_t* tab = hak->hndtab;
hak_hnd_t* node;
if (tab->free.next != &tab->free)
{
node = tab->free.next;
unchain(node);
}
else
{
hak_ooi_t id;
/* NOTE: the condition is >=, not <=. hawk's idmap-imp.h uses <=,
* which reallocates on every single node creation. */
if (tab->map.high >= tab->map.capa)
{
hak_ooi_t newcapa, inc;
hak_hnd_t** tmp;
inc = MAP_CAPA_MAX - tab->map.capa;
if (inc <= 0)
{
hak_seterrbfmt(hak, HAK_EFLOOD, "too many system handles");
return HAK_NULL;
}
if (inc > MAP_ALIGN) inc = MAP_ALIGN;
newcapa = tab->map.capa + inc;
tmp = (hak_hnd_t**)hak_reallocmem(hak, tab->map.tab, HAK_SIZEOF(*tmp) * newcapa);
if (HAK_UNLIKELY(!tmp)) return HAK_NULL;
HAK_MEMSET(&tmp[tab->map.capa], 0, HAK_SIZEOF(*tmp) * (newcapa - tab->map.capa));
tab->map.tab = tmp;
tab->map.capa = newcapa;
}
id = tab->map.high;
/* an id travels to hak code as a small integer */
if (!HAK_IN_SMOOI_RANGE(id))
{
hak_seterrbfmt(hak, HAK_EFLOOD, "system handle id %zd out of the permitted range", id);
return HAK_NULL;
}
node = (hak_hnd_t*)hak_callocmem(hak, HAK_SIZEOF(*node));
if (HAK_UNLIKELY(!node)) return HAK_NULL;
node->id = id;
tab->map.high++;
}
HAK_ASSERT(hak, tab->map.tab[node->id] == HAK_NULL);
tab->map.tab[node->id] = node;
chain_to_used(tab, node);
node->owner = -1;
return node;
}
static void free_node (hak_t* hak, hak_hnd_t* node)
{
hak_hndtab_t* tab = hak->hndtab;
hak_ooi_t id = node->id;
unchain(node);
tab->map.tab[id] = HAK_NULL;
node->type = 0;
node->flags = 0;
node->owner = -1;
node->u.ptr = HAK_NULL;
node->dtor = HAK_NULL;
if (tab->map.high == id + 1)
{
/* the highest id. give the memory back and lower the watermark. */
hak_freemem(hak, node);
tab->map.high--;
}
else
{
node->id = id; /* keep the id for reuse */
chain_to_free(tab, node);
}
}
/* ------------------------------------------------------------------------- */
static int remember_fd (hak_t* hak, int fd, hak_ooi_t id)
{
hak_hndtab_t* tab = hak->hndtab;
if (fd < 0) return 0;
if (fd >= tab->fdmap.capa)
{
hak_ooi_t newcapa, i;
hak_ooi_t* tmp;
newcapa = HAK_ALIGN_POW2((hak_ooi_t)fd + 1, MAP_ALIGN);
tmp = (hak_ooi_t*)hak_reallocmem(hak, tab->fdmap.tab, HAK_SIZEOF(*tmp) * newcapa);
if (HAK_UNLIKELY(!tmp)) return -1;
for (i = tab->fdmap.capa; i < newcapa; i++) tmp[i] = -1;
tab->fdmap.tab = tmp;
tab->fdmap.capa = newcapa;
}
tab->fdmap.tab[fd] = id;
return 0;
}
static HAK_INLINE void forget_fd (hak_t* hak, int fd)
{
hak_hndtab_t* tab = hak->hndtab;
if (fd >= 0 && fd < tab->fdmap.capa) tab->fdmap.tab[fd] = -1;
}
static HAK_INLINE hak_ooi_t fd_to_id (hak_t* hak, int fd)
{
hak_hndtab_t* tab = hak->hndtab;
if (fd < 0 || fd >= tab->fdmap.capa) return -1;
return tab->fdmap.tab[fd];
}
/* ------------------------------------------------------------------------- */
/**
* Work out what kind of descriptor \a fd is and whether the multiplexer will
* take it. Doing this once here, at wrap time, is what keeps every caller
* from discovering it later as an EPERM out of epoll_ctl - or, on a poll()
* build, from never discovering it at all because poll() reports a regular
* file as permanently ready.
*/
static int probe_fd (hak_t* hak, int fd, hak_hnd_type_t* type, int* muxable)
{
#if defined(_WIN32)
/* TODO: GetFileType() on the underlying HANDLE. until then a caller must
* state the type and nothing is muxable. */
*type = 0;
*muxable = 0;
return 0;
#else
struct stat st;
if (fstat(fd, &st) <= -1)
{
hak_seterrbfmtwithsyserr(hak, 0, errno, "unable to identify handle %d", fd);
return -1;
}
if (S_ISFIFO(st.st_mode)) { *type = HAK_HND_TYPE_PIPE; *muxable = 1; }
else if (S_ISSOCK(st.st_mode)) { *type = HAK_HND_TYPE_SCK; *muxable = 1; }
else if (S_ISCHR(st.st_mode)) { *type = HAK_HND_TYPE_CHR; *muxable = 1; }
else if ((st.st_mode & S_IFMT) == 0)
{
/* an anonymous inode. linux hands these out for pidfd, eventfd,
* timerfd and signalfd: no file type bits at all, yet all of them are
* pollable. without this arm they would fall through to FILE below and
* the multiplexer would refuse them. */
*type = HAK_HND_TYPE_EVT; *muxable = 1;
}
else { *type = HAK_HND_TYPE_FILE; *muxable = 0; }
return 0;
#endif
}
static int set_nonblock (hak_t* hak, int fd)
{
#if defined(_WIN32)
hak_seterrnum(hak, HAK_ENOIMPL);
return -1;
#elif defined(O_NONBLOCK)
int fl;
fl = fcntl(fd, F_GETFL, 0);
if (fl <= -1 || fcntl(fd, F_SETFL, fl | O_NONBLOCK) <= -1)
{
hak_seterrbfmtwithsyserr(hak, 0, errno, "unable to set handle %d non-blocking", fd);
return -1;
}
return 0;
#else
hak_seterrnum(hak, HAK_ENOIMPL);
return -1;
#endif
}
/* ------------------------------------------------------------------------- */
hak_hnd_t* hak_wrapfd (hak_t* hak, int fd, hak_hnd_type_t type_hint, int flags)
{
hak_hnd_t* node;
hak_hnd_type_t type;
int muxable = 0;
if (fd < 0)
{
hak_seterrbfmt(hak, HAK_EINVAL, "invalid handle %d", fd);
return HAK_NULL;
}
if (fd_to_id(hak, fd) >= 0)
{
/* refuse rather than hand out a second node. see fdmap above. */
hak_seterrbfmt(hak, HAK_EEXIST, "handle %d already wrapped as %zd", fd, fd_to_id(hak, fd));
return HAK_NULL;
}
if (probe_fd(hak, fd, &type, &muxable) <= -1) return HAK_NULL;
if (type_hint && !(type_hint & type))
{
hak_seterrbfmt(hak, HAK_EINVAL, "handle %d not of the required kind", fd);
return HAK_NULL;
}
if ((flags & HAK_HND_OPEN_NONBLOCK) && set_nonblock(hak, fd) <= -1) return HAK_NULL;
node = alloc_node(hak);
if (HAK_UNLIKELY(!node)) return HAK_NULL;
node->type = type;
node->flags = flags & (HAK_HND_FLAG_NONBLOCK | HAK_HND_FLAG_KEEPOPEN);
/* the probe decides, unless the caller asserts it knows better */
if (muxable || (flags & HAK_HND_FLAG_MUXABLE)) node->flags |= HAK_HND_FLAG_MUXABLE;
node->u.fd = fd;
if (remember_fd(hak, fd, node->id) <= -1)
{
free_node(hak, node);
return HAK_NULL;
}
return node;
}
hak_hnd_t* hak_wrapptr (hak_t* hak, void* ptr, hak_hnd_type_t type, int flags, hak_hnd_dtor_t dtor)
{
hak_hnd_t* node;
if (!ptr || !(type & (HAK_HND_TYPE_DIR | HAK_HND_TYPE_PROC)))
{
hak_seterrbfmt(hak, HAK_EINVAL, "invalid pointer-shaped handle");
return HAK_NULL;
}
node = alloc_node(hak);
if (HAK_UNLIKELY(!node)) return HAK_NULL;
node->type = type;
node->flags = flags & HAK_HND_FLAG_KEEPOPEN; /* never muxable */
node->u.ptr = ptr;
node->dtor = dtor;
return node;
}
hak_hnd_t* hak_wrapfd_once (hak_t* hak, int fd, hak_hnd_type_t type_hint, int flags)
{
hak_ooi_t id;
if (fd < 0)
{
hak_seterrbfmt(hak, HAK_EINVAL, "invalid handle %d", fd);
return HAK_NULL;
}
id = fd_to_id(hak, fd);
if (id >= 0) return hak->hndtab->map.tab[id];
return hak_wrapfd(hak, fd, type_hint, flags);
}
void hak_ownhnd (hak_t* hak, hak_hnd_t* hnd, hak_hnd_t* owner)
{
HAK_ASSERT(hak, hnd != owner);
hnd->owner = owner? owner->id: -1;
}
/* ------------------------------------------------------------------------- */
hak_hnd_t* hak_gethnd (hak_t* hak, hak_ooi_t id, int acceptable_types)
{
hak_hndtab_t* tab = hak->hndtab;
hak_hnd_t* node;
if (id < 0 || id >= tab->map.high || !(node = tab->map.tab[id]))
{
hak_seterrbfmt(hak, HAK_EBADHND, "invalid system handle %zd", id);
return HAK_NULL;
}
if (!(node->type & acceptable_types))
{
hak_seterrbfmt(hak, HAK_EBADHND, "system handle %zd not of an acceptable kind", id);
return HAK_NULL;
}
return node;
}
hak_hnd_t* hak_gethndwithoop (hak_t* hak, hak_oop_t id, int acceptable_types)
{
if (!HAK_OOP_IS_SMOOI(id))
{
hak_seterrbfmt(hak, HAK_EBADHND, "system handle not a small integer - %O", id);
return HAK_NULL;
}
return hak_gethnd(hak, HAK_OOP_TO_SMOOI(id), acceptable_types);
}
/* ------------------------------------------------------------------------- */
int hak_closehnd (hak_t* hak, hak_hnd_t* hnd)
{
hak_hndtab_t* tab = hak->hndtab;
hak_hnd_t* p;
hak_hnd_t* next;
hak_ooi_t id = hnd->id;
int n = 0;
/* 1. handles this one owns go first. a child process must not outlive the
* node that represents it. */
for (p = tab->used.next; p != &tab->used; p = next)
{
next = p->next;
if (p != hnd && p->owner == id) hak_closehnd(hak, p);
}
/* 2. drop any multiplexer registration while the handle is still valid.
* doing this after the close would leave the VM watching a descriptor
* number that may already have been recycled. */
if ((hnd->flags & HAK_HND_FLAG_IN_MUX) && (hnd->type & HAK_HND_TYPE_ALL_FD))
{
hak_releaseiohandle(hak, hnd->u.fd);
hnd->flags &= ~HAK_HND_FLAG_IN_MUX;
}
/* 3. release the resource itself */
if (!(hnd->flags & HAK_HND_FLAG_KEEPOPEN))
{
if (hnd->dtor) /* destructor available */
{
/* the subsystem that created the resource knows how to dispose of
* it. this is also the path hak_finihndtab() takes, which is why a
* pointer-shaped node needs a destructor to avoid leaking both the
* resource and, for a child process, the process itself. */
hnd->dtor(hak, hnd);
}
else if (hnd->type & HAK_HND_TYPE_ALL_FD)
{
/* all file-descriptor based handles */
#if defined(_WIN32)
if (!CloseHandle((HANDLE)(hak_uintptr_t)hnd->u.fd)) n = -1;
#else
if (close(hnd->u.fd) <= -1)
{
hak_seterrbfmtwithsyserr(hak, 0, errno, "unable to close handle %d", hnd->u.fd);
n = -1;
}
#endif
}
/* a pointer-shaped node with no destructor releases nothing, which is
* only correct when the pointer is owned elsewhere. */
}
if (hnd->type & HAK_HND_TYPE_ALL_FD)
{
/* all file-descriptor based handles */
forget_fd(hak, hnd->u.fd);
}
free_node(hak, hnd);
return n;
}
/* ------------------------------------------------------------------------- */
int hak_bindhnd (hak_t* hak, hak_hnd_t* hnd, hak_oop_semaphore_t sem, hak_semaphore_io_type_t io_type)
{
if (!(hnd->flags & HAK_HND_FLAG_MUXABLE))
{
/* a regular file is the common case here. it is never reported as
* not-ready, so waiting on one is meaningless as well as unsupported. */
hak_seterrbfmt(hak, HAK_EINVAL, "system handle %zd cannot be multiplexed", hnd->id);
return -1;
}
if (hak_add_sem_to_sem_io_tuple(hak, sem, hnd->u.fd, io_type) <= -1) return -1;
hnd->flags |= HAK_HND_FLAG_IN_MUX;
return 0;
}
/* ------------------------------------------------------------------------- */
hak_ooi_t hak_readhnd (hak_t* hak, hak_hnd_t* hnd, void* buf, hak_oow_t len)
{
#if !defined(_WIN32)
hak_ooi_t n;
#endif
if (!(hnd->type & HAK_HND_TYPE_ALL_STREAM))
{
hak_seterrbfmt(hak, HAK_EBADHND, "system handle %zd not readable", hnd->id);
return HAK_HND_IO_ERROR;
}
if (len > (hak_oow_t)HAK_TYPE_MAX(hak_ooi_t)) len = (hak_oow_t)HAK_TYPE_MAX(hak_ooi_t);
#if defined(_WIN32)
{
DWORD count;
if (len > (hak_oow_t)HAK_TYPE_MAX(DWORD)) len = (hak_oow_t)HAK_TYPE_MAX(DWORD);
if (!ReadFile((HANDLE)(hak_uintptr_t)hnd->u.fd, buf, (DWORD)len, &count, HAK_NULL))
{
DWORD e = GetLastError();
if (e == ERROR_BROKEN_PIPE) return 0; /* end of file */
hak_seterrwithsyserr(hak, 1, e);
return HAK_HND_IO_ERROR;
}
return (hak_ooi_t)count;
}
#else
n = read(hnd->u.fd, buf, len);
if (n <= -1)
{
if (errno == EINTR) return HAK_HND_IO_WOULDBLOCK; /* let hak code retry */
#if defined(EWOULDBLOCK) && defined(EAGAIN) && (EWOULDBLOCK != EAGAIN)
if (errno == EAGAIN || errno == EWOULDBLOCK) return HAK_HND_IO_WOULDBLOCK;
#elif defined(EAGAIN)
if (errno == EAGAIN) return HAK_HND_IO_WOULDBLOCK;
#elif defined(EWOULDBLOCK)
if (errno == EWOULDBLOCK) return HAK_HND_IO_WOULDBLOCK;
#endif
hak_seterrwithsyserr(hak, 0, errno);
return HAK_HND_IO_ERROR;
}
return n;
#endif
}
hak_ooi_t hak_writehnd (hak_t* hak, hak_hnd_t* hnd, const void* buf, hak_oow_t len)
{
#if !defined(_WIN32)
hak_ooi_t n;
#endif
if (!(hnd->type & HAK_HND_TYPE_ALL_STREAM))
{
hak_seterrbfmt(hak, HAK_EBADHND, "system handle %zd not writable", hnd->id);
return HAK_HND_IO_ERROR;
}
if (len > (hak_oow_t)HAK_TYPE_MAX(hak_ooi_t)) len = (hak_oow_t)HAK_TYPE_MAX(hak_ooi_t);
#if defined(_WIN32)
{
DWORD count;
if (len > (hak_oow_t)HAK_TYPE_MAX(DWORD)) len = (hak_oow_t)HAK_TYPE_MAX(DWORD);
if (!WriteFile((HANDLE)(hak_uintptr_t)hnd->u.fd, buf, (DWORD)len, &count, HAK_NULL))
{
hak_seterrwithsyserr(hak, 1, GetLastError());
return HAK_HND_IO_ERROR;
}
return (hak_ooi_t)count;
}
#else
n = write(hnd->u.fd, buf, len);
if (n <= -1)
{
if (errno == EINTR) return HAK_HND_IO_WOULDBLOCK;
#if defined(EWOULDBLOCK) && defined(EAGAIN) && (EWOULDBLOCK != EAGAIN)
if (errno == EAGAIN || errno == EWOULDBLOCK) return HAK_HND_IO_WOULDBLOCK;
#elif defined(EAGAIN)
if (errno == EAGAIN) return HAK_HND_IO_WOULDBLOCK;
#elif defined(EWOULDBLOCK)
if (errno == EWOULDBLOCK) return HAK_HND_IO_WOULDBLOCK;
#endif
hak_seterrwithsyserr(hak, 0, errno);
return HAK_HND_IO_ERROR;
}
return n;
#endif
}
+36
View File
@@ -1345,6 +1345,42 @@ void hak_pio_fini (hak_pio_t* pio)
hak_pio_wait(pio);
}
void hak_pio_free (hak_pio_t* pio)
{
hak_t* hak = pio->hak;
hak_errnum_t errnum;
/* a teardown path must not clobber whatever error brought us here */
errnum = hak_geterrnum(hak);
hak_pio_end(pio, HAK_PIO_ERR);
hak_pio_end(pio, HAK_PIO_OUT);
hak_pio_end(pio, HAK_PIO_IN);
if (pio->child != HAK_PIO_PID_NIL)
{
int n;
/* look without blocking first - a child that has already exited
* needs no killing */
pio->flags |= HAK_PIO_WAITNOBLOCK;
pio->flags &= ~HAK_PIO_WAITNORETRY;
n = hak_pio_wait(pio);
if (n == 255 + 1)
{
/* still running. SIGKILL cannot be caught or ignored, so the
* blocking wait below is bounded however the child behaves. */
hak_pio_kill(pio);
pio->flags &= ~HAK_PIO_WAITNOBLOCK;
hak_pio_wait(pio);
}
}
hak_freemem(hak, pio);
hak_seterrnum(hak, errnum);
}
hak_pio_hnd_t hak_pio_gethnd (const hak_pio_t* pio, hak_pio_hid_t hid)
{
return pio->handle[hid];
+60 -2
View File
@@ -23,6 +23,7 @@
*/
#include "hak-prv.h"
#include <hak-hnd.h>
struct pf_t
{
@@ -1258,8 +1259,19 @@ static hak_pfrc_t pf_object_new (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
static hak_pfrc_t pf_system_get_sigfd (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_ooi_t fd;
hak_hnd_t* hnd;
fd = hak->vmprim.vm_getsigfd(hak);
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(fd));
/* hand back a system handle id rather than the descriptor itself, so that
* the result can be given to sem-signal-on-input - which resolves handle
* ids, not descriptors. wrapped HAK_HND_OPEN_KEEPOPEN because the VM owns
* this descriptor and manages its blocking mode; the table must never
* close it. wrapfd_once() keeps the id stable across calls. */
hnd = hak_wrapfd_once(hak, (int)fd, 0, HAK_HND_OPEN_KEEPOPEN);
if (HAK_UNLIKELY(!hnd)) return HAK_PF_FAILURE;
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(hnd->id));
return HAK_PF_SUCCESS;
}
@@ -1277,6 +1289,50 @@ static hak_pfrc_t pf_system_get_sig (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs
return HAK_PF_SUCCESS;
}
/* (system-catch-sig signo) - route an operating system signal into the
* signal descriptor, where hak code can wait for
* it with sem-signal-on-input
* (system-uncatch-sig signo) - release it again
*
* Note the difference from system-set-sig, which does not touch the operating
* system at all: that one injects a number into the descriptor directly, as a
* way for hak code to post a synthetic signal to itself.
*/
static hak_pfrc_t __system_catch_sig (hak_t* hak, hak_ooi_t nargs, int enable)
{
hak_oop_t tmp;
hak_ooi_t signo;
tmp = HAK_STACK_GETARG(hak, nargs, 0);
if (!HAK_OOP_IS_SMOOI(tmp))
{
hak_seterrbfmt(hak, HAK_EINVAL, "signal number not a small integer - %O", tmp);
return HAK_PF_FAILURE;
}
signo = HAK_OOP_TO_SMOOI(tmp);
if (!hak->vmprim.vm_catchsig)
{
hak_seterrbfmt(hak, HAK_ENOIMPL, "signal routing not supported");
return HAK_PF_FAILURE;
}
if (hak->vmprim.vm_catchsig(hak, (int)signo, enable) <= -1) return HAK_PF_FAILURE;
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(signo));
return HAK_PF_SUCCESS;
}
static hak_pfrc_t pf_system_catch_sig (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
return __system_catch_sig(hak, nargs, 1);
}
static hak_pfrc_t pf_system_uncatch_sig (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
return __system_catch_sig(hak, nargs, 0);
}
static hak_pfrc_t pf_system_set_sig (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_t tmp;
@@ -1310,9 +1366,11 @@ static pf_t builtin_prims[] =
{ 1, HAK_TYPE_MAX(hak_oow_t), pf_scanf, 5, { 's','c','a','n','f' } },
{ 1, HAK_TYPE_MAX(hak_oow_t), pf_sprintf, 7, { 's','p','r','i','n','t','f' } },
{ 0, 0, pf_system_get_sigfd,16, { 's','y','s','t','e','m','-','g','e','t','-','s','i','g','f','d' } },
{ 0, 0, pf_system_get_sigfd, 16, { 's','y','s','t','e','m','-','g','e','t','-','s','i','g','f','d' } },
{ 0, 0, pf_system_get_sig, 14, { 's','y','s','t','e','m','-','g','e','t','-','s','i','g' } },
{ 1, 1, pf_system_set_sig, 14, { 's','y','s','t','e','m','-','s','e','t','-','s','i','g' } },
{ 1, 1, pf_system_catch_sig, 16, { 's','y','s','t','e','m','-','c','a','t','c','h','-','s','i','g' } },
{ 1, 1, pf_system_uncatch_sig, 18, { 's','y','s','t','e','m','-','u','n','c','a','t','c','h','-','s','i','g' } },
{ 0, 0, pf_gc, 2, { 'g','c' } },
+503 -248
View File
File diff suppressed because it is too large Load Diff
+86
View File
@@ -313,6 +313,88 @@ static hak_pfrc_t pf_core_basic_size (hak_t* hak, hak_mod_t* mod, hak_ooi_t narg
return HAK_PF_SUCCESS;
}
/* ------------------------------------------------------------------------ *
* CONS CELLS
*
* A data list written #(1 2 3) is a chain of Cons cells, and a Cons keeps its
* head and tail in named instance variables rather than indexed slots. That
* puts it outside the reach of basicAt and primAt, both of which require a
* flexi (indexed) receiver, so a list was previously opaque to hak code. These
* three give the class library what it needs to walk one and to build one.
* ------------------------------------------------------------------------ */
static hak_pfrc_t pf_core_car (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_t obj;
obj = HAK_STACK_GETARG(hak, nargs, 0);
if (!HAK_IS_CONS(hak, obj))
{
hak_seterrbfmt(hak, HAK_EINVAL, "receiver not a cons - %O", obj);
return HAK_PF_FAILURE;
}
HAK_STACK_SETRET(hak, nargs, HAK_CONS_CAR(obj));
return HAK_PF_SUCCESS;
}
static hak_pfrc_t pf_core_cdr (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_t obj;
obj = HAK_STACK_GETARG(hak, nargs, 0);
if (!HAK_IS_CONS(hak, obj))
{
hak_seterrbfmt(hak, HAK_EINVAL, "receiver not a cons - %O", obj);
return HAK_PF_FAILURE;
}
HAK_STACK_SETRET(hak, nargs, HAK_CONS_CDR(obj));
return HAK_PF_SUCCESS;
}
/* (core.cons head tail) -> a new cons cell */
static hak_pfrc_t pf_core_cons (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_t car, cdr, c;
car = HAK_STACK_GETARG(hak, nargs, 0);
cdr = HAK_STACK_GETARG(hak, nargs, 1);
/* keep both rooted across the allocation. the argument stack roots them
* too and the collector is mark-sweep, so this is belt-and-braces; it is
* what would keep these local copies valid if the compacting collector in
* gc.c were ever turned on. */
hak_pushvolat(hak, &car);
hak_pushvolat(hak, &cdr);
c = hak_makecons(hak, car, cdr);
hak_popvolats(hak, 2);
if (HAK_UNLIKELY(!c)) return HAK_PF_FAILURE;
HAK_STACK_SETRET(hak, nargs, c);
return HAK_PF_SUCCESS;
}
/* (core.classOf obj) -> the class of obj
*
* className answers a string, which is no use for building something of the
* same kind as the receiver. This answers the class itself, so that a method
* can do what Smalltalk spells "self species new:" - collect and select on a
* String returning a String rather than an Array of characters.
*
* HAK_CLASSOF() copes with a value encoded into the pointer - a small integer,
* a character, an error or a small pointer - so this is total: every object has
* a class.
*/
static hak_pfrc_t pf_core_class_of (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_t obj;
obj = HAK_STACK_GETARG(hak, nargs, 0);
HAK_STACK_SETRET(hak, nargs, (hak_oop_t)HAK_CLASSOF(hak, obj));
return HAK_PF_SUCCESS;
}
static hak_pfrc_t pf_core_class_name (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_t obj;
@@ -515,9 +597,13 @@ static hak_pfinfo_t pfinfos[] =
{ "bit-shift", { HAK_PFBASE_FUNC, hak_pf_integer_bshift, 2, 2 } },
{ "bit-xor", { HAK_PFBASE_FUNC, hak_pf_integer_bxor, 2, 2 } },
{ "car", { HAK_PFBASE_FUNC, pf_core_car, 1, 1 } },
{ "cdr", { HAK_PFBASE_FUNC, pf_core_cdr, 1, 1 } },
{ "charToSmooi", { HAK_PFBASE_FUNC, pf_core_char_to_smooi, 1, 1 } },
{ "className", { HAK_PFBASE_FUNC, pf_core_class_name, 1, 1 } },
{ "classOf", { HAK_PFBASE_FUNC, pf_core_class_of, 1, 1 } },
{ "classRespondsTo", { HAK_PFBASE_FUNC, pf_core_class_responds_to, 2, 2 } },
{ "cons", { HAK_PFBASE_FUNC, pf_core_cons, 2, 2 } },
{ "eqk?", { HAK_PFBASE_FUNC, hak_pf_eqk, 2, 2 } },
{ "eql?", { HAK_PFBASE_FUNC, hak_pf_eql, 2, 2 } },
+260 -14
View File
@@ -82,38 +82,284 @@ static hak_pfrc_t pf_dic_put (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
}
static int walker (hak_t* hak, hak_oop_dic_t dic, hak_oop_cons_t pair, void* ctx)
/* ------------------------------------------------------------------------ *
* INSPECTION
* ------------------------------------------------------------------------ */
/* resolve and validate the dictionary argument shared by everything here */
static hak_oop_dic_t arg_to_dic (hak_t* hak, hak_ooi_t nargs, hak_ooi_t idx)
{
HAK_DEBUG2(hak, "walker ===> %O =====> %O\n", HAK_CONS_CAR(pair), HAK_CONS_CDR(pair));
hak_oop_t d = HAK_STACK_GETARG(hak, nargs, idx);
if (!HAK_IS_DIC(hak, d))
{
hak_seterrbfmt(hak, HAK_EINVAL, "parameter not a dictionary - %O", d);
return HAK_NULL;
}
return (hak_oop_dic_t)d;
}
/* (dic.make [bucket-size]) -> a new dictionary
* The literal #{} covers the common case; this is for choosing an initial
* bucket size when the eventual population is known. */
static hak_pfrc_t pf_dic_make (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oow_t inisize = 16;
hak_oop_t d;
if (nargs >= 1)
{
hak_oop_t t = HAK_STACK_GETARG(hak, nargs, 0);
hak_ooi_t v;
if (hak_inttoooi(hak, t, &v) == 0) return HAK_PF_FAILURE;
if (v <= 0)
{
hak_seterrbfmt(hak, HAK_EINVAL, "bucket size not positive - %O", t);
return HAK_PF_FAILURE;
}
inisize = (hak_oow_t)v;
}
d = hak_makedic(hak, inisize);
if (HAK_UNLIKELY(!d)) return HAK_PF_FAILURE;
HAK_STACK_SETRET(hak, nargs, d);
return HAK_PF_SUCCESS;
}
/* (dic.size d) -> how many pairs it holds */
static hak_pfrc_t pf_dic_size (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_dic_t dic = arg_to_dic(hak, nargs, 0);
if (HAK_UNLIKELY(!dic)) return HAK_PF_FAILURE;
HAK_STACK_SETRET(hak, nargs, dic->tally);
return HAK_PF_SUCCESS;
}
/* (dic.has? d k) -> true or false
* dic.get answers with an error object for a missing key, which is awkward to
* test when the stored value could itself be an error. */
static hak_pfrc_t pf_dic_has (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_dic_t dic = arg_to_dic(hak, nargs, 0);
hak_oop_t key;
if (HAK_UNLIKELY(!dic)) return HAK_PF_FAILURE;
key = HAK_STACK_GETARG(hak, nargs, 1);
HAK_STACK_SETRET(hak, nargs, hak_getatdic(hak, dic, key)? hak->_true: hak->_false);
return HAK_PF_SUCCESS;
}
/* (dic.delete d k) -> true if a pair went away, false if there was none */
static hak_pfrc_t pf_dic_delete (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_dic_t dic = arg_to_dic(hak, nargs, 0);
hak_oop_t key;
if (HAK_UNLIKELY(!dic)) return HAK_PF_FAILURE;
key = HAK_STACK_GETARG(hak, nargs, 1);
HAK_STACK_SETRET(hak, nargs, (hak_zapatdic(hak, dic, key) >= 0)? hak->_true: hak->_false);
return HAK_PF_SUCCESS;
}
/* ------------------------------------------------------------------------ *
* ITERATION
*
* A dictionary is iterated by asking for its keys or values as an array and
* walking that from hak code. There is deliberately no callback form: calling
* a hak block from inside a primitive would need the virtual machine to be
* re-entrant, which it is not, and collecting first also keeps the caller
* clear of the question of what happens when the dictionary is modified
* during a walk.
* ------------------------------------------------------------------------ */
struct collect_t
{
hak_oop_t arr;
hak_oow_t idx;
hak_oow_t capa;
int want; /* WANT_KEY, WANT_VALUE or WANT_PAIR */
};
#define WANT_KEY 0
#define WANT_VALUE 1
#define WANT_PAIR 2
typedef struct collect_t collect_t;
static int collect_walker (hak_t* hak, hak_oop_dic_t dic, hak_oop_cons_t pair, void* ctx)
{
collect_t* c = (collect_t*)ctx;
/* tally and the number of pairs actually walked should agree, but never
* write past the array we sized from it */
if (c->idx >= c->capa) return -1;
HAK_OBJ_SET_OOP_VAL(c->arr, c->idx,
(c->want == WANT_PAIR)? (hak_oop_t)pair:
(c->want == WANT_VALUE)? HAK_CONS_CDR(pair): HAK_CONS_CAR(pair));
c->idx++;
return 0;
}
static hak_pfrc_t pf_dic_walk (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
static hak_pfrc_t collect (hak_t* hak, hak_ooi_t nargs, int want)
{
/* TODO: write a proper function
* (dic.apply #{ ... } callable-or-lambda)
*/
hak_oop_t arg;
hak_oop_t dic;
hak_oop_t arr;
hak_ooi_t n;
collect_t c;
arg = HAK_STACK_GETARG(hak, nargs, 0);
if (!HAK_IS_DIC(hak,arg))
if (!arg_to_dic(hak, nargs, 0)) return HAK_PF_FAILURE;
dic = HAK_STACK_GETARG(hak, nargs, 0);
n = HAK_OOP_TO_SMOOI(((hak_oop_dic_t)dic)->tally);
/* Keep the dictionary rooted across the allocation. hak's collector is
* mark-sweep and does not relocate, and the argument stack roots the
* dictionary anyway, so this is belt-and-braces today; it is what would
* keep the code correct if the compacting collector in gc.c were ever
* turned on. */
hak_pushvolat(hak, &dic);
arr = hak_makearray(hak, (hak_oow_t)n);
hak_popvolat(hak);
if (HAK_UNLIKELY(!arr)) return HAK_PF_FAILURE;
/* nothing below allocates, so raw slot writes are safe from here */
c.arr = arr;
c.idx = 0;
c.capa = (hak_oow_t)n;
c.want = want;
hak_walkdic(hak, (hak_oop_dic_t)dic, collect_walker, &c);
HAK_STACK_SETRET(hak, nargs, arr);
return HAK_PF_SUCCESS;
}
/* (dic.keys d) -> an array of the keys */
static hak_pfrc_t pf_dic_keys (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
return collect(hak, nargs, WANT_KEY);
}
/* (dic.values d) -> an array of the values, in the same order as dic.keys */
static hak_pfrc_t pf_dic_values (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
return collect(hak, nargs, WANT_VALUE);
}
/* (dic.pairs d) -> an array of the associations themselves
*
* One array instead of the two that dic.keys plus dic.values costs; read each
* association with core.car and core.cdr. */
static hak_pfrc_t pf_dic_pairs (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
return collect(hak, nargs, WANT_PAIR);
}
/* ------------------------------------------------------------------------ *
* ALLOCATION-FREE TRAVERSAL
*
* dic.keys, dic.values and dic.pairs each build an array. These two allocate
* nothing at all: walk the slots from 0 to dic.bucketSize and read whichever
* are occupied, taking key and value from the association with core.car and
* core.cdr.
*
* The cost is that the traversal is live rather than a snapshot - inserting
* during one may grow the bucket and rearrange everything, so a pair can be
* seen twice or missed. Removing is safe, since a removal never grows the
* bucket. Use dic.keys where a snapshot matters; use these where the traversal
* is hot and nothing is being inserted.
*
* These do expose that a bucket exists and has gaps. The bucket doubles once it
* is nearly full, so for a dictionary of any size the load runs between about
* 57% and 96% - a quarter of the slots walked are empty on average. A small
* dictionary still inside its initial bucket of 26 is far sparser than that, so
* the slot walk is relatively worst exactly where it matters least.
* ------------------------------------------------------------------------ */
/* (dic.bucketSize d) -> how many slots to walk */
static hak_pfrc_t pf_dic_bucket_size (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_dic_t dic = arg_to_dic(hak, nargs, 0);
if (HAK_UNLIKELY(!dic)) return HAK_PF_FAILURE;
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP((hak_ooi_t)HAK_OBJ_GET_SIZE(dic->bucket)));
return HAK_PF_SUCCESS;
}
/* (dic.pairAt d index) -> the association in that slot, or nil if it is empty */
static hak_pfrc_t pf_dic_pair_at (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_dic_t dic = arg_to_dic(hak, nargs, 0);
hak_oop_t t;
hak_ooi_t i;
if (HAK_UNLIKELY(!dic)) return HAK_PF_FAILURE;
t = HAK_STACK_GETARG(hak, nargs, 1);
if (hak_inttoooi(hak, t, &i) == 0) return HAK_PF_FAILURE;
if (i < 0 || i >= (hak_ooi_t)HAK_OBJ_GET_SIZE(dic->bucket))
{
hak_seterrbfmt(hak, HAK_EINVAL, "parameter not a dictionary - %O", arg);
hak_seterrbfmt(hak, HAK_ERANGE, "slot %zd out of range - the bucket holds %zu", i, HAK_OBJ_GET_SIZE(dic->bucket));
return HAK_PF_FAILURE;
}
hak_walkdic(hak, (hak_oop_dic_t)arg, walker, HAK_NULL);
HAK_STACK_SETRET(hak, nargs, hak->_true);
t = dic->bucket->slot[i];
HAK_STACK_SETRET(hak, nargs, HAK_IS_CONS(hak, t)? t: hak->_nil);
return HAK_PF_SUCCESS;
}
/* (dic.clear d) -> the number of pairs removed */
static hak_pfrc_t pf_dic_clear (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_t dic;
hak_oop_t keys;
hak_ooi_t n, i, gone = 0;
collect_t c;
if (!arg_to_dic(hak, nargs, 0)) return HAK_PF_FAILURE;
dic = HAK_STACK_GETARG(hak, nargs, 0);
n = HAK_OOP_TO_SMOOI(((hak_oop_dic_t)dic)->tally);
/* collect the keys before removing any: zapping during a walk would
* disturb the buckets the walk is traversing. see collect() on the guard. */
hak_pushvolat(hak, &dic);
keys = hak_makearray(hak, (hak_oow_t)n);
hak_popvolat(hak);
if (HAK_UNLIKELY(!keys)) return HAK_PF_FAILURE;
c.arr = keys;
c.idx = 0;
c.capa = (hak_oow_t)n;
c.want = WANT_KEY;
hak_walkdic(hak, (hak_oop_dic_t)dic, collect_walker, &c);
for (i = 0; i < n; i++)
{
if (hak_zapatdic(hak, (hak_oop_dic_t)dic, HAK_OBJ_GET_OOP_VAL(keys, i)) >= 0) gone++;
}
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(gone));
return HAK_PF_SUCCESS;
}
/* sorted: hak_findpfbase() binary-searches this table */
static hak_pfinfo_t pfinfos[] =
{
{ "bucketSize", { HAK_PFBASE_FUNC, pf_dic_bucket_size, 1, 1 } },
{ "clear", { HAK_PFBASE_FUNC, pf_dic_clear, 1, 1 } },
{ "delete", { HAK_PFBASE_FUNC, pf_dic_delete, 2, 2 } },
{ "get", { HAK_PFBASE_FUNC, pf_dic_get, 2, 2 } },
/* { "make", { HAK_PFBASE_FUNC, pf_dic_make, 1, 1 } }, */
{ "has?", { HAK_PFBASE_FUNC, pf_dic_has, 2, 2 } },
{ "keys", { HAK_PFBASE_FUNC, pf_dic_keys, 1, 1 } },
{ "make", { HAK_PFBASE_FUNC, pf_dic_make, 0, 1 } },
{ "pairAt", { HAK_PFBASE_FUNC, pf_dic_pair_at, 2, 2 } },
{ "pairs", { HAK_PFBASE_FUNC, pf_dic_pairs, 1, 1 } },
{ "put", { HAK_PFBASE_FUNC, pf_dic_put, 3, 3 } },
{ "walk", { HAK_PFBASE_FUNC, pf_dic_walk, 2, 2 } },
{ "size", { HAK_PFBASE_FUNC, pf_dic_size, 1, 1 } },
{ "values", { HAK_PFBASE_FUNC, pf_dic_values, 1, 1 } }
};
/* ------------------------------------------------------------------------ */
+573 -36
View File
@@ -25,10 +25,26 @@
*/
/* _GNU_SOURCE must precede any libc header - pipe2() is behind it */
#if !defined(_WIN32) && !defined(_GNU_SOURCE)
# define _GNU_SOURCE
#endif
#include "_sys.h"
#include <hak-hnd.h>
#include <hak-pio.h>
#include <hak-str.h>
#include <stdlib.h>
#if !defined(_WIN32)
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
# include <fcntl.h>
# include <errno.h>
# include <sys/syscall.h>
#endif
#if defined(HAVE_SYS_TIME_H)
# include <sys/time.h>
#endif
@@ -146,70 +162,591 @@ static hak_pfrc_t pf_sys_random (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
return HAK_PF_SUCCESS;
}
#include <stdio.h> // TODO: remove this and replace it by own impl
static hak_pfrc_t pf_sys_popen (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_t t;
hak_bch_t* cmd;
FILE* pp;
t = HAK_STACK_GETARG(hak, nargs, 0);
// TODO: support byte array?
/*if (!HAK_IS_STRING(hak, t)) goto oops;*/
if (!HAK_OBJ_IS_CHAR_POINTER(t) ||
HAK_OBJ_GET_SIZE(t) == 0 ||
/* ------------------------------------------------------------------------ *
* SYSTEM HANDLE PRIMITIVES
*
* Every one of these takes or returns a handle id - a small integer resolved
* against the handle table in hak-hnd.h. hak code never sees a descriptor, so
* it cannot name one it did not open, and hak_closehnd() is guaranteed the
* chance to unbind a handle from the multiplexer before it disappears.
*
* sys.read and sys.write follow the non-blocking contract: they return the
* byte count, 0 at end of file, or -1 when the handle would have blocked.
* -1 is an ordinary outcome - the caller is expected to wait on a semaphore
* bound with sem-signal-on-input/-output and try again. Only a genuine
* failure raises.
* ------------------------------------------------------------------------ */
/* pull a null-terminated hak string out of an argument */
static hak_bch_t* dup_path_arg (hak_t* hak, hak_oop_t t)
{
if (!HAK_OBJ_IS_CHAR_POINTER(t) || HAK_OBJ_GET_SIZE(t) == 0 ||
hak_count_oocstr(HAK_OBJ_GET_CHAR_SLOT(t)) != HAK_OBJ_GET_SIZE(t))
{
/* invalid command arguments */
goto oops;
hak_seterrbfmt(hak, HAK_EINVAL, "path not a proper string - %O", t);
return HAK_NULL;
}
return hak_dupootobcstr(hak, HAK_OBJ_GET_CHAR_SLOT(t), HAK_NULL);
}
cmd = hak_dupootobcstr(hak, HAK_OBJ_GET_CHAR_SLOT(t), HAK_NULL);
if (!cmd) goto oops;
#if !defined(_WIN32)
/* translate a mode string into open() flags. hak has no way to expose
* O_RDONLY and friends as constants yet - HAK_PFBASE_CONST is unimplemented -
* so a mode string is what a script can actually write today. */
static int mode_str_to_oflags (hak_t* hak, hak_oop_t t, int* oflags)
{
const hak_ooch_t* p;
hak_oow_t len;
int fl;
/* TODO: we need a bidirectional popen.. replace it with our own impl. */
pp = popen(cmd, "r");
if (!pp) goto oops;
if (!HAK_IN_SMPTR_RANGE(pp))
if (!HAK_OBJ_IS_CHAR_POINTER(t))
{
pclose(pp);
goto oops;
hak_seterrbfmt(hak, HAK_EINVAL, "mode not a string - %O", t);
return -1;
}
/* using smptr in this mannger is dangerous. because the caller may set random values to other function like pclose...... */
HAK_STACK_SETRET(hak, nargs, HAK_SMPTR_TO_OOP(pp));
return HAK_PF_SUCCESS;
p = HAK_OBJ_GET_CHAR_SLOT(t);
len = HAK_OBJ_GET_SIZE(t);
oops:
// TODO: set return value..
if (len == 1 && p[0] == 'r') fl = O_RDONLY;
else if (len == 1 && p[0] == 'w') fl = O_WRONLY | O_CREAT | O_TRUNC;
else if (len == 1 && p[0] == 'a') fl = O_WRONLY | O_CREAT | O_APPEND;
else if (len == 2 && p[0] == 'r' && p[1] == '+') fl = O_RDWR;
else if (len == 2 && p[0] == 'w' && p[1] == '+') fl = O_RDWR | O_CREAT | O_TRUNC;
else if (len == 2 && p[0] == 'a' && p[1] == '+') fl = O_RDWR | O_CREAT | O_APPEND;
else
{
hak_seterrbfmt(hak, HAK_EINVAL, "unrecognized open mode - %O", t);
return -1;
}
#if defined(O_CLOEXEC)
/* a child process has no business inheriting a handle hak code opened */
fl |= O_CLOEXEC;
#endif
#if defined(O_NONBLOCK)
/* required in the open() itself for a fifo, where a blocking open would
* wait for a peer. harmless on a regular file. */
fl |= O_NONBLOCK;
#endif
#if defined(O_LARGEFILE)
fl |= O_LARGEFILE;
#endif
*oflags = fl;
return 0;
}
#endif
static hak_pfrc_t pf_sys_open (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
#if defined(_WIN32)
hak_seterrnum(hak, HAK_ENOIMPL);
return HAK_PF_FAILURE;
#else
hak_bch_t* path;
hak_hnd_t* hnd;
int oflags, fd;
hak_ooi_t mode = 0644;
path = dup_path_arg(hak, HAK_STACK_GETARG(hak, nargs, 0));
if (HAK_UNLIKELY(!path)) return HAK_PF_FAILURE;
if (mode_str_to_oflags(hak, HAK_STACK_GETARG(hak, nargs, 1), &oflags) <= -1)
{
hak_freemem(hak, path);
return HAK_PF_FAILURE;
}
if (nargs >= 3 && hak_inttoooi(hak, HAK_STACK_GETARG(hak, nargs, 2), &mode) == 0)
{
hak_freemem(hak, path);
return HAK_PF_FAILURE;
}
fd = open(path, oflags, (int)mode);
if (fd <= -1)
{
hak_seterrbfmtwithsyserr(hak, 0, errno, "unable to open %hs", path);
hak_freemem(hak, path);
return HAK_PF_FAILURE;
}
hak_freemem(hak, path);
/* the probe inside hak_wrapfd() decides the type and whether the
* multiplexer will take it, so a regular file never reaches epoll. */
hnd = hak_wrapfd(hak, fd, 0, HAK_HND_OPEN_NONBLOCK);
if (HAK_UNLIKELY(!hnd))
{
close(fd);
return HAK_PF_FAILURE;
}
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(hnd->id));
return HAK_PF_SUCCESS;
#endif
}
static hak_pfrc_t pf_sys_close (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_hnd_t* hnd;
hnd = hak_gethndwithoop(hak, HAK_STACK_GETARG(hak, nargs, 0), HAK_HND_TYPE_ALL_FD);
if (HAK_UNLIKELY(!hnd)) return HAK_PF_FAILURE;
/* hak_closehnd() unbinds the handle from the multiplexer before the
* descriptor goes away and releases the node either way. */
if (hak_closehnd(hak, hnd) <= -1) return HAK_PF_FAILURE;
HAK_STACK_SETRET(hak, nargs, hak->_nil);
return HAK_PF_SUCCESS;
}
static hak_pfrc_t pf_sys_pclose (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
/* resolve the (buffer, offset, length) triple shared by read and write */
static int get_buf_args (hak_t* hak, hak_ooi_t nargs, hak_oop_t bufoop,
hak_oob_t** ptr, hak_oow_t* len)
{
hak_oop_t t;
hak_oow_t offset = 0, length, maxlen;
t = HAK_STACK_GETARG(hak, nargs, 0);
if (HAK_OOP_IS_SMPTR(t))
if (!HAK_OBJ_IS_BYTE_POINTER(bufoop))
{
FILE* pp;
pp = (FILE*)HAK_OOP_TO_SMPTR(t);
if (pp) pclose(pp);
hak_seterrbfmt(hak, HAK_EINVAL, "buffer not a byte array - %O", bufoop);
return -1;
}
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(0));
maxlen = HAK_OBJ_GET_SIZE(bufoop);
length = maxlen;
if (nargs >= 3)
{
hak_oop_t t = HAK_STACK_GETARG(hak, nargs, 2);
if (hak_inttooow(hak, t, &offset) == 0)
{
hak_seterrbfmt(hak, HAK_EINVAL, "invalid offset - %O", t);
return -1;
}
if (offset > maxlen)
{
hak_seterrbfmt(hak, HAK_ERANGE, "offset %zu past the end of a %zu byte buffer", offset, maxlen);
return -1;
}
length = maxlen - offset;
if (nargs >= 4)
{
t = HAK_STACK_GETARG(hak, nargs, 3);
if (hak_inttooow(hak, t, &length) == 0)
{
hak_seterrbfmt(hak, HAK_EINVAL, "invalid length - %O", t);
return -1;
}
if (length > maxlen - offset) length = maxlen - offset;
}
}
/* a raw slot pointer is safe to hold here: the collector is mark-sweep so
* it never relocates an object, and the buffer stays rooted on the
* argument stack for the duration of the call */
*ptr = &HAK_OBJ_GET_BYTE_SLOT(bufoop)[offset];
*len = length;
return 0;
}
static hak_pfrc_t pf_sys_read (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_hnd_t* hnd;
hak_oob_t* ptr;
hak_oow_t len;
hak_ooi_t n;
hnd = hak_gethndwithoop(hak, HAK_STACK_GETARG(hak, nargs, 0), HAK_HND_TYPE_ALL_FD);
if (HAK_UNLIKELY(!hnd)) return HAK_PF_FAILURE;
if (get_buf_args(hak, nargs, HAK_STACK_GETARG(hak, nargs, 1), &ptr, &len) <= -1) return HAK_PF_FAILURE;
n = hak_readhnd(hak, hnd, ptr, len);
if (n == HAK_HND_IO_ERROR) return HAK_PF_FAILURE;
/* n is >= 0, or -1 meaning the handle would have blocked */
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(n));
return HAK_PF_SUCCESS;
}
static hak_pfrc_t pf_sys_write (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_hnd_t* hnd;
hak_oob_t* ptr;
hak_oow_t len;
hak_ooi_t n;
hnd = hak_gethndwithoop(hak, HAK_STACK_GETARG(hak, nargs, 0), HAK_HND_TYPE_ALL_FD);
if (HAK_UNLIKELY(!hnd)) return HAK_PF_FAILURE;
if (get_buf_args(hak, nargs, HAK_STACK_GETARG(hak, nargs, 1), &ptr, &len) <= -1) return HAK_PF_FAILURE;
n = hak_writehnd(hak, hnd, ptr, len);
if (n == HAK_HND_IO_ERROR) return HAK_PF_FAILURE;
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(n));
return HAK_PF_SUCCESS;
}
/* (sys.pipe) -> #[read-handle write-handle]
*
* This is how hak code obtains a handle the multiplexer will accept without
* any raw descriptor ever crossing the boundary. Both ends are non-blocking
* and close-on-exec. */
static hak_pfrc_t pf_sys_pipe (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
#if defined(_WIN32)
hak_seterrnum(hak, HAK_ENOIMPL);
return HAK_PF_FAILURE;
#else
int p[2];
hak_hnd_t *r = HAK_NULL, *w = HAK_NULL;
hak_oop_t arr;
#if defined(HAVE_PIPE2) && defined(O_CLOEXEC) && defined(O_NONBLOCK)
if (pipe2(p, O_CLOEXEC | O_NONBLOCK) <= -1)
{
hak_seterrbfmtwithsyserr(hak, 0, errno, "unable to create a pipe");
return HAK_PF_FAILURE;
}
#else
if (pipe(p) <= -1)
{
hak_seterrbfmtwithsyserr(hak, 0, errno, "unable to create a pipe");
return HAK_PF_FAILURE;
}
#endif
r = hak_wrapfd(hak, p[0], 0, HAK_HND_OPEN_NONBLOCK);
if (HAK_UNLIKELY(!r)) goto oops;
w = hak_wrapfd(hak, p[1], 0, HAK_HND_OPEN_NONBLOCK);
if (HAK_UNLIKELY(!w)) goto oops;
/* the allocation may collect, but the handles live outside the heap */
arr = hak_makearray(hak, 2);
if (HAK_UNLIKELY(!arr)) goto oops;
HAK_OBJ_SET_OOP_VAL(arr, 0, HAK_SMOOI_TO_OOP(r->id));
HAK_OBJ_SET_OOP_VAL(arr, 1, HAK_SMOOI_TO_OOP(w->id));
HAK_STACK_SETRET(hak, nargs, arr);
return HAK_PF_SUCCESS;
oops:
if (w) hak_closehnd(hak, w); else close(p[1]);
if (r) hak_closehnd(hak, r); else close(p[0]);
return HAK_PF_FAILURE;
#endif
}
/* ------------------------------------------------------------------------ *
* CHILD PROCESSES
*
* A child is represented as a group of handles: one HAK_HND_TYPE_PROC node
* holding the hak_pio_t, plus one HAK_HND_TYPE_PIPE node per requested stream.
* The pipe nodes are owned by the proc node, so tearing the proc node down
* tears the whole group down in the right order - children first, which is
* what lets each pipe unbind itself from the multiplexer while its descriptor
* is still valid.
*
* pio owns those descriptors, so a stream node closes itself through
* hak_pio_end() rather than close(2). That keeps pio's own view consistent -
* it nils the handle it just closed, so the later hak_pio_free() will not
* close it twice - and it means sys.close on a child's stdin really does send
* EOF, which is how a filter like `tr` is told to flush and exit.
*
* Every pipe is non-blocking, so sys.read and sys.write on a child's streams
* behave exactly like they do on a sys.pipe handle - including returning -1
* for "would block", which is what makes a child usable from a coprocess
* without stalling the VM.
*
* Where the platform provides pidfd_open(), the group also carries an exit
* handle: a descriptor that becomes readable when the child terminates. It is
* an ordinary muxable handle, so waiting for a child costs nothing more than
* waiting for a pipe - no signal handler, no shared signal stream, and none of
* the coalescing that makes SIGCHLD awkward, since the handle is per child and
* stays readable once set. Without it the exit handle is nil and a caller has
* to poll sys.pwait.
* ------------------------------------------------------------------------ */
/* kept in the pio extension area, so no separate allocation is needed */
struct proc_xtn_t
{
int reaped; /* has the child been waited on already? */
int status; /* ...and what did it exit with */
};
typedef struct proc_xtn_t proc_xtn_t;
static void proc_dtor (hak_t* hak, hak_hnd_t* hnd)
{
/* hak_pio_free() ends the pipes, reaps the child - killing it if it is
* still running - and frees the object, all without an unbounded wait.
* this runs from sys.pclose and from hak_finihndtab() alike, so a script
* that simply forgets a child still cannot leak one. */
hak_pio_free((hak_pio_t*)hnd->u.ptr);
}
/* close a child's stream through pio, so that pio stops believing it still
* owns the descriptor. the stream is identified by matching the descriptor,
* which is unambiguous because a pio's three ends are always distinct. */
static void stream_dtor (hak_t* hak, hak_hnd_t* hnd)
{
hak_hnd_t* ph;
/* the owner is still alive here: hak_closehnd() closes what a node owns
* before closing the node itself */
ph = hak_gethnd(hak, hnd->owner, HAK_HND_TYPE_PROC);
if (ph)
{
hak_pio_t* pio = (hak_pio_t*)ph->u.ptr;
hak_pio_hid_t hid;
for (hid = HAK_PIO_IN; hid <= HAK_PIO_ERR; hid++)
{
if (hak_pio_gethnd(pio, hid) == (hak_pio_hnd_t)hnd->u.fd)
{
hak_pio_end(pio, hid);
return;
}
}
}
/* the owner is gone, or the stream is no longer pio's - fall back so the
* descriptor is not leaked */
close(hnd->u.fd);
}
/* wrap one of the child's streams as a pipe handle owned by the proc node */
static hak_hnd_t* wrap_stream (hak_t* hak, hak_pio_t* pio, hak_pio_hid_t hid, hak_hnd_t* owner)
{
hak_hnd_t* h;
h = hak_wrapfd(hak, (int)hak_pio_gethnd(pio, hid), HAK_HND_TYPE_PIPE, 0);
if (HAK_UNLIKELY(!h)) return HAK_NULL;
hak_ownhnd(hak, h, owner);
h->dtor = stream_dtor;
return h;
}
/* (sys.popen cmd [mode]) -> #[proc in out err exit]
*
* mode is any combination of 'r' (read the child's stdout), 'w' (write to its
* stdin) and 'e' (read its stderr); the default is "r". A stream that was not
* requested comes back as nil, as does the exit handle where the platform has
* no pidfd_open(). The command is run through a shell, as popen() does.
*/
static hak_pfrc_t pf_sys_popen (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_oop_t cmdoop, arr;
hak_pio_t* pio;
proc_xtn_t* x;
hak_hnd_t *ph, *ih = HAK_NULL, *oh = HAK_NULL, *eh = HAK_NULL, *xh = HAK_NULL;
int flags;
cmdoop = HAK_STACK_GETARG(hak, nargs, 0);
if (!HAK_OBJ_IS_CHAR_POINTER(cmdoop) || HAK_OBJ_GET_SIZE(cmdoop) == 0 ||
hak_count_oocstr(HAK_OBJ_GET_CHAR_SLOT(cmdoop)) != HAK_OBJ_GET_SIZE(cmdoop))
{
hak_seterrbfmt(hak, HAK_EINVAL, "command not a proper string - %O", cmdoop);
return HAK_PF_FAILURE;
}
/* every wait is non-blocking; sys.pwait reports 256 for a live child
* rather than stopping every other coprocess. */
flags = HAK_PIO_SHELL | HAK_PIO_WAITNOBLOCK;
if (nargs >= 2)
{
hak_oop_t m = HAK_STACK_GETARG(hak, nargs, 1);
const hak_ooch_t* p;
hak_oow_t i, len;
if (!HAK_OBJ_IS_CHAR_POINTER(m))
{
hak_seterrbfmt(hak, HAK_EINVAL, "mode not a string - %O", m);
return HAK_PF_FAILURE;
}
p = HAK_OBJ_GET_CHAR_SLOT(m);
len = HAK_OBJ_GET_SIZE(m);
for (i = 0; i < len; i++)
{
switch (p[i])
{
case 'r': flags |= HAK_PIO_READOUT | HAK_PIO_OUTNOBLOCK; break;
case 'w': flags |= HAK_PIO_WRITEIN | HAK_PIO_INNOBLOCK; break;
case 'e': flags |= HAK_PIO_READERR | HAK_PIO_ERRNOBLOCK; break;
default:
hak_seterrbfmt(hak, HAK_EINVAL, "unrecognized popen mode - %O", m);
return HAK_PF_FAILURE;
}
}
}
else flags |= HAK_PIO_READOUT | HAK_PIO_OUTNOBLOCK;
pio = hak_pio_open(hak, HAK_SIZEOF(proc_xtn_t), HAK_OBJ_GET_CHAR_SLOT(cmdoop), flags, HAK_NULL, HAK_NULL);
if (HAK_UNLIKELY(!pio)) return HAK_PF_FAILURE;
x = (proc_xtn_t*)hak_pio_getxtn(pio);
x->reaped = 0;
x->status = 0;
/* the proc node owns the pio object from here on: if any wrap below
* fails, closing it disposes of the child through proc_dtor(). */
ph = hak_wrapptr(hak, pio, HAK_HND_TYPE_PROC, 0, proc_dtor);
if (HAK_UNLIKELY(!ph))
{
hak_pio_free(pio);
return HAK_PF_FAILURE;
}
if ((flags & HAK_PIO_WRITEIN) && !(ih = wrap_stream(hak, pio, HAK_PIO_IN, ph))) goto oops;
if ((flags & HAK_PIO_READOUT) && !(oh = wrap_stream(hak, pio, HAK_PIO_OUT, ph))) goto oops;
if ((flags & HAK_PIO_READERR) && !(eh = wrap_stream(hak, pio, HAK_PIO_ERR, ph))) goto oops;
/* -DHAK_SYS_NO_EXITHND suppresses the exit handle, which is how the SIGCHLD
* fallback path in the library layer gets exercised on a platform that does
* have pidfd_open(). */
#if defined(SYS_pidfd_open) && !defined(HAK_SYS_NO_EXITHND)
{
/* the syscall directly rather than the glibc wrapper, which only
* appeared in glibc 2.36 */
int xfd = (int)syscall(SYS_pidfd_open, (pid_t)hak_pio_getchild(pio), 0);
if (xfd >= 0)
{
/* HAK_HND_OPEN_MUXABLE because we know a pidfd is pollable; the
* probe reaches the same conclusion via its anonymous-inode arm,
* but saying so here keeps this correct on a platform where it
* does not. */
xh = hak_wrapfd(hak, xfd, 0, HAK_HND_OPEN_MUXABLE);
if (HAK_UNLIKELY(!xh))
{
close(xfd);
goto oops;
}
hak_ownhnd(hak, xh, ph);
}
/* a failure here is not fatal: an older kernel simply means no exit
* handle, and the caller polls sys.pwait instead. */
}
#endif
/* this may collect, but handle nodes live outside the object heap */
arr = hak_makearray(hak, 5);
if (HAK_UNLIKELY(!arr)) goto oops;
HAK_OBJ_SET_OOP_VAL(arr, 0, HAK_SMOOI_TO_OOP(ph->id));
HAK_OBJ_SET_OOP_VAL(arr, 1, ih? HAK_SMOOI_TO_OOP(ih->id): hak->_nil);
HAK_OBJ_SET_OOP_VAL(arr, 2, oh? HAK_SMOOI_TO_OOP(oh->id): hak->_nil);
HAK_OBJ_SET_OOP_VAL(arr, 3, eh? HAK_SMOOI_TO_OOP(eh->id): hak->_nil);
HAK_OBJ_SET_OOP_VAL(arr, 4, xh? HAK_SMOOI_TO_OOP(xh->id): hak->_nil);
HAK_STACK_SETRET(hak, nargs, arr);
return HAK_PF_SUCCESS;
oops:
hak_closehnd(hak, ph); /* takes the stream nodes and the child with it */
return HAK_PF_FAILURE;
}
/* (sys.pwait proc) -> 0..255 | 256 + signo | 256 if still running
*
* Never blocks. A script that wants to wait can loop on this, or - once
* SIGCHLD is routed to the signal descriptor - wait on that instead.
*/
static hak_pfrc_t pf_sys_pwait (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_hnd_t* ph;
hak_pio_t* pio;
proc_xtn_t* x;
int n;
ph = hak_gethndwithoop(hak, HAK_STACK_GETARG(hak, nargs, 0), HAK_HND_TYPE_PROC);
if (HAK_UNLIKELY(!ph)) return HAK_PF_FAILURE;
pio = (hak_pio_t*)ph->u.ptr;
x = (proc_xtn_t*)hak_pio_getxtn(pio);
if (x->reaped)
{
/* the child was waited on already; waitpid() would now fail with
* ECHILD, so report what it exited with instead */
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(x->status));
return HAK_PF_SUCCESS;
}
n = hak_pio_wait(pio);
if (n <= -1) return HAK_PF_FAILURE;
if (n != 255 + 1)
{
x->reaped = 1;
x->status = n;
}
HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(n));
return HAK_PF_SUCCESS;
}
/* (sys.pkill proc) - SIGKILL the child */
static hak_pfrc_t pf_sys_pkill (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_hnd_t* ph;
ph = hak_gethndwithoop(hak, HAK_STACK_GETARG(hak, nargs, 0), HAK_HND_TYPE_PROC);
if (HAK_UNLIKELY(!ph)) return HAK_PF_FAILURE;
if (hak_pio_kill((hak_pio_t*)ph->u.ptr) <= -1) return HAK_PF_FAILURE;
HAK_STACK_SETRET(hak, nargs, hak->_nil);
return HAK_PF_SUCCESS;
}
/* (sys.pclose proc) -> the child's exit status if it is known, else nil
*
* Tears the whole group down: the stream handles are released and unbound
* from the multiplexer, and the child is reaped - killed first if it has not
* exited. It does not wait for a running child to finish on its own, so it
* cannot stall the other coprocesses; use sys.pwait for that.
*/
static hak_pfrc_t pf_sys_pclose (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
hak_hnd_t* ph;
proc_xtn_t* x;
hak_oop_t retv;
ph = hak_gethndwithoop(hak, HAK_STACK_GETARG(hak, nargs, 0), HAK_HND_TYPE_PROC);
if (HAK_UNLIKELY(!ph)) return HAK_PF_FAILURE;
x = (proc_xtn_t*)hak_pio_getxtn((hak_pio_t*)ph->u.ptr);
retv = x->reaped? HAK_SMOOI_TO_OOP(x->status): hak->_nil;
if (hak_closehnd(hak, ph) <= -1) return HAK_PF_FAILURE;
HAK_STACK_SETRET(hak, nargs, retv);
return HAK_PF_SUCCESS;
}
static hak_pfinfo_t pfinfos[] =
{
{ "close", { HAK_PFBASE_FUNC, pf_sys_close, 1, 1 } },
{ "open", { HAK_PFBASE_FUNC, pf_sys_open, 2, 3 } },
{ "pclose", { HAK_PFBASE_FUNC, pf_sys_pclose, 1, 1 } },
{ "pipe", { HAK_PFBASE_FUNC, pf_sys_pipe, 0, 0 } },
{ "pkill", { HAK_PFBASE_FUNC, pf_sys_pkill, 1, 1 } },
{ "popen", { HAK_PFBASE_FUNC, pf_sys_popen, 1, 2 } },
{ "pwait", { HAK_PFBASE_FUNC, pf_sys_pwait, 1, 1 } },
{ "random", { HAK_PFBASE_FUNC, pf_sys_random, 0, 0 } },
{ "read", { HAK_PFBASE_FUNC, pf_sys_read, 2, 4 } },
{ "srandom", { HAK_PFBASE_FUNC, pf_sys_srandom, 1, 1 } },
{ "stime", { HAK_PFBASE_FUNC, pf_sys_stime, 1, 1 } },
{ "time", { HAK_PFBASE_FUNC, pf_sys_time, 0, 0 } }
{ "time", { HAK_PFBASE_FUNC, pf_sys_time, 0, 0 } },
{ "write", { HAK_PFBASE_FUNC, pf_sys_write, 2, 4 } }
};
/* ------------------------------------------------------------------------ */
+122
View File
@@ -0,0 +1,122 @@
## ---------------------------------------------------------------------------------
## Exercises for the kernel classes.
##
## This was the tail of kernel.hak. It is kept apart because kernel.hak has to
## be loadable as a library: anything with $include "kernel.hak" in it was
## running these exercises, and their output - and their deliberate errors -
## came along with it.
##
## Run it directly: hak src/kernel-demo.hak
## ---------------------------------------------------------------------------------
$include "kernel.hak"
k := "abcdefghijklmn"
printf "string length %d\n" ("aaaa":length)
printf "substring [%s]\n" (k:slice 5 6)
try {
printf "substring [%c]\n" (k:at 13)
k:atPut 14 'A'
printf "[%s]\n" k
} catch (e) {
printf "EXCEPTION %O\n" e
}
k := #[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
try {
k:atPut 2 'A'
printf "%O\n" k
} catch (e) {
printf "EXCEPTION %O\n" e
}
k := #b[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
try {
k:atPut 2 -10
printf "%O\n" k
} catch (e) {
printf "EXCEPTION %O\n" e
}
k := (Array:new 10)
k:atPut 3 "hello"
printf "%O\n" k
printf "[%O]\n" (String:new 5)
printf "[%O]\n" (String:basicNew 5)
printf "[%O]\n" (String:respondsTo "new")
printf "[%O]\n" (String:respondsTo "newx")
printf "[%O]\n" (" ":respondsTo "new")
printf "[%O]\n" (" ":respondsTo "length")
##printf "[%O]\n" (String:classVariableNames)
##printf "[%O]\n" (String:instanceVariableNames)
##printf "%O\n" #"abcdefg"
printf "----------------------------------------\n"
k := #[1 2 3]
printf "%O\n" (k:basicAt 2)
class [#varying] Z: Object (a b c) {
fun[#classinst] new() {
self.a := 10
self.b := 20
self.c := 30
}
fun aaa() {
printf "%d %d %d\n" a b c
}
}
fun Z:abc() {
printf "%d %d %d\n" a b c ## this is not recognized as ....
}
class Q {
fun[#class] k () {
k := (Z:basicNew 10) ## #varying is really required? what is the big deal even if you allow it regardless?
##k := (Z:new) ## no way to add extra fields.
k:basicAtPut 2 "hello"
k:basicAtPut 3 "world"
printf "----------------------------------------\n"
printf "%O\n" (k:basicAt 20)
##k := (Z:new)
##k:aaa
}
}
fun c() {
(Q:k)
}
##try {
printf ">>>>>>>>>>>>>>>\n"
d := ((fun (x){
printf "<<%d>>\n" x
c
}) 90)
##(j)
(d 10)
##} catch (e) {
printf "EXCEPTION: %O\n" e
## try {
throw 10000
## } catch (e) {
## printf "EXCEPTION-X: %O\n" e
## }
##}
## if the assigne operator is seen
## a := (fun (x) { })
## a := fun (x) { } <<--- can I support this syntax??
-110
View File
@@ -82,113 +82,3 @@ $include "Collection.hak"
##$include "System.hak"
## ---------------------------------------------------------------------------------
k := "abcdefghijklmn"
printf "string length %d\n" ("aaaa":length)
printf "substring [%s]\n" (k:slice 5 6)
try {
printf "substring [%c]\n" (k:at 13)
k:atPut 14 'A'
printf "[%s]\n" k
} catch (e) {
printf "EXCEPTION %O\n" e
}
k := #[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
try {
k:atPut 2 'A'
printf "%O\n" k
} catch (e) {
printf "EXCEPTION %O\n" e
}
k := #b[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
try {
k:atPut 2 -10
printf "%O\n" k
} catch (e) {
printf "EXCEPTION %O\n" e
}
k := (Array:new 10)
k:atPut 3 "hello"
printf "%O\n" k
printf "[%O]\n" (String:new 5)
printf "[%O]\n" (String:basicNew 5)
printf "[%O]\n" (String:respondsTo "new")
printf "[%O]\n" (String:respondsTo "newx")
printf "[%O]\n" (" ":respondsTo "new")
printf "[%O]\n" (" ":respondsTo "length")
##printf "[%O]\n" (String:classVariableNames)
##printf "[%O]\n" (String:instanceVariableNames)
##printf "%O\n" #"abcdefg"
printf "----------------------------------------\n"
k := #[1 2 3]
printf "%O\n" (k:basicAt 2)
class [#varying] Z: Object (a b c) {
fun[#classinst] new() {
self.a := 10
self.b := 20
self.c := 30
}
fun aaa() {
printf "%d %d %d\n" a b c
}
}
fun Z:abc() {
printf "%d %d %d\n" a b c ## this is not recognized as ....
}
class Q {
fun[#class] k () {
k := (Z:basicNew 10) ## #varying is really required? what is the big deal even if you allow it regardless?
##k := (Z:new) ## no way to add extra fields.
k:basicAtPut 2 "hello"
k:basicAtPut 3 "world"
printf "----------------------------------------\n"
printf "%O\n" (k:basicAt 20)
##k := (Z:new)
##k:aaa
}
}
fun c() {
(Q:k)
}
##try {
printf ">>>>>>>>>>>>>>>\n"
d := ((fun (x){
printf "<<%d>>\n" x
c
}) 90)
##(j)
(d 10)
##} catch (e) {
printf "EXCEPTION: %O\n" e
## try {
throw 10000
## } catch (e) {
## printf "EXCEPTION-X: %O\n" e
## }
##}
## if the assigne operator is seen
## a := (fun (x) { })
## a := fun (x) { } <<--- can I support this syntax??
+235
View File
@@ -0,0 +1,235 @@
## ---------------------------------------------------------------------------
## Child process supervision.
##
## sys.popen hands back a group of handles and sys.pwait never blocks, which
## leaves every caller to invent its own way of learning that a child has
## finished. This layer supplies one: a set of children watched together, so
## that a coprocess can wait for whichever of them finishes first without
## stalling the rest of the virtual machine.
##
## Two mechanisms can report a child's exit, and they have different shapes:
##
## - An exit handle (a pidfd on linux) belongs to one child and stays
## readable once that child is gone. Each child gets its own semaphore in
## the group, so a wakeup names exactly the child that finished.
##
## - SIGCHLD is a single process-wide event. Only one semaphore may be bound
## to a descriptor, so the whole group shares one semaphore on the signal
## descriptor; and because standard signals coalesce - three children
## exiting can produce one SIGCHLD - a wakeup must examine every child
## rather than just one.
##
## The difference is confined to spawn() and wait(). Callers see the same
## interface either way, and read the same #[proc in out err exit sem] record.
## ---------------------------------------------------------------------------
## record slots
## 0 proc handle, 1 stdin, 2 stdout, 3 stderr, 4 exit handle, 5 semaphore
class ChildGroup(
sg ## semaphore group: the per-child semaphores or the shared one,
## plus tmo
tmo ## timeout semaphore, always a member of sg
kids ## array of child records, nil in a freed slot
capa
nkids
shared ## true when exit handles are unavailable and SIGCHLD is in use
sigsem ## the shared semaphore; only in shared mode
) {
fun[#ci] new() {
set sg (semgr-new)
set tmo (sem-new)
semgr-add sg tmo
set capa 8
set kids (core.basicNew Array 8)
set nkids 0
set shared false
set sigsem nil
return self
}
fun count() { return self.nkids }
## --- registry ------------------------------------------------------
fun remember(kid) {
| i n bigger |
i := 0
while (< i self.capa) {
if (nil? (core.basicAt self.kids i)) {
core.basicAtPut self.kids i kid
set nkids (+ self.nkids 1)
return kid
}
i := (+ i 1)
}
## no free slot: widen the array and retry
n := (* self.capa 2)
bigger := (core.basicNew Array n)
i := 0
while (< i self.capa) {
core.basicAtPut bigger i (core.basicAt self.kids i)
i := (+ i 1)
}
set kids bigger
set capa n
return (self:remember kid)
}
fun forget(kid) {
| i |
i := 0
while (< i self.capa) {
if (eqv? (core.basicAt self.kids i) kid) {
core.basicAtPut self.kids i nil
set nkids (- self.nkids 1)
return kid
}
i := (+ i 1)
}
return nil
}
## --- spawning ------------------------------------------------------
## Spawn a command and watch it. mode is as sys.popen takes it.
## Returns the child record.
fun spawn(cmd mode) {
| p proc xh sem kid |
p := (sys.popen cmd mode)
proc := (core.basicAt p 0)
xh := (core.basicAt p 4)
sem := nil
if (nil? xh) {
## No per-child exit handle on this platform. Establish one shared
## semaphore on the signal descriptor for the whole group, once.
if (not self.shared) {
set shared true
system-catch-sig 17 ## SIGCHLD - define this as a constant...
set sigsem (sem-new)
semgr-add self.sg self.sigsem
sem-signal-on-input self.sigsem (system-get-sigfd)
}
} else {
## A handle of its own, so a wakeup identifies this child directly.
sem := (sem-new)
semgr-add self.sg sem
sem-signal-on-input sem xh
}
kid := (core.basicNew Array 6)
core.basicAtPut kid 0 proc
core.basicAtPut kid 1 (core.basicAt p 1)
core.basicAtPut kid 2 (core.basicAt p 2)
core.basicAtPut kid 3 (core.basicAt p 3)
core.basicAtPut kid 4 xh
core.basicAtPut kid 5 sem
return (self:remember kid)
}
## --- waiting -------------------------------------------------------
## The first child that has already finished, or nil.
## Scanning every child is what makes SIGCHLD coalescing harmless: one
## signal covering three exits loses nothing.
fun finished() {
| i kid |
i := 0
while (< i self.capa) {
kid := (core.basicAt self.kids i)
if (not (nil? kid)) {
if (not (= (sys.pwait (core.basicAt kid 0)) 256)) { return kid }
}
i := (+ i 1)
}
return nil
}
fun kid-of-sem(sem) {
| i kid |
i := 0
while (< i self.capa) {
kid := (core.basicAt self.kids i)
if (not (nil? kid)) {
if (eqv? (core.basicAt kid 5) sem) { return kid }
}
i := (+ i 1)
}
return nil
}
## Wait for one of the watched children to finish, or for secs to elapse.
## Returns the child record, or nil on timeout. Other coprocesses keep
## running throughout: the wait is on a semaphore, not on a system call.
fun wait(secs) {
| w kid |
## one may have finished before we were asked
kid := (self:finished)
if (not (nil? kid)) { return kid }
while true {
sem-signal self.tmo secs 0
w := (semgr-wait self.sg)
sem-unsignal self.tmo
if (eqv? w self.tmo) { return nil }
if self.shared {
## the signal descriptor spoke: take the byte, then look at
## everyone, since one SIGCHLD may stand for several exits
system-get-sig
kid := (self:finished)
if (not (nil? kid)) { return kid }
## otherwise it was a SIGCHLD for a child of the host
## application, or one already reaped. keep waiting.
} else {
kid := (self:kid-of-sem w)
if (not (nil? kid)) {
sem-unsignal w
return kid
}
}
}
}
## --- accessors and teardown ----------------------------------------
fun proc-of(kid) { return (core.basicAt kid 0) }
fun in-of(kid) { return (core.basicAt kid 1) }
fun out-of(kid) { return (core.basicAt kid 2) }
fun err-of(kid) { return (core.basicAt kid 3) }
## The exit status, or 256 while the child is still running.
fun status(kid) { return (sys.pwait (core.basicAt kid 0)) }
## Stop watching a child and release everything it holds.
fun close(kid) {
| sem |
sem := (core.basicAt kid 5)
if (not (nil? sem)) {
sem-unsignal sem
semgr-remove self.sg sem
}
self:forget kid
sys.pclose (core.basicAt kid 0)
}
## Release the group. Any children still in it are torn down.
fun done() {
| i kid |
i := 0
while (< i self.capa) {
kid := (core.basicAt self.kids i)
if (not (nil? kid)) { self:close kid }
i := (+ i 1)
}
if self.shared {
sem-unsignal self.sigsem
system-uncatch-sig 17
set shared false
}
}
}
+18 -1
View File
@@ -9,14 +9,25 @@ AM_CPPFLAGS = \
LDADD = ../lib/libhak.la
check_SCRIPTS = \
classof-01.hak \
cons-01.hak \
dic-01.hak \
feed-01.hak \
fun-01.hak \
hnd-01.hak \
hnd-02.hak \
hnd-03.hak \
insta-01.hak \
insta-02.hak \
prim-01.hak \
proc-01.hak \
proclib-01.hak \
ret-01.hak \
retvar-01.hak \
sig-01.hak \
sysproc-01.hak \
sysproc-02.hak \
tick-01.hak \
va-01.hak \
var-01.hak \
var-02.hak \
@@ -26,15 +37,21 @@ check_ERRORS = \
call-5001.err \
class-5001.err \
feed-5001.err \
hnd-5001.err \
mlist-5001.err \
sig-5001.err \
var-5001.err \
var-5002.err \
var-5004.err
check_PROGRAMS = \
t-001
t-001 \
t-002 \
t-003
t_001_SOURCES = t-001.c tap.h
t_002_SOURCES = t-002.c tap.h
t_003_SOURCES = t-003.c tap.h
##noinst_SCRIPTS = $(check_SCRIPTS)
EXTRA_DIST = $(check_SCRIPTS) $(check_ERRORS)
+56 -4
View File
@@ -89,7 +89,7 @@ PRE_UNINSTALL = :
POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
check_PROGRAMS = t-001$(EXEEXT)
check_PROGRAMS = t-001$(EXEEXT) t-002$(EXEEXT) t-003$(EXEEXT)
subdir = t
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_sign.m4 \
@@ -112,6 +112,14 @@ AM_V_lt = $(am__v_lt_@AM_V@)
am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
am__v_lt_0 = --silent
am__v_lt_1 =
am_t_002_OBJECTS = t-002.$(OBJEXT)
t_002_OBJECTS = $(am_t_002_OBJECTS)
t_002_LDADD = $(LDADD)
t_002_DEPENDENCIES = ../lib/libhak.la
am_t_003_OBJECTS = t-003.$(OBJEXT)
t_003_OBJECTS = $(am_t_003_OBJECTS)
t_003_LDADD = $(LDADD)
t_003_DEPENDENCIES = ../lib/libhak.la
AM_V_P = $(am__v_P_@AM_V@)
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
am__v_P_0 = false
@@ -127,7 +135,8 @@ am__v_at_1 =
DEFAULT_INCLUDES =
depcomp = $(SHELL) $(top_srcdir)/ac/depcomp
am__maybe_remake_depfiles = depfiles
am__depfiles_remade = ./$(DEPDIR)/t-001.Po
am__depfiles_remade = ./$(DEPDIR)/t-001.Po ./$(DEPDIR)/t-002.Po \
./$(DEPDIR)/t-003.Po
am__mv = mv -f
COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
@@ -147,8 +156,8 @@ AM_V_CCLD = $(am__v_CCLD_@AM_V@)
am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
am__v_CCLD_0 = @echo " CCLD " $@;
am__v_CCLD_1 =
SOURCES = $(t_001_SOURCES)
DIST_SOURCES = $(t_001_SOURCES)
SOURCES = $(t_001_SOURCES) $(t_002_SOURCES) $(t_003_SOURCES)
DIST_SOURCES = $(t_001_SOURCES) $(t_002_SOURCES) $(t_003_SOURCES)
am__can_run_installinfo = \
case $$AM_UPDATE_INFO_DIR in \
n|no|NO) false;; \
@@ -538,14 +547,25 @@ AM_CPPFLAGS = \
LDADD = ../lib/libhak.la
check_SCRIPTS = \
classof-01.hak \
cons-01.hak \
dic-01.hak \
feed-01.hak \
fun-01.hak \
hnd-01.hak \
hnd-02.hak \
hnd-03.hak \
insta-01.hak \
insta-02.hak \
prim-01.hak \
proc-01.hak \
proclib-01.hak \
ret-01.hak \
retvar-01.hak \
sig-01.hak \
sysproc-01.hak \
sysproc-02.hak \
tick-01.hak \
va-01.hak \
var-01.hak \
var-02.hak \
@@ -555,12 +575,16 @@ check_ERRORS = \
call-5001.err \
class-5001.err \
feed-5001.err \
hnd-5001.err \
mlist-5001.err \
sig-5001.err \
var-5001.err \
var-5002.err \
var-5004.err
t_001_SOURCES = t-001.c tap.h
t_002_SOURCES = t-002.c tap.h
t_003_SOURCES = t-003.c tap.h
EXTRA_DIST = $(check_SCRIPTS) $(check_ERRORS)
TESTS = $(check_PROGRAMS) $(check_SCRIPTS) $(check_ERRORS)
TEST_EXTENSIONS = .hak .err
@@ -611,6 +635,14 @@ t-001$(EXEEXT): $(t_001_OBJECTS) $(t_001_DEPENDENCIES) $(EXTRA_t_001_DEPENDENCIE
@rm -f t-001$(EXEEXT)
$(AM_V_CCLD)$(LINK) $(t_001_OBJECTS) $(t_001_LDADD) $(LIBS)
t-002$(EXEEXT): $(t_002_OBJECTS) $(t_002_DEPENDENCIES) $(EXTRA_t_002_DEPENDENCIES)
@rm -f t-002$(EXEEXT)
$(AM_V_CCLD)$(LINK) $(t_002_OBJECTS) $(t_002_LDADD) $(LIBS)
t-003$(EXEEXT): $(t_003_OBJECTS) $(t_003_DEPENDENCIES) $(EXTRA_t_003_DEPENDENCIES)
@rm -f t-003$(EXEEXT)
$(AM_V_CCLD)$(LINK) $(t_003_OBJECTS) $(t_003_LDADD) $(LIBS)
mostlyclean-compile:
-rm -f *.$(OBJEXT)
@@ -618,6 +650,8 @@ distclean-compile:
-rm -f *.tab.c
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-001.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-002.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-003.Po@am__quote@ # am--include-marker
$(am__depfiles_remade):
@$(MKDIR_P) $(@D)
@@ -880,6 +914,20 @@ t-001.log: t-001$(EXEEXT)
--log-file $$b.log --trs-file $$b.trs \
$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
"$$tst" $(AM_TESTS_FD_REDIRECT)
t-002.log: t-002$(EXEEXT)
@p='t-002$(EXEEXT)'; \
b='t-002'; \
$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
--log-file $$b.log --trs-file $$b.trs \
$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
"$$tst" $(AM_TESTS_FD_REDIRECT)
t-003.log: t-003$(EXEEXT)
@p='t-003$(EXEEXT)'; \
b='t-003'; \
$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
--log-file $$b.log --trs-file $$b.trs \
$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
"$$tst" $(AM_TESTS_FD_REDIRECT)
.hak.log:
@p='$<'; \
$(am__set_b); \
@@ -988,6 +1036,8 @@ clean-am: clean-checkPROGRAMS clean-generic clean-libtool \
distclean: distclean-am
-rm -f ./$(DEPDIR)/t-001.Po
-rm -f ./$(DEPDIR)/t-002.Po
-rm -f ./$(DEPDIR)/t-003.Po
-rm -f Makefile
distclean-am: clean-am distclean-compile distclean-generic \
distclean-tags
@@ -1034,6 +1084,8 @@ installcheck-am:
maintainer-clean: maintainer-clean-am
-rm -f ./$(DEPDIR)/t-001.Po
-rm -f ./$(DEPDIR)/t-002.Po
-rm -f ./$(DEPDIR)/t-003.Po
-rm -f Makefile
maintainer-clean-am: distclean-am maintainer-clean-generic
+34
View File
@@ -0,0 +1,34 @@
## core.classOf
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
## every kind of object has a class, including values encoded in the pointer
chk (eqv? (core.classOf #[1 2]) Array) "an array's class is Array"
chk (eqv? (core.classOf #b[1 2]) ByteArray) "a byte array's class is ByteArray"
chk (eqv? (core.classOf "s") String) "a string's class is String"
chk (eqv? (core.classOf #{}) Dictionary) "a dictionary's class is Dictionary"
chk (eqv? (core.classOf #(1 2)) Cons) "a data list's class is Cons"
chk (class? (core.classOf 42)) "a small integer has a class"
chk (class? (core.classOf 'c')) "a character has a class"
chk (class? (core.classOf nil)) "nil has a class"
chk (class? (core.classOf true)) "true has a class"
chk (class? (core.classOf (fun(x) { return x }))) "a block has a class"
## a class is itself an object, so it has a class of its own. this is where
## classOf and className differ: className answers the receiver's own name
## when handed a class, classOf answers what the class is an instance of.
chk (class? (core.classOf Array)) "a class has a class"
## className answers a symbol, not a string, and for a class it answers that
## class's own name - so an instance and its class agree
chk (eqv? (core.className #[1 2]) (core.className Array)) "className of an instance matches its class"
chk (not (eqv? (core.classOf Array) Array)) "a class is not an instance of itself"
## the point of it: building something of the receiver's own kind
fun newLike(x size) { return (core.basicNew (core.classOf x) size) }
chk (eqv? (core.classOf (newLike #[1] 2)) Array) "an array can make an array"
chk (eqv? (core.classOf (newLike "s" 2)) String) "a string can make a string"
chk (eqv? (core.classOf (newLike #b[1] 2)) ByteArray) "a byte array can make a byte array"
chk (= (core.basicSize (newLike #[1] 5)) 5) "the new one has the requested size"
+79
View File
@@ -0,0 +1,79 @@
## core.car / core.cdr / core.cons
##
## A data list #(1 2 3) is a chain of Cons cells whose head and tail live in
## named instance variables, so basicAt and primAt cannot reach them. These
## three are what let the class library walk and build a list.
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
l := #(1 2 3)
chk (eqv? (core.classOf l) Cons) "a data list is a chain of Cons cells"
chk (= (core.car l) 1) "car answers the head"
chk (eqv? (core.classOf (core.cdr l)) Cons) "cdr answers the tail"
chk (= (core.car (core.cdr l)) 2) "cadr reaches the second element"
chk (nil? (core.cdr (core.cdr (core.cdr l)))) "the tail of the last cell is nil"
## walking
n := 0
sum := 0
p := l
while (not (nil? p)) {
n := (+ n 1)
sum := (+ sum (core.car p))
p := (core.cdr p)
}
chk (= n 3) "a list can be walked to its end"
chk (= sum 6) "and every element visited"
## building
b := (core.cons 1 (core.cons 2 (core.cons 3 nil)))
chk (eqv? (core.classOf b) Cons) "cons builds a cell"
chk (= (core.car b) 1) "the built list starts where it should"
chk (= (core.car (core.cdr (core.cdr b))) 3) "and ends where it should"
chk (nil? (core.cdr (core.cdr (core.cdr b)))) "and is properly terminated"
## an improper list is allowed - the tail need not be a list
i := (core.cons 1 2)
chk (= (core.cdr i) 2) "cons accepts a non-list tail"
## select over a list, species preserved, built with cons alone
fun l-reverse(l) {
| p out |
out := nil
p := l
while (not (nil? p)) {
out := (core.cons (core.car p) out)
p := (core.cdr p)
}
return out
}
fun l-select(l blk) {
| p out x |
out := nil
p := l
while (not (nil? p)) {
x := (core.car p)
if (blk x) { out := (core.cons x out) }
p := (core.cdr p)
}
return (l-reverse out)
}
v := #(1 2 3 4 5 6)
r := (l-select v (fun(x) { return (= 0 (rem x 2)) }))
chk (eqv? (core.classOf r) Cons) "select over a list answers a list"
chk (= (core.car r) 2) "with the right first element"
chk (= (core.car (core.cdr r)) 4) "and the right second"
chk (= (core.car (core.cdr (core.cdr r))) 6) "and the right third"
chk (nil? (core.cdr (core.cdr (core.cdr r)))) "and nothing more"
chk (= (core.car v) 1) "the original list is untouched"
## the type gate
bad := 0
try { core.car nil } catch (e) { bad := (+ bad 1) }
try { core.cdr nil } catch (e) { bad := (+ bad 1) }
try { core.car #[1 2] } catch (e) { bad := (+ bad 1) }
try { core.cdr "s" } catch (e) { bad := (+ bad 1) }
chk (= bad 4) "car and cdr refuse anything that is not a cons"
+133
View File
@@ -0,0 +1,133 @@
## the dic module
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
d := #{}
chk (dictionary? d) "#{} is a dictionary"
chk (= (dic.size d) 0) "a new dictionary is empty"
dic.put d "a" 1
dic.put d "b" 2
dic.put d "c" 3
chk (= (dic.size d) 3) "dic.size counts the pairs"
chk (= (dic.get d "b") 2) "dic.get finds a value"
chk (error? (dic.get d "z")) "dic.get answers with an error for a missing key"
chk (dic.has? d "b") "dic.has? is true for a present key"
chk (not (dic.has? d "z")) "dic.has? is false for a missing key"
## replacing a value must not grow the dictionary
dic.put d "b" 22
chk (= (dic.get d "b") 22) "dic.put replaces a value"
chk (= (dic.size d) 3) "replacing does not change the size"
## keys and values line up pair by pair, whatever order the buckets give
ks := (dic.keys d)
vs := (dic.values d)
chk (array? ks) "dic.keys returns an array"
chk (array? vs) "dic.values returns an array"
chk (= (core.basicSize ks) 3) "dic.keys has one entry per pair"
chk (= (core.basicSize vs) 3) "dic.values has one entry per pair"
aligned := 0
i := 0
while (< i 3) {
if (= (dic.get d (core.basicAt ks i)) (core.basicAt vs i)) { aligned := (+ aligned 1) }
i := (+ i 1)
}
chk (= aligned 3) "dic.keys and dic.values are in the same order"
chk (dic.delete d "b") "dic.delete reports a removal"
chk (not (dic.delete d "b")) "dic.delete reports nothing to remove the second time"
chk (= (dic.size d) 2) "dic.delete shrinks the dictionary"
chk (not (dic.has? d "b")) "the deleted key is gone"
chk (= (core.basicSize (dic.keys d)) 2) "dic.keys reflects the removal"
## dic.make, for choosing a bucket size up front
e := (dic.make 64)
chk (dictionary? e) "dic.make returns a dictionary"
chk (= (dic.size e) 0) "dic.make starts empty"
dic.put e 1 "one"
chk (eql? (dic.get e 1) "one") "a dic.make dictionary works"
f := (dic.make)
chk (dictionary? f) "dic.make takes no argument too"
## non-string keys
g := #{}
dic.put g 7 "seven"
dic.put g "7" "string seven"
chk (eql? (dic.get g 7) "seven") "an integer key works"
chk (eql? (dic.get g "7") "string seven") "and does not collide with the string of it"
chk (= (dic.clear d) 2) "dic.clear reports how many went"
chk (= (dic.size d) 0) "dic.clear empties the dictionary"
chk (= (core.basicSize (dic.keys d)) 0) "dic.keys is empty afterwards"
chk (= (dic.clear d) 0) "clearing an empty dictionary removes nothing"
## survives collection while holding a sizeable dictionary
h := #{}
n := 200
i := 0
while (< i n) {
dic.put h i (* i 3)
i := (+ i 1)
}
gc
bad := 0
hk := (dic.keys h)
hv := (dic.values h)
i := 0
while (< i n) {
if (not (= (core.basicAt hv i) (* (core.basicAt hk i) 3))) { bad := (+ bad 1) }
i := (+ i 1)
}
chk (= (dic.size h) n) "a 200 pair dictionary keeps its size across a collection"
chk (= bad 0) "and every key still maps to its own value"
## --- one array instead of two ---
p2 := #{}
dic.put p2 "x" 10
dic.put p2 "y" 20
ps := (dic.pairs p2)
chk (array? ps) "dic.pairs returns an array"
chk (= (core.basicSize ps) 2) "dic.pairs has one entry per pair"
chk (eqv? (core.classOf (core.basicAt ps 0)) Cons) "each entry is an association"
paired := 0
i := 0
while (< i 2) {
a := (core.basicAt ps i)
if (eqv? (dic.get p2 (core.car a)) (core.cdr a)) { paired := (+ paired 1) }
i := (+ i 1)
}
chk (= paired 2) "car and cdr of each association agree with dic.get"
## --- traversal that allocates nothing ---
chk (integer? (dic.bucketSize p2)) "dic.bucketSize answers how many slots to walk"
chk (>= (dic.bucketSize p2) (dic.size p2)) "the bucket is at least as big as the population"
n := (dic.bucketSize p2)
found := 0
i := 0
while (< i n) {
a := (dic.pairAt p2 i)
if (not (nil? a)) {
if (eqv? (dic.get p2 (core.car a)) (core.cdr a)) { found := (+ found 1) }
}
i := (+ i 1)
}
chk (= found 2) "walking the slots visits every pair exactly once"
## an empty slot answers nil rather than failing
empties := 0
i := 0
while (< i n) {
if (nil? (dic.pairAt p2 i)) { empties := (+ empties 1) }
i := (+ i 1)
}
chk (= (+ empties 2) n) "every slot is either an association or nil"
## out of range is an error, not a silent nil
oob := 0
try { dic.pairAt p2 n } catch (e) { oob := (+ oob 1) }
try { dic.pairAt p2 -1 } catch (e) { oob := (+ oob 1) }
chk (= oob 2) "dic.pairAt refuses a slot outside the bucket"
+48
View File
@@ -0,0 +1,48 @@
## system handle table: file open/read/write/close and the validation gate
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
path := "/tmp/hak-hnd-01.tmp"
## --- write ---
h := (sys.open path "w")
chk (integer? h) "sys.open for write returns a handle id"
buf := (core.basicNew ByteArray 5)
core.basicAtPut buf 0 104
core.basicAtPut buf 1 101
core.basicAtPut buf 2 108
core.basicAtPut buf 3 108
core.basicAtPut buf 4 111
chk (= (sys.write h buf) 5) "sys.write returns the byte count"
sys.close h
## --- read it back ---
h := (sys.open path "r")
rb := (core.basicNew ByteArray 16)
n := (sys.read h rb)
chk (= n 5) "sys.read returns the byte count"
chk (= (core.basicAt rb 0) 104) "first byte round-tripped"
chk (= (core.basicAt rb 4) 111) "last byte round-tripped"
chk (= (sys.read h rb) 0) "sys.read at end of file returns 0"
## --- offset and length ---
sys.close h
h := (sys.open path "r")
zb := (core.basicNew ByteArray 16)
chk (= (sys.read h zb 2 3) 3) "sys.read honours offset and length"
chk (= (core.basicAt zb 0) 0) "bytes before the offset are untouched"
chk (= (core.basicAt zb 2) 104) "data landed at the offset"
sys.close h
## --- close returns nil ---
h := (sys.open path "r")
chk (nil? (sys.close h)) "sys.close returns nil"
## --- a fresh handle reuses the freed id ---
h2 := (sys.open path "r")
chk (integer? h2) "a handle id is handed out again after a close"
sys.close h2
+36
View File
@@ -0,0 +1,36 @@
## sys.pipe and the non-blocking I/O contract
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
p := (sys.pipe)
chk (array? p) "sys.pipe returns an array"
r := (core.basicAt p 0)
w := (core.basicAt p 1)
chk (integer? r) "the read end is a handle id"
chk (integer? w) "the write end is a handle id"
chk (not (= r w)) "the two ends are distinct handles"
buf := (core.basicNew ByteArray 16)
## a pipe with nothing in it must report "would block" rather than stalling
chk (= (sys.read r buf) -1) "reading an empty pipe returns -1 (would block)"
wb := (core.basicNew ByteArray 3)
core.basicAtPut wb 0 97
core.basicAtPut wb 1 98
core.basicAtPut wb 2 99
chk (= (sys.write w wb) 3) "writing to the pipe returns the count"
n := (sys.read r buf)
chk (= n 3) "the data comes back"
chk (= (core.basicAt buf 0) 97) "byte 0 correct"
chk (= (core.basicAt buf 2) 99) "byte 2 correct"
chk (= (sys.read r buf) -1) "the pipe is empty again"
## closing the writer turns the next read into an end-of-file
sys.close w
chk (= (sys.read r buf) 0) "reading after the writer closed returns 0 (eof)"
sys.close r
+82
View File
@@ -0,0 +1,82 @@
## the async I/O model: a coprocess blocked on a handle must not stall the VM.
## the primitive never blocks - it returns -1 - and the wait happens on a
## semaphore bound to the handle, so the scheduler keeps running everyone else.
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
iosem := (sem-new)
tmo := (sem-new)
sg := (semgr-new)
semgr-add sg iosem
semgr-add sg tmo
fin := (sem-new)
## returns 1 when the handle became readable, 0 when the timeout won
fun waitin(h secs) {
| s |
sem-signal tmo secs 0
sem-signal-on-input iosem h
s := (semgr-wait sg)
sem-unsignal iosem
sem-unsignal tmo
if (eqv? s tmo) { return 0 } \
else { return 1 }
}
## --- the timeout arm: nothing is ever written ---
q := (sys.pipe)
chk (= (waitin (core.basicAt q 0) 1) 0) "a semaphore group timeout fires when no data arrives"
sys.close (core.basicAt q 0)
sys.close (core.basicAt q 1)
## --- the readable arm, with a coprocess proving the VM kept running ---
p := (sys.pipe)
r := (core.basicAt p 0)
w := (core.basicAt p 1)
ticks := 0
got := -2
fun reader() {
| buf n |
buf := (core.basicNew ByteArray 16)
while true {
n := (sys.read r buf)
if (>= n 0) {
got := n
sem-signal fin
return 0
}
if (= (waitin r 5) 0) {
got := -1
sem-signal fin
return 0
}
}
}
fun writer() {
| wb |
## yield a few times first: the reader is blocked on the semaphore by now,
## so these ticks only happen if the VM is still scheduling
while (< ticks 4) {
ticks := (+ ticks 1)
yield
}
wb := (core.basicNew ByteArray 2)
core.basicAtPut wb 0 120
core.basicAtPut wb 1 121
sys.write w wb
}
fork reader
fork writer
sem-wait fin
chk (= ticks 4) "other coprocesses ran while the reader was blocked"
chk (= got 2) "the reader woke and read the data"
sys.close r
sys.close w
+61
View File
@@ -0,0 +1,61 @@
## a raw file descriptor is not a system handle. this is the property that
## stops hak code naming a descriptor it never opened - including hak's own
## multiplexer, signal and io-thread descriptors, and anything the host
## application that embeds hak holds open.
s := (sem-new)
sem-signal-on-input s 0 ##ERROR: system handle 0
---
s := (sem-new)
sem-signal-on-input s 4 ##ERROR: system handle 4
---
## a handle id stops resolving once it is closed, so a stale id cannot reach
## a descriptor that has since been recycled
p := (sys.pipe)
r := (core.basicAt p 0)
sys.close r
sys.read r (core.basicNew ByteArray 4) ##ERROR: system handle 0
---
## a regular file is never accepted by the multiplexer: epoll refuses one
## outright, and poll() would report it permanently ready
f := (sys.open "/proc/version" "r")
s := (sem-new)
sem-signal-on-input s f ##ERROR: not of an acceptable kind
---
## the type gate: a pipe handle is not a process handle
p := (sys.pipe)
sys.pwait (core.basicAt p 0) ##ERROR: not of an acceptable kind
---
## ...and a process handle is not a stream
p := (sys.popen "true" "r")
sys.read (core.basicAt p 0) (core.basicNew ByteArray 4) ##ERROR: not of an acceptable kind
---
## an exit handle is muxable but carries no bytes
p := (sys.popen "true" "r")
sys.read (core.basicAt p 4) (core.basicNew ByteArray 4) ##ERROR: not readable
---
## a missing file is reported, not guessed at
sys.open "/nonexistent/definitely-not-here" "r" ##ERROR: open /nonexistent/definitely-not-here
---
## an unknown open mode is rejected
sys.open "/tmp/hak-hnd-5001.tmp" "q" ##ERROR: open mode
---
## an unknown popen mode likewise
sys.popen "true" "z" ##ERROR: popen mode
+85
View File
@@ -0,0 +1,85 @@
## the child supervision layer in src/proc.hak
$include "../src/proc.hak"
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
## --- children are collected in completion order, whichever finishes first ---
g := (ChildGroup:new)
chk (= (g:count) 0) "a new group watches nothing"
a := (g:spawn "sleep 3; exit 11" "r")
b := (g:spawn "sleep 1; exit 22" "r")
c := (g:spawn "sleep 2; exit 33" "r")
chk (= (g:count) 3) "spawn adds to the group"
chk (integer? (g:proc-of a)) "the record carries a process handle"
chk (integer? (g:out-of a)) "...and the requested stream"
chk (nil? (g:in-of a)) "...and nil for one not requested"
ticks := 0
fun ticker() {
while (< ticks 5) {
ticks := (+ ticks 1)
yield
}
}
fork ticker
order := (core.basicNew Array 3)
n := 0
while (< n 3) {
kid := (g:wait 25)
if (nil? kid) {
printf "ERROR: g:wait timed out\n"
n := 3
} \
else {
core.basicAtPut order n (g:status kid)
g:close kid
n := (+ n 1)
}
}
chk (= (core.basicAt order 0) 22) "the first to finish was reported first"
chk (= (core.basicAt order 1) 33) "then the second"
chk (= (core.basicAt order 2) 11) "then the third"
chk (= ticks 5) "coprocesses ran while the group waited"
chk (= (g:count) 0) "close removes a child from the group"
g:done
## --- simultaneous exits ---
## standard signals do not queue, so on the SIGCHLD path one signal can stand
## for every one of these; the group must still account for all of them.
g2 := (ChildGroup:new)
i := 0
while (< i 6) {
g2:spawn "sleep 1; exit 7" "r"
i := (+ i 1)
}
seen := 0
bad := 0
while (< seen 6) {
kid := (g2:wait 25)
if (nil? kid) {
printf "ERROR: timed out with children unaccounted for\n"
seen := 6
bad := 1
} \
else {
if (not (= (g2:status kid) 7)) { bad := 1 }
g2:close kid
seen := (+ seen 1)
}
}
chk (= bad 0) "six children exiting together were all collected"
g2:done
## --- a timeout is reported as nil, not as a hang ---
g3 := (ChildGroup:new)
g3:spawn "sleep 30" "r"
chk (= (g3:count) 1) "the group is watching the long-running child"
chk (nil? (g3:wait 1)) "g:wait returns nil when nothing finishes in time"
g3:done
chk (= (g3:count) 0) "g:done emptied a group that still held a running child"
+82
View File
@@ -0,0 +1,82 @@
## operating system signal routing
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
## --- SIGPIPE must not be fatal ---
## with the default disposition, writing to a pipe whose reader has gone kills
## the process outright and no I/O primitive can ever report the condition.
p := (sys.pipe)
r := (core.basicAt p 0)
w := (core.basicAt p 1)
sys.close r
raised := false
try {
sys.write w (core.basicNew ByteArray 4)
} catch (e) {
raised := true
}
chk raised "writing to a pipe with no reader raises instead of killing the VM"
sys.close w
## the VM is genuinely still usable afterwards, not merely still alive
q := (sys.pipe)
chk (= (sys.write (core.basicAt q 1) (core.basicNew ByteArray 2)) 2) "I/O still works after SIGPIPE"
sys.close (core.basicAt q 0)
sys.close (core.basicAt q 1)
## --- catch and uncatch are idempotent ---
chk (= (system-catch-sig 10) 10) "system-catch-sig returns the signal number"
chk (= (system-catch-sig 10) 10) "catching an already caught signal is fine"
chk (= (system-uncatch-sig 10) 10) "system-uncatch-sig returns the signal number"
chk (= (system-uncatch-sig 10) 10) "uncatching an uncaught signal is fine"
## --- a real signal reaches hak code, without stalling the coprocesses ---
## SIGCHLD is used because a child exiting is something this test can arrange
## on its own, with no outside help.
system-catch-sig 17
h := (system-get-sigfd)
s := (sem-new)
tmo := (sem-new)
sg := (semgr-new)
semgr-add sg s
semgr-add sg tmo
fin := (sem-new)
ticks := 0
signo := -1
pr := (sys.popen "sleep 1; exit 4" "r")
proc := (core.basicAt pr 0)
fun waiter() {
| w |
sem-signal tmo 20 0
sem-signal-on-input s h
w := (semgr-wait sg)
sem-unsignal s
sem-unsignal tmo
if (eqv? w tmo) { signo := -2 } \
else { signo := (system-get-sig) }
sem-signal fin
return 0
}
fun ticker() {
## the waiter is parked on the signal descriptor by now
while (< ticks 4) {
ticks := (+ ticks 1)
yield
}
}
fork waiter
fork ticker
sem-wait fin
chk (= ticks 4) "coprocesses ran while a coprocess waited on a signal"
chk (= signo 17) "the signal number came through the signal descriptor"
chk (= (sys.pwait proc) 4) "and the child's exit status is readable"
sys.pclose proc
system-uncatch-sig 17
+41
View File
@@ -0,0 +1,41 @@
## signals that cannot be caught
system-catch-sig 9 ##ERROR: 9 not routable
---
system-catch-sig 19 ##ERROR: 19 not routable
---
## signals that indicate a crash: turning one into a byte on a pipe and
## carrying on would hide the fault rather than report it
system-catch-sig 11 ##ERROR: 11 not routable
---
system-catch-sig 7 ##ERROR: 7 not routable
---
system-catch-sig 8 ##ERROR: 8 not routable
---
system-catch-sig 4 ##ERROR: 4 not routable
---
## the timer signal hak itself uses to switch processes
system-catch-sig 26 ##ERROR: 26 not routable
---
system-catch-sig 0 ##ERROR: 0 not routable
---
system-catch-sig 9999 ##ERROR: 9999 not routable
---
system-catch-sig "two" ##ERROR: number not a small integer
+100
View File
@@ -0,0 +1,100 @@
## child processes: sys.popen / pwait / pkill / pclose over the handle table
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
iosem := (sem-new)
tmo := (sem-new)
sg := (semgr-new)
semgr-add sg iosem
semgr-add sg tmo
fun waitin(h secs) {
| s |
sem-signal tmo secs 0
sem-signal-on-input iosem h
s := (semgr-wait sg)
sem-unsignal iosem
sem-unsignal tmo
if (eqv? s tmo) { return 0 } else { return 1 }
}
## read until data arrives, retrying on -1
fun rd(h buf) {
| n |
while true {
n := (sys.read h buf)
if (>= n 0) { return n }
if (= (waitin h 5) 0) { return -1 }
}
}
## reap without blocking the VM, waiting on a short timer between looks
fun reap(proc) {
| n k |
k := 0
while (< k 300) {
n := (sys.pwait proc)
if (not (= n 256)) { return n }
sem-signal tmo 0 20000000
semgr-wait sg
sem-unsignal tmo
k := (+ k 1)
}
return 256
}
buf := (core.basicNew ByteArray 64)
## --- stdout and the exit status ---
p := (sys.popen "printf abc; exit 3" "r")
chk (array? p) "sys.popen returns a handle array"
chk (integer? (core.basicAt p 0)) "slot 0 is the process handle"
chk (nil? (core.basicAt p 1)) "stdin is nil when 'w' was not requested"
chk (integer? (core.basicAt p 2)) "stdout is a handle when 'r' was requested"
chk (nil? (core.basicAt p 3)) "stderr is nil when 'e' was not requested"
chk (= (rd (core.basicAt p 2) buf) 3) "the child's stdout is readable"
chk (= (reap (core.basicAt p 0)) 3) "the exit status comes through"
sys.pclose (core.basicAt p 0)
## --- bidirectional: write to stdin, read stdout ---
p := (sys.popen "tr a-z A-Z" "rw")
inh := (core.basicAt p 1)
outh := (core.basicAt p 2)
chk (integer? inh) "stdin is a handle when 'w' was requested"
wb := (core.basicNew ByteArray 3)
core.basicAtPut wb 0 120
core.basicAtPut wb 1 121
core.basicAtPut wb 2 122
chk (= (sys.write inh wb) 3) "writing to the child's stdin works"
## closing the child's stdin must really send eof, or tr never flushes
sys.close inh
chk (= (rd outh buf) 3) "the child answered after stdin was closed"
chk (= (core.basicAt buf 0) 88) "the child transformed the data (x -> X)"
sys.pclose (core.basicAt p 0)
## --- stderr kept separate from stdout ---
p := (sys.popen "printf OUT; printf ERRR 1>&2" "re")
chk (= (rd (core.basicAt p 2) buf) 3) "stdout has its own stream"
chk (= (rd (core.basicAt p 3) buf) 4) "stderr has its own stream"
sys.pclose (core.basicAt p 0)
## --- a signalled child reports 256 + signo ---
p := (sys.popen "kill -TERM $$" "r")
chk (= (reap (core.basicAt p 0)) 271) "a signalled child reports 256 + SIGTERM"
sys.pclose (core.basicAt p 0)
## --- sys.pkill ---
p := (sys.popen "sleep 60" "r")
chk (= (sys.pwait (core.basicAt p 0)) 256) "a running child reports 256"
sys.pkill (core.basicAt p 0)
chk (= (reap (core.basicAt p 0)) 265) "after pkill the child reports 256 + SIGKILL"
sys.pclose (core.basicAt p 0)
## --- pclose tears the whole group down ---
p := (sys.popen "sleep 60" "r")
outh := (core.basicAt p 2)
sys.pclose (core.basicAt p 0)
chk true "pclose released the group"
+64
View File
@@ -0,0 +1,64 @@
## the exit handle: waiting for a child to terminate with no polling at all.
##
## sys.popen's slot 4 is a handle that becomes readable when the child exits,
## so it is waited on exactly like a pipe. Where the platform has no such
## facility the slot is nil and this test reports that it was skipped.
fun chk(ok msg) {
if ok { printf "OK: %s\n" msg } \
else { printf "ERROR: %s\n" msg }
}
iosem := (sem-new)
tmo := (sem-new)
sg := (semgr-new)
semgr-add sg iosem
semgr-add sg tmo
fin := (sem-new)
p := (sys.popen "sleep 1; exit 5" "r")
proc := (core.basicAt p 0)
xh := (core.basicAt p 4)
if (nil? xh) {
printf "OK: no exit handle on this platform - skipped\n"
sys.pclose proc
} \
else {
ticks := 0
status := -2
fun waiter() {
| s |
sem-signal tmo 20 0
sem-signal-on-input iosem xh
s := (semgr-wait sg)
sem-unsignal iosem
sem-unsignal tmo
if (eqv? s tmo) { status := -1 } \
else { status := (sys.pwait proc) }
sem-signal fin
return 0
}
fun ticker() {
## the waiter is parked on the exit handle by now; these ticks only
## happen if the VM went on scheduling instead of blocking
while (< ticks 4) {
ticks := (+ ticks 1)
yield
}
}
chk (integer? xh) "sys.popen hands back an exit handle"
fork waiter
fork ticker
sem-wait fin
chk (= ticks 4) "other coprocesses ran while the child was alive"
chk (= status 5) "the exit handle woke the waiter and the status was read"
## it belongs to the process handle, so pclose takes it away
sys.pclose proc
chk true "pclose released the exit handle with the group"
}
+251
View File
@@ -0,0 +1,251 @@
/* multiple hak instances in one process.
*
* lib/std.c keeps process-wide state that no single hak instance owns: the
* g_hak chain that links every live instance, and g_sig_state[] holding the
* signal dispositions saved before hak installed its own. An embedder may
* hold several instances at once, so that state is shared and is guarded by
* a global lock rather than by any per-instance one.
*
* Run with no arguments, as make check does, this covers the sequential case,
* which is deterministic and is what regressions are caught by. Pass a thread
* count for the concurrent stress mode:
*
* ./t-002 8
*
* The stress mode is kept out of make check because its runtime scales with
* the thread count and it is timing dependent, so it is a poor gate even
* though it passes. Reach for it when touching chain(), unchain() or the
* signal handler bookkeeping. */
#include <hak.h>
#include "tap.h"
#include <signal.h>
#include <stdlib.h>
#if !defined(__DOS__) && !defined(EMSCRIPTEN) && defined(HAVE_PTHREAD) && defined(HAVE_STRERROR_R)
# define USE_THREAD
# include <pthread.h>
#endif
#define NINST 4
#if defined(HAVE_SIGACTION)
static int disposition_of (int sig, void** handler)
{
struct sigaction sa;
if (sigaction(sig, (struct sigaction*)0, &sa) <= -1) return -1;
*handler = (sa.sa_flags & SA_SIGINFO)? (void*)sa.sa_sigaction: (void*)sa.sa_handler;
return 0;
}
#else
static int disposition_of (int sig, void** handler) { *handler = (void*)0; return -1; }
#endif
/* open NINST instances, then close them in an order that leaves the one being
* unchained with a live neighbour on each side, which the plain open-then-close
* pattern never produces. */
static void interleaved (void)
{
hak_t* inst[NINST];
int i;
static const int order[NINST] = { 1, 3, 0, 2 }; /* middles first */
for (i = 0; i < NINST; i++)
{
inst[i] = hak_openstd(0, HAK_NULL);
OK (inst[i] != HAK_NULL, "instantiation with siblings already open");
}
for (i = 0; i < NINST; i++)
{
if (inst[order[i]]) hak_close(inst[order[i]]);
inst[order[i]] = HAK_NULL;
}
/* the chain must still be usable once emptied out of order */
inst[0] = hak_openstd(0, HAK_NULL);
OK (inst[0] != HAK_NULL, "instantiation after out-of-order teardown");
if (inst[0]) hak_close(inst[0]);
}
/* a surviving instance must still work after its neighbours are gone */
static void survivor (void)
{
hak_t* keep;
hak_t* tmp;
int i, n;
keep = hak_openstd(0, HAK_NULL);
OK (keep != HAK_NULL, "instantiation of the survivor");
if (!keep) return;
for (i = 0; i < NINST; i++)
{
tmp = hak_openstd(0, HAK_NULL);
if (tmp) hak_close(tmp);
}
n = hak_ignite(keep, 0);
OK (n == 0, "survivor ignites after its neighbours are closed");
n = hak_addbuiltinprims(keep);
OK (n == 0, "survivor registers builtin primitives");
hak_close(keep);
}
#if defined(USE_THREAD)
/* Concurrent open/close, hammering the global lock that guards g_hak and
* g_sig_state. Removing either GLOBAL_LOCK() in lib/std.c makes this fault
* within a couple of runs at eight threads. */
#define ROUNDS 150
static void* worker (void* arg)
{
int i;
for (i = 0; i < ROUNDS; i++)
{
hak_t* h = hak_openstd(0, HAK_NULL);
if (h) hak_close(h);
}
return (void*)0;
}
static int stress (int nthr)
{
pthread_t t[64];
int i;
if (nthr < 1) nthr = 1;
if (nthr > 64) nthr = 64;
for (i = 0; i < nthr; i++)
{
if (pthread_create(&t[i], (pthread_attr_t*)0, worker, (void*)0) != 0) break;
}
nthr = i;
for (i = 0; i < nthr; i++) pthread_join(t[i], (void**)0);
printf("# %d threads x %d instances completed\n", nthr, ROUNDS);
return 0;
}
#else
static int stress (int nthr)
{
printf("# built without thread support - stress mode unavailable\n");
return 0;
}
#endif
/* Signal dispositions are process-wide, so the library must not touch them
* behind the host's back. Neutralising SIGPIPE is the application's call -
* bin/hak.c makes it - and a program that would rather die quietly on a broken
* pipe is entitled to that. Opening and closing an instance must therefore
* leave every disposition exactly as it found it. */
static const int WATCHED[] = {
#if defined(SIGPIPE)
SIGPIPE,
#endif
SIGINT, SIGTERM
};
#define NWATCHED ((int)(sizeof(WATCHED) / sizeof(WATCHED[0])))
static void host_signals_untouched (void)
{
void* before[NWATCHED];
void* during[NWATCHED];
void* after[NWATCHED];
hak_t* h;
int i, probed = 0;
for (i = 0; i < NWATCHED; i++)
{
if (disposition_of(WATCHED[i], &before[i]) <= -1) return; /* no sigaction */
probed = 1;
}
if (!probed) return;
h = hak_openstd(0, HAK_NULL);
OK (h != HAK_NULL, "instantiation failure");
if (!h) return;
for (i = 0; i < NWATCHED; i++) disposition_of(WATCHED[i], &during[i]);
hak_close(h);
for (i = 0; i < NWATCHED; i++) disposition_of(WATCHED[i], &after[i]);
for (i = 0; i < NWATCHED; i++)
{
OK (during[i] == before[i], "an open instance leaves the host disposition alone");
OK (after[i] == before[i], "a closed instance leaves the host disposition alone");
}
}
/* hak_catch_termreq() is the sanctioned way to hand hak the termination
* signals, and hak_uncatch_termreq() must put the host's dispositions back
* exactly as it found them. Nothing in the tree calls either, so this is the
* only exercise they get - and the only coverage of the restore path in
* unset_signal_handler(). */
static const int TERMREQ[] = {
SIGTERM,
SIGINT
#if defined(SIGHUP)
, SIGHUP
#endif
};
#define NTERMREQ ((int)(sizeof(TERMREQ) / sizeof(TERMREQ[0])))
static void termreq_round_trips (void)
{
void* before[NTERMREQ];
void* during[NTERMREQ];
void* after[NTERMREQ];
int i;
for (i = 0; i < NTERMREQ; i++)
{
if (disposition_of(TERMREQ[i], &before[i]) <= -1) return; /* no sigaction */
}
#if defined(SIGPIPE)
/* SIGPIPE is deliberately NOT in the set above. It is not a termination
* request - it is the opposite, a measure against being terminated - and
* neutralising it is the application's call, not the library's, because
* the disposition is process-wide. bin/hak.c makes that call for itself.
* So termreq must leave it exactly alone. */
void* pipe_before;
void* pipe_during;
int pipe_probed = (disposition_of(SIGPIPE, &pipe_before) >= 0);
#endif
hak_catch_termreq();
for (i = 0; i < NTERMREQ; i++) disposition_of(TERMREQ[i], &during[i]);
#if defined(SIGPIPE)
if (pipe_probed) disposition_of(SIGPIPE, &pipe_during);
#endif
hak_uncatch_termreq();
for (i = 0; i < NTERMREQ; i++) disposition_of(TERMREQ[i], &after[i]);
for (i = 0; i < NTERMREQ; i++)
{
OK (during[i] != before[i], "hak_catch_termreq installs a handler");
OK (after[i] == before[i], "hak_uncatch_termreq restores the original");
}
#if defined(SIGPIPE)
if (pipe_probed) OK (pipe_during == pipe_before, "hak_catch_termreq leaves SIGPIPE to the application");
#endif
}
int main (int argc, char* argv[])
{
if (argc > 1) return stress(atoi(argv[1]));
no_plan();
host_signals_untouched();
termreq_round_trips();
interleaved();
survivor();
return exit_status();
}
+64
View File
@@ -0,0 +1,64 @@
/* hak_raisetick() and hak_rcvtick() - the per-instance tick counter pair.
*
* The tick is published as a counter rather than a flag so that the raiser and
* the scheduler never write the same field: hak_raisetick() only increments
* hak->tick, and the scheduler only writes hak->last_tick, copying the value
* it observed. A raise landing between the scheduler's test and its update is
* therefore still pending afterwards rather than being lost, and no atomic
* operation is needed to get that.
*
* This checks that contract directly on the fields, which is white-box but is
* the only way to reach hak_raisetick() - it has no script-level binding, and
* driving it through a running VM cannot isolate it from the global tick.
*
* What this does NOT cover is the scheduler acting on a pending tick.
* t/tick-01.hak covers that for the global half. */
#include <hak.h>
#include "tap.h"
int main (int argc, char* argv[])
{
hak_t* hak;
no_plan();
hak = hak_openstd(0, HAK_NULL);
OK (hak != HAK_NULL, "instantiation");
if (!hak) return exit_status();
/* enabling reception seeds the watermark, so an instance does not act on
* ticks raised before it was listening */
hak_raisetick(hak);
hak_raisetick(hak);
hak_rcvtick(hak, 1);
OK (hak->last_tick == hak->tick, "enabling reception discards earlier ticks");
hak_raisetick(hak);
OK (hak->last_tick != hak->tick, "hak_raisetick leaves a tick pending");
/* a second raise while one is already pending must not cancel it - the
* flag version could lose one here, the counter cannot */
hak_raisetick(hak);
OK (hak->last_tick != hak->tick, "a second raise keeps the tick pending");
/* the scheduler consumes a tick by copying, never by clearing */
hak->last_tick = hak->tick;
OK (hak->last_tick == hak->tick, "copying the observed value consumes it");
hak_raisetick(hak);
OK (hak->last_tick != hak->tick, "and a later raise is seen again");
/* disabling reception must not lose a pending tick either - rcv_tick
* gates whether ticks are acted on, not whether they are recorded */
hak_rcvtick(hak, 0);
OK (hak->last_tick != hak->tick, "disabling reception leaves the tick recorded");
OK (hak->rcv_tick == 0, "reception is off");
hak_rcvtick(hak, 1);
OK (hak->last_tick == hak->tick, "re-enabling reception reseeds the watermark");
OK (hak->rcv_tick == 1, "reception is on");
hak_close(hak);
return exit_status();
}
+45
View File
@@ -0,0 +1,45 @@
## preemptive process switching driven by the global tick counter
##
## The ticker - a signal handler on POSIX, a thread on _WIN32/__OS2__, a timer
## interrupt on __DOS__ - only increments a global counter. Each instance
## notices the change itself in switch_process_if_needed() and switches to the
## next runnable process. Nothing else can interrupt a process that does not
## yield, so a process yielding the CPU without ever asking to is proof that
## the whole path works: hak_start_ticker, hak_rcvtick, the counter, and the
## comparison against last_tick.
fun chk(ok msg) {
if ok {
printf "OK: %s\n" msg
} else {
printf "ERROR: %s\n" msg
}
}
flag := 0
fun setter() { flag := 1 }
## setter is runnable from here on, but it cannot run while this process holds
## the CPU - and this process never calls yield.
p := (fork setter)
i := 0
while (< i 3000000) {
if (== flag 1) { break }
i := (+ i 1)
}
## Without preemption the loop runs to its bound with flag still 0. With the
## bound at 3000000 that takes well under a second, so a broken tick path
## fails quickly rather than hanging.
chk (== flag 1) "a process that never yields is preempted so another can run"
## Guards against this test going vacuous if fork() ever switches to the new
## process immediately: then setter would have run before the loop started and
## i would be ~0, proving nothing about preemption. One tick is 20ms, which is
## a great many iterations.
chk (> i 1000) "the switch came from the ticker, not from an eager fork"
## The preempted process must be resumed, not abandoned - reaching here at all
## means the scheduler came back to it.
chk (< i 3000000) "the preempted process resumes and runs to completion"