self-hosted: function types use table lookup

This commit is contained in:
Andrew Kelley 2018-07-24 14:20:49 -04:00
parent 1d4a94b635
commit 2ea08561cf
5 changed files with 358 additions and 72 deletions

View File

@ -168,6 +168,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code)
//}
const fn_type = fn_val.base.typ.cast(Type.Fn).?;
const fn_type_normal = &fn_type.key.data.Normal;
try addLLVMFnAttr(ofile, llvm_fn, "nounwind");
//add_uwtable_attr(g, fn_table_entry->llvm_value);
@ -209,7 +210,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code)
// 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;
const cur_ret_ptr = if (fn_type_normal.return_type.handleIsPtr()) llvm.GetParam(llvm_fn, 0) else null;
// build all basic blocks
for (code.basic_block_list.toSlice()) |bb| {

View File

@ -220,12 +220,14 @@ pub const Compilation = struct {
int_type_table: event.Locked(IntTypeTable),
array_type_table: event.Locked(ArrayTypeTable),
ptr_type_table: event.Locked(PtrTypeTable),
fn_type_table: event.Locked(FnTypeTable),
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 FnTypeTable = std.HashMap(*const Type.Fn.Key, *Type.Fn, Type.Fn.Key.hash, Type.Fn.Key.eql);
const TypeTable = std.HashMap([]const u8, *Type, mem.hash_slice_u8, mem.eql_slice_u8);
const CompileErrList = std.ArrayList(*Msg);
@ -384,6 +386,7 @@ pub const Compilation = struct {
.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)),
.fn_type_table = event.Locked(FnTypeTable).init(loop, FnTypeTable.init(loop.allocator)),
.c_int_types = undefined,
.meta_type = undefined,
@ -414,6 +417,7 @@ pub const Compilation = struct {
comp.int_type_table.private_data.deinit();
comp.array_type_table.private_data.deinit();
comp.ptr_type_table.private_data.deinit();
comp.fn_type_table.private_data.deinit();
comp.arena_allocator.deinit();
comp.loop.allocator.destroy(comp);
}
@ -1160,10 +1164,47 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void {
fn_decl.value = Decl.Fn.Val{ .Fn = fn_val };
symbol_name_consumed = true;
// Define local parameter variables
//for (size_t i = 0; i < fn_type_id->param_count; i += 1) {
// FnTypeParamInfo *param_info = &fn_type_id->param_info[i];
// AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i);
// Buf *param_name;
// bool is_var_args = param_decl_node && param_decl_node->data.param_decl.is_var_args;
// if (param_decl_node && !is_var_args) {
// param_name = param_decl_node->data.param_decl.name;
// } else {
// param_name = buf_sprintf("arg%" ZIG_PRI_usize "", i);
// }
// if (param_name == nullptr) {
// continue;
// }
// TypeTableEntry *param_type = param_info->type;
// bool is_noalias = param_info->is_noalias;
// if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) {
// add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter"));
// }
// VariableTableEntry *var = add_variable(g, param_decl_node, fn_table_entry->child_scope,
// param_name, true, create_const_runtime(param_type), nullptr);
// var->src_arg_index = i;
// fn_table_entry->child_scope = var->child_scope;
// var->shadowable = var->shadowable || is_var_args;
// if (type_has_bits(param_type)) {
// fn_table_entry->variable_list.append(var);
// }
// if (fn_type->data.fn.gen_param_info) {
// var->gen_arg_index = fn_type->data.fn.gen_param_info[i].gen_index;
// }
//}
const analyzed_code = try await (async comp.genAndAnalyzeCode(
&fndef_scope.base,
body_node,
fn_type.return_type,
fn_type.key.data.Normal.return_type,
) catch unreachable);
errdefer analyzed_code.destroy(comp.gpa());
@ -1199,14 +1240,13 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn
var params = ArrayList(Type.Fn.Param).init(comp.gpa());
var params_consumed = false;
defer if (params_consumed) {
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| {
@ -1219,8 +1259,29 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn
});
}
}
const fn_type = try Type.Fn.create(comp, return_type, params.toOwnedSlice(), is_var_args);
const key = Type.Fn.Key{
.alignment = null,
.data = Type.Fn.Key.Data{
.Normal = Type.Fn.Normal{
.return_type = return_type,
.params = params.toOwnedSlice(),
.is_var_args = false, // TODO
.cc = Type.Fn.CallingConvention.Auto, // TODO
},
},
};
params_consumed = true;
var key_consumed = false;
defer if (!key_consumed) {
for (key.data.Normal.params) |param| {
param.typ.base.deref(comp);
}
comp.gpa().free(key.data.Normal.params);
};
const fn_type = try await (async Type.Fn.get(comp, key) catch unreachable);
key_consumed = true;
errdefer fn_type.base.base.deref(comp);
return fn_type;

