Skip to content

Sema: allow field access instructions to be emitted at comptime #15287

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

Closed
wants to merge 2 commits into from
Closed
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 lib/std/crypto/25519/ed25519.zig
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ test "ed25519 test vectors" {
},
};
for (entries) |entry| {
var msg: [64 / 2]u8 = undefined;
var msg: [entry.msg_hex.len / 2]u8 = undefined;
_ = try fmt.hexToBytes(&msg, entry.msg_hex);
var public_key_bytes: [32]u8 = undefined;
_ = try fmt.hexToBytes(&public_key_bytes, entry.public_key_hex);
Expand Down
2 changes: 1 addition & 1 deletion lib/std/net/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ test "parse and render UNIX addresses" {
const fmt_addr = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable;
try std.testing.expectEqualSlices(u8, "/tmp/testpath", fmt_addr);

const too_long = [_]u8{'a'} ** 200;
const too_long = [_]u8{'a'} ** (addr.un.path.len + 1);
try testing.expectError(error.NameTooLong, net.Address.initUnix(too_long[0..]));
}

Expand Down
127 changes: 80 additions & 47 deletions src/AstGen.zig

Large diffs are not rendered by default.

55 changes: 42 additions & 13 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1453,9 +1453,18 @@ fn analyzeBodyInner(
break break_data.inst;
}
},
.block, .block_comptime => blk: {
.block, .block_comptime, .block_comptime_defer_error => blk: {
if (!block.is_comptime) {
break :blk try sema.zirBlock(block, inst, tags[inst] == .block_comptime);
break :blk try sema.zirBlock(
block,
inst,
switch (tags[inst]) {
.block => .normal,
.block_comptime => .@"comptime",
.block_comptime_defer_error => .comptime_defer_error,
else => unreachable,
},
);
}
// Same as `block_inline`. TODO https://github.com/ziglang/zig/issues/8220
const inst_data = datas[inst].pl_node;
Expand Down Expand Up @@ -5372,7 +5381,16 @@ fn zirSuspendBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) Comp
return sema.failWithUseOfAsync(parent_block, src);
}

fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index, force_comptime: bool) CompileError!Air.Inst.Ref {
fn zirBlock(
sema: *Sema,
parent_block: *Block,
inst: Zir.Inst.Index,
block_type: enum {
normal,
@"comptime",
comptime_defer_error,
},
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();

Expand All @@ -5382,6 +5400,11 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index, force_compt
const body = sema.code.extra[extra.end..][0..extra.data.body_len];
const gpa = sema.gpa;

const force_comptime = switch (block_type) {
.normal => false,
.@"comptime", .comptime_defer_error => true,
};

// Reserve space for a Block instruction so that generated Break instructions can
// point to it, even if it doesn't end up getting used because the code ends up being
// comptime evaluated or is an unlabeled block.
Expand Down Expand Up @@ -5425,7 +5448,14 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index, force_compt
defer child_block.instructions.deinit(gpa);
defer label.merges.deinit(gpa);

return sema.resolveBlockBody(parent_block, src, &child_block, body, inst, &label.merges);
const result = try sema.resolveBlockBody(parent_block, src, &child_block, body, inst, &label.merges);
if (!parent_block.is_comptime and block_type == .@"comptime") {
// We're giving this value to runtime code - ensure the value is actually known at
// comptime, since comptime eval is allowed to secretly emit runtime instructions as
// long as they end up unused (e.g. for `runtime_array.len` accesses)
_ = try sema.resolveValue(parent_block, src, result, "result of comptime scope");
}
return result;
}

fn resolveBlockBody(
Expand Down Expand Up @@ -24029,7 +24059,6 @@ fn structFieldPtrByIndex(
);
}

try sema.requireRuntimeBlock(block, src, null);
return block.addStructFieldPtr(struct_ptr, field_index, ptr_field_ty);
}

Expand Down Expand Up @@ -24074,7 +24103,6 @@ fn structFieldVal(
return sema.addConstant(field.ty, field_values[field_index]);
}

try sema.requireRuntimeBlock(block, src, null);
return block.addStructFieldVal(struct_byval, field_index, field.ty);
},
else => unreachable,
Expand Down Expand Up @@ -24126,6 +24154,8 @@ fn tupleFieldValByIndex(
field_index: u32,
tuple_ty: Type,
) CompileError!Air.Inst.Ref {
_ = src;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this parameter.


const field_ty = tuple_ty.structFieldType(field_index);

if (tuple_ty.structFieldValueComptime(field_index)) |default_value| {
Expand All @@ -24145,7 +24175,6 @@ fn tupleFieldValByIndex(
return sema.addConstant(field_ty, default_val);
}

try sema.requireRuntimeBlock(block, src, null);
return block.addStructFieldVal(tuple_byval, field_index, field_ty);
}

Expand Down Expand Up @@ -24226,8 +24255,9 @@ fn unionFieldPtr(
);
}

