Skip to content

Commit

Permalink
self-hosted: generate LLVM IR for simple function
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewrk committed Jul 15, 2018
1 parent 28c3d48 commit 363f4fa
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 11 deletions.
158 changes: 157 additions & 1 deletion src-self-hosted/codegen.zig
Expand Up @@ -8,6 +8,7 @@ const ir = @import("ir.zig");
const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
const event = std.event;
const assert = std.debug.assert;

pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) !void {
fn_val.base.ref();
Expand Down Expand Up @@ -35,9 +36,23 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code)

try renderToLlvmModule(&ofile, fn_val, code);

// TODO module level assembly
//if (buf_len(&g->global_asm) != 0) {
// LLVMSetModuleInlineAsm(g->module, buf_ptr(&g->global_asm));
//}

// TODO
//ZigLLVMDIBuilderFinalize(g->dbuilder);

if (comp.verbose_llvm_ir) {
llvm.DumpModule(ofile.module);
}

// verify the llvm module when safety is on
if (std.debug.runtime_safety) {
var error_ptr: ?[*]u8 = null;
_ = llvm.VerifyModule(ofile.module, llvm.AbortProcessAction, &error_ptr);
}
}

pub const ObjectFile = struct {
Expand All @@ -55,5 +70,146 @@ pub const ObjectFile = struct {
pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) !void {
// TODO audit more of codegen.cpp:fn_llvm_value and port more logic
const llvm_fn_type = try fn_val.base.typeof.getLlvmType(ofile);
const llvm_fn = llvm.AddFunction(ofile.module, fn_val.symbol_name.ptr(), llvm_fn_type);
const llvm_fn = llvm.AddFunction(
ofile.module,
fn_val.symbol_name.ptr(),
llvm_fn_type,
) orelse return error.OutOfMemory;

const want_fn_safety = fn_val.block_scope.safety.get(ofile.comp);
if (want_fn_safety and ofile.comp.haveLibC()) {
try addLLVMFnAttr(ofile, llvm_fn, "sspstrong");
try addLLVMFnAttrStr(ofile, llvm_fn, "stack-protector-buffer-size", "4");
}

// TODO
//if (fn_val.align_stack) |align_stack| {
// try addLLVMFnAttrInt(ofile, llvm_fn, "alignstack", align_stack);
//}

const fn_type = fn_val.base.typeof.cast(Type.Fn).?;

try addLLVMFnAttr(ofile, llvm_fn, "nounwind");
//add_uwtable_attr(g, fn_table_entry->llvm_value);
try addLLVMFnAttr(ofile, llvm_fn, "nobuiltin");

//if (g->build_mode == BuildModeDebug && fn_table_entry->fn_inline != FnInlineAlways) {
// ZigLLVMAddFunctionAttr(fn_table_entry->llvm_value, "no-frame-pointer-elim", "true");
// ZigLLVMAddFunctionAttr(fn_table_entry->llvm_value, "no-frame-pointer-elim-non-leaf", nullptr);
//}

//if (fn_table_entry->section_name) {
// LLVMSetSection(fn_table_entry->llvm_value, buf_ptr(fn_table_entry->section_name));
//}
//if (fn_table_entry->align_bytes > 0) {
// LLVMSetAlignment(fn_table_entry->llvm_value, (unsigned)fn_table_entry->align_bytes);
//} else {
// // We'd like to set the best alignment for the function here, but on Darwin LLVM gives
// // "Cannot getTypeInfo() on a type that is unsized!" assertion failure when calling
// // any of the functions for getting alignment. Not specifying the alignment should
// // use the ABI alignment, which is fine.
//}

//if (!type_has_bits(return_type)) {
// // nothing to do
//} else if (type_is_codegen_pointer(return_type)) {
// addLLVMAttr(fn_table_entry->llvm_value, 0, "nonnull");
//} else if (handle_is_ptr(return_type) &&
// calling_convention_does_first_arg_return(fn_type->data.fn.fn_type_id.cc))
//{
// addLLVMArgAttr(fn_table_entry->llvm_value, 0, "sret");
// addLLVMArgAttr(fn_table_entry->llvm_value, 0, "nonnull");
//}

// TODO set parameter attributes

// TODO
//uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(g, fn_table_entry);
//if (err_ret_trace_arg_index != UINT32_MAX) {
// addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)err_ret_trace_arg_index, "nonnull");
//}

const cur_ret_ptr = if (fn_type.return_type.handleIsPtr()) llvm.GetParam(llvm_fn, 0) else null;

// build all basic blocks
for (code.basic_block_list.toSlice()) |bb| {
bb.llvm_block = llvm.AppendBasicBlockInContext(
ofile.context,
llvm_fn,
bb.name_hint,
) orelse return error.OutOfMemory;
}
const entry_bb = code.basic_block_list.at(0);
llvm.PositionBuilderAtEnd(ofile.builder, entry_bb.llvm_block);

llvm.ClearCurrentDebugLocation(ofile.builder);

// TODO set up error return tracing
// TODO allocate temporary stack values
// TODO create debug variable declarations for variables and allocate all local variables
// TODO finishing error return trace setup. we have to do this after all the allocas.
// TODO create debug variable declarations for parameters

for (code.basic_block_list.toSlice()) |current_block| {
llvm.PositionBuilderAtEnd(ofile.builder, current_block.llvm_block);
for (current_block.instruction_list.toSlice()) |instruction| {
if (instruction.ref_count == 0 and !instruction.hasSideEffects()) continue;

instruction.llvm_value = try instruction.render(ofile, fn_val);
}
current_block.llvm_exit_block = llvm.GetInsertBlock(ofile.builder);
}
}

fn addLLVMAttr(
ofile: *ObjectFile,
val: llvm.ValueRef,
attr_index: llvm.AttributeIndex,
attr_name: []const u8,
) !void {
const kind_id = llvm.GetEnumAttributeKindForName(attr_name.ptr, attr_name.len);
assert(kind_id != 0);
const llvm_attr = llvm.CreateEnumAttribute(ofile.context, kind_id, 0) orelse return error.OutOfMemory;
llvm.AddAttributeAtIndex(val, attr_index, llvm_attr);
}

fn addLLVMAttrStr(
ofile: *ObjectFile,
val: llvm.ValueRef,
attr_index: llvm.AttributeIndex,
attr_name: []const u8,
attr_val: []const u8,
) !void {
const llvm_attr = llvm.CreateStringAttribute(
ofile.context,
attr_name.ptr,
@intCast(c_uint, attr_name.len),
attr_val.ptr,
@intCast(c_uint, attr_val.len),
) orelse return error.OutOfMemory;
llvm.AddAttributeAtIndex(val, attr_index, llvm_attr);
}

fn addLLVMAttrInt(
val: llvm.ValueRef,
attr_index: llvm.AttributeIndex,
attr_name: []const u8,
attr_val: u64,
) !void {
const kind_id = llvm.GetEnumAttributeKindForName(attr_name.ptr, attr_name.len);
assert(kind_id != 0);
const llvm_attr = llvm.CreateEnumAttribute(ofile.context, kind_id, attr_val) orelse return error.OutOfMemory;
llvm.AddAttributeAtIndex(val, attr_index, llvm_attr);
}

fn addLLVMFnAttr(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8) !void {
return addLLVMAttr(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name);
}

fn addLLVMFnAttrStr(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8, attr_val: []const u8) !void {
return addLLVMAttrStr(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name, attr_val);
}

fn addLLVMFnAttrInt(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8, attr_val: u64) !void {
return addLLVMAttrInt(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name, attr_val);
}
6 changes: 5 additions & 1 deletion src-self-hosted/compilation.zig
Expand Up @@ -606,6 +606,10 @@ pub const Compilation = struct {
return error.Todo;
}

pub fn haveLibC(self: *Compilation) bool {
return self.libc_link_lib != null;
}

pub fn addLinkLib(self: *Compilation, name: []const u8, provided_explicitly: bool) !*LinkLib {
const is_libc = mem.eql(u8, name, "c");

Expand Down Expand Up @@ -741,7 +745,7 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void {
analyzed_code.dump();
}

// Kick off rendering to LLVM comp, but it doesn't block the fn decl
// Kick off rendering to LLVM module, but it doesn't block the fn decl
// analysis from being complete.
try comp.build_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code);
}
56 changes: 48 additions & 8 deletions src-self-hosted/ir.zig
Expand Up @@ -10,6 +10,8 @@ const assert = std.debug.assert;
const Token = std.zig.Token;
const ParsedFile = @import("parsed_file.zig").ParsedFile;
const Span = @import("errmsg.zig").Span;
const llvm = @import("llvm.zig");
const ObjectFile = @import("codegen.zig").ObjectFile;