View File

@ -281,11 +281,13 @@ pub const Inst = struct {
return error.SemanticAnalysisFailed;
};
if (fn_type.params.len != self.params.args.len) {
const fn_type_param_count = fn_type.paramCount();
if (fn_type_param_count != self.params.args.len) {
try ira.addCompileError(
self.base.span,
"expected {} arguments, found {}",
fn_type.params.len,
fn_type_param_count,
self.params.args.len,
);
return error.SemanticAnalysisFailed;
@ -299,7 +301,7 @@ pub const Inst = struct {
.fn_ref = fn_ref,
.args = args,
});
new_inst.val = IrVal{ .KnownType = fn_type.return_type };
new_inst.val = IrVal{ .KnownType = fn_type.key.data.Normal.return_type };
return new_inst;
}

View File

@ -221,57 +221,267 @@ pub const Type = struct {
pub const Fn = struct {
base: Type,
return_type: *Type,
params: []Param,
is_var_args: bool,
key: Key,
garbage_node: std.atomic.Stack(*Fn).Node,
pub const Key = struct {
data: Data,
alignment: ?u32,
pub const Data = union(enum) {
Generic: Generic,
Normal: Normal,
};
pub fn hash(self: *const Key) u32 {
var result: u32 = 0;
result +%= hashAny(self.alignment, 0);
switch (self.data) {
Data.Generic => |generic| {
result +%= hashAny(generic.param_count, 1);
switch (generic.cc) {
CallingConvention.Async => |allocator_type| result +%= hashAny(allocator_type, 2),
else => result +%= hashAny(CallingConvention(generic.cc), 3),
}
},
Data.Normal => |normal| {
result +%= hashAny(normal.return_type, 4);
result +%= hashAny(normal.is_var_args, 5);
result +%= hashAny(normal.cc, 6);
for (normal.params) |param| {
result +%= hashAny(param.is_noalias, 7);
result +%= hashAny(param.typ, 8);
}
},
}
return result;
}
pub fn eql(self: *const Key, other: *const Key) bool {
if ((self.alignment == null) != (other.alignment == null)) return false;
if (self.alignment) |self_align| {
if (self_align != other.alignment.?) return false;
}
if (@TagType(Data)(self.data) != @TagType(Data)(other.data)) return false;
switch (self.data) {
Data.Generic => |*self_generic| {
const other_generic = &other.data.Generic;
if (self_generic.param_count != other_generic.param_count) return false;
if (CallingConvention(self_generic.cc) != CallingConvention(other_generic.cc)) return false;
switch (self_generic.cc) {
CallingConvention.Async => |self_allocator_type| {
const other_allocator_type = other_generic.cc.Async;
if (self_allocator_type != other_allocator_type) return false;
},
else => {},
}
},
Data.Normal => |*self_normal| {
const other_normal = &other.data.Normal;
if (self_normal.cc != other_normal.cc) return false;
if (self_normal.is_var_args != other_normal.is_var_args) return false;
if (self_normal.return_type != other_normal.return_type) return false;
for (self_normal.params) |*self_param, i| {
const other_param = &other_normal.params[i];
if (self_param.is_noalias != other_param.is_noalias) return false;
if (self_param.typ != other_param.typ) return false;
}
},
}
return true;
}
pub fn deref(key: Key, comp: *Compilation) void {
switch (key.data) {
Key.Data.Generic => |generic| {
switch (generic.cc) {
CallingConvention.Async => |allocator_type| allocator_type.base.deref(comp),
else => {},
}
},
Key.Data.Normal => |normal| {
normal.return_type.base.deref(comp);
for (normal.params) |param| {
param.typ.base.deref(comp);
}
},
}
}
pub fn ref(key: Key) void {
switch (key.data) {
Key.Data.Generic => |generic| {
switch (generic.cc) {
CallingConvention.Async => |allocator_type| allocator_type.base.ref(),
else => {},
}
},
Key.Data.Normal => |normal| {
normal.return_type.base.ref();
for (normal.params) |param| {
param.typ.base.ref();
}
},
}
}
};
pub const Normal = struct {
params: []Param,
return_type: *Type,
is_var_args: bool,
cc: CallingConvention,
};
pub const Generic = struct {
param_count: usize,
cc: CC,
pub const CC = union(CallingConvention) {
Auto,
C,
Cold,
Naked,
Stdcall,
Async: *Type, // allocator type
};
};
pub const CallingConvention = enum {
Auto,
C,
Cold,
Naked,
Stdcall,
Async,
};
pub const Param = struct {
is_noalias: bool,
typ: *Type,
};
pub fn create(comp: *Compilation, return_type: *Type, params: []Param, is_var_args: bool) !*Fn {
const result = try comp.gpa().create(Fn{
.base = undefined,
.return_type = return_type,
.params = params,
.is_var_args = is_var_args,
});
errdefer comp.gpa().destroy(result);
fn ccFnTypeStr(cc: CallingConvention) []const u8 {
return switch (cc) {
CallingConvention.Auto => "",
CallingConvention.C => "extern ",
CallingConvention.Cold => "coldcc ",
CallingConvention.Naked => "nakedcc ",
CallingConvention.Stdcall => "stdcallcc ",
CallingConvention.Async => unreachable,
};
}
result.base.init(comp, Id.Fn, "TODO fn type name");
pub fn paramCount(self: *Fn) usize {
return switch (self.key.data) {
Key.Data.Generic => |generic| generic.param_count,
Key.Data.Normal => |normal| normal.params.len,
};
}
result.return_type.base.ref();
for (result.params) |param| {
param.typ.base.ref();
/// takes ownership of key.Normal.params on success
pub async fn get(comp: *Compilation, key: Key) !*Fn {
{
const held = await (async comp.fn_type_table.acquire() catch unreachable);
defer held.release();
if (held.value.get(&key)) |entry| {
entry.value.base.base.ref();
return entry.value;
}
}
return result;
key.ref();
errdefer key.deref(comp);
const self = try comp.gpa().create(Fn{
.base = undefined,
.key = key,
.garbage_node = undefined,
});
errdefer comp.gpa().destroy(self);
var name_buf = try std.Buffer.initSize(comp.gpa(), 0);
defer name_buf.deinit();
const name_stream = &std.io.BufferOutStream.init(&name_buf).stream;
switch (key.data) {
Key.Data.Generic => |generic| {
switch (generic.cc) {
CallingConvention.Async => |async_allocator_type| {
try name_stream.print("async<{}> ", async_allocator_type.name);
},
else => {
const cc_str = ccFnTypeStr(generic.cc);
try name_stream.write(cc_str);
},
}
try name_stream.write("fn(");
var param_i: usize = 0;
while (param_i < generic.param_count) : (param_i += 1) {
const arg = if (param_i == 0) "var" else ", var";
try name_stream.write(arg);
}
try name_stream.write(")");
if (key.alignment) |alignment| {
try name_stream.print(" align<{}>", alignment);
}
try name_stream.write(" var");
},
Key.Data.Normal => |normal| {
const cc_str = ccFnTypeStr(normal.cc);
try name_stream.print("{}fn(", cc_str);
for (normal.params) |param, i| {
if (i != 0) try name_stream.write(", ");
if (param.is_noalias) try name_stream.write("noalias ");
try name_stream.write(param.typ.name);
}
if (normal.is_var_args) {
if (normal.params.len != 0) try name_stream.write(", ");
try name_stream.write("...");
}
try name_stream.write(")");
if (key.alignment) |alignment| {
try name_stream.print(" align<{}>", alignment);
}
try name_stream.print(" {}", normal.return_type.name);
},
}
self.base.init(comp, Id.Fn, name_buf.toOwnedSlice());
{
const held = await (async comp.fn_type_table.acquire() catch unreachable);
defer held.release();
_ = try held.value.put(&self.key, self);
}
return self;
}
pub fn destroy(self: *Fn, comp: *Compilation) void {
self.return_type.base.deref(comp);
for (self.params) |param| {
param.typ.base.deref(comp);
}
self.key.deref(comp);
comp.gpa().destroy(self);
}
pub fn getLlvmType(self: *Fn, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef {
const llvm_return_type = switch (self.return_type.id) {
const normal = &self.key.data.Normal;
const llvm_return_type = switch (normal.return_type.id) {
Type.Id.Void => llvm.VoidTypeInContext(llvm_context) orelse return error.OutOfMemory,
else => try self.return_type.getLlvmType(allocator, llvm_context),
else => try normal.return_type.getLlvmType(allocator, llvm_context),
};
const llvm_param_types = try allocator.alloc(llvm.TypeRef, self.params.len);
const llvm_param_types = try allocator.alloc(llvm.TypeRef, normal.params.len);
defer allocator.free(llvm_param_types);
for (llvm_param_types) |*llvm_param_type, i| {
llvm_param_type.* = try self.params[i].typ.getLlvmType(allocator, llvm_context);
llvm_param_type.* = try normal.params[i].typ.getLlvmType(allocator, llvm_context);
}
return llvm.FunctionType(
llvm_return_type,
llvm_param_types.ptr,
@intCast(c_uint, llvm_param_types.len),
@boolToInt(self.is_var_args),
@boolToInt(normal.is_var_args),
) orelse error.OutOfMemory;
}
};
@ -347,8 +557,10 @@ pub const Type = struct {
is_signed: bool,
pub fn hash(self: *const Key) u32 {
const rands = [2]u32{ 0xa4ba6498, 0x75fc5af7 };
return rands[@boolToInt(self.is_signed)] *% self.bit_count;
var result: u32 = 0;
result +%= hashAny(self.is_signed, 0);
result +%= hashAny(self.bit_count, 1);
return result;
}
pub fn eql(self: *const Key, other: *const Key) bool {
@ -443,15 +655,16 @@ pub const Type = struct {
alignment: Align,
pub fn hash(self: *const Key) u32 {
const align_hash = switch (self.alignment) {
var result: u32 = 0;
result +%= switch (self.alignment) {
Align.Abi => 0xf201c090,
Align.Override => |x| x,
Align.Override => |x| hashAny(x, 0),
};
return hash_usize(@ptrToInt(self.child_type)) *%
hash_enum(self.mut) *%
hash_enum(self.vol) *%
hash_enum(self.size) *%
align_hash;
result +%= hashAny(self.child_type, 1);
result +%= hashAny(self.mut, 2);
result +%= hashAny(self.vol, 3);
result +%= hashAny(self.size, 4);
return result;
}
pub fn eql(self: *const Key, other: *const Key) bool {
@ -605,7 +818,10 @@ pub const Type = struct {
len: usize,
pub fn hash(self: *const Key) u32 {
return hash_usize(@ptrToInt(self.elem_type)) *% hash_usize(self.len);
var result: u32 = 0;
result +%= hashAny(self.elem_type, 0);
result +%= hashAny(self.len, 1);
return result;
}
pub fn eql(self: *const Key, other: *const Key) bool {
@ -818,27 +1034,37 @@ pub const Type = struct {
};
};
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)];
fn hashAny(x: var, comptime seed: u64) u32 {
switch (@typeInfo(@typeOf(x))) {
builtin.TypeId.Int => |info| {
comptime var rng = comptime std.rand.DefaultPrng.init(seed);
const unsigned_x = @bitCast(@IntType(false, info.bits), x);
if (info.bits <= 32) {
return u32(unsigned_x) *% comptime rng.random.scalar(u32);
} else {
return @truncate(u32, unsigned_x *% comptime rng.random.scalar(@typeOf(unsigned_x)));
}
},
builtin.TypeId.Pointer => |info| {
switch (info.size) {
builtin.TypeInfo.Pointer.Size.One => return hashAny(@ptrToInt(x), seed),
builtin.TypeInfo.Pointer.Size.Many => @compileError("implement hash function"),
builtin.TypeInfo.Pointer.Size.Slice => @compileError("implement hash function"),
}
},
builtin.TypeId.Enum => return hashAny(@enumToInt(x), seed),
builtin.TypeId.Bool => {
comptime var rng = comptime std.rand.DefaultPrng.init(seed);
const vals = comptime [2]u32{ rng.random.scalar(u32), rng.random.scalar(u32) };
return vals[@boolToInt(x)];
},
builtin.TypeId.Optional => {
if (x) |non_opt| {
return hashAny(non_opt, seed);
} else {
return hashAny(u32(1), seed);
}
},
else => @compileError("implement hash function for " ++ @typeName(@typeOf(x))),
}
}

View File

@ -3941,7 +3941,7 @@ AstNode *get_param_decl_node(FnTableEntry *fn_entry, size_t index) {
return nullptr;
}
static void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entry, VariableTableEntry **arg_vars) {
static void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entry) {
TypeTableEntry *fn_type = fn_table_entry->type_entry;
assert(!fn_type->data.fn.is_generic);
FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;
@ -3979,10 +3979,6 @@ static void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entr
if (fn_type->data.fn.gen_param_info) {
var->gen_arg_index = fn_type->data.fn.gen_param_info[i].gen_index;
}
if (arg_vars) {
arg_vars[i] = var;
}
}
}
@ -4082,7 +4078,7 @@ static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) {
if (!fn_table_entry->child_scope)
fn_table_entry->child_scope = &fn_table_entry->fndef_scope->base;
define_local_param_variables(g, fn_table_entry, nullptr);
define_local_param_variables(g, fn_table_entry);
TypeTableEntry *fn_type = fn_table_entry->type_entry;
assert(!fn_type->data.fn.is_generic);