Skip to content

Commit cb53c97

Browse files
committed
stage2: implement comptime variables
1 parent ca6951f commit cb53c97

File tree

5 files changed

+166
-4
lines changed

5 files changed

+166
-4
lines changed

src/AstGen.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2357,7 +2357,7 @@ fn varDecl(
23572357
return &sub_scope.base;
23582358
},
23592359
.keyword_var => {
2360-
const is_comptime = var_decl.comptime_token != null;
2360+
const is_comptime = var_decl.comptime_token != null or gz.force_comptime;
23612361
var resolve_inferred_alloc: Zir.Inst.Ref = .none;
23622362
const var_data: struct {
23632363
result_loc: ResultLoc,

src/Module.zig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,13 @@ pub const Scope = struct {
11391139
instructions: ArrayListUnmanaged(*ir.Inst),
11401140
label: ?*Label = null,
11411141
inlining: ?*Inlining,
1142+
/// If runtime_index is not 0 then one of these is guaranteed to be non null.
1143+
runtime_cond: ?LazySrcLoc = null,
1144+
runtime_loop: ?LazySrcLoc = null,
1145+
/// Non zero if a non-inline loop or a runtime conditional have been encountered.
1146+
/// Stores to to comptime variables are only allowed when var.runtime_index <= runtime_index.
1147+
runtime_index: u32 = 0,
1148+
11421149
is_comptime: bool,
11431150

11441151
/// This `Block` maps a block ZIR instruction to the corresponding
@@ -1182,6 +1189,9 @@ pub const Scope = struct {
11821189
.label = null,
11831190
.inlining = parent.inlining,
11841191
.is_comptime = parent.is_comptime,
1192+
.runtime_cond = parent.runtime_cond,
1193+
.runtime_loop = parent.runtime_loop,
1194+
.runtime_index = parent.runtime_index,
11851195
};
11861196
}
11871197

src/Sema.zig

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ pub fn analyzeBody(
509509
};
510510
if (air_inst.ty.isNoReturn())
511511
return always_noreturn;
512-
try map.putNoClobber(sema.gpa, inst, air_inst);
512+
try map.put(sema.gpa, inst, air_inst);
513513
}
514514
}
515515

@@ -1238,9 +1238,26 @@ fn zirAllocExtended(
12381238
}
12391239

12401240
fn zirAllocComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!*Inst {
1241+
const tracy = trace(@src());
1242+
defer tracy.end();
1243+
12411244
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
12421245
const src = inst_data.src();
1243-
return sema.mod.fail(&block.base, src, "TODO implement Sema.zirAllocComptime", .{});
1246+
const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
1247+
const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
1248+
const ptr_type = try sema.mod.simplePtrType(sema.arena, var_type, true, .One);
1249+
1250+
const val_payload = try sema.arena.create(Value.Payload.ComptimeAlloc);
1251+
val_payload.* = .{
1252+
.data = .{
1253+
.runtime_index = block.runtime_index,
1254+
.val = undefined, // astgen guarantees there will be a store before the first load
1255+
},
1256+
};
1257+
return sema.mod.constInst(sema.arena, src, .{
1258+
.ty = ptr_type,
1259+
.val = Value.initPayload(&val_payload.base),
1260+
});
12441261
}
12451262

12461263
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
17421759
};
17431760
var child_block = parent_block.makeSubBlock();
17441761
child_block.label = &label;
1762+
child_block.runtime_cond = null;
1763+
child_block.runtime_loop = src;
1764+
child_block.runtime_index += 1;
17451765
const merges = &child_block.label.?.merges;
17461766

