Skip to content

Commit a9646f1

Browse files
committed
Port to DynASM, passes (almost) all selftests
Changes main output to be generating machine code via DynASM, rather than outputting text to be passed to the assembler. Because chibicc assumes run-and-exit, it doesn't do any free/cleanup. To work around this, while compiling multiple translation units in a single run, each module jankily purges and resets and the heap is tossed between files. For each translation unit, a custom-obj-ish thing is output (which maybe should have been ELF or whatever instead of custom...). After compiling all the files listed on the command line, it does a link pass to handle function references cross-module as well as data relocations (for example to create literals or initialize global data). There's also no particularly good reason that these objs (.dyo) need to be written to disk (rather than simply being an in-memory structure), it was just useful to debug the output. All chibicc self-tests are passing except for two: 1) asm.c (because it just passes the text of the asm block to the system assembler which no longer exists); and 2) tls.c just because TLS support hasn't been written yet. This change also deletes most of the "driver" part of main.c as it didn't seem to make a lot of sense with a JIT mode. Perhaps it would be useful to bring some of that back, if only to be able to use it to build common C software like sqlite, etc. for testing purposes. The code for -S (output textual assembler) that is chibicc's normal mode is still in the code, this was done during the rewrite to make it easier to track. A future change will remove that code; a pristine copy of chibicc is equally useful to compare final output with in any case.
1 parent 33fcb55 commit a9646f1

32 files changed

+23283
-2605
lines changed

Makefile

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,81 @@
1-
CFLAGS=-std=c11 -g -fno-common -Wall -Wno-switch
1+
CFLAGS=-std=c11 -g -fno-common -Wall -Werror -Wno-switch -pthread
22

3-
SRCS=codegen.c hashmap.c main.c parse.c preprocess.c strings.c tokenize.c type.c unicode.c
3+
SRCS=codegen.c hashmap.c main.c parse.c preprocess.c strings.c tokenize.c type.c unicode.c alloc.c dyo.c link.c
44
OBJS=$(SRCS:.c=.o)
55

