Skip to content

Commit

Permalink
self-hosted: adding a fn to an llvm module
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewrk committed Jul 14, 2018
1 parent 91636f1 commit 278829f
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 63 deletions.
61 changes: 61 additions & 0 deletions src-self-hosted/codegen.zig
@@ -0,0 +1,61 @@
const std = @import("std");
// TODO codegen pretends that Module is renamed to Build because I plan to
// do that refactor at some point
const Build = @import("module.zig").Module;
// we go through llvm instead of c for 2 reasons:
// 1. to avoid accidentally calling the non-thread-safe functions
// 2. patch up some of the types to remove nullability
const llvm = @import("llvm.zig");
const ir = @import("ir.zig");
const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
const event = std.event;

pub async fn renderToLlvm(build: *Build, fn_val: *Value.Fn, code: *ir.Code) !void {
fn_val.base.ref();
defer fn_val.base.deref(build);
defer code.destroy(build.a());

const llvm_handle = try build.event_loop_local.getAnyLlvmContext();
defer llvm_handle.release(build.event_loop_local);

const context = llvm_handle.node.data;

const module = llvm.ModuleCreateWithNameInContext(build.name.ptr(), context) orelse return error.OutOfMemory;
defer llvm.DisposeModule(module);

const builder = llvm.CreateBuilderInContext(context) orelse return error.OutOfMemory;
defer llvm.DisposeBuilder(builder);

var cunit = CompilationUnit{
.build = build,
.module = module,
.builder = builder,
.context = context,
.lock = event.Lock.init(build.loop),
};

try renderToLlvmModule(&cunit, fn_val, code);

if (build.verbose_llvm_ir) {
llvm.DumpModule(cunit.module);
}
}

pub const CompilationUnit = struct {
build: *Build,
module: llvm.ModuleRef,
builder: llvm.BuilderRef,
context: llvm.ContextRef,
lock: event.Lock,

fn a(self: *CompilationUnit) *std.mem.Allocator {
return self.build.a();
}
};

pub fn renderToLlvmModule(cunit: *CompilationUnit, 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(cunit);
const llvm_fn = llvm.AddFunction(cunit.module, fn_val.symbol_name.ptr(), llvm_fn_type);
}
9 changes: 1 addition & 8 deletions src-self-hosted/ir.zig
Expand Up @@ -375,15 +375,8 @@ pub const Instruction = struct {

pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Instruction {
const target = try self.params.target.getAsParam();

try ira.src_implicit_return_type_list.append(target);

return ira.irb.build(
AddImplicitReturnType,
self.base.scope,
self.base.span,
Params{ .target = target },
);
return ira.irb.buildConstVoid(self.base.scope, self.base.span, true);
}
};
};
Expand Down
23 changes: 20 additions & 3 deletions src-self-hosted/llvm.zig
Expand Up @@ -2,10 +2,27 @@ const builtin = @import("builtin");
const c = @import("c.zig");
const assert = @import("std").debug.assert;

pub const ValueRef = removeNullability(c.LLVMValueRef);
pub const ModuleRef = removeNullability(c.LLVMModuleRef);
pub const ContextRef = removeNullability(c.LLVMContextRef);
pub const BuilderRef = removeNullability(c.LLVMBuilderRef);
pub const ContextRef = removeNullability(c.LLVMContextRef);
pub const ModuleRef = removeNullability(c.LLVMModuleRef);
pub const ValueRef = removeNullability(c.LLVMValueRef);
pub const TypeRef = removeNullability(c.LLVMTypeRef);

pub const AddFunction = c.LLVMAddFunction;
pub const CreateBuilderInContext = c.LLVMCreateBuilderInContext;
pub const DisposeBuilder = c.LLVMDisposeBuilder;
pub const DisposeModule = c.LLVMDisposeModule;
pub const DumpModule = c.LLVMDumpModule;
pub const ModuleCreateWithNameInContext = c.LLVMModuleCreateWithNameInContext;
pub const VoidTypeInContext = c.LLVMVoidTypeInContext;

pub const FunctionType = LLVMFunctionType;
extern fn LLVMFunctionType(
ReturnType: TypeRef,
ParamTypes: [*]TypeRef,
ParamCount: c_uint,
IsVarArg: c_int,
) ?TypeRef;

