#include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/resource.h> #include <sys/stat.h> #define static_assert(c) _Static_assert(c, #c) typedef uint32_t value; static_assert(_Alignof(value) <= 4); struct closure { void *native_code; uint16_t num_params; uint16_t env_size; value env_items[]; }; struct tuple { uint16_t num_items; uint16_t layout; value items[]; }; struct variant { uint16_t label; value item; }; struct byte_vector { uint32_t num_bytes; char bytes[]; }; static_assert(_Alignof(struct closure) <= 8); static_assert(_Alignof(struct tuple) <= 4); static_assert(_Alignof(struct variant) <= 4); static_assert(_Alignof(struct byte_vector) <= 4); enum { TAG_HEAP_CLOSURE = 0x1, TAG_HEAP_TUPLE = 0x3, TAG_HEAP_VARIANT = 0x5, TAG_HEAP_BYTE_VECTOR = 0x7, TAG_IMMEDIATE_BOOLEAN = 0x0f, TAG_IMMEDIATE_TUPLE = 0x1f, TAG_IMMEDIATE_VARIANT = 0x2f, }; enum { empty_tuple = TAG_IMMEDIATE_TUPLE, }; enum { value_true = TAG_IMMEDIATE_BOOLEAN, value_false = (TAG_IMMEDIATE_BOOLEAN | 0x100), }; enum { INTEGER_MIN = INT32_MIN / 2, INTEGER_MAX = INT32_MAX / 2, }; static struct { uint32_t num_entries; const uint16_t *entries; } record_layouts; static FILE *file_table[FOPEN_MAX]; static void err_print_line(const char *s) { fprintf(stderr, "%s\n", s); } static _Noreturn void halt(void) { exit(1); } // The Heap // // The heap is a single contiguous memory region in which value // representation data is stored. // // The heap grows monotonically. If its capacity is exceeded, then the // process exits with a nonzero exit status. // // The size of the heap for a given program is fixed at compile time and // is at most pow(2, 30) bytes (1 GiB). // // All objects allocated in the heap have 4-byte, 8-byte, or 16-byte // alignment. Each object allocated in the heap has an associated heap // identifier, established by heap_alloc. The heap_access function provides // the means for obtaining the native address of an object with a given heap // identifier. // // Members: // bytes - The bytes member holds the native address of the memory // region, which must have 16-byte alignment. The memory region // starting at this address must contain at least (4 * heap.limit) // bytes. // limit - The limit member establishes the growth limit for the heap. It // must not exceed pow(2, 28). The capacity of the heap is // (4 * heap.limit) bytes. This limit is fixed at initialization // time. // top - The top member grows as allocations are made but never exceeds // the limit member. It is initially zero. static struct { char *bytes; uint32_t limit; uint32_t top; } heap; // heap_access // // Parameters: // id - A heap object identifier produced by heap_alloc. static void * heap_access(uint32_t id) { return heap.bytes + 4 * id; } // heap_alloc // // Parameters: // align - The address alignment constraint. It must be 1, 2, 4, 8, or 16. // size - The number of bytes required, which must be greater than zero. static uint32_t heap_alloc(size_t align, size_t size) { uint32_t start = heap.top; if (align > 4) { uint32_t c = align / 4 - 1; start = (start + c) & ~c; } if (start > heap.limit || size > 4 * (heap.limit - start)) { err_print_line("Failed to allocate memory."); halt(); } heap.top = start + ((uint32_t)size + 3) / 4; return start; } static value value_make_box(unsigned int tag, uint32_t id) { return (id << 4) | tag; } static void * value_unbox(value x) { return heap_access(x >> 4); } static bool value_has_tag(value x, unsigned int mask, unsigned int tag) { return (x & mask) == tag; } static struct closure * closure_unbox(value closure) { if (!value_has_tag(closure, 0xf, TAG_HEAP_CLOSURE)) { err_print_line("Value is not a function."); halt(); } return value_unbox(closure); } static struct tuple * tuple_unbox(value tuple) { if (!value_has_tag(tuple, 0xf, TAG_HEAP_TUPLE)) { err_print_line("Value is not a tuple."); halt(); } return value_unbox(tuple); } static struct variant * variant_unbox(value variant) { if (!value_has_tag(variant, 0xf, TAG_HEAP_VARIANT)) { err_print_line("Value is not a variant."); halt(); } return value_unbox(variant); } static value byte_vector_make(uint32_t num_bytes, const char *bytes) { if (num_bytes > INTEGER_MAX) { err_print_line("Byte vector is too big."); halt(); } size_t align = _Alignof(struct byte_vector); size_t size = sizeof(struct byte_vector) + num_bytes; uint32_t id = heap_alloc(align, size); struct byte_vector *byte_vector_rep = heap_access(id); byte_vector_rep->num_bytes = num_bytes; if (num_bytes > 0 && bytes != NULL) memmove(byte_vector_rep->bytes, bytes, num_bytes); return value_make_box(TAG_HEAP_BYTE_VECTOR, id); } static struct byte_vector * byte_vector_unbox(value byte_vector) { if (!value_has_tag(byte_vector, 0xf, TAG_HEAP_BYTE_VECTOR)) { err_print_line("Value is not a byte vector."); halt(); } return value_unbox(byte_vector); } static char * byte_vector_bytes(value byte_vector) { struct byte_vector *byte_vector_rep = byte_vector_unbox(byte_vector); return byte_vector_rep->bytes; } static bool byte_vector_is_string(struct byte_vector *byte_vector_rep) { if (byte_vector_rep->num_bytes == 0) return false; if (byte_vector_rep->bytes[byte_vector_rep->num_bytes - 1] != 0) return false; return true; } static struct byte_vector * string_unbox(value string) { struct byte_vector *byte_vector_rep = byte_vector_unbox(string); if (!byte_vector_is_string(byte_vector_rep)) { err_print_line("Value is not a string."); halt(); } return byte_vector_rep; } static char * string_bytes(value string) { struct byte_vector *byte_vector_rep = string_unbox(string); return byte_vector_rep->bytes; } static uint32_t string_length(value string) { struct byte_vector *byte_vector_rep = byte_vector_unbox(string); if (!byte_vector_is_string(byte_vector_rep)) { err_print_line("Value is not a string."); halt(); } return byte_vector_rep->num_bytes - 1; } static int simple_strcmp(const char *s1, const char *s2) { for (;;) { char a = *s1; char b = *s2; if (a == 0) return (b == 0) ? 0 : -1; if (b == 0) return 1; if (a < b) return -1; if (a > b) return 1; s1++; s2++; } } static bool value_is_number(value v) { return (v & 1) == 0; } static value integer_encode(int32_t n) { if (n < INTEGER_MIN || INTEGER_MAX < n) { err_print_line("Number is out of range."); halt(); } return n << 1; } static int32_t integer_decode(value v) { if (!value_is_number(v)) { err_print_line("Value is not a number."); halt(); } #ifdef __GNUC__ int32_t n = v; // "implementation-defined" behaviour, according to C11. return n >> 1; // "implementation-defined" behaviour, according to C11. #else #error "Need validation of implementation-defined behaviour." #endif } static value boolean_encode(bool b) { return b ? value_true : value_false; } static value file_open(value name, const char *mode) { FILE *stream = fopen(byte_vector_bytes(name), mode); if (stream == NULL) { err_print_line("Failed to open file."); halt(); } int fd = fileno(stream); if (fd >= FOPEN_MAX) { err_print_line("Too many open files."); halt(); } file_table[fd] = stream; return integer_encode(fd); } static FILE * file_lookup(value fd_value, int *result_fd) { int32_t fd = integer_decode(fd_value); if (fd < 0 || fd >= FOPEN_MAX) { err_print_line("File descriptor is out of range."); halt(); } if (result_fd != NULL) *result_fd = fd; return file_table[fd]; } static void stack_init(uint32_t limit) { static_assert(sizeof(rlim_t) == 8); struct rlimit rlim; int r = getrlimit(RLIMIT_STACK, &rlim); if (r != 0) goto fail; rlim.rlim_cur = limit; r = setrlimit(RLIMIT_STACK, &rlim); if (r != 0) goto fail; return; fail: err_print_line("Failed to set the stack limit."); halt(); } // s36: init // // heap_num_bytes must be at most pow(2, 30). // The alignment of heap_bytes must be at least 16. void s36(uint32_t heap_num_bytes, char *heap_bytes, uint32_t stack_limit, uint32_t record_layouts_num_entries, const uint16_t *record_layouts_entries) { heap.limit = heap_num_bytes / 4; heap.top = 0; heap.bytes = heap_bytes; stack_init(stack_limit); record_layouts.num_entries = record_layouts_num_entries; record_layouts.entries = record_layouts_entries; file_table[0] = stdin; file_table[1] = stdout; file_table[2] = stderr; } // s87: halt // // Halt execution, returing 1 as the exit code of the process. _Noreturn value s87(void) { exit(1); } // s75: closure_make // // Construct a fresh closure value. // // Parameters: // native_code - A C function pointer to a function whose return type is // value and which takes one more than num_params arguments, all of // type value. // num_params - The number of parameters associated with the closure (not // the native function). // env_size - The number of values to be stored in the closure // environment. // env_items - If env_size is zero, then env_items may be NULL; // otherwise, env_items must be an array containing env_size values. // The values provided comprise the environment of the closure. value s75(void *native_code, uint16_t num_params, uint16_t env_size, const value *env_items) { size_t align = _Alignof(struct closure); size_t size = sizeof(struct closure) + env_size * sizeof(value); uint32_t id = heap_alloc(align, size); struct closure *closure_rep = heap_access(id); closure_rep->native_code = native_code; closure_rep->num_params = num_params; closure_rep->env_size = env_size; if (env_size > 0) memmove(closure_rep->env_items, env_items, env_size * sizeof(value)); return value_make_box(TAG_HEAP_CLOSURE, id); } // s62: closure_env_items const value * s62(value closure) { const struct closure *closure_rep = closure_unbox(closure); return closure_rep->env_items; } // s35: closure_native_code const void * s35(value closure, uint16_t num_args) { const struct closure *closure_rep = closure_unbox(closure); if (closure_rep->num_params != num_args) { err_print_line("Ill-formed function application."); halt(); } return closure_rep->native_code; } // s27: variant_make_nonempty // // Construct a fresh variant value where the enclosed value is not {}. // // Parameters: // label - The label! // item - The enclosed value. value s27(uint16_t label, value item) { size_t align = _Alignof(struct variant); size_t size = sizeof(struct variant); uint32_t id = heap_alloc(align, size); struct variant *variant_rep = heap_access(id); variant_rep->label = label; variant_rep->item = item; return value_make_box(TAG_HEAP_VARIANT, id); } // s09: variant_label // // The label of a variant value. // // Parameters: // variant - The variant! uint16_t s09(value variant) { if (value_has_tag(variant, 0xff, TAG_IMMEDIATE_VARIANT)) return variant >> 8; struct variant *variant_rep = variant_unbox(variant); return variant_rep->label; } // s06: variant_item // // The value embedded within a variant. // // Parameters: // variant - The variant! value s06(value variant) { if (value_has_tag(variant, 0xff, TAG_IMMEDIATE_VARIANT)) return empty_tuple; struct variant *variant_rep = variant_unbox(variant); return variant_rep->item; } // s30: tuple_make_with_layout value s30(uint16_t num_items, const value *items, uint16_t layout) { size_t align = _Alignof(struct tuple); size_t size = sizeof(struct tuple) + num_items * sizeof(value); uint32_t id = heap_alloc(align, size); struct tuple *tuple_rep = heap_access(id); tuple_rep->num_items = num_items; tuple_rep->layout = layout; if (num_items > 0) memmove(tuple_rep->items, items, num_items * sizeof(value)); return value_make_box(TAG_HEAP_TUPLE, id); } // s78: tuple_make_with_no_layout value s78(uint16_t num_items, const value *items) { return s30(num_items, items, UINT16_MAX); } // s68: tuple_fetch_at_offset value s68(value tuple, uint16_t offset) { if (tuple == empty_tuple) goto fail; struct tuple *tuple_rep = tuple_unbox(tuple); if (offset >= tuple_rep->num_items) goto fail; return tuple_rep->items[offset]; fail: err_print_line("Ill-formed tuple access."); halt(); } // s31: tuple_fetch_at_label value s31(value tuple, uint16_t label) { if (tuple == empty_tuple) goto fail; const unsigned short *entries = record_layouts.entries; struct tuple *tuple_rep = tuple_unbox(tuple); unsigned int layout = tuple_rep->layout; if (layout == UINT16_MAX) goto fail; for (unsigned int i = layout; entries[i] != UINT16_MAX; i++) { if (entries[i] == label) return tuple_rep->items[i - layout]; } fail: err_print_line("Ill-formed record access."); halt(); } // s33: tuple_items const value * s33(value tuple, uint16_t num_items) { if (tuple == empty_tuple) { err_print_line("Invalid use of empty tuple."); halt(); } struct tuple *tuple_rep = tuple_unbox(tuple); if (tuple_rep->num_items != num_items) { err_print_line("Tuple mismatch."); halt(); } return tuple_rep->items; } // s57: string_make value s57(const char *s) { size_t len = strlen(s); if (len > INTEGER_MAX) { err_print_line("String is too long."); halt(); } return byte_vector_make(len + 1, s); } // s89: stuck_cond _Noreturn value s89(void) { err_print_line("Cond expression has no applicable clause."); halt(); } // s88: stuck_switch _Noreturn value s88(void) { err_print_line("Switch expression has no applicable clause."); halt(); } // s53: stuck_match _Noreturn value s53(void) { err_print_line("Match expression has no applicable clause."); halt(); } // s26: prim_die value s26(value string) { err_print_line(string_bytes(string)); halt(); } // s18: prim_print value s18(value string) { printf("%s", string_bytes(string)); return empty_tuple; } // s79: prim_print_line value s79(value string) { printf("%s\n", string_bytes(string)); return empty_tuple; } // s20: prim_file_create value s20(value name) { return file_open(name, "w"); } // s23: prim_file_open value s23(value name) { return file_open(name, "r"); } // s92: prim_file_close value s92(value fd_value) { int32_t fd = integer_decode(fd_value); if (fd < 0 || fd >= FOPEN_MAX) { err_print_line("Invalid file descriptor."); halt(); } if (fd <= 2) { err_print_line("Attempted to close stdin, stdout, or stderr."); halt(); } FILE *stream = file_table[fd]; if (stream != NULL) { fclose(stream); file_table[fd] = NULL; } return empty_tuple; } // s28: prim_file_read_all value s28(value fd_value) { int fd; FILE *stream = file_lookup(fd_value, &fd); if (stream == NULL) { err_print_line("File is not open."); halt(); } struct stat statbuf; if (-1 == fstat(fd, &statbuf)) { err_print_line("Failed to determine file size."); halt(); } off_t file_size = statbuf.st_size; if (file_size < 0 || file_size > UINT32_MAX - 1) goto fail; value string = byte_vector_make(file_size + 1, NULL); char *bytes = byte_vector_bytes(string); if (1 != fread(bytes, file_size, 1, stream)) goto fail; bytes[file_size] = 0; return string; fail: err_print_line("Failed to read file."); halt(); } // s97: prim_file_write value s97(value fd_value, value string) { int fd; FILE *stream = file_lookup(fd_value, &fd); if (stream == NULL) { err_print_line("File is not open."); halt(); } uint32_t length = string_length(string); if (length > 0) { size_t r = fwrite(byte_vector_bytes(string), length, 1, stream); if (r != 1) { err_print_line("Failed to write to file."); halt(); } } return empty_tuple; } // s12: prim_show_integer value s12(value integer) { char text[16]; size_t len = snprintf(text, sizeof(text), "%ld", (long)integer_decode(integer)); if (len >= sizeof(text)) { err_print_line("Failed to show integer."); halt(); } return byte_vector_make(len + 1, text); } // s93: prim_multiply value s93(value a, value b) { int32_t n; if (__builtin_mul_overflow(integer_decode(a), integer_decode(b), &n)) { err_print_line("Integer is out of range."); halt(); } return integer_encode(n); } // s19: prim_add value s19(value a, value b) { int32_t n; if (__builtin_add_overflow(integer_decode(a), integer_decode(b), &n)) { err_print_line("Integer is out of range."); halt(); } return integer_encode(n); } // s47: prim_subtract value s47(value a, value b) { int32_t n; if (__builtin_sub_overflow(integer_decode(a), integer_decode(b), &n)) { err_print_line("Integer is out of range."); halt(); } return integer_encode(n); } // s84: prim_negate value s84(value n) { return integer_encode(-integer_decode(n)); } // s50: prim_equal value s50(value a, value b) { return boolean_encode(integer_decode(a) == integer_decode(b)); } // s10: prim_less value s10(value a, value b) { return boolean_encode(integer_decode(a) < integer_decode(b)); } // s63: prim_less_or_equal value s63(value a, value b) { return boolean_encode(integer_decode(a) <= integer_decode(b)); } // s61: prim_greater value s61(value a, value b) { return boolean_encode(integer_decode(a) > integer_decode(b)); } // s55: prim_greater_or_equal value s55(value a, value b) { return boolean_encode(integer_decode(a) >= integer_decode(b)); } // s65: prim_string_length value s65(value string) { return integer_encode(string_length(string)); } // s69: prim_string_fetch value s69(value string, value i_value) { int32_t i = integer_decode(i_value); const struct byte_vector *byte_vector_rep = string_unbox(string); if (i < 0 || (uint32_t)i >= byte_vector_rep->num_bytes - 1) { err_print_line("Character index is out of range."); halt(); } return integer_encode(byte_vector_rep->bytes[i]); } // s37: prim_string_compare value s37(value s1, value s2) { return integer_encode(simple_strcmp(string_bytes(s1), string_bytes(s2))); } // s45: prim_string_equal value s45(value s1, value s2) { int c = simple_strcmp(string_bytes(s1), string_bytes(s2)); return boolean_encode(c == 0); } // s25: prim_string_append value s25(value s1, value s2) { uint32_t s1_length = string_length(s1); uint32_t s2_length = string_length(s2); uint32_t s3_length = s1_length + s2_length; value s3 = byte_vector_make(s3_length + 1, NULL); char *s3_bytes = byte_vector_bytes(s3); memmove(s3_bytes, string_bytes(s1), s1_length); memmove(s3_bytes + s1_length, string_bytes(s2), s2_length + 1); return s3; } // s44: prim_string_clip value s44(value s1, value begin, value end) { uint32_t s1_length = string_length(s1); int32_t b = integer_decode(begin); int32_t e = integer_decode(end); if (b < 0 || e < 0 || b > e || (uint32_t)e > s1_length) { err_print_line("String clip parameters are invalid."); halt(); } uint32_t s2_length = e - b; value s2 = byte_vector_make(s2_length + 1, NULL); const char *s1_bytes = string_bytes(s1); char *s2_bytes = string_bytes(s2); memmove(s2_bytes, s1_bytes + b, s2_length); s2_bytes[s2_length] = 0; return s2; }