6-
TEST_SRCS=$(wildcard test/*.c)
7-
TESTS=$(TEST_SRCS:.c=.exe)
8-
9-
# Stage 1
6+
# XXX:
7+
# test/asm.c
8+
# chibicc passes asm() blocks directly through to the system assembler, so now
9+
# that we're not using that, there's no assembler available.
10+
#
11+
# test/tls.c
12+
# TLS not implemented
13+
#
14+
# test/commonsym.c
15+
# currently passes, but I thought I disabled support, so need to investigate.
16+
#
17+
TEST_SRCS=\
18+
test/alignof.c \
19+
test/alloca.c \
20+
test/arith.c \
21+
test/atomic.c \
22+
test/attribute.c \
23+
test/bitfield.c \
24+
test/builtin.c \
25+
test/cast.c \
26+
test/commonsym.c \
27+
test/compat.c \
28+
test/complit.c \
29+
test/const.c \
30+
test/constexpr.c \
31+
test/control.c \
32+
test/decl.c \
33+
test/enum.c \
34+
test/extern.c \
35+
test/float.c \
36+
test/function.c \
37+
test/generic.c \
38+
test/initializer.c \
39+
test/line.c \
40+
test/literal.c \
41+
test/macro.c \
42+
test/offsetof.c \
43+
test/pointer.c \
44+
test/pragma-once.c \
45+
test/sizeof.c \
46+
test/stdhdr.c \
47+
test/string.c \
48+
test/struct.c \
49+
test/typedef.c \
50+
test/typeof.c \
51+
test/usualconv.c \
52+
test/unicode.c \
53+
test/union.c \
54+
test/varargs.c \
55+
test/variable.c \
56+
test/vla.c
1057

1158
chibicc: $(OBJS)
12-
$(CC) $(CFLAGS) -o $@ $^ wait_hack.c $(LDFLAGS)
13-
14-
$(OBJS): chibicc.h
15-
16-
test/%.exe: chibicc test/%.c
17-
./chibicc -Iinclude -Itest -c -o test/$*.o test/$*.c
18-
$(CC) -pthread -o $@ test/$*.o wait_hack.c -xc test/common
59+
$(CC) $(CFLAGS) -o $@ $^ wait_hack.c -ldl $(LDFLAGS)
1960

20-
test: $(TESTS)
21-
for i in $^; do echo $$i; ./$$i || exit 1; echo; done
22-
test/driver.sh ./chibicc
61+
dumpdyo: dumpdyo.o dyo.o hashmap.o alloc.o
62+
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
2363

24-
test-all: test test-stage2
25-
26-
# Stage 2
27-
28-
stage2/chibicc: $(OBJS:%=stage2/%)
29-
$(CC) $(CFLAGS) -o $@ wait_hack.c $^ $(LDFLAGS)
64+
$(OBJS): chibicc.h
3065

31-
stage2/%.o: chibicc %.c
32-
mkdir -p stage2/test
33-
./chibicc -c -o $(@D)/$*.o $*.c
66+
minilua: dynasm/minilua.c
67+
$(CC) $(CFLAGS) -o $@ $^ -lm
3468

35-
stage2/test/%.exe: stage2/chibicc test/%.c
36-
mkdir -p stage2/test
37-
./stage2/chibicc -Iinclude -Itest -c -o stage2/test/$*.o test/$*.c
38-
$(CC) -pthread -o $@ stage2/test/$*.o -xc test/common
69+
codegen.c: codegen.in.c minilua
70+
./minilua dynasm/dynasm.lua -o $@ codegen.in.c
3971

40-
test-stage2: $(TESTS:test/%=stage2/test/%)
41-
for i in $^; do echo $$i; ./$$i || exit 1; echo; done
42-
test/driver.sh ./stage2/chibicc
72+
test: $(TEST_SRCS) chibicc
73+
for i in $(filter-out chibicc,$^); do echo $$i; ./chibicc -Itest test/common $$i || exit 1; echo; done
4374

4475
# Misc.
4576

4677
clean:
47-
rm -rf chibicc tmp* $(TESTS) test/*.s test/*.exe stage2
78+
rm -rf chibicc codegen.c tmp* test/*.s test/*.exe minilua *.dyo
4879
find * -type f '(' -name '*~' -o -name '*.o' ')' -exec rm {} ';'
4980

50-
.PHONY: test clean test-stage2
81+
.PHONY: test clean test-stage2 dumpdyo

alloc.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#include "chibicc.h"
2+
3+
#ifdef _MSC_VER
4+
#include <windows.h>
5+
#else
6+
#include <sys/mman.h>
7+
#endif
8+
9+
static void* allmem;
10+
static void* current_alloc_pointer;
11+
12+
#define HEAP_SIZE (256<<20)
13+
14+
void bumpcalloc_init(void) {
15+
allmem = allocate_writable_memory(HEAP_SIZE);
16+
current_alloc_pointer = allmem;
17+
}
18+
19+
void* bumpcalloc(size_t num, size_t size) {
20+
size_t toalloc = align_to(num * size, 8);
21+
void* ret = current_alloc_pointer;
22+
current_alloc_pointer += toalloc;
23+
if (current_alloc_pointer > allmem + HEAP_SIZE) {
24+
fprintf(stderr, "heap exhausted");
25+
abort();
26+
}
27+
memset(ret, 0, toalloc);
28+
return ret;
29+
}
30+
31+
void* bumplamerealloc(void* old, size_t old_size, size_t new_size) {
32+
void* newptr = bumpcalloc(1, new_size);
33+
memcpy(newptr, old, MIN(old_size, new_size));
34+
return newptr;
35+
}
36+
37+
void bumpcalloc_reset(void) {
38+
free_executable_memory(allmem, HEAP_SIZE);
39+
allmem = NULL;
40+
current_alloc_pointer = NULL;
41+
}
42+
43+
void* aligned_allocate(size_t size, size_t alignment) {
44+
#ifdef _MSC_VER
45+
return _aligned_malloc(size, alignment);
46+
#else
47+
return aligned_alloc(alignment, size);
48+
#endif
49+
}
50+
51+
// Allocates RW memory of given size and returns a pointer to it. On failure,
52+
// prints out the error and returns NULL. Unlike malloc, the memory is allocated
53+
// on a page boundary so it's suitable for calling mprotect.
54+
void* allocate_writable_memory(size_t size) {
55+
#ifdef _MSC_VER
56+
void* p =
57+
VirtualAlloc(0, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
58+
if (!p) {
59+
fprintf(stderr, "VirtualAlloc failed");
60+
return NULL;
61+
}
62+
return p;
63+
#else
64+
void* ptr =
65+
mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
66+
if (ptr == (void*)-1) {
67+
perror("mmap");
68+
return NULL;
69+
}
70+
return ptr;
71+
#endif
72+
}
73+
74+
// Sets a RX permission on the given memory, which must be page-aligned. Returns
75+
// 0 on success. On failure, prints out the error and returns -1.
76+
bool make_memory_executable(void* m, size_t size) {
77+
#ifdef _MSC_VER
78+
// TODO: alloc as non-execute
79+
return true;
80+
#else
81+
if (mprotect(m, size, PROT_READ | PROT_EXEC) == -1) {
82+
perror("mprotect");
83+
return false;
84+
}
85+
return true;
86+
#endif
87+
}
88+
89+
void free_executable_memory(void* p, size_t size) {
90+
#ifdef _MSC_VER
91+
VirtualFree(p, size, MEM_RELEASE);
92+
#else
93+
munmap(p, size);
94+
#endif
95+
}

0 commit comments

Comments
 (0)