fn removeNullability(comptime T: type) type {
comptime assert(@typeId(T) == builtin.TypeId.Optional);
Expand Down
7 changes: 6 additions & 1 deletion src-self-hosted/main.zig
Expand Up @@ -14,6 +14,7 @@ const c = @import("c.zig");
const introspect = @import("introspect.zig");
const Args = arg.Args;
const Flag = arg.Flag;
const EventLoopLocal = @import("module.zig").EventLoopLocal;
const Module = @import("module.zig").Module;
const Target = @import("target.zig").Target;
const errmsg = @import("errmsg.zig");
Expand Down Expand Up @@ -386,9 +387,13 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo

var loop: event.Loop = undefined;
try loop.initMultiThreaded(allocator);
defer loop.deinit();

var event_loop_local = EventLoopLocal.init(&loop);
defer event_loop_local.deinit();

var module = try Module.create(
&loop,
&event_loop_local,
root_name,
root_source_file,
Target.Native,
Expand Down
112 changes: 77 additions & 35 deletions src-self-hosted/module.zig
Expand Up @@ -25,14 +25,58 @@ const ParsedFile = @import("parsed_file.zig").ParsedFile;
const Value = @import("value.zig").Value;
const Type = Value.Type;
const Span = errmsg.Span;
const codegen = @import("codegen.zig");

/// Data that is local to the event loop.
pub const EventLoopLocal = struct {
loop: *event.Loop,
llvm_handle_pool: std.atomic.Stack(llvm.ContextRef),

fn init(loop: *event.Loop) EventLoopLocal {
return EventLoopLocal{
.loop = loop,
.llvm_handle_pool = std.atomic.Stack(llvm.ContextRef).init(),
};
}

fn deinit(self: *EventLoopLocal) void {
while (self.llvm_handle_pool.pop()) |node| {
c.LLVMContextDispose(node.data);
self.loop.allocator.destroy(node);
}
}

/// Gets an exclusive handle on any LlvmContext.
/// Caller must release the handle when done.
pub fn getAnyLlvmContext(self: *EventLoopLocal) !LlvmHandle {
if (self.llvm_handle_pool.pop()) |node| return LlvmHandle{ .node = node };

const context_ref = c.LLVMContextCreate() orelse return error.OutOfMemory;
errdefer c.LLVMContextDispose(context_ref);

const node = try self.loop.allocator.create(std.atomic.Stack(llvm.ContextRef).Node{
.next = undefined,
.data = context_ref,
});
errdefer self.loop.allocator.destroy(node);

return LlvmHandle{ .node = node };
}
};

pub const LlvmHandle = struct {
node: *std.atomic.Stack(llvm.ContextRef).Node,

pub fn release(self: LlvmHandle, event_loop_local: *EventLoopLocal) void {
event_loop_local.llvm_handle_pool.push(self.node);
}
};

pub const Module = struct {
event_loop_local: *EventLoopLocal,
loop: *event.Loop,
name: Buffer,
root_src_path: ?[]const u8,
llvm_module: llvm.ModuleRef,
context: llvm.ContextRef,
builder: llvm.BuilderRef,
target: Target,
build_mode: builtin.Mode,
zig_lib_dir: []const u8,
Expand Down Expand Up @@ -187,7 +231,7 @@ pub const Module = struct {
};

pub fn create(
loop: *event.Loop,
event_loop_local: *EventLoopLocal,
name: []const u8,
root_src_path: ?[]const u8,
target: *const Target,
Expand All @@ -196,29 +240,20 @@ pub const Module = struct {
zig_lib_dir: []const u8,
cache_dir: []const u8,
) !*Module {
const loop = event_loop_local.loop;

var name_buffer = try Buffer.init(loop.allocator, name);
errdefer name_buffer.deinit();

const context = c.LLVMContextCreate() orelse return error.OutOfMemory;
errdefer c.LLVMContextDispose(context);

const llvm_module = c.LLVMModuleCreateWithNameInContext(name_buffer.ptr(), context) orelse return error.OutOfMemory;
errdefer c.LLVMDisposeModule(llvm_module);

const builder = c.LLVMCreateBuilderInContext(context) orelse return error.OutOfMemory;
errdefer c.LLVMDisposeBuilder(builder);

const events = try event.Channel(Event).create(loop, 0);
errdefer events.destroy();

const module = try loop.allocator.create(Module{
.loop = loop,
.event_loop_local = event_loop_local,
.events = events,
.name = name_buffer,
.root_src_path = root_src_path,
.llvm_module = llvm_module,
.context = context,
.builder = builder,
.target = target.*,
.kind = kind,
.build_mode = build_mode,
Expand Down Expand Up @@ -290,7 +325,7 @@ pub const Module = struct {
.base = Value{
.id = Value.Id.Type,
.typeof = undefined,
.ref_count = 3, // 3 because it references itself twice
.ref_count = std.atomic.Int(usize).init(3), // 3 because it references itself twice
},
.id = builtin.TypeId.Type,
},
Expand All @@ -305,7 +340,7 @@ pub const Module = struct {
.base = Value{
.id = Value.Id.Type,
.typeof = &Type.MetaType.get(module).base,
.ref_count = 1,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.Void,
},
Expand All @@ -317,7 +352,7 @@ pub const Module = struct {
.base = Value{
.id = Value.Id.Type,
.typeof = &Type.MetaType.get(module).base,
.ref_count = 1,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.NoReturn,
},
Expand All @@ -329,7 +364,7 @@ pub const Module = struct {
.base = Value{
.id = Value.Id.Type,
.typeof = &Type.MetaType.get(module).base,
.ref_count = 1,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.Bool,
},
Expand All @@ -340,7 +375,7 @@ pub const Module = struct {
.base = Value{
.id = Value.Id.Void,
.typeof = &Type.Void.get(module).base,
.ref_count = 1,
.ref_count = std.atomic.Int(usize).init(1),
},
});
errdefer module.a().destroy(module.void_value);
Expand All @@ -349,7 +384,7 @@ pub const Module = struct {
.base = Value{
.id = Value.Id.Bool,
.typeof = &Type.Bool.get(module).base,
.ref_count = 1,
.ref_count = std.atomic.Int(usize).init(1),
},
.x = true,
});
Expand All @@ -359,7 +394,7 @@ pub const Module = struct {
.base = Value{
.id = Value.Id.Bool,
.typeof = &Type.Bool.get(module).base,
.ref_count = 1,
.ref_count = std.atomic.Int(usize).init(1),
},
.x = false,
});
Expand All @@ -369,16 +404,12 @@ pub const Module = struct {
.base = Value{
.id = Value.Id.NoReturn,
.typeof = &Type.NoReturn.get(module).base,
.ref_count = 1,
.ref_count = std.atomic.Int(usize).init(1),
},
});
errdefer module.a().destroy(module.noreturn_value);
}

fn dump(self: *Module) void {
c.LLVMDumpModule(self.module);
}

pub fn destroy(self: *Module) void {
self.noreturn_value.base.deref(self);
self.void_value.base.deref(self);
Expand All @@ -389,9 +420,6 @@ pub const Module = struct {
self.meta_type.base.base.deref(self);

self.events.destroy();
c.LLVMDisposeBuilder(self.builder);
c.LLVMDisposeModule(self.llvm_module);
c.LLVMContextDispose(self.context);
self.name.deinit();

self.a().destroy(self);
Expand Down Expand Up @@ -657,10 +685,19 @@ async fn generateDeclFn(module: *Module, fn_decl: *Decl.Fn) !void {
const fndef_scope = try Scope.FnDef.create(module, fn_decl.base.parent_scope);
defer fndef_scope.base.deref(module);

const fn_type = try Type.Fn.create(module);
// TODO actually look at the return type of the AST
const return_type = &Type.Void.get(module).base;
defer return_type.base.deref(module);

const is_var_args = false;
const params = ([*]Type.Fn.Param)(undefined)[0..0];
const fn_type = try Type.Fn.create(module, return_type, params, is_var_args);
defer fn_type.base.base.deref(module);

const fn_val = try Value.Fn.create(module, fn_type, fndef_scope);
var symbol_name = try std.Buffer.init(module.a(), fn_decl.base.name);
errdefer symbol_name.deinit();

const fn_val = try Value.Fn.create(module, fn_type, fndef_scope, symbol_name);
defer fn_val.base.deref(module);

fn_decl.value = Decl.Fn.Val{ .Ok = fn_val };
Expand All @@ -674,6 +711,7 @@ async fn generateDeclFn(module: *Module, fn_decl: *Decl.Fn) !void {
) catch unreachable)) catch |err| switch (err) {
// This poison value should not cause the errdefers to run. It simply means
// that self.compile_errors is populated.
// TODO https://github.com/ziglang/zig/issues/769
error.SemanticAnalysisFailed => return {},
else => return err,
};
Expand All @@ -692,14 +730,18 @@ async fn generateDeclFn(module: *Module, fn_decl: *Decl.Fn) !void {
) catch unreachable)) catch |err| switch (err) {
// This poison value should not cause the errdefers to run. It simply means
// that self.compile_errors is populated.
// TODO https://github.com/ziglang/zig/issues/769
error.SemanticAnalysisFailed => return {},
else => return err,
};
defer analyzed_code.destroy(module.a());
errdefer analyzed_code.destroy(module.a());

if (module.verbose_ir) {
std.debug.warn("analyzed:\n");
analyzed_code.dump();
}
// TODO now render to LLVM module

// Kick off rendering to LLVM module, but it doesn't block the fn decl
// analysis from being complete.
try module.build_group.call(codegen.renderToLlvm, module, fn_val, analyzed_code);
}

0 comments on commit 278829f

Please sign in to comment.