diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 2bdf5be2..30f728ed 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -340,6 +340,7 @@ fn diagnoseField( .array_ty, .vector_ty, .record_ty, + .pointer, => unreachable, }); } diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index aef3a452..d2d36d35 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -753,6 +753,10 @@ pub fn float80Type(comp: *const Compilation) ?Type { return target_util.float80Type(comp.target); } +pub fn internString(comp: *Compilation, str: []const u8) !StrInt.StringId { + return comp.string_interner.internExtra(comp.gpa, str); +} + /// Smallest integer type with at least N bits pub fn intLeastN(comp: *const Compilation, bits: usize, signedness: std.builtin.Signedness) Type { if (bits == 64 and (comp.target.isDarwin() or comp.target.isWasm())) { diff --git a/src/aro/Diagnostics/messages.def b/src/aro/Diagnostics/messages.def index fc740b36..e4c2ded0 100644 --- a/src/aro/Diagnostics/messages.def +++ b/src/aro/Diagnostics/messages.def @@ -527,6 +527,12 @@ unknown_warning .opt = W("unknown-warning-option") .kind = .warning +array_overflow + .msg = "{s}" + .extra = .str + .opt = W("array-bounds") + .kind = .warning + overflow .msg = "overflow in expression; result is '{s}'" .extra = .str @@ -2535,3 +2541,13 @@ auto_type_self_initialized .msg = "variable '{s}' declared with deduced type '__auto_type' cannot appear in its own initializer" .extra = .str .kind = .@"error" + +non_constant_initializer + .msg = "initializer element is not a compile-time constant" + .kind = .@"error" + +subtract_pointers_zero_elem_size + .msg = "subtraction of pointers to type '{s}' of zero size has undefined behavior" + .kind = .warning + .opt = W("pointer-arith") + .extra = .str diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 21df6625..055311d2 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -26,6 +26,7 @@ const Symbol = SymbolStack.Symbol; const record_layout = @import("record_layout.zig"); const StrInt = @import("StringInterner.zig"); const StringId = StrInt.StringId; +const Pointer = @import("backend").Interner.Key.Pointer; const Builtins = @import("Builtins.zig"); const Builtin = Builtins.Builtin; const evalBuiltin = @import("Builtins/eval.zig").eval; @@ -377,6 +378,24 @@ fn errOverflow(p: *Parser, op_tok: TokenIndex, res: Result) !void { try p.errStr(.overflow, op_tok, try res.str(p)); } +fn errArrayOverflow(p: *Parser, op_tok: TokenIndex, res: Result) !void { + const strings_top = p.strings.items.len; + defer p.strings.items.len = strings_top; + + const w = p.strings.writer(); + const format = + \\The pointer incremented by {s} refers past the last possible element in {d}-bit address space containing {d}-bit ({d}-byte) elements (max possible {d} elements) + ; + const increment = try res.str(p); + const ptr_bits = p.comp.types.intptr.bitSizeof(p.comp).?; + const element_size = res.ty.elemType().sizeof(p.comp) orelse 1; + const max_elems = p.comp.maxArrayBytes() / element_size; + + try w.print(format, .{ increment, ptr_bits, element_size * 8, element_size, max_elems }); + const duped = try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); + return p.errStr(.array_overflow, op_tok, duped); +} + fn errExpectedToken(p: *Parser, expected: Token.Id, actual: Token.Id) Error { switch (actual) { .invalid => try p.errExtra(.expected_invalid, p.tok_i, .{ .tok_id_expected = expected }), @@ -1284,16 +1303,11 @@ fn staticAssert(p: *Parser) Error!bool { try p.errStr(.pre_c23_compat, static_assert, "'_Static_assert' with no message"); } - // Array will never be zero; a value of zero for a pointer is a null pointer constant - if ((res.ty.isArray() or res.ty.isPtr()) and !res.val.isZero(p.comp)) { - const err_start = p.comp.diagnostics.list.items.len; - try p.errTok(.const_decl_folded, res_token); - if (res.ty.isPtr() and err_start != p.comp.diagnostics.list.items.len) { - // Don't show the note if the .const_decl_folded diagnostic was not added - try p.errTok(.constant_expression_conversion_not_allowed, res_token); - } - } + const is_int_expr = res.ty.isInt(); try res.boolCast(p, .{ .specifier = .bool }, res_token); + if (!is_int_expr) { + res.val = .{}; + } if (res.val.opt_ref == .none) { if (res.ty.specifier != .invalid) { try p.errTok(.static_assert_not_constant, res_token); @@ -5512,6 +5526,7 @@ pub const Result = struct { if (a_ptr and b_ptr) { if (!a.ty.eql(b.ty, p.comp, false)) try p.errStr(.incompatible_pointers, tok, try p.typePairStr(a.ty, b.ty)); + if (a.ty.elemType().sizeof(p.comp) orelse 1 == 0) try p.errStr(.subtract_pointers_zero_elem_size, tok, try p.typeStr(a.ty.elemType())); a.ty = p.comp.types.ptrdiff; } @@ -6589,8 +6604,12 @@ fn eqExpr(p: *Parser) Error!Result { if (try lhs.adjustTypes(ne.?, &rhs, p, .equality)) { const op: std.math.CompareOperator = if (tag == .equal_expr) .eq else .neq; - const res = lhs.val.compare(op, rhs.val, p.comp); - lhs.val = Value.fromBool(res); + const res: ?bool = if (lhs.ty.isPtr() or rhs.ty.isPtr()) + lhs.val.comparePointers(op, rhs.val, p.comp) + else + lhs.val.compare(op, rhs.val, p.comp); + + lhs.val = if (res) |val| Value.fromBool(val) else .{}; } else { lhs.val.boolCast(p.comp); } @@ -6620,8 +6639,11 @@ fn compExpr(p: *Parser) Error!Result { .greater_than_equal_expr => .gte, else => unreachable, }; - const res = lhs.val.compare(op, rhs.val, p.comp); - lhs.val = Value.fromBool(res); + const res: ?bool = if (lhs.ty.isPtr() or rhs.ty.isPtr()) + lhs.val.comparePointers(op, rhs.val, p.comp) + else + lhs.val.compare(op, rhs.val, p.comp); + lhs.val = if (res) |val| Value.fromBool(val) else .{}; } else { lhs.val.boolCast(p.comp); } @@ -6674,11 +6696,21 @@ fn addExpr(p: *Parser) Error!Result { const lhs_ty = lhs.ty; if (try lhs.adjustTypes(minus.?, &rhs, p, if (plus != null) .add else .sub)) { if (plus != null) { - if (try lhs.val.add(lhs.val, rhs.val, lhs.ty, p.comp) and - lhs.ty.signedness(p.comp) != .unsigned) try p.errOverflow(plus.?, lhs); + if (try lhs.val.add(lhs.val, rhs.val, lhs.ty, p.comp)) { + if (lhs.ty.isPtr()) { + try p.errArrayOverflow(plus.?, lhs); + } else if (lhs.ty.signedness(p.comp) != .unsigned) { + try p.errOverflow(plus.?, lhs); + } + } } else { - if (try lhs.val.sub(lhs.val, rhs.val, lhs.ty, p.comp) and - lhs.ty.signedness(p.comp) != .unsigned) try p.errOverflow(minus.?, lhs); + const elem_size = if (lhs_ty.isPtr()) lhs_ty.elemType().sizeof(p.comp) orelse 1 else 1; + if (elem_size == 0 and rhs.ty.isPtr()) { + lhs.val = .{}; + } else { + if (try lhs.val.sub(lhs.val, rhs.val, lhs.ty, elem_size, p.comp) and + lhs.ty.signedness(p.comp) != .unsigned) try p.errOverflow(minus.?, lhs); + } } } if (lhs.ty.specifier != .invalid and lhs_ty.isPtr() and !lhs_ty.isVoidStar() and lhs_ty.elemType().hasIncompleteSize()) { @@ -7034,6 +7066,57 @@ fn offsetofMemberDesignator(p: *Parser, base_ty: Type, want_bits: bool) Error!Re return Result{ .ty = base_ty, .val = val, .node = lhs.node }; } +fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: *Value) !Pointer { + const tys = p.nodes.items(.ty); + const tags = p.nodes.items(.tag); + const data = p.nodes.items(.data); + const tag = tags[@intFromEnum(node)]; + + switch (tag) { + .implicit_cast => { + const cast_data = data[@intFromEnum(node)].cast; + return switch (cast_data.kind) { + .array_to_pointer => p.computeOffsetExtra(cast_data.operand, offset_so_far), + else => unreachable, + }; + }, + .paren_expr => return p.computeOffsetExtra(data[@intFromEnum(node)].un, offset_so_far), + .decl_ref_expr => { + const var_name = try p.comp.internString(p.tokSlice(data[@intFromEnum(node)].decl_ref)); + const sym = p.syms.findSymbol(var_name).?; // symbol must exist if we get here; otherwise it's a syntax error + return .{ .decl = @intFromEnum(sym.node), .offset = offset_so_far.ref() }; + }, + .array_access_expr => { + const bin_data = data[@intFromEnum(node)].bin; + const ty = tys[@intFromEnum(node)]; + + const index_val = p.value_map.get(bin_data.rhs) orelse return error.InvalidReloc; + var size = try Value.int(ty.sizeof(p.comp).?, p.comp); + const mul_overflow = try size.mul(size, index_val, p.comp.types.ptrdiff, p.comp); + + const add_overflow = try offset_so_far.add(size, offset_so_far.*, p.comp.types.ptrdiff, p.comp); + _ = mul_overflow; + _ = add_overflow; + return p.computeOffsetExtra(bin_data.lhs, offset_so_far); + }, + .member_access_expr => { + const member = data[@intFromEnum(node)].member; + const record = tys[@intFromEnum(member.lhs)].getRecord().?; + // const field_offset: i64 = @intCast(@divExact(record.fields[member.index].layout.offset_bits, 8)); + const field_offset = try Value.int(@divExact(record.fields[member.index].layout.offset_bits, 8), p.comp); + _ = try offset_so_far.add(field_offset, offset_so_far.*, p.comp.types.ptrdiff, p.comp); + return p.computeOffsetExtra(member.lhs, offset_so_far); + }, + else => return error.InvalidReloc, + } +} + +/// Compute the offset (in bytes) of an expression from a base pointer. +fn computeOffset(p: *Parser, node: NodeIndex) !Pointer { + var val: Value = .zero; + return p.computeOffsetExtra(node, &val); +} + /// unExpr /// : (compoundLiteral | primaryExpr) suffixExpr* /// | '&&' IDENTIFIER @@ -7074,9 +7157,11 @@ fn unExpr(p: *Parser) Error!Result { try p.err(.invalid_preproc_operator); return error.ParsingFailed; } + const ampersand_tok = p.tok_i; p.tok_i += 1; var operand = try p.castExpr(); try operand.expect(p); + var addr_val: Value = .{}; const tree = p.tmpTree(); if (p.getNode(operand.node, .member_access_expr) orelse @@ -7084,12 +7169,23 @@ fn unExpr(p: *Parser) Error!Result { { if (tree.isBitfield(member_node)) try p.errTok(.addr_of_bitfield, tok); } - if (!tree.isLval(operand.node) and !operand.ty.is(.invalid)) { + const operand_ty_valid = !operand.ty.is(.invalid); + if (!tree.isLval(operand.node) and operand_ty_valid) { try p.errTok(.addr_of_rvalue, tok); + } else if (operand_ty_valid and p.func.ty == null) { + // address of global + const reloc: Pointer = p.computeOffset(operand.node) catch |e| switch (e) { + error.InvalidReloc => blk: { + try p.errTok(.non_constant_initializer, ampersand_tok); + break :blk .{ .decl = @intFromEnum(NodeIndex.none), .offset = .zero }; + }, + else => |er| return er, + }; + addr_val = try Value.reloc(reloc, p.comp); } if (operand.ty.qual.register) try p.errTok(.addr_of_register, tok); - if (!operand.ty.is(.invalid)) { + if (operand_ty_valid) { const elem_ty = try p.arena.create(Type); elem_ty.* = operand.ty; operand.ty = Type{ @@ -7099,6 +7195,7 @@ fn unExpr(p: *Parser) Error!Result { } try operand.saveValue(p); try operand.un(p, .addr_of_expr, tok); + operand.val = addr_val; return operand; }, .asterisk => { @@ -7144,7 +7241,7 @@ fn unExpr(p: *Parser) Error!Result { try operand.usualUnaryConversion(p, tok); if (operand.val.isArithmetic(p.comp)) { - _ = try operand.val.sub(Value.zero, operand.val, operand.ty, p.comp); + _ = try operand.val.negate(operand.val, operand.ty, p.comp); } else { operand.val = .{}; } @@ -7198,7 +7295,7 @@ fn unExpr(p: *Parser) Error!Result { try operand.usualUnaryConversion(p, tok); if (operand.val.is(.int, p.comp) or operand.val.is(.int, p.comp)) { - if (try operand.val.sub(operand.val, Value.one, operand.ty, p.comp)) + if (try operand.val.decrement(operand.val, operand.ty, p.comp)) try p.errOverflow(tok, operand); } else { operand.val = .{}; diff --git a/src/aro/Value.zig b/src/aro/Value.zig index 61b8f0f8..1cf44cde 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -2,9 +2,10 @@ const std = @import("std"); const assert = std.debug.assert; const BigIntConst = std.math.big.int.Const; const BigIntMutable = std.math.big.int.Mutable; -const backend = @import("backend"); -const Interner = backend.Interner; +const Interner = @import("backend").Interner; const BigIntSpace = Interner.Tag.Int.BigIntSpace; +const StringInterner = @import("StringInterner.zig"); +const StringId = StringInterner.StringId; const Compilation = @import("Compilation.zig"); const Type = @import("Type.zig"); const target_util = @import("target.zig"); @@ -32,11 +33,19 @@ pub fn int(i: anytype, comp: *Compilation) !Value { } } +pub fn reloc(r: Interner.Key.Pointer, comp: *Compilation) !Value { + return intern(comp, .{ .pointer = r }); +} + pub fn ref(v: Value) Interner.Ref { std.debug.assert(v.opt_ref != .none); return @enumFromInt(@intFromEnum(v.opt_ref)); } +pub fn fromRef(r: Interner.Ref) Value { + return .{ .opt_ref = @enumFromInt(@intFromEnum(r)) }; +} + pub fn is(v: Value, tag: std.meta.Tag(Interner.Key), comp: *const Compilation) bool { if (v.opt_ref == .none) return false; return comp.interner.get(v.ref()) == tag; @@ -243,12 +252,14 @@ pub const IntCastChangeKind = enum { /// `.none` value remains unchanged. pub fn intCast(v: *Value, dest_ty: Type, comp: *Compilation) !IntCastChangeKind { if (v.opt_ref == .none) return .none; + const key = comp.interner.get(v.ref()); + if (key == .pointer) return .none; const dest_bits: usize = @intCast(dest_ty.bitSizeof(comp).?); const dest_signed = dest_ty.signedness(comp) == .signed; var space: BigIntSpace = undefined; - const big = v.toBigInt(&space, comp); + const big = keyToBigInt(key, &space); const value_bits = big.bitCountTwosComp(); // if big is negative, then is signed. @@ -379,11 +390,12 @@ fn bigIntToFloat(limbs: []const std.math.big.Limb, positive: bool) f128 { } } -pub fn toBigInt(val: Value, space: *BigIntSpace, comp: *const Compilation) BigIntConst { - return switch (comp.interner.get(val.ref()).int) { - inline .u64, .i64 => |x| BigIntMutable.init(&space.limbs, x).toConst(), - .big_int => |b| b, - }; +fn keyToBigInt(key: Interner.Key, space: *BigIntSpace) BigIntConst { + return key.int.toBigInt(space); +} + +fn toBigInt(val: Value, space: *BigIntSpace, comp: *const Compilation) BigIntConst { + return keyToBigInt(comp.interner.get(val.ref()), space); } pub fn isZero(v: Value, comp: *const Compilation) bool { @@ -407,6 +419,7 @@ pub fn isZero(v: Value, comp: *const Compilation) bool { inline else => |data| return data[0] == 0.0 and data[1] == 0.0, }, .bytes => return false, + .pointer => return false, else => unreachable, } } @@ -470,9 +483,10 @@ pub fn toBool(v: Value, comp: *const Compilation) bool { pub fn toInt(v: Value, comptime T: type, comp: *const Compilation) ?T { if (v.opt_ref == .none) return null; - if (comp.interner.get(v.ref()) != .int) return null; + const key = comp.interner.get(v.ref()); + if (key != .int) return null; var space: BigIntSpace = undefined; - const big_int = v.toBigInt(&space, comp); + const big_int = keyToBigInt(key, &space); return big_int.to(T) catch null; } @@ -525,26 +539,52 @@ pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b }; res.* = try intern(comp, .{ .float = f }); return false; - } else { - var lhs_space: BigIntSpace = undefined; - var rhs_space: BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space, comp); - const rhs_bigint = rhs.toBigInt(&rhs_space, comp); + } + const lhs_key = comp.interner.get(lhs.ref()); + const rhs_key = comp.interner.get(rhs.ref()); + if (lhs_key == .pointer or rhs_key == .pointer) { + const rel, const index = if (lhs_key == .pointer) + .{ lhs_key.pointer, rhs } + else + .{ rhs_key.pointer, lhs }; + + const elem_size = try int(ty.elemType().sizeof(comp) orelse 1, comp); + var total_offset: Value = undefined; + const mul_overflow = try total_offset.mul(elem_size, index, comp.types.ptrdiff, comp); + const old_offset = fromRef(rel.offset); + const add_overflow = try total_offset.add(total_offset, old_offset, comp.types.ptrdiff, comp); + _ = try total_offset.intCast(comp.types.ptrdiff, comp); + res.* = try reloc(.{ .decl = rel.decl, .offset = total_offset.ref() }, comp); + return mul_overflow or add_overflow; + } - const limbs = try comp.gpa.alloc( - std.math.big.Limb, - std.math.big.int.calcTwosCompLimbCount(bits), - ); - defer comp.gpa.free(limbs); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + var lhs_space: BigIntSpace = undefined; + var rhs_space: BigIntSpace = undefined; + const lhs_bigint = keyToBigInt(lhs_key, &lhs_space); + const rhs_bigint = keyToBigInt(rhs_key, &rhs_space); - const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, ty.signedness(comp), bits); - res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } }); - return overflowed; - } + const limbs = try comp.gpa.alloc( + std.math.big.Limb, + std.math.big.int.calcTwosCompLimbCount(bits), + ); + defer comp.gpa.free(limbs); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + + const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, ty.signedness(comp), bits); + res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } }); + return overflowed; } -pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool { +pub fn negate(res: *Value, val: Value, ty: Type, comp: *Compilation) !bool { + return res.sub(zero, val, ty, undefined, comp); +} + +pub fn decrement(res: *Value, val: Value, ty: Type, comp: *Compilation) !bool { + return res.sub(val, one, ty, undefined, comp); +} + +/// elem_size is only used when subtracting two pointers, so we can scale the result by the size of the element type +pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, elem_size: u64, comp: *Compilation) !bool { const bits: usize = @intCast(ty.bitSizeof(comp).?); if (ty.isFloat()) { if (ty.isComplex()) { @@ -568,23 +608,50 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b }; res.* = try intern(comp, .{ .float = f }); return false; - } else { - var lhs_space: BigIntSpace = undefined; - var rhs_space: BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space, comp); - const rhs_bigint = rhs.toBigInt(&rhs_space, comp); - - const limbs = try comp.gpa.alloc( - std.math.big.Limb, - std.math.big.int.calcTwosCompLimbCount(bits), - ); - defer comp.gpa.free(limbs); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - - const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, ty.signedness(comp), bits); - res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } }); + } + const lhs_key = comp.interner.get(lhs.ref()); + const rhs_key = comp.interner.get(rhs.ref()); + if (lhs_key == .pointer and rhs_key == .pointer) { + const lhs_reloc = lhs_key.pointer; + const rhs_reloc = rhs_key.pointer; + if (lhs_reloc.decl != rhs_reloc.decl) { + res.* = .{}; + return false; + } + const lhs_offset = fromRef(lhs_reloc.offset); + const rhs_offset = fromRef(rhs_reloc.offset); + const overflowed = try res.sub(lhs_offset, rhs_offset, comp.types.ptrdiff, undefined, comp); + const rhs_size = try int(elem_size, comp); + _ = try res.div(res.*, rhs_size, comp.types.ptrdiff, comp); return overflowed; + } else if (lhs_key == .pointer) { + const rel = lhs_key.pointer; + + const lhs_size = try int(elem_size, comp); + var total_offset: Value = undefined; + const mul_overflow = try total_offset.mul(lhs_size, rhs, comp.types.ptrdiff, comp); + const old_offset = fromRef(rel.offset); + const add_overflow = try total_offset.sub(old_offset, total_offset, comp.types.ptrdiff, undefined, comp); + _ = try total_offset.intCast(comp.types.ptrdiff, comp); + res.* = try reloc(.{ .decl = rel.decl, .offset = total_offset.ref() }, comp); + return mul_overflow or add_overflow; } + + var lhs_space: BigIntSpace = undefined; + var rhs_space: BigIntSpace = undefined; + const lhs_bigint = keyToBigInt(lhs_key, &lhs_space); + const rhs_bigint = keyToBigInt(rhs_key, &rhs_space); + + const limbs = try comp.gpa.alloc( + std.math.big.Limb, + std.math.big.int.calcTwosCompLimbCount(bits), + ); + defer comp.gpa.free(limbs); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + + const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, ty.signedness(comp), bits); + res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } }); + return overflowed; } pub fn mul(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool { @@ -723,7 +790,7 @@ pub fn rem(lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !Value { var tmp: Value = undefined; _ = try tmp.div(lhs, rhs, ty, comp); _ = try tmp.mul(tmp, rhs, ty, comp); - _ = try tmp.sub(lhs, tmp, ty, comp); + _ = try tmp.sub(lhs, tmp, ty, undefined, comp); return tmp; } } @@ -899,12 +966,17 @@ pub fn complexConj(val: Value, ty: Type, comp: *Compilation) !Value { return intern(comp, .{ .complex = cf }); } -pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *const Compilation) bool { +fn shallowCompare(lhs: Value, op: std.math.CompareOperator, rhs: Value) ?bool { if (op == .eq) { return lhs.opt_ref == rhs.opt_ref; } else if (lhs.opt_ref == rhs.opt_ref) { return std.math.Order.eq.compare(op); } + return null; +} + +pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *const Compilation) bool { + if (lhs.shallowCompare(op, rhs)) |val| return val; const lhs_key = comp.interner.get(lhs.ref()); const rhs_key = comp.interner.get(rhs.ref()); @@ -927,6 +999,29 @@ pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *cons return lhs_bigint.order(rhs_bigint).compare(op); } +/// Returns null for values that cannot be compared at compile time (e.g. `&x < &y`) for globals `x` and `y`. +pub fn comparePointers(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *const Compilation) ?bool { + if (lhs.shallowCompare(op, rhs)) |val| return val; + + const lhs_key = comp.interner.get(lhs.ref()); + const rhs_key = comp.interner.get(rhs.ref()); + + if (lhs_key == .pointer and rhs_key == .pointer) { + const lhs_reloc = lhs_key.pointer; + const rhs_reloc = rhs_key.pointer; + switch (op) { + .eq => if (lhs_reloc.decl != rhs_reloc.decl) return false, + .neq => if (lhs_reloc.decl != rhs_reloc.decl) return true, + else => if (lhs_reloc.decl != rhs_reloc.decl) return null, + } + + const lhs_offset = fromRef(lhs_reloc.offset); + const rhs_offset = fromRef(rhs_reloc.offset); + return lhs_offset.compare(op, rhs_offset, comp); + } + return null; +} + fn twosCompIntLimit(limit: std.math.big.int.TwosCompIntLimit, ty: Type, comp: *Compilation) !Value { const signedness = ty.signedness(comp); if (limit == .min and signedness == .unsigned) return Value.zero; @@ -983,6 +1078,7 @@ pub fn print(v: Value, ty: Type, comp: *const Compilation, w: anytype) @TypeOf(w .cf32 => |components| return w.print("{d} + {d}i", .{ @round(@as(f64, @floatCast(components[0])) * 1000000) / 1000000, @round(@as(f64, @floatCast(components[1])) * 1000000) / 1000000 }), inline else => |components| return w.print("{d} + {d}i", .{ @as(f64, @floatCast(components[0])), @as(f64, @floatCast(components[1])) }), }, + .pointer => {}, else => unreachable, // not a value } } diff --git a/src/backend/Interner.zig b/src/backend/Interner.zig index 631ec8ee..3315a08d 100644 --- a/src/backend/Interner.zig +++ b/src/backend/Interner.zig @@ -65,6 +65,7 @@ pub const Key = union(enum) { float: Float, complex: Complex, bytes: []const u8, + pointer: Pointer, pub const Float = union(enum) { f16: f16, @@ -80,6 +81,12 @@ pub const Key = union(enum) { cf80: [2]f80, cf128: [2]f128, }; + pub const Pointer = struct { + /// NodeIndex of decl whose address we are offsetting from + decl: u32, + /// Offset in bytes + offset: Ref, + }; pub fn hash(key: Key) u32 { var hasher = Hash.init(0); @@ -303,6 +310,8 @@ pub const Tag = enum(u8) { bytes, /// `data` is `Record` record_ty, + /// `data` is Pointer + pointer, pub const Array = struct { len0: u32, @@ -322,6 +331,11 @@ pub const Tag = enum(u8) { child: Ref, }; + pub const Pointer = struct { + decl: u32, + offset: Ref, + }; + pub const Int = struct { limbs_index: u32, limbs_len: u32, @@ -606,6 +620,15 @@ pub fn put(i: *Interner, gpa: Allocator, key: Key) !Ref { }), }); }, + .pointer => |info| { + i.items.appendAssumeCapacity(.{ + .tag = .pointer, + .data = try i.addExtra(gpa, Tag.Pointer{ + .decl = info.decl, + .offset = info.offset, + }), + }); + }, .int => |repr| int: { var space: Tag.Int.BigIntSpace = undefined; const big = repr.toBigInt(&space); @@ -792,6 +815,13 @@ pub fn get(i: *const Interner, ref: Ref) Key { .child = vector_ty.child, } }; }, + .pointer => { + const pointer = i.extraData(Tag.Pointer, data); + return .{ .pointer = .{ + .decl = pointer.decl, + .offset = pointer.offset, + } }; + }, .u32 => .{ .int = .{ .u64 = data } }, .i32 => .{ .int = .{ .i64 = @as(i32, @bitCast(data)) } }, .int_positive, .int_negative => { diff --git a/test/cases/const decl folding.c b/test/cases/const decl folding.c index d32d1288..694b71ad 100644 --- a/test/cases/const decl folding.c +++ b/test/cases/const decl folding.c @@ -67,14 +67,14 @@ _Static_assert(4.2, ""); "const decl folding.c:27:14: note: previous case defined here" \ "const decl folding.c:34:27: error: '__builtin_choose_expr' requires a constant expression" \ "const decl folding.c:38:15: warning: variable length array folded to constant array as an extension [-Wgnu-folding-constant]" \ - "const decl folding.c:43:16: warning: expression is not an integer constant expression; folding it to a constant is a GNU extension [-Wgnu-folding-constant]" \ "const decl folding.c:43:16: warning: implicit conversion turns string literal into bool: 'char [1]' to '_Bool' [-Wstring-conversion]" \ + "const decl folding.c:43:16: error: static_assert expression is not an integral constant expression" \ "const decl folding.c:44:1: error: static assertion failed \"\"" \ - "const decl folding.c:46:1: error: static assertion failed \"\"" \ - "const decl folding.c:47:16: warning: expression is not an integer constant expression; folding it to a constant is a GNU extension [-Wgnu-folding-constant]" \ - "const decl folding.c:47:16: note: this conversion is not allowed in a constant expression" \ - "const decl folding.c:50:16: warning: expression is not an integer constant expression; folding it to a constant is a GNU extension [-Wgnu-folding-constant]" \ + "const decl folding.c:46:16: error: static_assert expression is not an integral constant expression" \ + "const decl folding.c:47:16: error: static_assert expression is not an integral constant expression" \ "const decl folding.c:50:16: warning: address of array 'arr' will always evaluate to 'true' [-Wpointer-bool-conversion]" \ + "const decl folding.c:50:16: error: static_assert expression is not an integral constant expression" \ "const decl folding.c:51:1: error: static assertion failed \"\"" \ "const decl folding.c:53:16: warning: implicit conversion from 'double' to '_Bool' changes value from 4.2 to true [-Wfloat-conversion]" \ + "const decl folding.c:53:16: error: static_assert expression is not an integral constant expression" \ diff --git a/test/cases/relocations.c b/test/cases/relocations.c new file mode 100644 index 00000000..6c049d22 --- /dev/null +++ b/test/cases/relocations.c @@ -0,0 +1,58 @@ +//aro-args --target=x86_64-linux -std=c23 + +int arr[20] = {0}; +_Static_assert(&arr[10] - &arr[0] == 10); +_Static_assert(&arr[1] + 3 == &arr[4]); +_Static_assert(&arr[4] - 4 == &arr[0]); + +struct Child { + int x[10]; + char c; +}; + +struct Parent { + int x; + struct Child children[20]; +}; + +struct Parent parents[10]; +_Static_assert(&parents[5].children[10].x[5] - &parents[0].children[0].x[0] == 1220); + +int x; +int y; +_Static_assert(&x != &y); +_Static_assert(&x + 1 == &y + 1); + +_Static_assert(&x + 1 != &x + 2); +_Static_assert(&x == &x); +_Static_assert(&x >= &x); +_Static_assert(&x > &y); +_Static_assert(&x); + +struct __attribute__((packed)) Packed { + int x; + char c; + int y; +}; + +struct Packed packed; +_Static_assert(&packed.x - &packed.y == -1); + +char *p = (char*)(&x + 100); + +_Static_assert((char*)(&x+100) - (char*)&x == 400,""); +_Static_assert(&x - 2 != &x + 2, ""); +_Static_assert(&x - 2 == -2 + &x, ""); + +union Empty {}; + +union Empty empty[10]; +_Static_assert(&empty[4] - &empty[0] == 0, ""); +_Static_assert(&empty[4] >= &empty[0], ""); + +#define EXPECTED_ERRORS "relocations.c:24:1: error: static assertion failed" \ + "relocations.c:29:16: error: static_assert expression is not an integral constant expression" \ + "relocations.c:30:16: error: static_assert expression is not an integral constant expression" \ + "relocations.c:50:26: warning: subtraction of pointers to type 'union Empty' of zero size has undefined behavior [-Wpointer-arith]" \ + "relocations.c:50:16: error: static_assert expression is not an integral constant expression" \ +