diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-11-16 19:50:39 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-11-16 19:50:39 -0500 |
| commit | 0e8673f53415e367ee5db9e1384398e0905c5a35 (patch) | |
| tree | d7d2af6503aea7aa499c1a5eabf3c2f5a3591d3a /src/codegen | |
| parent | 952d865bd231834adad30905c469edc5a46d000a (diff) | |
| parent | 09588c795c08064971f61ee147d06972f0add94e (diff) | |
| download | zig-0e8673f53415e367ee5db9e1384398e0905c5a35.tar.gz zig-0e8673f53415e367ee5db9e1384398e0905c5a35.zip | |
Merge pull request #10152 from drew-gpf/master
C backend: fix most cast and all pointer+generics behavior tests
Diffstat (limited to 'src/codegen')
| -rw-r--r-- | src/codegen/c.zig | 201 | ||||
| -rw-r--r-- | src/codegen/llvm.zig | 30 |
2 files changed, 191 insertions, 40 deletions
diff --git a/src/codegen/c.zig b/src/codegen/c.zig index e95a5a77ec..3aef5a8f92 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -19,6 +19,7 @@ const Zir = @import("../Zir.zig"); const Liveness = @import("../Liveness.zig"); const Mutability = enum { Const, Mut }; +const BigIntConst = std.math.big.int.Const; pub const CValue = union(enum) { none: void, @@ -226,13 +227,59 @@ pub const DeclGen = struct { try dg.renderDeclName(decl, writer); } + fn renderInt128( + writer: anytype, + int_val: anytype, + ) error{ OutOfMemory, AnalysisFail }!void { + const int_info = @typeInfo(@TypeOf(int_val)).Int; + const is_signed = int_info.signedness == .signed; + const is_neg = int_val < 0; + comptime assert(int_info.bits > 64 and int_info.bits <= 128); + + // Clang and GCC don't support 128-bit integer constants but will hopefully unfold them + // if we construct one manually. + const magnitude = std.math.absCast(int_val); + + const high = @truncate(u64, magnitude >> 64); + const low = @truncate(u64, magnitude); + + // (int128_t)/<->( ( (uint128_t)( val_high << 64 )u ) + (uint128_t)val_low/u ) + if (is_signed) try writer.writeAll("(int128_t)"); + if (is_neg) try writer.writeByte('-'); + + assert(high > 0); + try writer.print("(((uint128_t)0x{x}u<<64)", .{high}); + + if (low > 0) + try writer.print("+(uint128_t)0x{x}u", .{low}); + + return writer.writeByte(')'); + } + + fn renderBigIntConst( + dg: *DeclGen, + writer: anytype, + val: BigIntConst, + signed: bool, + ) error{ OutOfMemory, AnalysisFail }!void { + if (signed) { + try renderInt128(writer, val.to(i128) catch { + return dg.fail("TODO implement integer constants larger than 128 bits", .{}); + }); + } else { + try renderInt128(writer, val.to(u128) catch { + return dg.fail("TODO implement integer constants larger than 128 bits", .{}); + }); + } + } + fn renderValue( dg: *DeclGen, writer: anytype, ty: Type, val: Value, ) error{ OutOfMemory, AnalysisFail }!void { - if (val.isUndef()) { + if (val.isUndefDeep()) { switch (ty.zigTypeTag()) { // Using '{}' for integer and floats seemed to error C compilers (both GCC and Clang) // with 'error: expected expression' (including when built with 'zig cc') @@ -240,18 +287,18 @@ pub const DeclGen = struct { const c_bits = toCIntBits(ty.intInfo(dg.module.getTarget()).bits) orelse return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); switch (c_bits) { - 8 => return writer.writeAll("0xaaU"), - 16 => return writer.writeAll("0xaaaaU"), - 32 => return writer.writeAll("0xaaaaaaaaU"), - 64 => return writer.writeAll("0xaaaaaaaaaaaaaaaaUL"), - 128 => return writer.writeAll("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaULL"), + 8 => return writer.writeAll("0xaau"), + 16 => return writer.writeAll("0xaaaau"), + 32 => return writer.writeAll("0xaaaaaaaau"), + 64 => return writer.writeAll("0xaaaaaaaaaaaaaaaau"), + 128 => return renderInt128(writer, @as(u128, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)), else => unreachable, } }, .Float => { switch (ty.floatBits(dg.module.getTarget())) { - 32 => return writer.writeAll("zig_bitcast_f32_u32(0xaaaaaaaa)"), - 64 => return writer.writeAll("zig_bitcast_f64_u64(0xaaaaaaaaaaaaaaaa)"), + 32 => return writer.writeAll("zig_bitcast_f32_u32(0xaaaaaaaau)"), + 64 => return writer.writeAll("zig_bitcast_f64_u64(0xaaaaaaaaaaaaaaaau)"), else => return dg.fail("TODO float types > 64 bits are not support in renderValue() as of now", .{}), } }, @@ -265,10 +312,14 @@ pub const DeclGen = struct { } } switch (ty.zigTypeTag()) { - .Int => { - if (ty.isSignedInt()) - return writer.print("{d}", .{val.toSignedInt()}); - return writer.print("{d}", .{val.toUnsignedInt()}); + .Int => switch (val.tag()) { + .int_big_positive => try dg.renderBigIntConst(writer, val.castTag(.int_big_positive).?.asBigInt(), ty.isSignedInt()), + .int_big_negative => try dg.renderBigIntConst(writer, val.castTag(.int_big_negative).?.asBigInt(), true), + else => { + if (ty.isSignedInt()) + return writer.print("{d}", .{val.toSignedInt()}); + return writer.print("{d}u", .{val.toUnsignedInt()}); + }, }, .Float => { if (ty.floatBits(dg.module.getTarget()) <= 64) { @@ -286,8 +337,11 @@ pub const DeclGen = struct { return dg.fail("TODO: C backend: implement lowering large float values", .{}); }, .Pointer => switch (val.tag()) { - .null_value, .zero => try writer.writeAll("NULL"), - .one => try writer.writeAll("1"), + .null_value => try writer.writeAll("NULL"), + // Technically this should produce NULL but the integer literal 0 will always coerce + // to the assigned pointer type. Note this is just a hack to fix warnings from ordered comparisons (<, >, etc) + // between pointers and 0, which is an extension to begin with. + .zero => try writer.writeByte('0'), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; return dg.renderDeclValue(writer, ty, val, decl); @@ -316,6 +370,11 @@ pub const DeclGen = struct { const decl = val.castTag(.extern_fn).?.data; try dg.renderDeclName(decl, writer); }, + .int_u64, .one => { + try writer.writeAll("(("); + try dg.renderType(writer, ty); + try writer.print(")0x{x}u)", .{val.toUnsignedInt()}); + }, else => unreachable, }, .Array => { @@ -728,6 +787,8 @@ pub const DeclGen = struct { .i32 => try w.writeAll("int32_t"), .u64 => try w.writeAll("uint64_t"), .i64 => try w.writeAll("int64_t"), + .u128 => try w.writeAll("uint128_t"), + .i128 => try w.writeAll("int128_t"), .usize => try w.writeAll("uintptr_t"), .isize => try w.writeAll("intptr_t"), .c_short => try w.writeAll("short"), @@ -787,8 +848,9 @@ pub const DeclGen = struct { }, .Array => { // We are referencing the array so it will decay to a C pointer. - try dg.renderType(w, t.elemType()); - return w.writeAll(" *"); + // NB: arrays are not really types in C so they are either specified in the declaration + // or are already pointed to; our only job is to render the element type. + return dg.renderType(w, t.elemType()); }, .Optional => { var opt_buf: Type.Payload.ElemType = undefined; @@ -987,7 +1049,7 @@ pub fn genDecl(o: *Object) !void { } try fwd_decl_writer.writeAll(";\n"); - if (variable.init.isUndef()) { + if (variable.init.isUndefDeep()) { return; } @@ -1070,10 +1132,12 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO // TODO use a different strategy for add that communicates to the optimizer // that wrapping is UB. - .add, .ptr_add => try airBinOp (f, inst, " + "), + .add => try airBinOp (f, inst, " + "), + .ptr_add => try airPtrAddSub (f, inst, " + "), // TODO use a different strategy for sub that communicates to the optimizer // that wrapping is UB. - .sub, .ptr_sub => try airBinOp (f, inst, " - "), + .sub => try airBinOp (f, inst, " - "), + .ptr_sub => try airPtrAddSub (f, inst, " - "), // TODO use a different strategy for mul that communicates to the optimizer // that wrapping is UB. .mul => try airBinOp (f, inst, " * "), @@ -1187,7 +1251,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .ptr_slice_len_ptr => try airPtrSliceFieldPtr(f, inst, ".len;\n"), .ptr_slice_ptr_ptr => try airPtrSliceFieldPtr(f, inst, ".ptr;\n"), - .ptr_elem_val => try airPtrElemVal(f, inst, "["), + .ptr_elem_val => try airPtrElemVal(f, inst), .ptr_elem_ptr => try airPtrElemPtr(f, inst), .slice_elem_val => try airSliceElemVal(f, inst), .slice_elem_ptr => try airSliceElemPtr(f, inst), @@ -1240,20 +1304,39 @@ fn airPtrSliceFieldPtr(f: *Function, inst: Air.Inst.Index, suffix: []const u8) ! return f.fail("TODO: C backend: airPtrSliceFieldPtr", .{}); } -fn airPtrElemVal(f: *Function, inst: Air.Inst.Index, prefix: []const u8) !CValue { - const is_volatile = false; // TODO - if (!is_volatile and f.liveness.isUnused(inst)) - return CValue.none; +fn airPtrElemVal(f: *Function, inst: Air.Inst.Index) !CValue { + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const ptr_ty = f.air.typeOf(bin_op.lhs); + if (!ptr_ty.isVolatilePtr() and f.liveness.isUnused(inst)) return CValue.none; - _ = prefix; - return f.fail("TODO: C backend: airPtrElemVal", .{}); + const ptr = try f.resolveInst(bin_op.lhs); + const index = try f.resolveInst(bin_op.rhs); + const writer = f.object.writer(); + const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); + try writer.writeAll(" = "); + try f.writeCValue(writer, ptr); + try writer.writeByte('['); + try f.writeCValue(writer, index); + try writer.writeAll("];\n"); + return local; } fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue { - if (f.liveness.isUnused(inst)) - return CValue.none; + if (f.liveness.isUnused(inst)) return CValue.none; - return f.fail("TODO: C backend: airPtrElemPtr", .{}); + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; + + const ptr = try f.resolveInst(bin_op.lhs); + const index = try f.resolveInst(bin_op.rhs); + const writer = f.object.writer(); + const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); + try writer.writeAll(" = &"); + try f.writeCValue(writer, ptr); + try writer.writeByte('['); + try f.writeCValue(writer, index); + try writer.writeAll("];\n"); + return local; } fn airSliceElemVal(f: *Function, inst: Air.Inst.Index) !CValue { @@ -1317,6 +1400,10 @@ fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue { const local = try f.allocLocal(elem_type, mutability); try writer.writeAll(";\n"); + // Arrays are already pointers so they don't need to be referenced. + if (elem_type.zigTypeTag() == .Array) + return CValue{ .local = local.local }; + return CValue{ .local_ref = local.local }; } @@ -1344,6 +1431,8 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { if (!is_volatile and f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); + if (inst_ty.zigTypeTag() == .Array) + return f.fail("TODO: C backend: implement airLoad for arrays", .{}); const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); @@ -1470,7 +1559,7 @@ fn airBoolToInt(f: *Function, inst: Air.Inst.Index) !CValue { return local; } -fn airStoreUndefined(f: *Function, dest_ptr: CValue) !CValue { +fn airStoreUndefined(f: *Function, dest_ptr: CValue, dest_type: Type) !CValue { const is_debug_build = f.object.dg.module.optimizeMode() == .Debug; if (!is_debug_build) return CValue.none; @@ -1494,9 +1583,11 @@ fn airStoreUndefined(f: *Function, dest_ptr: CValue) !CValue { try writer.writeAll("));\n"); }, else => { + const indirection = if (dest_type.childType().zigTypeTag() == .Array) "" else "*"; + try writer.writeAll("memset("); try f.writeCValue(writer, dest_ptr); - try writer.writeAll(", 0xaa, sizeof(*"); + try writer.print(", 0xaa, sizeof({s}", .{indirection}); try f.writeCValue(writer, dest_ptr); try writer.writeAll("));\n"); }, @@ -1509,11 +1600,18 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const dest_ptr = try f.resolveInst(bin_op.lhs); const src_val = try f.resolveInst(bin_op.rhs); + const lhs_type = f.air.typeOf(bin_op.lhs); + // TODO Sema should emit a different instruction when the store should + // possibly do the safety 0xaa bytes for undefined. const src_val_is_undefined = - if (f.air.value(bin_op.rhs)) |v| v.isUndef() else false; + if (f.air.value(bin_op.rhs)) |v| v.isUndefDeep() else false; if (src_val_is_undefined) - return try airStoreUndefined(f, dest_ptr); + return try airStoreUndefined(f, dest_ptr, lhs_type); + + // Don't check this for airStoreUndefined as that will work for arrays already + if (lhs_type.childType().zigTypeTag() == .Array) + return f.fail("TODO: C backend: implement airStore for arrays", .{}); const writer = f.object.writer(); switch (dest_ptr) { @@ -1810,6 +1908,33 @@ fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue return local; } +fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { + if (f.liveness.isUnused(inst)) + return CValue.none; + + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const lhs = try f.resolveInst(bin_op.lhs); + const rhs = try f.resolveInst(bin_op.rhs); + + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); + + // We must convert to and from integer types to prevent UB if the operation results in a NULL pointer, + // or if LHS is NULL. The operation is only UB if the result is NULL and then dereferenced. + try writer.writeAll(" = ("); + try f.renderType(writer, inst_ty); + try writer.writeAll(")(((uintptr_t)"); + try f.writeCValue(writer, lhs); + try writer.print("){s}(", .{operator}); + try f.writeCValue(writer, rhs); + try writer.writeAll("*sizeof("); + try f.renderType(writer, inst_ty.childType()); + try writer.print(")));\n", .{}); + + return local; +} + fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; @@ -2306,15 +2431,17 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc const writer = f.object.writer(); const struct_obj = struct_ptr_ty.elemType().castTag(.@"struct").?.data; const field_name = struct_obj.fields.keys()[index]; + const field_val = struct_obj.fields.values()[index]; + const addrof = if (field_val.ty.zigTypeTag() == .Array) "" else "&"; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); switch (struct_ptr) { .local_ref => |i| { - try writer.print(" = &t{d}.{};\n", .{ i, fmtIdent(field_name) }); + try writer.print(" = {s}t{d}.{};\n", .{ addrof, i, fmtIdent(field_name) }); }, else => { - try writer.writeAll(" = &"); + try writer.print(" = {s}", .{addrof}); try f.writeCValue(writer, struct_ptr); try writer.print("->{};\n", .{fmtIdent(field_name)}); }, @@ -2529,7 +2656,9 @@ fn airPtrToInt(f: *Function, inst: Air.Inst.Index) !CValue { const writer = f.object.writer(); const operand = try f.resolveInst(un_op); - try writer.writeAll(" = "); + try writer.writeAll(" = ("); + try f.renderType(writer, inst_ty); + try writer.writeAll(")"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d0f6d62ad7..306a3df83c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1078,7 +1078,7 @@ pub const DeclGen = struct { }; return self.context.constStruct(&fields, fields.len, .False); }, - .int_u64 => { + .int_u64, .one, .int_big_positive => { const llvm_usize = try self.llvmType(Type.usize); const llvm_int = llvm_usize.constInt(tv.val.toUnsignedInt(), .False); return llvm_int.constIntToPtr(try self.llvmType(tv.ty)); @@ -3464,8 +3464,30 @@ pub const FuncGen = struct { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const dest_ptr = try self.resolveInst(bin_op.lhs); const ptr_ty = self.air.typeOf(bin_op.lhs); - const src_operand = try self.resolveInst(bin_op.rhs); - self.store(dest_ptr, ptr_ty, src_operand, .NotAtomic); + + // TODO Sema should emit a different instruction when the store should + // possibly do the safety 0xaa bytes for undefined. + const val_is_undef = if (self.air.value(bin_op.rhs)) |val| val.isUndefDeep() else false; + if (val_is_undef) { + const elem_ty = ptr_ty.childType(); + const target = self.dg.module.getTarget(); + const elem_size = elem_ty.abiSize(target); + const u8_llvm_ty = self.context.intType(8); + const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0); + const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, ""); + const fill_char = u8_llvm_ty.constInt(0xaa, .False); + const dest_ptr_align = ptr_ty.ptrAlignment(target); + const usize_llvm_ty = try self.dg.llvmType(Type.usize); + const len = usize_llvm_ty.constInt(elem_size, .False); + _ = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align, ptr_ty.isVolatilePtr()); + if (self.dg.module.comp.bin_file.options.valgrind) { + // TODO generate valgrind client request to mark byte range as undefined + // see gen_valgrind_undef() in codegen.cpp + } + } else { + const src_operand = try self.resolveInst(bin_op.rhs); + self.store(dest_ptr, ptr_ty, src_operand, .NotAtomic); + } return null; } @@ -3651,7 +3673,7 @@ pub const FuncGen = struct { const dest_ptr = try self.resolveInst(pl_op.operand); const ptr_ty = self.air.typeOf(pl_op.operand); const value = try self.resolveInst(extra.lhs); - const val_is_undef = if (self.air.value(extra.lhs)) |val| val.isUndef() else false; + const val_is_undef = if (self.air.value(extra.lhs)) |val| val.isUndefDeep() else false; const len = try self.resolveInst(extra.rhs); const u8_llvm_ty = self.context.intType(8); const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0); |
