G

G Programming Language
git clone http://git.omkov.net/G
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2023-07-12 10:36:29
Commit8afca84b873628ea66a00a16be614d515f23f10c
Parentd2c465aaf479e4049cc7a79f14fc09a72a69f178

Implement strings, cstring(), and len()

Diffstat

M README.md | 6 ++++--
M examples/hello.g | 11 ++++++++---
M src/analyse.c | 12 ++++++------
M src/init.c | 11 ++++++++++-
M src/lex.c | 5 ++++-
M src/llvm.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
M src/llvm.h | 5 +++--
A src/llvm/attr.c | 33 +++++++++++++++++++++++++++++++++
A src/llvm/attr.def | 4 ++++
A src/llvm/llvm.h | 22 ++++++++++++++++++++++
M src/log.c | 1 +
M src/log.h | 7 +++++--
M src/main.c | 15 ++++++++++-----
M src/parse.c | 13 +++++++++----
M src/parse.h | 4 ++--
M src/type.c | 11 ++++++-----
M src/type.h | 20 ++++++++++----------

17 files changed, 278 insertions, 111 deletions

diff --git a/README.md b/README.md
index 6054900..ab9df5a 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,8 @@ command. The second command will output an executable file, *a.out* by default.
 - [x] Implement pointers
 - [x] Implement arrays
 - [ ] Implement variable length arrays (?)
+- [ ] Implement structs
+- [ ] Implement unions
 - [x] Implement expressions
 - [x] Implement type casting
 - [ ] Implement type casting to pointers and arrays
@@ -66,13 +68,13 @@ command. The second command will output an executable file, *a.out* by default.
 - [ ] Implement `defer`
 - [ ] Implement `errdefer` (?)
 - [ ] Implement variadic procedure arguments
-- [ ] Implement first class strings
+- [x] Implement first class strings
 - [x] Implement syscalls
 - [ ] Implement generics of some kind
 - [ ] Implement module definition
 - [ ] Implement module use
 - [ ] Implement foreign code calling
-- [ ] Optional types using `?` prefix
+- [ ] Optional types using `?` prefix (?)
 - [ ] Error or types using `!` prefix (?)
 - ...
 - [ ] Re-write compiler in **G**
diff --git a/examples/hello.g b/examples/hello.g
index 62f4cde..ecfebc2 100644
--- a/examples/hello.g
+++ b/examples/hello.g
@@ -1,4 +1,9 @@
-main :: proc() -> sint {
-	hello : [14]u8 = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\n' };
-	return #syscall(uint(1), sint(1), ptr(hello), uint(14));
+print :: proc(s: string) {
+	#syscall(uint(1), sint(1), cstring(s), len(s));
+	return;
+}
+
+main :: proc() -> u8 {
+	print("Hello, World!\n");
+	return 0;
 }
diff --git a/src/analyse.c b/src/analyse.c
index fb1830e..0178a82 100644
--- a/src/analyse.c
+++ b/src/analyse.c
@@ -107,6 +107,7 @@ static void analyse_stmt_decl(ast *a, syt *st) {
 			/* TODO */
 		}
 	}
+	else if (value_type->k == TY_STRING) { if (A.t == NULL) { A.t = C[0]->t; } return; }
 
 	else { note("TODO", A.ln, A.cl, -1, "Unhandled value kind %s", ast_ks[C[0]->k]); }
 }
@@ -165,9 +166,8 @@ static void analyse_expr(ast *a, syt *st) {
 			A.k = AK_CAST; A.t = sym->t;
 			if (CL > 2) { note("TODO", A.ln, A.cl, 0, "Type casts must only have a single argument"); }
 		}
-		
-		for (UINT i = 1; i < CL; i += 1) { analyse_expr(C[i], st); }
-	} break;
+		else if (sym->k == AK_PROC) { A.k = AK_BUILTIN; A.t = sym->t; }
+	} goto childs;
 	case AK_SUBS: {
 		assert(CL == 2);
 
@@ -177,9 +177,9 @@ static void analyse_expr(ast *a, syt *st) {
 	} break;
 	case AK_PROC:   { analyse_expr_proc(a, st);              } break;
 	case AK_HASH:   { analyse_expr_hash(a, st);              } break;
-	case AK_OP_ADO: { A.t = type_ptr(ast_type(C[0], st), 1); } break;
-	case AK_OP_DRF: { A.t = ast_type(C[0], st)->base;        } break;
-	default: { for (UINT i = 0; i < CL; i += 1) { analyse_expr(C[i], st); }} break;
+	case AK_OP_ADO: { A.t = type_ptr(ast_type(C[0], st), 1); } goto childs;
+	case AK_OP_DRF: { A.t = ast_type(C[0], st)->base;        } goto childs;
+	childs: default: { for (UINT i = 0; i < CL; i += 1) { analyse_expr(C[i], st); }} break;
 	}
 }
 
