Commit dae4c18aa7

Andrew Kelley <andrew@ziglang.org>
2021-08-02 07:04:18
stage2: ZIR encodes comptime parameters
`func_extended` ZIR instructions now have a one of the unused flags used as a `has_comptime_bits` boolean. When set, it means 1 or more parameters are `comptime`. In this case, there is a u32 per every 32 parameters (usually just 1 u32) with each bit indicating whether the corresponding parameter is `comptime`. Sema uses this information to correctly mark generic functions as generic. There is now a TODO compile error in place in case a generic function call happens. A future commit will do the generic function call implementation.
1 parent d5f173d
src/AstGen.zig
@@ -1064,11 +1064,28 @@ fn fnProtoExpr(
     const param_types = try gpa.alloc(Zir.Inst.Ref, param_count);
     defer gpa.free(param_types);
 
+    const bits_per_param = 1;
+    const params_per_u32 = 32 / bits_per_param;
+    // We only need this if there are greater than params_per_u32 fields.
+    var bit_bag = ArrayListUnmanaged(u32){};
+    defer bit_bag.deinit(gpa);
+    var cur_bit_bag: u32 = 0;
     var is_var_args = false;
     {
         var param_type_i: usize = 0;
         var it = fn_proto.iterate(tree.*);
         while (it.next()) |param| : (param_type_i += 1) {
+            if (param_type_i % params_per_u32 == 0 and param_type_i != 0) {
+                try bit_bag.append(gpa, cur_bit_bag);
+                cur_bit_bag = 0;
+            }
+            const is_comptime = if (param.comptime_noalias) |token|
+                token_tags[token] == .keyword_comptime
+            else
+                false;
+            cur_bit_bag = (cur_bit_bag >> bits_per_param) |
+                (@as(u32, @boolToInt(is_comptime)) << 31);
+
             if (param.anytype_ellipsis3) |token| {
                 switch (token_tags[token]) {
                     .keyword_anytype => {
@@ -1088,6 +1105,11 @@ fn fnProtoExpr(
                 try expr(gz, scope, .{ .ty = .type_type }, param_type_node);
         }
         assert(param_type_i == param_count);
+
+        const empty_slot_count = params_per_u32 - (param_type_i % params_per_u32);
+        if (empty_slot_count < params_per_u32) {
+            cur_bit_bag >>= @intCast(u5, empty_slot_count * bits_per_param);
+        }
     }
 
     const align_inst: Zir.Inst.Ref = if (fn_proto.ast.align_expr == 0) .none else inst: {
@@ -1131,6 +1153,8 @@ fn fnProtoExpr(
         .is_inferred_error = false,
         .is_test = false,
         .is_extern = false,
+        .cur_bit_bag = cur_bit_bag,
+        .bit_bag = bit_bag.items,
     });
     return rvalue(gz, rl, result, fn_proto.ast.proto_node);
 }
@@ -2916,11 +2940,28 @@ fn fnDecl(
     const param_types = try gpa.alloc(Zir.Inst.Ref, param_count);
     defer gpa.free(param_types);
 
+    const bits_per_param = 1;
+    const params_per_u32 = 32 / bits_per_param;
+    // We only need this if there are greater than params_per_u32 fields.
+    var bit_bag = ArrayListUnmanaged(u32){};
+    defer bit_bag.deinit(gpa);
+    var cur_bit_bag: u32 = 0;
     var is_var_args = false;
     {
         var param_type_i: usize = 0;
         var it = fn_proto.iterate(tree.*);
         while (it.next()) |param| : (param_type_i += 1) {
+            if (param_type_i % params_per_u32 == 0 and param_type_i != 0) {
+                try bit_bag.append(gpa, cur_bit_bag);
+                cur_bit_bag = 0;
+            }
+            const is_comptime = if (param.comptime_noalias) |token|
+                token_tags[token] == .keyword_comptime
+            else
+                false;
+            cur_bit_bag = (cur_bit_bag >> bits_per_param) |
+                (@as(u32, @boolToInt(is_comptime)) << 31);
+
             if (param.anytype_ellipsis3) |token| {
                 switch (token_tags[token]) {
                     .keyword_anytype => {
@@ -2940,6 +2981,11 @@ fn fnDecl(
                 try expr(&decl_gz, &decl_gz.base, .{ .ty = .type_type }, param_type_node);
         }
         assert(param_type_i == param_count);
+
+        const empty_slot_count = params_per_u32 - (param_type_i % params_per_u32);
+        if (empty_slot_count < params_per_u32) {
+            cur_bit_bag >>= @intCast(u5, empty_slot_count * bits_per_param);
+        }
     }
 
     const lib_name: u32 = if (fn_proto.lib_name) |lib_name_token| blk: {
@@ -3001,6 +3047,8 @@ fn fnDecl(
             .is_inferred_error = false,
             .is_test = false,
             .is_extern = true,
+            .cur_bit_bag = cur_bit_bag,
+            .bit_bag = bit_bag.items,
         });
     } else func: {
         if (is_var_args) {
@@ -3094,6 +3142,8 @@ fn fnDecl(
             .is_inferred_error = is_inferred_error,
             .is_test = false,
             .is_extern = false,
+            .cur_bit_bag = cur_bit_bag,
+            .bit_bag = bit_bag.items,
         });
     };
 
@@ -3439,6 +3489,8 @@ fn testDecl(
         .is_inferred_error = true,
         .is_test = true,
         .is_extern = false,
+        .cur_bit_bag = 0,
+        .bit_bag = &.{},
     });
 
     _ = try decl_block.addBreak(.break_inline, block_inst, func_inst);
@@ -9135,6 +9187,8 @@ const GenZir = struct {
         is_inferred_error: bool,
         is_test: bool,
         is_extern: bool,
+        cur_bit_bag: u32,
+        bit_bag: []const u32,
     }) !Zir.Inst.Ref {
         assert(args.src_node != 0);
         assert(args.ret_ty != .none);
@@ -9172,13 +9226,18 @@ const GenZir = struct {
             src_locs = &src_locs_buffer;
         }
 
+        const any_are_comptime = args.cur_bit_bag != 0 or for (args.bit_bag) |x| {
+            if (x != 0) break true;
+        } else false;
+
         if (args.cc != .none or args.lib_name != 0 or
             args.is_var_args or args.is_test or args.align_inst != .none or
-            args.is_extern)
+            args.is_extern or any_are_comptime)
         {
             try astgen.extra.ensureUnusedCapacity(
                 gpa,
                 @typeInfo(Zir.Inst.ExtendedFunc).Struct.fields.len +
+                    @boolToInt(any_are_comptime) + args.bit_bag.len +
                     args.param_types.len + args.body.len + src_locs.len +
                     @boolToInt(args.lib_name != 0) +
                     @boolToInt(args.align_inst != .none) +
@@ -9199,6 +9258,10 @@ const GenZir = struct {
             if (args.align_inst != .none) {
                 astgen.extra.appendAssumeCapacity(@enumToInt(args.align_inst));
             }
+            if (any_are_comptime) {
+                astgen.extra.appendSliceAssumeCapacity(args.bit_bag); // Likely empty.
+                astgen.extra.appendAssumeCapacity(args.cur_bit_bag);
+            }
             astgen.appendRefsAssumeCapacity(args.param_types);
             astgen.extra.appendSliceAssumeCapacity(args.body);
             astgen.extra.appendSliceAssumeCapacity(src_locs);
@@ -9216,6 +9279,7 @@ const GenZir = struct {
                         .has_align = args.align_inst != .none,
                         .is_test = args.is_test,
                         .is_extern = args.is_extern,
+                        .has_comptime_bits = any_are_comptime,
                     }),
                     .operand = payload_index,
                 } },
src/Sema.zig
@@ -104,6 +104,9 @@ pub fn analyzeFnBody(
             extra_index += @boolToInt(small.has_lib_name);
             extra_index += @boolToInt(small.has_cc);
             extra_index += @boolToInt(small.has_align);
+            if (small.has_comptime_bits) {
+                extra_index += (extra.data.param_types_len + 31) / 32;
+            }
             extra_index += extra.data.param_types_len;
             const body = sema.code.extra[extra_index..][0..extra.data.body_len];
             break :blk body;
@@ -2533,6 +2536,9 @@ fn analyzeCall(
 
         break :res result;
     } else res: {
+        if (func_ty.fnIsGeneric()) {
+            return sema.mod.fail(&block.base, func_src, "TODO implement generic fn call", .{});
+        }
         try sema.requireRuntimeBlock(block, call_src);
         try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len +
             args.len);
@@ -3208,6 +3214,7 @@ fn zirFunc(
         false,
         src_locs,
         null,
+        &.{},
     );
 }
 
@@ -3225,6 +3232,7 @@ fn funcCommon(
     is_extern: bool,
     src_locs: Zir.Inst.Func.SrcLocs,
     opt_lib_name: ?[]const u8,
+    comptime_bits: []const u32,
 ) CompileError!Air.Inst.Ref {
     const src: LazySrcLoc = .{ .node_offset = src_node_offset };
     const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset };
@@ -3257,13 +3265,23 @@ fn funcCommon(
             }
         }
 
+        var any_are_comptime = false;
         const param_types = try sema.arena.alloc(Type, zir_param_types.len);
         for (zir_param_types) |param_type, i| {
             // TODO make a compile error from `resolveType` report the source location
             // of the specific parameter. Will need to take a similar strategy as
             // `resolveSwitchItemVal` to avoid resolving the source location unless
             // we actually need to report an error.
-            param_types[i] = try sema.resolveType(block, src, param_type);
+            const param_src = src;
+            param_types[i] = try sema.resolveType(block, param_src, param_type);
+
+            any_are_comptime = any_are_comptime or blk: {
+                if (comptime_bits.len == 0)
+                    break :blk false;
+                const bag = comptime_bits[i / 32];
+                const is_comptime = @truncate(u1, bag >> @intCast(u5, i % 32)) != 0;
+                break :blk is_comptime;
+            };
         }
 
         if (align_val.tag() != .null_value) {
@@ -3286,6 +3304,7 @@ fn funcCommon(
             .return_type = return_type,
             .cc = cc,
             .is_var_args = var_args,
+            .is_generic = any_are_comptime,
         });
     };
 
@@ -6526,6 +6545,13 @@ fn zirFuncExtended(
         break :blk align_tv.val;
     } else Value.initTag(.null_value);
 
+    const comptime_bits: []const u32 = if (!small.has_comptime_bits) &.{} else blk: {
+        const amt = (extra.data.param_types_len + 31) / 32;
+        const bit_bags = sema.code.extra[extra_index..][0..amt];
+        extra_index += amt;
+        break :blk bit_bags;
+    };
+
     const param_types = sema.code.refSlice(extra_index, extra.data.param_types_len);
     extra_index += param_types.len;
 
@@ -6554,6 +6580,7 @@ fn zirFuncExtended(
         is_extern,
         src_locs,
         lib_name,
+        comptime_bits,
     );
 }
 
src/type.zig
@@ -764,6 +764,7 @@ pub const Type = extern union {
                     .param_types = param_types,
                     .cc = payload.cc,
                     .is_var_args = payload.is_var_args,
+                    .is_generic = payload.is_generic,
                 });
             },
             .pointer => {
@@ -2407,6 +2408,19 @@ pub const Type = extern union {
         };
     }
 
+    /// Asserts the type is a function.
+    pub fn fnIsGeneric(self: Type) bool {
+        return switch (self.tag()) {
+            .fn_noreturn_no_args => false,
+            .fn_void_no_args => false,
+            .fn_naked_noreturn_no_args => false,
+            .fn_ccc_void_no_args => false,
+            .function => self.castTag(.function).?.data.is_generic,
+
+            else => unreachable,
+        };
+    }
+
     pub fn isNumeric(self: Type) bool {
         return switch (self.tag()) {
             .f16,
@@ -3214,6 +3228,7 @@ pub const Type = extern union {
                 return_type: Type,
                 cc: std.builtin.CallingConvention,
                 is_var_args: bool,
+                is_generic: bool,
             },
         };
 
src/Zir.zig
@@ -2226,9 +2226,13 @@ pub const Inst = struct {
     /// 0. lib_name: u32, // null terminated string index, if has_lib_name is set
     /// 1. cc: Ref, // if has_cc is set
     /// 2. align: Ref, // if has_align is set
-    /// 3. param_type: Ref // for each param_types_len
-    /// 4. body: Index // for each body_len
-    /// 5. src_locs: Func.SrcLocs // if body_len != 0
+    /// 3. comptime_bits: u32 // for every 32 parameters, if has_comptime_bits is set
+    ///    - sets of 1 bit:
+    ///      0bX: whether corresponding parameter is comptime
+    /// 4. param_type: Ref // for each param_types_len
+    ///    - `none` indicates that the param type is `anytype`.
+    /// 5. body: Index // for each body_len
+    /// 6. src_locs: Func.SrcLocs // if body_len != 0
     pub const ExtendedFunc = struct {
         src_node: i32,
         return_type: Ref,
@@ -2243,7 +2247,8 @@ pub const Inst = struct {
             has_align: bool,
             is_test: bool,
             is_extern: bool,
-            _: u9 = undefined,
+            has_comptime_bits: bool,
+            _: u8 = undefined,
         };
     };
 
@@ -4291,6 +4296,7 @@ const Writer = struct {
             body,
             src,
             src_locs,
+            &.{},
         );
     }
 
@@ -4317,6 +4323,13 @@ const Writer = struct {
             break :blk align_inst;
         };
 
+        const comptime_bits: []const u32 = if (!small.has_comptime_bits) &.{} else blk: {
+            const amt = (extra.data.param_types_len + 31) / 32;
+            const bit_bags = self.code.extra[extra_index..][0..amt];
+            extra_index += amt;
+            break :blk bit_bags;
+        };
+
         const param_types = self.code.refSlice(extra_index, extra.data.param_types_len);
         extra_index += param_types.len;
 
@@ -4339,6 +4352,7 @@ const Writer = struct {
             body,
             src,
             src_locs,
+            comptime_bits,
         );
     }
 
@@ -4422,10 +4436,16 @@ const Writer = struct {
         body: []const Inst.Index,
         src: LazySrcLoc,
         src_locs: Zir.Inst.Func.SrcLocs,
+        comptime_bits: []const u32,
     ) !void {
         try stream.writeAll("[");
         for (param_types) |param_type, i| {
             if (i != 0) try stream.writeAll(", ");
+            if (comptime_bits.len != 0) {
+                const bag = comptime_bits[i / 32];
+                const is_comptime = @truncate(u1, bag >> @intCast(u5, i % 32)) != 0;
+                try self.writeFlag(stream, "comptime ", is_comptime);
+            }
             try self.writeInstRef(stream, param_type);
         }
         try stream.writeAll("], ");