Commit 7efd7bc3b8

Veikka Tuominen <git@vexu.eu>
2021-06-06 20:08:31
stage2: implement comptime variables
1 parent ca6951f
Changed files (5)
src/AstGen.zig
@@ -2357,7 +2357,7 @@ fn varDecl(
             return &sub_scope.base;
         },
         .keyword_var => {
-            const is_comptime = var_decl.comptime_token != null;
+            const is_comptime = var_decl.comptime_token != null or gz.force_comptime;
             var resolve_inferred_alloc: Zir.Inst.Ref = .none;
             const var_data: struct {
                 result_loc: ResultLoc,
src/Module.zig
@@ -1139,6 +1139,13 @@ pub const Scope = struct {
         instructions: ArrayListUnmanaged(*ir.Inst),
         label: ?*Label = null,
         inlining: ?*Inlining,
+        /// If runtime_index is not 0 then one of these is guaranteed to be non null.
+        runtime_cond: ?LazySrcLoc = null,
+        runtime_loop: ?LazySrcLoc = null,
+        /// Non zero if a non-inline loop or a runtime conditional have been encountered.
+        /// Stores to to comptime variables are only allowed when var.runtime_index <= runtime_index.
+        runtime_index: u32 = 0,
+
         is_comptime: bool,
 
         /// This `Block` maps a block ZIR instruction to the corresponding
@@ -1182,6 +1189,9 @@ pub const Scope = struct {
                 .label = null,
                 .inlining = parent.inlining,
                 .is_comptime = parent.is_comptime,
+                .runtime_cond = parent.runtime_cond,
+                .runtime_loop = parent.runtime_loop,
+                .runtime_index = parent.runtime_index,
             };
         }
 
src/Sema.zig
@@ -509,7 +509,7 @@ pub fn analyzeBody(
         };
         if (air_inst.ty.isNoReturn())
             return always_noreturn;
-        try map.putNoClobber(sema.gpa, inst, air_inst);
+        try map.put(sema.gpa, inst, air_inst);
     }
 }
 
@@ -1238,9 +1238,26 @@ fn zirAllocExtended(
 }
 
 fn zirAllocComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const src = inst_data.src();
-    return sema.mod.fail(&block.base, src, "TODO implement Sema.zirAllocComptime", .{});
+    const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
+    const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
+    const ptr_type = try sema.mod.simplePtrType(sema.arena, var_type, true, .One);
+
+    const val_payload = try sema.arena.create(Value.Payload.ComptimeAlloc);
+    val_payload.* = .{
+        .data = .{
+            .runtime_index = block.runtime_index,
+            .val = undefined, // astgen guarantees there will be a store before the first load
+        },
+    };
+    return sema.mod.constInst(sema.arena, src, .{
+        .ty = ptr_type,
+        .val = Value.initPayload(&val_payload.base),
+    });
 }
 
 fn zirAllocInferredComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!*Inst {
@@ -1742,6 +1759,9 @@ fn zirLoop(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) InnerE
     };
     var child_block = parent_block.makeSubBlock();
     child_block.label = &label;
+    child_block.runtime_cond = null;
+    child_block.runtime_loop = src;
+    child_block.runtime_index += 1;
     const merges = &child_block.label.?.merges;
 
     defer child_block.instructions.deinit(sema.gpa);
