From cb53c972e36c1e9bcbccde24e76105f7e7af4ed0 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 6 Jun 2021 21:08:31 +0300 Subject: [PATCH] stage2: implement comptime variables --- src/AstGen.zig | 2 +- src/Module.zig | 10 +++++++ src/Sema.zig | 63 ++++++++++++++++++++++++++++++++++++++-- src/value.zig | 26 +++++++++++++++++ test/stage2/test.zig | 69 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 4 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 564813f9e3c4..c4482131d89f 100644 --- a/src/AstGen.zig +++ b/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, diff --git a/src/Module.zig b/src/Module.zig index c20bb2eb089b..8e868c4ed842 100644 --- a/src/Module.zig +++ b/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, }; } diff --git a/src/Sema.zig b/src/Sema.zig index 718e145a7d94..0dd9ea47e28b 100644 --- a/src/Sema.zig +++ b/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; @@ -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); diff --git a/src/value.zig b/src/value.zig index 9c29bdf80c40..c358975667c0 100644 --- a/src/value.zig +++ b/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; diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 15ccae474801..53f5c2a6ac30 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1420,4 +1420,73 @@ 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.addCompareOutput( + \\pub fn main() void { + \\ var a: u32 = 0; + \\ if (a == 0) { + \\ comptime var b: u32 = 0; + \\ b = 1; + \\ } + \\} + , ""); + + case.addError( + \\pub fn main() void { + \\ comptime var i: u64 = 0; + \\ while (i < 5) : (i += 1) {} + \\} + , &.{ // FIXME storePtr() src is incorrect + ":1:1: error: cannot store to comptime variable in non-inline loop", + ":16:5: note: non-inline loop here", + }); + } }