authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-05-03 20:10:42+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-05-04 02:51:47+02:00
loge07d8fccd1389f4d9da4f5d906c8349d3014add8
treecfb9ea9396f9556519947de0656bc4abe1327e99
parentdb936b909471f5c7e0344489ab1e7d59ab387658
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

Merge pull request #23263 from mlugg/comptime-field-ptr

Sema: fix pointers to comptime fields of comptime-known aggregate pointers

4 files changed, 79 insertions(+), 33 deletions(-)

src/Sema.zig+24-9
...@@ -28582,12 +28582,17 @@ fn structFieldPtrByIndex(...@@ -28582,12 +28582,17 @@ fn structFieldPtrByIndex(
28582 const zcu = pt.zcu;28582 const zcu = pt.zcu;
28583 const ip = &zcu.intern_pool;28583 const ip = &zcu.intern_pool;
2858428584
28585 if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| {28585 const struct_type = zcu.typeToStruct(struct_ty).?;
28586 const val = try struct_ptr_val.ptrField(field_index, pt);28586 const field_is_comptime = struct_type.fieldIsComptime(ip, field_index);
28587 return Air.internedToRef(val.toIntern());28587
28588 // Comptime fields are handled later
28589 if (!field_is_comptime) {
28590 if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| {
28591 const val = try struct_ptr_val.ptrField(field_index, pt);
28592 return Air.internedToRef(val.toIntern());
28593 }
28588 }28594 }
2858928595
28590 const struct_type = zcu.typeToStruct(struct_ty).?;
28591 const field_ty = struct_type.field_types.get(ip)[field_index];28596 const field_ty = struct_type.field_types.get(ip)[field_index];
28592 const struct_ptr_ty = sema.typeOf(struct_ptr);28597 const struct_ptr_ty = sema.typeOf(struct_ptr);
28593 const struct_ptr_ty_info = struct_ptr_ty.ptrInfo(zcu);28598 const struct_ptr_ty_info = struct_ptr_ty.ptrInfo(zcu);
...@@ -28607,6 +28612,7 @@ fn structFieldPtrByIndex(...@@ -28607,6 +28612,7 @@ fn structFieldPtrByIndex(
28607 try Type.fromInterned(struct_ptr_ty_info.child).abiAlignmentSema(pt);28612 try Type.fromInterned(struct_ptr_ty_info.child).abiAlignmentSema(pt);
2860828613
28609 if (struct_type.layout == .@"packed") {28614 if (struct_type.layout == .@"packed") {
28615 assert(!field_is_comptime);
28610 switch (struct_ty.packedStructFieldPtrInfo(struct_ptr_ty, field_index, pt)) {28616 switch (struct_ty.packedStructFieldPtrInfo(struct_ptr_ty, field_index, pt)) {
28611 .bit_ptr => |packed_offset| {28617 .bit_ptr => |packed_offset| {
28612 ptr_ty_data.flags.alignment = parent_align;28618 ptr_ty_data.flags.alignment = parent_align;
...@@ -28617,6 +28623,7 @@ fn structFieldPtrByIndex(...@@ -28617,6 +28623,7 @@ fn structFieldPtrByIndex(
28617 },28623 },
28618 }28624 }
28619 } else if (struct_type.layout == .@"extern") {28625 } else if (struct_type.layout == .@"extern") {
28626 assert(!field_is_comptime);
28620 // For extern structs, field alignment might be bigger than type's28627 // For extern structs, field alignment might be bigger than type's
28621 // natural alignment. Eg, in `extern struct { x: u32, y: u16 }` the28628 // natural alignment. Eg, in `extern struct { x: u32, y: u16 }` the
28622 // second field is aligned as u32.28629 // second field is aligned as u32.
...@@ -28640,7 +28647,7 @@ fn structFieldPtrByIndex(...@@ -28640,7 +28647,7 @@ fn structFieldPtrByIndex(
2864028647
28641 const ptr_field_ty = try pt.ptrTypeSema(ptr_ty_data);28648 const ptr_field_ty = try pt.ptrTypeSema(ptr_ty_data);
2864228649
28643 if (struct_type.fieldIsComptime(ip, field_index)) {28650 if (field_is_comptime) {
28644 try struct_ty.resolveStructFieldInits(pt);28651 try struct_ty.resolveStructFieldInits(pt);
28645 const val = try pt.intern(.{ .ptr = .{28652 const val = try pt.intern(.{ .ptr = .{
28646 .ty = ptr_field_ty.toIntern(),28653 .ty = ptr_field_ty.toIntern(),
...@@ -29173,7 +29180,8 @@ fn tupleFieldPtr(...@@ -29173,7 +29180,8 @@ fn tupleFieldPtr(
29173 const pt = sema.pt;29180 const pt = sema.pt;
29174 const zcu = pt.zcu;29181 const zcu = pt.zcu;
29175 const tuple_ptr_ty = sema.typeOf(tuple_ptr);29182 const tuple_ptr_ty = sema.typeOf(tuple_ptr);
29176 const tuple_ty = tuple_ptr_ty.childType(zcu);29183 const tuple_ptr_info = tuple_ptr_ty.ptrInfo(zcu);
29184 const tuple_ty: Type = .fromInterned(tuple_ptr_info.child);
29177 try tuple_ty.resolveFields(pt);29185 try tuple_ty.resolveFields(pt);
29178 const field_count = tuple_ty.structFieldCount(zcu);29186 const field_count = tuple_ty.structFieldCount(zcu);
2917929187
...@@ -29191,9 +29199,16 @@ fn tupleFieldPtr(...@@ -29191,9 +29199,16 @@ fn tupleFieldPtr(
29191 const ptr_field_ty = try pt.ptrTypeSema(.{29199 const ptr_field_ty = try pt.ptrTypeSema(.{
29192 .child = field_ty.toIntern(),29200 .child = field_ty.toIntern(),
29193 .flags = .{29201 .flags = .{
29194 .is_const = !tuple_ptr_ty.ptrIsMutable(zcu),29202 .is_const = tuple_ptr_info.flags.is_const,
29195 .is_volatile = tuple_ptr_ty.isVolatilePtr(zcu),29203 .is_volatile = tuple_ptr_info.flags.is_volatile,
29196 .address_space = tuple_ptr_ty.ptrAddressSpace(zcu),29204 .address_space = tuple_ptr_info.flags.address_space,
29205 .alignment = a: {
29206 if (tuple_ptr_info.flags.alignment == .none) break :a .none;
29207 // The tuple pointer isn't naturally aligned, so the field pointer might be underaligned.
29208 const tuple_align = tuple_ptr_info.flags.alignment;
29209 const field_align = try field_ty.abiAlignmentSema(pt);
29210 break :a tuple_align.min(field_align);
29211 },
29197 },29212 },
29198 });29213 });
2919929214
src/codegen/c.zig+18-24
...@@ -611,7 +611,7 @@ pub const Function = struct {...@@ -611,7 +611,7 @@ pub const Function = struct {
611 const a = try Assignment.start(f, writer, ctype);611 const a = try Assignment.start(f, writer, ctype);
612 try f.writeCValue(writer, dst, .Other);612 try f.writeCValue(writer, dst, .Other);
613 try a.assign(f, writer);613 try a.assign(f, writer);
614 try f.writeCValue(writer, src, .Initializer);614 try f.writeCValue(writer, src, .Other);
615 try a.end(f, writer);615 try a.end(f, writer);
616 }616 }
617617
...@@ -2826,7 +2826,7 @@ pub fn genLazyFn(o: *Object, lazy_ctype_pool: *const CType.Pool, lazy_fn: LazyFn...@@ -2826,7 +2826,7 @@ pub fn genLazyFn(o: *Object, lazy_ctype_pool: *const CType.Pool, lazy_fn: LazyFn
2826 });2826 });
2827 try o.dg.renderTypeAndName(w, name_ty, .{ .identifier = "name" }, Const, .none, .complete);2827 try o.dg.renderTypeAndName(w, name_ty, .{ .identifier = "name" }, Const, .none, .complete);
2828 try w.writeAll(" = ");2828 try w.writeAll(" = ");
2829 try o.dg.renderValue(w, Value.fromInterned(name_val), .Initializer);2829 try o.dg.renderValue(w, Value.fromInterned(name_val), .StaticInitializer);
2830 try w.writeAll(";\n return (");2830 try w.writeAll(";\n return (");
2831 try o.dg.renderType(w, name_slice_ty);2831 try o.dg.renderType(w, name_slice_ty);
2832 try w.print("){{{}, {}}};\n", .{2832 try w.print("){{{}, {}}};\n", .{
...@@ -4044,7 +4044,7 @@ fn airStore(f: *Function, inst: Air.Inst.Index, safety: bool) !CValue {...@@ -4044,7 +4044,7 @@ fn airStore(f: *Function, inst: Air.Inst.Index, safety: bool) !CValue {
4044 const new_local = try f.allocLocal(inst, src_ty);4044 const new_local = try f.allocLocal(inst, src_ty);
4045 try f.writeCValue(writer, new_local, .Other);4045 try f.writeCValue(writer, new_local, .Other);
4046 try writer.writeAll(" = ");4046 try writer.writeAll(" = ");
4047 try f.writeCValue(writer, src_val, .Initializer);4047 try f.writeCValue(writer, src_val, .Other);
4048 try writer.writeAll(";\n");4048 try writer.writeAll(";\n");
40494049
4050 break :blk new_local;4050 break :blk new_local;
...@@ -4515,7 +4515,7 @@ fn airSlice(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -4515,7 +4515,7 @@ fn airSlice(f: *Function, inst: Air.Inst.Index) !CValue {
4515 const a = try Assignment.start(f, writer, .usize);4515 const a = try Assignment.start(f, writer, .usize);
4516 try f.writeCValueMember(writer, local, .{ .identifier = "len" });4516 try f.writeCValueMember(writer, local, .{ .identifier = "len" });
4517 try a.assign(f, writer);4517 try a.assign(f, writer);
4518 try f.writeCValue(writer, len, .Initializer);4518 try f.writeCValue(writer, len, .Other);
4519 try a.end(f, writer);4519 try a.end(f, writer);
4520 }4520 }
4521 return local;4521 return local;
...@@ -4933,7 +4933,7 @@ fn airSwitchDispatch(f: *Function, inst: Air.Inst.Index) !void {...@@ -4933,7 +4933,7 @@ fn airSwitchDispatch(f: *Function, inst: Air.Inst.Index) !void {
4933 const cond_local = f.loop_switch_conds.get(br.block_inst).?;4933 const cond_local = f.loop_switch_conds.get(br.block_inst).?;
4934 try f.writeCValue(writer, .{ .local = cond_local }, .Other);4934 try f.writeCValue(writer, .{ .local = cond_local }, .Other);
4935 try writer.writeAll(" = ");4935 try writer.writeAll(" = ");
4936 try f.writeCValue(writer, cond, .Initializer);4936 try f.writeCValue(writer, cond, .Other);
4937 try writer.writeAll(";\n");4937 try writer.writeAll(";\n");
4938 try writer.print("goto zig_switch_{d}_loop;", .{@intFromEnum(br.block_inst)});4938 try writer.print("goto zig_switch_{d}_loop;", .{@intFromEnum(br.block_inst)});
4939}4939}
...@@ -4978,14 +4978,8 @@ fn bitcast(f: *Function, dest_ty: Type, operand: CValue, operand_ty: Type) !CVal...@@ -4978,14 +4978,8 @@ fn bitcast(f: *Function, dest_ty: Type, operand: CValue, operand_ty: Type) !CVal
4978 const operand_lval = if (operand == .constant) blk: {4978 const operand_lval = if (operand == .constant) blk: {
4979 const operand_local = try f.allocLocal(null, operand_ty);4979 const operand_local = try f.allocLocal(null, operand_ty);
4980 try f.writeCValue(writer, operand_local, .Other);4980 try f.writeCValue(writer, operand_local, .Other);
4981 if (operand_ty.isAbiInt(zcu)) {4981 try writer.writeAll(" = ");
4982 try writer.writeAll(" = ");4982 try f.writeCValue(writer, operand, .Other);
4983 } else {
4984 try writer.writeAll(" = (");
4985 try f.renderType(writer, operand_ty);
4986 try writer.writeByte(')');
4987 }
4988 try f.writeCValue(writer, operand, .Initializer);
4989 try writer.writeAll(";\n");4983 try writer.writeAll(";\n");
4990 break :blk operand_local;4984 break :blk operand_local;
4991 } else operand;4985 } else operand;
...@@ -5697,7 +5691,7 @@ fn airOptionalPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -5697,7 +5691,7 @@ fn airOptionalPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
5697 const a = try Assignment.start(f, writer, opt_ctype);5691 const a = try Assignment.start(f, writer, opt_ctype);
5698 try f.writeCValueDeref(writer, operand);5692 try f.writeCValueDeref(writer, operand);
5699 try a.assign(f, writer);5693 try a.assign(f, writer);
5700 try f.object.dg.renderValue(writer, Value.false, .Initializer);5694 try f.object.dg.renderValue(writer, Value.false, .Other);
5701 try a.end(f, writer);5695 try a.end(f, writer);
5702 return .none;5696 return .none;
5703 },5697 },
...@@ -5717,7 +5711,7 @@ fn airOptionalPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -5717,7 +5711,7 @@ fn airOptionalPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
5717 const a = try Assignment.start(f, writer, opt_ctype);5711 const a = try Assignment.start(f, writer, opt_ctype);
5718 try f.writeCValueDerefMember(writer, operand, .{ .identifier = "is_null" });5712 try f.writeCValueDerefMember(writer, operand, .{ .identifier = "is_null" });
5719 try a.assign(f, writer);5713 try a.assign(f, writer);
5720 try f.object.dg.renderValue(writer, Value.false, .Initializer);5714 try f.object.dg.renderValue(writer, Value.false, .Other);
5721 try a.end(f, writer);5715 try a.end(f, writer);
5722 }5716 }
5723 if (f.liveness.isUnused(inst)) return .none;5717 if (f.liveness.isUnused(inst)) return .none;
...@@ -5843,7 +5837,7 @@ fn airFieldParentPtr(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -5843,7 +5837,7 @@ fn airFieldParentPtr(f: *Function, inst: Air.Inst.Index) !CValue {
5843 try writer.writeByte(')');5837 try writer.writeByte(')');
58445838
5845 switch (fieldLocation(container_ptr_ty, field_ptr_ty, extra.field_index, pt)) {5839 switch (fieldLocation(container_ptr_ty, field_ptr_ty, extra.field_index, pt)) {
5846 .begin => try f.writeCValue(writer, field_ptr_val, .Initializer),5840 .begin => try f.writeCValue(writer, field_ptr_val, .Other),
5847 .field => |field| {5841 .field => |field| {
5848 const u8_ptr_ty = try pt.adjustPtrTypeChild(field_ptr_ty, .u8);5842 const u8_ptr_ty = try pt.adjustPtrTypeChild(field_ptr_ty, .u8);
58495843
...@@ -5897,7 +5891,7 @@ fn fieldPtr(...@@ -5897,7 +5891,7 @@ fn fieldPtr(
5897 try writer.writeByte(')');5891 try writer.writeByte(')');
58985892
5899 switch (fieldLocation(container_ptr_ty, field_ptr_ty, field_index, pt)) {5893 switch (fieldLocation(container_ptr_ty, field_ptr_ty, field_index, pt)) {
5900 .begin => try f.writeCValue(writer, container_ptr_val, .Initializer),5894 .begin => try f.writeCValue(writer, container_ptr_val, .Other),
5901 .field => |field| {5895 .field => |field| {
5902 try writer.writeByte('&');5896 try writer.writeByte('&');
5903 try f.writeCValueDerefMember(writer, container_ptr_val, field);5897 try f.writeCValueDerefMember(writer, container_ptr_val, field);
...@@ -6020,7 +6014,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -6020,7 +6014,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
6020 const operand_local = try f.allocLocal(inst, struct_ty);6014 const operand_local = try f.allocLocal(inst, struct_ty);
6021 try f.writeCValue(writer, operand_local, .Other);6015 try f.writeCValue(writer, operand_local, .Other);
6022 try writer.writeAll(" = ");6016 try writer.writeAll(" = ");
6023 try f.writeCValue(writer, struct_byval, .Initializer);6017 try f.writeCValue(writer, struct_byval, .Other);
6024 try writer.writeAll(";\n");6018 try writer.writeAll(";\n");
6025 break :blk operand_local;6019 break :blk operand_local;
6026 } else struct_byval;6020 } else struct_byval;
...@@ -6118,7 +6112,7 @@ fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValu...@@ -6118,7 +6112,7 @@ fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValu
6118 try writer.writeAll(" = (");6112 try writer.writeAll(" = (");
6119 try f.renderType(writer, inst_ty);6113 try f.renderType(writer, inst_ty);
6120 try writer.writeByte(')');6114 try writer.writeByte(')');
6121 try f.writeCValue(writer, operand, .Initializer);6115 try f.writeCValue(writer, operand, .Other);
6122 try writer.writeAll(";\n");6116 try writer.writeAll(";\n");
6123 return local;6117 return local;
6124 }6118 }
...@@ -6163,7 +6157,7 @@ fn airWrapOptional(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -6163,7 +6157,7 @@ fn airWrapOptional(f: *Function, inst: Air.Inst.Index) !CValue {
6163 const a = try Assignment.start(f, writer, operand_ctype);6157 const a = try Assignment.start(f, writer, operand_ctype);
6164 try f.writeCValueMember(writer, local, .{ .identifier = "payload" });6158 try f.writeCValueMember(writer, local, .{ .identifier = "payload" });
6165 try a.assign(f, writer);6159 try a.assign(f, writer);
6166 try f.writeCValue(writer, operand, .Initializer);6160 try f.writeCValue(writer, operand, .Other);
6167 try a.end(f, writer);6161 try a.end(f, writer);
6168 }6162 }
6169 return local;6163 return local;
...@@ -6364,7 +6358,7 @@ fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -6364,7 +6358,7 @@ fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue {
6364 try f.writeCValueMember(writer, local, .{ .identifier = "ptr" });6358 try f.writeCValueMember(writer, local, .{ .identifier = "ptr" });
6365 try a.assign(f, writer);6359 try a.assign(f, writer);
6366 if (operand == .undef) {6360 if (operand == .undef) {
6367 try f.writeCValue(writer, .{ .undef = inst_ty.slicePtrFieldType(zcu) }, .Initializer);6361 try f.writeCValue(writer, .{ .undef = inst_ty.slicePtrFieldType(zcu) }, .Other);
6368 } else {6362 } else {
6369 const ptr_ctype = try f.ctypeFromType(ptr_ty, .complete);6363 const ptr_ctype = try f.ctypeFromType(ptr_ty, .complete);
6370 const ptr_child_ctype = ptr_ctype.info(ctype_pool).pointer.elem_ctype;6364 const ptr_child_ctype = ptr_ctype.info(ctype_pool).pointer.elem_ctype;
...@@ -6381,7 +6375,7 @@ fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -6381,7 +6375,7 @@ fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue {
6381 try writer.writeByte('&');6375 try writer.writeByte('&');
6382 try f.writeCValueDeref(writer, operand);6376 try f.writeCValueDeref(writer, operand);
6383 try writer.print("[{}]", .{try f.fmtIntLiteral(try pt.intValue(.usize, 0))});6377 try writer.print("[{}]", .{try f.fmtIntLiteral(try pt.intValue(.usize, 0))});
6384 } else try f.writeCValue(writer, operand, .Initializer);6378 } else try f.writeCValue(writer, operand, .Other);
6385 }6379 }
6386 try a.end(f, writer);6380 try a.end(f, writer);
6387 }6381 }
...@@ -6911,7 +6905,7 @@ fn airMemset(f: *Function, inst: Air.Inst.Index, safety: bool) !CValue {...@@ -6911,7 +6905,7 @@ fn airMemset(f: *Function, inst: Air.Inst.Index, safety: bool) !CValue {
6911 try writer.writeAll("for (");6905 try writer.writeAll("for (");
6912 try f.writeCValue(writer, index, .Other);6906 try f.writeCValue(writer, index, .Other);
6913 try writer.writeAll(" = ");6907 try writer.writeAll(" = ");
6914 try f.object.dg.renderValue(writer, try pt.intValue(.usize, 0), .Initializer);6908 try f.object.dg.renderValue(writer, try pt.intValue(.usize, 0), .Other);
6915 try writer.writeAll("; ");6909 try writer.writeAll("; ");
6916 try f.writeCValue(writer, index, .Other);6910 try f.writeCValue(writer, index, .Other);
6917 try writer.writeAll(" != ");6911 try writer.writeAll(" != ");
...@@ -7281,7 +7275,7 @@ fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -7281,7 +7275,7 @@ fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue {
7281 .float => try pt.floatValue(scalar_ty, std.math.nan(f128)),7275 .float => try pt.floatValue(scalar_ty, std.math.nan(f128)),
7282 else => unreachable,7276 else => unreachable,
7283 },7277 },
7284 }, .Initializer);7278 }, .Other);
7285 try writer.writeAll(";\n");7279 try writer.writeAll(";\n");
72867280
7287 const v = try Vectorize.start(f, inst, writer, operand_ty);7281 const v = try Vectorize.start(f, inst, writer, operand_ty);
test/behavior/tuple.zig+18
...@@ -603,3 +603,21 @@ test "empty union in tuple" {...@@ -603,3 +603,21 @@ test "empty union in tuple" {
603 try std.testing.expectEqualStrings("0", info.@"struct".fields[0].name);603 try std.testing.expectEqualStrings("0", info.@"struct".fields[0].name);
604 try std.testing.expect(@typeInfo(info.@"struct".fields[0].type) == .@"union");604 try std.testing.expect(@typeInfo(info.@"struct".fields[0].type) == .@"union");
605}605}
606
607test "field pointer of underaligned tuple" {
608 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
609 const S = struct {
610 fn doTheTest() !void {
611 const T = struct { u8, u32 };
612 var val: T align(2) = .{ 1, 2 };
613
614 comptime assert(@TypeOf(&val[0]) == *u8); // `u8` field pointer isn't overaligned
615 comptime assert(@TypeOf(&val[1]) == *align(2) u32); // `u32` field pointer is correctly underaligned
616
617 try expect(val[0] == 1);
618 try expect(val[1] == 2);
619 }
620 };
621 try S.doTheTest();
622 try comptime S.doTheTest();
623}
test/cases/compile_errors/runtime_store_to_comptime_field.zig created+19
...@@ -0,0 +1,19 @@
1const init: u32 = 1;
2fn rt() u32 {
3 return 3;
4}
5
6var tuple_val = .{init};
7export fn tuple_field() void {
8 tuple_val[0] = rt();
9}
10
11var struct_val = .{ .x = init };
12export fn struct_field() void {
13 struct_val.x = rt();
14}
15
16// error
17//
18// :8:14: error: cannot store runtime value in compile time variable
19// :13:15: error: cannot store runtime value in compile time variable