@@ -4066,6 +4086,9 @@ fn analyzeSwitch(
     const cases = try sema.arena.alloc(Inst.SwitchBr.Case, scalar_cases_len);
 
     var case_block = child_block.makeSubBlock();
+    case_block.runtime_loop = null;
+    case_block.runtime_cond = operand.src;
+    case_block.runtime_index += 1;
     defer case_block.instructions.deinit(gpa);
 
     var extra_index: usize = special.end;
@@ -4584,14 +4607,14 @@ fn zirArithmetic(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerEr
 
     const tag_override = block.sema.code.instructions.items(.tag)[inst];
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
-    const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+    sema.src = .{ .node_offset_bin_op = inst_data.src_node };
     const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
     const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
     const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
     const lhs = try sema.resolveInst(extra.lhs);
     const rhs = try sema.resolveInst(extra.rhs);
 
-    return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src);
+    return sema.analyzeArithmetic(block, tag_override, lhs, rhs, sema.src, lhs_src, rhs_src);
 }
 
 fn zirOverflowArithmetic(
@@ -5150,6 +5173,9 @@ fn zirBoolBr(
     };
 
     var child_block = parent_block.makeSubBlock();
+    child_block.runtime_loop = null;
+    child_block.runtime_cond = lhs.src;
+    child_block.runtime_index += 1;
     defer child_block.instructions.deinit(sema.gpa);
 
     var then_block = child_block.makeSubBlock();
@@ -5258,6 +5284,9 @@ fn zirCondbr(
     }
 
     var sub_block = parent_block.makeSubBlock();
+    sub_block.runtime_loop = null;
+    sub_block.runtime_cond = cond.src;
+    sub_block.runtime_index += 1;
     defer sub_block.instructions.deinit(sema.gpa);
 
     _ = try sema.analyzeBody(&sub_block, then_body);
@@ -6753,7 +6782,35 @@ fn storePtr(
     if ((try sema.typeHasOnePossibleValue(block, src, elem_ty)) != null)
         return;
 
-    // TODO handle comptime pointer writes
+    if (try sema.resolvePossiblyUndefinedValue(block, src, ptr)) |ptr_val| {
+        const const_val = (try sema.resolvePossiblyUndefinedValue(block, src, value)) orelse
+            return sema.mod.fail(&block.base, src, "cannot store runtime value in compile time variable", .{});
+
+        const comptime_alloc = ptr_val.castTag(.comptime_alloc).?;
+        if (comptime_alloc.data.runtime_index < block.runtime_index) {
+            if (block.runtime_cond) |cond_src| {
+                const msg = msg: {
+                    const msg = try sema.mod.errMsg(&block.base, src, "store to comptime variable depends on runtime condition", .{});
+                    errdefer msg.destroy(sema.gpa);
+                    try sema.mod.errNote(&block.base, cond_src, msg, "runtime condition here", .{});
+                    break :msg msg;
+                };
+                return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
+            }
+            if (block.runtime_loop) |loop_src| {
+                const msg = msg: {
+                    const msg = try sema.mod.errMsg(&block.base, src, "cannot store to comptime variable in non-inline loop", .{});
+                    errdefer msg.destroy(sema.gpa);
+                    try sema.mod.errNote(&block.base, loop_src, msg, "non-inline loop here", .{});
+                    break :msg msg;
+                };
+                return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
+            }
+            unreachable;
+        }
+        comptime_alloc.data.val = const_val;
+        return;
+    }
     // TODO handle if the element type requires comptime
 
     try sema.requireRuntimeBlock(block, src);
src/value.zig
@@ -101,6 +101,8 @@ pub const Value = extern union {
         variable,
         /// Represents a pointer to another immutable value.
         ref_val,
+        /// Represents a comptime variables storage.
+        comptime_alloc,
         /// Represents a pointer to a decl, not the value of the decl.
         decl_ref,
         elem_ptr,
@@ -223,6 +225,7 @@ pub const Value = extern union {
                 .int_i64 => Payload.I64,
                 .function => Payload.Function,
                 .variable => Payload.Variable,
+                .comptime_alloc => Payload.ComptimeAlloc,
                 .elem_ptr => Payload.ElemPtr,
                 .field_ptr => Payload.FieldPtr,
                 .float_16 => Payload.Float_16,
@@ -403,6 +406,7 @@ pub const Value = extern union {
                 };
                 return Value{ .ptr_otherwise = &new_payload.base };
             },
+            .comptime_alloc => return self.copyPayloadShallow(allocator, Payload.ComptimeAlloc),
             .decl_ref => return self.copyPayloadShallow(allocator, Payload.Decl),
             .elem_ptr => {
                 const payload = self.castTag(.elem_ptr).?;
@@ -577,6 +581,11 @@ pub const Value = extern union {
                 try out_stream.writeAll("&const ");
                 val = ref_val;
             },
+            .comptime_alloc => {
+                const ref_val = val.castTag(.comptime_alloc).?.data.val;
+                try out_stream.writeAll("&");
+                val = ref_val;
+            },
             .decl_ref => return out_stream.writeAll("(decl ref)"),
             .elem_ptr => {
                 const elem_ptr = val.castTag(.elem_ptr).?.data;
@@ -713,6 +722,7 @@ pub const Value = extern union {
             .extern_fn,
             .variable,
             .ref_val,
+            .comptime_alloc,
             .decl_ref,
             .elem_ptr,
             .field_ptr,
@@ -1186,6 +1196,10 @@ pub const Value = extern union {
                 const payload = self.castTag(.ref_val).?;
                 std.hash.autoHash(&hasher, payload.data.hash());
             },
+            .comptime_alloc => {
+                const payload = self.castTag(.comptime_alloc).?;
+                std.hash.autoHash(&hasher, payload.data.val.hash());
+            },
             .int_big_positive, .int_big_negative => {
                 var space: BigIntSpace = undefined;
                 const big = self.toBigInt(&space);
@@ -1277,6 +1291,7 @@ pub const Value = extern union {
     /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
     pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
         return switch (self.tag()) {
+            .comptime_alloc => self.castTag(.comptime_alloc).?.data.val,
             .ref_val => self.castTag(.ref_val).?.data,
             .decl_ref => self.castTag(.decl_ref).?.data.value(),
             .elem_ptr => {
@@ -1462,6 +1477,7 @@ pub const Value = extern union {
             .int_big_positive,
             .int_big_negative,
             .ref_val,
+            .comptime_alloc,
             .decl_ref,
             .elem_ptr,
             .field_ptr,
@@ -1542,6 +1558,16 @@ pub const Value = extern union {
             data: Value,
         };
 
+        pub const ComptimeAlloc = struct {
+            pub const base_tag = Tag.comptime_alloc;
+
+            base: Payload = Payload{ .tag = base_tag },
+            data: struct {
+                val: Value,
+                runtime_index: u32,
+            },
+        };
+
         pub const ElemPtr = struct {
             pub const base_tag = Tag.elem_ptr;
 
test/stage2/test.zig
@@ -1420,4 +1420,86 @@ pub fn addCases(ctx: *TestContext) !void {
             \\}
         , &[_][]const u8{":4:27: error: expected type, found comptime_int"});
     }
+    {
+        var case = ctx.exe("comptime var", linux_x64);
+
+        case.addError(
+            \\pub fn main() void {
+            \\    var a: u32 = 0;
+            \\    comptime var b: u32 = 0;
+            \\    if (a == 0) b = 3;
+            \\}
+        , &.{
+            ":4:21: error: store to comptime variable depends on runtime condition",
+            ":4:11: note: runtime condition here",
+        });
+
+        case.addError(
+            \\pub fn main() void {
+            \\    var a: u32 = 0;
+            \\    comptime var b: u32 = 0;
+            \\    switch (a) {
+            \\        0 => {},
+            \\        else => b = 3,
+            \\    }
+            \\}
+        , &.{
+            ":6:21: error: store to comptime variable depends on runtime condition",
+            ":4:13: note: runtime condition here",
+        });
+
+        case.addCompareOutput(
+            \\pub fn main() void {
+            \\    comptime var len: u32 = 5;
+            \\    print(len);
+            \\    len += 9;
+            \\    print(len);
+            \\}
+            \\
+            \\fn print(len: usize) void {
+            \\    asm volatile ("syscall"
+            \\        :
+            \\        : [number] "{rax}" (1),
+            \\          [arg1] "{rdi}" (1),
+            \\          [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")),
+            \\          [arg3] "{rdx}" (len)
+            \\        : "rcx", "r11", "memory"
+            \\    );
+            \\    return;
+            \\}
+        , "HelloHello, World!\n");
+
+        case.addError(
+            \\comptime {
+            \\    var x: i32 = 1;
+            \\    x += 1;
+            \\    if (x != 1) unreachable;
+            \\}
+        , &.{":4:17: error: unable to resolve comptime value"});
+
+        case.addError(
+            \\pub fn main() void {
+            \\    comptime var i: u64 = 0;
+            \\    while (i < 5) : (i += 1) {}
+            \\}
+        , &.{
+            ":3:24: error: cannot store to comptime variable in non-inline loop",
+            ":3:5: note: non-inline loop here",
+        });
+
+        case.addCompareOutput(
+            \\pub fn main() void {
+            \\    var a: u32 = 0;
+            \\    if (a == 0) {
+            \\        comptime var b: u32 = 0;
+            \\        b = 1;
+            \\    }
+            \\}
+            \\comptime {
+            \\    var x: i32 = 1;
+            \\    x += 1;
+            \\    if (x != 2) unreachable;
+            \\}
+        , "");
+    }
 }