Commit 2ea08561cf

Andrew Kelley <superjoe30@gmail.com>
2018-07-24 20:20:49
self-hosted: function types use table lookup
1 parent 1d4a94b
Changed files (5)
src/analyze.cpp
@@ -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);
src-self-hosted/codegen.zig
@@ -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| {
src-self-hosted/compilation.zig
@@ -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;
src-self-hosted/ir.zig
@@ -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;
         }
 
src-self-hosted/type.zig
@@ -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{
+        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,
+            };
+        }
+
+        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,
+            };
+        }
+
+        /// 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;
+                }
+            }
+
+            key.ref();
+            errdefer key.deref(comp);
+
+            const self = try comp.gpa().create(Fn{
                 .base = undefined,
-                .return_type = return_type,
-                .params = params,
-                .is_var_args = is_var_args,
+                .key = key,
+                .garbage_node = undefined,
             });
-            errdefer comp.gpa().destroy(result);
+            errdefer comp.gpa().destroy(self);
 
-            result.base.init(comp, Id.Fn, "TODO fn type name");
+            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());
 
-            result.return_type.base.ref();
-            for (result.params) |param| {
-                param.typ.base.ref();
+            {
+                const held = await (async comp.fn_type_table.acquire() catch unreachable);
+                defer held.release();
+
+                _ = try held.value.put(&self.key, self);
             }
-            return result;
+            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))),
+    }
 }