diff --git a/README.org b/README.org index c2291eaeb..6558d8541 100644 --- a/README.org +++ b/README.org @@ -80,7 +80,7 @@ precompiled archives from the [[https://github.com/WebAssembly/wasi-sdk/releases tar xf libclang_rt.builtins-wasm32-wasi-15.0.tar.gz #+end_src -2. Extract the =wasi-sysroot-*.tar.gz= archive on your local system and set =WASI_SYSROOT_PATH= to its path. +2. <>Extract the =wasi-sysroot-*.tar.gz= archive on your local system and set =WASI_SYSROOT_PATH= to its path. For example: @@ -109,11 +109,13 @@ stack test --ta '-p "! /slow tests/"' ** Usage Example In the following example a MiniJuvix file is compiled using the C backend. The -result is compiled to WASM using [[https://emscripten.org][emscripten]] and then executed using [[https://wasmer.io][wasmer]]. +result is compiled to WASM using [[https://llvm.org][Clang]] and then executed using [[https://wasmer.io][wasmer]]. + +NB: Set the =WASI_SYSROOT_PATH= environment variable to the root of the WASI sysroot, see [[sysroot]]. #+begin_src shell cd tests/positive/MiniC/HelloWorld -minijuvix minic Input.mjuvix | clang --target=wasm32-wasi -nodefaultlibs -lc --sysroot $WASI_SYSROOT_PATH -x c - -o out.wasm && wasmer out.wasm +minijuvix minic Input.mjuvix | clang --target=wasm32-wasi -nodefaultlibs -lc --sysroot $WASI_SYSROOT_PATH -I../../../../minic-runtime/libc -x c - -o out.wasm && wasmer out.wasm #+end_src #+RESULTS: diff --git a/minic-runtime/libc/minic-runtime.h b/minic-runtime/libc/minic-runtime.h new file mode 100644 index 000000000..86e8b3104 --- /dev/null +++ b/minic-runtime/libc/minic-runtime.h @@ -0,0 +1,22 @@ +#ifndef MINIC_RUNTIME_H_ +#define MINIC_RUNTIME_H_ + +#include +#include + +char* intToStr(int i) { + int length = snprintf(NULL, 0, "%d", i); + char* str = (char*)malloc(length + 1); + snprintf(str, length + 1, "%d", i); + return str; +} + +int putStr(const char* str) { + return fputs(str, stdout); +} + +int putStrLn(const char* str) { + return puts(str); +} + +#endif // MINIC_RUNTIME_H_ diff --git a/minic-runtime/standalone/minic-runtime.h b/minic-runtime/standalone/minic-runtime.h new file mode 100644 index 000000000..b2af69ef9 --- /dev/null +++ b/minic-runtime/standalone/minic-runtime.h @@ -0,0 +1,136 @@ +#ifndef MINIC_RUNTIME_H_ +#define MINIC_RUNTIME_H_ + +typedef __SIZE_TYPE__ size_t; +typedef __UINT8_TYPE__ uint8_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; + +/** + * Allocator + */ + +void *malloc(size_t size); +void free(void *p); + +/** + * WASI Definitions + */ + +typedef struct Ciovec { + uint8_t *buf; + uint32_t buf_len; +} Ciovec_t; + +uint16_t fd_write(uint32_t fd, Ciovec_t *iovs_ptr, uint32_t iovs_len, uint32_t *nwritten) + __attribute__(( + __import_module__("wasi_snapshot_preview1"), + __import_name__("fd_write"), + )); + +_Noreturn void proc_exit(uint32_t rval) + __attribute__(( + __import_module__("wasi_snapshot_preview1"), + __import_name__("proc_exit"), + )); + +/** + * stdlib.h + */ + +#define EXIT_FAILURE 1 + +_Noreturn void exit(int rval) { + proc_exit(rval); +} + +/** + * string.h + */ + +int strcmp(const char *s1, const char *s2) { + while (*s1) { + if (*s1 != *s2) { + break; + } + s1++; + s2++; + } + return *(const unsigned char*)s1 - *(const unsigned char*)s2; +} + +size_t strlen(const char *s) { + const char* p = s; + while(*p!='\0') { + ++p; + } + return p - s; +} + +/** + * Utilities + */ + +/** + * C++ version 0.4 char* style "itoa": + * Written by Lukás Chmela + * Released under GPLv3. + + */ +char* itoa(int value, char* result, int base) { + // check that the base if valid + if (base < 2 || base > 36) { *result = '\0'; return result; } + + char* ptr = result, *ptr1 = result, tmp_char; + int tmp_value; + + do { + tmp_value = value; + value /= base; + *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)]; + } while ( value ); + + // Apply negative sign + if (tmp_value < 0) *ptr++ = '-'; + *ptr-- = '\0'; + while(ptr1 < ptr) { + tmp_char = *ptr; + *ptr--= *ptr1; + *ptr1++ = tmp_char; + } + return result; +} + +char* intToStr(int i) { + // int will occupy at least one char + size_t len = 1; + + // calculate the size of the digits + int n = i < 0 ? -i : i; + while (n >= 10) { + n /= 10; + len += 1; + } + + // add an extra char for the negative sign + if (i < 0) { + len += 1; + } + + char* buf = (char*)malloc(len + 1); + itoa(i, buf, 10); + return buf; +} + +int putStr(const char* str) { + size_t n = strlen(str); + Ciovec_t vec = {.buf = (uint8_t*)str, .buf_len=n}; + return fd_write(1, &vec, 1, 0); +} + +int putStrLn(const char* str) { + return putStr(str) || putStr("\n"); +} + + +#endif // MINIC_RUNTIME_H_ diff --git a/minic-runtime/standalone/walloc.c b/minic-runtime/standalone/walloc.c new file mode 100644 index 000000000..315d9e74c --- /dev/null +++ b/minic-runtime/standalone/walloc.c @@ -0,0 +1,466 @@ +// walloc.c: a small malloc implementation for use in WebAssembly targets +// Copyright (c) 2020 Igalia, S.L. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +typedef __SIZE_TYPE__ size_t; +typedef __UINTPTR_TYPE__ uintptr_t; +typedef __UINT8_TYPE__ uint8_t; + +#define NULL ((void *) 0) + +#define STATIC_ASSERT_EQ(a, b) _Static_assert((a) == (b), "eq") + +#ifndef NDEBUG +#define ASSERT(x) do { if (!(x)) __builtin_trap(); } while (0) +#else +#define ASSERT(x) do { } while (0) +#endif +#define ASSERT_EQ(a,b) ASSERT((a) == (b)) + +static inline size_t max(size_t a, size_t b) { + return a < b ? b : a; +} +static inline uintptr_t align(uintptr_t val, uintptr_t alignment) { + return (val + alignment - 1) & ~(alignment - 1); +} +#define ASSERT_ALIGNED(x, y) ASSERT((x) == align((x), y)) + +#define CHUNK_SIZE 256 +#define CHUNK_SIZE_LOG_2 8 +#define CHUNK_MASK (CHUNK_SIZE - 1) +STATIC_ASSERT_EQ(CHUNK_SIZE, 1 << CHUNK_SIZE_LOG_2); + +#define PAGE_SIZE 65536 +#define PAGE_SIZE_LOG_2 16 +#define PAGE_MASK (PAGE_SIZE - 1) +STATIC_ASSERT_EQ(PAGE_SIZE, 1 << PAGE_SIZE_LOG_2); + +#define CHUNKS_PER_PAGE 256 +STATIC_ASSERT_EQ(PAGE_SIZE, CHUNK_SIZE * CHUNKS_PER_PAGE); + +#define GRANULE_SIZE 8 +#define GRANULE_SIZE_LOG_2 3 +#define LARGE_OBJECT_THRESHOLD 256 +#define LARGE_OBJECT_GRANULE_THRESHOLD 32 + +STATIC_ASSERT_EQ(GRANULE_SIZE, 1 << GRANULE_SIZE_LOG_2); +STATIC_ASSERT_EQ(LARGE_OBJECT_THRESHOLD, + LARGE_OBJECT_GRANULE_THRESHOLD * GRANULE_SIZE); + +struct chunk { + char data[CHUNK_SIZE]; +}; + +// There are small object pages for allocations of these sizes. +#define FOR_EACH_SMALL_OBJECT_GRANULES(M) \ + M(1) M(2) M(3) M(4) M(5) M(6) M(8) M(10) M(16) M(32) + +enum chunk_kind { +#define DEFINE_SMALL_OBJECT_CHUNK_KIND(i) GRANULES_##i, + FOR_EACH_SMALL_OBJECT_GRANULES(DEFINE_SMALL_OBJECT_CHUNK_KIND) +#undef DEFINE_SMALL_OBJECT_CHUNK_KIND + + SMALL_OBJECT_CHUNK_KINDS, + FREE_LARGE_OBJECT = 254, + LARGE_OBJECT = 255 +}; + +static const uint8_t small_object_granule_sizes[] = +{ +#define SMALL_OBJECT_GRANULE_SIZE(i) i, + FOR_EACH_SMALL_OBJECT_GRANULES(SMALL_OBJECT_GRANULE_SIZE) +#undef SMALL_OBJECT_GRANULE_SIZE +}; + +static enum chunk_kind granules_to_chunk_kind(unsigned granules) { +#define TEST_GRANULE_SIZE(i) if (granules <= i) return GRANULES_##i; + FOR_EACH_SMALL_OBJECT_GRANULES(TEST_GRANULE_SIZE); +#undef TEST_GRANULE_SIZE + return LARGE_OBJECT; +} + +static unsigned chunk_kind_to_granules(enum chunk_kind kind) { + switch (kind) { +#define CHUNK_KIND_GRANULE_SIZE(i) case GRANULES_##i: return i; + FOR_EACH_SMALL_OBJECT_GRANULES(CHUNK_KIND_GRANULE_SIZE); +#undef CHUNK_KIND_GRANULE_SIZE + default: + return -1; + } +} + +// Given a pointer P returned by malloc(), we get a header pointer via +// P&~PAGE_MASK, and a chunk index via (P&PAGE_MASK)/CHUNKS_PER_PAGE. If +// chunk_kinds[chunk_idx] is [FREE_]LARGE_OBJECT, then the pointer is a large +// object, otherwise the kind indicates the size in granules of the objects in +// the chunk. +struct page_header { + uint8_t chunk_kinds[CHUNKS_PER_PAGE]; +}; + +struct page { + union { + struct page_header header; + struct chunk chunks[CHUNKS_PER_PAGE]; + }; +}; + +#define PAGE_HEADER_SIZE (sizeof (struct page_header)) +#define FIRST_ALLOCATABLE_CHUNK 1 +STATIC_ASSERT_EQ(PAGE_HEADER_SIZE, FIRST_ALLOCATABLE_CHUNK * CHUNK_SIZE); + +static struct page* get_page(void *ptr) { + return (struct page*) (char*) (((uintptr_t) ptr) & ~PAGE_MASK); +} +static unsigned get_chunk_index(void *ptr) { + return (((uintptr_t) ptr) & PAGE_MASK) / CHUNK_SIZE; +} + +struct freelist { + struct freelist *next; +}; + +struct large_object { + struct large_object *next; + size_t size; +}; + +#define LARGE_OBJECT_HEADER_SIZE (sizeof (struct large_object)) + +static inline void* get_large_object_payload(struct large_object *obj) { + return ((char*) obj) + LARGE_OBJECT_HEADER_SIZE; +} +static inline struct large_object* get_large_object(void *ptr) { + return (struct large_object*) (((char*) ptr) - LARGE_OBJECT_HEADER_SIZE); +} + +static struct freelist *small_object_freelists[SMALL_OBJECT_CHUNK_KINDS]; +static struct large_object *large_objects; + +extern void __heap_base; +static size_t walloc_heap_size; + +static struct page* +allocate_pages(size_t payload_size, size_t *n_allocated) { + size_t needed = payload_size + PAGE_HEADER_SIZE; + size_t heap_size = __builtin_wasm_memory_size(0) * PAGE_SIZE; + uintptr_t base = heap_size; + uintptr_t preallocated = 0, grow = 0; + + if (!walloc_heap_size) { + // We are allocating the initial pages, if any. We skip the first 64 kB, + // then take any additional space up to the memory size. + uintptr_t heap_base = align((uintptr_t)&__heap_base, PAGE_SIZE); + preallocated = heap_size - heap_base; // Preallocated pages. + walloc_heap_size = preallocated; + base -= preallocated; + } + + if (preallocated < needed) { + // Always grow the walloc heap at least by 50%. + grow = align(max(walloc_heap_size / 2, needed - preallocated), + PAGE_SIZE); + ASSERT(grow); + if (__builtin_wasm_memory_grow(0, grow >> PAGE_SIZE_LOG_2) == -1) { + return NULL; + } + walloc_heap_size += grow; + } + + struct page *ret = (struct page *)base; + size_t size = grow + preallocated; + ASSERT(size); + ASSERT_ALIGNED(size, PAGE_SIZE); + *n_allocated = size / PAGE_SIZE; + return ret; +} + +static char* +allocate_chunk(struct page *page, unsigned idx, enum chunk_kind kind) { + page->header.chunk_kinds[idx] = kind; + return page->chunks[idx].data; +} + +// It's possible for splitting to produce a large object of size 248 (256 minus +// the header size) -- i.e. spanning a single chunk. In that case, push the +// chunk back on the GRANULES_32 small object freelist. +static void maybe_repurpose_single_chunk_large_objects_head(void) { + if (large_objects->size < CHUNK_SIZE) { + unsigned idx = get_chunk_index(large_objects); + char *ptr = allocate_chunk(get_page(large_objects), idx, GRANULES_32); + large_objects = large_objects->next; + struct freelist* head = (struct freelist *)ptr; + head->next = small_object_freelists[GRANULES_32]; + small_object_freelists[GRANULES_32] = head; + } +} + +// If there have been any large-object frees since the last large object +// allocation, go through the freelist and merge any adjacent objects. +static int pending_large_object_compact = 0; +static struct large_object** +maybe_merge_free_large_object(struct large_object** prev) { + struct large_object *obj = *prev; + while (1) { + char *end = get_large_object_payload(obj) + obj->size; + ASSERT_ALIGNED((uintptr_t)end, CHUNK_SIZE); + unsigned chunk = get_chunk_index(end); + if (chunk < FIRST_ALLOCATABLE_CHUNK) { + // Merging can't create a large object that newly spans the header chunk. + // This check also catches the end-of-heap case. + return prev; + } + struct page *page = get_page(end); + if (page->header.chunk_kinds[chunk] != FREE_LARGE_OBJECT) { + return prev; + } + struct large_object *next = (struct large_object*) end; + + struct large_object **prev_prev = &large_objects, *walk = large_objects; + while (1) { + ASSERT(walk); + if (walk == next) { + obj->size += LARGE_OBJECT_HEADER_SIZE + walk->size; + *prev_prev = walk->next; + if (prev == &walk->next) { + prev = prev_prev; + } + break; + } + prev_prev = &walk->next; + walk = walk->next; + } + } +} +static void +maybe_compact_free_large_objects(void) { + if (pending_large_object_compact) { + pending_large_object_compact = 0; + struct large_object **prev = &large_objects; + while (*prev) { + prev = &(*maybe_merge_free_large_object(prev))->next; + } + } +} + +// Allocate a large object with enough space for SIZE payload bytes. Returns a +// large object with a header, aligned on a chunk boundary, whose payload size +// may be larger than SIZE, and whose total size (header included) is +// chunk-aligned. Either a suitable allocation is found in the large object +// freelist, or we ask the OS for some more pages and treat those pages as a +// large object. If the allocation fits in that large object and there's more +// than an aligned chunk's worth of data free at the end, the large object is +// split. +// +// The return value's corresponding chunk in the page as starting a large +// object. +static struct large_object* +allocate_large_object(size_t size) { + maybe_compact_free_large_objects(); + struct large_object *best = NULL, **best_prev = &large_objects; + size_t best_size = -1; + for (struct large_object **prev = &large_objects, *walk = large_objects; + walk; + prev = &walk->next, walk = walk->next) { + if (walk->size >= size && walk->size < best_size) { + best_size = walk->size; + best = walk; + best_prev = prev; + if (best_size + LARGE_OBJECT_HEADER_SIZE + == align(size + LARGE_OBJECT_HEADER_SIZE, CHUNK_SIZE)) + // Not going to do any better than this; just return it. + break; + } + } + + if (!best) { + // The large object freelist doesn't have an object big enough for this + // allocation. Allocate one or more pages from the OS, and treat that new + // sequence of pages as a fresh large object. It will be split if + // necessary. + size_t size_with_header = size + sizeof(struct large_object); + size_t n_allocated = 0; + struct page *page = allocate_pages(size_with_header, &n_allocated); + if (!page) { + return NULL; + } + char *ptr = allocate_chunk(page, FIRST_ALLOCATABLE_CHUNK, LARGE_OBJECT); + best = (struct large_object *)ptr; + size_t page_header = ptr - ((char*) page); + best->next = large_objects; + best->size = best_size = + n_allocated * PAGE_SIZE - page_header - LARGE_OBJECT_HEADER_SIZE; + ASSERT(best_size >= size_with_header); + } + + allocate_chunk(get_page(best), get_chunk_index(best), LARGE_OBJECT); + + struct large_object *next = best->next; + *best_prev = next; + + size_t tail_size = (best_size - size) & ~CHUNK_MASK; + if (tail_size) { + // The best-fitting object has 1 or more aligned chunks free after the + // requested allocation; split the tail off into a fresh aligned object. + struct page *start_page = get_page(best); + char *start = get_large_object_payload(best); + char *end = start + best_size; + + if (start_page == get_page(end - tail_size - 1)) { + // The allocation does not span a page boundary; yay. + ASSERT_ALIGNED((uintptr_t)end, CHUNK_SIZE); + } else if (size < PAGE_SIZE - LARGE_OBJECT_HEADER_SIZE - CHUNK_SIZE) { + // If the allocation itself smaller than a page, split off the head, then + // fall through to maybe split the tail. + ASSERT_ALIGNED((uintptr_t)end, PAGE_SIZE); + size_t first_page_size = PAGE_SIZE - (((uintptr_t)start) & PAGE_MASK); + struct large_object *head = best; + allocate_chunk(start_page, get_chunk_index(start), FREE_LARGE_OBJECT); + head->size = first_page_size; + head->next = large_objects; + large_objects = head; + + maybe_repurpose_single_chunk_large_objects_head(); + + struct page *next_page = start_page + 1; + char *ptr = allocate_chunk(next_page, FIRST_ALLOCATABLE_CHUNK, LARGE_OBJECT); + best = (struct large_object *) ptr; + best->size = best_size = best_size - first_page_size - CHUNK_SIZE - LARGE_OBJECT_HEADER_SIZE; + ASSERT(best_size >= size); + start = get_large_object_payload(best); + tail_size = (best_size - size) & ~CHUNK_MASK; + } else { + // A large object that spans more than one page will consume all of its + // tail pages. Therefore if the split traverses a page boundary, round up + // to page size. + ASSERT_ALIGNED((uintptr_t)end, PAGE_SIZE); + size_t first_page_size = PAGE_SIZE - (((uintptr_t)start) & PAGE_MASK); + size_t tail_pages_size = align(size - first_page_size, PAGE_SIZE); + size = first_page_size + tail_pages_size; + tail_size = best_size - size; + } + best->size -= tail_size; + + unsigned tail_idx = get_chunk_index(end - tail_size); + while (tail_idx < FIRST_ALLOCATABLE_CHUNK && tail_size) { + // We would be splitting in a page header; don't do that. + tail_size -= CHUNK_SIZE; + tail_idx++; + } + + if (tail_size) { + struct page *page = get_page(end - tail_size); + char *tail_ptr = allocate_chunk(page, tail_idx, FREE_LARGE_OBJECT); + struct large_object *tail = (struct large_object *) tail_ptr; + tail->next = large_objects; + tail->size = tail_size - LARGE_OBJECT_HEADER_SIZE; + ASSERT_ALIGNED((uintptr_t)(get_large_object_payload(tail) + tail->size), CHUNK_SIZE); + large_objects = tail; + + maybe_repurpose_single_chunk_large_objects_head(); + } + } + + ASSERT_ALIGNED((uintptr_t)(get_large_object_payload(best) + best->size), CHUNK_SIZE); + return best; +} + +static struct freelist* +obtain_small_objects(enum chunk_kind kind) { + struct freelist** whole_chunk_freelist = &small_object_freelists[GRANULES_32]; + void *chunk; + if (*whole_chunk_freelist) { + chunk = *whole_chunk_freelist; + *whole_chunk_freelist = (*whole_chunk_freelist)->next; + } else { + chunk = allocate_large_object(0); + if (!chunk) { + return NULL; + } + } + char *ptr = allocate_chunk(get_page(chunk), get_chunk_index(chunk), kind); + char *end = ptr + CHUNK_SIZE; + struct freelist *next = NULL; + size_t size = chunk_kind_to_granules(kind) * GRANULE_SIZE; + for (size_t i = size; i <= CHUNK_SIZE; i += size) { + struct freelist *head = (struct freelist*) (end - i); + head->next = next; + next = head; + } + return next; +} + +static inline size_t size_to_granules(size_t size) { + return (size + GRANULE_SIZE - 1) >> GRANULE_SIZE_LOG_2; +} +static struct freelist** get_small_object_freelist(enum chunk_kind kind) { + ASSERT(kind < SMALL_OBJECT_CHUNK_KINDS); + return &small_object_freelists[kind]; +} + +static void* +allocate_small(enum chunk_kind kind) { + struct freelist **loc = get_small_object_freelist(kind); + if (!*loc) { + struct freelist *freelist = obtain_small_objects(kind); + if (!freelist) { + return NULL; + } + *loc = freelist; + } + struct freelist *ret = *loc; + *loc = ret->next; + return (void *) ret; +} + +static void* +allocate_large(size_t size) { + struct large_object *obj = allocate_large_object(size); + return obj ? get_large_object_payload(obj) : NULL; +} + +void* +malloc(size_t size) { + size_t granules = size_to_granules(size); + enum chunk_kind kind = granules_to_chunk_kind(granules); + return (kind == LARGE_OBJECT) ? allocate_large(size) : allocate_small(kind); +} + +void +free(void *ptr) { + if (!ptr) return; + struct page *page = get_page(ptr); + unsigned chunk = get_chunk_index(ptr); + uint8_t kind = page->header.chunk_kinds[chunk]; + if (kind == LARGE_OBJECT) { + struct large_object *obj = get_large_object(ptr); + obj->next = large_objects; + large_objects = obj; + allocate_chunk(page, chunk, FREE_LARGE_OBJECT); + pending_large_object_compact = 1; + } else { + size_t granules = kind; + struct freelist **loc = get_small_object_freelist(granules); + struct freelist *obj = ptr; + obj->next = *loc; + *loc = obj; + } +} diff --git a/package.yaml b/package.yaml index b5e9df4dd..ac0249a5f 100644 --- a/package.yaml +++ b/package.yaml @@ -27,6 +27,7 @@ dependencies: - edit-distance == 0.2.* - extra == 1.7.* - transformers == 0.5.* +- file-embed == 0.0.* - filepath == 1.4.* - gitrev == 1.3.* - hashable == 1.4.* diff --git a/src/MiniJuvix/Internal/Strings.hs b/src/MiniJuvix/Internal/Strings.hs index 8cf7db207..d9649a4ba 100644 --- a/src/MiniJuvix/Internal/Strings.hs +++ b/src/MiniJuvix/Internal/Strings.hs @@ -211,3 +211,9 @@ tag = "tag" main_ :: IsString s => s main_ = "main" + +minicRuntime :: IsString s => s +minicRuntime = "minic-runtime.h" + +putStrLn_ :: IsString s => s +putStrLn_ = "putStrLn" diff --git a/src/MiniJuvix/Translation/MonoJuvixToMiniC.hs b/src/MiniJuvix/Translation/MonoJuvixToMiniC.hs index c42c7160b..e0f253106 100644 --- a/src/MiniJuvix/Translation/MonoJuvixToMiniC.hs +++ b/src/MiniJuvix/Translation/MonoJuvixToMiniC.hs @@ -36,9 +36,8 @@ entryMiniC i = return (MiniCResult (serialize cunitResult)) cheader = map ExternalMacro - [ CppIncludeSystem Str.stdlib, - CppIncludeSystem Str.stdbool, - CppIncludeSystem Str.stdio + [ CppIncludeSystem Str.stdbool, + CppIncludeFile Str.minicRuntime ] cmodules :: [CCode] cmodules = toList (i ^. Mono.resultModules) >>= (run . runReader compileInfo . goModule) @@ -214,10 +213,9 @@ goFunctionDef Mono.FunctionDef {..} = StatementCompound [ StatementExpr ( functionCall - (ExpressionVar Str.fprintf) - [ ExpressionVar Str.stderr_, - ExpressionLiteral (LiteralString "Error: Pattern match(es) are non-exhaustive in %s\n"), - ExpressionLiteral (LiteralString (_funDefName ^. Mono.nameText)) + (ExpressionVar Str.putStrLn_) + [ ExpressionLiteral + (LiteralString ("Error: Pattern match(es) are non-exhaustive in " <> (_funDefName ^. Mono.nameText))) ] ), StatementExpr diff --git a/src/MiniJuvix/Translation/ScopedToAbstract.hs b/src/MiniJuvix/Translation/ScopedToAbstract.hs index 1ed4eb4b8..9e81bbeb1 100644 --- a/src/MiniJuvix/Translation/ScopedToAbstract.hs +++ b/src/MiniJuvix/Translation/ScopedToAbstract.hs @@ -162,7 +162,7 @@ goInductive :: Sem r A.InductiveDef goInductive InductiveDef {..} = do _inductiveParameters' <- mapM goInductiveParameter _inductiveParameters - _inductiveType' <- sequence $ goExpression <$> _inductiveType + _inductiveType' <- mapM goExpression _inductiveType _inductiveConstructors' <- mapM goConstructorDef _inductiveConstructors inductiveInfo <- registerInductive diff --git a/test/BackendC/Positive.hs b/test/BackendC/Positive.hs index 2362c59ff..3b3159498 100644 --- a/test/BackendC/Positive.hs +++ b/test/BackendC/Positive.hs @@ -1,6 +1,9 @@ +{-# LANGUAGE TemplateHaskell #-} + module BackendC.Positive where import Base +import Data.FileEmbed import Data.Text.IO qualified as TIO import MiniJuvix.Pipeline import MiniJuvix.Translation.MonoJuvixToMiniC as MiniC @@ -44,33 +47,74 @@ testDescr PosTest {..} = let entryPoint = EntryPoint "." (pure mainFile) p :: MiniC.MiniCResult <- runIO (upToMiniC entryPoint) - actual <- - withTempDir - ( \dirPath -> do - let cOutputFile = dirPath "out.c" - wasmOutputFile = dirPath "out.wasm" - TIO.writeFile cOutputFile (p ^. MiniC.resultCCode) - step "WASM generation" - P.callProcess - "clang" - [ "-nodefaultlibs", - "-lc", - "--target=wasm32-wasi", - "--sysroot", - sysrootPath, - "-o", - wasmOutputFile, - cOutputFile - ] - step "WASM execution" - pack <$> P.readProcess "wasmer" [wasmOutputFile] "" - ) - expected <- TIO.readFile expectedFile + + step "Compile C with standalone runtime" + actualStandalone <- clangCompile (standaloneArgs sysrootPath) p step step "Compare expected and actual program output" - assertEqDiff ("check: WASM output = " <> expectedFile) actual expected + assertEqDiff ("check: WASM output = " <> expectedFile) actualStandalone expected + + step "Compile C with libc runtime" + actualLibc <- clangCompile (libcArgs sysrootPath) p step + step "Compare expected and actual program output" + assertEqDiff ("check: WASM output = " <> expectedFile) actualLibc expected } +clangCompile :: + (FilePath -> FilePath -> [String]) -> + MiniC.MiniCResult -> + (String -> IO ()) -> + IO Text +clangCompile mkClangArgs cResult step = + withTempDir + ( \dirPath -> do + let cOutputFile = dirPath "out.c" + wasmOutputFile = dirPath "out.wasm" + TIO.writeFile cOutputFile (cResult ^. MiniC.resultCCode) + step "WASM generation" + P.callProcess + "clang" + (mkClangArgs wasmOutputFile cOutputFile) + step "WASM execution" + pack <$> P.readProcess "wasmer" [wasmOutputFile] "" + ) + +standaloneArgs :: FilePath -> FilePath -> FilePath -> [String] +standaloneArgs sysrootPath wasmOutputFile cOutputFile = + [ "-nodefaultlibs", + "-I", + takeDirectory minicRuntime, + "--target=wasm32-wasi", + "--sysroot", + sysrootPath, + "-o", + wasmOutputFile, + wallocPath, + cOutputFile + ] + where + minicRuntime :: FilePath + minicRuntime = $(makeRelativeToProject "minic-runtime/standalone/minic-runtime.h" >>= strToExp) + wallocPath :: FilePath + wallocPath = $(makeRelativeToProject "minic-runtime/standalone/walloc.c" >>= strToExp) + +libcArgs :: FilePath -> FilePath -> FilePath -> [String] +libcArgs sysrootPath wasmOutputFile cOutputFile = + [ "-nodefaultlibs", + "-I", + takeDirectory minicRuntime, + "-lc", + "--target=wasm32-wasi", + "--sysroot", + sysrootPath, + "-o", + wasmOutputFile, + cOutputFile + ] + where + minicRuntime :: FilePath + minicRuntime = $(makeRelativeToProject "minic-runtime/libc/minic-runtime.h" >>= strToExp) + allTests :: TestTree allTests = testGroup diff --git a/tests/positive/MiniC/HelloWorld/Input.mjuvix b/tests/positive/MiniC/HelloWorld/Input.mjuvix index 1eae2f73a..6e927eb90 100644 --- a/tests/positive/MiniC/HelloWorld/Input.mjuvix +++ b/tests/positive/MiniC/HelloWorld/Input.mjuvix @@ -1,9 +1,5 @@ module Input; -foreign c { - #include -}; - axiom String : Type; compile String { @@ -20,7 +16,7 @@ axiom put-str-ln : String → Action; compile put-str-ln { ghc ↦ "putStrLn"; - c ↦ "puts"; + c ↦ "putStrLn"; }; main : Action; diff --git a/tests/positive/MiniC/HigherOrder/Input.mjuvix b/tests/positive/MiniC/HigherOrder/Input.mjuvix index 8adbf8cc0..89bb3cb3a 100644 --- a/tests/positive/MiniC/HigherOrder/Input.mjuvix +++ b/tests/positive/MiniC/HigherOrder/Input.mjuvix @@ -1,10 +1,5 @@ module Input; -foreign c { - #include -}; - - -------------------------------------------------------------------------------- -- Strings -------------------------------------------------------------------------------- @@ -29,10 +24,6 @@ foreign c { int sequence(int a, int b) { return a + b; \} - - int putStr(char* s) { - return fputs(s, stdout); - \} }; infixl 1 >>; @@ -51,7 +42,7 @@ compile put-str { axiom put-str-ln : String → Action; compile put-str-ln { - c ↦ "puts"; + c ↦ "putStrLn"; }; -------------------------------------------------------------------------------- @@ -68,13 +59,6 @@ foreign c { int plus(int l, int r) { return l + r; \} - - char* intToStr(int i) { - int length = snprintf(NULL, 0, "%d", i); - char* str = malloc(length + 1); - snprintf(str, length + 1, "%d", i); - return str; - \} }; infixl 6 +int; diff --git a/tests/positive/MiniC/MultiModules/IO.mjuvix b/tests/positive/MiniC/MultiModules/IO.mjuvix index f50251c10..84c8775c8 100644 --- a/tests/positive/MiniC/MultiModules/IO.mjuvix +++ b/tests/positive/MiniC/MultiModules/IO.mjuvix @@ -14,10 +14,6 @@ foreign c { int sequence(int a, int b) { return a + b; \} - - int putStr(char* s) { - return fputs(s, stdout); - \} }; infixl 1 >>; @@ -39,7 +35,7 @@ axiom put-str-ln : String → Action; compile put-str-ln { ghc ↦ "putStrLn"; - c ↦ "puts"; + c ↦ "putStrLn"; }; end; diff --git a/tests/positive/MiniC/Nat/Input.mjuvix b/tests/positive/MiniC/Nat/Input.mjuvix index 77c5196b7..65f20f578 100644 --- a/tests/positive/MiniC/Nat/Input.mjuvix +++ b/tests/positive/MiniC/Nat/Input.mjuvix @@ -1,9 +1,5 @@ module Input; -foreign c { - #include -}; - -------------------------------------------------------------------------------- -- Booleans -------------------------------------------------------------------------------- @@ -39,10 +35,6 @@ foreign c { int sequence(int a, int b) { return a + b; \} - - int putStr(char* s) { - return fputs(s, stdout); - \} }; infixl 1 >>; @@ -64,7 +56,7 @@ axiom put-str-ln : String → Action; compile put-str-ln { ghc ↦ "putStrLn"; - c ↦ "puts"; + c ↦ "putStrLn"; }; bool-to-str : Bool → String; @@ -86,13 +78,6 @@ foreign c { int plus(int l, int r) { return l + r; \} - - char* intToStr(int i) { - int length = snprintf(NULL, 0, "%d", i); - char* str = malloc(length + 1); - snprintf(str, length + 1, "%d", i); - return str; - \} }; infixl 6 +int; diff --git a/tests/positive/MiniC/Polymorphism/Input.mjuvix b/tests/positive/MiniC/Polymorphism/Input.mjuvix index 29cb8ccdf..b712068c1 100644 --- a/tests/positive/MiniC/Polymorphism/Input.mjuvix +++ b/tests/positive/MiniC/Polymorphism/Input.mjuvix @@ -1,9 +1,5 @@ module Input; -foreign c { - #include -}; - -------------------------------------------------------------------------------- -- Booleans -------------------------------------------------------------------------------- @@ -39,10 +35,6 @@ foreign c { int sequence(int a, int b) { return a + b; \} - - int putStr(char* s) { - return fputs(s, stdout); - \} }; infixl 1 >>; @@ -64,7 +56,7 @@ axiom put-str-ln : String → Action; compile put-str-ln { ghc ↦ "putStrLn"; - c ↦ "puts"; + c ↦ "putStrLn"; }; bool-to-str : Bool → String;