diff --git a/src/init.c b/src/init.c
index 542a9b0..c3a0dc1 100644
--- a/src/init.c
+++ b/src/init.c
@@ -11,7 +11,7 @@
 #include <string.h>
 
 static ast kwds[] = {
-	{ AK_TYPE, 0, 0, 0, "ptr",  &TYPE(TY_PTR),  { 0 }, NULL },
+	{ AK_TYPE, 0, 0, 0, "ptr", &TYPE(TY_PTR), { 0 }, NULL },
 
 	/* Boolean Types */
 	{ AK_TYPE, 0, 0, 0, "bool", &TYPE(TY_BOOL), { 0 }, NULL },
@@ -41,6 +41,15 @@ static ast kwds[] = {
 	{ AK_TYPE, 0, 0, 0, "f64",  &TYPE(TY_F64),  { 0 }, NULL },
 	{ AK_TYPE, 0, 0, 0, "f128", &TYPE(TY_F128), { 0 }, NULL },
 
+	/* Character and String Types */
+	{ AK_TYPE, 0, 0, 0, "char",   &TYPE(TY_CHAR),   { 0 }, NULL },
+	{ AK_TYPE, 0, 0, 0, "rune",   &TYPE(TY_RUNE),   { 0 }, NULL },
+	{ AK_TYPE, 0, 0, 0, "string", &TYPE(TY_STRING), { 0 }, NULL },
+	
+	/* Builtins */
+	{ AK_PROC, 0, 0, 0, "cstring", &(type){ TY_PTR, TF_PTR, -1, "", &TYPE(TY_U8) }, { 0 }, NULL },
+	{ AK_PROC, 0, 0, 0, "len", &TYPE(TY_UINT), { 0 }, NULL },
+	
 	{ AK_VOID, 0, 0, 0, NULL, NULL, { 0 }, NULL }
 };
 
diff --git a/src/lex.c b/src/lex.c
index 722ea95..c240889 100644
--- a/src/lex.c
+++ b/src/lex.c
@@ -115,7 +115,7 @@ tok lex_next(lex *l) {
 
 		for (P += 1; is_alpha(C) || is_digit_dec(C); P += 1);
 		if (C == '.') { T.k = TK_FLT; P += 1; for (P += 1; is_digit_dec(C); P += 1); }
-		/* TODO lex exponent part of real numbers */
+		if (C == 'e') { P += (C == '+' || C == '-') ? 2 : 1; for (P += 1; is_digit_dec(C); P += 1); }
 
 		UINT sl = P - s; CL += sl;
 
@@ -286,6 +286,7 @@ void lex_debug(lex *l) {
 
 /* Parse an integer string into a value. */
 static inline u64 parse_int(char *s) {
+	/* TODO lex exponent part of numbers */
 	register u64 v = 0; u64 c; register UINT b = 10;
 
 	if (s[0] == '0') switch (s[1]) {
@@ -309,6 +310,7 @@ static inline u64 parse_int(char *s) {
 }
 
 static inline f128 parse_flt(char *s) {
+	/* TODO lex exponent part of numbers */
 	register f128 v = 0; u64 c; char *endptr;
 
 	v = strtold(s, &endptr);
diff --git a/src/llvm.c b/src/llvm.c
index b2a1eca..6522299 100644
--- a/src/llvm.c
+++ b/src/llvm.c
@@ -4,6 +4,7 @@
 // All rights reserved.
 
 #include "llvm.h"
+#include "llvm/llvm.h"
 #include "log.h"
 #include "parse.h"
 #include "symbol.h"
@@ -38,12 +39,12 @@ static LLVMValueRef llvm_stmt_for(ast *a, syt *st);
 static LLVMValueRef llvm_expr(ast *a, syt *st, bool load);
 static LLVMValueRef llvm_expr_proc(ast *a, syt *st);
 static LLVMValueRef llvm_expr_cast(ast *a, syt *st);
+static LLVMValueRef llvm_expr_builtin(ast *a, syt *st);
 static LLVMValueRef llvm_expr_hash(ast *a, syt *st);
 
+static LLVMValueRef llvm_str(ast *a, syt *st, bool load);
 static LLVMValueRef llvm_arr(ast *a, syt *st);
 
-static inline void llvm_init(char *file);
-static inline void llvm_free(void);
 static LLVMTypeRef llvm_type(type *t);
 static LLVMValueRef llvm_ival(type *t);
 
@@ -52,66 +53,70 @@ static LLVMValueRef llvm_ival(type *t);
 #define CL (a->c.al) /* AST child array length shorthand */
 
 /* Generate IR from an AST with LLVM. */
-void llvm(ast *a, char *file) {
-	llvm_init(file);
-	
-	/* Generate IR for all child nodes */
-	for (UINT i = 0; i < a->c.al; i += 1) { llvm_stmt_decl(a->c.a[i], &a->st); }
-	
-	char *err;
-	LLVMVerifyModule(llvm_module, LLVMAbortProcessAction, &err);
-	LLVMDisposeMessage(err);
-	
-	char *triple = LLVMGetDefaultTargetTriple();
-	LLVMSetTarget(llvm_module, triple);
-	
-	LLVMInitializeAllTargetInfos();
-	LLVMInitializeAllTargets();
-	LLVMInitializeAllTargetMCs();
-	LLVMInitializeAllAsmParsers();
-	LLVMInitializeAllAsmPrinters();
-	
-	LLVMTargetRef target; LLVMGetTargetFromTriple(triple, &target, &err);
-	if (!target) { error(2, "LLVMGetTargetFromTriple: %s", err); }
-	
-	LLVMTargetMachineRef machine = LLVMCreateTargetMachine(
-		target, triple, "generic", "", LLVMCodeGenLevelNone,
-		LLVMRelocDefault, LLVMCodeModelDefault
-	);
-	
-	LLVMTargetMachineEmitToFile(machine, llvm_module, "llvm.o", LLVMObjectFile, &err);
-	
-	LLVMDisposeTargetMachine(machine); llvm_free();
-}
-
-void llvm_bitcode(ast *a, char *file) {
-	llvm_init(file);
+void llvm(ast *a, char *file, llvm_action action) {
+	llvm_context = LLVMGetGlobalContext();
+	llvm_module  = LLVMModuleCreateWithName((file_name = file));
+	llvm_builder = LLVMCreateBuilder();
 
 	/* Generate IR for all child nodes */
 	for (UINT i = 0; i < a->c.al; i += 1) { llvm_stmt_decl(a->c.a[i], &a->st); }
 
-	if (LLVMWriteBitcodeToFile(llvm_module, "llvm.bc")) {
-		error(2, "LLVMWriteBitcodeToFile failure");
-	}
+	char *err = NULL; bool failed = false;
 
-	char *err = NULL;
-	LLVMVerifyModule(llvm_module, LLVMAbortProcessAction, &err);
-	LLVMDisposeMessage(err); llvm_free();
-}
-
-void llvm_ir(ast *a, char *file) {
-	llvm_init(file);
+	failed = LLVMVerifyModule(llvm_module, LLVMPrintMessageAction, &err);
+	LLVMDisposeMessage(err); err = NULL;
 
-	/* Generate IR for all child nodes */
-	for (UINT i = 0; i < a->c.al; i += 1) { llvm_stmt_decl(a->c.a[i], &a->st); }
-	
-	if (LLVMPrintModuleToFile(llvm_module, "llvm.ll", NULL)) {
-		error(2, "LLVMPrintModuleToFile failure");
+	switch (action) {
+	case llvm_obj: case llvm_asm: {
+		// if (failed) { break; } /* Don't compile after failure */
+		
+		char *triple = LLVMGetDefaultTargetTriple();
+		printf("Target: %s\n", triple);
+		LLVMSetTarget(llvm_module, triple);
+		
+		LLVMInitializeAllTargetInfos();
+		LLVMInitializeAllTargets();
+		LLVMInitializeAllTargetMCs();
+		LLVMInitializeAllAsmParsers();
+		LLVMInitializeAllAsmPrinters();
+		
+		LLVMTargetRef target = NULL;
+		
+		if (LLVMGetTargetFromTriple(triple, &target, &err)) {
+			panic("LLVMGetTargetFromTriple: %s", err);
+		}
+		if (err != NULL) { panic("%s", err); } LLVMDisposeMessage(err); err = NULL;
+		
+		LLVMTargetMachineRef machine = LLVMCreateTargetMachine(
+			target, triple, "generic", "", LLVMCodeGenLevelNone,
+			LLVMRelocPIC, LLVMCodeModelDefault
+		);
+		
+		char *outfile = action == llvm_obj ? "llvm.o" : "llvm.s";
+		int outform = action == llvm_obj ? LLVMObjectFile : LLVMAssemblyFile;
+		if (LLVMTargetMachineEmitToFile(machine, llvm_module, outfile, outform, &err)) {
+			panic("LLVMTargetMachineEmitToFile: %s", err);
+		}
+		if (err != NULL) { panic("%s", err); } LLVMDisposeMessage(err); err = NULL;
+		
+		LLVMDisposeTargetMachine(machine);
+	} break;
+	case llvm_ir: {
+		if (LLVMPrintModuleToFile(llvm_module, "llvm.ll", &err)) {
+			panic("LLVMPrintModuleToFile: %s", err);
+		}
+		if (err != NULL) { panic("%s", err); } LLVMDisposeMessage(err); err = NULL;
+	} break;
+	case llvm_bc: {
+		if (LLVMWriteBitcodeToFile(llvm_module, "llvm.bc")) {
+			panic("LLVMWriteBitcodeToFile");
+		}
+	} break;
 	}
 
-	char *err = NULL;
-	LLVMVerifyModule(llvm_module, LLVMAbortProcessAction, &err);
-	LLVMDisposeMessage(err); llvm_free();
+	LLVMDisposeBuilder(llvm_builder);
+	LLVMDisposeModule(llvm_module);
+	LLVMShutdown();
 }
 
 /* Generate IR for a statement. */
@@ -157,11 +162,20 @@ static LLVMValueRef llvm_stmt_decl(ast *a, syt *st) {
 		if (A.p->k == AK_PROG) /* Global */ {
 			LLVMValueRef v = LLVMAddGlobal(llvm_module, llvm_type(A.t), "");
 			LLVMSetInitializer(v, CL ? llvm_expr(C[0], st, true) : llvm_ival(A.t));
+			LLVMSetVisibility(v, LLVMHiddenVisibility); /* TODO configurable visibility */
 			A.llvm_v = v;
 		}
 		else /* Local */ {
 			LLVMValueRef v = LLVMBuildAlloca(llvm_builder, llvm_type(A.t), "");
-			LLVMBuildStore(llvm_builder, CL ? llvm_expr(C[0], st, true) : llvm_ival(A.t), v);
+			
+			if (A.t->k == TY_STRING) {
+				LLVMBuildMemCpy(
+					llvm_builder, v, 8, CL ? llvm_expr(C[0], st, true) : llvm_ival(A.t), 8,
+					LLVMConstInt(LLVMIntType(64), 16, false)
+				);
+			}
+			else { LLVMBuildStore(llvm_builder, CL ? llvm_expr(C[0], st, true) : llvm_ival(A.t), v); }
+			
 			A.llvm_v = v;
 		}
 	}
@@ -240,6 +254,7 @@ static LLVMValueRef llvm_expr(ast *a, syt *st, bool load) {
 	case AK_BOOL: { return LLVMConstInt(llvm_type(a->t), a->v_bool, false); } break;
 	case AK_INT:  { return LLVMConstInt(llvm_type(a->t), a->v_int, false);  } break;
 	case AK_FLT:  { return LLVMConstReal(llvm_type(a->t), a->v_flt);        } break;
+	case AK_STR:  { return llvm_str(a, st, load); } break;
 	case AK_ARR:  { return llvm_arr(a, st);       } break;
 	case AK_PROC: { return llvm_expr_proc(a, st); } break;
 	case AK_CAST: { return llvm_expr_cast(a, st); } break;
@@ -259,6 +274,7 @@ static LLVMValueRef llvm_expr(ast *a, syt *st, bool load) {
 		LLVMValueRef args[CL - 1]; for (UINT i = 1; i < CL; i += 1) { args[i - 1] = llvm_expr(C[i], st, true); }
 		return LLVMBuildCall2(llvm_builder, sym->llvm_t, sym->llvm_v, args, CL - 1, "");
 	} break;
+	case AK_BUILTIN: { return llvm_expr_builtin(a, st); } break;
 	case AK_HASH_SYSCALL:
 	case AK_HASH: { return llvm_expr_hash(a, st); } break;
 	case AK_SUBS: {
@@ -467,6 +483,33 @@ static LLVMValueRef llvm_expr_cast(ast *a, syt *st) {
 	return NULL;
 }
 
+/* Generate IR for a builtin. */
+static LLVMValueRef llvm_expr_builtin(ast *a, syt *st) {
+	assert(A.k == AK_BUILTIN);
+	
+	ast *sym = syt_search_h(st, C[0]->h, C[0]->s);
+	if (sym == NULL) { note(file_name, A.ln, A.cl, -1, "Undefined builtin \"%s\" (llvm:llvm_expr_builtin)", C[0]->s); }
+	
+	if (strncmp(sym->s, "cstring", 7) == 0) {
+		assert(CL == 2);
+		if (ast_type(C[1], st)->k != TY_STRING) { panic("cstring builtin only takes strings, not \"%s\"s", ast_type(C[1], st)->s); }
+		
+		LLVMValueRef idc[2] = { LLVMConstInt(LLVMIntType(32), 0, false), LLVMConstInt(LLVMIntType(32), 0, false) };
+		LLVMValueRef v = LLVMBuildInBoundsGEP2(llvm_builder, llvm_type(ast_type(C[1], st)), llvm_expr(C[1], st, false), idc, 2, "");
+		return LLVMBuildLoad2(llvm_builder, LLVMPointerType(LLVMIntType(8), 0), v, "");
+	}
+	else if (strncmp(sym->s, "len", 3) == 0) {
+		assert(CL == 2);
+		if (ast_type(C[1], st)->k != TY_STRING) { panic("len builtin only takes strings, not \"%s\"s", ast_type(C[1], st)->s); }
+		
+		LLVMValueRef idc[2] = { LLVMConstInt(LLVMIntType(32), 0, false), LLVMConstInt(LLVMIntType(32), 1, false) };
+		LLVMValueRef v = LLVMBuildInBoundsGEP2(llvm_builder, llvm_type(ast_type(C[1], st)), llvm_expr(C[1], st, false), idc, 2, "");
+		return LLVMBuildLoad2(llvm_builder, LLVMIntType(64), v, "");
+	}
+	
+	return NULL;
+}
+
 /* Generate IR for a hash procedure. */
 static LLVMValueRef llvm_expr_hash(ast *a, syt *st) {
 	assert(A.k == AK_HASH_SYSCALL);
@@ -503,6 +546,26 @@ static LLVMValueRef llvm_expr_hash(ast *a, syt *st) {
 	return LLVMBuildCall2(llvm_builder, func_type, inline_asm, args, CL, "");
 }
 
+/* Generate IR for a string constant. */
+static LLVMValueRef llvm_str(ast *a, syt *st, bool load) {
+	assert(A.k == AK_STR);
+	
+	LLVMValueRef va[2] = {
+		LLVMBuildGlobalString(llvm_builder, A.s, "string"),
+		LLVMConstInt(LLVMIntType(64), strlen(A.s), false)
+	};
+	
+	LLVMValueRef vs = LLVMAddGlobal(llvm_module, llvm_type(&TYPE(TY_STRING)), "string");
+	LLVMSetInitializer(vs, LLVMConstNamedStruct(llvm_type(&TYPE(TY_STRING)), va, 2));
+	LLVMSetGlobalConstant(vs, true);
+	LLVMSetLinkage(vs, LLVMPrivateLinkage);
+	LLVMSetUnnamedAddress(vs, LLVMGlobalUnnamedAddr);
+	LLVMSetAlignment(vs, 8);
+	
+	if (!load) { return vs; }
+	else { return LLVMBuildLoad2(llvm_builder, llvm_type(&TYPE(TY_STRING)), vs, ""); }
+}
+
 /* Generate IR for an array. */
 static LLVMValueRef llvm_arr(ast *a, syt *st) {
 	assert(A.k == AK_ARR);
@@ -514,21 +577,6 @@ static LLVMValueRef llvm_arr(ast *a, syt *st) {
 	return LLVMConstArray(llvm_type(C[0]->t), va, CL);
 }
 
-/* Initialise LLVM. */
-static inline void llvm_init(char *file) {
-	file_name = file;
-	llvm_context = LLVMGetGlobalContext();
-	llvm_module  = LLVMModuleCreateWithName(file_name);
-	llvm_builder = LLVMCreateBuilder();
-}
-
-/* Uninitialise LLVM. */
-static inline void llvm_free(void) {
-	LLVMDisposeBuilder(llvm_builder); llvm_builder = NULL;
-	LLVMDisposeModule(llvm_module); llvm_module = NULL;
-	LLVMShutdown(); llvm_context = NULL;
-}
-
 /* Return the appropriate LLVMTypeRef for a G type. */
 static LLVMTypeRef llvm_type(type *t) {
 	switch (t->k) {
@@ -552,6 +600,13 @@ static LLVMTypeRef llvm_type(type *t) {
 	case TY_F32:  { return LLVMFloatType();  } break;
 	case TY_F64:  { return LLVMDoubleType(); } break;
 	case TY_F128: { return LLVMFP128Type();  } break;
+	case TY_STRING: {
+		static LLVMTypeRef str_t = NULL; if (str_t == NULL) {
+			str_t = LLVMStructCreateNamed(llvm_context, "string");
+			LLVMTypeRef ta[2] = { LLVMPointerType(LLVMIntType(8), 0), LLVMIntType(64) };
+			LLVMStructSetBody(str_t, ta, 2, false);
+		} return str_t;
+	} break;
 	default: { return NULL; } break;
 	}
 }
@@ -582,6 +637,18 @@ static LLVMValueRef llvm_ival(type *t) {
 	case TY_F32:  { return LLVMConstReal(LLVMFloatType(),  0.0); } break;
 	case TY_F64:  { return LLVMConstReal(LLVMDoubleType(), 0.0); } break;
 	case TY_F128: { return LLVMConstReal(LLVMFP128Type(),  0.0); } break;
+	case TY_STRING: {
+		static LLVMValueRef vs = NULL; if (vs == NULL) {
+			LLVMValueRef va[2] = {
+				LLVMBuildGlobalString(llvm_builder, "", ""),
+				LLVMConstInt(LLVMIntType(64), 0, false)
+			};
+			
+			vs = LLVMAddGlobal(llvm_module, llvm_type(&TYPE(TY_STRING)), "");
+			LLVMSetInitializer(vs, LLVMConstNamedStruct(llvm_type(&TYPE(TY_STRING)), va, 2));
+			LLVMSetGlobalConstant(vs, true);
+		} return vs;
+	}
 	default: { return NULL; } break;
 	}
 }
diff --git a/src/llvm.h b/src/llvm.h
index 3349152..83b4f67 100644
--- a/src/llvm.h
+++ b/src/llvm.h
@@ -8,8 +8,8 @@
 
 #include "parse.h"
 
-extern void llvm(ast *a, char *file);
-extern void llvm_bitcode(ast *a, char *file);
-extern void llvm_ir(ast *a, char *file);
+typedef enum { llvm_obj, llvm_asm, llvm_bc, llvm_ir } llvm_action;
+
+extern void llvm(ast *a, char *file, llvm_action o);
 
 #endif // G_LLVM_H_CZUMSHFW
diff --git a/src/llvm/attr.c b/src/llvm/attr.c
new file mode 100644
index 0000000..4aab1c2
--- /dev/null
+++ b/src/llvm/attr.c
@@ -0,0 +1,33 @@
+// attr.c
+// LLVM attributes source file for G
+// Copyright (C) 2023, Jakob Wakeling
+// All rights reserved.
+
+#include "../log.h"
+#include "../util/util.h"
+#include "llvm.h"
+
+#include <llvm-c/Core.h>
+#include <llvm-c/Types.h>
+
+#include <string.h>
+
+#define ATTR(a, b) b,
+static char *llvm_func_attr_s[] = {
+#include "attr.def"
+};
+#undef ATTR
+
+/* Add an attribute to a function, returns -1 if the attribute does not exist. */
+int LLVMAddFuncAttr(LLVMContextRef C, LLVMValueRef F, llvm_func_attr attr, unsigned index) {
+	u32 ak = LLVMGetEnumAttributeKindForName(llvm_func_attr_s[attr], strlen(llvm_func_attr_s[attr]));
+	if (ak == 0) { dprint(NULL, 0, 0, "Attribute \"%s\" does not exist", llvm_func_attr_s[attr]); return -1; }
+	
+	LLVMAttributeRef ar = LLVMCreateEnumAttribute(C, ak, 0);
+	LLVMAddAttributeAtIndex(F, index, ar); return 0;
+}
+
+void LLVMAddStringAttr(LLVMContextRef C, LLVMValueRef F, const char *k, const char *v, unsigned index) {
+	LLVMAttributeRef ar = LLVMCreateStringAttribute(C, k, strlen(k), v, strlen(v));
+	LLVMAddAttributeAtIndex(F, index, ar);
+}
diff --git a/src/llvm/attr.def b/src/llvm/attr.def
new file mode 100644
index 0000000..bfefc91
--- /dev/null
+++ b/src/llvm/attr.def
@@ -0,0 +1,4 @@
+ATTR(NOINLINE, "noinline")
+ATTR(NOUNWIND, "nounwind")
+ATTR(OPTNONE, "optnone")
+ATTR(UWTABLE, "uwtable")
diff --git a/src/llvm/llvm.h b/src/llvm/llvm.h
new file mode 100644
index 0000000..fa43176
--- /dev/null
+++ b/src/llvm/llvm.h
@@ -0,0 +1,22 @@
+// llvm/llvm.h
+// LLVM header file for G
+// Copyright (C) 2023, Jakob Wakeling
+// All rights reserved.
+
+#ifndef G_LLVM_LLVM_H_ZW0CA5L6
+#define G_LLVM_LLVM_H_ZW0CA5L6
+
+#include "../util/util.h"
+
+#include <llvm-c/Types.h>
+
+#define ATTR(a, b) LLVM_ATTR_##a,
+typedef enum {
+#include "attr.def"
+} llvm_func_attr;
+#undef ATTR
+
+extern int LLVMAddFuncAttr(LLVMContextRef C, LLVMValueRef F, llvm_func_attr attr, unsigned index);
+extern void LLVMAddStringAttr(LLVMContextRef C, LLVMValueRef F, const char *k, const char *v, unsigned index);
+
+#endif // G_LLVM_LLVM_H_ZW0CA5L6
diff --git a/src/log.c b/src/log.c
index 2d6184d..58b469c 100644
--- a/src/log.c
+++ b/src/log.c
@@ -22,6 +22,7 @@ void note(const char *file, UINT ln, UINT cl, sint level, const char *format, ..
 	if (file) { fprintf(stderr, "%s:%lu:%lu: ", file, ln + 1, cl + 1); }
 
 	if (level <= -1) { fprintf(stderr, "fatal: "); }
+	else if (level == 9) { fprintf(stderr, "debug: "); }
 	else if (level == 0) { fprintf(stderr, "error: "); }
 	else if (level <= 3) { fprintf(stderr, "warning: "); }
 	else if (level >= 4) { fprintf(stderr, "note: "); }
diff --git a/src/log.h b/src/log.h
index 8ca274e..6f56c89 100644
--- a/src/log.h
+++ b/src/log.h
@@ -16,12 +16,14 @@ extern sint log_level, log_limit;
 extern void note(const char *file, UINT ln, UINT cl, sint level, const char *format, ...);
 extern bool has_error(void);
 
+extern noreturn void __panic(const char *file, UINT ln, const char *format, ...);
+
 #ifdef NDEBUG
+#define dprint(file, ln, cl, format, ...) ((void)0)
 #define panic(format, ...) (__panic(NULL, 0, format __VA_OPT__(,) __VA_ARGS__))
 #else
+#define dprint(file, ln, cl, format, ...) (note(file, ln, cl, 9, format __VA_OPT__(,) __VA_ARGS__))
 #define panic(format, ...) (__panic(__FILE__, __LINE__, format __VA_OPT__(,) __VA_ARGS__))
 #endif
 
-extern noreturn void __panic(const char *file, UINT ln, const char *format, ...);
-
 #endif // G_LOG_H_1RPM5P9E
diff --git a/src/main.c b/src/main.c
index 9c52165..fde1694 100644
--- a/src/main.c
+++ b/src/main.c
@@ -24,7 +24,9 @@ static struct lop lops[] = {
 
 static bool bflag = false, Bflag = false, cflag = false;
 static bool Eflag = false, pflag = false, Pflag = false;
-static bool qflag = false;
+static bool qflag = false, Sflag = false;
+
+static int verbosity = 0;
 
 static void compile(const char *file, char *src, UINT len);
 static void compile_file(const char *file);
@@ -37,7 +39,7 @@ static void hlp(void);
 static void ver(void);
 
 int main(int ac, char *av[]) { A0 = av[0];
-	struct opt opt = OPTGET_INIT; opt.str = "bBcEf:O:pPqW:"; opt.lops = lops;
+	struct opt opt = OPTGET_INIT; opt.str = "bBcEf:O:pPqSvW:"; opt.lops = lops;
 	for (int o; (o = optget(&opt, av, 1)) != -1;) switch (o) {
 	case 'b': { bflag = true; } break; /* Output LLVM IR files */
 	case 'B': { Bflag = true; } break; /* Output LLVM bitcode files */
@@ -48,6 +50,8 @@ int main(int ac, char *av[]) { A0 = av[0];
 	case 'p': { pflag = true; } break; /* Output parser AST */
 	case 'P': { Pflag = true; } break; /* Output analyser AST */
 	case 'q': { qflag = true; } break; /* Silence certain outputs (for benchmarking) */
+	case 'S': { Sflag = true; } break; /* Output assembly files */
+	case 'v': { verbosity += 1; } break; /* Increase verbosity */
 	case 'W': { opt_W(opt.arg); } break; /* Configure warnings and errors */
 	case 256: { hlp(); } return 0;
 	case 257: { ver(); } return 0;
@@ -74,9 +78,10 @@ static void compile(const char * file, char *src, UINT len) {
 	if (Pflag) { if (!qflag) { ast_print(a, 0); } goto end; }
 	if (has_error()) { exit(1); }
 
-	if (bflag) { llvm_ir(a, strdup(file)); }
-	else if (Bflag) { llvm_bitcode(a, strdup(file)); }
-	else { llvm(a, strdup(file)); }
+	if (bflag) { llvm(a, strdup(file), llvm_ir); }
+	else if (Bflag) { llvm(a, strdup(file), llvm_bc); }
+	else if (Sflag) { llvm(a, strdup(file), llvm_asm); }
+	else { llvm(a, strdup(file), llvm_obj); }
 
 	end:; return;
 }
diff --git a/src/parse.c b/src/parse.c
index f96234c..71035ca 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -33,9 +33,9 @@ char *ast_ks[] = {
 
 	"AK_ASSIGN", "AK_AS_ADD", "AK_AS_SUB", "AK_AS_MUL", "AK_AS_DIV", "AK_AS_MOD",
 
-	"AK_ID", "AK_CALL", "AK_BOOL", "AK_INT", "AK_FLT", "AK_ARR", "AK_SUBS",
+	"AK_ID", "AK_CALL", "AK_BOOL", "AK_INT", "AK_FLT", "AK_ARR", "AK_SUBS", "AK_STR",
 
-	"AK_HASH", "AK_HASH_SYSCALL"
+	"AK_HASH", "AK_HASH_SYSCALL", "AK_BUILTIN"
 };
 
 static ast *parse_stmt(lex *l, syt *st);
@@ -71,7 +71,7 @@ inline void ast_free(ast **a) {
 	if (a == NULL || *a == NULL) { return; }
 	if ((*a)->s != NULL) { free((*a)->s); }
 	if ((*a)->c.a != NULL) { free((*a)->c.a); }
-	free(*a); a = NULL; /* TODO free LLVM pointers? */
+	free(*a); a = NULL;
 }
 
 /* Push a child AST node to an AST node. */
@@ -307,6 +307,10 @@ static ast *parse_expr(lex *l, syt *st, s32 o) {
 	boolean: { left->t = &TYPE(TY_BOOL); lex_next(l); } break;
 	case TK_INT:    { left = parse_int(l, st);           } break;
 	case TK_FLT:    { left = parse_flt(l, st);           } break;
+	case TK_STR: {
+		left = ast_init(AK_STR, T.ln, T.cl); left->h = T.h;
+		left->s = strdup_or_fail(T.s); left->t = &TYPE(TY_STRING); lex_next(l);
+	} break;
 	case TK_LBRACE: { return parse_expr_compound(l, st); } break;
 	case TK_PROC:   { return parse_expr_proc(l, st);     } break;
 	case TK_HASH: {
@@ -537,7 +541,7 @@ static s32 ast_precedence(ast_k ak) {
 /* Duplicate a string or fail. */
 static inline char *strdup_or_fail(const char *s) {
 	register char *r = strdup(s);
-	if (r == NULL) { error(1, "%s", SERR); }
+	if (r == NULL) { panic("%s", SERR); }
 	return r;
 }
 
diff --git a/src/parse.h b/src/parse.h
index 8e4c781..fc75a35 100644
--- a/src/parse.h
+++ b/src/parse.h
@@ -29,9 +29,9 @@ typedef enum {
 
 	AK_ASSIGN, AK_AS_ADD, AK_AS_SUB, AK_AS_MUL, AK_AS_DIV, AK_AS_MOD,
 
-	AK_ID, AK_CALL, AK_BOOL, AK_INT, AK_FLT, AK_ARR, AK_SUBS,
+	AK_ID, AK_CALL, AK_BOOL, AK_INT, AK_FLT, AK_ARR, AK_SUBS, AK_STR,
 
-	AK_HASH, AK_HASH_SYSCALL
+	AK_HASH, AK_HASH_SYSCALL, AK_BUILTIN
 } ast_k;
 
 /*
diff --git a/src/type.c b/src/type.c
index 8247b38..0bf3f62 100644
--- a/src/type.c
+++ b/src/type.c
@@ -95,10 +95,10 @@ type types[] = {
 	{ TY_Q256BE, TF_CLX | TF_SIGN | TF_BE, 32, "q256be" },
 	{ TY_Q512BE, TF_CLX | TF_SIGN | TF_BE,  4, "q512be" },
 
-	{ TY_BYTE, TF_INT,            1, "byte" },
-	{ TY_CHAR, TF_INT | TF_CHAR,  1, "char" },
-	{ TY_RUNE, TF_INT | TF_CHAR,  4, "rune" },
-	{ TY_STR,  TF_STR,           -1, "str"  },
+	{ TY_BYTE,   TF_INT,            1, "byte"   },
+	{ TY_CHAR,   TF_INT | TF_CHAR,  1, "char"   },
+	{ TY_RUNE,   TF_INT | TF_CHAR,  4, "rune"   },
+	{ TY_STRING, TF_STRING,        16, "string" },
 };
 
 static type_a types_a = { NULL, 0, 0 };
@@ -195,5 +195,5 @@ inline bool is_com(type *t1, type *t2) {
 	if (is_int(t1) && is_int(t2)) { return true; }
 	if (is_flt(t1) && is_flt(t2)) { return true; }
 
-	return false; /* TODO */
+	return false;
 }
diff --git a/src/type.h b/src/type.h
index 31f8b6f..32d8296 100644
--- a/src/type.h
+++ b/src/type.h
@@ -18,8 +18,8 @@ typedef enum {
 	TY_UINT, TY_U8, TY_U16, TY_U32, TY_U64, TY_U128,
 	TY_SINT, TY_S8, TY_S16, TY_S32, TY_S64, TY_S128,
 
-	TY_F16, TY_F32, TY_F64,   TY_F128,
-	TY_C32, TY_C64, TY_C128,  TY_C256,
+	TY_F16, TY_F32, TY_F64, TY_F128,
+	TY_C32, TY_C64, TY_C128, TY_C256,
 	TY_Q64, TY_Q128, TY_Q256, TY_Q512,
 
 	TY_U16LE, TY_U32LE, TY_U64LE, TY_U128LE,
@@ -37,19 +37,19 @@ typedef enum {
 	TY_Q64LE, TY_Q128LE, TY_Q256LE, TY_Q512LE,
 	TY_Q64BE, TY_Q128BE, TY_Q256BE, TY_Q512BE,
 
-	TY_BYTE, TY_CHAR, TY_RUNE, TY_STR,
+	TY_BYTE, TY_CHAR, TY_RUNE, TY_STRING,
 } type_k;
 
 typedef enum {
-	TF_NULL = BIT(0),  TF_PTR  = BIT(1),
-	TF_BOOL = BIT(2),  TF_INT  = BIT(3),
-	TF_FLT  = BIT(4),  TF_CLX  = BIT(5),
-	TF_QTN  = BIT(6),  TF_SIGN = BIT(7),
+	TF_NULL = BIT(0), TF_PTR  = BIT(1),
+	TF_BOOL = BIT(2), TF_INT  = BIT(3),
+	TF_FLT  = BIT(4), TF_CLX  = BIT(5),
+	TF_QTN  = BIT(6), TF_SIGN = BIT(7),
 
-	TF_LE   = BIT(14), TF_BE   = BIT(15),
-	TF_CHAR = BIT(16), TF_STR  = BIT(17),
+	TF_LE   = BIT(14), TF_BE     = BIT(15),
+	TF_CHAR = BIT(16), TF_STRING = BIT(17),
 
-	TF_NUM  = TF_INT | TF_FLT | TF_CLX,
+	TF_NUM = TF_INT | TF_FLT | TF_CLX,
 } type_f;
 
 /* k : Kind, f : Flags, l : Length, s : String */