mirror of
https://github.com/anoma/juvix.git
synced 2024-10-27 02:04:29 +03:00
Add minic-runtime for linking without libc (#113)
* [minic] Add minic-runtime for linking without libc The C generated by minic now depends on `minic-runtime.h`. There are two versions of this, one that depends on libc and one that doesn't (i.e standalone). The standalone runtime implements the minimal set of functions required for the tests to compile and run. The standalone runtime also contains a copy of https://github.com/wingo/walloc, a small memory allocator that's indended for use with WASM. The benefit of using the standalone runtime is that the resulting binaries are much smaller. For example the Nat example compiles to about 24k WASM binary with libc, the standalone version is 2.8k. This commit adds a dependency on https://hackage.haskell.org/package/file-embed for finding the path to the minic-runtime directory in tests. It does not add any additional transitive dependencies to the project.
This commit is contained in:
parent
50ea7373ee
commit
cfc11fecc5
@ -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. <<sysroot>>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:
|
||||
|
22
minic-runtime/libc/minic-runtime.h
Normal file
22
minic-runtime/libc/minic-runtime.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef MINIC_RUNTIME_H_
|
||||
#define MINIC_RUNTIME_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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_
|
136
minic-runtime/standalone/minic-runtime.h
Normal file
136
minic-runtime/standalone/minic-runtime.h
Normal file
@ -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_
|
466
minic-runtime/standalone/walloc.c
Normal file
466
minic-runtime/standalone/walloc.c
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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.*
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,9 +1,5 @@
|
||||
module Input;
|
||||
|
||||
foreign c {
|
||||
#include <stdio.h>
|
||||
};
|
||||
|
||||
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;
|
||||
|
@ -1,10 +1,5 @@
|
||||
module Input;
|
||||
|
||||
foreign c {
|
||||
#include <stdio.h>
|
||||
};
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- 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;
|
||||
|
@ -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;
|
||||
|
@ -1,9 +1,5 @@
|
||||
module Input;
|
||||
|
||||
foreign c {
|
||||
#include <stdio.h>
|
||||
};
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- 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;
|
||||
|
@ -1,9 +1,5 @@
|
||||
module Input;
|
||||
|
||||
foreign c {
|
||||
#include <stdio.h>
|
||||
};
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- 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;
|
||||
|
Loading…
Reference in New Issue
Block a user