try sema.requireRuntimeBlock(block, src, null);
if (!initializing and union_obj.layout == .Auto and block.wantSafety() and
// Note: the field access is valid at comptime (we may end up getting a nested comptime field /
// array length / similar), but we don't need the safety check
if (!block.is_comptime and !initializing and union_obj.layout == .Auto and block.wantSafety() and
union_ty.unionTagTypeSafety() != null and union_obj.fields.count() > 1)
{
const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index);
Expand Down Expand Up @@ -24300,8 +24330,9 @@ fn unionFieldVal(
}
}

try sema.requireRuntimeBlock(block, src, null);
if (union_obj.layout == .Auto and block.wantSafety() and
// Note: the field access is valid at comptime (we may end up getting a nested comptime field /
// array length / similar), but we don't need the safety check
if (!block.is_comptime and union_obj.layout == .Auto and block.wantSafety() and
union_ty.unionTagTypeSafety() != null and union_obj.fields.count() > 1)
{
const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index);
Expand Down Expand Up @@ -24537,7 +24568,6 @@ fn tupleFieldPtr(
try sema.validateRuntimeElemAccess(block, field_index_src, field_ty, tuple_ty, tuple_ptr_src);
}

try sema.requireRuntimeBlock(block, tuple_ptr_src, null);
return block.addStructFieldPtr(tuple_ptr, field_index, ptr_field_ty);
}

Expand Down Expand Up @@ -24575,7 +24605,6 @@ fn tupleField(

try sema.validateRuntimeElemAccess(block, field_index_src, field_ty, tuple_ty, tuple_src);

try sema.requireRuntimeBlock(block, tuple_src, null);
return block.addStructFieldVal(tuple, field_index, field_ty);
}

Expand Down
11 changes: 10 additions & 1 deletion src/Zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,11 @@ pub const Inst = struct {
/// Like `block`, but forces full evaluation of its contents at compile-time.
/// Uses the `pl_node` union field. Payload is `Block`.
block_comptime,
/// Like `block_comptime`, but errors from the final block result not being comptime-known
/// are deferred to the result's use site. The consumer of the result is expected to emit an
/// error if it is not comptime-known.
/// Uses the `pl_node` union field. Payload is `Block`.
block_comptime_defer_error,
/// A list of instructions which are analyzed in the parent context, without
/// generating a runtime block. Must terminate with an "inline" variant of
/// a noreturn instruction.
Expand Down Expand Up @@ -1040,6 +1045,7 @@ pub const Inst = struct {
.bit_or,
.block,
.block_comptime,
.block_comptime_defer_error,
.block_inline,
.suspend_block,
.loop,
Expand Down Expand Up @@ -1349,6 +1355,7 @@ pub const Inst = struct {
.bit_or,
.block,
.block_comptime,
.block_comptime_defer_error,
.block_inline,
.suspend_block,
.loop,
Expand Down Expand Up @@ -1585,6 +1592,7 @@ pub const Inst = struct {
.bit_or = .pl_node,
.block = .pl_node,
.block_comptime = .pl_node,
.block_comptime_defer_error = .pl_node,
.block_inline = .pl_node,
.suspend_block = .pl_node,
.bool_not = .un_node,
Expand Down Expand Up @@ -3892,7 +3900,7 @@ fn findDeclsInner(

// Block instructions, recurse over the bodies.

.block, .block_comptime, .block_inline => {
.block, .block_comptime, .block_comptime_defer_error, .block_inline => {
const inst_data = datas[inst].pl_node;
const extra = zir.extraData(Inst.Block, inst_data.payload_index);
const body = zir.extra[extra.end..][0..extra.data.body_len];
Expand Down Expand Up @@ -4121,6 +4129,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
};
assert(tags[info.param_block] == .block or
tags[info.param_block] == .block_comptime or
tags[info.param_block] == .block_comptime_defer_error or
tags[info.param_block] == .block_inline);
const param_block = zir.extraData(Inst.Block, datas[info.param_block].pl_node.payload_index);
const param_body = zir.extra[param_block.end..][0..param_block.data.body_len];
Expand Down
1 change: 1 addition & 0 deletions src/print_zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ const Writer = struct {

.block,
.block_comptime,
.block_comptime_defer_error,
.block_inline,
.suspend_block,
.loop,
Expand Down
26 changes: 22 additions & 4 deletions test/behavior/eval.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1409,12 +1409,30 @@ test "continue in inline for inside a comptime switch" {
test "length of global array is determinable at comptime" {
const S = struct {
var bytes: [1024]u8 = undefined;
};
try std.testing.expect(comptime S.bytes.len == 1024);
}

fn foo() !void {
try std.testing.expect(bytes.len == 1024);
}
test "length of array in global struct is determinable at comptime" {
const S = struct {
var g: struct { bytes: [1024]u8 } = undefined;
};
try std.testing.expect(comptime S.g.bytes.len == 1024);
}

test "length of array in global tuple is determinable at comptime" {
const S = struct {
var g: struct { [1024]u8 } = undefined;
};
try std.testing.expect(comptime S.g[0].len == 1024);
try std.testing.expect(comptime S.g.@"0".len == 1024);
}

test "length of array in global union is determinable at comptime" {
const S = struct {
var g: union { bytes: [1024]u8 } = undefined;
};
comptime try S.foo();
try std.testing.expect(comptime S.g.bytes.len == 1024);
}

test "continue nested inline for loop" {
Expand Down