self-hosted can compile libc hello world

This commit is contained in:
Andrew Kelley 2018-07-22 23:27:58 -04:00
parent 58c5f94a99
commit 93e78ee722
15 changed files with 1359 additions and 185 deletions

View File

@ -624,6 +624,7 @@ set(ZIG_STD_FILES
"zig/ast.zig"
"zig/index.zig"
"zig/parse.zig"
"zig/parse_string_literal.zig"
"zig/render.zig"
"zig/tokenizer.zig"
)

View File

@ -78,6 +78,7 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code)
.dibuilder = dibuilder,
.context = context,
.lock = event.Lock.init(comp.loop),
.arena = &code.arena.allocator,
};
try renderToLlvmModule(&ofile, fn_val, code);
@ -139,6 +140,7 @@ pub const ObjectFile = struct {
dibuilder: *llvm.DIBuilder,
context: llvm.ContextRef,
lock: event.Lock,
arena: *std.mem.Allocator,
fn gpa(self: *ObjectFile) *std.mem.Allocator {
return self.comp.gpa();
@ -147,7 +149,7 @@ 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_type = try fn_val.base.typ.getLlvmType(ofile.arena, ofile.context);
const llvm_fn = llvm.AddFunction(
ofile.module,
fn_val.symbol_name.ptr(),
@ -165,7 +167,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code)
// try addLLVMFnAttrInt(ofile, llvm_fn, "alignstack", align_stack);
//}
const fn_type = fn_val.base.typeof.cast(Type.Fn).?;
const fn_type = fn_val.base.typ.cast(Type.Fn).?;
try addLLVMFnAttr(ofile, llvm_fn, "nounwind");
//add_uwtable_attr(g, fn_table_entry->llvm_value);

View File

@ -194,6 +194,7 @@ pub const Compilation = struct {
bool_type: *Type.Bool,
noreturn_type: *Type.NoReturn,
comptime_int_type: *Type.ComptimeInt,
u8_type: *Type.Int,
void_value: *Value.Void,
true_value: *Value.Bool,
@ -203,6 +204,7 @@ pub const Compilation = struct {
target_machine: llvm.TargetMachineRef,
target_data_ref: llvm.TargetDataRef,
target_layout_str: [*]u8,
target_ptr_bits: u32,
/// for allocating things which have the same lifetime as this Compilation
arena_allocator: std.heap.ArenaAllocator,
@ -223,10 +225,14 @@ pub const Compilation = struct {
primitive_type_table: TypeTable,
int_type_table: event.Locked(IntTypeTable),
array_type_table: event.Locked(ArrayTypeTable),
ptr_type_table: event.Locked(PtrTypeTable),
c_int_types: [CInt.list.len]*Type.Int,
const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql);
const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql);
const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql);
const TypeTable = std.HashMap([]const u8, *Type, mem.hash_slice_u8, mem.eql_slice_u8);
const CompileErrList = std.ArrayList(*errmsg.Msg);
@ -383,6 +389,8 @@ pub const Compilation = struct {
.deinit_group = event.Group(void).init(loop),
.compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)),
.int_type_table = event.Locked(IntTypeTable).init(loop, IntTypeTable.init(loop.allocator)),
.array_type_table = event.Locked(ArrayTypeTable).init(loop, ArrayTypeTable.init(loop.allocator)),
.ptr_type_table = event.Locked(PtrTypeTable).init(loop, PtrTypeTable.init(loop.allocator)),
.c_int_types = undefined,
.meta_type = undefined,
@ -394,10 +402,12 @@ pub const Compilation = struct {
.noreturn_type = undefined,
.noreturn_value = undefined,
.comptime_int_type = undefined,
.u8_type = undefined,
.target_machine = undefined,
.target_data_ref = undefined,
.target_layout_str = undefined,
.target_ptr_bits = target.getArchPtrBitWidth(),
.root_package = undefined,
.std_package = undefined,
@ -409,6 +419,8 @@ pub const Compilation = struct {
});
errdefer {
comp.int_type_table.private_data.deinit();
comp.array_type_table.private_data.deinit();
comp.ptr_type_table.private_data.deinit();
comp.arena_allocator.deinit();
comp.loop.allocator.destroy(comp);
}
@ -517,15 +529,16 @@ pub const Compilation = struct {
.name = "type",
.base = Value{
.id = Value.Id.Type,
.typeof = undefined,
.typ = undefined,
.ref_count = std.atomic.Int(usize).init(3), // 3 because it references itself twice
},
.id = builtin.TypeId.Type,
.abi_alignment = Type.AbiAlignment.init(comp.loop),
},
.value = undefined,
});
comp.meta_type.value = &comp.meta_type.base;
comp.meta_type.base.base.typeof = &comp.meta_type.base;
comp.meta_type.base.base.typ = &comp.meta_type.base;
assert((try comp.primitive_type_table.put(comp.meta_type.base.name, &comp.meta_type.base)) == null);
comp.void_type = try comp.arena().create(Type.Void{
@ -533,10 +546,11 @@ pub const Compilation = struct {
.name = "void",
.base = Value{
.id = Value.Id.Type,
.typeof = &Type.MetaType.get(comp).base,
.typ = &Type.MetaType.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.Void,
.abi_alignment = Type.AbiAlignment.init(comp.loop),
},
});
assert((try comp.primitive_type_table.put(comp.void_type.base.name, &comp.void_type.base)) == null);
@ -546,10 +560,11 @@ pub const Compilation = struct {
.name = "noreturn",
.base = Value{
.id = Value.Id.Type,
.typeof = &Type.MetaType.get(comp).base,
.typ = &Type.MetaType.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.NoReturn,
.abi_alignment = Type.AbiAlignment.init(comp.loop),
},
});
assert((try comp.primitive_type_table.put(comp.noreturn_type.base.name, &comp.noreturn_type.base)) == null);
@ -559,10 +574,11 @@ pub const Compilation = struct {
.name = "comptime_int",
.base = Value{
.id = Value.Id.Type,
.typeof = &Type.MetaType.get(comp).base,
.typ = &Type.MetaType.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.ComptimeInt,
.abi_alignment = Type.AbiAlignment.init(comp.loop),
},
});
assert((try comp.primitive_type_table.put(comp.comptime_int_type.base.name, &comp.comptime_int_type.base)) == null);
@ -572,10 +588,11 @@ pub const Compilation = struct {
.name = "bool",
.base = Value{
.id = Value.Id.Type,
.typeof = &Type.MetaType.get(comp).base,
.typ = &Type.MetaType.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.Bool,
.abi_alignment = Type.AbiAlignment.init(comp.loop),
},
});
assert((try comp.primitive_type_table.put(comp.bool_type.base.name, &comp.bool_type.base)) == null);
@ -583,7 +600,7 @@ pub const Compilation = struct {
comp.void_value = try comp.arena().create(Value.Void{
.base = Value{
.id = Value.Id.Void,
.typeof = &Type.Void.get(comp).base,
.typ = &Type.Void.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
});
@ -591,7 +608,7 @@ pub const Compilation = struct {
comp.true_value = try comp.arena().create(Value.Bool{
.base = Value{
.id = Value.Id.Bool,
.typeof = &Type.Bool.get(comp).base,
.typ = &Type.Bool.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.x = true,
@ -600,7 +617,7 @@ pub const Compilation = struct {
comp.false_value = try comp.arena().create(Value.Bool{
.base = Value{
.id = Value.Id.Bool,
.typeof = &Type.Bool.get(comp).base,
.typ = &Type.Bool.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.x = false,
@ -609,7 +626,7 @@ pub const Compilation = struct {
comp.noreturn_value = try comp.arena().create(Value.NoReturn{
.base = Value{
.id = Value.Id.NoReturn,
.typeof = &Type.NoReturn.get(comp).base,
.typ = &Type.NoReturn.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
});
@ -620,10 +637,11 @@ pub const Compilation = struct {
.name = cint.zig_name,
.base = Value{
.id = Value.Id.Type,
.typeof = &Type.MetaType.get(comp).base,
.typ = &Type.MetaType.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.Int,
.abi_alignment = Type.AbiAlignment.init(comp.loop),
},
.key = Type.Int.Key{
.is_signed = cint.is_signed,
@ -634,6 +652,24 @@ pub const Compilation = struct {
comp.c_int_types[i] = c_int_type;
assert((try comp.primitive_type_table.put(cint.zig_name, &c_int_type.base)) == null);
}
comp.u8_type = try comp.arena().create(Type.Int{
.base = Type{
.name = "u8",
.base = Value{
.id = Value.Id.Type,
.typ = &Type.MetaType.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.Int,
.abi_alignment = Type.AbiAlignment.init(comp.loop),
},
.key = Type.Int.Key{
.is_signed = false,
.bit_count = 8,
},
.garbage_node = undefined,
});
assert((try comp.primitive_type_table.put(comp.u8_type.base.name, &comp.u8_type.base)) == null);
}
/// This function can safely use async/await, because it manages Compilation's lifetime,
@ -750,7 +786,7 @@ pub const Compilation = struct {
ast.Node.Id.Comptime => {
const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl);
try decl_group.call(addCompTimeBlock, self, &decls.base, comptime_node);
try self.prelink_group.call(addCompTimeBlock, self, &decls.base, comptime_node);
},
ast.Node.Id.VarDecl => @panic("TODO"),
ast.Node.Id.FnProto => {
@ -770,7 +806,6 @@ pub const Compilation = struct {
.name = name,
.visib = parseVisibToken(tree, fn_proto.visib_token),
.resolution = event.Future(BuildError!void).init(self.loop),
.resolution_in_progress = 0,
.parent_scope = &decls.base,
},
.value = Decl.Fn.Val{ .Unresolved = {} },
@ -778,16 +813,22 @@ pub const Compilation = struct {
});
errdefer self.gpa().destroy(fn_decl);
try decl_group.call(addTopLevelDecl, self, &fn_decl.base);
try decl_group.call(addTopLevelDecl, self, decls, &fn_decl.base);
},
ast.Node.Id.TestDecl => @panic("TODO"),
else => unreachable,
}
}
try await (async decl_group.wait() catch unreachable);
// Now other code can rely on the decls scope having a complete list of names.
decls.name_future.resolve();
}
try await (async self.prelink_group.wait() catch unreachable);
(await (async self.prelink_group.wait() catch unreachable)) catch |err| switch (err) {
error.SemanticAnalysisFailed => {},
else => return err,
};
const any_prelink_errors = blk: {
const compile_errors = await (async self.compile_errors.acquire() catch unreachable);
@ -857,14 +898,31 @@ pub const Compilation = struct {
analyzed_code.destroy(comp.gpa());
}
async fn addTopLevelDecl(self: *Compilation, decl: *Decl) !void {
async fn addTopLevelDecl(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void {
const tree = &decl.findRootScope().tree;
const is_export = decl.isExported(tree);
var add_to_table_resolved = false;
const add_to_table = async self.addDeclToTable(decls, decl) catch unreachable;
errdefer if (!add_to_table_resolved) cancel add_to_table; // TODO https://github.com/ziglang/zig/issues/1261
if (is_export) {
try self.prelink_group.call(verifyUniqueSymbol, self, decl);
try self.prelink_group.call(resolveDecl, self, decl);
}
add_to_table_resolved = true;
try await add_to_table;
}
async fn addDeclToTable(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void {
const held = await (async decls.table.acquire() catch unreachable);
defer held.release();
if (try held.value.put(decl.name, decl)) |other_decl| {
try self.addCompileError(decls.base.findRoot(), decl.getSpan(), "redefinition of '{}'", decl.name);
// TODO note: other definition here
}
}
fn addCompileError(self: *Compilation, root: *Scope.Root, span: Span, comptime fmt: []const u8, args: ...) !void {
@ -1043,6 +1101,15 @@ pub const Compilation = struct {
return result_val.cast(Type).?;
}
/// This declaration has been blessed as going into the final code generation.
pub async fn resolveDecl(comp: *Compilation, decl: *Decl) !void {
if (await (async decl.resolution.start() catch unreachable)) |ptr| return ptr.*;
decl.resolution.data = try await (async generateDecl(comp, decl) catch unreachable);
decl.resolution.resolve();
return decl.resolution.data;
}
};
fn printError(comptime format: []const u8, args: ...) !void {
@ -1062,20 +1129,6 @@ fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib
}
}
/// This declaration has been blessed as going into the final code generation.
pub async fn resolveDecl(comp: *Compilation, decl: *Decl) !void {
if (await (async decl.resolution.start() catch unreachable)) |ptr| return ptr.*;
decl.resolution.data = (await (async generateDecl(comp, decl) catch unreachable)) catch |err| switch (err) {
// This poison value should not cause the errdefers to run. It simply means
// that comp.compile_errors is populated.
error.SemanticAnalysisFailed => {},
else => err,
};
decl.resolution.resolve();
return decl.resolution.data;
}
/// The function that actually does the generation.
async fn generateDecl(comp: *Compilation, decl: *Decl) !void {
switch (decl.id) {
@ -1089,34 +1142,27 @@ async fn generateDecl(comp: *Compilation, decl: *Decl) !void {
}
async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void {
const body_node = fn_decl.fn_proto.body_node orelse @panic("TODO extern fn proto decl");
const body_node = fn_decl.fn_proto.body_node orelse return await (async generateDeclFnProto(comp, fn_decl) catch unreachable);
const fndef_scope = try Scope.FnDef.create(comp, fn_decl.base.parent_scope);
defer fndef_scope.base.deref(comp);
const return_type_node = switch (fn_decl.fn_proto.return_type) {
ast.Node.FnProto.ReturnType.Explicit => |n| n,
ast.Node.FnProto.ReturnType.InferErrorSet => |n| n,
};
const return_type = try await (async comp.analyzeTypeExpr(&fndef_scope.base, return_type_node) catch unreachable);
return_type.base.deref(comp);
const is_var_args = false;
const params = ([*]Type.Fn.Param)(undefined)[0..0];
const fn_type = try Type.Fn.create(comp, return_type, params, is_var_args);
const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable);
defer fn_type.base.base.deref(comp);
var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name);
errdefer symbol_name.deinit();
var symbol_name_consumed = false;
errdefer if (!symbol_name_consumed) symbol_name.deinit();
// The Decl.Fn owns the initial 1 reference count
const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name);
fn_decl.value = Decl.Fn.Val{ .Ok = fn_val };
fn_decl.value = Decl.Fn.Val{ .Fn = fn_val };
symbol_name_consumed = true;
const analyzed_code = try await (async comp.genAndAnalyzeCode(
&fndef_scope.base,
body_node,
return_type,
fn_type.return_type,
) catch unreachable);
errdefer analyzed_code.destroy(comp.gpa());
@ -1141,3 +1187,54 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) void {
fn getZigDir(allocator: *mem.Allocator) ![]u8 {
return os.getAppDataDir(allocator, "zig");
}
async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.FnProto) !*Type.Fn {
const return_type_node = switch (fn_proto.return_type) {
ast.Node.FnProto.ReturnType.Explicit => |n| n,
ast.Node.FnProto.ReturnType.InferErrorSet => |n| n,
};
const return_type = try await (async comp.analyzeTypeExpr(scope, return_type_node) catch unreachable);
return_type.base.deref(comp);
var params = ArrayList(Type.Fn.Param).init(comp.gpa());
var params_consumed = false;
defer if (params_consumed) {
for (params.toSliceConst()) |param| {
param.typ.base.deref(comp);
}
params.deinit();
};
const is_var_args = false;
{
var it = fn_proto.params.iterator(0);
while (it.next()) |param_node_ptr| {
const param_node = param_node_ptr.*.cast(ast.Node.ParamDecl).?;
const param_type = try await (async comp.analyzeTypeExpr(scope, param_node.type_node) catch unreachable);
errdefer param_type.base.deref(comp);
try params.append(Type.Fn.Param{
.typ = param_type,
.is_noalias = param_node.noalias_token != null,
});
}
}
const fn_type = try Type.Fn.create(comp, return_type, params.toOwnedSlice(), is_var_args);
params_consumed = true;
errdefer fn_type.base.base.deref(comp);
return fn_type;
}
async fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void {
const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable);
defer fn_type.base.base.deref(comp);
var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name);
var symbol_name_consumed = false;
defer if (!symbol_name_consumed) symbol_name.deinit();
// The Decl.Fn owns the initial 1 reference count
const fn_proto_val = try Value.FnProto.create(comp, fn_type, symbol_name);
fn_decl.value = Decl.Fn.Val{ .FnProto = fn_proto_val };
symbol_name_consumed = true;
}

View File

@ -15,7 +15,6 @@ pub const Decl = struct {
name: []const u8,
visib: Visib,
resolution: event.Future(Compilation.BuildError!void),
resolution_in_progress: u8,
parent_scope: *Scope,
pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8);
@ -63,12 +62,13 @@ pub const Decl = struct {
pub const Fn = struct {
base: Decl,
value: Val,
fn_proto: *const ast.Node.FnProto,
fn_proto: *ast.Node.FnProto,
// TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous
pub const Val = union {
pub const Val = union(enum) {
Unresolved: void,
Ok: *Value.Fn,
Fn: *Value.Fn,
FnProto: *Value.FnProto,
};
pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 {

View File

@ -16,11 +16,18 @@ pub const Span = struct {
last: ast.TokenIndex,
pub fn token(i: TokenIndex) Span {
return Span {
return Span{
.first = i,
.last = i,
};
}
pub fn node(n: *ast.Node) Span {
return Span{
.first = n.firstToken(),
.last = n.lastToken(),
};
}
};
pub const Msg = struct {

View File

@ -11,6 +11,7 @@ const Token = std.zig.Token;
const Span = @import("errmsg.zig").Span;
const llvm = @import("llvm.zig");
const ObjectFile = @import("codegen.zig").ObjectFile;
const Decl = @import("decl.zig").Decl;
pub const LVal = enum {
None,
@ -30,10 +31,10 @@ pub const IrVal = union(enum) {
pub fn dump(self: IrVal) void {
switch (self) {
IrVal.Unknown => typeof.dump(),
IrVal.KnownType => |typeof| {
IrVal.Unknown => std.debug.warn("Unknown"),
IrVal.KnownType => |typ| {
std.debug.warn("KnownType(");
typeof.dump();
typ.dump();
std.debug.warn(")");
},
IrVal.KnownValue => |value| {
@ -108,21 +109,29 @@ pub const Inst = struct {
unreachable;
}
pub fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst {
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
if (base.id == @field(Id, @memberName(Id, i))) {
const T = @field(Inst, @memberName(Id, i));
return @fieldParentPtr(T, "base", base).analyze(ira);
}
pub async fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst {
switch (base.id) {
Id.Return => return @fieldParentPtr(Return, "base", base).analyze(ira),
Id.Const => return @fieldParentPtr(Const, "base", base).analyze(ira),
Id.Call => return @fieldParentPtr(Call, "base", base).analyze(ira),
Id.DeclRef => return await (async @fieldParentPtr(DeclRef, "base", base).analyze(ira) catch unreachable),
Id.Ref => return await (async @fieldParentPtr(Ref, "base", base).analyze(ira) catch unreachable),
Id.DeclVar => return @fieldParentPtr(DeclVar, "base", base).analyze(ira),
Id.CheckVoidStmt => return @fieldParentPtr(CheckVoidStmt, "base", base).analyze(ira),
Id.Phi => return @fieldParentPtr(Phi, "base", base).analyze(ira),
Id.Br => return @fieldParentPtr(Br, "base", base).analyze(ira),
Id.AddImplicitReturnType => return @fieldParentPtr(AddImplicitReturnType, "base", base).analyze(ira),
Id.PtrType => return await (async @fieldParentPtr(PtrType, "base", base).analyze(ira) catch unreachable),
}
unreachable;
}
pub fn render(base: *Inst, 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.Call => return @fieldParentPtr(Call, "base", base).render(ofile, fn_val),
Id.DeclRef => unreachable,
Id.PtrType => unreachable,
Id.Ref => @panic("TODO"),
Id.DeclVar => @panic("TODO"),
Id.CheckVoidStmt => @panic("TODO"),
@ -135,7 +144,7 @@ pub const Inst = struct {
fn ref(base: *Inst, builder: *Builder) void {
base.ref_count += 1;
if (base.owner_bb != builder.current_basic_block and !base.isCompTime()) {
base.owner_bb.ref();
base.owner_bb.ref(builder);
}
}
@ -155,11 +164,51 @@ pub const Inst = struct {
}
}
fn getConstVal(self: *Inst, ira: *Analyze) !*Value {
if (self.isCompTime()) {
return self.val.KnownValue;
} else {
try ira.addCompileError(self.span, "unable to evaluate constant expression");
return error.SemanticAnalysisFailed;
}
}
fn getAsConstType(param: *Inst, ira: *Analyze) !*Type {
const meta_type = Type.MetaType.get(ira.irb.comp);
meta_type.base.base.deref(ira.irb.comp);
const inst = try param.getAsParam();
const casted = try ira.implicitCast(inst, &meta_type.base);
const val = try casted.getConstVal(ira);
return val.cast(Value.Type).?;
}
fn getAsConstAlign(param: *Inst, ira: *Analyze) !u32 {
return error.Unimplemented;
//const align_type = Type.Int.get_align(ira.irb.comp);
//align_type.base.base.deref(ira.irb.comp);
//const inst = try param.getAsParam();
//const casted = try ira.implicitCast(inst, align_type);
//const val = try casted.getConstVal(ira);
//uint32_t align_bytes = bigint_as_unsigned(&const_val->data.x_bigint);
//if (align_bytes == 0) {
// ir_add_error(ira, value, buf_sprintf("alignment must be >= 1"));
// return false;
//}
//if (!is_power_of_2(align_bytes)) {
// ir_add_error(ira, value, buf_sprintf("alignment value %" PRIu32 " is not a power of 2", align_bytes));
// return false;
//}
}
/// asserts that the type is known
fn getKnownType(self: *Inst) *Type {
switch (self.val) {
IrVal.KnownType => |typeof| return typeof,
IrVal.KnownValue => |value| return value.typeof,
IrVal.KnownType => |typ| return typ,
IrVal.KnownValue => |value| return value.typ,
IrVal.Unknown => unreachable,
}
}
@ -171,8 +220,8 @@ pub const Inst = struct {
pub fn isNoReturn(base: *const Inst) bool {
switch (base.val) {
IrVal.Unknown => return false,
IrVal.KnownValue => |x| return x.typeof.id == Type.Id.NoReturn,
IrVal.KnownType => |typeof| return typeof.id == Type.Id.NoReturn,
IrVal.KnownValue => |x| return x.typ.id == Type.Id.NoReturn,
IrVal.KnownType => |typ| return typ.id == Type.Id.NoReturn,
}
}
@ -196,6 +245,85 @@ pub const Inst = struct {
Phi,
Br,
AddImplicitReturnType,
Call,
DeclRef,
PtrType,
};
pub const Call = struct {
base: Inst,
params: Params,
const Params = struct {
fn_ref: *Inst,
args: []*Inst,
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(self: *const Call) void {
std.debug.warn("#{}(", self.params.fn_ref.debug_id);
for (self.params.args) |arg| {
std.debug.warn("#{},", arg.debug_id);
}
std.debug.warn(")");
}
pub fn hasSideEffects(self: *const Call) bool {
return true;
}
pub fn analyze(self: *const Call, ira: *Analyze) !*Inst {
const fn_ref = try self.params.fn_ref.getAsParam();
const fn_ref_type = fn_ref.getKnownType();
const fn_type = fn_ref_type.cast(Type.Fn) orelse {
try ira.addCompileError(fn_ref.span, "type '{}' not a function", fn_ref_type.name);
return error.SemanticAnalysisFailed;
};
if (fn_type.params.len != self.params.args.len) {
try ira.addCompileError(
self.base.span,
"expected {} arguments, found {}",
fn_type.params.len,
self.params.args.len,
);
return error.SemanticAnalysisFailed;
}
const args = try ira.irb.arena().alloc(*Inst, self.params.args.len);
for (self.params.args) |arg, i| {
args[i] = try arg.getAsParam();
}
const new_inst = try ira.irb.build(Call, self.base.scope, self.base.span, Params{
.fn_ref = fn_ref,
.args = args,
});
new_inst.val = IrVal{ .KnownType = fn_type.return_type };
return new_inst;
}
pub fn render(self: *Call, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef {
const fn_ref = self.params.fn_ref.llvm_value.?;
const args = try ofile.arena.alloc(llvm.ValueRef, self.params.args.len);
for (self.params.args) |arg, i| {
args[i] = arg.llvm_value.?;
}
const llvm_cc = llvm.CCallConv;
const fn_inline = llvm.FnInline.Auto;
return llvm.BuildCall(
ofile.builder,
fn_ref,
args.ptr,
@intCast(c_uint, args.len),
llvm_cc,
fn_inline,
c"",
) orelse error.OutOfMemory;
}
};
pub const Const = struct {
@ -254,14 +382,14 @@ pub const Inst = 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 {
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);
_ = llvm.BuildRet(ofile.builder, value) orelse return error.OutOfMemory;
}
return null;
}
@ -285,7 +413,7 @@ pub const Inst = struct {
return false;
}
pub fn analyze(self: *const Ref, ira: *Analyze) !*Inst {
pub async fn analyze(self: *const Ref, ira: *Analyze) !*Inst {
const target = try self.params.target.getAsParam();
if (ira.getCompTimeValOrNullUndefOk(target)) |val| {
@ -294,7 +422,6 @@ pub const Inst = struct {
Value.Ptr.Mut.CompTimeConst,
self.params.mut,
self.params.volatility,
val.typeof.getAbiAlignment(ira.irb.comp),
);
}
@ -304,14 +431,13 @@ pub const Inst = struct {
.volatility = self.params.volatility,
});
const elem_type = target.getKnownType();
const ptr_type = Type.Pointer.get(
ira.irb.comp,
elem_type,
self.params.mut,
self.params.volatility,
Type.Pointer.Size.One,
elem_type.getAbiAlignment(ira.irb.comp),
);
const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{
.child_type = elem_type,
.mut = self.params.mut,
.vol = self.params.volatility,
.size = Type.Pointer.Size.One,
.alignment = Type.Pointer.Align.Abi,
}) catch unreachable);
// TODO: potentially set the hint that this is a stack pointer. But it might not be - this
// could be a ref of a global, for example
new_inst.val = IrVal{ .KnownType = &ptr_type.base };
@ -320,6 +446,97 @@ pub const Inst = struct {
}
};
pub const DeclRef = struct {
base: Inst,
params: Params,
const Params = struct {
decl: *Decl,
lval: LVal,
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const DeclRef) void {}
pub fn hasSideEffects(inst: *const DeclRef) bool {
return false;
}
pub async fn analyze(self: *const DeclRef, ira: *Analyze) !*Inst {
(await (async ira.irb.comp.resolveDecl(self.params.decl) catch unreachable)) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => return error.SemanticAnalysisFailed,
};
switch (self.params.decl.id) {
Decl.Id.CompTime => unreachable,
Decl.Id.Var => return error.Unimplemented,
Decl.Id.Fn => {
const fn_decl = @fieldParentPtr(Decl.Fn, "base", self.params.decl);
const decl_val = switch (fn_decl.value) {
Decl.Fn.Val.Unresolved => unreachable,
Decl.Fn.Val.Fn => |fn_val| &fn_val.base,
Decl.Fn.Val.FnProto => |fn_proto| &fn_proto.base,
};
switch (self.params.lval) {
LVal.None => {
return ira.irb.buildConstValue(self.base.scope, self.base.span, decl_val);
},
LVal.Ptr => return error.Unimplemented,
}
},
}
}
};
pub const PtrType = struct {
base: Inst,
params: Params,
const Params = struct {
child_type: *Inst,
mut: Type.Pointer.Mut,
vol: Type.Pointer.Vol,
size: Type.Pointer.Size,
alignment: ?*Inst,
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const PtrType) void {}
pub fn hasSideEffects(inst: *const PtrType) bool {
return false;
}
pub async fn analyze(self: *const PtrType, ira: *Analyze) !*Inst {
const child_type = try self.params.child_type.getAsConstType(ira);
// if (child_type->id == TypeTableEntryIdUnreachable) {
// ir_add_error(ira, &instruction->base, buf_sprintf("pointer to noreturn not allowed"));
// return ira->codegen->builtin_types.entry_invalid;
// } else if (child_type->id == TypeTableEntryIdOpaque && instruction->ptr_len == PtrLenUnknown) {
// ir_add_error(ira, &instruction->base, buf_sprintf("unknown-length pointer to opaque"));
// return ira->codegen->builtin_types.entry_invalid;
// }
const alignment = if (self.params.alignment) |align_inst| blk: {
const amt = try align_inst.getAsConstAlign(ira);
break :blk Type.Pointer.Align{ .Override = amt };
} else blk: {
break :blk Type.Pointer.Align{ .Abi = {} };
};
const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{
.child_type = child_type,
.mut = self.params.mut,
.vol = self.params.vol,
.size = self.params.size,
.alignment = alignment,
}) catch unreachable);
ptr_type.base.base.deref(ira.irb.comp);
return ira.irb.buildConstValue(self.base.scope, self.base.span, &ptr_type.base.base);
}
};
pub const DeclVar = struct {
base: Inst,
params: Params,
@ -351,14 +568,21 @@ pub const Inst = struct {
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const CheckVoidStmt) void {}
pub fn dump(self: *const CheckVoidStmt) void {
std.debug.warn("#{}", self.params.target.debug_id);
}
pub fn hasSideEffects(inst: *const CheckVoidStmt) bool {
return true;
}
pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Inst {
return error.Unimplemented; // TODO
const target = try self.params.target.getAsParam();
if (target.getKnownType().id != Type.Id.Void) {
try ira.addCompileError(self.base.span, "expression value is ignored");
return error.SemanticAnalysisFailed;
}
return ira.irb.buildConstVoid(self.base.scope, self.base.span, true);
}
};
@ -583,7 +807,7 @@ pub const BasicBlock = struct {
/// the basic block that this one derives from in analysis
parent: ?*BasicBlock,
pub fn ref(self: *BasicBlock) void {
pub fn ref(self: *BasicBlock, builder: *Builder) void {
self.ref_count += 1;
}
@ -724,8 +948,42 @@ pub const Builder = struct {
ast.Node.Id.VarDecl => return error.Unimplemented,
ast.Node.Id.Defer => return error.Unimplemented,
ast.Node.Id.InfixOp => return error.Unimplemented,
ast.Node.Id.PrefixOp => return error.Unimplemented,
ast.Node.Id.SuffixOp => return error.Unimplemented,
ast.Node.Id.PrefixOp => {
const prefix_op = @fieldParentPtr(ast.Node.PrefixOp, "base", node);
switch (prefix_op.op) {
ast.Node.PrefixOp.Op.AddressOf => return error.Unimplemented,
ast.Node.PrefixOp.Op.ArrayType => |n| return error.Unimplemented,
ast.Node.PrefixOp.Op.Await => return error.Unimplemented,
ast.Node.PrefixOp.Op.BitNot => return error.Unimplemented,
ast.Node.PrefixOp.Op.BoolNot => return error.Unimplemented,
ast.Node.PrefixOp.Op.Cancel => return error.Unimplemented,
ast.Node.PrefixOp.Op.OptionalType => return error.Unimplemented,
ast.Node.PrefixOp.Op.Negation => return error.Unimplemented,
ast.Node.PrefixOp.Op.NegationWrap => return error.Unimplemented,
ast.Node.PrefixOp.Op.Resume => return error.Unimplemented,
ast.Node.PrefixOp.Op.PtrType => |ptr_info| {
const inst = try await (async irb.genPtrType(prefix_op, ptr_info, scope) catch unreachable);
return irb.lvalWrap(scope, inst, lval);
},
ast.Node.PrefixOp.Op.SliceType => |ptr_info| return error.Unimplemented,
ast.Node.PrefixOp.Op.Try => return error.Unimplemented,
}
},
ast.Node.Id.SuffixOp => {
const suffix_op = @fieldParentPtr(ast.Node.SuffixOp, "base", node);
switch (suffix_op.op) {
@TagType(ast.Node.SuffixOp.Op).Call => |*call| {
const inst = try await (async irb.genCall(suffix_op, call, scope) catch unreachable);
return irb.lvalWrap(scope, inst, lval);
},
@TagType(ast.Node.SuffixOp.Op).ArrayAccess => |n| return error.Unimplemented,
@TagType(ast.Node.SuffixOp.Op).Slice => |slice| return error.Unimplemented,
@TagType(ast.Node.SuffixOp.Op).ArrayInitializer => |init_list| return error.Unimplemented,
@TagType(ast.Node.SuffixOp.Op).StructInitializer => |init_list| return error.Unimplemented,
@TagType(ast.Node.SuffixOp.Op).Deref => return error.Unimplemented,
@TagType(ast.Node.SuffixOp.Op).UnwrapOptional => return error.Unimplemented,
}
},
ast.Node.Id.Switch => return error.Unimplemented,
ast.Node.Id.While => return error.Unimplemented,
ast.Node.Id.For => return error.Unimplemented,
@ -744,7 +1002,11 @@ pub const Builder = struct {
return irb.lvalWrap(scope, try irb.genIntLit(int_lit, scope), lval);
},
ast.Node.Id.FloatLiteral => return error.Unimplemented,
ast.Node.Id.StringLiteral => return error.Unimplemented,
ast.Node.Id.StringLiteral => {
const str_lit = @fieldParentPtr(ast.Node.StringLiteral, "base", node);
const inst = try await (async irb.genStrLit(str_lit, scope) catch unreachable);
return irb.lvalWrap(scope, inst, lval);
},
ast.Node.Id.MultilineStringLiteral => return error.Unimplemented,
ast.Node.Id.CharLiteral => return error.Unimplemented,
ast.Node.Id.BoolLiteral => return error.Unimplemented,
@ -789,6 +1051,99 @@ pub const Builder = struct {
}
}
async fn genCall(irb: *Builder, suffix_op: *ast.Node.SuffixOp, call: *ast.Node.SuffixOp.Op.Call, scope: *Scope) !*Inst {
const fn_ref = try await (async irb.genNode(suffix_op.lhs, scope, LVal.None) catch unreachable);
const args = try irb.arena().alloc(*Inst, call.params.len);
var it = call.params.iterator(0);
var i: usize = 0;
while (it.next()) |arg_node_ptr| : (i += 1) {
args[i] = try await (async irb.genNode(arg_node_ptr.*, scope, LVal.None) catch unreachable);
}
//bool is_async = node->data.fn_call_expr.is_async;
//IrInstruction *async_allocator = nullptr;
//if (is_async) {
// if (node->data.fn_call_expr.async_allocator) {
// async_allocator = ir_gen_node(irb, node->data.fn_call_expr.async_allocator, scope);
// if (async_allocator == irb->codegen->invalid_instruction)
// return async_allocator;
// }
//}
return irb.build(Inst.Call, scope, Span.token(suffix_op.rtoken), Inst.Call.Params{
.fn_ref = fn_ref,
.args = args,
});
//IrInstruction *fn_call = ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, FnInlineAuto, is_async, async_allocator, nullptr);
//return ir_lval_wrap(irb, scope, fn_call, lval);
}
async fn genPtrType(
irb: *Builder,
prefix_op: *ast.Node.PrefixOp,
ptr_info: ast.Node.PrefixOp.PtrInfo,
scope: *Scope,
) !*Inst {
// TODO port more logic
//assert(node->type == NodeTypePointerType);
//PtrLen ptr_len = (node->data.pointer_type.star_token->id == TokenIdStar ||
// node->data.pointer_type.star_token->id == TokenIdStarStar) ? PtrLenSingle : PtrLenUnknown;
//bool is_const = node->data.pointer_type.is_const;
//bool is_volatile = node->data.pointer_type.is_volatile;
//AstNode *expr_node = node->data.pointer_type.op_expr;
//AstNode *align_expr = node->data.pointer_type.align_expr;
//IrInstruction *align_value;
//if (align_expr != nullptr) {
// align_value = ir_gen_node(irb, align_expr, scope);
// if (align_value == irb->codegen->invalid_instruction)
// return align_value;
//} else {
// align_value = nullptr;
//}
const child_type = try await (async irb.genNode(prefix_op.rhs, scope, LVal.None) catch unreachable);
//uint32_t bit_offset_start = 0;
//if (node->data.pointer_type.bit_offset_start != nullptr) {
// if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_start, 32, false)) {
// Buf *val_buf = buf_alloc();
// bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_start, 10);
// exec_add_error_node(irb->codegen, irb->exec, node,
// buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf)));
// return irb->codegen->invalid_instruction;
// }
// bit_offset_start = bigint_as_unsigned(node->data.pointer_type.bit_offset_start);
//}
//uint32_t bit_offset_end = 0;
//if (node->data.pointer_type.bit_offset_end != nullptr) {
// if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_end, 32, false)) {
// Buf *val_buf = buf_alloc();
// bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_end, 10);
// exec_add_error_node(irb->codegen, irb->exec, node,
// buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf)));
// return irb->codegen->invalid_instruction;
// }
// bit_offset_end = bigint_as_unsigned(node->data.pointer_type.bit_offset_end);
//}
//if ((bit_offset_start != 0 || bit_offset_end != 0) && bit_offset_start >= bit_offset_end) {
// exec_add_error_node(irb->codegen, irb->exec, node,
// buf_sprintf("bit offset start must be less than bit offset end"));
// return irb->codegen->invalid_instruction;
//}
return irb.build(Inst.PtrType, scope, Span.node(&prefix_op.base), Inst.PtrType.Params{
.child_type = child_type,
.mut = Type.Pointer.Mut.Mut,
.vol = Type.Pointer.Vol.Non,
.size = Type.Pointer.Size.Many,
.alignment = null,
});
}
fn isCompTime(irb: *Builder, target_scope: *Scope) bool {
if (irb.is_comptime)
return true;
@ -847,6 +1202,56 @@ pub const Builder = struct {
return inst;
}
pub async fn genStrLit(irb: *Builder, str_lit: *ast.Node.StringLiteral, scope: *Scope) !*Inst {
const str_token = irb.root_scope.tree.tokenSlice(str_lit.token);
const src_span = Span.token(str_lit.token);
var bad_index: usize = undefined;
var buf = std.zig.parseStringLiteral(irb.comp.gpa(), str_token, &bad_index) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.InvalidCharacter => {
try irb.comp.addCompileError(
irb.root_scope,
src_span,
"invalid character in string literal: '{c}'",
str_token[bad_index],
);
return error.SemanticAnalysisFailed;
},
};
var buf_cleaned = false;
errdefer if (!buf_cleaned) irb.comp.gpa().free(buf);
if (str_token[0] == 'c') {
// first we add a null
buf = try irb.comp.gpa().realloc(u8, buf, buf.len + 1);
buf[buf.len - 1] = 0;
// next make an array value
const array_val = try await (async Value.Array.createOwnedBuffer(irb.comp, buf) catch unreachable);
buf_cleaned = true;
defer array_val.base.deref(irb.comp);
// then make a pointer value pointing at the first element
const ptr_val = try await (async Value.Ptr.createArrayElemPtr(
irb.comp,
array_val,
Type.Pointer.Mut.Const,
Type.Pointer.Size.Many,
0,
) catch unreachable);
defer ptr_val.base.deref(irb.comp);
return irb.buildConstValue(scope, src_span, &ptr_val.base);
} else {
const array_val = try await (async Value.Array.createOwnedBuffer(irb.comp, buf) catch unreachable);
buf_cleaned = true;
defer array_val.base.deref(irb.comp);
return irb.buildConstValue(scope, src_span, &array_val.base);
}
}
pub async fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst {
const block_scope = try Scope.Block.create(irb.comp, parent_scope);
@ -911,7 +1316,10 @@ pub const Builder = struct {
_ = irb.build(
Inst.CheckVoidStmt,
child_scope,
statement_value.span,
Span{
.first = statement_node.firstToken(),
.last = statement_node.lastToken(),
},
Inst.CheckVoidStmt.Params{ .target = statement_value },
);
}
@ -1068,6 +1476,8 @@ pub const Builder = struct {
if (result) |primitive_type| {
defer primitive_type.base.deref(irb.comp);
switch (lval) {
// if (lval == LValPtr) {
// return ir_build_ref(irb, scope, node, value, false, false);
LVal.Ptr => return error.Unimplemented,
LVal.None => return irb.buildConstValue(scope, src_span, &primitive_type.base),
}
@ -1079,15 +1489,6 @@ pub const Builder = struct {
},
error.OutOfMemory => return error.OutOfMemory,
}
//TypeTableEntry *primitive_type = get_primitive_type(irb->codegen, variable_name);
//if (primitive_type != nullptr) {
// IrInstruction *value = ir_build_const_type(irb, scope, node, primitive_type);
// if (lval == LValPtr) {
// return ir_build_ref(irb, scope, node, value, false, false);
// } else {
// return value;
// }
//}
//VariableTableEntry *var = find_variable(irb->codegen, scope, variable_name);
//if (var) {
@ -1098,9 +1499,12 @@ pub const Builder = struct {
// return ir_build_load_ptr(irb, scope, node, var_ptr);
//}
//Tld *tld = find_decl(irb->codegen, scope, variable_name);
//if (tld)
// return ir_build_decl_ref(irb, scope, node, tld, lval);
if (await (async irb.findDecl(scope, name) catch unreachable)) |decl| {
return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{
.decl = decl,
.lval = lval,
});
}
//if (node->owner->any_imports_failed) {
// // skip the error message since we had a failing import in this file
@ -1251,8 +1655,26 @@ pub const Builder = struct {
const FieldType = comptime @typeOf(@field(I.Params(undefined), @memberName(I.Params, i)));
switch (FieldType) {
*Inst => @field(inst.params, @memberName(I.Params, i)).ref(self),
*BasicBlock => @field(inst.params, @memberName(I.Params, i)).ref(self),
?*Inst => if (@field(inst.params, @memberName(I.Params, i))) |other| other.ref(self),
else => {},
[]*Inst => {
// TODO https://github.com/ziglang/zig/issues/1269
for (@field(inst.params, @memberName(I.Params, i))) |other|
other.ref(self);
},
[]*BasicBlock => {
// TODO https://github.com/ziglang/zig/issues/1269
for (@field(inst.params, @memberName(I.Params, i))) |other|
other.ref(self);
},
Type.Pointer.Mut,
Type.Pointer.Vol,
Type.Pointer.Size,
LVal,
*Decl,
=> {},
// it's ok to add more types here, just make sure any instructions are ref'd appropriately
else => @compileError("unrecognized type in Params: " ++ @typeName(FieldType)),
}
}
@ -1348,6 +1770,24 @@ pub const Builder = struct {
// is_comptime);
//// the above blocks are rendered by ir_gen after the rest of codegen
}
async fn findDecl(irb: *Builder, scope: *Scope, name: []const u8) ?*Decl {
var s = scope;
while (true) {
switch (s.id) {
Scope.Id.Decls => {
const decls = @fieldParentPtr(Scope.Decls, "base", s);
const table = await (async decls.getTableReadOnly() catch unreachable);
if (table.get(name)) |entry| {
return entry.value;
}
},
Scope.Id.Root => return null,
else => {},
}
s = s.parent.?;
}
}
};
const Analyze = struct {
@ -1930,7 +2370,6 @@ const Analyze = struct {
ptr_mut: Value.Ptr.Mut,
mut: Type.Pointer.Mut,
volatility: Type.Pointer.Vol,
ptr_align: u32,
) Analyze.Error!*Inst {
return error.Unimplemented;
}
@ -1945,7 +2384,7 @@ pub async fn gen(
errdefer irb.abort();
const entry_block = try irb.createBasicBlock(scope, c"Entry");
entry_block.ref(); // Entry block gets a reference because we enter it to begin.
entry_block.ref(&irb); // Entry block gets a reference because we enter it to begin.
try irb.setCursorAtEndAndAppendBlock(entry_block);
const result = try await (async irb.genNode(body_node, scope, LVal.None) catch unreachable);
@ -1965,7 +2404,7 @@ pub async fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type)
errdefer ira.abort();
const new_entry_bb = try ira.getNewBasicBlock(old_entry_bb, null);
new_entry_bb.ref();
new_entry_bb.ref(&ira.irb);
ira.irb.current_basic_block = new_entry_bb;
@ -1979,7 +2418,8 @@ pub async fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type)
continue;
}
const return_inst = try old_instruction.analyze(&ira);
const return_inst = try await (async old_instruction.analyze(&ira) catch unreachable);
assert(return_inst.val != IrVal.Unknown); // at least the type should be known at this point
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

View File

@ -23,12 +23,17 @@ pub const TargetMachineRef = removeNullability(c.LLVMTargetMachineRef);
pub const TargetDataRef = removeNullability(c.LLVMTargetDataRef);
pub const DIBuilder = c.ZigLLVMDIBuilder;
pub const ABIAlignmentOfType = c.LLVMABIAlignmentOfType;
pub const AddAttributeAtIndex = c.LLVMAddAttributeAtIndex;
pub const AddFunction = c.LLVMAddFunction;
pub const AddGlobal = c.LLVMAddGlobal;
pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag;
pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag;
pub const ArrayType = c.LLVMArrayType;
pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation;
pub const ConstAllOnes = c.LLVMConstAllOnes;
pub const ConstArray = c.LLVMConstArray;
pub const ConstBitCast = c.LLVMConstBitCast;
pub const ConstInt = c.LLVMConstInt;
pub const ConstIntOfArbitraryPrecision = c.LLVMConstIntOfArbitraryPrecision;
pub const ConstNeg = c.LLVMConstNeg;
@ -59,6 +64,7 @@ pub const GetEnumAttributeKindForName = c.LLVMGetEnumAttributeKindForName;
pub const GetHostCPUName = c.ZigLLVMGetHostCPUName;
pub const GetMDKindIDInContext = c.LLVMGetMDKindIDInContext;
pub const GetNativeFeatures = c.ZigLLVMGetNativeFeatures;
pub const GetUndef = c.LLVMGetUndef;
pub const HalfTypeInContext = c.LLVMHalfTypeInContext;
pub const InitializeAllAsmParsers = c.LLVMInitializeAllAsmParsers;
pub const InitializeAllAsmPrinters = c.LLVMInitializeAllAsmPrinters;
@ -81,14 +87,24 @@ pub const MDStringInContext = c.LLVMMDStringInContext;
pub const MetadataTypeInContext = c.LLVMMetadataTypeInContext;
pub const ModuleCreateWithNameInContext = c.LLVMModuleCreateWithNameInContext;
pub const PPCFP128TypeInContext = c.LLVMPPCFP128TypeInContext;
pub const PointerType = c.LLVMPointerType;
pub const SetAlignment = c.LLVMSetAlignment;
pub const SetDataLayout = c.LLVMSetDataLayout;
pub const SetGlobalConstant = c.LLVMSetGlobalConstant;
pub const SetInitializer = c.LLVMSetInitializer;
pub const SetLinkage = c.LLVMSetLinkage;
pub const SetTarget = c.LLVMSetTarget;
pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr;
pub const StructTypeInContext = c.LLVMStructTypeInContext;
pub const TokenTypeInContext = c.LLVMTokenTypeInContext;
pub const TypeOf = c.LLVMTypeOf;
pub const VoidTypeInContext = c.LLVMVoidTypeInContext;
pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext;
pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext;
pub const ConstInBoundsGEP = LLVMConstInBoundsGEP;
pub extern fn LLVMConstInBoundsGEP(ConstantVal: ValueRef, ConstantIndices: [*]ValueRef, NumIndices: c_uint) ?ValueRef;
pub const GetTargetFromTriple = LLVMGetTargetFromTriple;
extern fn LLVMGetTargetFromTriple(Triple: [*]const u8, T: *TargetRef, ErrorMessage: ?*[*]u8) Bool;
@ -145,13 +161,28 @@ pub const EmitBinary = EmitOutputType.ZigLLVM_EmitBinary;
pub const EmitLLVMIr = EmitOutputType.ZigLLVM_EmitLLVMIr;
pub const EmitOutputType = c.ZigLLVM_EmitOutputType;
pub const CCallConv = c.LLVMCCallConv;
pub const FastCallConv = c.LLVMFastCallConv;
pub const ColdCallConv = c.LLVMColdCallConv;
pub const WebKitJSCallConv = c.LLVMWebKitJSCallConv;
pub const AnyRegCallConv = c.LLVMAnyRegCallConv;
pub const X86StdcallCallConv = c.LLVMX86StdcallCallConv;
pub const X86FastcallCallConv = c.LLVMX86FastcallCallConv;
pub const CallConv = c.LLVMCallConv;
pub const FnInline = extern enum {
Auto,
Always,
Never,
};
fn removeNullability(comptime T: type) type {
comptime assert(@typeId(T) == builtin.TypeId.Optional);
return T.Child;
}
pub const BuildRet = LLVMBuildRet;
extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ValueRef;
extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ?ValueRef;
pub const TargetMachineEmitToFile = ZigLLVMTargetMachineEmitToFile;
extern fn ZigLLVMTargetMachineEmitToFile(
@ -163,3 +194,8 @@ extern fn ZigLLVMTargetMachineEmitToFile(
is_debug: bool,
is_small: bool,
) bool;
pub const BuildCall = ZigLLVMBuildCall;
extern fn ZigLLVMBuildCall(B: BuilderRef, Fn: ValueRef, Args: [*]ValueRef, NumArgs: c_uint, CC: c_uint, fn_inline: FnInline, Name: [*]const u8) ?ValueRef;
pub const PrivateLinkage = c.LLVMLinkage.LLVMPrivateLinkage;

View File

@ -9,6 +9,7 @@ const Value = @import("value.zig").Value;
const ir = @import("ir.zig");
const Span = @import("errmsg.zig").Span;
const assert = std.debug.assert;
const event = std.event;
pub const Scope = struct {
id: Id,
@ -123,7 +124,15 @@ pub const Scope = struct {
pub const Decls = struct {
base: Scope,
table: Decl.Table,
/// The lock must be respected for writing. However once name_future resolves,
/// readers can freely access it.
table: event.Locked(Decl.Table),
/// Once this future is resolved, the table is complete and available for unlocked
/// read-only access. It does not mean all the decls are resolved; it means only that
/// the table has all the names. Each decl in the table has its own resolution state.
name_future: event.Future(void),
/// Creates a Decls scope with 1 reference
pub fn create(comp: *Compilation, parent: *Scope) !*Decls {
@ -133,15 +142,10 @@ pub const Scope = struct {
.parent = parent,
.ref_count = 1,
},
.table = undefined,
.table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())),
.name_future = event.Future(void).init(comp.loop),
});
errdefer comp.gpa().destroy(self);
self.table = Decl.Table.init(comp.gpa());
errdefer self.table.deinit();
parent.ref();
return self;
}
@ -149,6 +153,11 @@ pub const Scope = struct {
self.table.deinit();
comp.gpa().destroy(self);
}
pub async fn getTableReadOnly(self: *Decls) *Decl.Table {
_ = await (async self.name_future.get() catch unreachable);
return &self.table.private_data;
}
};
pub const Block = struct {

View File

@ -14,6 +14,7 @@ test "compile errors" {
defer ctx.deinit();
try @import("../test/stage2/compile_errors.zig").addCases(&ctx);
//try @import("../test/stage2/compare_output.zig").addCases(&ctx);
try ctx.run();
}

View File

@ -4,12 +4,17 @@ const Scope = @import("scope.zig").Scope;
const Compilation = @import("compilation.zig").Compilation;
const Value = @import("value.zig").Value;
const llvm = @import("llvm.zig");
const ObjectFile = @import("codegen.zig").ObjectFile;
const event = std.event;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
pub const Type = struct {
base: Value,
id: Id,
name: []const u8,
abi_alignment: AbiAlignment,
pub const AbiAlignment = event.Future(error{OutOfMemory}!u32);
pub const Id = builtin.TypeId;
@ -43,33 +48,37 @@ pub const Type = struct {
}
}
pub fn getLlvmType(base: *Type, ofile: *ObjectFile) (error{OutOfMemory}!llvm.TypeRef) {
pub fn getLlvmType(
base: *Type,
allocator: *Allocator,
llvm_context: llvm.ContextRef,
) (error{OutOfMemory}!llvm.TypeRef) {
switch (base.id) {
Id.Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(ofile),
Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(ofile),
Id.Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(allocator, llvm_context),
Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(allocator, llvm_context),
Id.Type => unreachable,
Id.Void => unreachable,
Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(ofile),
Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(allocator, llvm_context),
Id.NoReturn => unreachable,
Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmType(ofile),
Id.Float => return @fieldParentPtr(Float, "base", base).getLlvmType(ofile),
Id.Pointer => return @fieldParentPtr(Pointer, "base", base).getLlvmType(ofile),
Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmType(ofile),
Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmType(allocator, llvm_context),
Id.Float => return @fieldParentPtr(Float, "base", base).getLlvmType(allocator, llvm_context),
Id.Pointer => return @fieldParentPtr(Pointer, "base", base).getLlvmType(allocator, llvm_context),
Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmType(allocator, llvm_context),
Id.ComptimeFloat => unreachable,
Id.ComptimeInt => unreachable,
Id.Undefined => unreachable,
Id.Null => unreachable,
Id.Optional => return @fieldParentPtr(Optional, "base", base).getLlvmType(ofile),
Id.ErrorUnion => return @fieldParentPtr(ErrorUnion, "base", base).getLlvmType(ofile),
Id.ErrorSet => return @fieldParentPtr(ErrorSet, "base", base).getLlvmType(ofile),
Id.Enum => return @fieldParentPtr(Enum, "base", base).getLlvmType(ofile),
Id.Union => return @fieldParentPtr(Union, "base", base).getLlvmType(ofile),
Id.Optional => return @fieldParentPtr(Optional, "base", base).getLlvmType(allocator, llvm_context),
Id.ErrorUnion => return @fieldParentPtr(ErrorUnion, "base", base).getLlvmType(allocator, llvm_context),
Id.ErrorSet => return @fieldParentPtr(ErrorSet, "base", base).getLlvmType(allocator, llvm_context),
Id.Enum => return @fieldParentPtr(Enum, "base", base).getLlvmType(allocator, llvm_context),
Id.Union => return @fieldParentPtr(Union, "base", base).getLlvmType(allocator, llvm_context),
Id.Namespace => unreachable,
Id.Block => unreachable,
Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(ofile),
Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(allocator, llvm_context),
Id.ArgTuple => unreachable,
Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(ofile),
Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(ofile),
Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(allocator, llvm_context),
Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(allocator, llvm_context),
}
}
@ -156,16 +165,45 @@ pub const Type = struct {
base.* = Type{
.base = Value{
.id = Value.Id.Type,
.typeof = &MetaType.get(comp).base,
.typ = &MetaType.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = id,
.name = name,
.abi_alignment = AbiAlignment.init(comp.loop),
};
}
pub fn getAbiAlignment(base: *Type, comp: *Compilation) u32 {
@panic("TODO getAbiAlignment");
/// If you happen to have an llvm context handy, use getAbiAlignmentInContext instead.
/// Otherwise, this one will grab one from the pool and then release it.
pub async fn getAbiAlignment(base: *Type, comp: *Compilation) !u32 {
if (await (async base.abi_alignment.start() catch unreachable)) |ptr| return ptr.*;
{
const held = try comp.event_loop_local.getAnyLlvmContext();
defer held.release(comp.event_loop_local);
const llvm_context = held.node.data;
base.abi_alignment.data = await (async base.resolveAbiAlignment(comp, llvm_context) catch unreachable);
}
base.abi_alignment.resolve();
return base.abi_alignment.data;
}
/// If you have an llvm conext handy, you can use it here.
pub async fn getAbiAlignmentInContext(base: *Type, comp: *Compilation, llvm_context: llvm.ContextRef) !u32 {
if (await (async base.abi_alignment.start() catch unreachable)) |ptr| return ptr.*;
base.abi_alignment.data = await (async base.resolveAbiAlignment(comp, llvm_context) catch unreachable);
base.abi_alignment.resolve();
return base.abi_alignment.data;
}
/// Lower level function that does the work. See getAbiAlignment.
async fn resolveAbiAlignment(base: *Type, comp: *Compilation, llvm_context: llvm.ContextRef) !u32 {
const llvm_type = try base.getLlvmType(comp.gpa(), llvm_context);
return @intCast(u32, llvm.ABIAlignmentOfType(comp.target_data_ref, llvm_type));
}
pub const Struct = struct {
@ -176,7 +214,7 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Struct, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *Struct, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@ -189,7 +227,7 @@ pub const Type = struct {
pub const Param = struct {
is_noalias: bool,
typeof: *Type,
typ: *Type,
};
pub fn create(comp: *Compilation, return_type: *Type, params: []Param, is_var_args: bool) !*Fn {
@ -205,7 +243,7 @@ pub const Type = struct {
result.return_type.base.ref();
for (result.params) |param| {
param.typeof.base.ref();
param.typ.base.ref();
}
return result;
}
@ -213,20 +251,20 @@ pub const Type = struct {
pub fn destroy(self: *Fn, comp: *Compilation) void {
self.return_type.base.deref(comp);
for (self.params) |param| {
param.typeof.base.deref(comp);
param.typ.base.deref(comp);
}
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Fn, ofile: *ObjectFile) !llvm.TypeRef {
pub fn getLlvmType(self: *Fn, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef {
const llvm_return_type = switch (self.return_type.id) {
Type.Id.Void => llvm.VoidTypeInContext(ofile.context) orelse return error.OutOfMemory,
else => try self.return_type.getLlvmType(ofile),
Type.Id.Void => llvm.VoidTypeInContext(llvm_context) orelse return error.OutOfMemory,
else => try self.return_type.getLlvmType(allocator, llvm_context),
};
const llvm_param_types = try ofile.gpa().alloc(llvm.TypeRef, self.params.len);
defer ofile.gpa().free(llvm_param_types);
const llvm_param_types = try allocator.alloc(llvm.TypeRef, self.params.len);
defer allocator.free(llvm_param_types);
for (llvm_param_types) |*llvm_param_type, i| {
llvm_param_type.* = try self.params[i].typeof.getLlvmType(ofile);
llvm_param_type.* = try self.params[i].typ.getLlvmType(allocator, llvm_context);
}
return llvm.FunctionType(
@ -280,7 +318,7 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Bool, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *Bool, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@ -318,6 +356,11 @@ pub const Type = struct {
}
};
pub fn get_u8(comp: *Compilation) *Int {
comp.u8_type.base.base.ref();
return comp.u8_type;
}
pub async fn get(comp: *Compilation, key: Key) !*Int {
{
const held = await (async comp.int_type_table.acquire() catch unreachable);
@ -371,8 +414,8 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Int, ofile: *ObjectFile) !llvm.TypeRef {
return llvm.IntTypeInContext(ofile.context, self.key.bit_count) orelse return error.OutOfMemory;
pub fn getLlvmType(self: *Int, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef {
return llvm.IntTypeInContext(llvm_context, self.key.bit_count) orelse return error.OutOfMemory;
}
};
@ -383,56 +426,236 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Float, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *Float, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
pub const Pointer = struct {
base: Type,
mut: Mut,
vol: Vol,
size: Size,
alignment: u32,
key: Key,
garbage_node: std.atomic.Stack(*Pointer).Node,
pub const Key = struct {
child_type: *Type,
mut: Mut,
vol: Vol,
size: Size,
alignment: Align,
pub fn hash(self: *const Key) u32 {
const align_hash = switch (self.alignment) {
Align.Abi => 0xf201c090,
Align.Override => |x| x,
};
return hash_usize(@ptrToInt(self.child_type)) *%
hash_enum(self.mut) *%
hash_enum(self.vol) *%
hash_enum(self.size) *%
align_hash;
}
pub fn eql(self: *const Key, other: *const Key) bool {
if (self.child_type != other.child_type or
self.mut != other.mut or
self.vol != other.vol or
self.size != other.size or
@TagType(Align)(self.alignment) != @TagType(Align)(other.alignment))
{
return false;
}
switch (self.alignment) {
Align.Abi => return true,
Align.Override => |x| return x == other.alignment.Override,
}
}
};
pub const Mut = enum {
Mut,
Const,
};
pub const Vol = enum {
Non,
Volatile,
};
pub const Align = union(enum) {
Abi,
Override: u32,
};
pub const Size = builtin.TypeInfo.Pointer.Size;
pub fn destroy(self: *Pointer, comp: *Compilation) void {
self.garbage_node = std.atomic.Stack(*Pointer).Node{
.data = self,
.next = undefined,
};
comp.registerGarbage(Pointer, &self.garbage_node);
}
pub async fn gcDestroy(self: *Pointer, comp: *Compilation) void {
{
const held = await (async comp.ptr_type_table.acquire() catch unreachable);
defer held.release();
_ = held.value.remove(&self.key).?;
}
self.key.child_type.base.deref(comp);
comp.gpa().destroy(self);
}
pub fn get(
comp: *Compilation,
elem_type: *Type,
mut: Mut,
vol: Vol,
size: Size,
alignment: u32,
) *Pointer {
@panic("TODO get pointer");
pub async fn getAlignAsInt(self: *Pointer, comp: *Compilation) u32 {
switch (self.key.alignment) {
Align.Abi => return await (async self.key.child_type.getAbiAlignment(comp) catch unreachable),
Align.Override => |alignment| return alignment,
}
}
pub fn getLlvmType(self: *Pointer, ofile: *ObjectFile) llvm.TypeRef {
@panic("TODO");
pub async fn get(
comp: *Compilation,
key: Key,
) !*Pointer {
var normal_key = key;
switch (key.alignment) {
Align.Abi => {},
Align.Override => |alignment| {
const abi_align = try await (async key.child_type.getAbiAlignment(comp) catch unreachable);
if (abi_align == alignment) {
normal_key.alignment = Align.Abi;
}
},
}
{
const held = await (async comp.ptr_type_table.acquire() catch unreachable);
defer held.release();
if (held.value.get(&normal_key)) |entry| {
entry.value.base.base.ref();
return entry.value;
}
}
const self = try comp.gpa().create(Pointer{
.base = undefined,
.key = normal_key,
.garbage_node = undefined,
});
errdefer comp.gpa().destroy(self);
const size_str = switch (self.key.size) {
Size.One => "*",
Size.Many => "[*]",
Size.Slice => "[]",
};
const mut_str = switch (self.key.mut) {
Mut.Const => "const ",
Mut.Mut => "",
};
const vol_str = switch (self.key.vol) {
Vol.Volatile => "volatile ",
Vol.Non => "",
};
const name = switch (self.key.alignment) {
Align.Abi => try std.fmt.allocPrint(
comp.gpa(),
"{}{}{}{}",
size_str,
mut_str,
vol_str,
self.key.child_type.name,
),
Align.Override => |alignment| try std.fmt.allocPrint(
comp.gpa(),
"{}align<{}> {}{}{}",
size_str,
alignment,
mut_str,
vol_str,
self.key.child_type.name,
),
};
errdefer comp.gpa().free(name);
self.base.init(comp, Id.Pointer, name);
{
const held = await (async comp.ptr_type_table.acquire() catch unreachable);
defer held.release();
_ = try held.value.put(&self.key, self);
}
return self;
}
pub fn getLlvmType(self: *Pointer, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef {
const elem_llvm_type = try self.key.child_type.getLlvmType(allocator, llvm_context);
return llvm.PointerType(elem_llvm_type, 0) orelse return error.OutOfMemory;
}
};
pub const Array = struct {
base: Type,
key: Key,
garbage_node: std.atomic.Stack(*Array).Node,
pub const Key = struct {
elem_type: *Type,
len: usize,
pub fn hash(self: *const Key) u32 {
return hash_usize(@ptrToInt(self.elem_type)) *% hash_usize(self.len);
}
pub fn eql(self: *const Key, other: *const Key) bool {
return self.elem_type == other.elem_type and self.len == other.len;
}
};
pub fn destroy(self: *Array, comp: *Compilation) void {
self.key.elem_type.base.deref(comp);
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Array, ofile: *ObjectFile) llvm.TypeRef {
@panic("TODO");
pub async fn get(comp: *Compilation, key: Key) !*Array {
key.elem_type.base.ref();
errdefer key.elem_type.base.deref(comp);
{
const held = await (async comp.array_type_table.acquire() catch unreachable);
defer held.release();
if (held.value.get(&key)) |entry| {
entry.value.base.base.ref();
return entry.value;
}
}
const self = try comp.gpa().create(Array{
.base = undefined,
.key = key,
.garbage_node = undefined,
});
errdefer comp.gpa().destroy(self);
const name = try std.fmt.allocPrint(comp.gpa(), "[{}]{}", key.len, key.elem_type.name);
errdefer comp.gpa().free(name);
self.base.init(comp, Id.Array, name);
{
const held = await (async comp.array_type_table.acquire() catch unreachable);
defer held.release();
_ = try held.value.put(&self.key, self);
}
return self;
}
pub fn getLlvmType(self: *Array, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef {
const elem_llvm_type = try self.key.elem_type.getLlvmType(allocator, llvm_context);
return llvm.ArrayType(elem_llvm_type, @intCast(c_uint, self.key.len)) orelse return error.OutOfMemory;
}
};
@ -481,7 +704,7 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Optional, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *Optional, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@ -493,7 +716,7 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *ErrorUnion, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *ErrorUnion, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@ -505,7 +728,7 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *ErrorSet, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *ErrorSet, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@ -517,7 +740,7 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Enum, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *Enum, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@ -529,7 +752,7 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Union, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *Union, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@ -557,7 +780,7 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *BoundFn, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *BoundFn, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@ -577,7 +800,7 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Opaque, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *Opaque, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@ -589,8 +812,33 @@ pub const Type = struct {
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Promise, ofile: *ObjectFile) llvm.TypeRef {
pub fn getLlvmType(self: *Promise, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
};
fn hash_usize(x: usize) u32 {
return switch (@sizeOf(usize)) {
4 => x,
8 => @truncate(u32, x *% 0xad44ee2d8e3fc13d),
else => @compileError("implement this hash function"),
};
}
fn hash_enum(x: var) u32 {
const rands = []u32{
0x85ebf64f,
0x3fcb3211,
0x240a4e8e,
0x40bb0e3c,
0x78be45af,
0x1ca98e37,
0xec56053a,
0x906adc48,
0xd4fe9763,
0x54c80dac,
};
comptime assert(@memberCount(@typeOf(x)) < rands.len);
return rands[@enumToInt(x)];
}

View File

@ -11,7 +11,7 @@ const assert = std.debug.assert;
/// If there is only 1 ref then write need not copy
pub const Value = struct {
id: Id,
typeof: *Type,
typ: *Type,
ref_count: std.atomic.Int(usize),
/// Thread-safe
@ -22,23 +22,25 @@ pub const Value = struct {
/// Thread-safe
pub fn deref(base: *Value, comp: *Compilation) void {
if (base.ref_count.decr() == 1) {
base.typeof.base.deref(comp);
base.typ.base.deref(comp);
switch (base.id) {
Id.Type => @fieldParentPtr(Type, "base", base).destroy(comp),
Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(comp),
Id.FnProto => @fieldParentPtr(FnProto, "base", base).destroy(comp),
Id.Void => @fieldParentPtr(Void, "base", base).destroy(comp),
Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(comp),
Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(comp),
Id.Ptr => @fieldParentPtr(Ptr, "base", base).destroy(comp),
Id.Int => @fieldParentPtr(Int, "base", base).destroy(comp),
Id.Array => @fieldParentPtr(Array, "base", base).destroy(comp),
}
}
}
pub fn setType(base: *Value, new_type: *Type, comp: *Compilation) void {
base.typeof.base.deref(comp);
base.typ.base.deref(comp);
new_type.base.ref();
base.typeof = new_type;
base.typ = new_type;
}
pub fn getRef(base: *Value) *Value {
@ -59,11 +61,13 @@ pub const Value = struct {
switch (base.id) {
Id.Type => unreachable,
Id.Fn => @panic("TODO"),
Id.FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile),
Id.Void => return null,
Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile),
Id.NoReturn => unreachable,
Id.Ptr => @panic("TODO"),
Id.Ptr => return @fieldParentPtr(Ptr, "base", base).getLlvmConst(ofile),
Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmConst(ofile),
Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmConst(ofile),
}
}
@ -81,26 +85,87 @@ pub const Value = struct {
switch (base.id) {
Id.Type => unreachable,
Id.Fn => unreachable,
Id.FnProto => unreachable,
Id.Void => unreachable,
Id.Bool => unreachable,
Id.NoReturn => unreachable,
Id.Ptr => unreachable,
Id.Array => unreachable,
Id.Int => return &(try @fieldParentPtr(Int, "base", base).copy(comp)).base,
}
}
pub const Parent = union(enum) {
None,
BaseStruct: BaseStruct,
BaseArray: BaseArray,
BaseUnion: *Value,
BaseScalar: *Value,
pub const BaseStruct = struct {
val: *Value,
field_index: usize,
};
pub const BaseArray = struct {
val: *Value,
elem_index: usize,
};
};
pub const Id = enum {
Type,
Fn,
Void,
Bool,
NoReturn,
Array,
Ptr,
Int,
FnProto,
};
pub const Type = @import("type.zig").Type;
pub const FnProto = struct {
base: Value,
/// The main external name that is used in the .o file.
/// TODO https://github.com/ziglang/zig/issues/265
symbol_name: Buffer,
pub fn create(comp: *Compilation, fn_type: *Type.Fn, symbol_name: Buffer) !*FnProto {
const self = try comp.gpa().create(FnProto{
.base = Value{
.id = Value.Id.FnProto,
.typ = &fn_type.base,
.ref_count = std.atomic.Int(usize).init(1),
},
.symbol_name = symbol_name,
});
fn_type.base.base.ref();
return self;
}
pub fn destroy(self: *FnProto, comp: *Compilation) void {
self.symbol_name.deinit();
comp.gpa().destroy(self);
}
pub fn getLlvmConst(self: *FnProto, ofile: *ObjectFile) !?llvm.ValueRef {
const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
const llvm_fn = llvm.AddFunction(
ofile.module,
self.symbol_name.ptr(),
llvm_fn_type,
) orelse return error.OutOfMemory;
// TODO port more logic from codegen.cpp:fn_llvm_value
return llvm_fn;
}
};
pub const Fn = struct {
base: Value,
@ -135,7 +200,7 @@ pub const Value = struct {
const self = try comp.gpa().create(Fn{
.base = Value{
.id = Value.Id.Fn,
.typeof = &fn_type.base,
.typ = &fn_type.base,
.ref_count = std.atomic.Int(usize).init(1),
},
.fndef_scope = fndef_scope,
@ -224,6 +289,8 @@ pub const Value = struct {
pub const Ptr = struct {
base: Value,
special: Special,
mut: Mut,
pub const Mut = enum {
CompTimeConst,
@ -231,25 +298,210 @@ pub const Value = struct {
RunTime,
};
pub const Special = union(enum) {
Scalar: *Value,
BaseArray: BaseArray,
BaseStruct: BaseStruct,
HardCodedAddr: u64,
Discard,
};
pub const BaseArray = struct {
val: *Value,
elem_index: usize,
};
pub const BaseStruct = struct {
val: *Value,
field_index: usize,
};
pub async fn createArrayElemPtr(
comp: *Compilation,
array_val: *Array,
mut: Type.Pointer.Mut,
size: Type.Pointer.Size,
elem_index: usize,
) !*Ptr {
array_val.base.ref();
errdefer array_val.base.deref(comp);
const elem_type = array_val.base.typ.cast(Type.Array).?.key.elem_type;
const ptr_type = try await (async Type.Pointer.get(comp, Type.Pointer.Key{
.child_type = elem_type,
.mut = mut,
.vol = Type.Pointer.Vol.Non,
.size = size,
.alignment = Type.Pointer.Align.Abi,
}) catch unreachable);
var ptr_type_consumed = false;
errdefer if (!ptr_type_consumed) ptr_type.base.base.deref(comp);
const self = try comp.gpa().create(Value.Ptr{
.base = Value{
.id = Value.Id.Ptr,
.typ = &ptr_type.base,
.ref_count = std.atomic.Int(usize).init(1),
},
.special = Special{
.BaseArray = BaseArray{
.val = &array_val.base,
.elem_index = 0,
},
},
.mut = Mut.CompTimeConst,
});
ptr_type_consumed = true;
errdefer comp.gpa().destroy(self);
return self;
}
pub fn destroy(self: *Ptr, comp: *Compilation) void {
comp.gpa().destroy(self);
}
pub fn getLlvmConst(self: *Ptr, ofile: *ObjectFile) !?llvm.ValueRef {
const llvm_type = self.base.typ.getLlvmType(ofile.arena, ofile.context);
// TODO carefully port the logic from codegen.cpp:gen_const_val_ptr
switch (self.special) {
Special.Scalar => |scalar| @panic("TODO"),
Special.BaseArray => |base_array| {
// TODO put this in one .o file only, and after that, generate extern references to it
const array_llvm_value = (try base_array.val.getLlvmConst(ofile)).?;
const ptr_bit_count = ofile.comp.target_ptr_bits;
const usize_llvm_type = llvm.IntTypeInContext(ofile.context, ptr_bit_count) orelse return error.OutOfMemory;
const indices = []llvm.ValueRef{
llvm.ConstNull(usize_llvm_type) orelse return error.OutOfMemory,
llvm.ConstInt(usize_llvm_type, base_array.elem_index, 0) orelse return error.OutOfMemory,
};
return llvm.ConstInBoundsGEP(
array_llvm_value,
&indices,
@intCast(c_uint, indices.len),
) orelse return error.OutOfMemory;
},
Special.BaseStruct => |base_struct| @panic("TODO"),
Special.HardCodedAddr => |addr| @panic("TODO"),
Special.Discard => unreachable,
}
}
};
pub const Array = struct {
base: Value,
special: Special,
pub const Special = union(enum) {
Undefined,
OwnedBuffer: []u8,
Explicit: Data,
};
pub const Data = struct {
parent: Parent,
elements: []*Value,
};
/// Takes ownership of buffer
pub async fn createOwnedBuffer(comp: *Compilation, buffer: []u8) !*Array {
const u8_type = Type.Int.get_u8(comp);
defer u8_type.base.base.deref(comp);
const array_type = try await (async Type.Array.get(comp, Type.Array.Key{
.elem_type = &u8_type.base,
.len = buffer.len,
}) catch unreachable);
errdefer array_type.base.base.deref(comp);
const self = try comp.gpa().create(Value.Array{
.base = Value{
.id = Value.Id.Array,
.typ = &array_type.base,
.ref_count = std.atomic.Int(usize).init(1),
},
.special = Special{ .OwnedBuffer = buffer },
});
errdefer comp.gpa().destroy(self);
return self;
}
pub fn destroy(self: *Array, comp: *Compilation) void {
switch (self.special) {
Special.Undefined => {},
Special.OwnedBuffer => |buf| {
comp.gpa().free(buf);
},
Special.Explicit => {},
}
comp.gpa().destroy(self);
}
pub fn getLlvmConst(self: *Array, ofile: *ObjectFile) !?llvm.ValueRef {
switch (self.special) {
Special.Undefined => {
const llvm_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
return llvm.GetUndef(llvm_type);
},
Special.OwnedBuffer => |buf| {
const dont_null_terminate = 1;
const llvm_str_init = llvm.ConstStringInContext(
ofile.context,
buf.ptr,
@intCast(c_uint, buf.len),
dont_null_terminate,
) orelse return error.OutOfMemory;
const str_init_type = llvm.TypeOf(llvm_str_init);
const global = llvm.AddGlobal(ofile.module, str_init_type, c"") orelse return error.OutOfMemory;
llvm.SetInitializer(global, llvm_str_init);
llvm.SetLinkage(global, llvm.PrivateLinkage);
llvm.SetGlobalConstant(global, 1);
llvm.SetUnnamedAddr(global, 1);
llvm.SetAlignment(global, llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, str_init_type));
return global;
},
Special.Explicit => @panic("TODO"),
}
//{
// uint64_t len = type_entry->data.array.len;
// if (const_val->data.x_array.special == ConstArraySpecialUndef) {
// return LLVMGetUndef(type_entry->type_ref);
// }
// LLVMValueRef *values = allocate<LLVMValueRef>(len);
// LLVMTypeRef element_type_ref = type_entry->data.array.child_type->type_ref;
// bool make_unnamed_struct = false;
// for (uint64_t i = 0; i < len; i += 1) {
// ConstExprValue *elem_value = &const_val->data.x_array.s_none.elements[i];
// LLVMValueRef val = gen_const_val(g, elem_value, "");
// values[i] = val;
// make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(elem_value->type, val);
// }
// if (make_unnamed_struct) {
// return LLVMConstStruct(values, len, true);
// } else {
// return LLVMConstArray(element_type_ref, values, (unsigned)len);
// }
//}
}
};
pub const Int = struct {
base: Value,
big_int: std.math.big.Int,
pub fn createFromString(comp: *Compilation, typeof: *Type, base: u8, value: []const u8) !*Int {
pub fn createFromString(comp: *Compilation, typ: *Type, base: u8, value: []const u8) !*Int {
const self = try comp.gpa().create(Value.Int{
.base = Value{
.id = Value.Id.Int,
.typeof = typeof,
.typ = typ,
.ref_count = std.atomic.Int(usize).init(1),
},
.big_int = undefined,
});
typeof.base.ref();
typ.base.ref();
errdefer comp.gpa().destroy(self);
self.big_int = try std.math.big.Int.init(comp.gpa());
@ -261,9 +513,9 @@ pub const Value = struct {
}
pub fn getLlvmConst(self: *Int, ofile: *ObjectFile) !?llvm.ValueRef {
switch (self.base.typeof.id) {
switch (self.base.typ.id) {
Type.Id.Int => {
const type_ref = try self.base.typeof.getLlvmType(ofile);
const type_ref = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
if (self.big_int.len == 0) {
return llvm.ConstNull(type_ref);
}
@ -286,13 +538,13 @@ pub const Value = struct {
}
pub fn copy(old: *Int, comp: *Compilation) !*Int {
old.base.typeof.base.ref();
errdefer old.base.typeof.base.deref(comp);
old.base.typ.base.ref();
errdefer old.base.typ.base.deref(comp);
const new = try comp.gpa().create(Value.Int{
.base = Value{
.id = Value.Id.Int,
.typeof = old.base.typeof,
.typ = old.base.typ,
.ref_count = std.atomic.Int(usize).init(1),
},
.big_int = undefined,

View File

@ -76,6 +76,7 @@ pub fn Group(comptime ReturnType: type) type {
/// Wait for all the calls and promises of the group to complete.
/// Thread-safe.
/// Safe to call any number of times.
pub async fn wait(self: *Self) ReturnType {
// TODO catch unreachable because the allocation can be grouped with
// the coro frame allocation

View File

@ -2,6 +2,7 @@ const tokenizer = @import("tokenizer.zig");
pub const Token = tokenizer.Token;
pub const Tokenizer = tokenizer.Tokenizer;
pub const parse = @import("parse.zig").parse;
pub const parseStringLiteral = @import("parse_string_literal.zig").parseStringLiteral;
pub const render = @import("render.zig").render;
pub const ast = @import("ast.zig");
@ -10,4 +11,6 @@ test "std.zig tests" {
_ = @import("parse.zig");
_ = @import("render.zig");
_ = @import("tokenizer.zig");
_ = @import("parse_string_literal.zig");
}

View File

@ -0,0 +1,76 @@
const std = @import("../index.zig");
const assert = std.debug.assert;
const State = enum {
Start,
Backslash,
};
pub const ParseStringLiteralError = error{
OutOfMemory,
/// When this is returned, index will be the position of the character.
InvalidCharacter,
};
/// caller owns returned memory
pub fn parseStringLiteral(
allocator: *std.mem.Allocator,
bytes: []const u8,
bad_index: *usize, // populated if error.InvalidCharacter is returned
) ParseStringLiteralError![]u8 {
const first_index = if (bytes[0] == 'c') usize(2) else usize(1);
assert(bytes[bytes.len - 1] == '"');
var list = std.ArrayList(u8).init(allocator);
errdefer list.deinit();
const slice = bytes[first_index..];
try list.ensureCapacity(slice.len - 1);
var state = State.Start;
for (slice) |b, index| {
switch (state) {
State.Start => switch (b) {
'\\' => state = State.Backslash,
'\n' => {
bad_index.* = index;
return error.InvalidCharacter;
},
'"' => return list.toOwnedSlice(),
else => try list.append(b),
},
State.Backslash => switch (b) {
'x' => @panic("TODO"),
'u' => @panic("TODO"),
'U' => @panic("TODO"),
'n' => {
try list.append('\n');
state = State.Start;
},
'r' => {
try list.append('\r');
state = State.Start;
},
'\\' => {
try list.append('\\');
state = State.Start;
},
't' => {
try list.append('\t');
state = State.Start;
},
'"' => {
try list.append('"');
state = State.Start;
},
else => {
bad_index.* = index;
return error.InvalidCharacter;
},
},
else => unreachable,
}
}
unreachable;
}

View File

@ -73,6 +73,7 @@ pub const Token = struct {
return null;
}
/// TODO remove this enum
const StrLitKind = enum {
Normal,
C,