Sema: fix comptime union initialization

The mechanism behind initializing a union's tag is a bit complicated,
depending on whether the union is initialized at runtime,
forced comptime, or implicit comptime.

`coerce_result_ptr` now does not force a block to be a runtime context;
instead of adding runtime instructions directly, it forwards analysis to
the respective functions for initializing optionals and error unions.

`validateUnionInit` now has logic to still emit a runtime
`set_union_tag` instruction even if the union pointer is comptime-known,
for the case of a pointer that is not comptime mutable, such as a
variable or the result of `@intToPtr`.

`validateStructInit` looks for a completely different pattern now; it
now handles the possibility of the corresponding AIR instruction for
the `field_ptr` to be missing or the corresponding `store` to be missing.
See the new comment added to the function for more details. An
equivalent change should probably be made to `validateArrayInit`.

`analyzeOptionalPayloadPtr` and `analyzeErrUnionPayloadPtr` functions now
emit a `optional_payload_ptr_set` or `errunion_payload_ptr_set`
instruction respectively if `initializing` is true and the pointer value
is not comptime-mutable.

`storePtr2` now tries the comptime pointer store before checking if the
element type has one possible value because the comptime pointer store
can have side effects of setting a union tag, setting an optional payload
non-null, or setting an error union to be non-error.

The LLVM backend `lowerParentPtr` function is improved to take into
account the differences in how the LLVM values are lowered depending on
the Zig type. It now handles unions correctly as well as additionally
handling optionals and error unions.

In the LLVM backend, the instructions `optional_payload_ptr_set` and
`errunion_payload_ptr_set` check liveness analysis and only do the side
effects in the case the result of the instruction is unused.

A few wasm and C backend test cases regressed, but they are due to TODOs
in lowering of constants, so this is progress.
This commit is contained in:
Andrew Kelley 2022-02-21 22:50:38 -07:00
parent 7f48bc3493
commit 9dc98fbabb
6 changed files with 239 additions and 110 deletions

View file

