Skip to content

Stage2: implement comptime variables #9030

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/AstGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions src/Module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
};
}

Expand Down
89 changes: 83 additions & 6 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ pub fn analyzeBody(
// directly jump to the next one, rather than detouring through the loop
// continue expression. Related: https://github.com/ziglang/zig/issues/8220
var i: usize = 0;
while (true) : (i += 1) {
while (true) {
const inst = body[i];
const air_inst = switch (tags[inst]) {
// zig fmt: off
Expand Down Expand Up @@ -394,78 +394,97 @@ pub fn analyzeBody(
// putting them into the map.
.breakpoint => {
try sema.zirBreakpoint(block, inst);
i += 1;
continue;
},
.fence => {
try sema.zirFence(block, inst);
i += 1;
continue;
},
.dbg_stmt => {
try sema.zirDbgStmt(block, inst);
i += 1;
continue;
},
.ensure_err_payload_void => {
try sema.zirEnsureErrPayloadVoid(block, inst);
i += 1;
continue;
},
.ensure_result_non_error => {
try sema.zirEnsureResultNonError(block, inst);
i += 1;
continue;
},
.ensure_result_used => {
try sema.zirEnsureResultUsed(block, inst);
i += 1;
continue;
},
.set_eval_branch_quota => {
try sema.zirSetEvalBranchQuota(block, inst);
i += 1;
continue;
},
.store => {
try sema.zirStore(block, inst);
i += 1;
continue;
},
.store_node => {
try sema.zirStoreNode(block, inst);
i += 1;
continue;
},
.store_to_block_ptr => {
try sema.zirStoreToBlockPtr(block, inst);
i += 1;
continue;
},
.store_to_inferred_ptr => {
try sema.zirStoreToInferredPtr(block, inst);
i += 1;
continue;
},
.resolve_inferred_alloc => {
try sema.zirResolveInferredAlloc(block, inst);
i += 1;
continue;
},
.validate_struct_init_ptr => {
try sema.zirValidateStructInitPtr(block, inst);
i += 1;
continue;
},
.validate_array_init_ptr => {
try sema.zirValidateArrayInitPtr(block, inst);
i += 1;
continue;
},
.@"export" => {
try sema.zirExport(block, inst);
i += 1;
continue;
},
.set_align_stack => {
try sema.zirSetAlignStack(block, inst);
i += 1;
continue;
},
.set_cold => {
try sema.zirSetAlignStack(block, inst);
i += 1;
continue;
},
.set_float_mode => {
try sema.zirSetFloatMode(block, inst);
i += 1;
continue;
},
.set_runtime_safety => {
try sema.zirSetRuntimeSafety(block, inst);
i += 1;
continue;
},

Expand Down Expand Up @@ -509,7 +528,8 @@ 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);
i += 1;
}
}

Expand Down Expand Up @@ -1238,9 +1258,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 {
Expand Down Expand Up @@ -1742,6 +1779,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);
Expand Down Expand Up @@ -4066,6 +4106,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;
Expand Down Expand Up @@ -4584,14 +4627,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(
Expand Down Expand Up @@ -5150,6 +5193,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();
Expand Down Expand Up @@ -5258,6 +5304,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);
Expand Down Expand Up @@ -6753,7 +6802,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);
Expand Down
26 changes: 26 additions & 0 deletions src/value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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).?;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -713,6 +722,7 @@ pub const Value = extern union {
.extern_fn,
.variable,
.ref_val,
.comptime_alloc,
.decl_ref,
.elem_ptr,
.field_ptr,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand Down
Loading