Skip to content

Commit

Permalink
Compile time if (#388)
Browse files Browse the repository at this point in the history
  • Loading branch information
Akuli authored Dec 6, 2023
1 parent 87c84ae commit c086866
Show file tree
Hide file tree
Showing 19 changed files with 343 additions and 157 deletions.
12 changes: 11 additions & 1 deletion self_hosted/create_llvm_ir.jou
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "./typecheck.jou"
import "./types.jou"
import "./ast.jou"
import "./target.jou"
import "./evaluate.jou"
import "stdlib/io.jou"
import "stdlib/mem.jou"
import "stdlib/str.jou"
Expand Down Expand Up @@ -571,7 +572,16 @@ class AstToIR:
assert field != NULL
result = LLVMBuildExtractValue(self->builder, instance, field->union_id, field->name)

elif ast->kind == AstExpressionKind::GetVariable or ast->kind == AstExpressionKind::Indexing:
elif ast->kind == AstExpressionKind::GetVariable:
v = get_special_constant(ast->varname)
if v == -1:
# normal variable
pointer = self->do_address_of_expression(ast)
result = LLVMBuildLoad(self->builder, pointer, ast->varname)
else:
result = LLVMConstInt(LLVMInt1Type(), v, False as int)

elif ast->kind == AstExpressionKind::Indexing:
pointer = self->do_address_of_expression(ast)
result = LLVMBuildLoad(self->builder, pointer, ast->varname)

Expand Down
62 changes: 62 additions & 0 deletions self_hosted/evaluate.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Compile-time evaluating if statements.

import "./ast.jou"
import "./errors_and_warnings.jou"
import "stdlib/str.jou"
import "stdlib/mem.jou"


# Return values: 1=true, 0=false, -1=unknown
def get_special_constant(name: byte*) -> int:
if strcmp(name, "WINDOWS") == 0:
return WINDOWS as int
if strcmp(name, "MACOS") == 0:
return MACOS as int
return -1


def evaluate_condition(expr: AstExpression*) -> bool:
if expr->kind == AstExpressionKind::GetVariable:
v = get_special_constant(expr->varname)
if v == 1:
return True
if v == 0:
return False
fail(expr->location, "cannot evaluate condition at compile time")


# returns the statements to replace if statement with
def evaluate_compile_time_if_statement(if_stmt: AstIfStatement*) -> AstBody:
result = &if_stmt->else_body
for p = if_stmt->if_and_elifs; p < &if_stmt->if_and_elifs[if_stmt->n_if_and_elifs]; p++:
if evaluate_condition(&p->condition):
result = &p->body
break

ret = *result
*result = AstBody{} # avoid double-free
return ret


# Replace body->statements[i] with zero or more statements from another body.
def replace(body: AstBody*, i: int, new: AstBody) -> void:
body->statements[i].free()

item_size = sizeof(body->statements[0])
body->statements = realloc(body->statements, (body->nstatements + new.nstatements) * item_size)
memmove(&body->statements[i + new.nstatements], &body->statements[i+1], (body->nstatements - (i+1)) * item_size)
memcpy(&body->statements[i], new.statements, new.nstatements * item_size)

free(new.statements)
body->nstatements--
body->nstatements += new.nstatements


# This handles nested if statements.
def evaluate_compile_time_if_statements_in_body(body: AstBody*) -> void:
i = 0
while i < body->nstatements:
if body->statements[i].kind == AstStatementKind::If:
replace(body, i, evaluate_compile_time_if_statement(&body->statements[i].if_statement))
else:
i++
13 changes: 8 additions & 5 deletions self_hosted/main.jou
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "./paths.jou"
import "./target.jou"
import "./create_llvm_ir.jou"
import "./llvm.jou"
import "./evaluate.jou"
import "stdlib/mem.jou"
import "stdlib/process.jou"
import "stdlib/str.jou"
Expand Down Expand Up @@ -171,7 +172,7 @@ class Compiler:
def determine_automagic_files(self) -> void:
self->automagic_files[0] = malloc(strlen(self->stdlib_path) + 40)
sprintf(self->automagic_files[0], "%s/_assert_fail.jou", self->stdlib_path)
if is_windows():
if WINDOWS:
self->automagic_files[1] = malloc(strlen(self->stdlib_path) + 40)
sprintf(self->automagic_files[1], "%s/_windows_startup.jou", self->stdlib_path)

Expand Down Expand Up @@ -207,6 +208,8 @@ class Compiler:
ast.print()
free(tokens) # TODO: do this properly

evaluate_compile_time_if_statements_in_body(&ast.body)

self->files = realloc(self->files, sizeof self->files[0] * (self->nfiles + 1))
self->files[self->nfiles++] = FileState{ast = ast}

Expand Down Expand Up @@ -329,11 +332,11 @@ class Compiler:
else:
exe = strdup(self->args->output_file)

if is_windows() and not ends_with(exe, ".exe") and not ends_with(exe, ".EXE"):
if WINDOWS and not ends_with(exe, ".exe") and not ends_with(exe, ".EXE"):
exe = realloc(exe, strlen(exe) + 10)
strcat(exe, ".exe")

if is_windows():
if WINDOWS:
for i = 0; exe[i] != '\0'; i++:
if exe[i] == '/':
exe[i] = '\\'
Expand Down Expand Up @@ -374,7 +377,7 @@ class Compiler:

def link(self, object_files: byte**) -> byte*:
exe = self->get_exe_file_path()
if is_windows():
if WINDOWS:
c_compiler = find_installation_directory()
c_compiler = realloc(c_compiler, strlen(c_compiler) + 100)
strcat(c_compiler, "\\mingw64\\bin\\gcc.exe")
Expand All @@ -392,7 +395,7 @@ class Compiler:
sprintf(&command[strlen(command)], " \"%s\"", object_files[i])
strcat(command, " -lm")

if is_windows():
if WINDOWS:
# windows strips outermost quotes for some reason, so let's quote it all one more time...
memmove(&command[1], &command[0], strlen(command) + 1)
command[0] = '"'
Expand Down
150 changes: 65 additions & 85 deletions self_hosted/paths.jou
Original file line number Diff line number Diff line change
Expand Up @@ -3,87 +3,65 @@ import "stdlib/str.jou"
import "stdlib/io.jou"
import "stdlib/process.jou"

declare GetModuleFileNameA(hModule: void*, lpFilename: byte*, nSize: int) -> int
declare readlink(linkpath: byte*, result: byte*, result_size: long) -> long
declare _mkdir(path: byte*) -> int # windows
declare mkdir(path: byte*, mode: int) -> int # posix
if WINDOWS:
declare GetModuleFileNameA(hModule: void*, lpFilename: byte*, nSize: int) -> int
elif MACOS:
declare _NSGetExecutablePath(buf: byte*, bufsize: int*) -> int
else:
declare readlink(linkpath: byte*, result: byte*, result_size: long) -> long

if WINDOWS:
declare _mkdir(path: byte*) -> int
else:
declare mkdir(path: byte*, mode: int) -> int # posix

declare dirname(path: byte*) -> byte*
declare stat(path: byte*, buf: byte[1000]*) -> int # lol
declare popen(command: byte*, type: byte*) -> FILE*
declare pclose(stream: FILE*) -> int
declare _NSGetExecutablePath(buf: byte*, bufsize: int*) -> int


def is_windows() -> bool:
# TODO: this is just weird...
return getenv("OS") != NULL and strstr(getenv("OS"), "Windows") != NULL

# https://stackoverflow.com/a/3466183
def is_macos() -> bool:
if is_windows():
return False

uname = popen("uname", "r")
if uname == NULL:
return False

output: byte[100]
memset(&output, 0, sizeof(output))
fgets(output, sizeof(output) as int, uname)

pclose(uname)
return starts_with(output, "Darwin")


def _find_current_executable_windows() -> byte*:
buf = NULL
for size = 2L; True; size *= 2:
buf = realloc(buf, size)
memset(buf, 0, size)
ret = GetModuleFileNameA(NULL, buf, size as int)
if ret <= 0:
return NULL # error --> give up
if ret < size:
# buffer is big enough, it fits
return buf

def _find_current_executable_macos() -> byte*:
n = 1
result: byte* = malloc(n)
ret = _NSGetExecutablePath(result, &n) # sets n to desired size
assert ret < 0 # didn't fit
result = realloc(result, n)
ret = _NSGetExecutablePath(result, &n)
assert ret == 0
return result

def _find_current_executable_linux() -> byte*:
buf = NULL
for size = 2L; True; size *= 2:
buf = realloc(buf, size)
memset(buf, 0, size)
ret = readlink("/proc/self/exe", buf, size)
if ret <= 0:
return NULL
if ret < size:
# buffer is big enough, it fits
return buf


def find_current_executable() -> byte*:
if is_windows():
result = _find_current_executable_windows()
elif is_macos():
result = _find_current_executable_macos()
else:
result = _find_current_executable_linux()

if result == NULL:
# TODO: include os error message (GetLastError / errno)
fprintf(stderr, "error: cannot locate currently running executable, needed for finding the Jou standard library\n")
exit(1)

return result
def fail_finding_exe() -> noreturn:
# TODO: include os error message (GetLastError / errno)
fprintf(stderr, "error: cannot locate currently running executable, needed for finding the Jou standard library\n")
exit(1)


if WINDOWS:
def find_current_executable() -> byte*:
buf = NULL
for size = 2L; True; size *= 2:
buf = realloc(buf, size)
memset(buf, 0, size)
ret = GetModuleFileNameA(NULL, buf, size as int)
if ret <= 0:
fail_finding_exe()
if ret < size:
# buffer is big enough, it fits
return buf

elif MACOS:
def find_current_executable() -> byte*:
n = 1
result: byte* = malloc(n)
ret = _NSGetExecutablePath(result, &n) # sets n to desired size
assert ret < 0 # didn't fit
result = realloc(result, n)
ret = _NSGetExecutablePath(result, &n)
if ret != 0:
fail_finding_exe()
return result

else:
def find_current_executable() -> byte*:
buf = NULL
for size = 2L; True; size *= 2:
buf = realloc(buf, size)
memset(buf, 0, size)
ret = readlink("/proc/self/exe", buf, size)
if ret <= 0:
fail_finding_exe()
if ret < size:
# buffer is big enough, it fits
return buf


def find_installation_directory() -> byte*:
Expand All @@ -92,12 +70,13 @@ def find_installation_directory() -> byte*:
free(exe)
return result


def find_stdlib() -> byte*:
checked: byte*[3]
memset(&checked, 0, sizeof checked)

exedir = find_current_executable()
while is_windows() and strstr(exedir, "\\") != NULL:
while WINDOWS and strstr(exedir, "\\") != NULL:
*strstr(exedir, "\\") = '/'

for i = 0; i < sizeof checked / sizeof checked[0]; i++:
Expand Down Expand Up @@ -130,13 +109,14 @@ def find_stdlib() -> byte*:
fprintf(stderr, " %s\n", checked[i])
exit(1)

def my_mkdir(path: byte*) -> void:
# Ignoring return values, because there's currently no way to check errno.
# We need to ignore the error when directory exists already (EEXIST).
# Ideally we wouldn't ignore any other errors.
if is_windows():
# Ignoring return values, because there's currently no way to check errno.
# We need to ignore the error when directory exists already (EEXIST).
# Ideally we wouldn't ignore any other errors.
if WINDOWS:
def my_mkdir(path: byte*) -> void:
_mkdir(path)
else:
else:
def my_mkdir(path: byte*) -> void:
mkdir(path, 0o777) # this is what mkdir in bash does according to strace

def get_path_to_file_in_jou_compiled(filename: byte*) -> byte*:
Expand All @@ -153,7 +133,7 @@ def delete_slice(start: byte*, end: byte*) -> void:
memmove(start, end, strlen(end) + 1)

def simplify_path(path: byte*) -> void:
if is_windows():
if WINDOWS:
# Backslash to forward slash.
for p = path; *p != '\0'; p++:
if *p == '\\':
Expand Down
1 change: 1 addition & 0 deletions self_hosted/runs_wrong.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ tests/wrong_type/index.jou
stdlib/ascii.jou
tests/should_succeed/ascii_test.jou
stdlib/_macos_startup.jou
tests/should_succeed/if_WINDOWS_at_runtime.jou
3 changes: 1 addition & 2 deletions self_hosted/target.jou
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
# "target" variable, it contains everything you will ever need.

import "./llvm.jou"
import "./paths.jou"
import "stdlib/str.jou"
import "stdlib/io.jou"
import "stdlib/process.jou"
Expand All @@ -34,7 +33,7 @@ def init_target() -> void:
LLVMInitializeX86AsmParser()
LLVMInitializeX86AsmPrinter()

if is_windows():
if WINDOWS:
# LLVM's default is x86_64-pc-windows-msvc
target.triple = "x86_64-pc-windows-gnu"
else:
Expand Down
Loading

0 comments on commit c086866

Please sign in to comment.