pub const LVal = enum {
None,
Expand Down Expand Up @@ -61,6 +63,9 @@ pub const Instruction = struct {
/// the instruction that this one derives from in analysis
parent: ?*Instruction,

/// populated durign codegen
llvm_value: ?llvm.ValueRef,

pub fn cast(base: *Instruction, comptime T: type) ?*T {
if (base.id == comptime typeToId(T)) {
return @fieldParentPtr(T, "base", base);
Expand Down Expand Up @@ -108,14 +113,25 @@ pub const Instruction = struct {
inline while (i < @memberCount(Id)) : (i += 1) {
if (base.id == @field(Id, @memberName(Id, i))) {
const T = @field(Instruction, @memberName(Id, i));
const new_inst = try @fieldParentPtr(T, "base", base).analyze(ira);
new_inst.linkToParent(base);
return new_inst;
return @fieldParentPtr(T, "base", base).analyze(ira);
}
}
unreachable;
}

pub fn render(base: *Instruction, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?llvm.ValueRef) {
switch (base.id) {
Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val),
Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val),
Id.Ref => @panic("TODO"),
Id.DeclVar => @panic("TODO"),
Id.CheckVoidStmt => @panic("TODO"),
Id.Phi => @panic("TODO"),
Id.Br => @panic("TODO"),
Id.AddImplicitReturnType => @panic("TODO"),
}
}

fn getAsParam(param: *Instruction) !*Instruction {
const child = param.child orelse return error.SemanticAnalysisFailed;
switch (child.val) {
Expand Down Expand Up @@ -186,6 +202,10 @@ pub const Instruction = struct {
new_inst.val = IrVal{ .KnownValue = self.base.val.KnownValue.getRef() };
return new_inst;
}

pub fn render(self: *Const, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef {
return self.base.val.KnownValue.getLlvmConst(ofile);
}
};

pub const Return = struct {
Expand Down Expand Up @@ -214,6 +234,18 @@ pub const Instruction = struct {

return ira.irb.build(Return, self.base.scope, self.base.span, Params{ .return_value = casted_value });
}

pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) ?llvm.ValueRef {
const value = self.params.return_value.llvm_value;
const return_type = self.params.return_value.getKnownType();

if (return_type.handleIsPtr()) {
@panic("TODO");
} else {
_ = llvm.BuildRet(ofile.builder, value);
}
return null;
}
};

pub const Ref = struct {
Expand Down Expand Up @@ -387,12 +419,16 @@ pub const Variable = struct {

pub const BasicBlock = struct {
ref_count: usize,
name_hint: []const u8,
name_hint: [*]const u8, // must be a C string literal
debug_id: usize,
scope: *Scope,
instruction_list: std.ArrayList(*Instruction),
ref_instruction: ?*Instruction,

/// for codegen
llvm_block: llvm.BasicBlockRef,
llvm_exit_block: llvm.BasicBlockRef,

/// the basic block that is derived from this one in analysis
child: ?*BasicBlock,

Expand Down Expand Up @@ -426,7 +462,7 @@ pub const Code = struct {
pub fn dump(self: *Code) void {
var bb_i: usize = 0;
for (self.basic_block_list.toSliceConst()) |bb| {
std.debug.warn("{}_{}:\n", bb.name_hint, bb.debug_id);
std.debug.warn("{s}_{}:\n", bb.name_hint, bb.debug_id);
for (bb.instruction_list.toSliceConst()) |instr| {
std.debug.warn(" ");
instr.dump();
Expand Down Expand Up @@ -475,7 +511,7 @@ pub const Builder = struct {
}

/// No need to clean up resources thanks to the arena allocator.
pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: []const u8) !*BasicBlock {
pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: [*]const u8) !*BasicBlock {
const basic_block = try self.arena().create(BasicBlock{
.ref_count = 0,
.name_hint = name_hint,
Expand All @@ -485,6 +521,8 @@ pub const Builder = struct {
.child = null,
.parent = null,
.ref_instruction = null,
.llvm_block = undefined,
.llvm_exit_block = undefined,
});
self.next_debug_id += 1;
return basic_block;
Expand Down Expand Up @@ -600,7 +638,7 @@ pub const Builder = struct {
if (block.label) |label| {
block_scope.incoming_values = std.ArrayList(*Instruction).init(irb.arena());
block_scope.incoming_blocks = std.ArrayList(*BasicBlock).init(irb.arena());
block_scope.end_block = try irb.createBasicBlock(parent_scope, "BlockEnd");
block_scope.end_block = try irb.createBasicBlock(parent_scope, c"BlockEnd");
block_scope.is_comptime = try irb.buildConstBool(
parent_scope,
Span.token(block.lbrace),
Expand Down Expand Up @@ -777,6 +815,7 @@ pub const Builder = struct {
.span = span,
.child = null,
.parent = null,
.llvm_value = undefined,
},
.params = params,
});
Expand Down Expand Up @@ -968,7 +1007,7 @@ pub async fn gen(
var irb = try Builder.init(comp, parsed_file);
errdefer irb.abort();

const entry_block = try irb.createBasicBlock(scope, "Entry");
const entry_block = try irb.createBasicBlock(scope, c"Entry");
entry_block.ref(); // Entry block gets a reference because we enter it to begin.
try irb.setCursorAtEndAndAppendBlock(entry_block);

Expand Down Expand Up @@ -1013,6 +1052,7 @@ pub async fn analyze(comp: *Compilation, parsed_file: *ParsedFile, old_code: *Co
}

const return_inst = try old_instruction.analyze(&ira);
return_inst.linkToParent(old_instruction);
// Note: if we ever modify the above to handle error.CompileError by continuing analysis,
// then here we want to check if ira.isCompTime() and return early if true

Expand Down

0 comments on commit 363f4fa

Please sign in to comment.