@ -1586,19 +1586,23 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
}), }),
); );
}, },
.decl_ref_mut => {
const ptr_ty = try Type.ptr(sema.arena, .{
.pointee_type = pointee_ty,
.@"addrspace" = addr_space,
});
return sema.addConstant(ptr_ty, ptr_val);
},
else => {}, else => {},
} }
} }
} }
try sema.requireRuntimeBlock(block, src); // We would like to rely on the mechanism below even for comptime values.
// However in the case that the pointer points to comptime-mutable value,
// we cannot do it.
if (try sema.resolveDefinedValue(block, src, ptr)) |ptr_val| {
if (ptr_val.isComptimeMutablePtr()) {
const ptr_ty = try Type.ptr(sema.arena, .{
.pointee_type = pointee_ty,
.@"addrspace" = addr_space,
});
return sema.addConstant(ptr_ty, ptr_val);
}
}
// Make a dummy store through the pointer to test the coercion. // Make a dummy store through the pointer to test the coercion.
// We will then use the generated instructions to decide what // We will then use the generated instructions to decide what
@ -1638,7 +1642,7 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
switch (air_tags[trash_inst]) { switch (air_tags[trash_inst]) {
.bitcast => { .bitcast => {
if (Air.indexToRef(trash_inst) == dummy_operand) { if (Air.indexToRef(trash_inst) == dummy_operand) {
return block.addBitCast(ptr_ty, new_ptr); return sema.bitCast(block, ptr_ty, new_ptr, src);
} }
const ty_op = air_datas[trash_inst].ty_op; const ty_op = air_datas[trash_inst].ty_op;
const operand_ty = sema.getTmpAir().typeOf(ty_op.operand); const operand_ty = sema.getTmpAir().typeOf(ty_op.operand);
@ -1646,28 +1650,16 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
.pointee_type = operand_ty, .pointee_type = operand_ty,
.@"addrspace" = addr_space, .@"addrspace" = addr_space,
}); });
new_ptr = try block.addBitCast(ptr_operand_ty, new_ptr); new_ptr = try sema.bitCast(block, ptr_operand_ty, new_ptr, src);
}, },
.wrap_optional => { .wrap_optional => {
const ty_op = air_datas[trash_inst].ty_op; new_ptr = try sema.analyzeOptionalPayloadPtr(block, src, new_ptr, false, true);
const payload_ty = sema.getTmpAir().typeOf(ty_op.operand);
const ptr_payload_ty = try Type.ptr(sema.arena, .{
.pointee_type = payload_ty,
.@"addrspace" = addr_space,
});
new_ptr = try block.addTyOp(.optional_payload_ptr_set, ptr_payload_ty, new_ptr);
}, },
.wrap_errunion_err => { .wrap_errunion_err => {
return sema.fail(block, src, "TODO coerce_result_ptr wrap_errunion_err", .{}); return sema.fail(block, src, "TODO coerce_result_ptr wrap_errunion_err", .{});
}, },
.wrap_errunion_payload => { .wrap_errunion_payload => {
const ty_op = air_datas[trash_inst].ty_op; new_ptr = try sema.analyzeErrUnionPayloadPtr(block, src, new_ptr, false, true);
const payload_ty = sema.getTmpAir().typeOf(ty_op.operand);
const ptr_payload_ty = try Type.ptr(sema.arena, .{
.pointee_type = payload_ty,
.@"addrspace" = addr_space,
});
new_ptr = try block.addTyOp(.errunion_payload_ptr_set, ptr_payload_ty, new_ptr);
}, },
else => { else => {
if (std.debug.runtime_safety) { if (std.debug.runtime_safety) {
@ -2774,9 +2766,16 @@ fn validateUnionInit(
const field_index = @intCast(u32, field_index_big); const field_index = @intCast(u32, field_index_big);
// Handle the possibility of the union value being comptime-known. // Handle the possibility of the union value being comptime-known.
const union_ptr_inst = Air.refToIndex(sema.resolveInst(field_ptr_extra.lhs)).?; const union_ptr_inst = Air.refToIndex(union_ptr).?;
switch (sema.air_instructions.items(.tag)[union_ptr_inst]) { switch (sema.air_instructions.items(.tag)[union_ptr_inst]) {
.constant => return, // In this case the tag has already been set. No validation to do. .constant => {
if (try sema.resolveDefinedValue(block, init_src, union_ptr)) |ptr_val| {
if (ptr_val.isComptimeMutablePtr()) {
// In this case the tag has already been set. No validation to do.
return;
}
}
},
.bitcast => { .bitcast => {
// TODO here we need to go back and see if we need to convert the union // TODO here we need to go back and see if we need to convert the union
// to a comptime-known value. In such case, we must delete all the instructions // to a comptime-known value. In such case, we must delete all the instructions
@ -2895,7 +2894,7 @@ fn validateStructInit(
} }
var struct_is_comptime = true; var struct_is_comptime = true;
var first_block_index: usize = std.math.maxInt(u32); var first_block_index = block.instructions.items.len;
const air_tags = sema.air_instructions.items(.tag); const air_tags = sema.air_instructions.items(.tag);
const air_datas = sema.air_instructions.items(.data); const air_datas = sema.air_instructions.items(.data);
@ -2904,7 +2903,7 @@ fn validateStructInit(
// ends up being comptime-known. // ends up being comptime-known.
const field_values = try sema.arena.alloc(Value, fields.len); const field_values = try sema.arena.alloc(Value, fields.len);
for (found_fields) |field_ptr, i| { field: for (found_fields) |field_ptr, i| {
const field = fields[i]; const field = fields[i];
if (field_ptr != 0) { if (field_ptr != 0) {
@ -2919,40 +2918,57 @@ fn validateStructInit(
const field_ptr_air_ref = sema.inst_map.get(field_ptr).?; const field_ptr_air_ref = sema.inst_map.get(field_ptr).?;
const field_ptr_air_inst = Air.refToIndex(field_ptr_air_ref).?; const field_ptr_air_inst = Air.refToIndex(field_ptr_air_ref).?;
// Find the block index of the field_ptr so that we can look at the next
// instruction after it within the same block. //std.debug.print("validateStructInit (field_ptr_air_inst=%{d}):\n", .{
// field_ptr_air_inst,
//});
//for (block.instructions.items) |item| {
// std.debug.print(" %{d} = {s}\n", .{item, @tagName(air_tags[item])});
//}
// We expect to see something like this in the current block AIR:
// %a = field_ptr(...)
// store(%a, %b)
// If %b is a comptime operand, this field is comptime.
//
// However, in the case of a comptime-known pointer to a struct, the
// the field_ptr instruction is missing, so we have to pattern-match
// based only on the store instructions.
// `first_block_index` needs to point to the `field_ptr` if it exists;
// the `store` otherwise.
//
// It's also possible for there to be no store instruction, in the case
// of nested `coerce_result_ptr` instructions. If we see the `field_ptr`
// but we have not found a `store`, treat as a runtime-known field.
// Possible performance enhancement: save the `block_index` between iterations // Possible performance enhancement: save the `block_index` between iterations
// of the for loop. // of the for loop.
const next_air_inst = inst: { var block_index = block.instructions.items.len - 1;
var block_index = block.instructions.items.len - 1; while (block_index > 0) : (block_index -= 1) {
while (block.instructions.items[block_index] != field_ptr_air_inst) { const store_inst = block.instructions.items[block_index];
block_index -= 1; if (store_inst == field_ptr_air_inst) {
}
first_block_index = @minimum(first_block_index, block_index);
break :inst block.instructions.items[block_index + 1];
};
// If the next instructon is a store with a comptime operand, this field
// is comptime.
switch (air_tags[next_air_inst]) {
.store => {
const bin_op = air_datas[next_air_inst].bin_op;
if (bin_op.lhs != field_ptr_air_ref) {
struct_is_comptime = false;
continue;
}
if (try sema.resolveMaybeUndefValAllowVariables(block, field_src, bin_op.rhs)) |val| {
field_values[i] = val;
} else {
struct_is_comptime = false;
}
continue;
},
else => {
struct_is_comptime = false; struct_is_comptime = false;
continue; continue :field;
}, }
if (air_tags[store_inst] != .store) continue;
const bin_op = air_datas[store_inst].bin_op;
if (bin_op.lhs != field_ptr_air_ref) continue;
if (block_index > 0 and
field_ptr_air_inst == block.instructions.items[block_index - 1])
{
first_block_index = @minimum(first_block_index, block_index - 1);
} else {
first_block_index = @minimum(first_block_index, block_index);
}
if (try sema.resolveMaybeUndefValAllowVariables(block, field_src, bin_op.rhs)) |val| {
field_values[i] = val;
} else {
struct_is_comptime = false;
}
continue :field;
} }
struct_is_comptime = false;
continue :field;
} }
const field_name = struct_obj.fields.keys()[i]; const field_name = struct_obj.fields.keys()[i];
@ -5355,21 +5371,28 @@ fn analyzeOptionalPayloadPtr(
.@"addrspace" = optional_ptr_ty.ptrAddressSpace(), .@"addrspace" = optional_ptr_ty.ptrAddressSpace(),
}); });
if (try sema.resolveDefinedValue(block, src, optional_ptr)) |pointer_val| { if (try sema.resolveDefinedValue(block, src, optional_ptr)) |ptr_val| {
if (initializing) { if (initializing) {
if (!ptr_val.isComptimeMutablePtr()) {
// If the pointer resulting from this function was stored at comptime,
// the optional non-null bit would be set that way. But in this case,
// we need to emit a runtime instruction to do it.
try sema.requireRuntimeBlock(block, src);
_ = try block.addTyOp(.optional_payload_ptr_set, child_pointer, optional_ptr);
}
return sema.addConstant( return sema.addConstant(
child_pointer, child_pointer,
try Value.Tag.opt_payload_ptr.create(sema.arena, pointer_val), try Value.Tag.opt_payload_ptr.create(sema.arena, ptr_val),
); );
} }
if (try sema.pointerDeref(block, src, pointer_val, optional_ptr_ty)) |val| { if (try sema.pointerDeref(block, src, ptr_val, optional_ptr_ty)) |val| {
if (val.isNull()) { if (val.isNull()) {
return sema.fail(block, src, "unable to unwrap null", .{}); return sema.fail(block, src, "unable to unwrap null", .{});
} }
// The same Value represents the pointer to the optional and the payload. // The same Value represents the pointer to the optional and the payload.
return sema.addConstant( return sema.addConstant(
child_pointer, child_pointer,
try Value.Tag.opt_payload_ptr.create(sema.arena, pointer_val), try Value.Tag.opt_payload_ptr.create(sema.arena, ptr_val),
); );
} }
} }
@ -5379,10 +5402,11 @@ fn analyzeOptionalPayloadPtr(
const is_non_null = try block.addUnOp(.is_non_null_ptr, optional_ptr); const is_non_null = try block.addUnOp(.is_non_null_ptr, optional_ptr);
try sema.addSafetyCheck(block, is_non_null, .unwrap_null); try sema.addSafetyCheck(block, is_non_null, .unwrap_null);
} }
return block.addTyOp(if (initializing) const air_tag: Air.Inst.Tag = if (initializing)
.optional_payload_ptr_set .optional_payload_ptr_set
else else
.optional_payload_ptr, child_pointer, optional_ptr); .optional_payload_ptr;
return block.addTyOp(air_tag, child_pointer, optional_ptr);
} }
/// Value in, value out. /// Value in, value out.
@ -5510,21 +5534,28 @@ fn analyzeErrUnionPayloadPtr(
.@"addrspace" = operand_ty.ptrAddressSpace(), .@"addrspace" = operand_ty.ptrAddressSpace(),
}); });
if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { if (try sema.resolveDefinedValue(block, src, operand)) |ptr_val| {
if (initializing) { if (initializing) {
if (!ptr_val.isComptimeMutablePtr()) {
// If the pointer resulting from this function was stored at comptime,
// the error union error code would be set that way. But in this case,
// we need to emit a runtime instruction to do it.
try sema.requireRuntimeBlock(block, src);
_ = try block.addTyOp(.errunion_payload_ptr_set, operand_pointer_ty, operand);
}
return sema.addConstant( return sema.addConstant(
operand_pointer_ty, operand_pointer_ty,
try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), try Value.Tag.eu_payload_ptr.create(sema.arena, ptr_val),
); );
} }
if (try sema.pointerDeref(block, src, pointer_val, operand_ty)) |val| { if (try sema.pointerDeref(block, src, ptr_val, operand_ty)) |val| {
if (val.getError()) |name| { if (val.getError()) |name| {
return sema.fail(block, src, "caught unexpected error '{s}'", .{name}); return sema.fail(block, src, "caught unexpected error '{s}'", .{name});
} }
return sema.addConstant( return sema.addConstant(
operand_pointer_ty, operand_pointer_ty,
try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), try Value.Tag.eu_payload_ptr.create(sema.arena, ptr_val),
); );
} }
} }
@ -5534,10 +5565,11 @@ fn analyzeErrUnionPayloadPtr(
const is_non_err = try block.addUnOp(.is_err, operand); const is_non_err = try block.addUnOp(.is_err, operand);
try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion); try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion);
} }
return block.addTyOp(if (initializing) const air_tag: Air.Inst.Tag = if (initializing)
.errunion_payload_ptr_set .errunion_payload_ptr_set
else else
.unwrap_errunion_payload_ptr, operand_pointer_ty, operand); .unwrap_errunion_payload_ptr;
return block.addTyOp(air_tag, operand_pointer_ty, operand);
} }
/// Value in, value out /// Value in, value out
@ -15242,8 +15274,6 @@ fn storePtr2(
} }
const operand = try sema.coerce(block, elem_ty, uncasted_operand, operand_src); const operand = try sema.coerce(block, elem_ty, uncasted_operand, operand_src);
if ((try sema.typeHasOnePossibleValue(block, src, elem_ty)) != null)
return;
const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: { const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: {
const maybe_operand_val = try sema.resolveMaybeUndefVal(block, operand_src, operand); const maybe_operand_val = try sema.resolveMaybeUndefVal(block, operand_src, operand);
@ -15257,6 +15287,11 @@ fn storePtr2(
} else break :rs ptr_src; } else break :rs ptr_src;
} else ptr_src; } else ptr_src;
// We do this after the possible comptime store above, for the case of field_ptr stores
// to unions because we want the comptime tag to be set, even if the field type is void.
if ((try sema.typeHasOnePossibleValue(block, src, elem_ty)) != null)
return;
// TODO handle if the element type requires comptime // TODO handle if the element type requires comptime
try sema.requireRuntimeBlock(block, runtime_src); try sema.requireRuntimeBlock(block, runtime_src);

