1
1
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:
Paul Cadman 2022-05-19 08:48:23 +01:00 committed by GitHub
parent 50ea7373ee
commit cfc11fecc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 714 additions and 86 deletions

View File

@ -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:

View 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_

View 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_

View 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;
}
}

View File

@ -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.*

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;