17471767
defer child_block.instructions.deinit(sema.gpa);
@@ -4066,6 +4086,9 @@ fn analyzeSwitch(
40664086
const cases = try sema.arena.alloc(Inst.SwitchBr.Case, scalar_cases_len);
40674087

40684088
var case_block = child_block.makeSubBlock();
4089+
case_block.runtime_loop = null;
4090+
case_block.runtime_cond = operand.src;
4091+
case_block.runtime_index += 1;
40694092
defer case_block.instructions.deinit(gpa);
40704093

40714094
var extra_index: usize = special.end;
@@ -5150,6 +5173,9 @@ fn zirBoolBr(
51505173
};
51515174

51525175
var child_block = parent_block.makeSubBlock();
5176+
child_block.runtime_loop = null;
5177+
child_block.runtime_cond = lhs.src;
5178+
child_block.runtime_index += 1;
51535179
defer child_block.instructions.deinit(sema.gpa);
51545180

51555181
var then_block = child_block.makeSubBlock();
@@ -5258,6 +5284,9 @@ fn zirCondbr(
52585284
}
52595285

52605286
var sub_block = parent_block.makeSubBlock();
5287+
sub_block.runtime_loop = null;
5288+
sub_block.runtime_cond = cond.src;
5289+
sub_block.runtime_index += 1;
52615290
defer sub_block.instructions.deinit(sema.gpa);
52625291

52635292
_ = try sema.analyzeBody(&sub_block, then_body);
@@ -6753,7 +6782,35 @@ fn storePtr(
67536782
if ((try sema.typeHasOnePossibleValue(block, src, elem_ty)) != null)
67546783
return;
67556784

6756-
// TODO handle comptime pointer writes
6785+
if (try sema.resolvePossiblyUndefinedValue(block, src, ptr)) |ptr_val| {
6786+
const const_val = (try sema.resolvePossiblyUndefinedValue(block, src, value)) orelse
6787+
return sema.mod.fail(&block.base, src, "cannot store runtime value in compile time variable", .{});
6788+
6789+
const comptime_alloc = ptr_val.castTag(.comptime_alloc).?;
6790+
if (comptime_alloc.data.runtime_index < block.runtime_index) {
6791+
if (block.runtime_cond) |cond_src| {
6792+
const msg = msg: {
6793+
const msg = try sema.mod.errMsg(&block.base, src, "store to comptime variable depends on runtime condition", .{});
6794+
errdefer msg.destroy(sema.gpa);
6795+
try sema.mod.errNote(&block.base, cond_src, msg, "runtime condition here", .{});
6796+
break :msg msg;
6797+
};
6798+
return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
6799+
}
6800+
if (block.runtime_loop) |loop_src| {
6801+
const msg = msg: {
6802+
const msg = try sema.mod.errMsg(&block.base, src, "cannot store to comptime variable in non-inline loop", .{});
6803+
errdefer msg.destroy(sema.gpa);
6804+
try sema.mod.errNote(&block.base, loop_src, msg, "non-inline loop here", .{});
6805+
break :msg msg;
6806+
};
6807+
return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
6808+
}
6809+
unreachable;
6810+
}
6811+
comptime_alloc.data.val = const_val;
6812+
return;
6813+
}
67576814
// TODO handle if the element type requires comptime
67586815

67596816
try sema.requireRuntimeBlock(block, src);

src/value.zig

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ pub const Value = extern union {
101101
variable,
102102
/// Represents a pointer to another immutable value.
103103
ref_val,
104+
/// Represents a comptime variables storage.
105+
comptime_alloc,
104106
/// Represents a pointer to a decl, not the value of the decl.
105107
decl_ref,
106108
elem_ptr,
@@ -223,6 +225,7 @@ pub const Value = extern union {
223225
.int_i64 => Payload.I64,
224226
.function => Payload.Function,
225227
.variable => Payload.Variable,
228+
.comptime_alloc => Payload.ComptimeAlloc,
226229
.elem_ptr => Payload.ElemPtr,
227230
.field_ptr => Payload.FieldPtr,
228231
.float_16 => Payload.Float_16,
@@ -403,6 +406,7 @@ pub const Value = extern union {
403406
};
404407
return Value{ .ptr_otherwise = &new_payload.base };
405408
},
409+
.comptime_alloc => return self.copyPayloadShallow(allocator, Payload.ComptimeAlloc),
406410
.decl_ref => return self.copyPayloadShallow(allocator, Payload.Decl),
407411
.elem_ptr => {
408412
const payload = self.castTag(.elem_ptr).?;
@@ -577,6 +581,11 @@ pub const Value = extern union {
577581
try out_stream.writeAll("&const ");
578582
val = ref_val;
579583
},
584+
.comptime_alloc => {
585+
const ref_val = val.castTag(.comptime_alloc).?.data.val;
586+
try out_stream.writeAll("&");
587+
val = ref_val;
588+
},
580589
.decl_ref => return out_stream.writeAll("(decl ref)"),
581590
.elem_ptr => {
582591
const elem_ptr = val.castTag(.elem_ptr).?.data;
@@ -713,6 +722,7 @@ pub const Value = extern union {
713722
.extern_fn,
714723
.variable,
715724
.ref_val,
725+
.comptime_alloc,
716726
.decl_ref,
717727
.elem_ptr,
718728
.field_ptr,
@@ -1186,6 +1196,10 @@ pub const Value = extern union {
11861196
const payload = self.castTag(.ref_val).?;
11871197
std.hash.autoHash(&hasher, payload.data.hash());
11881198
},
1199+
.comptime_alloc => {
1200+
const payload = self.castTag(.comptime_alloc).?;
1201+
std.hash.autoHash(&hasher, payload.data.val.hash());
1202+
},
11891203
.int_big_positive, .int_big_negative => {
11901204
var space: BigIntSpace = undefined;
11911205
const big = self.toBigInt(&space);
@@ -1277,6 +1291,7 @@ pub const Value = extern union {
12771291
/// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
12781292
pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
12791293
return switch (self.tag()) {
1294+
.comptime_alloc => self.castTag(.comptime_alloc).?.data.val,
12801295
.ref_val => self.castTag(.ref_val).?.data,
12811296
.decl_ref => self.castTag(.decl_ref).?.data.value(),
12821297
.elem_ptr => {
@@ -1462,6 +1477,7 @@ pub const Value = extern union {
14621477
.int_big_positive,
14631478
.int_big_negative,
14641479
.ref_val,
1480+
.comptime_alloc,
14651481
.decl_ref,
14661482
.elem_ptr,
14671483
.field_ptr,
@@ -1542,6 +1558,16 @@ pub const Value = extern union {
15421558
data: Value,
15431559
};
15441560

1561+
pub const ComptimeAlloc = struct {
1562+
pub const base_tag = Tag.comptime_alloc;
1563+
1564+
base: Payload = Payload{ .tag = base_tag },
1565+
data: struct {
1566+
val: Value,
1567+
runtime_index: u32,
1568+
},
1569+
};
1570+
15451571
pub const ElemPtr = struct {
15461572
pub const base_tag = Tag.elem_ptr;
15471573

test/stage2/test.zig

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,4 +1420,73 @@ pub fn addCases(ctx: *TestContext) !void {
14201420
\\}
14211421
, &[_][]const u8{":4:27: error: expected type, found comptime_int"});
14221422
}
1423+
{
1424+
var case = ctx.exe("comptime var", linux_x64);
1425+
1426+
case.addError(
1427+
\\pub fn main() void {
1428+
\\ var a: u32 = 0;
1429+
\\ comptime var b: u32 = 0;
1430+
\\ if (a == 0) b = 3;
1431+
\\}
1432+
, &.{
1433+
":4:21: error: store to comptime variable depends on runtime condition",
1434+
":4:11: note: runtime condition here",
1435+
});
1436+
1437+
case.addError(
1438+
\\pub fn main() void {
1439+
\\ var a: u32 = 0;
1440+
\\ comptime var b: u32 = 0;
1441+
\\ switch (a) {
1442+
\\ 0 => {},
1443+
\\ else => b = 3,
1444+
\\ }
1445+
\\}
1446+
, &.{
1447+
":6:21: error: store to comptime variable depends on runtime condition",
1448+
":4:13: note: runtime condition here",
1449+
});
1450+
1451+
case.addCompareOutput(
1452+
\\pub fn main() void {
1453+
\\ comptime var len: u32 = 5;
1454+
\\ print(len);
1455+
\\ len += 9;
1456+
\\ print(len);
1457+
\\}
1458+
\\
1459+
\\fn print(len: usize) void {
1460+
\\ asm volatile ("syscall"
1461+
\\ :
1462+
\\ : [number] "{rax}" (1),
1463+
\\ [arg1] "{rdi}" (1),
1464+
\\ [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")),
1465+
\\ [arg3] "{rdx}" (len)
1466+
\\ : "rcx", "r11", "memory"
1467+
\\ );
1468+
\\ return;
1469+
\\}
1470+
, "HelloHello, World!\n");
1471+
1472+
case.addCompareOutput(
1473+
\\pub fn main() void {
1474+
\\ var a: u32 = 0;
1475+
\\ if (a == 0) {
1476+
\\ comptime var b: u32 = 0;
1477+
\\ b = 1;
1478+
\\ }
1479+
\\}
1480+
, "");
1481+
1482+
case.addError(
1483+
\\pub fn main() void {
1484+
\\ comptime var i: u64 = 0;
1485+
\\ while (i < 5) : (i += 1) {}
1486+
\\}
1487+
, &.{ // FIXME storePtr() src is incorrect
1488+
":1:1: error: cannot store to comptime variable in non-inline loop",
1489+
":16:5: note: non-inline loop here",
1490+
});
1491+
}
14231492
}

0 commit comments

Comments
 (0)