@@ -21,7 +21,7 @@
ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} -I m4
-SUBDIRS = lib man tools scripts benchmarks
+SUBDIRS = lib man tools scripts benchmarks fuzz
if BUILD_TESTS
SUBDIRS += tests
@@ -283,6 +283,7 @@ AC_CONFIG_FILES([
docs/reference/Makefile
docs/reference/intel-gpu-tools/Makefile
docs/reference/intel-gpu-tools/version.xml
+ fuzz/Makefile
lib/Makefile
lib/tests/Makefile
man/Makefile
new file mode 100644
@@ -0,0 +1 @@
+gemscript
new file mode 100644
@@ -0,0 +1,40 @@
+include Makefile.sources
+
+noinst_LTLIBRARIES = libscript.la
+
+libscript_la_SOURCES = \
+ file.c \
+ hash.c \
+ interpreter.c \
+ objects.c \
+ operators.c \
+ scanner.c \
+ script.h \
+ stack.c \
+ $(NULL)
+libscript_la_LIBADD = -lm
+
+noinst_PROGRAMS = \
+ gemscript \
+ $(NULL)
+
+AM_CFLAGS = $(DRM_CFLAGS) $(CWARNFLAGS) $(DEBUG_CFLAGS)\
+ -I$(srcdir)/.. \
+ -I$(srcdir)/../lib \
+ -include "$(srcdir)/../lib/check-ndebug.h" \
+ -DIGT_SRCDIR=\""$(abs_srcdir)"\" \
+ -DIGT_DATADIR=\""$(pkgdatadir)"\" \
+ $(LIBUNWIND_CFLAGS) $(WERROR_CFLAGS) \
+ $(LIBUDEV_CFLAGS) $(CAIRO_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = -Wl,--as-needed
+
+gemscript_SOURCES = \
+ gemscript.c \
+ $(NULL)
+gemscript_LDADD = libscript.la ../lib/libintel_tools.la -lm
+
+if BUILD_TESTS
+endif
+
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
+The intent here is to automate test developement by fuzzing the inputs to
+the DRM ioctls. We use a domain specific language (i.e. scripts) to generate
+a template for tests that we can feed into coverage driven fuzzers like
+american-fuzzy loop (afl-fuzzer).
+
+afl-fuzz -x ./keywords.gem -i ./scripts/ -o afl -- ./gemscript
new file mode 100644
@@ -0,0 +1,630 @@
+#include <stdio.h>
+#include <limits.h> /* INT_MAX */
+#include <string.h>
+#include <zlib.h>
+
+#include "script.h"
+
+#define CHUNK_SIZE 32768
+
+#define OWN_STREAM 0x1
+
+static inline obj_t new_file(struct file *file, struct script *ctx)
+{
+ return new_heap_object(&file->base, ctx, OBJ_TYPE_FILE);
+}
+
+obj_t file_new(struct script *ctx, const char *path, const char *mode)
+{
+ struct file *file = script_slab_alloc (ctx, sizeof (struct file));
+
+ file->data = NULL;
+ file->type = STDIO;
+ file->flags = OWN_STREAM;
+ file->src = fopen (path, mode);
+ if (file->src == NULL) {
+ script_slab_free (ctx, file, sizeof (struct file));
+ longjmp(ctx->error, -ENOENT);
+ }
+
+ file->data = script_alloc(ctx, 1, CHUNK_SIZE);
+ file->bp = file->data;
+ file->rem = 0;
+
+ return new_file(file, ctx);
+}
+
+obj_t file_new_for_stream (struct script *ctx, FILE *stream)
+{
+ struct file *file = script_slab_alloc (ctx, sizeof (struct file));
+
+ file->data = NULL;
+ file->type = STDIO;
+ file->flags = 0;
+ file->src = stream;
+ if (file->src == NULL) {
+ script_slab_free (ctx, file, sizeof (struct file));
+ longjmp(ctx->error, -ENOENT);
+ }
+
+ file->data = script_alloc(ctx, 1, CHUNK_SIZE);
+ file->bp = file->data;
+ file->rem = 0;
+
+ return new_file(file, ctx);
+}
+
+obj_t file_new_for_bytes(struct script *ctx,
+ const char *bytes, unsigned int length)
+{
+ struct file *file = script_slab_alloc (ctx, sizeof (struct file));
+
+ file->type = BYTES;
+ file->src = (uint8_t *) bytes;
+ file->data = (uint8_t *) bytes;
+ file->bp = (uint8_t *) bytes;
+ file->rem = length;
+
+ return new_file(file, ctx);
+}
+
+obj_t
+file_new_from_string(struct script *ctx, struct string *src)
+{
+ struct file *file = script_slab_alloc (ctx, sizeof (struct file));
+
+ if (src->deflate) {
+ uLongf len = src->deflate;
+ obj_t tmp_obj;
+
+ tmp_obj = string_new (ctx, NULL, src->deflate);
+ switch (src->method) {
+ case NONE:
+ default:
+ longjmp(ctx->error, -ENOMEM);
+ break;
+
+ case ZLIB:
+#if HAVE_ZLIB
+ if (uncompress ((Bytef *) tmp_obj.string->string, &len,
+ (Bytef *) src->string, src->len) != Z_OK)
+#endif
+ longjmp(ctx->error, -ENOMEM);
+ break;
+ case LZO:
+#if HAVE_LZO
+ if (lzo2a_decompress ((lzo_bytep) src->string, src->len,
+ (lzo_bytep) tmp_obj.string->string, &len,
+ NULL))
+#endif
+ longjmp(ctx->error, -ENOMEM);
+ break;
+ }
+
+ file->src = tmp_obj.string;
+ file->data = tmp_obj.string->string;
+ file->rem = tmp_obj.string->len;
+ } else {
+ file->src = src; src->base.ref++;
+ file->data = src->string;
+ file->rem = src->len;
+ }
+ file->type = BYTES;
+ file->bp = file->data;
+
+ return new_file(file, ctx);
+}
+
+static obj_t
+file_new_filter(struct script *ctx,
+ obj_t src,
+ const struct file_filter_funcs *funcs,
+ void *data)
+{
+ struct file *file = script_slab_alloc (ctx, sizeof (struct file));
+
+ file->type = FILTER;
+ file->data = data;
+ file->filter = funcs;
+ file->src = obj_as_file(ctx, src).file;
+
+ return new_file(file, ctx);
+}
+
+
+#if 0
+obj_t file_new_from_stream(struct script *ctx, FILE *file);
+obj_t file_new_from_procedure(struct script *ctx, obj_t src)
+#endif
+
+typedef struct _ascii85_decode_data {
+ uint8_t buf[CHUNK_SIZE];
+ uint8_t *bp;
+ short bytes_available;
+ short eod;
+} _ascii85_decode_data_t;
+
+static int _getc_skip_whitespace (struct file *src)
+{
+ int c;
+
+ do switch ((c = file_getc (src))) {
+ case 0x0:
+ case 0x9:
+ case 0xa:
+ case 0xc:
+ case 0xd:
+ case 0x20:
+ continue;
+
+ default:
+ return c;
+ } while (true);
+
+ return c;
+}
+
+static void _ascii85_decode (struct file *file)
+{
+ _ascii85_decode_data_t *data = file->data;
+ unsigned int n;
+
+ if (data->eod)
+ return;
+
+ data->bp = data->buf;
+
+ n = 0;
+ do {
+ unsigned int v = _getc_skip_whitespace (file->src);
+ if (v == 'z') {
+ data->buf[n+0] = 0;
+ data->buf[n+1] = 0;
+ data->buf[n+2] = 0;
+ data->buf[n+3] = 0;
+ } else if (v == '~') {
+ _getc_skip_whitespace (file->src); /* == '>' || IO_ERROR */
+ data->eod = true;
+ break;
+ } else if (v < '!' || v > 'u') {
+ /* IO_ERROR */
+ data->eod = true;
+ break;
+ } else {
+ unsigned int i;
+
+ v -= '!';
+ for (i = 1; i < 5; i++) {
+ int c = _getc_skip_whitespace (file->src);
+ if (c == '~') { /* short tuple */
+ _getc_skip_whitespace (file->src); /* == '>' || IO_ERROR */
+ data->eod = true;
+ switch (i) {
+ case 0:
+ case 1:
+ /* IO_ERROR */
+ break;
+ case 2:
+ v = v * (85*85*85) + 85*85*85 -1;
+ goto odd1;
+ case 3:
+ v = v * (85*85) + 85*85 -1;
+ goto odd2;
+ case 4:
+ v = v * 85 + 84;
+ data->buf[n+2] = v >> 8 & 0xff;
+odd2:
+ data->buf[n+1] = v >> 16 & 0xff;
+odd1:
+ data->buf[n+0] = v >> 24 & 0xff;
+ data->bytes_available = n + i - 1;
+ return;
+ }
+ break;
+ }
+ v = 85*v + c-'!';
+ }
+
+ data->buf[n+0] = v >> 24 & 0xff;
+ data->buf[n+1] = v >> 16 & 0xff;
+ data->buf[n+2] = v >> 8 & 0xff;
+ data->buf[n+3] = v >> 0 & 0xff;
+ }
+ n += 4;
+ } while (n < sizeof (data->buf) && !data->eod);
+
+ data->bytes_available = n;
+}
+
+static int _ascii85_decode_getc (struct file *file)
+{
+ _ascii85_decode_data_t *data = file->data;
+
+ if (data->bytes_available == 0) {
+ _ascii85_decode (file);
+
+ if (data->bytes_available == 0)
+ return EOF;
+ }
+
+ data->bytes_available--;
+ return *data->bp++;
+}
+
+static void _ascii85_decode_putc (struct file *file, int c)
+{
+ _ascii85_decode_data_t *data = file->data;
+ data->bytes_available++;
+ data->bp--;
+}
+
+static int _ascii85_decode_read (struct file *file, uint8_t *buf, int len)
+{
+ _ascii85_decode_data_t *data = file->data;
+
+ if (data->bytes_available == 0) {
+ _ascii85_decode (file);
+
+ if (data->bytes_available == 0)
+ return 0;
+ }
+
+ if (len > data->bytes_available)
+ len = data->bytes_available;
+ memcpy (buf, data->bp, len);
+ data->bp += len;
+ data->bytes_available -= len;
+ return len;
+}
+
+obj_t file_new_ascii85_decode (struct script *ctx,
+ struct dictionary *dict,
+ obj_t src)
+{
+ static const struct file_filter_funcs funcs = {
+ _ascii85_decode_getc,
+ _ascii85_decode_putc,
+ _ascii85_decode_read,
+ script_free,
+ };
+ _ascii85_decode_data_t *data;
+
+ data = script_alloc0 (ctx, 1, sizeof (_ascii85_decode_data_t));
+ return file_new_filter (ctx, src, &funcs, data);
+}
+
+#if HAVE_ZLIB
+#include <zlib.h>
+
+typedef struct _deflate_decode_data {
+ z_stream zlib_stream;
+
+ uint8_t in[CHUNK_SIZE];
+ uint8_t out[CHUNK_SIZE];
+
+ int bytes_available;
+ uint8_t *bp;
+} _deflate_decode_data_t;
+
+static void _deflate_decode (struct file *file)
+{
+ _deflate_decode_data_t *data = file->data;
+ uint8_t *bp;
+ int len;
+
+ data->zlib_stream.next_out = data->out;
+ data->zlib_stream.avail_out = sizeof (data->out);
+
+ bp = data->in;
+ len = sizeof (data->in);
+ if (data->zlib_stream.avail_in) {
+ memmove (data->in,
+ data->zlib_stream.next_in,
+ data->zlib_stream.avail_in);
+ len -= data->zlib_stream.avail_in;
+ bp += data->zlib_stream.avail_in;
+ }
+
+ len = file_read (file->src, bp, len);
+
+ data->zlib_stream.next_in = data->in;
+ data->zlib_stream.avail_in += len;
+
+ inflate (&data->zlib_stream, len == 0 ? Z_FINISH : Z_NO_FLUSH);
+
+ data->bytes_available = data->zlib_stream.next_out - data->out;
+ data->bp = data->out;
+}
+
+static int _deflate_decode_getc (struct file *file)
+{
+ _deflate_decode_data_t *data = file->data;
+
+ if (data->bytes_available == 0) {
+ _deflate_decode (file);
+
+ if (data->bytes_available == 0)
+ return EOF;
+ }
+
+ data->bytes_available--;
+ return *data->bp++;
+}
+
+static void _deflate_decode_putc (struct file *file, int c)
+{
+ _deflate_decode_data_t *data = file->data;
+ data->bytes_available++;
+ data->bp--;
+}
+
+static int _deflate_decode_read (struct file *file, uint8_t *buf, int len)
+{
+ _deflate_decode_data_t *data = file->data;
+
+ if (data->bytes_available == 0) {
+ _deflate_decode (file);
+
+ if (data->bytes_available == 0)
+ return 0;
+ }
+
+ if (len > (int) data->bytes_available)
+ len = data->bytes_available;
+ memcpy (buf, data->bp, len);
+ data->bp += len;
+ data->bytes_available -= len;
+ return len;
+}
+
+static void _deflate_destroy (struct script *ctx, void *closure)
+{
+ _deflate_decode_data_t *data;
+
+ data = closure;
+
+ inflateEnd (&data->zlib_stream);
+
+ script_free (ctx, data);
+}
+
+obj_t file_new_deflate_decode(struct script *ctx,
+ obj_t obj,
+ struct dictionary *dict)
+{
+ static const struct file_filter_funcs funcs = {
+ _deflate_decode_getc,
+ _deflate_decode_putc,
+ _deflate_decode_read,
+ _deflate_destroy,
+ };
+ _deflate_decode_data_t *data;
+
+ data = script_alloc (ctx, 1, sizeof (_deflate_decode_data_t));
+ data->zlib_stream.zalloc = Z_NULL;
+ data->zlib_stream.zfree = Z_NULL;
+ data->zlib_stream.opaque = Z_NULL;
+ data->zlib_stream.next_in = data->in;
+ data->zlib_stream.avail_in = 0;
+ data->zlib_stream.next_out = data->out;
+ data->zlib_stream.avail_out = sizeof (data->out);
+ data->bytes_available = 0;
+
+ if (inflateInit(&data->zlib_stream) != Z_OK) {
+ script_free(ctx, data);
+ longjmp(ctx->error, -ENOMEM);
+ }
+
+ return file_new_filter(ctx, src, &funcs, data);
+}
+#endif
+
+void file_execute (struct file *obj)
+{
+ scan_file(obj);
+}
+
+int file_getc (struct file *file)
+{
+ int c;
+
+ if (!file->src)
+ return EOF;
+
+ switch (file->type) {
+ case STDIO:
+ if (file->rem) {
+ c = *file->bp++;
+ file->rem--;
+ } else {
+ file->rem = fread (file->bp = file->data, 1, CHUNK_SIZE, file->src);
+ case BYTES:
+ if (file->rem) {
+ c = *file->bp++;
+ file->rem--;
+ } else
+ c = EOF;
+ }
+ break;
+
+ case PROCEDURE:
+ c = EOF;
+ break;
+
+ case FILTER:
+ c = file->filter->filter_getc (file);
+ break;
+
+ default:
+ c = EOF;
+ break;
+ }
+
+ return c;
+}
+
+int file_read (struct file *file, void *buf, int len)
+{
+ int ret;
+
+ if (!file->src)
+ longjmp(file->base.ctx->error, -EINVAL);
+
+ switch (file->type) {
+ case STDIO:
+ if (file->rem > 0) {
+ ret = len;
+ if (file->rem < ret)
+ ret = file->rem;
+ memcpy (buf, file->bp, ret);
+ file->bp += ret;
+ file->rem -= ret;
+ } else
+ ret = fread (buf, 1, len, file->src);
+ break;
+
+ case BYTES:
+ if (file->rem > 0) {
+ ret = len;
+ if (file->rem < ret)
+ ret = file->rem;
+ memcpy (buf, file->bp, ret);
+ file->bp += ret;
+ file->rem -= ret;
+ } else
+ ret = 0;
+ break;
+
+ case PROCEDURE:
+ ret = 0;
+ break;
+
+ case FILTER:
+ ret = file->filter->filter_read (file, buf, len);
+ break;
+
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+void file_putc (struct file *file, int c)
+{
+ if (file->src == NULL)
+ return;
+
+ switch ((int) file->type) {
+ case STDIO:
+ case BYTES:
+ file->bp--;
+ file->rem++;
+ break;
+ case FILTER:
+ file->filter->filter_putc (file, c);
+ break;
+ default:
+ break;
+ }
+}
+
+void file_flush (struct file *file)
+{
+ if (file->src == NULL)
+ return;
+
+ switch ((int) file->type) {
+ case FILTER: /* need to eat EOD */
+ while (file_getc (file) != EOF)
+ ;
+ break;
+ default:
+ break;
+ }
+}
+
+void file_close(struct file *file)
+{
+ if (file->src == NULL)
+ return;
+
+ switch (file->type) {
+ case STDIO:
+ if (file->flags & OWN_STREAM)
+ fclose (file->src);
+ break;
+ case BYTES:
+ if (file->src != file->data) {
+ struct string *src = file->src;
+ if (src != NULL && --src->base.ref == 0)
+ string_free(src);
+ }
+ break;
+ case FILTER:
+ {
+ struct file *src = file->src;
+ if (src != NULL && --src->base.ref == 0)
+ file_free(src);
+ }
+ break;
+ case PROCEDURE:
+ default:
+ break;
+ }
+ file->src = NULL;
+}
+
+void file_free(struct file *file)
+{
+ file_flush(file);
+ /* XXX putback */
+ file_close(file);
+
+ switch (file->type) {
+ case BYTES:
+ break;
+ case PROCEDURE:
+ break;
+ case STDIO:
+ script_free (file->base.ctx, file->data);
+ break;
+ case FILTER:
+ file->filter->filter_destroy (file->base.ctx, file->data);
+ break;
+ default:
+ break;
+ }
+
+ script_slab_free(file->base.ctx, file, sizeof (struct file));
+}
+
+obj_t file_as_string(struct file *file)
+{
+ char *bytes;
+ unsigned int len;
+ unsigned int allocated;
+
+ allocated = 16384;
+ bytes = script_alloc(file->base.ctx, 1, allocated);
+
+ len = 0;
+ do {
+ int ret;
+
+ ret = file_read(file, bytes + len, allocated - len);
+ if (ret == 0)
+ break;
+
+ len += ret;
+ if (len + 1 > allocated / 2) {
+ bytes = script_realloc(file->base.ctx, bytes,
+ allocated, 2);
+ allocated *= 2;
+ }
+ } while (true);
+
+ bytes[len] = '\0'; /* better safe than sorry! */
+ return string_new_from_bytes(file->base.ctx, bytes, len);
+}
new file mode 100644
@@ -0,0 +1,1088 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/shm.h>
+
+#include "script.h"
+
+#include "igt.h"
+
+#include "ioctl_wrappers.h"
+#include "igt_kcov.h"
+
+#define check(CNT) ostack_check(ctx, CNT)
+#define pop(CNT) ostack_pop(ctx, (CNT))
+#define push(OBJ) ostack_push(ctx, (OBJ))
+
+#define ERR(expr) do if ((expr)) longjmp(ctx->error, -EINVAL); while(0)
+
+#define gem_ctx(G) ((G)->base.heap.ctx)
+#define gem_error(G, E) longjmp(gem_ctx(G)->error, E)
+
+#define LOCAL_I915_EXEC_NO_RELOC (1<<11)
+#define LOCAL_I915_EXEC_HANDLE_LUT (1<<12)
+
+struct gem_batch;
+
+struct private {
+ struct {
+ name_t engine, engines;
+ name_t context;
+ name_t width;
+ name_t height;
+ name_t pitch;
+ name_t tiling;
+ name_t pinned;
+ name_t size;
+ name_t caching;
+ } name;
+};
+
+static struct dictionary *
+ostack_get_dictionary(struct script *ctx, unsigned int i)
+{
+ obj_t obj = ostack_get(ctx, i);
+
+ ERR((obj_type(obj) != OBJ_TYPE_DICTIONARY));
+
+ return obj.dictionary;
+}
+
+static struct string *
+ostack_get_string(struct script *ctx, unsigned int i)
+{
+ obj_t obj = ostack_get(ctx, i);
+
+ /* XXX to string */
+ ERR((obj_type(obj) != OBJ_TYPE_STRING));
+
+ return obj.string;
+}
+
+static long ostack_get_integer(struct script *ctx, unsigned int i)
+{
+ obj_t obj = ostack_get(ctx, i);
+ switch (obj_type(obj)) {
+ case OBJ_TYPE_BOOLEAN:
+ return obj.boolean;
+ case OBJ_TYPE_INTEGER:
+ return obj.integer;
+ case OBJ_TYPE_REAL:
+ return obj.real;
+ default:
+ ERR(1);
+ }
+}
+
+static long
+dictionary_get_integer(struct dictionary *dict, name_t name, long optional)
+{
+ obj_t obj = dictionary_get(dict, name);
+ switch (obj_type(obj)) {
+ case OBJ_TYPE_BOOLEAN:
+ return obj.boolean;
+ case OBJ_TYPE_INTEGER:
+ return obj.integer;
+ case OBJ_TYPE_REAL:
+ return obj.real;
+ default:
+ return optional;
+ }
+}
+
+enum {
+ GEM_TYPE_DRIVER = OBJ_TYPE_EXTENSION_0,
+ GEM_TYPE_CONTEXT,
+ GEM_TYPE_OBJECT,
+ GEM_TYPE_BATCH,
+};
+
+struct gem {
+ struct extension_object base;
+ int fd, gen, vendor;
+
+ unsigned engines[16];
+ unsigned nengine;
+};
+
+static struct gem *
+ostack_get_gem(struct script *ctx, unsigned int i)
+{
+ obj_t obj = ostack_get(ctx, i);
+
+ ERR((obj_type(obj) != GEM_TYPE_DRIVER));
+
+ obj.object->ref++;
+ return obj.ptr;
+}
+
+static void gem_free(void *ptr)
+{
+ struct gem *gem = ptr;
+ close(gem->fd);
+ script_free(gem_ctx(gem), gem);
+}
+
+static void gem_put(struct gem *gem)
+{
+ if (--gem->base.heap.ref == 0)
+ gem_free(gem);
+}
+
+static const struct extension gem_ops = {
+ .free = gem_free,
+};
+
+static const char *dictionary_get_string(struct dictionary *dict, const char *key)
+{
+ obj_t obj;
+
+ obj = dictionary_get(dict, name_new_static(dict->base.ctx, key).name);
+ if (obj_type(obj) != OBJ_TYPE_STRING)
+ longjmp(dict->base.ctx->error, -EINVAL);
+
+ return obj.string->string;
+}
+
+static int lookup_vendor(const char *str)
+{
+ if (!str)
+ return DRIVER_INTEL;
+
+ if (strcmp(str, "intel") == 0)
+ return DRIVER_INTEL;
+
+ return DRIVER_INTEL;
+}
+
+static void gem_new(struct script *ctx)
+{
+ obj_t obj;
+ struct gem *gem;
+ const char *vendor = NULL;
+
+ gem = script_alloc0(ctx, 1, sizeof(*gem));
+
+ check(1);
+
+ obj = ostack_get(ctx, 0);
+ switch (obj_type(obj)) {
+ case OBJ_TYPE_STRING:
+ vendor = obj.string->string;
+ break;
+ case OBJ_TYPE_DICTIONARY:
+ vendor = dictionary_get_string(obj.dictionary, "vendor");
+ break;
+ default:
+ ERR(1);
+ break;
+ }
+
+ pop(1);
+
+ gem->vendor = lookup_vendor(vendor);
+ gem->fd = drm_open_driver(gem->vendor);
+ if (gem->vendor == DRIVER_INTEL)
+ gem-> gen = intel_gen(intel_get_drm_devid(gem->fd));
+
+ push(new_extension_object(&gem->base, ctx, GEM_TYPE_DRIVER, &gem_ops));
+}
+
+struct gem_object {
+ struct extension_object base;
+ struct gem *gem;
+ uint32_t handle;
+ long width, height, pitch, size;
+ long alignment, offset;
+ unsigned flags;
+ int tiling, caching;
+ uint32_t *expected;
+
+ uint32_t *shadow;
+ bool dirty;
+
+ struct drm_i915_gem_exec_object2 *obj;
+};
+
+static struct gem_object *
+ostack_get_object(struct script *ctx, unsigned int i)
+{
+ obj_t obj = ostack_get(ctx, i);
+
+ ERR((obj_type(obj) != GEM_TYPE_OBJECT));
+
+ return obj.ptr;
+}
+
+static void gem_object_free(void *ptr)
+{
+ struct gem_object *go = ptr;
+ struct gem *gem = go->gem;
+
+ munmap(go->shadow, go->size);
+
+ gem_close(gem->fd, go->handle);
+ script_free(gem_ctx(go), go);
+
+ gem_put(gem);
+}
+
+static const struct extension gem_object_ops = {
+ .free = gem_object_free,
+};
+
+struct gem_context {
+ struct extension_object base;
+ struct gem *gem;
+ uint32_t handle;
+};
+
+static void gem_context_free(void *ptr)
+{
+ struct gem_context *gc = ptr;
+ struct gem *gem = gc->gem;
+
+ gem_context_destroy(gem->fd, gc->handle);
+ script_free(gem_ctx(gc), gc);
+
+ gem_put(gem);
+}
+
+static const struct extension gem_context_ops = {
+ .free = gem_context_free,
+};
+
+static void gem_object_new(struct script *ctx)
+{
+ struct private *ext = ctx->private;
+ struct dictionary *dict;
+ struct gem_object *go;
+ uint32_t shadow;
+
+ go = script_alloc0(ctx, 1, sizeof(*go));
+
+ check(2);
+
+ dict = ostack_get_dictionary(ctx, 0);
+ go->gem = ostack_get_gem(ctx, 1);
+
+ go->width = dictionary_get_integer(dict, ext->name.width, 0);
+ go->height = dictionary_get_integer(dict, ext->name.height, 0);
+ go->pitch = dictionary_get_integer(dict, ext->name.pitch, 0);
+ if (go->pitch == 0)
+ go->pitch = go->width * 4;
+ go->size = go->pitch * go->height;
+ if (go->size == 0)
+ go->size = dictionary_get_integer(dict, ext->name.size, 0);
+ ERR(go->size == 0);
+
+ if (dictionary_has(dict, ext->name.pinned)) {
+ go->offset = dictionary_get_integer(dict, ext->name.pinned, 0);
+ go->flags |= EXEC_OBJECT_PINNED;
+ }
+
+ go->size = (go->size + 4095) & -4096;
+ go->handle = gem_create(go->gem->fd, go->size);
+
+ go->tiling = dictionary_get_integer(dict, ext->name.tiling, I915_TILING_NONE);
+ if (go->tiling)
+ gem_set_tiling(go->gem->fd, go->handle, go->tiling, go->pitch);
+
+ go->caching = dictionary_get_integer(dict, ext->name.caching, -1);
+ if (go->caching != -1)
+ gem_set_caching(go->gem->fd, go->handle, go->caching);
+
+ shadow = gem_create(go->gem->fd, go->size);
+ go->shadow = gem_mmap__cpu(go->gem->fd, shadow, 0, go->size, PROT_WRITE);
+ gem_set_domain(go->gem->fd, shadow, I915_GEM_DOMAIN_CPU, I915_GEM_DOMAIN_CPU);
+ gem_close(go->gem->fd, shadow);
+
+ pop(1);
+
+ push(new_extension_object(&go->base, ctx, GEM_TYPE_OBJECT, &gem_object_ops));
+}
+
+static void gem_object_read(struct script *ctx)
+{
+ struct gem_object *go;
+ long offset, len;
+ char *buf;
+
+ check(3);
+
+ go = ostack_get_object(ctx, 2);
+ offset = ostack_get_integer(ctx, 1);
+ len = ostack_get_integer(ctx, 0);
+
+ if (offset < 0 || offset + len > go->size)
+ longjmp(ctx->error, -ERANGE);
+
+ buf = script_alloc(ctx, 1, len);
+ gem_read(go->gem->fd, go->handle, offset, buf, len);
+
+ pop(3);
+ push(string_new_from_bytes(ctx, buf, len));
+}
+
+static void gem_object_write(struct script *ctx)
+{
+ struct gem_object *go;
+ obj_t src;
+ long offset;
+
+ check(3);
+
+ go = ostack_get_object(ctx, 2);
+ offset = ostack_get_integer(ctx, 1);
+ src = ostack_get(ctx, 0);
+
+ if (obj_type(src) == OBJ_TYPE_STRING) {
+ struct string *str = ostack_get_string(ctx, 0);
+
+ if (offset < 0 || offset + str->len > go->size)
+ longjmp(ctx->error, -ERANGE);
+
+ memcpy((char *)go->shadow + offset, str->string, str->len);
+ go->dirty = true;
+
+ gem_write(go->gem->fd, go->handle, offset, str->string, str->len);
+ } else if (obj_is_number(src)) {
+ uint32_t value = number_get_value(src);
+
+ if (offset < 0 || offset >= go->size / sizeof(uint32_t))
+ longjmp(ctx->error, -ERANGE);
+
+ go->shadow[offset] = value;
+ go->dirty = true;
+
+ gem_write(go->gem->fd, go->handle,
+ offset*sizeof(uint32_t), &value, sizeof(value));
+ } else {
+ ERR(1);
+ }
+
+ pop(3);
+}
+
+static void gem_object_verify(struct script *ctx)
+{
+ struct gem_object *go;
+ uint32_t *map;
+
+ check(1);
+
+ go = ostack_get_object(ctx, 0);
+
+ if (go->dirty) {
+ map = gem_mmap__cpu(go->gem->fd, go->handle, 0, go->size, PROT_READ);
+ gem_set_domain(go->gem->fd, go->handle, I915_GEM_DOMAIN_CPU, 0);
+ for (long n = 0; n < go->size/4; n++) {
+ if (go->shadow[n] != map[n]) {
+ fprintf(stderr,
+ "discrepancy found in GEM object %d, at offset %ld: expected %x, found %x\n",
+ go->handle, n, go->shadow[n], map[n]);
+ abort();
+ }
+ }
+ munmap(map, go->size);
+ go->dirty = false;
+ }
+
+ pop(1);
+}
+
+static void gem_object_unref(struct gem_object *go)
+{
+ if (--go->base.heap.ref == 0)
+ gem_object_free(go);
+}
+
+static struct gem_object *gem_object_ref(struct gem_object *go)
+{
+ go->base.heap.ref++;
+ return go;
+}
+
+static void gem_context_new(struct script *ctx)
+{
+ struct gem_context *gc;
+
+ gc = script_alloc0(ctx, 1, sizeof(*gc));
+
+ check(1);
+ gc->gem = ostack_get_gem(ctx, 0);
+ gc->handle = gem_context_create(gc->gem->fd);
+ pop(1);
+
+ push(new_extension_object(&gc->base, ctx, GEM_TYPE_CONTEXT, &gem_context_ops));
+}
+
+struct gem_batch {
+ struct extension_object base;
+ struct gem *gem;
+
+ struct drm_i915_gem_exec_object2 batch_obj;
+ uint32_t *batch;
+
+ struct drm_i915_gem_exec_object2 *obj;
+ struct gem_object **go;
+
+ long nobj;
+ long nbatch, size;
+};
+
+static struct gem_batch *
+ostack_get_batch(struct script *ctx, unsigned int i)
+{
+ obj_t obj = ostack_get(ctx, i);
+
+ ERR((obj_type(obj) != GEM_TYPE_BATCH));
+
+ return obj.ptr;
+}
+
+static void gem_batch_free(void *ptr)
+{
+ struct gem_batch *gb = ptr;
+ struct gem *gem = gb->gem;
+
+ for (long n = 0; n < gb->nobj - 1; n++) {
+ if (gb->go[n]->gem != gb->gem)
+ gem_close(gb->gem->fd, gb->obj[n].handle);
+ gem_object_unref(gb->go[n]);
+ }
+
+ script_free(gem_ctx(gb), gb->obj);
+ script_free(gem_ctx(gb), gb->go);
+
+ munmap(gb->batch, gb->size);
+ gem_close(gem->fd, gb->batch_obj.handle);
+
+ script_free(gem_ctx(gb), gb);
+
+ gem_put(gem);
+}
+
+static const struct extension gem_batch_ops = {
+ .free = gem_batch_free,
+};
+
+static struct drm_i915_gem_exec_object2 *
+__gem_batch_add_object(struct gem_batch *gb)
+{
+ if ((gb->nobj & -gb->nobj) == gb->nobj) {
+ int len = 2*gb->nobj;
+ if (len == 0)
+ len = 1;
+ gb->obj = script_realloc(gem_ctx(gb), gb->obj,
+ len, sizeof(*gb->obj));
+ gb->go = script_realloc(gem_ctx(gb), gb->go,
+ len, sizeof(*gb->go));
+ }
+
+ return memset(&gb->obj[gb->nobj++], 0, sizeof(*gb->obj));
+}
+
+static struct drm_i915_gem_exec_object2 *
+gem_batch_add_object(struct gem_batch *gb, struct gem_object *go)
+{
+ struct drm_i915_gem_exec_object2 *obj;
+
+ if (go->obj)
+ return go->obj;
+
+ obj = __gem_batch_add_object(gb);
+ obj->handle = go->handle;
+ obj->alignment = go->alignment;
+ obj->offset = go->offset;
+ obj->flags = go->flags;
+
+ if (go->gem != gb->gem) {
+ uint32_t name = gem_flink(go->gem->fd, go->handle);
+ obj->handle = gem_open(gb->gem->fd, name);
+ }
+
+ go->obj = obj;
+ gb->go[obj - gb->obj] = gem_object_ref(go);
+
+ return obj;
+}
+
+static struct drm_i915_gem_relocation_entry *
+gem_batch_add_reloc(struct gem_batch *gb,
+ struct drm_i915_gem_exec_object2 *obj,
+ struct drm_i915_gem_exec_object2 *target)
+{
+ struct drm_i915_gem_relocation_entry *reloc;
+
+ if ((obj->relocation_count & -obj->relocation_count) == obj->relocation_count) {
+ int len = 2*obj->relocation_count;
+ if (len == 0)
+ len = 1;
+
+ reloc = (typeof(reloc))(uintptr_t)obj->relocs_ptr;
+ reloc = script_realloc(gem_ctx(gb), reloc, len, sizeof(*reloc));
+ obj->relocs_ptr = (uintptr_t)reloc;
+ }
+
+ reloc = (typeof(reloc))(uintptr_t)obj->relocs_ptr;
+ reloc = memset(reloc + obj->relocation_count, 0, sizeof(*reloc));
+ reloc->target_handle = target - gb->obj;
+ reloc->presumed_offset = target->offset;
+ obj->relocation_count++;
+
+ return reloc;
+}
+
+static uint32_t *
+gem_batch_add(struct gem_batch *gb, long count)
+{
+ uint32_t *ptr;
+
+ if (gb->nbatch + count > gb->size/sizeof(uint32_t)) {
+ uint32_t handle = gem_create(gb->gem->fd, gb->size*2);
+ uint32_t *map = gem_mmap__cpu(gb->gem->fd, handle,
+ 0, gb->size*2,
+ PROT_WRITE);
+ memcpy(map, gb->batch, gb->nbatch*sizeof(uint32_t));
+ munmap(gb->batch, gb->size);
+
+ gb->batch = map;
+ gb->size *= 2;
+ }
+
+ ptr = gb->batch + gb->nbatch;
+ gb->nbatch += count;
+
+ return ptr;
+}
+
+static long
+gem_batch_offset(struct gem_batch *gb, uint32_t *ptr)
+{
+ return (ptr - gb->batch) * sizeof(uint32_t);
+}
+
+static void __gem_batch_store(struct gem_batch *gb,
+ struct gem_object *go,
+ long offset, long value)
+{
+ struct drm_i915_gem_relocation_entry *reloc;
+ struct drm_i915_gem_exec_object2 *obj;
+ uint32_t *batch;
+ int i;
+
+ if (offset < 0 || offset >= go->size/sizeof(uint32_t))
+ gem_error(gb, -ERANGE);
+
+ /* XXX delayed vs multiple execution */
+ go->shadow[offset] = value;
+ go->dirty = true;
+ offset *= sizeof(uint32_t);
+
+ obj = gem_batch_add_object(gb, go);
+ reloc = gem_batch_add_reloc(gb, &gb->batch_obj, obj);
+ batch = gem_batch_add(gb, 4);
+
+ reloc->offset = gem_batch_offset(gb, batch) + sizeof(uint32_t);
+ reloc->delta = offset;
+ reloc->read_domains = I915_GEM_DOMAIN_INSTRUCTION;
+ reloc->write_domain = I915_GEM_DOMAIN_INSTRUCTION;
+ obj->flags |= EXEC_OBJECT_WRITE;
+
+ offset += obj->offset;
+
+ i = 0;
+ batch[i] = MI_STORE_DWORD_IMM | (gb->gem->gen < 6 ? 1 << 22 : 0);
+ if (gb->gem->gen >= 8) {
+ batch[++i] = offset;
+ batch[++i] = offset >> 32;
+ } else if (gb->gem->gen >= 4) {
+ batch[++i] = 0;
+ batch[++i] = offset;
+ reloc->offset += sizeof(uint32_t);
+ } else {
+ batch[i]--;
+ batch[++i] = offset;
+ }
+ batch[++i] = value;
+ while (++i < 4)
+ batch[i] = 0;
+}
+
+static void gem_batch_store(struct script *ctx)
+{
+ struct gem_batch *gb;
+ struct gem_object *go;
+ long offset, value;
+
+ check(4);
+
+ gb = ostack_get_batch(ctx, 3);
+ go = ostack_get_object(ctx, 2);
+ offset = ostack_get_integer(ctx, 1);
+ value = ostack_get_integer(ctx, 0);
+
+ __gem_batch_store(gb, go, offset, value);
+
+ pop(3);
+}
+
+static void gem_batch_exec(struct script *ctx)
+{
+ struct private *ext = ctx->private;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ struct gem_batch *gb;
+ struct dictionary *dict;
+ obj_t result;
+
+ check(2);
+
+ gb = ostack_get_batch(ctx, 1);
+ dict = ostack_get_dictionary(ctx, 0);
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = (uintptr_t)gb->obj;
+ execbuf.buffer_count = gb->nobj;
+ execbuf.flags = dictionary_get_integer(dict, ext->name.engine, 0);
+ execbuf.flags |= LOCAL_I915_EXEC_HANDLE_LUT;
+ execbuf.flags |= LOCAL_I915_EXEC_NO_RELOC;
+
+ result = dictionary_get(dict, ext->name.context);
+ if (obj_type(result) == GEM_TYPE_CONTEXT) {
+ struct gem_context *gc = result.ptr;
+ execbuf.rsvd1 = gc->handle;
+ }
+
+ gem_execbuf(gb->gem->fd, &execbuf);
+
+ for (long n = 0; n < gb->nobj - 1; n++)
+ gb->go[n]->offset = gb->obj[n].offset;
+
+ pop(1);
+}
+
+static void gem_batch_new(struct script *ctx)
+{
+ obj_t proc;
+ struct gem_batch *gb;
+
+ gb = script_alloc0(ctx, 1, sizeof(*gb));
+
+ check(2);
+
+ proc = ostack_copy(ctx, 0);
+ gb->gem = ostack_get_gem(ctx, 1);
+
+ ERR(!(obj_is_procedure(proc) || is_null(proc)));
+
+ gb->size = 4096;
+ gb->batch_obj.handle = gem_create(gb->gem->fd, gb->size);
+ gb->batch = gem_mmap__cpu(gb->gem->fd, gb->batch_obj.handle, 0, gb->size, PROT_WRITE);
+
+ pop(1);
+
+ push(new_extension_object(&gb->base, ctx, GEM_TYPE_BATCH, &gem_batch_ops));
+ gb->base.heap.ref++;
+
+ if (obj_is_procedure(proc))
+ array_execute(proc.array);
+ obj_free(proc);
+
+ for (long n = 0; n < gb->nobj; n++)
+ gb->go[n]->obj = NULL;
+
+ *__gem_batch_add_object(gb) = gb->batch_obj;
+ *gem_batch_add(gb, 1) = MI_BATCH_BUFFER_END;
+
+ if (!--gb->base.heap.ref)
+ gem_batch_free(gb);
+}
+
+static void gem_set(struct gem *gem, name_t key, obj_t value)
+{
+ gem_error(gem, -EINVAL);
+}
+
+static void gem_context_set(struct gem_context *gc, name_t key, obj_t value)
+{
+ gem_error(gc, -EINVAL);
+}
+
+static void gem_object_set(struct gem_object *go, name_t key, obj_t value)
+{
+ struct private *ext = gem_ctx(go)->private;
+
+ if (key == ext->name.tiling) {
+ go->tiling = number_get_value(value);
+ gem_set_tiling(go->gem->fd, go->handle, go->tiling, go->pitch);
+ } else if (key == ext->name.caching) {
+ go->caching = number_get_value(value);
+ gem_set_caching(go->gem->fd, go->handle, go->caching);
+ } else if (key == ext->name.pinned) {
+ if (obj_type(value) != OBJ_TYPE_NULL) {
+ go->offset = number_get_value(value);
+ go->flags |= EXEC_OBJECT_PINNED;
+ } else
+ go->flags &= ~EXEC_OBJECT_PINNED;
+ } else
+ gem_error(go, -EINVAL);
+}
+
+static void any_set(struct script *ctx)
+{
+ obj_t obj, value, key;
+
+ check(3);
+
+ obj = ostack_get(ctx, 2);
+ if (obj_type(obj) < GEM_TYPE_DRIVER) {
+ systemdict_execute(ctx, "set");
+ return;
+ }
+
+ key = ostack_get(ctx, 1);
+ ERR(obj_type(key) != OBJ_TYPE_NAME);
+
+ value = ostack_get(ctx, 0);
+
+ switch (obj_type(obj)) {
+ case GEM_TYPE_DRIVER:
+ gem_set(obj.ptr, key.name, value);
+ break;
+
+ case GEM_TYPE_CONTEXT:
+ gem_context_set(obj.ptr, key.name, value);
+ break;
+
+ case GEM_TYPE_OBJECT:
+ gem_object_set(obj.ptr, key.name, value);
+ break;
+
+ default:
+ ERR(1);
+ break;
+ }
+
+ pop(2);
+}
+
+static void gem_driver_wait(struct gem *gem)
+{
+ gem_quiescent_gpu(gem->fd);
+}
+
+static void gem_object_wait(struct gem_object *go)
+{
+ gem_wait(go->gem->fd, go->handle, NULL);
+}
+
+static void gem_batch_wait(struct gem_batch *gb)
+{
+ gem_wait(gb->gem->fd, gb->batch_obj.handle, NULL);
+}
+
+static void any_wait(struct script *ctx)
+{
+ obj_t obj;
+
+ check(1);
+ obj = ostack_get(ctx, 0);
+ switch (obj_type(obj)) {
+ case GEM_TYPE_DRIVER:
+ gem_driver_wait(obj.ptr);
+ break;
+
+ case GEM_TYPE_OBJECT:
+ gem_object_wait(obj.ptr);
+ break;
+
+ case GEM_TYPE_BATCH:
+ gem_batch_wait(obj.ptr);
+ break;
+
+ default:
+ ERR(1);
+ break;
+ }
+}
+
+static void gem_get_engines(struct gem *gem)
+{
+ obj_t obj;
+
+ if (gem->nengine == 0) {
+ unsigned engine;
+
+ for_each_engine(gem->fd, engine) {
+ if (engine == 0)
+ continue;
+
+ if (gem->gen == 6 && e__->exec_id == I915_EXEC_BSD)
+ continue;
+
+ if (gem_has_bsd2(gem->fd) && engine == I915_EXEC_BSD)
+ continue;
+
+ gem->engines[gem->nengine++] = engine;
+ }
+
+ if (gem->nengine == 0)
+ gem->engines[gem->nengine++] = 0;
+ }
+
+ obj = array_new(gem_ctx(gem), gem->nengine);
+ for (int n = 0; n < gem->nengine; n++)
+ array_append(obj.array, new_int(gem->engines[n]));
+ ostack_push(gem_ctx(gem), obj);
+}
+
+static void gem_get(struct gem *gem, name_t key)
+{
+ struct private *ext = gem_ctx(gem)->private;
+
+ if (key == ext->name.engines)
+ gem_get_engines(gem);
+ else
+ gem_error(gem, -EINVAL);
+}
+
+static void gem_context_get(struct gem_context *gc, name_t key)
+{
+ gem_error(gc, -EINVAL);
+}
+
+static void gem_object_get(struct gem_object *go, name_t key)
+{
+ struct private *ext = gem_ctx(go)->private;
+
+ if (key == ext->name.tiling) {
+ ostack_push(gem_ctx(go), new_int(go->tiling));
+ } else if (key == ext->name.caching) {
+ ostack_push(gem_ctx(go), new_int(go->caching));
+ } else
+ gem_error(go, -EINVAL);
+}
+
+static void any_get(struct script *ctx)
+{
+ obj_t obj, key;
+
+ check(2);
+
+ obj = ostack_get(ctx, 1);
+ if (obj_type(obj) < GEM_TYPE_DRIVER) {
+ systemdict_execute(ctx, "get");
+ return;
+ }
+
+ key = ostack_copy(ctx, 0);
+ ERR(obj_type(key) != OBJ_TYPE_NAME);
+
+ pop(1);
+
+ switch (obj_type(obj)) {
+ case GEM_TYPE_DRIVER:
+ gem_get(obj.ptr, key.name);
+ break;
+
+ case GEM_TYPE_CONTEXT:
+ gem_context_get(obj.ptr, key.name);
+ break;
+
+ case GEM_TYPE_OBJECT:
+ gem_object_get(obj.ptr, key.name);
+ break;
+
+ default:
+ ERR(1);
+ break;
+ }
+
+ obj_free(key);
+}
+
+static const struct odef {
+ const char *name;
+ void (*op)(struct script *);
+} operators[] = {
+ { "batch", gem_batch_new },
+ { "context", gem_context_new },
+ { "driver", gem_new },
+ { "exec", gem_batch_exec },
+ { "get", any_get },
+ { "object", gem_object_new },
+ { "read", gem_object_read },
+ { "set", any_set },
+ { "store", gem_batch_store },
+ { "verify", gem_object_verify },
+ { "wait", any_wait },
+ { "write", gem_object_write },
+ { NULL },
+};
+
+static void init_extension(struct script *ctx)
+{
+ struct dictionary *dict = extensiondict(ctx);
+ struct private *ext;
+
+ ext = script_alloc(ctx, 1, sizeof(*ext));
+ ctx->private = ext;
+
+#define NAME(N) ext->name.N = name_new_static(ctx, #N).name
+ NAME(engine);
+ NAME(engines);
+ NAME(context);
+ NAME(width);
+ NAME(height);
+ NAME(pitch);
+ NAME(tiling);
+ NAME(size);
+ NAME(caching);
+ NAME(pinned);
+#undef NAME
+
+ for (const struct odef *def = operators; def->name; def++)
+ dictionary_set(dict,
+ name_new_static(ctx, def->name).name,
+ new_operator(def->op));
+}
+
+static int gem_run(struct script *ctx, const char *filename)
+{
+ int ret;
+
+ intel_detect_and_clear_missed_interrupts(-1);
+ igt_kcov_start();
+
+ ret = script_run(ctx, filename);
+ if (ret)
+ fprintf(stderr, "%s(%s) => %d\n", __func__, filename, ret);
+
+ igt_kcov_stop();
+ igt_assert_eq(intel_detect_and_clear_missed_interrupts(-1), 0);
+
+ return ret;
+}
+
+#define AFL_MAP_SIZE_POW2 16
+#define AFL_MAP_SIZE (1 << AFL_MAP_SIZE_POW2)
+#define AFL_SHM_ID "__AFL_SHM_ID"
+#define AFL_FORKSRV 198
+
+static inline uint32_t hash_64(uint64_t val, unsigned int bits)
+{
+#define GOLDEN_RATIO_64 0x61C8864680B583EBull
+ return val * GOLDEN_RATIO_64 >> (64 - bits);
+}
+
+static void kcov_report(struct igt_kcov *kcov, void *data)
+{
+ uint8_t *afl = data;
+ const unsigned long n = __atomic_load_n(&kcov->bb[0], __ATOMIC_RELAXED);
+ unsigned prev = 0;
+
+ for (unsigned long i = 0; i < n; i++) {
+ unsigned bb = hash_64(kcov->bb[i+1], AFL_MAP_SIZE_POW2);
+ afl[bb ^ prev]++;
+ prev = hash_64(kcov->bb[i+1] + 1, AFL_MAP_SIZE_POW2);
+ }
+}
+
+static void setup_afl(void)
+{
+ const char *env;
+ bool stopped = false;
+ uint8_t *afl;
+ uint32_t ack;
+
+ env = getenv(AFL_SHM_ID);
+ if (!env)
+ return;
+
+ afl = shmat(atoi(env), NULL, 0);
+ if (afl == (void *)-1)
+ return;
+
+ afl[0] = 1; /* hello world! */
+ igt_kcov_setup(kcov_report, afl);
+
+ ack = 0;
+ if (write(AFL_FORKSRV + 1, &ack, sizeof(ack)) != sizeof(ack))
+ return;
+
+ for (;;) {
+ uint32_t pid;
+ int status;
+
+ if (read(AFL_FORKSRV, &ack, sizeof(ack)) != sizeof(ack))
+ break;
+
+ if (stopped && ack) {
+ if (waitpid(pid, &status, 0) == -1)
+ break;
+ stopped = false;
+ }
+ if (stopped) {
+ kill(pid, SIGCONT);
+ stopped = false;
+ } else {
+ pid = fork();
+ if (!pid) { /* child */
+ close(AFL_FORKSRV);
+ close(AFL_FORKSRV + 1);
+ return;
+ }
+ }
+ if (write(AFL_FORKSRV+ 1, &pid, 4) != 4)
+ break;
+
+ if (waitpid(pid, &status, WUNTRACED) == -1)
+ break;
+
+ stopped = WIFSTOPPED(status);
+ if (write(AFL_FORKSRV + 1, &status, 4) != 4)
+ break;
+ }
+
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ struct script ctx;
+ int ret;
+
+ script_init(&ctx);
+ init_extension(&ctx);
+
+ /* Last action before running scripts.
+ * AFL utilizes a forkserver so that initialisation is done once,
+ * then subsequent tests are executed using the Copy-on-Write process
+ * image.
+ */
+ setup_afl();
+
+ if (argc == 1)
+ return gem_run(&ctx, NULL);
+
+ for (int i = 1; i < argc; i++) {
+ switch (argc > 1 ? fork() : 0) {
+ case -1:
+ return -1;
+ case 0:
+ exit(gem_run(&ctx, argv[i]));
+ default:
+ if (wait(&ret) == -1)
+ return -1;
+ if (WIFSIGNALED(ret))
+ raise(WTERMSIG(ret));
+ if (!WIFEXITED(ret))
+ return -1;
+ if (WEXITSTATUS(ret))
+ return WEXITSTATUS(ret);
+ }
+ }
+
+ script_fini(&ctx);
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,398 @@
+/*
+ * Copyright © 2004 Red Hat, Inc.
+ * Copyright © 2005 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Red Hat, Inc.
+ *
+ * Contributor(s):
+ * Keith Packard <keithp@keithp.com>
+ * Graydon Hoare <graydon@redhat.com>
+ * Carl Worth <cworth@cworth.org>
+ * Karl Tomlinson <karlt+@karlt.net>, Mozilla Corporation
+ */
+
+#include "script.h"
+
+#include <stdlib.h>
+
+/*
+ * An entry can be in one of three states:
+ *
+ * FREE: Entry has never been used, terminates all searches.
+ * Appears in the table as a %NULL pointer.
+ *
+ * DEAD: Entry had been live in the past. A dead entry can be reused
+ * but does not terminate a search for an exact entry.
+ * Appears in the table as a pointer to DEAD_ENTRY.
+ *
+ * LIVE: Entry is currently being used.
+ * Appears in the table as any non-%NULL, non-DEAD_ENTRY pointer.
+ */
+
+#define DEAD_ENTRY ((struct hash_entry *) 0x1)
+
+#define ENTRY_IS_FREE(entry) ((entry) == NULL)
+#define ENTRY_IS_DEAD(entry) ((entry) == DEAD_ENTRY)
+#define ENTRY_IS_LIVE(entry) ((entry) > DEAD_ENTRY)
+
+
+/* This table is open-addressed with double hashing. Each table size is a
+ * prime chosen to be a little more than double the high water mark for a
+ * given arrangement, so the tables should remain < 50% full. The table
+ * size makes for the "first" hash modulus; a second prime (2 less than the
+ * first prime) serves as the "second" hash modulus, which is co-prime and
+ * thus guarantees a complete permutation of table indices.
+ *
+ * This structure, and accompanying table, is borrowed/modified from the
+ * file xserver/render/glyph.c in the freedesktop.org x server, with
+ * permission (and suggested modification of doubling sizes) by Keith
+ * Packard.
+ */
+
+static const struct hash_table_arrangement hash_table_arrangements [] = {
+ { 16, 43, 41 },
+ { 32, 73, 71 },
+ { 64, 151, 149 },
+ { 128, 283, 281 },
+ { 256, 571, 569 },
+ { 512, 1153, 1151 },
+ { 1024, 2269, 2267 },
+ { 2048, 4519, 4517 },
+ { 4096, 9013, 9011 },
+ { 8192, 18043, 18041 },
+ { 16384, 36109, 36107 },
+ { 32768, 72091, 72089 },
+ { 65536, 144409, 144407 },
+ { 131072, 288361, 288359 },
+ { 262144, 576883, 576881 },
+ { 524288, 1153459, 1153457 },
+ { 1048576, 2307163, 2307161 },
+ { 2097152, 4613893, 4613891 },
+ { 4194304, 9227641, 9227639 },
+ { 8388608, 18455029, 18455027 },
+ { 16777216, 36911011, 36911009 },
+ { 33554432, 73819861, 73819859 },
+ { 67108864, 147639589, 147639587 },
+ { 134217728, 295279081, 295279079 },
+ { 268435456, 590559793, 590559791 }
+};
+
+#define NUM_HASH_TABLE_ARRANGEMENTS ARRAY_LENGTH (hash_table_arrangements)
+
+void hash_table_init(struct script *ctx,
+ struct hash_table *ht,
+ hash_keys_equal_func_t keys_equal)
+{
+ ht->ctx = ctx;
+ ht->keys_equal = keys_equal;
+
+ ht->arrangement = &hash_table_arrangements[0];
+
+ ht->entries = script_alloc0(ctx,
+ ht->arrangement->size,
+ sizeof(struct hash_entry *));
+
+ ht->live_entries = 0;
+ ht->used_entries = 0;
+ ht->iterating = 0;
+}
+
+void hash_table_fini(struct hash_table *ht)
+{
+ script_free(ht->ctx, ht->entries);
+}
+
+static struct hash_entry **
+hash_table_lookup_unique_key(struct hash_table *ht, struct hash_entry *key)
+{
+ unsigned long table_size, i, idx, step;
+ struct hash_entry **entry;
+
+ table_size = ht->arrangement->size;
+ idx = key->hash % table_size;
+
+ entry = &ht->entries[idx];
+ if (!ENTRY_IS_LIVE(*entry))
+ return entry;
+
+ i = 1;
+ step = key->hash % ht->arrangement->rehash;
+ if (step == 0)
+ step = 1;
+ do {
+ idx += step;
+ if (idx >= table_size)
+ idx -= table_size;
+
+ entry = &ht->entries[idx];
+ if (!ENTRY_IS_LIVE(*entry))
+ return entry;
+ } while (++i < table_size);
+
+ return NULL;
+}
+
+static void hash_table_manage(struct hash_table *ht)
+{
+ struct hash_table tmp;
+ bool realloc = true;
+ unsigned long i;
+
+ /* This keeps the size of the hash table between 2 and approximately 8
+ * times the number of live entries and keeps the proportion of free
+ * entries (search-terminations) > 25%.
+ */
+ unsigned long high = ht->arrangement->high_water_mark;
+ unsigned long low = high >> 2;
+ unsigned long max_used = high + high / 2;
+
+ tmp = *ht;
+
+ if (ht->live_entries > high) {
+ tmp.arrangement = ht->arrangement + 1;
+ /* This code is being abused if we can't make a table big enough. */
+ } else if (ht->live_entries < low &&
+ /* Can't shrink if we're at the smallest size */
+ ht->arrangement != &hash_table_arrangements[0]) {
+ tmp.arrangement = ht->arrangement - 1;
+ } else if (ht->used_entries > max_used) {
+ /* Clean out dead entries to prevent lookups from becoming too slow. */
+ for (i = 0; i < ht->arrangement->size; ++i) {
+ if (ENTRY_IS_DEAD (ht->entries[i]))
+ ht->entries[i] = NULL;
+ }
+ ht->used_entries = ht->live_entries;
+
+ /* There is no need to reallocate but some entries may need to be
+ * moved. Typically the proportion of entries needing to be moved is
+ * small, but, if the moving should leave a large number of dead
+ * entries, they will be cleaned out next time this code is
+ * executed.
+ */
+ realloc = false;
+ } else {
+ return;
+ }
+
+ if (realloc) {
+ tmp.entries = script_alloc0 (ht->ctx,
+ tmp.arrangement->size,
+ sizeof(struct hash_entry *));
+ ht->used_entries = 0;
+ }
+
+ for (i = 0; i < ht->arrangement->size; ++i) {
+ struct hash_entry *entry, **pos;
+
+ entry = ht->entries[i];
+ if (ENTRY_IS_LIVE (entry)) {
+ ht->entries[i] = DEAD_ENTRY;
+
+ pos = hash_table_lookup_unique_key(&tmp, entry);
+ if (ENTRY_IS_FREE (*pos))
+ ht->used_entries++;
+
+ *pos = entry;
+ }
+ }
+
+ if (realloc) {
+ free (ht->entries);
+ ht->entries = tmp.entries;
+ ht->arrangement = tmp.arrangement;
+ }
+}
+
+static bool keys_equal(struct hash_table *ht,
+ const struct hash_entry *key,
+ const struct hash_entry *entry)
+{
+ if (entry->hash != key->hash)
+ return false;
+
+ return ht->keys_equal(key, entry);
+}
+
+void *hash_table_lookup(struct hash_table *ht, const struct hash_entry *key)
+{
+ struct hash_entry **entry;
+ unsigned long table_size, i, idx, step;
+
+ table_size = ht->arrangement->size;
+ idx = key->hash % table_size;
+ entry = &ht->entries[idx];
+
+ if (ENTRY_IS_LIVE (*entry)) {
+ if (keys_equal(ht, key, *entry))
+ return *entry;
+ } else if (ENTRY_IS_FREE (*entry))
+ return NULL;
+
+ i = 1;
+ step = key->hash % ht->arrangement->rehash;
+ if (step == 0)
+ step = 1;
+ do {
+ idx += step;
+ if (idx >= table_size)
+ idx -= table_size;
+
+ entry = &ht->entries[idx];
+ if (ENTRY_IS_LIVE(*entry)) {
+ if (keys_equal(ht, key, *entry))
+ return *entry;
+ } else if (ENTRY_IS_FREE(*entry))
+ return NULL;
+ } while (++i < table_size);
+
+ return NULL;
+}
+
+void *hash_table_lookup_unique(struct hash_table *ht, unsigned long id)
+{
+ struct hash_entry **entry;
+ unsigned long table_size, i, idx, step;
+
+ table_size = ht->arrangement->size;
+ idx = id % table_size;
+ entry = &ht->entries[idx];
+
+ if (ENTRY_IS_LIVE(*entry)) {
+ if ((*entry)->hash == id)
+ return *entry;
+ } else if (ENTRY_IS_FREE(*entry))
+ return NULL;
+
+ i = 1;
+ step = id % ht->arrangement->rehash;
+ if (step == 0)
+ step = 1;
+ do {
+ idx += step;
+ if (idx >= table_size)
+ idx -= table_size;
+
+ entry = &ht->entries[idx];
+ if (ENTRY_IS_LIVE(*entry)) {
+ if ((*entry)->hash == id)
+ return *entry;
+ } else if (ENTRY_IS_FREE(*entry))
+ return NULL;
+ } while (++i < table_size);
+
+ return NULL;
+}
+
+void hash_table_insert(struct hash_table *ht, struct hash_entry *key_and_value)
+{
+ struct hash_entry **entry;
+
+ ht->live_entries++;
+ hash_table_manage (ht);
+
+ entry = hash_table_lookup_unique_key(ht, key_and_value);
+ if (ENTRY_IS_FREE(*entry))
+ ht->used_entries++;
+
+ *entry = key_and_value;
+}
+
+static struct hash_entry **
+hash_table_lookup_exact_key(struct hash_table *ht, struct hash_entry *key)
+{
+ unsigned long table_size, i, idx, step;
+ struct hash_entry **entry;
+
+ table_size = ht->arrangement->size;
+ idx = key->hash % table_size;
+
+ entry = &ht->entries[idx];
+ if (*entry == key)
+ return entry;
+
+ i = 1;
+ step = key->hash % ht->arrangement->rehash;
+ if (step == 0)
+ step = 1;
+ do {
+ idx += step;
+ if (idx >= table_size)
+ idx -= table_size;
+
+ entry = &ht->entries[idx];
+ if (*entry == key)
+ return entry;
+ } while (++i < table_size);
+
+ return NULL;
+}
+
+void hash_table_remove(struct hash_table *ht, struct hash_entry *key)
+{
+ *hash_table_lookup_exact_key(ht, key) = DEAD_ENTRY;
+ ht->live_entries--;
+
+ /* Check for table resize. Don't do this when iterating as this will
+ * reorder elements of the table and cause the iteration to potentially
+ * skip some elements.
+ */
+ if (ht->iterating == 0) {
+ /* This call _can_ fail, but only in failing to allocate new
+ * memory to shrink the hash table. It does leave the table in a
+ * consistent state, and we've already succeeded in removing the
+ * entry, so we don't examine the failure status of this call.
+ */
+ hash_table_manage(ht);
+ }
+}
+
+void hash_table_foreach(struct hash_table *ht,
+ hash_callback_func_t hash_callback,
+ void *closure)
+{
+ unsigned long i;
+ struct hash_entry *entry;
+
+ /* Mark the table for iteration */
+ ++ht->iterating;
+ for (i = 0; i < ht->arrangement->size; i++) {
+ entry = ht->entries[i];
+ if (ENTRY_IS_LIVE(entry))
+ hash_callback (entry, closure);
+ }
+ /* If some elements were deleted during the iteration,
+ * the table may need resizing. Just do this every time
+ * as the check is inexpensive.
+ */
+ if (--ht->iterating == 0) {
+ /* Should we fail to shrink the hash table, it is left
+ * unaltered, and we don't need to propagate the error status.
+ */
+ hash_table_manage (ht);
+ }
+}
new file mode 100644
@@ -0,0 +1,362 @@
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "script.h"
+
+#ifndef MAX
+#define MAX(a,b) (((a)>=(b))?(a):(b))
+#endif
+
+struct perm_chunk {
+ struct perm_chunk *next;
+ long rem;
+ char *ptr;
+};
+
+void *script_alloc(struct script *ctx, long count, long size)
+{
+ void *ptr;
+
+ if (count > INT_MAX / size)
+ longjmp(ctx->error, -ENOMEM);
+
+ ptr = malloc(count * size);
+ if (!ptr)
+ longjmp(ctx->error, -ENOMEM);
+
+ return ptr;
+}
+
+void *script_alloc0(struct script *ctx, long count, long size)
+{
+ return memset(script_alloc(ctx, count, size), 0, count*size);
+}
+
+void *script_realloc(struct script *ctx, void *ptr, long count, long size)
+{
+ if (count > INT_MAX / size)
+ longjmp(ctx->error, -ENOMEM);
+
+ ptr = realloc(ptr, count * size);
+ if (!ptr)
+ longjmp(ctx->error, -ENOMEM);
+
+ return ptr;
+}
+
+void script_free(struct script *ctx, void *ptr)
+{
+ free (ptr);
+}
+
+void *script_perm_alloc(struct script *ctx, int size)
+{
+ struct perm_chunk *chunk;
+ void *ptr;
+
+ size = (size + sizeof (void *)-1) & -sizeof (void *);
+
+ chunk = ctx->perm_chunk;
+ if (chunk == NULL || chunk->rem < size) {
+ int chunk_size = (size + 8191) & -8192;
+ chunk = script_alloc (ctx, 1, sizeof (struct perm_chunk) + chunk_size);
+ chunk->rem = chunk_size;
+ chunk->ptr = (char *) (chunk + 1);
+ chunk->next = ctx->perm_chunk;
+ ctx->perm_chunk = chunk;
+ }
+
+ ptr = chunk->ptr;
+ chunk->ptr += size;
+ chunk->rem -= size;
+
+ return ptr;
+}
+
+void *
+script_slab_alloc (struct script *ctx, int size)
+{
+#if DEBUG_MALLOC
+ return malloc (size);
+#else
+ int chunk_size;
+ struct perm_chunk *chunk;
+ void *ptr;
+
+ chunk_size = 2 * sizeof (void *);
+ chunk_size = (size + chunk_size - 1) / chunk_size;
+
+ if (ctx->slabs[chunk_size].free_list) {
+ ptr = ctx->slabs[chunk_size].free_list;
+ ctx->slabs[chunk_size].free_list = *(void **) ptr;
+ return ptr;
+ }
+
+ chunk = ctx->slabs[chunk_size].chunk;
+ if (chunk == NULL || ! chunk->rem) {
+ int cnt = MAX (128, 8192 / (chunk_size * 2 * sizeof (void *)));
+
+ chunk = script_alloc (ctx,
+ 1, sizeof (struct perm_chunk) +
+ cnt * chunk_size * 2 * sizeof (void *));
+ chunk->rem = cnt;
+ chunk->ptr = (char *) (chunk + 1);
+ chunk->next = ctx->slabs[chunk_size].chunk;
+ ctx->slabs[chunk_size].chunk = chunk;
+ }
+
+ ptr = chunk->ptr;
+ chunk->ptr += chunk_size * 2 * sizeof (void *);
+ chunk->rem--;
+
+ return ptr;
+#endif
+}
+
+void
+script_slab_free (struct script *ctx, void *ptr, int size)
+{
+ int chunk_size;
+ void **free_list;
+
+ if (!ptr)
+ return;
+
+#if DEBUG_MALLOC
+ free (ptr);
+#else
+ chunk_size = 2 * sizeof (void *);
+ chunk_size = (size + chunk_size - 1) / chunk_size;
+
+ free_list = ptr;
+ *free_list = ctx->slabs[chunk_size].free_list;
+ ctx->slabs[chunk_size].free_list = ptr;
+#endif
+}
+
+static void
+perm_fini (struct script *ctx)
+{
+ while (ctx->perm_chunk != NULL) {
+ struct perm_chunk *chunk = ctx->perm_chunk;
+ ctx->perm_chunk = chunk->next;
+ script_free (ctx, chunk);
+ }
+}
+
+static void
+slab_fini (struct script *ctx)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof (ctx->slabs) / sizeof (ctx->slabs[0]); i++) {
+ while (ctx->slabs[i].chunk != NULL) {
+ struct perm_chunk *chunk = ctx->slabs[i].chunk;
+ ctx->slabs[i].chunk = chunk->next;
+ script_free (ctx, chunk);
+ }
+ }
+}
+
+static void init_dictionaries(struct script *ctx)
+{
+ struct stack *stack = &ctx->dstack;
+
+ stack_init(ctx, stack, 4);
+ stack_push(stack, dictionary_new(ctx)); /* systemdict */
+ stack_push(stack, dictionary_new(ctx)); /* extensiondict */
+ stack_push(stack, dictionary_new(ctx)); /* userdict */
+
+ script_init_operators(ctx);
+ script_init_constants(ctx);
+}
+
+struct dictionary *systemdict(struct script *ctx)
+{
+ return ctx->dstack.objects[0].dictionary;
+}
+
+void systemdict_execute(struct script *ctx, const char *key)
+{
+ struct dictionary *dict = ctx->dstack.objects[0].dictionary;
+ name_t name = name_new_static(ctx, key).name;
+ struct dictionary_entry *entry;
+
+ entry = hash_table_lookup_unique(&dict->ht, name);
+ if (!entry)
+ longjmp(ctx->error, -EINVAL);
+
+ return obj_execute(ctx, entry->obj);
+}
+
+struct dictionary *extensiondict(struct script *ctx)
+{
+ return ctx->dstack.objects[1].dictionary;
+}
+
+
+/* intern string */
+
+struct intern_string {
+ struct hash_entry hash_entry;
+ int len;
+ const char *string;
+};
+
+static unsigned long
+_intern_string_hash(const char *str, int len)
+{
+ const signed char *p = (const signed char *) str;
+ if (len > 0) {
+ unsigned int h = *p;
+
+ while (--len)
+ h = (h << 5) - h + *++p;
+
+ return h;
+ }
+ return 0;
+}
+
+static bool _intern_string_equal(const void *_a, const void *_b)
+{
+ const struct intern_string *a = _a;
+ const struct intern_string *b = _b;
+
+ if (a->len != b->len)
+ return false;
+
+ return memcmp(a->string, b->string, a->len) == 0;
+}
+
+int script_init(struct script *ctx)
+{
+ int ret;
+
+ memset(ctx, 0, sizeof (*ctx));
+
+ ret = setjmp(ctx->error);
+ if (ret)
+ return ret;
+
+ hash_table_init(ctx, &ctx->strings, _intern_string_equal);
+ stack_init(ctx, &ctx->ostack, 2048);
+ init_dictionaries(ctx);
+
+ scanner_init(ctx, &ctx->scanner);
+
+ return 0;
+}
+
+static void
+intern_print(void *entry, void *data)
+{
+ struct intern_string *string = entry;
+ fprintf(data, "word_%s=\"%s\"\n", string->string, string->string);
+}
+
+void script_print_strings(struct script *ctx, FILE *file)
+{
+ hash_table_foreach(&ctx->strings, intern_print, file);
+}
+
+void name_define(struct script *ctx, name_t name, obj_t obj)
+{
+ dictionary_set(ctx->dstack.objects[ctx->dstack.len-1].dictionary,
+ name, obj);
+}
+
+obj_t name_lookup(struct script *ctx, name_t name)
+{
+ int i;
+
+ for (i = ctx->dstack.len; i--; ) {
+ struct dictionary *dict = ctx->dstack.objects[i].dictionary;
+ struct dictionary_entry *entry;
+
+ entry = hash_table_lookup_unique(&dict->ht, name);
+ if (entry)
+ return entry->obj;
+ }
+
+ longjmp(ctx->error, -EINVAL);
+}
+
+void name_undefine(struct script *ctx, name_t name)
+{
+ long i;
+
+ for (i = ctx->dstack.len; --i; ) {
+ struct dictionary *dict = ctx->dstack.objects[i].dictionary;
+ if (dictionary_has(dict, name)) {
+ dictionary_unset(dict, name);
+ return;
+ }
+ }
+
+ longjmp(ctx->error, -EINVAL);
+}
+
+void *intern_string(struct script *ctx, const char *str, int len)
+{
+ struct intern_string tmpl, *atom;
+
+ tmpl.hash_entry.hash = _intern_string_hash(str, len);
+ tmpl.len = len;
+ tmpl.string = str;
+
+ atom = hash_table_lookup(&ctx->strings, &tmpl.hash_entry);
+ if (atom == NULL) {
+ atom = script_perm_alloc(ctx, sizeof(struct intern_string) + len + 1);
+ atom->hash_entry.hash = tmpl.hash_entry.hash;
+ atom->len = tmpl.len;
+ atom->string = (char *)(atom + 1);
+ memcpy(atom + 1, str, len);
+ ((char *)(atom + 1))[len] = '\0';
+
+ hash_table_insert(&ctx->strings, &atom->hash_entry);
+ }
+
+ return atom+1;
+}
+
+int script_run(struct script *ctx, const char *filename)
+{
+ obj_t file;
+ int ret;
+
+ if (filename == NULL)
+ file = file_new_for_stream(ctx, stdin);
+ else
+ file = file_new(ctx, filename, "r");
+ file.type |= OBJ_ATTR_EXECUTABLE;
+
+ ret = setjmp(ctx->error);
+ if (ret == 0)
+ file_execute(file.file);
+ obj_free(file);
+
+ return ret;
+}
+
+void script_fini(struct script *ctx)
+{
+ stack_fini(&ctx->ostack);
+ stack_fini(&ctx->dstack);
+ scanner_fini(&ctx->scanner);
+
+ hash_table_fini(&ctx->strings);
+ if (ctx->free_array)
+ array_free(ctx->free_array);
+ if (ctx->free_dictionary)
+ dictionary_free(ctx->free_dictionary);
+ if (ctx->free_string)
+ string_free(ctx->free_string);
+
+ slab_fini(ctx);
+ perm_fini(ctx);
+}
new file mode 100644
@@ -0,0 +1,467 @@
+#include <string.h>
+
+#include "script.h"
+
+static inline void __obj_execute(struct script *ctx, obj_t obj);
+
+obj_t array_new(struct script *ctx, long initial_size)
+{
+ struct array *array;
+
+ if (!ctx->free_array || ctx->free_array->stack.size <= initial_size) {
+ array = script_slab_alloc(ctx, sizeof (struct array));
+ stack_init (ctx, &array->stack, initial_size ? initial_size : 32);
+ } else {
+ array = ctx->free_array;
+ ctx->free_array = NULL;
+ }
+
+ return new_heap_object(&array->base, ctx, OBJ_TYPE_ARRAY);
+}
+
+void array_set(struct array *array, long elem, obj_t value)
+{
+ if (elem < 0)
+ longjmp(array->base.ctx->error, -EINVAL);
+
+ if (elem >= array->stack.len) {
+ stack_grow(&array->stack, elem + 1);
+
+ memset(array->stack.objects + array->stack.len,
+ 0, (elem - array->stack.len + 1) * sizeof (obj_t));
+ array->stack.len = elem + 1;
+ }
+
+ obj_free(array->stack.objects[elem]);
+ array->stack.objects[elem] = obj_reference(value);
+}
+
+obj_t array_get(struct array *array, long elem)
+{
+ if (elem < 0 || elem > array->stack.len)
+ longjmp(array->base.ctx->error, -EINVAL);
+
+ return array->stack.objects[elem];
+}
+
+void array_append(struct array *array, obj_t obj)
+{
+ stack_push(&array->stack, obj_reference(obj));
+}
+
+inline void array_execute(struct array *array)
+{
+ long i;
+
+ if (!array->stack.len)
+ return;
+
+ for (i = 0; i < array->stack.len; i++) {
+ obj_t obj = array->stack.objects[i];
+
+ if ((obj.type & (OBJ_TYPE_ARRAY | OBJ_ATTR_EXECUTABLE)) == OBJ_ATTR_EXECUTABLE) {
+ __obj_execute(array->base.ctx, obj);
+ continue;
+ }
+
+ ostack_push_copy(array->base.ctx, obj);
+ }
+}
+
+void array_free(struct array *array)
+{
+#if DEBUG_MALLOC
+ stack_fini (array->base.ctx, &array->stack);
+ script_slab_free(array->base.ctx, array, sizeof (struct array));
+#else
+ long n;
+
+ for (n = 0; n < array->stack.len; n++)
+ obj_free (array->stack.objects[n]);
+ array->stack.len = 0;
+
+ if (array->base.ctx->free_array) {
+ if (array->stack.size > array->base.ctx->free_array->stack.size) {
+ struct array *tmp = array->base.ctx->free_array;
+ array->base.ctx->free_array = array;
+ array = tmp;
+ }
+
+ stack_fini(&array->stack);
+ script_slab_free(array->base.ctx, array, sizeof (struct array));
+ } else
+ array->base.ctx->free_array = array;
+#endif
+}
+
+obj_t dictionary_new(struct script *ctx)
+{
+ struct dictionary *dict;
+
+ if (ctx->free_dictionary) {
+ dict = ctx->free_dictionary;
+ ctx->free_dictionary = NULL;
+ } else {
+ dict = script_slab_alloc(ctx, sizeof(struct dictionary));
+ hash_table_init(ctx, &dict->ht, NULL);
+ }
+
+ return new_heap_object(&dict->base, ctx, OBJ_TYPE_DICTIONARY);
+}
+
+struct _dictionary_entry_pluck {
+ struct script *ctx;
+ struct hash_table *ht;
+};
+
+static void
+_dictionary_entry_pluck (void *entry, void *data)
+{
+ struct dictionary_entry *dict_entry;
+ struct _dictionary_entry_pluck *pluck_data;
+
+ dict_entry = entry;
+ pluck_data = data;
+
+ hash_table_remove(pluck_data->ht, entry);
+ obj_free(dict_entry->obj);
+ script_slab_free(pluck_data->ctx,
+ entry, sizeof(struct dictionary_entry));
+}
+
+void
+dictionary_free(struct dictionary *dict)
+{
+ struct _dictionary_entry_pluck data;
+
+ data.ctx = dict->base.ctx;
+ data.ht = &dict->ht;
+ hash_table_foreach (&dict->ht, _dictionary_entry_pluck, &data);
+
+#if DEBUG_MALLOC
+ hash_table_fini(&dict->ht);
+ script_slab_free(ctx, dict, sizeof(struct dictionary));
+#else
+ if (dict->base.ctx->free_dictionary) {
+ hash_table_fini(&dict->ht);
+ script_slab_free(dict->base.ctx,
+ dict, sizeof(struct dictionary));
+ } else
+ dict->base.ctx->free_dictionary = dict;
+#endif
+}
+
+void dictionary_set(struct dictionary *dict, name_t name, obj_t value)
+{
+ struct dictionary_entry *entry;
+
+ entry = hash_table_lookup_unique(&dict->ht, name);
+ if (entry) {
+ /* replace the existing entry */
+ obj_free(entry->obj);
+ entry->obj = obj_reference(value);
+ return;
+ }
+
+ entry = script_slab_alloc(dict->base.ctx, sizeof(*entry));
+ entry->hash_entry.hash = name;
+ entry->obj = obj_reference(value);
+ hash_table_insert(&dict->ht, &entry->hash_entry);
+}
+
+obj_t dictionary_get(struct dictionary *dict, name_t name)
+{
+ struct dictionary_entry *entry;
+
+ entry = hash_table_lookup_unique(&dict->ht, name);
+ if (!entry)
+ return null();
+
+ return entry->obj;
+}
+
+bool dictionary_has(struct dictionary *dict, name_t name)
+{
+ return hash_table_lookup_unique(&dict->ht, name);
+}
+
+void dictionary_unset(struct dictionary *dict, name_t name)
+{
+ struct dictionary_entry *entry;
+
+ entry = hash_table_lookup_unique(&dict->ht, name);
+ if (entry) {
+ hash_table_remove(&dict->ht, &entry->hash_entry);
+ obj_free(entry->obj);
+ script_slab_free(dict->base.ctx,
+ entry, sizeof (struct dictionary_entry));
+ }
+}
+
+static obj_t new_name(const char *str)
+{
+ return (obj_t){.type = OBJ_TYPE_NAME, .name = (name_t)str };
+}
+
+obj_t name_new(struct script *ctx, const char *str, int len)
+{
+ return new_name(intern_string(ctx, str, len));
+}
+
+obj_t name_new_static(struct script *ctx, const char *str)
+{
+ return new_name(intern_string(ctx, str, strlen(str)));
+}
+
+static inline obj_t new_string(struct string *string)
+{
+ return (obj_t){.type = OBJ_TYPE_STRING, .string = string};
+}
+
+obj_t string_new(struct script *ctx, const char *str, int len)
+{
+ struct string *string;
+
+ if (len < 0)
+ len = strlen (str);
+
+ if (ctx->free_string == NULL || ctx->free_string->len <= len) {
+ string = script_slab_alloc(ctx, sizeof(struct string));
+ string->string = script_alloc(ctx, 1, len + 1);
+ } else {
+ string = ctx->free_string;
+ ctx->free_string = NULL;
+ }
+
+ if (str) {
+ memcpy (string->string, str, len);
+ string->string[len] = '\0';
+ }
+ string->len = len;
+ string->deflate = 0;
+ string->method = NONE;
+
+ return new_heap_object(&string->base, ctx, OBJ_TYPE_STRING);
+}
+
+obj_t
+string_deflate_new(struct script *ctx, void *bytes, int in_len, int out_len)
+{
+ obj_t s = string_new(ctx, bytes, in_len);
+
+ s.string->deflate = out_len;
+ s.string->method = ZLIB;
+
+ return s;
+}
+
+obj_t string_new_from_bytes (struct script *ctx, char *bytes, unsigned int len)
+{
+ struct string *string;
+
+ string = script_slab_alloc(ctx, sizeof(struct string));
+ string->string = bytes;
+ string->len = len;
+ string->deflate = 0;
+ string->method = NONE;
+
+ return new_heap_object(&string->base, ctx, OBJ_TYPE_STRING);
+}
+
+static inline void string_execute(struct string *string)
+{
+ obj_t obj;
+
+ if (!string->len)
+ return;
+
+ obj = file_new_for_bytes(string->base.ctx, string->string, string->len);
+ scan_file(obj.file);
+ obj_free(obj);
+}
+
+void string_free(struct string *string)
+{
+#if DEBUG_MALLOC
+ script_free(ctx, string->string);
+ script_slab_free(ctx, string, sizeof(struct string));
+#else
+ if (string->base.ctx->free_string) {
+ if (string->len > string->base.ctx->free_string->len) {
+ struct string *tmp = string->base.ctx->free_string;
+ string->base.ctx->free_string = string;
+ string = tmp;
+ }
+
+ script_free(string->base.ctx, string->string);
+ script_slab_free(string->base.ctx,
+ string, sizeof(struct string));
+ } else
+ string->base.ctx->free_string = string;
+#endif
+}
+
+static inline void __obj_execute(struct script *ctx, obj_t obj)
+{
+ do switch (obj.type & OBJ_TYPE_MASK) {
+ case OBJ_TYPE_OPERATOR:
+ return obj.op(ctx);
+
+ case OBJ_TYPE_ARRAY:
+ return array_execute(obj.array);
+ case OBJ_TYPE_FILE:
+ return file_execute(obj.file);
+ case OBJ_TYPE_STRING:
+ return string_execute(obj.string);
+
+ case OBJ_TYPE_NAME:
+ obj = name_lookup(ctx, obj.name);
+ if (obj.type & OBJ_ATTR_EXECUTABLE)
+ break;
+
+ /* fallthrough */
+ default:
+ return ostack_push_copy(ctx, obj);
+ } while (true);
+}
+
+void obj_execute(struct script *ctx, obj_t obj)
+{
+ __obj_execute(ctx, obj);
+}
+
+void obj_free(struct stack_object obj)
+{
+ if (!OBJ_IS_COMPOUND(obj))
+ return;
+
+ if (--obj.object->ref)
+ return;
+
+ switch (obj.type & OBJ_TYPE_MASK) {
+ case OBJ_TYPE_ARRAY:
+ array_free(obj.array);
+ break;
+ case OBJ_TYPE_DICTIONARY:
+ dictionary_free(obj.dictionary);
+ break;
+ case OBJ_TYPE_FILE:
+ file_free(obj.file);
+ break;
+ case OBJ_TYPE_STRING:
+ string_free(obj.string);
+ break;
+ default:
+ if ((obj.type & OBJ_TYPE_MASK) >= OBJ_TYPE_EXTENSION_0)
+ obj.extension->ops->free(obj.ptr);
+ break;
+ }
+}
+
+obj_t obj_as_file(struct script *ctx, obj_t src)
+{
+ int type = obj_type (src);
+ switch (type) {
+ case OBJ_TYPE_FILE:
+ return obj_reference (src);
+ case OBJ_TYPE_STRING:
+ return file_new_from_string(ctx, src.string);
+ case OBJ_TYPE_ARRAY:
+#if 0
+ if (src->type & OBJ_ATTR_EXECUTABLE)
+ return file_new_from_procedure (cs, src);
+#endif
+ default:
+ longjmp(ctx->error, -EINVAL);
+ }
+}
+
+static int
+lexcmp(void const *a, size_t alen,
+ void const *b, size_t blen)
+{
+ size_t len = alen < blen ? alen : blen;
+ int cmp = memcmp (a, b, len);
+ if (cmp)
+ return cmp;
+ if (alen == blen)
+ return 0;
+ return alen < blen ? -1 : +1;
+}
+
+int obj_compare(obj_t a, obj_t b)
+{
+ int atype = obj_type (a);
+ int btype = obj_type (b);
+ int sign;
+
+#define CMP(x,y) ((x) < (y) ? -1 : +1)
+
+ if (atype == btype) {
+ switch (atype) {
+ case OBJ_TYPE_BOOLEAN:
+ return CMP(a.boolean, b.boolean);
+ case OBJ_TYPE_INTEGER:
+ return CMP(a.integer, b.integer);
+ case OBJ_TYPE_REAL:
+ return CMP(a.real, b.real);
+ case OBJ_TYPE_NAME:
+ return CMP(a.name, b.name);
+ case OBJ_TYPE_STRING:
+ if (a.string == b.string)
+ return 0;
+
+ return lexcmp (a.string->string, a.string->len,
+ b.string->string, b.string->len);
+ case OBJ_TYPE_OPERATOR:
+ case OBJ_TYPE_ARRAY:
+ case OBJ_TYPE_DICTIONARY:
+ case OBJ_TYPE_FILE:
+ return (intptr_t)a.ptr - (intptr_t)b.ptr;
+
+ case OBJ_TYPE_NULL:
+ case OBJ_TYPE_MARK:
+ goto TYPE_CHECK_ERROR;
+ }
+ }
+
+ sign = +1;
+ if (atype < btype) {
+ obj_t tmp;
+
+ tmp = a; a = b; b = tmp;
+
+ atype = obj_type (a);
+ btype = obj_type (b);
+ sign = -1;
+ }
+
+ switch ((int) atype) {
+ case OBJ_TYPE_INTEGER:
+ if (btype == OBJ_TYPE_BOOLEAN)
+ return sign * CMP (a.integer, !!b.boolean);
+ break;
+ case OBJ_TYPE_REAL:
+ if (btype == OBJ_TYPE_INTEGER)
+ return sign * CMP (a.real, b.integer);
+ else if (btype == OBJ_TYPE_BOOLEAN)
+ return sign * CMP (a.real, !!b.boolean);
+ break;
+
+ case OBJ_TYPE_STRING:
+ if (btype == OBJ_TYPE_NAME) {
+ const char *bstr = (const char *) b.name;
+ return sign * lexcmp(a.string->string, a.string->len,
+ bstr, strlen (bstr));
+ }
+ break;
+
+ default:
+ break;
+ }
+
+#undef CMP
+
+TYPE_CHECK_ERROR:
+ return a.type - b.type;
+}
new file mode 100644
@@ -0,0 +1,1161 @@
+#include <stdio.h> /* snprintf */
+#include <stdlib.h> /* mkstemp */
+#include <string.h>
+
+#include <math.h>
+#include <limits.h> /* INT_MAX */
+#include <assert.h>
+
+#include "igt.h"
+#include "script.h"
+
+#define check(CNT) ostack_check(ctx, CNT)
+#define pop(CNT) ostack_pop(ctx, (CNT))
+#define push(OBJ) ostack_push(ctx, (OBJ))
+
+#define ERR(expr) do if ((expr)) longjmp(ctx->error, -EINVAL); while(0)
+
+static bool ostack_get_boolean(struct script *ctx, unsigned int i)
+{
+ obj_t obj = ostack_get(ctx, i);
+ switch (obj_type (obj)) {
+ case OBJ_TYPE_BOOLEAN:
+ return obj.boolean;
+ case OBJ_TYPE_INTEGER:
+ return !!obj.integer;
+ case OBJ_TYPE_REAL:
+ return obj.real != 0.;
+ default:
+ ERR(1);
+ }
+}
+
+static long ostack_get_integer(struct script *ctx, unsigned int i)
+{
+ obj_t obj = ostack_get(ctx, i);
+ switch (obj_type(obj)) {
+ case OBJ_TYPE_BOOLEAN:
+ return obj.boolean;
+ case OBJ_TYPE_INTEGER:
+ return obj.integer;
+ case OBJ_TYPE_REAL:
+ return obj.real;
+ default:
+ ERR(1);
+ }
+}
+
+static name_t ostack_get_name(struct script *ctx, unsigned int i)
+{
+ obj_t obj = ostack_get (ctx, i);
+
+ ERR(obj_type(obj) != OBJ_TYPE_NAME);
+
+ return obj.name;
+}
+
+static struct array *ostack_get_procedure(struct script *ctx, unsigned int i)
+{
+ obj_t obj = ostack_get(ctx, i);
+
+ ERR(!obj_is_procedure(obj));
+
+ obj.array->base.ref++;
+ return obj.array;
+}
+
+static void end_dict_construction(struct script *ctx)
+{
+ obj_t obj;
+ struct dictionary *dict;
+
+ obj = dictionary_new(ctx);
+
+ dict = obj.dictionary;
+ do {
+ obj_t name, value;
+
+ check(1);
+
+ value = ostack_get (ctx, 0);
+ if (obj_type (value) == OBJ_TYPE_MARK) {
+ pop(1);
+ break;
+ }
+
+ check(2);
+
+ name = ostack_get (ctx, 1);
+ ERR(obj_type (name) != OBJ_TYPE_NAME);
+
+ dictionary_set(dict, name.name, value);
+
+ pop(2);
+ } while (true);
+
+ push (obj);
+}
+
+static void end_array_construction(struct script *ctx)
+{
+ obj_t obj;
+ int len = 0;
+
+ do {
+ check(len + 1);
+
+ if (obj_type (ostack_get (ctx, len)) == OBJ_TYPE_MARK)
+ break;
+
+ len++;
+ } while (true);
+
+ obj = array_new (ctx, len);
+ if (len != 0) {
+ struct array *array = obj.array;
+ memcpy (array->stack.objects,
+ ostack_inplace(ctx, len - 1),
+ sizeof(obj_t) * len);
+ array->stack.len = len;
+ }
+ ctx->ostack.len -= len + 1;
+
+ push(obj);
+}
+
+static void _add(struct script *ctx)
+{
+ obj_t A, B;
+ enum object_type type_a, type_b;
+
+ check (2);
+
+ B = ostack_get (ctx, 0);
+ A = ostack_get (ctx, 1);
+
+ type_a = obj_type (A);
+ ERR(!(type_a == OBJ_TYPE_INTEGER || type_a == OBJ_TYPE_REAL));
+
+ type_b = obj_type (B);
+ ERR(!(type_b == OBJ_TYPE_INTEGER || type_b == OBJ_TYPE_REAL));
+
+ pop (2);
+
+ if (type_a == OBJ_TYPE_REAL && type_b == OBJ_TYPE_REAL)
+ ostack_push_real (ctx, A.real + B.real);
+ else if (type_a == OBJ_TYPE_INTEGER && type_b == OBJ_TYPE_INTEGER)
+ ostack_push_integer (ctx, A.integer + B.integer);
+ else {
+ double v;
+
+ if (type_a == OBJ_TYPE_REAL)
+ v = A.real;
+ else
+ v = A.integer;
+
+ if (type_b == OBJ_TYPE_REAL)
+ v += B.real;
+ else
+ v += B.integer;
+
+ ostack_push_real (ctx, v);
+ }
+}
+
+static void _and(struct script *ctx)
+{
+ obj_t a, b;
+
+ check (2);
+
+ a = ostack_get (ctx, 0);
+ b = ostack_get (ctx, 1);
+ ERR (obj_type (a) != obj_type (b));
+
+ pop (2);
+ switch (obj_type (a)) {
+ case OBJ_TYPE_INTEGER:
+ ostack_push_integer (ctx, a.integer & b.integer);
+ case OBJ_TYPE_BOOLEAN:
+ ostack_push_boolean (ctx, a.boolean & b.boolean);
+ default:
+ ERR(1);
+ }
+}
+
+static void _array(struct script *ctx)
+{
+ return push(array_new(ctx, 0));
+}
+
+static void _bind_substitute(struct script *ctx, struct array *array)
+{
+ long n;
+
+ /* perform operator substitution on the executable array (procedure) */
+ n = array->stack.len;
+ for (long i = 0; i < n; i++) {
+ obj_t obj = array->stack.objects[i];
+
+ if (obj.type == (OBJ_TYPE_NAME | OBJ_ATTR_EXECUTABLE)) {
+ for (long j = ctx->dstack.len; j--; ) {
+ struct dictionary *dict = ctx->dstack.objects[j].dictionary;
+ struct dictionary_entry *entry;
+
+ entry = hash_table_lookup_unique(&dict->ht,
+ obj.name);
+ if (entry) {
+ array->stack.objects[i] = entry->obj;
+ break;
+ }
+ }
+ } else if (obj_is_procedure(obj))
+ _bind_substitute(ctx, obj.array);
+ }
+}
+
+static void _idiom_substitute(struct script *ctx, struct array *array)
+{
+#if 0
+ long i, j;
+
+ /* XXX substring search, build array once then search for
+ * longest matching idiom, repeat. */
+
+ /* scan the top-most array for sequences we can pre-compile */
+
+ /* now recurse for subroutines */
+ j = array->stack.len;
+ for (i = 0; i < j; i++) {
+ obj_t *obj = &array->stack.objects[i];
+
+ if (obj_is_procedure (obj)) {
+ _idiom_substitute (ctx, obj.array);
+ }
+ }
+#endif
+}
+
+static void _bind(struct script *ctx)
+{
+ struct array *array;
+
+ check (1);
+
+ array = ostack_get_procedure (ctx, 0);
+ _bind_substitute (ctx, array);
+ _idiom_substitute (ctx, array);
+ array->base.ref--;
+}
+
+static void _bitshift(struct script *ctx)
+{
+ long v, shift;
+
+ check (2);
+
+ shift = ostack_get_integer(ctx, 0);
+ v = ostack_get_integer(ctx, 1);
+
+ if (shift < 0) {
+ shift = -shift;
+ v >>= shift;
+ } else
+ v <<= shift;
+
+ pop (1);
+ ostack_inplace(ctx, 0)->integer = v;
+}
+
+static void _copy (struct script *ctx)
+{
+ obj_t obj;
+
+ check (1);
+
+ obj = obj_reference (ostack_get (ctx, 0));
+ pop (1);
+
+ switch (obj_type (obj)) {
+ /*XXX array, string, dictionary, etc */
+ case OBJ_TYPE_INTEGER:
+ {
+ long i, n;
+
+ n = obj.integer;
+ ERR(n < 0);
+ check (n);
+
+ for (i = n; i--; )
+ ostack_push_copy (ctx, ostack_get (ctx, n-1));
+ break;
+ }
+ default:
+ ERR(1);
+ }
+}
+
+static void _cvi(struct script *ctx)
+{
+ obj_t val, obj;
+
+ check (1);
+
+ val = ostack_get (ctx, 0);
+ switch (obj_type (val)) {
+ case OBJ_TYPE_INTEGER:
+ break;
+
+ case OBJ_TYPE_REAL:
+ pop (1);
+ ostack_push_integer(ctx, val.real);
+ break;
+
+ case OBJ_TYPE_STRING:
+ ERR(!parse_number(&obj, val.string->string, val.string->len));
+
+ pop (1);
+ if (obj_type (obj) == OBJ_TYPE_INTEGER)
+ push (obj);
+ else
+ ostack_push_integer (ctx, obj.real);
+ break;
+
+ default:
+ ERR(1);
+ }
+}
+
+static void _cvr(struct script *ctx)
+{
+ obj_t val, obj;
+
+ check (1);
+
+ val = ostack_get (ctx, 0);
+ switch (obj_type (val)) {
+ case OBJ_TYPE_REAL:
+ break;
+
+ case OBJ_TYPE_INTEGER:
+ pop (1);
+ ostack_push_real (ctx, val.integer);
+ break;
+
+ case OBJ_TYPE_STRING:
+ ERR(!parse_number(&obj, val.string->string, val.string->len));
+
+ pop (1);
+ if (obj_type (obj) == OBJ_TYPE_REAL)
+ push (obj);
+ else
+ ostack_push_real (ctx, obj.integer);
+ break;
+
+ default:
+ ERR(1);
+ }
+}
+
+static void _def(struct script *ctx)
+{
+ check (2);
+ name_define (ctx, ostack_get_name (ctx, 1), ostack_get (ctx, 0));
+ pop (2);
+}
+
+static void _dict(struct script *ctx)
+{
+ push(dictionary_new(ctx));
+}
+
+static void _div(struct script *ctx)
+{
+ obj_t A;
+ obj_t B;
+ enum object_type type_a, type_b;
+
+ check (2);
+
+ B = ostack_get (ctx, 0);
+ A = ostack_get (ctx, 1);
+
+ type_a = obj_type (A);
+ ERR(! (type_a == OBJ_TYPE_INTEGER || type_a == OBJ_TYPE_REAL));
+
+ type_b = obj_type (B);
+ ERR(!(type_b == OBJ_TYPE_INTEGER || type_b == OBJ_TYPE_REAL));
+
+ pop (2);
+
+ if (type_a == OBJ_TYPE_REAL && type_b == OBJ_TYPE_REAL)
+ ostack_push_real (ctx, A.real / B.real);
+ else if (type_a == OBJ_TYPE_INTEGER && type_b == OBJ_TYPE_INTEGER)
+ ostack_push_integer (ctx, A.integer / B.integer);
+ else {
+ double v;
+
+ if (type_a == OBJ_TYPE_REAL)
+ v = A.real;
+ else
+ v = A.integer;
+
+ if (type_b == OBJ_TYPE_REAL)
+ v /= B.real;
+ else
+ v /= B.integer;
+
+ ostack_push_real (ctx, v);
+ }
+}
+
+static void _duplicate (struct script *ctx)
+{
+ check (1);
+ ostack_push_copy (ctx, ostack_get (ctx, 0));
+}
+
+static void _eq(struct script *ctx)
+{
+ int cmp;
+
+ check (2);
+ cmp = obj_compare(ostack_get (ctx, 1), ostack_get (ctx, 0));
+ pop (2);
+
+ ostack_push_boolean(ctx, cmp == 0);
+}
+
+static void _exch(struct script *ctx)
+{
+ stack_exch(&ctx->ostack);
+}
+
+static void _false(struct script *ctx)
+{
+ ostack_push_boolean(ctx, false);
+}
+
+static void _for(struct script *ctx)
+{
+ struct array *proc;
+ long i, inc, limit;
+
+ check (4);
+ proc = ostack_get_procedure(ctx, 0);
+ limit = ostack_get_integer(ctx, 1);
+ inc = ostack_get_integer(ctx, 2);
+ i = ostack_get_integer(ctx, 3);
+ pop (4);
+
+ for (; i <= limit; i += inc) {
+ ostack_push_integer(ctx, i);
+ array_execute(proc);
+ }
+
+ if (--proc->base.ref == 0)
+ array_free(proc);
+}
+
+static void _foreach(struct script *ctx)
+{
+ struct array *proc;
+ obj_t obj;
+
+ check(2);
+ proc = ostack_get_procedure(ctx, 0);
+ obj = ostack_copy(ctx, 1);
+ pop(2);
+
+ if (obj_type(obj) == OBJ_TYPE_ARRAY) {
+ for (long n = 0; n < obj.array->stack.len; n++) {
+ ostack_push_copy(ctx, obj.array->stack.objects[n]);
+ array_execute(proc);
+ }
+ }
+
+ obj_free(obj);
+ if (--proc->base.ref == 0)
+ array_free(proc);
+}
+
+static void _fork(struct script *ctx)
+{
+ struct array *proc;
+ long count;
+
+ check(2);
+ proc = ostack_get_procedure(ctx, 0);
+ count = number_get_value(ostack_get(ctx, 1));
+ if (count < 0)
+ count = sysconf(_SC_NPROCESSORS_ONLN);
+ pop(2);
+
+ igt_fork(child, count) {
+ int ret = setjmp(ctx->error);
+ if (ret)
+ exit(-ret);
+
+ ostack_push_integer(ctx, child);
+ array_execute(proc);
+ }
+
+ if (--proc->base.ref == 0)
+ array_free(proc);
+}
+
+static void _ge(struct script *ctx)
+{
+ int cmp;
+
+ check (2);
+ cmp = obj_compare (ostack_get (ctx, 1), ostack_get (ctx, 0));
+ pop (2);
+
+ ostack_push_boolean (ctx, cmp >= 0);
+}
+
+static void _get(struct script *ctx)
+{
+ obj_t key, src, obj;
+
+ check (2);
+ key = ostack_get (ctx, 0);
+ src = ostack_get (ctx, 1);
+ pop (1);
+
+ switch (obj_type(src)) {
+ case OBJ_TYPE_DICTIONARY:
+ ERR(obj_type (key) != OBJ_TYPE_NAME);
+ obj = dictionary_get (src.dictionary, key.name);
+ break;
+
+ case OBJ_TYPE_ARRAY:
+ ERR(obj_type (key) != OBJ_TYPE_INTEGER);
+ obj = array_get (src.array, key.integer);
+ break;
+#if 0
+ case OBJ_TYPE_STRING:
+ obj = string_get (src, key);
+ break;
+#endif
+
+ default:
+ ERR(1);
+ }
+
+ ostack_push_copy(ctx, obj);
+}
+
+static void _gt (struct script *ctx)
+{
+ int cmp;
+
+ check (2);
+ cmp = obj_compare(ostack_get(ctx, 1), ostack_get(ctx, 0));
+ pop (2);
+
+ ostack_push_boolean (ctx, cmp > 0);
+}
+
+static void _if(struct script *ctx)
+{
+ struct array *proc;
+ bool predicate;
+
+ check(2);
+ proc = ostack_get_procedure(ctx, 0);
+ predicate = ostack_get_boolean(ctx, 1);
+ pop(2);
+
+ if (predicate)
+ array_execute(proc);
+
+ if (--proc->base.ref == 0)
+ array_free(proc);
+}
+
+static void _ifelse (struct script *ctx)
+{
+ struct array *true_proc, *false_proc;
+ bool predicate;
+
+ check(3);
+ false_proc = ostack_get_procedure(ctx, 0);
+ true_proc = ostack_get_procedure(ctx, 1);
+ predicate = ostack_get_boolean(ctx, 2);
+ pop(3);
+
+ if (predicate)
+ array_execute(true_proc);
+ else
+ array_execute(false_proc);
+
+ if (--true_proc->base.ref == 0)
+ array_free(true_proc);
+ if (--false_proc->base.ref == 0)
+ array_free(false_proc);
+}
+
+static void _index(struct script *ctx)
+{
+ long n;
+
+ check(1);
+ n = ostack_get_integer(ctx, 0);
+ pop(1);
+
+ check(n+1);
+ return ostack_push(ctx, ostack_copy(ctx, n));
+}
+
+static void _integer (struct script *ctx)
+{
+ obj_t *obj;
+
+ check (1);
+
+ obj = ostack_inplace(ctx, 0);
+ switch (obj_type (*obj)) {
+ case OBJ_TYPE_INTEGER:
+ break;
+ case OBJ_TYPE_REAL:
+ obj->integer = obj->real;
+ obj->type = OBJ_TYPE_INTEGER;
+ break;
+ default:
+ ERR(1);
+ }
+}
+
+static void _interrupt(struct script *ctx)
+{
+ struct array *proc;
+
+ check(1);
+ proc = ostack_get_procedure(ctx, 0);
+ pop(1);
+
+ igt_while_interruptible(true)
+ array_execute(proc);
+
+ if (--proc->base.ref == 0)
+ array_free(proc);
+}
+
+static void _le(struct script *ctx)
+{
+ int cmp;
+
+ check (2);
+ cmp = obj_compare(ostack_get(ctx, 1), ostack_get (ctx, 0));
+ pop (2);
+
+ ostack_push_boolean(ctx, cmp <= 0);
+}
+
+static void _lt(struct script *ctx)
+{
+ int cmp;
+
+ check (2);
+ cmp = obj_compare(ostack_get(ctx, 1), ostack_get (ctx, 0));
+ pop (2);
+
+ ostack_push_boolean(ctx, cmp < 0);
+}
+
+static void _mark(struct script *ctx)
+{
+ return ostack_push_mark (ctx);
+}
+
+static void _ne(struct script *ctx)
+{
+ int cmp;
+
+ check (2);
+ cmp = obj_compare(ostack_get (ctx, 1), ostack_get (ctx, 0));
+ pop (2);
+
+ ostack_push_boolean (ctx, cmp);
+}
+
+static void _neg(struct script *ctx)
+{
+ obj_t *obj;
+
+ check (1);
+
+ obj = ostack_inplace(ctx, 0);
+ switch (obj_type(*obj)) {
+ case OBJ_TYPE_INTEGER:
+ obj->integer = -obj->integer;
+ break;
+ case OBJ_TYPE_REAL:
+ obj->real = -obj->real;
+ break;
+ default:
+ ERR(1);
+ }
+}
+
+static void _not(struct script *ctx)
+{
+ obj_t *obj;
+
+ check (1);
+
+ obj = ostack_inplace(ctx, 0);
+ switch (obj_type(*obj)) {
+ case OBJ_TYPE_BOOLEAN:
+ obj->boolean = !obj->boolean;
+ break;
+ case OBJ_TYPE_INTEGER:
+ obj->type = OBJ_TYPE_BOOLEAN;
+ obj->boolean = !obj->integer;
+ break;
+ case OBJ_TYPE_REAL:
+ obj->type = OBJ_TYPE_BOOLEAN;
+ obj->boolean = obj->real == 0.0;
+ break;
+ default:
+ ERR(1);
+ }
+}
+
+static void _null(struct script *ctx)
+{
+ ostack_push_null(ctx);
+}
+
+static void _mod(struct script *ctx)
+{
+ long x, y;
+
+ check (2);
+ y = ostack_get_integer (ctx, 0);
+ x = ostack_get_integer (ctx, 1);
+ pop (2);
+
+ return ostack_push_integer(ctx, x % y);
+}
+
+static void _mul(struct script *ctx)
+{
+ obj_t A;
+ obj_t B;
+ enum object_type type_a, type_b;
+
+ check (2);
+
+ B = ostack_get (ctx, 0);
+ A = ostack_get (ctx, 1);
+
+ type_a = obj_type (A);
+ ERR (!(type_a == OBJ_TYPE_INTEGER || type_a == OBJ_TYPE_REAL));
+
+ type_b = obj_type (B);
+ ERR (!(type_b == OBJ_TYPE_INTEGER || type_b == OBJ_TYPE_REAL));
+
+ pop (2);
+
+ if (type_a == OBJ_TYPE_REAL && type_b == OBJ_TYPE_REAL)
+ ostack_push_real (ctx, A.real * B.real);
+ else if (type_a == OBJ_TYPE_INTEGER && type_b == OBJ_TYPE_INTEGER)
+ ostack_push_integer (ctx, A.integer * B.integer);
+ else {
+ double v;
+
+ if (type_a == OBJ_TYPE_REAL)
+ v = A.real;
+ else
+ v = A.integer;
+
+ if (type_b == OBJ_TYPE_REAL)
+ v *= B.real;
+ else
+ v *= B.integer;
+
+ return ostack_push_real (ctx, v);
+ }
+}
+
+static void _or(struct script *ctx)
+{
+ obj_t a, b;
+
+ check (2);
+
+ a = ostack_get (ctx, 0);
+ b = ostack_get (ctx, 1);
+ ERR(obj_type (a) != obj_type (b));
+
+ pop (2);
+ switch (obj_type (a)) {
+ case OBJ_TYPE_INTEGER:
+ return ostack_push_integer (ctx, a.integer | b.integer);
+ case OBJ_TYPE_BOOLEAN:
+ return ostack_push_boolean (ctx, a.boolean | b.boolean);
+ default:
+ ERR(1);
+ }
+}
+
+static void _pop(struct script *ctx)
+{
+ check(1);
+ pop(1);
+}
+
+static void _repeat(struct script *ctx)
+{
+ struct array *proc;
+ long count;
+
+ check(2);
+ proc = ostack_get_procedure(ctx, 0);
+ count = ostack_get_integer(ctx, 1);
+ ERR(count < 0);
+ pop(2);
+
+ while (count--)
+ array_execute(proc);
+
+ if (--proc->base.ref == 0)
+ array_free(proc);
+}
+
+static void _roll(struct script *ctx)
+{
+ long j, n;
+
+ check(2);
+ j = ostack_get_integer(ctx, 0);
+ n = ostack_get_integer(ctx, 1);
+ pop(2);
+
+ if (n <= 0)
+ return;
+ check(n);
+
+ if (j <= -n || j >= n)
+ j %= n;
+ if (j == 0)
+ return;
+
+ stack_roll(&ctx->ostack, j, n);
+}
+
+static void _set(struct script *ctx)
+{
+ obj_t key, value, dst;
+
+ check (3);
+
+ value = ostack_get(ctx, 0);
+ key = ostack_get(ctx, 1);
+ dst = ostack_get(ctx, 2);
+
+ switch (obj_type (dst)) {
+ case OBJ_TYPE_DICTIONARY:
+ ERR(obj_type(key) != OBJ_TYPE_NAME);
+ dictionary_set(dst.dictionary, key.name, value);
+ break;
+ case OBJ_TYPE_ARRAY:
+ ERR(obj_type(key) != OBJ_TYPE_INTEGER);
+ array_set (dst.array, key.integer, value);
+ break;
+
+ case OBJ_TYPE_STRING:
+#if 0
+ string_put(dst, key, value);
+ break;
+#endif
+ default:
+ ERR(1);
+ }
+
+ pop (2);
+}
+
+static void _timeout(struct script *ctx)
+{
+ struct array *proc;
+ long timeout;
+
+ check(2);
+ proc = ostack_get_procedure(ctx, 0);
+ timeout = ostack_get_integer(ctx, 1);
+ pop(2);
+
+ igt_until_timeout(timeout)
+ array_execute(proc);
+
+ if (--proc->base.ref == 0)
+ array_free(proc);
+}
+
+static void _true(struct script *ctx)
+{
+ ostack_push_boolean (ctx, true);
+}
+
+static void _sub(struct script *ctx)
+{
+ obj_t A, B;
+ enum object_type type_a, type_b;
+
+ check (2);
+
+ B = ostack_get (ctx, 0);
+ A = ostack_get (ctx, 1);
+
+ type_a = obj_type (A);
+ ERR (!(type_a == OBJ_TYPE_INTEGER || type_a == OBJ_TYPE_REAL));
+
+ type_b = obj_type (B);
+ ERR (!(type_b == OBJ_TYPE_INTEGER || type_b == OBJ_TYPE_REAL));
+
+ pop (2);
+
+ if (type_a == OBJ_TYPE_REAL && type_b == OBJ_TYPE_REAL)
+ ostack_push_real (ctx, A.real - B.real);
+ else if (type_a == OBJ_TYPE_INTEGER && type_b == OBJ_TYPE_INTEGER)
+ ostack_push_integer (ctx, A.integer - B.integer);
+ else {
+ double v;
+
+ if (type_a == OBJ_TYPE_REAL)
+ v = A.real;
+ else
+ v = A.integer;
+
+ if (type_b == OBJ_TYPE_REAL)
+ v -= B.real;
+ else
+ v -= B.integer;
+
+ ostack_push_real(ctx, v);
+ }
+}
+
+static void _undef(struct script *ctx)
+{
+ name_t name;
+
+ check (1);
+ name = ostack_get_name(ctx, 0);
+ pop (1);
+
+ name_undefine (ctx, name);
+}
+
+static void _unset(struct script *ctx)
+{
+ obj_t dst;
+ name_t name;
+
+ check (2);
+
+ name = ostack_get_name (ctx, 0);
+ dst = ostack_get (ctx, 1);
+
+ switch (obj_type (dst)) {
+ case OBJ_TYPE_DICTIONARY:
+ dictionary_unset (dst.dictionary, name);
+ break;
+ case OBJ_TYPE_STRING:
+ case OBJ_TYPE_ARRAY:
+ default:
+ ERR(1);
+ }
+
+ pop (1);
+}
+
+static void _waitchildren(struct script *ctx)
+{
+ igt_waitchildren();
+}
+
+static void _xor(struct script *ctx)
+{
+ obj_t a, b;
+
+ check (2);
+ a = ostack_get (ctx, 0);
+ b = ostack_get (ctx, 1);
+ ERR (obj_type (a) != obj_type (b));
+ pop (2);
+
+ switch (obj_type (a)) {
+ case OBJ_TYPE_INTEGER:
+ ostack_push_integer (ctx, a.integer ^ b.integer);
+ case OBJ_TYPE_BOOLEAN:
+ ostack_push_boolean (ctx, a.boolean ^ b.boolean);
+ default:
+ ERR(1);
+ }
+}
+
+static void _debug_print(struct script *ctx)
+{
+ obj_t obj;
+
+ check (1);
+ obj = ostack_get (ctx, 0);
+ switch (obj_type(obj)) {
+ case OBJ_TYPE_NULL:
+ fprintf(stderr, "NULL\n");
+ break;
+
+ /* atomics */
+ case OBJ_TYPE_BOOLEAN:
+ fprintf (stderr, "boolean: %s\n",
+ obj.boolean ? "true" : "false");
+ break;
+ case OBJ_TYPE_INTEGER:
+ fprintf (stderr, "integer: %ld\n", obj.integer);
+ break;
+ case OBJ_TYPE_MARK:
+ fprintf (stderr, "mark\n");
+ break;
+ case OBJ_TYPE_NAME:
+ fprintf (stderr, "name: %s\n", (char *) obj.name);
+ break;
+ case OBJ_TYPE_OPERATOR:
+ fprintf (stderr, "operator: %p\n", obj.ptr);
+ break;
+ case OBJ_TYPE_REAL:
+ fprintf (stderr, "real: %g\n", obj.real);
+ break;
+
+ /* compound */
+ case OBJ_TYPE_ARRAY:
+ fprintf (stderr, "array\n");
+ break;
+ case OBJ_TYPE_DICTIONARY:
+ fprintf (stderr, "dictionary\n");
+ break;
+ case OBJ_TYPE_FILE:
+ fprintf (stderr, "file\n");
+ break;
+ case OBJ_TYPE_STRING:
+ fprintf (stderr, "string: %s\n", obj.string->string);
+ break;
+
+ default:
+ fprintf (stderr, "unknown type=%d, ptr=%p\n", obj.type, obj.ptr);
+ break;
+ }
+ pop (1);
+}
+
+static const struct opdef {
+ const char *name;
+ void (*op)(struct script *);
+} ops[] = {
+ { "<<", _mark },
+ { ">>", end_dict_construction },
+ { "[", _mark },
+ { "]", end_array_construction },
+ { "add", _add },
+ { "and", _and },
+ { "array", _array },
+ { "bind", _bind },
+ { "bitshift", _bitshift },
+ { "copy", _copy },
+ { "cvi", _cvi },
+ { "cvr", _cvr },
+ { "def", _def },
+ { "dict", _dict },
+ { "div", _div },
+ { "dup", _duplicate },
+ { "eq", _eq },
+ { "exch", _exch },
+ { "false", _false },
+ { "for", _for },
+ { "foreach", _foreach },
+ { "fork", _fork },
+ { "ge", _ge },
+ { "get", _get },
+ { "gt", _gt },
+ { "if", _if },
+ { "ifelse", _ifelse },
+ { "index", _index },
+ { "integer", _integer },
+ { "interrupt", _interrupt },
+ { "le", _le },
+ { "lt", _lt },
+ { "mark", _mark },
+ { "mod", _mod },
+ { "mul", _mul },
+ { "ne", _ne },
+ { "neg", _neg },
+ { "not", _not },
+ { "null", _null },
+ { "or", _or },
+ { "pop", _pop },
+ //{ "rand", NULL },
+ { "repeat", _repeat },
+ { "roll", _roll },
+ { "set", _set },
+ { "sub", _sub },
+ { "timeout", _timeout },
+ { "true", _true },
+ //{ "type", NULL },
+ { "undef", _undef },
+ { "unset", _unset },
+ { "waitchildren", _waitchildren },
+ //{ "where", NULL },
+ { "xor", _xor },
+
+ { "=", _debug_print },
+
+ { NULL, NULL },
+};
+
+void script_init_operators(struct script *ctx)
+{
+ struct dictionary *dict = systemdict(ctx);
+
+ for (const struct opdef *def = ops; def->name != NULL; def++)
+ dictionary_set(dict,
+ name_new_static(ctx, def->name).name,
+ new_operator(def->op));
+}
+
+static const struct idef {
+ const char *name;
+ long value;
+} idefs[] = {
+ { NULL, 0 }
+};
+
+static const struct rdef {
+ const char *name;
+ float value;
+} rdefs[] = {
+ { "math.pi", M_PI },
+ { "math.2pi", 2 * M_PI },
+ { "math.sqrt2", M_SQRT2 },
+ { "math.ln2", M_LN2 },
+
+ { NULL, 0 }
+};
+
+void script_init_constants(struct script *ctx)
+{
+ struct dictionary *dict = systemdict(ctx);
+
+ for (const struct idef *def = idefs; def->name != NULL; def++)
+ dictionary_set(dict,
+ name_new_static(ctx, def->name).name,
+ new_int(def->value));
+
+ for (const struct rdef *def = rdefs; def->name != NULL; def++)
+ dictionary_set(dict,
+ name_new_static(ctx, def->name).name,
+ new_real(def->value));
+}
new file mode 100644
@@ -0,0 +1,938 @@
+#include <limits.h> /* INT_MAX */
+#include <math.h> /* pow */
+#include <stdio.h> /* EOF */
+#include <stdint.h> /* for {INT,UINT}*_{MIN,MAX} */
+#include <stdlib.h> /* malloc/free */
+#include <string.h> /* memset */
+#include <assert.h>
+#include <zlib.h>
+
+#include "script.h"
+
+#if WORDS_BIGENDIAN
+#define le16(x) bswap_16(x)
+#define le32(x) bswap_32(x)
+#define be16(x) x
+#define be32(x) x
+#define to_be32(x) x
+#else
+#define le16(x) x
+#define le32(x) x
+#define be16(x) bswap_16(x)
+#define be32(x) bswap_32(x)
+#define to_be32(x) bswap_32(x)
+#endif
+
+/*
+ * whitespace:
+ * 0 - nul
+ * 9 - tab
+ * A - LF
+ * C - FF
+ * D - CR
+ *
+ * syntax delimiters
+ * ( = 28, ) = 29 - literal strings
+ * < = 3C, > = 3E - hex/base85 strings, dictionary name
+ * [ = 5B, ] = 5D - array
+ * { = 7B, } = 7C - procedure
+ * / = 5C - literal marker
+ * % = 25 - comment
+ */
+
+static void buffer_init(struct script *ctx, struct buffer *buffer)
+{
+ buffer->size = 16384;
+ buffer->base = script_alloc (ctx, 1, buffer->size);
+
+ buffer->ptr = buffer->base;
+ buffer->end = buffer->base + buffer->size;
+}
+
+static void buffer_fini(struct script *ctx, struct buffer *buffer)
+{
+ script_free (ctx, buffer->base);
+}
+
+static void _buffer_grow(struct scanner *scan)
+{
+ int offset = scan->buffer.ptr - scan->buffer.base;
+ char *base = script_realloc(scan->ctx, scan->buffer.base, 2, scan->buffer.size);
+
+ scan->buffer.base = base;
+ scan->buffer.size *= 2;
+ scan->buffer.ptr = base + offset;
+ scan->buffer.end = base + scan->buffer.size;
+}
+
+static inline void buffer_check(struct scanner *scan, int count)
+{
+ if (scan->buffer.ptr + count > scan->buffer.end)
+ _buffer_grow(scan);
+}
+
+static inline void buffer_add(struct buffer *buffer, int c)
+{
+ *buffer->ptr++ = c;
+}
+
+static inline void buffer_reset(struct buffer *buffer)
+{
+ buffer->ptr = buffer->base;
+}
+
+static void token_start(struct scanner *scan)
+{
+ buffer_reset (&scan->buffer);
+}
+
+static void token_add(struct scanner *scan, int c)
+{
+ buffer_check(scan, 1);
+ buffer_add(&scan->buffer, c);
+}
+
+static void token_add_unchecked(struct scanner *scan, int c)
+{
+ buffer_add(&scan->buffer, c);
+}
+
+bool parse_number(obj_t *obj, const char *s, int len)
+{
+ int radix = 0;
+ long long mantissa = 0;
+ int exponent = 0;
+ int sign = 1;
+ int decimal = -1;
+ int exponent_sign = 0;
+ const char * const end = s + len;
+
+ switch (*s) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ mantissa = *s - '0';
+ case '+':
+ break;
+ case '-':
+ sign = -1;
+ break;
+ case '.':
+ decimal = 0;
+ break;
+ default:
+ return false;
+ }
+
+ while (++s < end) {
+ if (*s < '0') {
+ if (*s == '.') {
+ if (radix)
+ return false;
+ if (decimal != -1)
+ return false;
+ if (exponent_sign)
+ return false;
+
+ decimal = 0;
+ } else if (*s == '!') {
+ if (radix)
+ return false;
+ if (decimal != -1)
+ return false;
+ if (exponent_sign)
+ return false;
+
+ radix = mantissa;
+ mantissa = 0;
+
+ if (radix < 2 || radix > 36)
+ return false;
+ } else
+ return false;
+ } else if (*s <= '9') {
+ int v = *s - '0';
+ if (radix && v >= radix)
+ return false;
+
+ if (exponent_sign) {
+ exponent = 10 * exponent + v;
+ } else {
+ if (radix)
+ mantissa = radix * mantissa + v;
+ else
+ mantissa = 10 * mantissa + v;
+ if (decimal != -1)
+ decimal++;
+ }
+ } else if (*s == 'E' || * s== 'e') {
+ if (radix == 0) {
+ if (s + 1 == end)
+ return false;
+
+ exponent_sign = 1;
+ if (s[1] == '-') {
+ exponent_sign = -1;
+ s++;
+ } else if (s[1] == '+')
+ s++;
+ } else {
+ int v = 0xe;
+
+ if (v >= radix)
+ return false;
+
+ mantissa = radix * mantissa + v;
+ }
+ } else if (*s < 'A') {
+ return false;
+ } else if (*s <= 'Z') {
+ int v = *s - 'A' + 0xA;
+
+ if (v >= radix)
+ return false;
+
+ mantissa = radix * mantissa + v;
+ } else if (*s < 'a') {
+ return false;
+ } else if (*s <= 'z') {
+ int v = *s - 'a' + 0xa;
+
+ if (v >= radix)
+ return false;
+
+ mantissa = radix * mantissa + v;
+ } else
+ return false;
+ }
+
+ if (exponent_sign || decimal != -1) {
+ if (mantissa == 0) {
+ obj->type = OBJ_TYPE_REAL;
+ obj->real = 0.;
+ return true;
+ } else {
+ int e;
+ double v;
+
+ v = mantissa;
+ e = exponent * exponent_sign;
+ if (decimal != -1)
+ e -= decimal;
+ switch (e) {
+ case -7: v *= 0.0000001; break;
+ case -6: v *= 0.000001; break;
+ case -5: v *= 0.00001; break;
+ case -4: v *= 0.0001; break;
+ case -3: v *= 0.001; break;
+ case -2: v *= 0.01; break;
+ case -1: v *= 0.1; break;
+ case 0: break;
+ case 1: v *= 10; break;
+ case 2: v *= 100; break;
+ case 3: v *= 1000; break;
+ case 4: v *= 10000; break;
+ case 5: v *= 100000; break;
+ case 6: v *= 1000000; break;
+ default:
+ v *= pow (10, e); /* XXX */
+ break;
+ }
+
+ obj->type = OBJ_TYPE_REAL;
+ obj->real = sign * v;
+ return true;
+ }
+ } else {
+ obj->type = OBJ_TYPE_INTEGER;
+ obj->integer = sign * mantissa;
+ return true;
+ }
+}
+
+static void token_end(struct scanner *scan, struct file *src)
+{
+ char *s;
+ obj_t obj;
+ int len;
+
+ /*
+ * Any token that consists entirely of regular characters and
+ * cannot be interpreted as a number is treated as a name object
+ * (more precisely, an executable name). All characters except
+ * delimiters and white-space characters can appear in names,
+ * including characters ordinarily considered to be punctuation.
+ */
+
+ if (scan->buffer.ptr == scan->buffer.base)
+ return;
+
+ s = scan->buffer.base;
+ len = scan->buffer.ptr - scan->buffer.base;
+
+ if (!scan->bind) {
+ if (s[0] == '{') { /* special case procedures */
+ if (scan->build_procedure.type != OBJ_TYPE_NULL)
+ stack_push(&scan->procedure_stack,
+ scan->build_procedure);
+
+ scan->build_procedure = array_new (scan->ctx, 0);
+ scan->build_procedure.type |= OBJ_ATTR_EXECUTABLE;
+ return;
+ } else if (s[0] == '}') {
+ if (scan->build_procedure.type == OBJ_TYPE_NULL)
+ longjmp (scan->ctx->error, -EINVAL);
+
+ if (scan->procedure_stack.len) {
+ obj_t next;
+
+ next = stack_peek(&scan->procedure_stack, 0);
+ array_append(next.array, scan->build_procedure);
+ scan->build_procedure = next;
+ scan->procedure_stack.len--;
+ } else {
+ ostack_push(scan->ctx, scan->build_procedure);
+ scan->build_procedure.type = OBJ_TYPE_NULL;
+ }
+
+ return;
+ }
+ }
+
+ if (s[0] == '/') {
+ if (len >= 2 && s[1] == '/') /* substituted name */
+ obj = name_lookup (scan->ctx, name_new(scan->ctx, s + 2, len - 2).name);
+ else /* literal name */
+ obj = name_new (scan->ctx, s + 1, len - 1);
+ } else {
+ if (!parse_number (&obj, s, len)) {
+ obj = name_new (scan->ctx, s, len);
+ obj.type |= OBJ_ATTR_EXECUTABLE;
+ }
+ }
+
+ /* consume whitespace after token, before calling the interpreter */
+ if (scan->build_procedure.type != OBJ_TYPE_NULL) {
+ array_append(scan->build_procedure.array, obj);
+ } else if (obj.type & OBJ_ATTR_EXECUTABLE) {
+ obj_execute(scan->ctx, obj);
+ obj_free(obj);
+ } else {
+ ostack_push(scan->ctx, obj);
+ }
+}
+
+static void string_add(struct scanner *scan, int c)
+{
+ buffer_check(scan, 1);
+ buffer_add(&scan->buffer, c);
+}
+
+static void string_end(struct scanner *scan)
+{
+ obj_t obj = string_new(scan->ctx,
+ scan->buffer.base,
+ scan->buffer.ptr - scan->buffer.base);
+ if (scan->build_procedure.type != OBJ_TYPE_NULL)
+ array_append(scan->build_procedure.array, obj);
+ else
+ ostack_push(scan->ctx, obj);
+}
+
+static int hex_value(int c)
+{
+ if (c < '0')
+ return EOF;
+ if (c <= '9')
+ return c - '0';
+ c |= 32;
+ if (c < 'a')
+ return EOF;
+ if (c <= 'f')
+ return c - 'a' + 0xa;
+ return EOF;
+}
+
+static void hex_add(struct scanner *scan, int c)
+{
+ if (scan->accumulator_count == 0) {
+ scan->accumulator |= hex_value (c) << 4;
+ scan->accumulator_count = 1;
+ } else {
+ scan->accumulator |= hex_value (c) << 0;
+ buffer_check(scan, 1);
+ buffer_add(&scan->buffer, scan->accumulator);
+
+ scan->accumulator = 0;
+ scan->accumulator_count = 0;
+ }
+}
+
+static void hex_end(struct scanner *scan)
+{
+ obj_t obj;
+
+ if (scan->accumulator_count)
+ hex_add(scan, '0');
+
+ obj = string_new(scan->ctx,
+ scan->buffer.base,
+ scan->buffer.ptr - scan->buffer.base);
+
+ if (scan->build_procedure.type != OBJ_TYPE_NULL)
+ array_append(scan->build_procedure.array, obj);
+ else
+ ostack_push(scan->ctx, obj);
+}
+
+static void base85_add(struct scanner *scan, int c)
+{
+ if (c == 'z') {
+ if (scan->accumulator_count != 0)
+ longjmp (scan->ctx->error, -EINVAL);
+
+ buffer_check (scan, 4);
+ buffer_add (&scan->buffer, 0);
+ buffer_add (&scan->buffer, 0);
+ buffer_add (&scan->buffer, 0);
+ buffer_add (&scan->buffer, 0);
+ } else if (c < '!' || c > 'u') {
+ longjmp (scan->ctx->error, -EINVAL);
+ } else {
+ scan->accumulator = scan->accumulator*85 + c - '!';
+ if (++scan->accumulator_count == 5) {
+ buffer_check (scan, 4);
+ buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
+ buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff);
+ buffer_add (&scan->buffer, (scan->accumulator >> 8) & 0xff);
+ buffer_add (&scan->buffer, (scan->accumulator >> 0) & 0xff);
+
+ scan->accumulator = 0;
+ scan->accumulator_count = 0;
+ }
+ }
+}
+
+static void base85_end(struct scanner *scan, bool deflate)
+{
+ obj_t obj;
+
+ buffer_check(scan, 4);
+
+ switch (scan->accumulator_count) {
+ case 0:
+ break;
+ case 1:
+ longjmp (scan->ctx->error, -EINVAL);
+ break;
+
+ case 2:
+ scan->accumulator = scan->accumulator * (85*85*85) + 85*85*85 -1;
+ buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
+ break;
+ case 3:
+ scan->accumulator = scan->accumulator * (85*85) + 85*85 -1;
+ buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
+ buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff);
+ break;
+ case 4:
+ scan->accumulator = scan->accumulator * 85 + 84;
+ buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
+ buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff);
+ buffer_add (&scan->buffer, (scan->accumulator >> 8) & 0xff);
+ break;
+ }
+
+ if (deflate) {
+ uLongf len = be32(*(uint32_t *) scan->buffer.base);
+ Bytef *source = (Bytef *)(scan->buffer.base + sizeof (uint32_t));
+
+ obj = string_deflate_new(scan->ctx, source,
+ (Bytef *) scan->buffer.ptr - source, len);
+ } else {
+ obj = string_new(scan->ctx,
+ scan->buffer.base,
+ scan->buffer.ptr - scan->buffer.base);
+ }
+
+ if (scan->build_procedure.type != OBJ_TYPE_NULL)
+ array_append(scan->build_procedure.array, obj);
+ else
+ ostack_push(scan->ctx, obj);
+}
+
+static void base64_add(struct scanner *scan, int c)
+{
+ int val;
+
+ /* Convert Base64 character to its 6 bit nibble */
+ val = scan->accumulator;
+ if (c =='/') {
+ val = (val << 6) | 63;
+ } else if (c =='+') {
+ val = (val << 6) | 62;
+ } else if (c >='A' && c <='Z') {
+ val = (val << 6) | (c -'A');
+ } else if (c >='a' && c <='z') {
+ val = (val << 6) | (c -'a' + 26);
+ } else if (c >='0' && c <='9') {
+ val = (val << 6) | (c -'0' + 52);
+ }
+
+ buffer_check (scan, 1);
+ switch (scan->accumulator_count++) {
+ case 0:
+ break;
+
+ case 1:
+ buffer_add (&scan->buffer, (val >> 4) & 0xFF);
+ val &= 0xF;
+ break;
+
+ case 2:
+ buffer_add (&scan->buffer, (val >> 2) & 0xFF);
+ val &= 0x3;
+ break;
+
+ case 3:
+ buffer_add (&scan->buffer, (val >> 0) & 0xFF);
+ scan->accumulator_count = 0;
+ val = 0;
+ break;
+ }
+
+ if (c == '=') {
+ scan->accumulator_count = 0;
+ scan->accumulator = 0;
+ } else {
+ scan->accumulator = val;
+ }
+}
+
+static void base64_end(struct scanner *scan)
+{
+ obj_t obj;
+
+ switch (scan->accumulator_count) {
+ case 0:
+ break;
+ case 2:
+ base64_add(scan, (scan->accumulator << 2) & 0x3f);
+ base64_add(scan, '=');
+ break;
+ case 1:
+ base64_add(scan, (scan->accumulator << 4) & 0x3f);
+ base64_add(scan, '=');
+ base64_add(scan, '=');
+ break;
+ }
+
+ obj = string_new(scan->ctx,
+ scan->buffer.base,
+ scan->buffer.ptr - scan->buffer.base);
+
+ if (scan->build_procedure.type != OBJ_TYPE_NULL)
+ array_append(scan->build_procedure.array, obj);
+ else
+ ostack_push(scan->ctx, obj);
+}
+
+void scan_file(struct file *src)
+{
+ struct script *ctx = src->base.ctx;
+ struct scanner *scan = &ctx->scanner;
+ int c, next;
+ int deflate = 0;
+ int string_p;
+
+scan_none:
+ while ((c = file_getc (src)) != EOF) {
+ obj_t obj = { OBJ_TYPE_NULL };
+
+ switch (c) {
+ case 0xa:
+ scan->line_number++;
+ case 0x0:
+ case 0x9:
+ case 0xc:
+ case 0xd:
+ case 0x20: /* ignore whitespace */
+ break;
+
+ case '%':
+ goto scan_comment;
+
+ case '(':
+ goto scan_string;
+
+ case '[': /* needs special case */
+ case ']':
+ case '{':
+ case '}':
+ token_start (scan);
+ token_add_unchecked (scan, c);
+ token_end (scan, src);
+ goto scan_none;
+
+ case '<':
+ next = file_getc (src);
+ switch (next) {
+ case EOF:
+ file_putc (src, '<');
+ return;
+ case '<':
+ /* dictionary name */
+ token_start (scan);
+ token_add_unchecked (scan, '<');
+ token_add_unchecked (scan, '<');
+ token_end (scan, src);
+ goto scan_none;
+ case '|':
+ deflate = 1;
+ case '~':
+ goto scan_base85;
+ case '{':
+ goto scan_base64;
+ default:
+ file_putc (src, next);
+ goto scan_hex;
+ }
+ break;
+
+ case '#': /* PDF 1.2 escape code */
+ {
+ int c_hi = file_getc (src);
+ int c_lo = file_getc (src);
+ c = (hex_value (c_hi) << 4) | hex_value (c_lo);
+ }
+ /* fall-through */
+ default:
+ /* XXX can use 128+ for binary tokens */
+ token_start(scan);
+ token_add_unchecked (scan, c);
+ goto scan_token;
+ }
+
+ if (obj.type != OBJ_TYPE_NULL) {
+ if (scan->build_procedure.type != OBJ_TYPE_NULL) {
+ array_append(scan->build_procedure.array, obj);
+ } else if (obj.type & OBJ_ATTR_EXECUTABLE) {
+ obj_execute(ctx, obj);
+ obj_free(obj);
+ } else {
+ ostack_push(ctx, obj);
+ }
+ }
+ }
+ return;
+
+scan_token:
+ while ((c = file_getc (src)) != EOF) {
+ switch (c) {
+ case 0xa:
+ scan->line_number++;
+ case 0x0:
+ case 0x9:
+ case 0xc:
+ case 0xd:
+ case 0x20:
+ token_end (scan, src);
+ goto scan_none;
+
+ /* syntax delimiters */
+ case '%':
+ token_end (scan, src);
+ goto scan_comment;
+ /* syntax error? */
+ case '(':
+ token_end (scan, src);
+ goto scan_string;
+ /* XXX syntax error? */
+ case ')':
+ token_end (scan, src);
+ goto scan_none;
+ case '/':
+ /* need to special case '^//?' */
+ if (scan->buffer.ptr > scan->buffer.base+1 ||
+ scan->buffer.base[0] != '/')
+ {
+ token_end (scan, src);
+ token_start (scan);
+ }
+ token_add_unchecked (scan, '/');
+ goto scan_token;
+
+ case '{':
+ case '}':
+ case ']':
+ token_end(scan, src);
+ token_start(scan);
+ token_add_unchecked(scan, c);
+ token_end(scan, src);
+ goto scan_none;
+
+ case '<':
+ file_putc (src, '<');
+ token_end (scan, src);
+ goto scan_none;
+
+ case '#': /* PDF 1.2 escape code */
+ {
+ int c_hi = file_getc (src);
+ int c_lo = file_getc (src);
+ c = (hex_value (c_hi) << 4) | hex_value (c_lo);
+ }
+ /* fall-through */
+ default:
+ token_add (scan, c);
+ break;
+ }
+ }
+ token_end(scan, src);
+ return;
+
+scan_comment:
+ /* discard until newline */
+ while ((c = file_getc (src)) != EOF) {
+ switch (c) {
+ case 0xa:
+ scan->line_number++;
+ case 0xc:
+ goto scan_none;
+ }
+ }
+ return;
+
+scan_string:
+ buffer_reset (&scan->buffer);
+ string_p = 1;
+ while ((c = file_getc (src)) != EOF) {
+ switch (c) {
+ case '\\': /* escape */
+ next = file_getc (src);
+ switch (next) {
+ case EOF:
+ longjmp (ctx->error, -EINVAL);
+
+ case 'n':
+ string_add (scan, '\n');
+ break;
+ case 'r':
+ string_add (scan, '\r');
+ break;
+ case 't':
+ string_add (scan, '\t');
+ break;
+ case 'b':
+ string_add (scan, '\b');
+ break;
+ case 'f':
+ string_add (scan, '\f');
+ break;
+ case '\\':
+ string_add (scan, '\\');
+ break;
+ case '(':
+ string_add (scan, '(');
+ break;
+ case ')':
+ string_add (scan, ')');
+ break;
+
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ { /* octal code: \d{1,3} */
+ int i;
+
+ c = next - '0';
+
+ for (i = 0; i < 2; i++) {
+ next = file_getc (src);
+ switch (next) {
+ case EOF:
+ return;
+
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ c = 8*c + next-'0';
+ break;
+
+ default:
+ file_putc (src, next);
+ goto octal_code_done;
+ }
+ }
+octal_code_done:
+ string_add (scan, c);
+ }
+ break;
+
+ case 0xa:
+ /* skip the newline */
+ next = file_getc (src); /* might be compound LFCR */
+ switch (next) {
+ case EOF:
+ return;
+ case 0xc:
+ break;
+ default:
+ file_putc (src, next);
+ break;
+ }
+ scan->line_number++;
+ break;
+ case 0xc:
+ break;
+
+ default:
+ /* ignore the '\' */
+ break;
+ }
+ break;
+
+ case '(':
+ string_p++;
+ string_add (scan, c);
+ break;
+
+ case ')':
+ if (--string_p == 0) {
+ string_end (scan);
+ goto scan_none;
+ }
+ /* fall through */
+ default:
+ string_add (scan, c);
+ break;
+ }
+ }
+ longjmp (ctx->error, -EINVAL);
+
+scan_hex:
+ buffer_reset (&scan->buffer);
+ scan->accumulator_count = 0;
+ scan->accumulator = 0;
+ while ((c = file_getc (src)) != EOF) {
+ switch (c) {
+ case 0xa:
+ scan->line_number++;
+ case 0x0:
+ case 0x9:
+ case 0xc:
+ case 0xd:
+ case 0x20: /* ignore whitespace */
+ break;
+
+ case '>':
+ hex_end (scan); /* fixup odd digit with '0' */
+ goto scan_none;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ hex_add(scan, c);
+ break;
+
+ default:
+ longjmp(ctx->error, -EINVAL);
+ }
+ }
+ longjmp (ctx->error, -EINVAL);
+
+scan_base85:
+ buffer_reset (&scan->buffer);
+ scan->accumulator = 0;
+ scan->accumulator_count = 0;
+ while ((c = file_getc (src)) != EOF) {
+ switch (c) {
+ case '~':
+ next = file_getc (src);
+ switch (next) {
+ case EOF:
+ return;
+
+ case '>':
+ base85_end(scan, deflate);
+ deflate = 0;
+ goto scan_none;
+ }
+ file_putc (src, next);
+
+ /* fall-through */
+ default:
+ base85_add(scan, c);
+ break;
+ }
+ }
+ longjmp (ctx->error, -EINVAL);
+
+scan_base64:
+ buffer_reset (&scan->buffer);
+ scan->accumulator = 0;
+ scan->accumulator_count = 0;
+ while ((c = file_getc (src)) != EOF) {
+ switch (c) {
+ case '}':
+ next = file_getc (src);
+ switch (next) {
+ case EOF:
+ return;
+
+ case '>':
+ base64_end (scan);
+ goto scan_none;
+ }
+ longjmp (ctx->error, -EINVAL);
+
+ default:
+ base64_add(scan, c);
+ break;
+ }
+ }
+ longjmp (ctx->error, -EINVAL);
+}
+
+void scanner_init(struct script *ctx, struct scanner *scanner)
+{
+ memset(scanner, 0, sizeof(struct scanner));
+ scanner->ctx = ctx;
+
+ buffer_init(ctx, &scanner->buffer);
+ stack_init(ctx, &scanner->procedure_stack, 4);
+
+ scanner->bind = 0;
+}
+
+void scanner_fini (struct scanner *scanner)
+{
+ buffer_fini(scanner->ctx, &scanner->buffer);
+ stack_fini(&scanner->procedure_stack);
+ if (scanner->build_procedure.type != OBJ_TYPE_NULL)
+ obj_free (scanner->build_procedure);
+}
new file mode 100644
@@ -0,0 +1,532 @@
+#ifndef SCRIPT_H
+#define SCRIPT_H
+
+#include <errno.h>
+#include <setjmp.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <byteswap.h>
+
+struct script;
+
+enum object_type {
+ OBJ_TYPE_NULL = 0,
+
+ /* atomics */
+ OBJ_TYPE_BOOLEAN,
+ OBJ_TYPE_INTEGER,
+ OBJ_TYPE_MARK,
+ OBJ_TYPE_NAME,
+ OBJ_TYPE_OPERATOR,
+ OBJ_TYPE_REAL,
+
+ /* compound */
+ OBJ_TYPE_ARRAY = 0x8,
+ OBJ_TYPE_DICTIONARY,
+ OBJ_TYPE_FILE,
+ OBJ_TYPE_STRING,
+
+ /* extension */
+ OBJ_TYPE_EXTENSION_0 = 0x10,
+} object_type_t;
+
+#define OBJ_IS_ATOM(OBJ) (((OBJ)->type & OBJ_TYPE_MASK) < 0x08)
+#define OBJ_IS_COMPOUND(OBJ) ((OBJ).type & 0xf8)
+
+enum { /* attributes */
+ OBJ_ATTR_EXECUTABLE = 1 << 8,
+ OBJ_ATTR_WRITABLE = 1 << 9
+};
+#define OBJ_ATTR_MASK (OBJ_ATTR_EXECUTABLE | OBJ_ATTR_WRITABLE)
+#define OBJ_TYPE_MASK (~OBJ_ATTR_MASK)
+
+typedef bool
+(*hash_predicate_func_t) (void *entry);
+
+typedef void
+(*hash_callback_func_t) (void *entry,
+ void *closure);
+
+typedef bool
+(*hash_keys_equal_func_t) (const void *key_a, const void *key_b);
+
+typedef long name_t;
+
+typedef struct stack_object {
+ enum object_type type;
+ union {
+ bool boolean;
+ long integer;
+ float real;
+
+ void *ptr;
+ name_t name;
+ void (*op)(struct script *);
+
+ struct heap_object *object;
+ struct extension_object *extension;
+
+ struct array *array;
+ struct dictionary *dictionary;
+ struct file *file;
+ struct string *string;
+ };
+} obj_t;
+
+struct heap_object {
+ enum object_type type;
+ unsigned int ref;
+ struct script *ctx;
+};
+
+struct hash_entry {
+ unsigned long hash;
+};
+
+struct hash_table_arrangement {
+ unsigned long high_water_mark;
+ unsigned long size;
+ unsigned long rehash;
+};
+
+struct hash_table {
+ struct script *ctx;
+ hash_keys_equal_func_t keys_equal;
+
+ const struct hash_table_arrangement *arrangement;
+ struct hash_entry **entries;
+
+ unsigned long live_entries;
+ unsigned long used_entries;
+ unsigned long iterating; /* Iterating, no insert, no resize */
+};
+
+
+/* simple, embedded doubly-linked links */
+struct script_list {
+ struct script_list *next, *prev;
+};
+
+struct buffer {
+ char *base, *ptr, *end;
+ unsigned int size;
+};
+
+struct stack {
+ struct script *ctx;
+ obj_t *objects;
+ long len;
+ long size;
+};
+
+struct array {
+ struct heap_object base;
+ struct stack stack;
+};
+
+struct dictionary_entry {
+ struct hash_entry hash_entry;
+ obj_t obj;
+};
+
+struct dictionary {
+ struct heap_object base;
+ struct hash_table ht;
+};
+
+struct string {
+ struct heap_object base;
+ long len;
+ long deflate;
+ enum {
+ NONE,
+ ZLIB,
+ LZO,
+ } method;
+ char *string;
+};
+
+struct file_filter_funcs {
+ int (*filter_getc) (struct file *);
+ void (*filter_putc) (struct file *, int);
+ int (*filter_read) (struct file *, uint8_t *, int);
+ void (*filter_destroy) (struct script *, void *);
+};
+
+struct file {
+ struct heap_object base;
+ enum {
+ STDIO,
+ BYTES,
+ PROCEDURE,
+ FILTER
+ } type;
+ unsigned int flags;
+ void *src;
+ void *data;
+ uint8_t *bp;
+ int rem;
+ const struct file_filter_funcs *filter;
+};
+
+#if 0
+union union_object {
+ void *ptr[2];
+ struct stack stack;
+ struct array arry;
+ struct dictionary dictionary;
+ csi_matrix_t matrix;
+ struct string string;
+ struct file file;
+ obj_t object;
+};
+#endif
+
+struct scanner {
+ struct script *ctx;
+
+ int depth;
+ int bind;
+
+ struct buffer buffer;
+ struct stack procedure_stack;
+ obj_t build_procedure;
+
+ unsigned int accumulator;
+ unsigned int accumulator_count;
+
+ unsigned int line_number;
+};
+
+struct perm_chunk;
+
+struct script {
+ jmp_buf error;
+ void *private;
+
+ struct hash_table strings;
+
+ struct stack ostack;
+ struct stack dstack;
+
+ struct scanner scanner;
+
+ struct perm_chunk *perm_chunk;
+ struct {
+ struct perm_chunk *chunk;
+ void *free_list;
+ } slabs[16];
+ struct array *free_array;
+ struct dictionary *free_dictionary;
+ struct string *free_string;
+};
+
+struct script_operator_def {
+ const char *name;
+ void (*op)(struct script *);
+};
+
+struct script_integer_constant_def {
+ const char *name;
+ long value;
+};
+
+struct script_real_constant_def {
+ const char *name;
+ float value;
+};
+
+static inline obj_t new_heap_object(struct heap_object *obj,
+ struct script *ctx,
+ enum object_type type)
+{
+ obj->type = type;
+ obj->ref = 1;
+ obj->ctx = ctx;
+
+ return (obj_t){.type = type, .object = obj};
+}
+
+struct extension {
+ void (*free)(void *);
+};
+
+struct extension_object {
+ struct heap_object heap;
+ const struct extension *ops;
+};
+
+static inline obj_t new_extension_object(struct extension_object *obj,
+ struct script *ctx,
+ enum object_type type,
+ const struct extension *ops)
+{
+ obj->heap.type = type;
+ obj->heap.ref = 1;
+ obj->heap.ctx = ctx;
+ obj->ops = ops;
+
+ return (obj_t){.type = type, .extension = obj};
+}
+
+obj_t file_new(struct script *ctx, const char *path, const char *mode);
+obj_t file_new_for_stream (struct script *ctx, FILE *stream);
+obj_t file_new_for_bytes (struct script *ctx,
+ const char *bytes, unsigned int length);
+obj_t file_new_from_string (struct script *ctx, struct string *src);
+obj_t file_new_ascii85_decode (struct script *ctx,
+ struct dictionary *dict, obj_t src);
+obj_t file_new_deflate_decode (struct script *ctx,
+ struct dictionary *dict,
+ obj_t src);
+void file_execute (struct file *obj);
+int file_getc (struct file *obj);
+int file_read (struct file *obj, void *buf, int len);
+void file_putc (struct file *obj, int c);
+void file_flush(struct file *obj);
+void file_close(struct file *obj);
+void file_free(struct file *obj);
+obj_t file_as_string(struct file *file);
+
+void
+hash_table_init(struct script *ctx,
+ struct hash_table *ht,
+ hash_keys_equal_func_t keys_equal);
+
+void
+hash_table_fini(struct hash_table *ht);
+
+void *
+hash_table_lookup(struct hash_table *ht, const struct hash_entry *key);
+
+void *
+hash_table_lookup_unique(struct hash_table *ht, unsigned long id);
+
+void
+hash_table_insert(struct hash_table *ht,
+ struct hash_entry *entry);
+
+void
+hash_table_remove(struct hash_table *ht,
+ struct hash_entry *key);
+
+void
+hash_table_foreach (struct hash_table *ht,
+ hash_callback_func_t hash_callback,
+ void *closure);
+
+void *script_alloc(struct script *ctx, long count, long size);
+void * script_alloc0(struct script *ctx, long count, long size);
+void * script_realloc(struct script *ctx, void *ptr, long count, long size);
+void script_free(struct script *ctx, void *ptr);
+
+void * script_slab_alloc(struct script *ctx, int size);
+void script_slab_free(struct script *ctx, void *ptr, int size);
+
+void * script_perm_alloc(struct script *ctx, int size);
+
+void name_define(struct script *ctx, name_t name, obj_t obj);
+obj_t name_lookup(struct script *ctx, name_t name);
+void name_undefine(struct script *ctx, name_t name);
+
+void *intern_string(struct script *ctx, const char *str, int len);
+
+obj_t array_new(struct script *ctx, long initial_size);
+obj_t array_get(struct array *array, long elem);
+void array_set(struct array *array, long elem, obj_t value);
+void array_append(struct array *array, obj_t obj);
+void array_execute(struct array *array);
+void array_free(struct array *array);
+
+static inline obj_t
+new_bool(bool v)
+{
+ return (obj_t){.type = OBJ_TYPE_BOOLEAN, .boolean =v };
+}
+
+obj_t dictionary_new(struct script *ctx);
+void dictionary_set(struct dictionary *dict, name_t name, obj_t value);
+obj_t dictionary_get(struct dictionary *dict, name_t name);
+bool dictionary_has(struct dictionary *dict, name_t name);
+void dictionary_unset(struct dictionary *dict, name_t name);
+void dictionary_free(struct dictionary *dict);
+
+static inline obj_t
+new_int(long v)
+{
+ return (obj_t){.type = OBJ_TYPE_INTEGER, .integer = v};
+}
+
+obj_t name_new(struct script *ctx, const char *str, int len);
+obj_t name_new_static(struct script *ctx, const char *str);
+
+static inline obj_t
+new_operator(void (*op)(struct script *))
+{
+ return (obj_t){.type = OBJ_TYPE_OPERATOR | OBJ_ATTR_EXECUTABLE, .op=op};
+}
+
+static inline obj_t
+new_real(float v)
+{
+ return (obj_t){ .type = OBJ_TYPE_REAL, .real = v};
+}
+
+obj_t string_new(struct script *ctx, const char *str, int len);
+obj_t string_deflate_new (struct script *ctx, void *bytes, int in_len, int out_len);
+obj_t string_new_from_bytes (struct script *ctx, char *bytes, unsigned int len);
+void string_free(struct string *string);
+
+void obj_execute(struct script *ctx, obj_t obj);
+static inline obj_t obj_reference(obj_t obj)
+{
+ if (OBJ_IS_COMPOUND(obj))
+ obj.object->ref++;
+
+ return obj;
+}
+void obj_free(obj_t obj);
+obj_t obj_as_file(struct script *ctx, obj_t src);
+int obj_compare(obj_t a, obj_t b);
+
+void scan_file(struct file *src);
+
+void scanner_init(struct script *ctx, struct scanner *scanner);
+void scanner_fini(struct scanner *scanner);
+bool parse_number(obj_t *obj, const char *s, int len);
+
+void stack_init(struct script *ctx, struct stack *stack, long size);
+void stack_roll(struct stack *stack, long mod, long n);
+void stack_grow(struct stack *stack, long cnt);
+void stack_push_internal(struct stack *stack, obj_t obj);
+obj_t stack_peek(struct stack *stack, long i);
+void stack_pop(struct stack *stack, long count);
+void stack_exch(struct stack *stack);
+void stack_fini(struct stack *stack);
+
+static inline int obj_type(const obj_t obj)
+{
+ return obj.type & OBJ_TYPE_MASK;
+}
+
+static inline bool obj_is_procedure(const obj_t obj)
+{
+ return obj.type == (OBJ_TYPE_ARRAY | OBJ_ATTR_EXECUTABLE);
+}
+
+static inline bool obj_is_number(const obj_t obj)
+{
+ switch (obj_type(obj)) {
+ case OBJ_TYPE_BOOLEAN:
+ case OBJ_TYPE_INTEGER:
+ case OBJ_TYPE_REAL:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static inline double
+number_get_value (const obj_t obj)
+{
+ switch (obj_type(obj)) {
+ case OBJ_TYPE_BOOLEAN: return obj.boolean;
+ case OBJ_TYPE_INTEGER: return obj.integer;
+ case OBJ_TYPE_REAL: return obj.real;
+ default: return 0.;
+ }
+}
+
+static inline void
+stack_push(struct stack *stack, obj_t obj)
+{
+ if (stack->len == stack->size)
+ return stack_push_internal(stack, obj);
+
+ stack->objects[stack->len++] = obj;
+}
+
+static inline void
+ostack_check(struct script *ctx, long count)
+{
+ if (ctx->ostack.len < count)
+ longjmp(ctx->error, -EINVAL);
+}
+
+static inline obj_t ostack_get(struct script *ctx, long i)
+{
+ return ctx->ostack.objects[ctx->ostack.len - i -1];
+}
+
+static inline obj_t ostack_copy(struct script *ctx, long i)
+{
+ return obj_reference(ostack_get(ctx, i));
+}
+
+static inline obj_t *ostack_inplace(struct script *ctx, long i)
+{
+ return &ctx->ostack.objects[ctx->ostack.len - i -1];
+}
+
+static inline void ostack_pop(struct script *ctx, long count)
+{
+ do
+ obj_free(ctx->ostack.objects[--ctx->ostack.len]);
+ while (--count);
+}
+
+static inline void ostack_push_copy(struct script *ctx, obj_t obj)
+{
+ stack_push(&ctx->ostack, obj_reference(obj));
+}
+
+static inline void ostack_push(struct script *ctx, obj_t obj)
+{
+ return stack_push(&ctx->ostack, obj);
+}
+
+static inline void ostack_push_boolean (struct script *ctx, bool v)
+{
+ return stack_push(&ctx->ostack, new_bool(v));
+}
+static inline void ostack_push_integer(struct script *ctx, long v)
+{
+ return stack_push(&ctx->ostack, new_int(v));
+}
+static inline void ostack_push_mark (struct script *ctx)
+{
+ return stack_push(&ctx->ostack, (obj_t){.type = OBJ_TYPE_MARK});
+}
+static inline obj_t null(void)
+{
+ return (obj_t){.type = OBJ_TYPE_NULL};
+}
+static inline bool is_null(obj_t obj)
+{
+ return obj.type == OBJ_TYPE_NULL;
+}
+static inline void ostack_push_null(struct script *ctx)
+{
+ return stack_push(&ctx->ostack, null());
+}
+static inline void ostack_push_real(struct script *ctx, float v)
+{
+ return stack_push(&ctx->ostack, new_real(v));
+}
+
+struct dictionary *systemdict(struct script *ctx);
+void systemdict_execute(struct script *ctx, const char *name);
+struct dictionary *extensiondict(struct script *ctx);
+void script_init_operators(struct script *ctx);
+void script_init_constants(struct script *ctx);
+
+int script_init(struct script *ctx);
+int script_run(struct script *ctx, const char *filename);
+void script_fini(struct script *ctx);
+
+void script_print_strings(struct script *ctx, FILE *file);
+
+#endif /* SCRIPT_H */
new file mode 100644
@@ -0,0 +1,22 @@
+(intel) driver
+
+/engines get { % driver engine --
+ /engine exch def
+ << /size 4096 >> object { % driver object child --
+ 3 -1 roll { % object child driver batch
+ 4 -2 roll dup store
+ } batch << /engine engine /context 5 index context >> exec
+ } -1 exch fork waitchildren
+ verify
+} foreach
+
+<< /size 4096 >> object exch
+{ % object driver child --
+ exch /engines get { /engine exch def % object child driver --
+ (intel) driver { % object child driver driver batch --
+ 4 index 4 index engine store
+ } batch << /engine engine >> exec
+ pop pop
+ } foreach
+} -1 exch fork waitchildren
+exch verify
new file mode 100644
@@ -0,0 +1,16 @@
+(intel) driver {
+ << /size 4096 >> object /global exch def
+ -1 { 1 add { pop exch % count driver --
+ << /size 4096 /caching 0 >> object exch
+ << /size 4096 /caching 1 >> object exch
+ /engines get { /engine exch def % count uncached cached driver --
+ { % count uncached cached driver batch --
+ global 5 index engine store
+ 3 index engine 16!c0ffee store
+ 2 index engine 16!deadbeef store
+ } batch << /engine engine >> exec pop
+ } foreach exch verify exch verify
+ } 1 exch fork } 1024 exch repeat waitchildren
+ global verify
+ pop
+} bind 10 exch timeout
new file mode 100644
@@ -0,0 +1,17 @@
+(intel) driver /engines get
+{ % driver engine --
+ { % driver engine child --
+ pop exch null batch
+ { { % engine driver batch --
+ << /engine 4 index >> exec wait
+ } interrupt } 10 exch timeout
+ } 1 exch fork pop
+} bind foreach waitchildren
+
+{ % driver child --
+ pop null batch exch % batch driver --
+ { { /engines get { % batch driver engine --
+ 3 -1 roll exch % driver batch engine --
+ << /engine 3 -1 roll >> exec exch % batch driver --
+ } foreach exch wait exch } interrupt } 10 exch timeout
+} bind -1 exch fork waitchildren
new file mode 100644
@@ -0,0 +1,14 @@
+(intel) driver {
+ << /size 4096 >> object /global exch def
+ -1 { 1 add { pop exch % count driver --
+ << /size 4096 /caching 0 >> object exch
+ << /size 4096 /caching 1 >> object exch
+ /engines get { /engine exch def % count uncached cached driver --
+ global 4 index engine write
+ 2 index engine 16!c0ffee write
+ 1 index engine 16!deadbeef write
+ } foreach exch verify exch verify
+ } 1 exch fork } 1024 exch repeat waitchildren
+ global verify
+ pop
+} bind 10 exch timeout
new file mode 100644
@@ -0,0 +1,119 @@
+#include <string.h>
+
+#include "script.h"
+
+void stack_init(struct script *ctx, struct stack *stack, long size)
+{
+ stack->ctx = ctx;
+ stack->len = 0;
+ stack->size = size;
+ stack->objects = script_alloc (ctx, size, sizeof (obj_t));
+}
+
+void
+stack_fini(struct stack *stack)
+{
+ long n;
+
+ for (n = 0; n < stack->len; n++)
+ obj_free(stack->objects[n]);
+
+ script_free(stack->ctx, stack->objects);
+}
+
+void stack_roll(struct stack *stack, long mod, long n)
+{
+ obj_t stack_copy[128];
+ obj_t *copy;
+ long last, i, len;
+
+ switch (mod) { /* special cases */
+ case 1:
+ last = stack->len - 1;
+ stack_copy[0] = stack->objects[last];
+ for (i = last; --n; i--)
+ stack->objects[i] = stack->objects[i-1];
+ stack->objects[i] = stack_copy[0];
+ return;
+
+ case -1:
+ last = stack->len - 1;
+ stack_copy[0] = stack->objects[i = last - n + 1];
+ for (; --n; i++)
+ stack->objects[i] = stack->objects[i+1];
+ stack->objects[i] = stack_copy[0];
+ return;
+ }
+
+ /* fall back to a copy */
+ if (n > sizeof(stack_copy)/sizeof(stack_copy[0]))
+ copy = script_alloc (stack->ctx, n, sizeof (obj_t));
+ else
+ copy = stack_copy;
+
+ i = stack->len - n;
+ memcpy (copy, stack->objects + i, n * sizeof (obj_t));
+ mod = -mod;
+ if (mod < 0)
+ mod += n;
+ last = mod;
+ for (len = n; n--; i++) {
+ stack->objects[i] = copy[last];
+ if (++last == len)
+ last = 0;
+ }
+
+ if (copy != stack_copy)
+ script_free (stack->ctx, copy);
+}
+
+void stack_grow(struct stack *stack, long cnt)
+{
+ if (cnt <= stack->size)
+ return;
+
+ do {
+ stack->size *= 2;
+ } while (stack->size <= cnt);
+
+ stack->objects = script_realloc(stack->ctx,
+ stack->objects,
+ stack->size, sizeof (obj_t));
+}
+
+void stack_push_internal(struct stack *stack, obj_t obj)
+{
+ stack_grow(stack, stack->size + 1);
+ stack->objects[stack->len++] = obj;
+}
+
+obj_t stack_peek(struct stack *stack, long i)
+{
+ if (stack->len < i)
+ longjmp(stack->ctx->error, -EINVAL);
+
+ return stack->objects[stack->len - i -1];
+}
+
+void stack_pop(struct stack *stack, long count)
+{
+ if (stack->len < count)
+ count = stack->len;
+
+ while (count--)
+ obj_free(stack->objects[--stack->len]);
+}
+
+void stack_exch(struct stack *stack)
+{
+ obj_t tmp;
+ long n;
+
+ if (stack->len < 2)
+ longjmp(stack->ctx->error, -EINVAL);
+
+ n = stack->len - 1;
+ tmp = stack->objects[n];
+ stack->objects[n] = stack->objects[n - 1];
+ stack->objects[n - 1] = tmp;
+}
@@ -451,7 +451,8 @@ unsigned intel_detect_and_clear_missed_interrupts(int fd)
unsigned missed = 0;
FILE *file;
- gem_quiescent_gpu(fd);
+ if (fd != -1)
+ gem_quiescent_gpu(fd);
file = igt_debugfs_fopen("i915_ring_missed_irq", "r");
if (file) {
@@ -73,7 +73,7 @@ extern const struct intel_execution_engine {
for (const struct intel_execution_engine *e__ = intel_execution_engines;\
e__->name; \
e__++) \
- for_if (gem_has_ring(fd, flags__ = e__->exec_id | e__->flags))
+ for_if (gem_has_ring(fd__, flags__ = e__->exec_id | e__->flags))
#endif /* IGT_GT_H */