View file

@ -1329,32 +1329,25 @@ pub const DeclGen = struct {
const llvm_int = llvm_usize.constInt(tv.val.toUnsignedInt(), .False); const llvm_int = llvm_usize.constInt(tv.val.toUnsignedInt(), .False);
return llvm_int.constIntToPtr(try dg.llvmType(tv.ty)); return llvm_int.constIntToPtr(try dg.llvmType(tv.ty));
}, },
.field_ptr => { .field_ptr, .opt_payload_ptr, .eu_payload_ptr => {
const field_ptr = tv.val.castTag(.field_ptr).?.data; const parent = try dg.lowerParentPtr(tv.val);
const parent_ptr = try dg.lowerParentPtr(field_ptr.container_ptr); return parent.llvm_ptr.constBitCast(try dg.llvmType(tv.ty));
const llvm_u32 = dg.context.intType(32);
const indices: [2]*const llvm.Value = .{
llvm_u32.constInt(0, .False),
llvm_u32.constInt(field_ptr.field_index, .False),
};
const uncasted = parent_ptr.constInBoundsGEP(&indices, indices.len);
return uncasted.constBitCast(try dg.llvmType(tv.ty));
}, },
.elem_ptr => { .elem_ptr => {
const elem_ptr = tv.val.castTag(.elem_ptr).?.data; const elem_ptr = tv.val.castTag(.elem_ptr).?.data;
const parent_ptr = try dg.lowerParentPtr(elem_ptr.array_ptr); const parent = try dg.lowerParentPtr(elem_ptr.array_ptr);
const llvm_usize = try dg.llvmType(Type.usize); const llvm_usize = try dg.llvmType(Type.usize);
if (parent_ptr.typeOf().getElementType().getTypeKind() == .Array) { if (parent.llvm_ptr.typeOf().getElementType().getTypeKind() == .Array) {
const indices: [2]*const llvm.Value = .{ const indices: [2]*const llvm.Value = .{
llvm_usize.constInt(0, .False), llvm_usize.constInt(0, .False),
llvm_usize.constInt(elem_ptr.index, .False), llvm_usize.constInt(elem_ptr.index, .False),
}; };
return parent_ptr.constInBoundsGEP(&indices, indices.len); return parent.llvm_ptr.constInBoundsGEP(&indices, indices.len);
} else { } else {
const indices: [1]*const llvm.Value = .{ const indices: [1]*const llvm.Value = .{
llvm_usize.constInt(elem_ptr.index, .False), llvm_usize.constInt(elem_ptr.index, .False),
}; };
return parent_ptr.constInBoundsGEP(&indices, indices.len); return parent.llvm_ptr.constInBoundsGEP(&indices, indices.len);
} }
}, },
.null_value, .zero => { .null_value, .zero => {
@ -1800,11 +1793,7 @@ pub const DeclGen = struct {
llvm_ptr: *const llvm.Value, llvm_ptr: *const llvm.Value,
}; };
fn lowerParentPtrDecl( fn lowerParentPtrDecl(dg: *DeclGen, ptr_val: Value, decl: *Module.Decl) Error!ParentPtr {
dg: *DeclGen,
ptr_val: Value,
decl: *Module.Decl,
) Error!ParentPtr {
decl.markAlive(); decl.markAlive();
var ptr_ty_payload: Type.Payload.ElemType = .{ var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer }, .base = .{ .tag = .single_mut_pointer },
@ -1818,42 +1807,134 @@ pub const DeclGen = struct {
}; };
} }
fn lowerParentPtr(dg: *DeclGen, ptr_val: Value) Error!*const llvm.Value { fn lowerParentPtr(dg: *DeclGen, ptr_val: Value) Error!ParentPtr {
switch (ptr_val.tag()) { switch (ptr_val.tag()) {
.decl_ref_mut => { .decl_ref_mut => {
const decl = ptr_val.castTag(.decl_ref_mut).?.data.decl; const decl = ptr_val.castTag(.decl_ref_mut).?.data.decl;
return (try dg.lowerParentPtrDecl(ptr_val, decl)).llvm_ptr; return dg.lowerParentPtrDecl(ptr_val, decl);
}, },
.decl_ref => { .decl_ref => {
const decl = ptr_val.castTag(.decl_ref).?.data; const decl = ptr_val.castTag(.decl_ref).?.data;
return (try dg.lowerParentPtrDecl(ptr_val, decl)).llvm_ptr; return dg.lowerParentPtrDecl(ptr_val, decl);
}, },
.variable => { .variable => {
const decl = ptr_val.castTag(.variable).?.data.owner_decl; const decl = ptr_val.castTag(.variable).?.data.owner_decl;
return (try dg.lowerParentPtrDecl(ptr_val, decl)).llvm_ptr; return dg.lowerParentPtrDecl(ptr_val, decl);
}, },
.field_ptr => { .field_ptr => {
const field_ptr = ptr_val.castTag(.field_ptr).?.data; const field_ptr = ptr_val.castTag(.field_ptr).?.data;
const parent_ptr = try dg.lowerParentPtr(field_ptr.container_ptr); const parent = try dg.lowerParentPtr(field_ptr.container_ptr);
const field_index = @intCast(u32, field_ptr.field_index);
const llvm_u32 = dg.context.intType(32); const llvm_u32 = dg.context.intType(32);
const indices: [2]*const llvm.Value = .{ const target = dg.module.getTarget();
llvm_u32.constInt(0, .False), switch (parent.ty.zigTypeTag()) {
llvm_u32.constInt(field_ptr.field_index, .False), .Union => {
}; const fields = parent.ty.unionFields();
return parent_ptr.constInBoundsGEP(&indices, indices.len); const layout = parent.ty.unionGetLayout(target);
const field_ty = fields.values()[field_index].ty;
if (layout.payload_size == 0) {
// In this case a pointer to the union and a pointer to any
// (void) payload is the same.
return ParentPtr{
.llvm_ptr = parent.llvm_ptr,
.ty = field_ty,
};
}
if (layout.tag_size == 0) {
const indices: [2]*const llvm.Value = .{
llvm_u32.constInt(0, .False),
llvm_u32.constInt(0, .False),
};
return ParentPtr{
.llvm_ptr = parent.llvm_ptr.constInBoundsGEP(&indices, indices.len),
.ty = field_ty,
};
}
const llvm_pl_index = @boolToInt(layout.tag_align >= layout.payload_align);
const indices: [2]*const llvm.Value = .{
llvm_u32.constInt(0, .False),
llvm_u32.constInt(llvm_pl_index, .False),
};
return ParentPtr{
.llvm_ptr = parent.llvm_ptr.constInBoundsGEP(&indices, indices.len),
.ty = field_ty,
};
},
.Struct => {
var ty_buf: Type.Payload.Pointer = undefined;
const llvm_field_index = llvmFieldIndex(parent.ty, field_index, target, &ty_buf).?;
const indices: [2]*const llvm.Value = .{
llvm_u32.constInt(0, .False),
llvm_u32.constInt(llvm_field_index, .False),
};
return ParentPtr{
.llvm_ptr = parent.llvm_ptr.constInBoundsGEP(&indices, indices.len),
.ty = parent.ty.structFieldType(field_index),
};
},
else => unreachable,
}
}, },
.elem_ptr => { .elem_ptr => {
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data; const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
const parent_ptr = try dg.lowerParentPtr(elem_ptr.array_ptr); const parent = try dg.lowerParentPtr(elem_ptr.array_ptr);
const llvm_usize = try dg.llvmType(Type.usize); const llvm_usize = try dg.llvmType(Type.usize);
const indices: [2]*const llvm.Value = .{ const indices: [2]*const llvm.Value = .{
llvm_usize.constInt(0, .False), llvm_usize.constInt(0, .False),
llvm_usize.constInt(elem_ptr.index, .False), llvm_usize.constInt(elem_ptr.index, .False),
}; };
return parent_ptr.constInBoundsGEP(&indices, indices.len); return ParentPtr{
.llvm_ptr = parent.llvm_ptr.constInBoundsGEP(&indices, indices.len),
.ty = parent.ty.childType(),
};
},
.opt_payload_ptr => {
const opt_payload_ptr = ptr_val.castTag(.opt_payload_ptr).?.data;
const parent = try dg.lowerParentPtr(opt_payload_ptr);
var buf: Type.Payload.ElemType = undefined;
const payload_ty = parent.ty.optionalChild(&buf);
if (!payload_ty.hasRuntimeBits() or parent.ty.isPtrLikeOptional()) {
// In this case, we represent pointer to optional the same as pointer
// to the payload.
return ParentPtr{
.llvm_ptr = parent.llvm_ptr,
.ty = payload_ty,
};
}
const llvm_u32 = dg.context.intType(32);
const indices: [2]*const llvm.Value = .{
llvm_u32.constInt(0, .False),
llvm_u32.constInt(0, .False),
};
return ParentPtr{
.llvm_ptr = parent.llvm_ptr.constInBoundsGEP(&indices, indices.len),
.ty = payload_ty,
};
},
.eu_payload_ptr => {
const eu_payload_ptr = ptr_val.castTag(.eu_payload_ptr).?.data;
const parent = try dg.lowerParentPtr(eu_payload_ptr);
const payload_ty = parent.ty.errorUnionPayload();
if (!payload_ty.hasRuntimeBits()) {
// In this case, we represent pointer to error union the same as pointer
// to the payload.
return ParentPtr{
.llvm_ptr = parent.llvm_ptr,
.ty = payload_ty,
};
}
const llvm_u32 = dg.context.intType(32);
const indices: [2]*const llvm.Value = .{
llvm_u32.constInt(0, .False),
llvm_u32.constInt(1, .False),
};
return ParentPtr{
.llvm_ptr = parent.llvm_ptr.constInBoundsGEP(&indices, indices.len),
.ty = payload_ty,
};
}, },
.opt_payload_ptr => return dg.todo("implement lowerParentPtr for optional payload", .{}),
.eu_payload_ptr => return dg.todo("implement lowerParentPtr for error union payload", .{}),
else => unreachable, else => unreachable,
} }
} }
@ -3142,7 +3223,9 @@ pub const FuncGen = struct {
const non_null_ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""); const non_null_ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
_ = self.builder.buildStore(non_null_bit, non_null_ptr); _ = self.builder.buildStore(non_null_bit, non_null_ptr);
} }
// Then return the payload pointer. // Then return the payload pointer (only if it's used).
if (self.liveness.isUnused(inst))
return null;
const indices: [2]*const llvm.Value = .{ const indices: [2]*const llvm.Value = .{
index_type.constNull(), // dereference the pointer index_type.constNull(), // dereference the pointer
index_type.constNull(), // first field is the payload index_type.constNull(), // first field is the payload
@ -3236,7 +3319,9 @@ pub const FuncGen = struct {
const non_null_ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""); const non_null_ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
_ = self.builder.buildStore(non_error_val, non_null_ptr); _ = self.builder.buildStore(non_error_val, non_null_ptr);
} }
// Then return the payload pointer. // Then return the payload pointer (only if it is used).
if (self.liveness.isUnused(inst))
return null;
const indices: [2]*const llvm.Value = .{ const indices: [2]*const llvm.Value = .{
index_type.constNull(), // dereference the pointer index_type.constNull(), // dereference the pointer
index_type.constInt(1, .False), // second field is the payload index_type.constInt(1, .False), // second field is the payload
@ -5257,7 +5342,7 @@ fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) llvm.Ca
} }
/// Take into account 0 bit fields. Returns null if an llvm field could not be found. This only /// Take into account 0 bit fields. Returns null if an llvm field could not be found. This only
/// happends if you want the field index of a zero sized field at the end of the struct. /// happens if you want the field index of a zero sized field at the end of the struct.
fn llvmFieldIndex( fn llvmFieldIndex(
ty: Type, ty: Type,
field_index: u32, field_index: u32,

View file

@ -12,6 +12,8 @@ fn couldFail() anyerror!i32 {
var some_struct: SomeStruct = undefined; var some_struct: SomeStruct = undefined;
test "fixed" { test "fixed" {
if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest; // TODO
some_struct = SomeStruct{ some_struct = SomeStruct{
.field = couldFail() catch @as(i32, 0), .field = couldFail() catch @as(i32, 0),
}; };

View file

@ -63,6 +63,8 @@ test "ignore lval with underscore (for loop)" {
} }
test "basic for loop" { test "basic for loop" {
if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest; // TODO
const expected_result = [_]u8{ 9, 8, 7, 6, 0, 1, 2, 3 } ** 3; const expected_result = [_]u8{ 9, 8, 7, 6, 0, 1, 2, 3 } ** 3;
var buffer: [expected_result.len]u8 = undefined; var buffer: [expected_result.len]u8 = undefined;

View file

@ -72,6 +72,8 @@ test "optional with void type" {
} }
test "address of unwrap optional" { test "address of unwrap optional" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO

View file

@ -348,6 +348,9 @@ const Foo1 = union(enum) {
var glbl: Foo1 = undefined; var glbl: Foo1 = undefined;
test "global union with single field is correctly initialized" { test "global union with single field is correctly initialized" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
glbl = Foo1{ glbl = Foo1{
.f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 }, .f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 },
}; };
@ -363,6 +366,7 @@ var glbl_array: [2]FooUnion = undefined;
test "initialize global array of union" { test "initialize global array of union" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
glbl_array[1] = FooUnion{ .U1 = 2 }; glbl_array[1] = FooUnion{ .U1 = 2 };
glbl_array[0] = FooUnion{ .U0 = 1 }; glbl_array[0] = FooUnion{ .U0 = 1 };
@ -487,8 +491,7 @@ test "tagged union with all void fields but a meaningful tag" {
} }
}; };
try S.doTheTest(); try S.doTheTest();
// TODO enable the test at comptime too comptime try S.doTheTest();
//comptime try S.doTheTest();
} }
test "union(enum(u32)) with specified and unspecified tag values" { test "union(enum(u32)) with specified and unspecified tag values" {