| ... | ... | @@ -19,6 +19,7 @@ const Zir = @import("../Zir.zig"); |
| 19 | 19 | const Liveness = @import("../Liveness.zig"); |
| 20 | 20 | |
| 21 | 21 | const Mutability = enum { Const, Mut }; |
| 22 | const BigIntConst = std.math.big.int.Const; |
| 22 | 23 | |
| 23 | 24 | pub const CValue = union(enum) { |
| 24 | 25 | none: void, |
| ... | ... | @@ -226,13 +227,59 @@ pub const DeclGen = struct { |
| 226 | 227 | try dg.renderDeclName(decl, writer); |
| 227 | 228 | } |
| 228 | 229 | |
| 230 | fn renderInt128( |
| 231 | writer: anytype, |
| 232 | int_val: anytype, |
| 233 | ) error{ OutOfMemory, AnalysisFail }!void { |
| 234 | const int_info = @typeInfo(@TypeOf(int_val)).Int; |
| 235 | const is_signed = int_info.signedness == .signed; |
| 236 | const is_neg = int_val < 0; |
| 237 | comptime assert(int_info.bits > 64 and int_info.bits <= 128); |
| 238 | |
| 239 | // Clang and GCC don't support 128-bit integer constants but will hopefully unfold them |
| 240 | // if we construct one manually. |
| 241 | const magnitude = std.math.absCast(int_val); |
| 242 | |
| 243 | const high = @truncate(u64, magnitude >> 64); |
| 244 | const low = @truncate(u64, magnitude); |
| 245 | |
| 246 | // (int128_t)/<->( ( (uint128_t)( val_high << 64 )u ) + (uint128_t)val_low/u ) |
| 247 | if (is_signed) try writer.writeAll("(int128_t)"); |
| 248 | if (is_neg) try writer.writeByte('-'); |
| 249 | |
| 250 | assert(high > 0); |
| 251 | try writer.print("(((uint128_t)0x{x}u<<64)", .{high}); |
| 252 | |
| 253 | if (low > 0) |
| 254 | try writer.print("+(uint128_t)0x{x}u", .{low}); |
| 255 | |
| 256 | return writer.writeByte(')'); |
| 257 | } |
| 258 | |
| 259 | fn renderBigIntConst( |
| 260 | dg: *DeclGen, |
| 261 | writer: anytype, |
| 262 | val: BigIntConst, |
| 263 | signed: bool, |
| 264 | ) error{ OutOfMemory, AnalysisFail }!void { |
| 265 | if (signed) { |
| 266 | try renderInt128(writer, val.to(i128) catch { |
| 267 | return dg.fail("TODO implement integer constants larger than 128 bits", .{}); |
| 268 | }); |
| 269 | } else { |
| 270 | try renderInt128(writer, val.to(u128) catch { |
| 271 | return dg.fail("TODO implement integer constants larger than 128 bits", .{}); |
| 272 | }); |
| 273 | } |
| 274 | } |
| 275 | |
| 229 | 276 | fn renderValue( |
| 230 | 277 | dg: *DeclGen, |
| 231 | 278 | writer: anytype, |
| 232 | 279 | ty: Type, |
| 233 | 280 | val: Value, |
| 234 | 281 | ) error{ OutOfMemory, AnalysisFail }!void { |
| 235 | | if (val.isUndef()) { |
| 282 | if (val.isUndefDeep()) { |
| 236 | 283 | switch (ty.zigTypeTag()) { |
| 237 | 284 | // Using '{}' for integer and floats seemed to error C compilers (both GCC and Clang) |
| 238 | 285 | // with 'error: expected expression' (including when built with 'zig cc') |
| ... | ... | @@ -240,18 +287,18 @@ pub const DeclGen = struct { |
| 240 | 287 | const c_bits = toCIntBits(ty.intInfo(dg.module.getTarget()).bits) orelse |
| 241 | 288 | return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); |
| 242 | 289 | switch (c_bits) { |
| 243 | | 8 => return writer.writeAll("0xaaU"), |
| 244 | | 16 => return writer.writeAll("0xaaaaU"), |
| 245 | | 32 => return writer.writeAll("0xaaaaaaaaU"), |
| 246 | | 64 => return writer.writeAll("0xaaaaaaaaaaaaaaaaUL"), |
| 247 | | 128 => return writer.writeAll("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaULL"), |
| 290 | 8 => return writer.writeAll("0xaau"), |
| 291 | 16 => return writer.writeAll("0xaaaau"), |
| 292 | 32 => return writer.writeAll("0xaaaaaaaau"), |
| 293 | 64 => return writer.writeAll("0xaaaaaaaaaaaaaaaau"), |
| 294 | 128 => return renderInt128(writer, @as(u128, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)), |
| 248 | 295 | else => unreachable, |
| 249 | 296 | } |
| 250 | 297 | }, |
| 251 | 298 | .Float => { |
| 252 | 299 | switch (ty.floatBits(dg.module.getTarget())) { |
| 253 | | 32 => return writer.writeAll("zig_bitcast_f32_u32(0xaaaaaaaa)"), |
| 254 | | 64 => return writer.writeAll("zig_bitcast_f64_u64(0xaaaaaaaaaaaaaaaa)"), |
| 300 | 32 => return writer.writeAll("zig_bitcast_f32_u32(0xaaaaaaaau)"), |
| 301 | 64 => return writer.writeAll("zig_bitcast_f64_u64(0xaaaaaaaaaaaaaaaau)"), |
| 255 | 302 | else => return dg.fail("TODO float types > 64 bits are not support in renderValue() as of now", .{}), |
| 256 | 303 | } |
| 257 | 304 | }, |
| ... | ... | @@ -265,10 +312,14 @@ pub const DeclGen = struct { |
| 265 | 312 | } |
| 266 | 313 | } |
| 267 | 314 | switch (ty.zigTypeTag()) { |
| 268 | | .Int => { |
| 269 | | if (ty.isSignedInt()) |
| 270 | | return writer.print("{d}", .{val.toSignedInt()}); |
| 271 | | return writer.print("{d}", .{val.toUnsignedInt()}); |
| 315 | .Int => switch (val.tag()) { |
| 316 | .int_big_positive => try dg.renderBigIntConst(writer, val.castTag(.int_big_positive).?.asBigInt(), ty.isSignedInt()), |
| 317 | .int_big_negative => try dg.renderBigIntConst(writer, val.castTag(.int_big_negative).?.asBigInt(), true), |
| 318 | else => { |
| 319 | if (ty.isSignedInt()) |
| 320 | return writer.print("{d}", .{val.toSignedInt()}); |
| 321 | return writer.print("{d}u", .{val.toUnsignedInt()}); |
| 322 | }, |
| 272 | 323 | }, |
| 273 | 324 | .Float => { |
| 274 | 325 | if (ty.floatBits(dg.module.getTarget()) <= 64) { |
| ... | ... | @@ -286,8 +337,11 @@ pub const DeclGen = struct { |
| 286 | 337 | return dg.fail("TODO: C backend: implement lowering large float values", .{}); |
| 287 | 338 | }, |
| 288 | 339 | .Pointer => switch (val.tag()) { |
| 289 | | .null_value, .zero => try writer.writeAll("NULL"), |
| 290 | | .one => try writer.writeAll("1"), |
| 340 | .null_value => try writer.writeAll("NULL"), |
| 341 | // Technically this should produce NULL but the integer literal 0 will always coerce |
| 342 | // to the assigned pointer type. Note this is just a hack to fix warnings from ordered comparisons (<, >, etc) |
| 343 | // between pointers and 0, which is an extension to begin with. |
| 344 | .zero => try writer.writeByte('0'), |
| 291 | 345 | .decl_ref => { |
| 292 | 346 | const decl = val.castTag(.decl_ref).?.data; |
| 293 | 347 | return dg.renderDeclValue(writer, ty, val, decl); |
| ... | ... | @@ -316,6 +370,11 @@ pub const DeclGen = struct { |
| 316 | 370 | const decl = val.castTag(.extern_fn).?.data; |
| 317 | 371 | try dg.renderDeclName(decl, writer); |
| 318 | 372 | }, |
| 373 | .int_u64, .one => { |
| 374 | try writer.writeAll("(("); |
| 375 | try dg.renderType(writer, ty); |
| 376 | try writer.print(")0x{x}u)", .{val.toUnsignedInt()}); |
| 377 | }, |
| 319 | 378 | else => unreachable, |
| 320 | 379 | }, |
| 321 | 380 | .Array => { |
| ... | ... | @@ -728,6 +787,8 @@ pub const DeclGen = struct { |
| 728 | 787 | .i32 => try w.writeAll("int32_t"), |
| 729 | 788 | .u64 => try w.writeAll("uint64_t"), |
| 730 | 789 | .i64 => try w.writeAll("int64_t"), |
| 790 | .u128 => try w.writeAll("uint128_t"), |
| 791 | .i128 => try w.writeAll("int128_t"), |
| 731 | 792 | .usize => try w.writeAll("uintptr_t"), |
| 732 | 793 | .isize => try w.writeAll("intptr_t"), |
| 733 | 794 | .c_short => try w.writeAll("short"), |
| ... | ... | @@ -787,8 +848,9 @@ pub const DeclGen = struct { |
| 787 | 848 | }, |
| 788 | 849 | .Array => { |
| 789 | 850 | // We are referencing the array so it will decay to a C pointer. |
| 790 | | try dg.renderType(w, t.elemType()); |
| 791 | | return w.writeAll(" *"); |
| 851 | // NB: arrays are not really types in C so they are either specified in the declaration |
| 852 | // or are already pointed to; our only job is to render the element type. |
| 853 | return dg.renderType(w, t.elemType()); |
| 792 | 854 | }, |
| 793 | 855 | .Optional => { |
| 794 | 856 | var opt_buf: Type.Payload.ElemType = undefined; |
| ... | ... | @@ -987,7 +1049,7 @@ pub fn genDecl(o: *Object) !void { |
| 987 | 1049 | } |
| 988 | 1050 | try fwd_decl_writer.writeAll(";\n"); |
| 989 | 1051 | |
| 990 | | if (variable.init.isUndef()) { |
| 1052 | if (variable.init.isUndefDeep()) { |
| 991 | 1053 | return; |
| 992 | 1054 | } |
| 993 | 1055 | |
| ... | ... | @@ -1070,10 +1132,12 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO |
| 1070 | 1132 | |
| 1071 | 1133 | // TODO use a different strategy for add that communicates to the optimizer |
| 1072 | 1134 | // that wrapping is UB. |
| 1073 | | .add, .ptr_add => try airBinOp (f, inst, " + "), |
| 1135 | .add => try airBinOp (f, inst, " + "), |
| 1136 | .ptr_add => try airPtrAddSub (f, inst, " + "), |
| 1074 | 1137 | // TODO use a different strategy for sub that communicates to the optimizer |
| 1075 | 1138 | // that wrapping is UB. |
| 1076 | | .sub, .ptr_sub => try airBinOp (f, inst, " - "), |
| 1139 | .sub => try airBinOp (f, inst, " - "), |
| 1140 | .ptr_sub => try airPtrAddSub (f, inst, " - "), |
| 1077 | 1141 | // TODO use a different strategy for mul that communicates to the optimizer |
| 1078 | 1142 | // that wrapping is UB. |
| 1079 | 1143 | .mul => try airBinOp (f, inst, " * "), |
| ... | ... | @@ -1187,7 +1251,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO |
| 1187 | 1251 | .ptr_slice_len_ptr => try airPtrSliceFieldPtr(f, inst, ".len;\n"), |
| 1188 | 1252 | .ptr_slice_ptr_ptr => try airPtrSliceFieldPtr(f, inst, ".ptr;\n"), |
| 1189 | 1253 | |
| 1190 | | .ptr_elem_val => try airPtrElemVal(f, inst, "["), |
| 1254 | .ptr_elem_val => try airPtrElemVal(f, inst), |
| 1191 | 1255 | .ptr_elem_ptr => try airPtrElemPtr(f, inst), |
| 1192 | 1256 | .slice_elem_val => try airSliceElemVal(f, inst), |
| 1193 | 1257 | .slice_elem_ptr => try airSliceElemPtr(f, inst), |
| ... | ... | @@ -1240,20 +1304,39 @@ fn airPtrSliceFieldPtr(f: *Function, inst: Air.Inst.Index, suffix: []const u8) ! |
| 1240 | 1304 | return f.fail("TODO: C backend: airPtrSliceFieldPtr", .{}); |
| 1241 | 1305 | } |
| 1242 | 1306 | |
| 1243 | | fn airPtrElemVal(f: *Function, inst: Air.Inst.Index, prefix: []const u8) !CValue { |
| 1244 | | const is_volatile = false; // TODO |
| 1245 | | if (!is_volatile and f.liveness.isUnused(inst)) |
| 1246 | | return CValue.none; |
| 1307 | fn airPtrElemVal(f: *Function, inst: Air.Inst.Index) !CValue { |
| 1308 | const bin_op = f.air.instructions.items(.data)[inst].bin_op; |
| 1309 | const ptr_ty = f.air.typeOf(bin_op.lhs); |
| 1310 | if (!ptr_ty.isVolatilePtr() and f.liveness.isUnused(inst)) return CValue.none; |
| 1247 | 1311 | |
| 1248 | | _ = prefix; |
| 1249 | | return f.fail("TODO: C backend: airPtrElemVal", .{}); |
| 1312 | const ptr = try f.resolveInst(bin_op.lhs); |
| 1313 | const index = try f.resolveInst(bin_op.rhs); |
| 1314 | const writer = f.object.writer(); |
| 1315 | const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); |
| 1316 | try writer.writeAll(" = "); |
| 1317 | try f.writeCValue(writer, ptr); |
| 1318 | try writer.writeByte('['); |
| 1319 | try f.writeCValue(writer, index); |
| 1320 | try writer.writeAll("];\n"); |
| 1321 | return local; |
| 1250 | 1322 | } |
| 1251 | 1323 | |
| 1252 | 1324 | fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue { |
| 1253 | | if (f.liveness.isUnused(inst)) |
| 1254 | | return CValue.none; |
| 1325 | if (f.liveness.isUnused(inst)) return CValue.none; |
| 1255 | 1326 | |
| 1256 | | return f.fail("TODO: C backend: airPtrElemPtr", .{}); |
| 1327 | const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; |
| 1328 | const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; |
| 1329 | |
| 1330 | const ptr = try f.resolveInst(bin_op.lhs); |
| 1331 | const index = try f.resolveInst(bin_op.rhs); |
| 1332 | const writer = f.object.writer(); |
| 1333 | const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); |
| 1334 | try writer.writeAll(" = &"); |
| 1335 | try f.writeCValue(writer, ptr); |
| 1336 | try writer.writeByte('['); |
| 1337 | try f.writeCValue(writer, index); |
| 1338 | try writer.writeAll("];\n"); |
| 1339 | return local; |
| 1257 | 1340 | } |
| 1258 | 1341 | |
| 1259 | 1342 | fn airSliceElemVal(f: *Function, inst: Air.Inst.Index) !CValue { |
| ... | ... | @@ -1317,6 +1400,10 @@ fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue { |
| 1317 | 1400 | const local = try f.allocLocal(elem_type, mutability); |
| 1318 | 1401 | try writer.writeAll(";\n"); |
| 1319 | 1402 | |
| 1403 | // Arrays are already pointers so they don't need to be referenced. |
| 1404 | if (elem_type.zigTypeTag() == .Array) |
| 1405 | return CValue{ .local = local.local }; |
| 1406 | |
| 1320 | 1407 | return CValue{ .local_ref = local.local }; |
| 1321 | 1408 | } |
| 1322 | 1409 | |
| ... | ... | @@ -1344,6 +1431,8 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { |
| 1344 | 1431 | if (!is_volatile and f.liveness.isUnused(inst)) |
| 1345 | 1432 | return CValue.none; |
| 1346 | 1433 | const inst_ty = f.air.typeOfIndex(inst); |
| 1434 | if (inst_ty.zigTypeTag() == .Array) |
| 1435 | return f.fail("TODO: C backend: implement airLoad for arrays", .{}); |
| 1347 | 1436 | const operand = try f.resolveInst(ty_op.operand); |
| 1348 | 1437 | const writer = f.object.writer(); |
| 1349 | 1438 | const local = try f.allocLocal(inst_ty, .Const); |
| ... | ... | @@ -1470,7 +1559,7 @@ fn airBoolToInt(f: *Function, inst: Air.Inst.Index) !CValue { |
| 1470 | 1559 | return local; |
| 1471 | 1560 | } |
| 1472 | 1561 | |
| 1473 | | fn airStoreUndefined(f: *Function, dest_ptr: CValue) !CValue { |
| 1562 | fn airStoreUndefined(f: *Function, dest_ptr: CValue, dest_type: Type) !CValue { |
| 1474 | 1563 | const is_debug_build = f.object.dg.module.optimizeMode() == .Debug; |
| 1475 | 1564 | if (!is_debug_build) |
| 1476 | 1565 | return CValue.none; |
| ... | ... | @@ -1494,9 +1583,11 @@ fn airStoreUndefined(f: *Function, dest_ptr: CValue) !CValue { |
| 1494 | 1583 | try writer.writeAll("));\n"); |
| 1495 | 1584 | }, |
| 1496 | 1585 | else => { |
| 1586 | const indirection = if (dest_type.childType().zigTypeTag() == .Array) "" else "*"; |
| 1587 | |
| 1497 | 1588 | try writer.writeAll("memset("); |
| 1498 | 1589 | try f.writeCValue(writer, dest_ptr); |
| 1499 | | try writer.writeAll(", 0xaa, sizeof(*"); |
| 1590 | try writer.print(", 0xaa, sizeof({s}", .{indirection}); |
| 1500 | 1591 | try f.writeCValue(writer, dest_ptr); |
| 1501 | 1592 | try writer.writeAll("));\n"); |
| 1502 | 1593 | }, |
| ... | ... | @@ -1509,11 +1600,18 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { |
| 1509 | 1600 | const bin_op = f.air.instructions.items(.data)[inst].bin_op; |
| 1510 | 1601 | const dest_ptr = try f.resolveInst(bin_op.lhs); |
| 1511 | 1602 | const src_val = try f.resolveInst(bin_op.rhs); |
| 1603 | const lhs_type = f.air.typeOf(bin_op.lhs); |
| 1512 | 1604 | |
| 1605 | // TODO Sema should emit a different instruction when the store should |
| 1606 | // possibly do the safety 0xaa bytes for undefined. |
| 1513 | 1607 | const src_val_is_undefined = |
| 1514 | | if (f.air.value(bin_op.rhs)) |v| v.isUndef() else false; |
| 1608 | if (f.air.value(bin_op.rhs)) |v| v.isUndefDeep() else false; |
| 1515 | 1609 | if (src_val_is_undefined) |
| 1516 | | return try airStoreUndefined(f, dest_ptr); |
| 1610 | return try airStoreUndefined(f, dest_ptr, lhs_type); |
| 1611 | |
| 1612 | // Don't check this for airStoreUndefined as that will work for arrays already |
| 1613 | if (lhs_type.childType().zigTypeTag() == .Array) |
| 1614 | return f.fail("TODO: C backend: implement airStore for arrays", .{}); |
| 1517 | 1615 | |
| 1518 | 1616 | const writer = f.object.writer(); |
| 1519 | 1617 | switch (dest_ptr) { |
| ... | ... | @@ -1810,6 +1908,33 @@ fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue |
| 1810 | 1908 | return local; |
| 1811 | 1909 | } |
| 1812 | 1910 | |
| 1911 | fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { |
| 1912 | if (f.liveness.isUnused(inst)) |
| 1913 | return CValue.none; |
| 1914 | |
| 1915 | const bin_op = f.air.instructions.items(.data)[inst].bin_op; |
| 1916 | const lhs = try f.resolveInst(bin_op.lhs); |
| 1917 | const rhs = try f.resolveInst(bin_op.rhs); |
| 1918 | |
| 1919 | const writer = f.object.writer(); |
| 1920 | const inst_ty = f.air.typeOfIndex(inst); |
| 1921 | const local = try f.allocLocal(inst_ty, .Const); |
| 1922 | |
| 1923 | // We must convert to and from integer types to prevent UB if the operation results in a NULL pointer, |
| 1924 | // or if LHS is NULL. The operation is only UB if the result is NULL and then dereferenced. |
| 1925 | try writer.writeAll(" = ("); |
| 1926 | try f.renderType(writer, inst_ty); |
| 1927 | try writer.writeAll(")(((uintptr_t)"); |
| 1928 | try f.writeCValue(writer, lhs); |
| 1929 | try writer.print("){s}(", .{operator}); |
| 1930 | try f.writeCValue(writer, rhs); |
| 1931 | try writer.writeAll("*sizeof("); |
| 1932 | try f.renderType(writer, inst_ty.childType()); |
| 1933 | try writer.print(")));\n", .{}); |
| 1934 | |
| 1935 | return local; |
| 1936 | } |
| 1937 | |
| 1813 | 1938 | fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { |
| 1814 | 1939 | if (f.liveness.isUnused(inst)) return CValue.none; |
| 1815 | 1940 | |
| ... | ... | @@ -2306,15 +2431,17 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc |
| 2306 | 2431 | const writer = f.object.writer(); |
| 2307 | 2432 | const struct_obj = struct_ptr_ty.elemType().castTag(.@"struct").?.data; |
| 2308 | 2433 | const field_name = struct_obj.fields.keys()[index]; |
| 2434 | const field_val = struct_obj.fields.values()[index]; |
| 2435 | const addrof = if (field_val.ty.zigTypeTag() == .Array) "" else "&"; |
| 2309 | 2436 | |
| 2310 | 2437 | const inst_ty = f.air.typeOfIndex(inst); |
| 2311 | 2438 | const local = try f.allocLocal(inst_ty, .Const); |
| 2312 | 2439 | switch (struct_ptr) { |
| 2313 | 2440 | .local_ref => |i| { |
| 2314 | | try writer.print(" = &t{d}.{};\n", .{ i, fmtIdent(field_name) }); |
| 2441 | try writer.print(" = {s}t{d}.{};\n", .{ addrof, i, fmtIdent(field_name) }); |
| 2315 | 2442 | }, |
| 2316 | 2443 | else => { |
| 2317 | | try writer.writeAll(" = &"); |
| 2444 | try writer.print(" = {s}", .{addrof}); |
| 2318 | 2445 | try f.writeCValue(writer, struct_ptr); |
| 2319 | 2446 | try writer.print("->{};\n", .{fmtIdent(field_name)}); |
| 2320 | 2447 | }, |
| ... | ... | @@ -2529,7 +2656,9 @@ fn airPtrToInt(f: *Function, inst: Air.Inst.Index) !CValue { |
| 2529 | 2656 | const writer = f.object.writer(); |
| 2530 | 2657 | const operand = try f.resolveInst(un_op); |
| 2531 | 2658 | |
| 2532 | | try writer.writeAll(" = "); |
| 2659 | try writer.writeAll(" = ("); |
| 2660 | try f.renderType(writer, inst_ty); |
| 2661 | try writer.writeAll(")"); |
| 2533 | 2662 | try f.writeCValue(writer, operand); |
| 2534 | 2663 | try writer.writeAll(";\n"); |
| 2535 | 2664 | return local; |