From ac68bed9cd91cdfd3030c7c6cf72b2c73588f295 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Mon, 23 Sep 2024 15:19:51 -0700 Subject: [PATCH 1/6] Parser: add frontend support for global var relocations --- src/aro/Attribute.zig | 1 + src/aro/Diagnostics/messages.def | 4 ++ src/aro/Parser.zig | 99 +++++++++++++++++++++++++------- src/aro/Tree.zig | 2 +- src/aro/Value.zig | 93 ++++++++++++++++++++++++++++-- src/backend/Interner.zig | 42 ++++++++++++++ test/cases/const decl folding.c | 10 ++-- test/cases/relocations.c | 44 ++++++++++++++ 8 files changed, 264 insertions(+), 31 deletions(-) create mode 100644 test/cases/relocations.c diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 2bdf5be2..01fac90c 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -340,6 +340,7 @@ fn diagnoseField( .array_ty, .vector_ty, .record_ty, + .global_var_offset, => unreachable, }); } diff --git a/src/aro/Diagnostics/messages.def b/src/aro/Diagnostics/messages.def index fc740b36..5bd5221b 100644 --- a/src/aro/Diagnostics/messages.def +++ b/src/aro/Diagnostics/messages.def @@ -2535,3 +2535,7 @@ 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" diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 21df6625..47e78f50 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -486,9 +486,9 @@ pub fn valueChangedStr(p: *Parser, res: *Result, old_value: Value, int_ty: Type) try w.writeAll(" changes "); if (res.val.isZero(p.comp)) try w.writeAll("non-zero "); try w.writeAll("value from "); - try old_value.print(res.ty, p.comp, w); + try old_value.print(res.ty, p.comp, p.comp.string_interner.getSlowTypeMapper(), w); try w.writeAll(" to "); - try res.val.print(int_ty, p.comp, w); + try res.val.print(int_ty, p.comp, p.comp.string_interner.getSlowTypeMapper(), w); return try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); } @@ -1284,16 +1284,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); @@ -5183,7 +5178,7 @@ pub const Result = struct { const strings_top = p.strings.items.len; defer p.strings.items.len = strings_top; - try res.val.print(res.ty, p.comp, p.strings.writer()); + try res.val.print(res.ty, p.comp, p.comp.string_interner.getSlowTypeMapper(), p.strings.writer()); return try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); } @@ -6589,8 +6584,8 @@ 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 = lhs.val.compareExtra(op, rhs.val, p.comp); + lhs.val = if (res) |val| Value.fromBool(val) else .{}; } else { lhs.val.boolCast(p.comp); } @@ -6620,8 +6615,8 @@ 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 = lhs.val.compareExtra(op, rhs.val, p.comp); + lhs.val = if (res) |val| Value.fromBool(val) else .{}; } else { lhs.val.boolCast(p.comp); } @@ -6677,7 +6672,7 @@ fn addExpr(p: *Parser) Error!Result { 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); } else { - if (try lhs.val.sub(lhs.val, rhs.val, lhs.ty, p.comp) and + if (try lhs.val.sub(lhs.val, rhs.val, lhs.ty, rhs.ty, p.comp) and lhs.ty.signedness(p.comp) != .unsigned) try p.errOverflow(minus.?, lhs); } } @@ -7034,6 +7029,54 @@ fn offsetofMemberDesignator(p: *Parser, base_ty: Type, want_bits: bool) Error!Re return Result{ .ty = base_ty, .val = val, .node = lhs.node }; } +const Reloc = struct { + global: StringId, + offset: i64, +}; + +fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: i64) !Reloc { + 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)); + return .{ .global = var_name, .offset = offset_so_far }; + }, + .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; + const as_int = index_val.toInt(i64, p.comp).?; + const size = ty.sizeof(p.comp).?; + const this_offset = @as(i64, @intCast(size)) * as_int; + return p.computeOffsetExtra(bin_data.lhs, this_offset + 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)); + return p.computeOffsetExtra(member.lhs, offset_so_far + field_offset); + }, + else => return error.InvalidReloc, + } +} + +fn computeOffset(p: *Parser, node: NodeIndex) !Reloc { + return p.computeOffsetExtra(node, 0); +} + /// unExpr /// : (compoundLiteral | primaryExpr) suffixExpr* /// | '&&' IDENTIFIER @@ -7074,9 +7117,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 +7129,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: Reloc = p.computeOffset(operand.node) catch |e| switch (e) { + error.InvalidReloc => blk: { + try p.errTok(.non_constant_initializer, ampersand_tok); + break :blk .{ .global = .empty, .offset = 0 }; + }, + else => |er| return er, + }; + addr_val = try Value.reloc(reloc.global, reloc.offset, 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 +7155,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 +7201,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 +7255,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/Tree.zig b/src/aro/Tree.zig index 3bab9453..fd639e5d 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -899,7 +899,7 @@ fn dumpNode( if (tree.value_map.get(node)) |val| { try config.setColor(w, LITERAL); try w.writeAll(" (value: "); - try val.print(ty, tree.comp, w); + try val.print(ty, tree.comp, mapper, w); try w.writeByte(')'); } if (tag == .implicit_return and data.return_zero) { diff --git a/src/aro/Value.zig b/src/aro/Value.zig index 61b8f0f8..3b18068a 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -4,6 +4,8 @@ const BigIntConst = std.math.big.int.Const; const BigIntMutable = std.math.big.int.Mutable; const backend = @import("backend"); const Interner = backend.Interner; +const StringInterner = backend.StringInterner; +const StringId = StringInterner.StringId; const BigIntSpace = Interner.Tag.Int.BigIntSpace; const Compilation = @import("Compilation.zig"); const Type = @import("Type.zig"); @@ -32,6 +34,10 @@ pub fn int(i: anytype, comp: *Compilation) !Value { } } +pub fn reloc(name: StringId, offset: i64, comp: *Compilation) !Value { + return intern(comp, .{ .global_var_offset = .{ .name = name, .offset = offset } }); +} + pub fn ref(v: Value) Interner.Ref { std.debug.assert(v.opt_ref != .none); return @enumFromInt(@intFromEnum(v.opt_ref)); @@ -407,6 +413,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, + .global_var_offset => return false, else => unreachable, } } @@ -503,6 +510,21 @@ fn complexAddSub(lhs: Value, rhs: Value, comptime T: type, op: ComplexOp, comp: pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool { const bits: usize = @intCast(ty.bitSizeof(comp).?); + if (ty.isPtr()) blk: { + const lhs_key = comp.interner.get(lhs.ref()); + const rhs_key = comp.interner.get(rhs.ref()); + const rel, const delta = if (lhs_key == .global_var_offset) + .{ lhs_key.global_var_offset, rhs.toInt(i64, comp).? } + else if (rhs_key == .global_var_offset) + .{ rhs_key.global_var_offset, lhs.toInt(i64, comp).? } + else + break :blk; + + const elem_size: i64 = @intCast(ty.elemType().sizeof(comp) orelse 1); + const total_offset = rel.offset + elem_size * delta; + res.* = try reloc(rel.name, total_offset, comp); + return false; + } if (ty.isFloat()) { if (ty.isComplex()) { res.* = switch (bits) { @@ -544,8 +566,28 @@ pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b } } -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, Type.int, comp); +} + +pub fn decrement(res: *Value, val: Value, ty: Type, comp: *Compilation) !bool { + return res.sub(val, one, ty, Type.int, comp); +} + +pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, rhs_ty: Type, comp: *Compilation) !bool { const bits: usize = @intCast(ty.bitSizeof(comp).?); + if (ty.isPtr()) { + const maybe_rel = comp.interner.get(lhs.ref()); + if (maybe_rel == .global_var_offset) { + const rel = maybe_rel.global_var_offset; + const delta = rhs.toInt(i64, comp).?; + + const elem_size: i64 = @intCast(ty.elemType().sizeof(comp) orelse 1); + const total_offset = rel.offset - elem_size * delta; + res.* = try reloc(rel.name, total_offset, comp); + return false; + } + } if (ty.isFloat()) { if (ty.isComplex()) { res.* = switch (bits) { @@ -569,6 +611,22 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b res.* = try intern(comp, .{ .float = f }); return false; } else { + if (rhs_ty.isPtr()) { + const maybe_lhs_reloc = comp.interner.get(lhs.ref()); + const maybe_rhs_reloc = comp.interner.get(rhs.ref()); + if (maybe_lhs_reloc == .global_var_offset and maybe_rhs_reloc == .global_var_offset) { + const lhs_reloc = maybe_lhs_reloc.global_var_offset; + const rhs_reloc = maybe_rhs_reloc.global_var_offset; + if (lhs_reloc.name != rhs_reloc.name) { + res.* = .{}; + return false; + } + const difference, const overflowed = @subWithOverflow(lhs_reloc.offset, rhs_reloc.offset); + const rhs_size: i64 = @intCast(rhs_ty.elemType().sizeof(comp) orelse 1); + res.* = try int(@divTrunc(difference, rhs_size), comp); + return overflowed != 0; + } + } var lhs_space: BigIntSpace = undefined; var rhs_space: BigIntSpace = undefined; const lhs_bigint = lhs.toBigInt(&lhs_space, comp); @@ -723,7 +781,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, Type.int, comp); return tmp; } } @@ -899,7 +957,7 @@ 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 { +pub fn compareExtra(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *const Compilation) ?bool { if (op == .eq) { return lhs.opt_ref == rhs.opt_ref; } else if (lhs.opt_ref == rhs.opt_ref) { @@ -919,6 +977,19 @@ pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *cons const imag_equal = std.math.compare(lhs.imag(f128, comp), .eq, rhs.imag(f128, comp)); return !real_equal or !imag_equal; } + if (lhs_key == .global_var_offset and rhs_key == .global_var_offset) { + const lhs_reloc = lhs_key.global_var_offset; + const rhs_reloc = rhs_key.global_var_offset; + switch (op) { + .eq => if (lhs_reloc.name != rhs_reloc.name) return false, + .neq => if (lhs_reloc.name != rhs_reloc.name) return true, + else => if (lhs_reloc.name != rhs_reloc.name) return null, + } + + return std.math.compare(lhs_reloc.offset, op, rhs_reloc.offset); + } else if (lhs_key == .global_var_offset or rhs_key == .global_var_offset) { + return null; + } var lhs_bigint_space: BigIntSpace = undefined; var rhs_bigint_space: BigIntSpace = undefined; @@ -927,6 +998,10 @@ pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *cons return lhs_bigint.order(rhs_bigint).compare(op); } +pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *const Compilation) bool { + return lhs.compareExtra(op, rhs, comp).?; +} + 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; @@ -963,7 +1038,7 @@ pub fn maxInt(ty: Type, comp: *Compilation) !Value { return twosCompIntLimit(.max, ty, comp); } -pub fn print(v: Value, ty: Type, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { +pub fn print(v: Value, ty: Type, comp: *const Compilation, mapper: StringInterner.TypeMapper, w: anytype) @TypeOf(w).Error!void { if (ty.is(.bool)) { return w.writeAll(if (v.isZero(comp)) "false" else "true"); } @@ -983,6 +1058,16 @@ 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])) }), }, + .global_var_offset => |rel| { + const name = mapper.lookup(rel.name); + try w.print("&{s}", .{name}); + if (rel.offset == 0) return; + if (rel.offset == std.math.minInt(i64)) { + return w.print(" - {d}", .{std.math.maxInt(i64) + 1}); + } + const sign: u8 = if (rel.offset < 0) '-' else '+'; + return w.print(" {c} {d}", .{ sign, @abs(rel.offset) }); + }, else => unreachable, // not a value } } diff --git a/src/backend/Interner.zig b/src/backend/Interner.zig index 631ec8ee..531f8191 100644 --- a/src/backend/Interner.zig +++ b/src/backend/Interner.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const StringId = @import("StringInterner.zig").StringId; const Allocator = std.mem.Allocator; const assert = std.debug.assert; const BigIntConst = std.math.big.int.Const; @@ -65,6 +66,7 @@ pub const Key = union(enum) { float: Float, complex: Complex, bytes: []const u8, + global_var_offset: GlobalVarOffset, pub const Float = union(enum) { f16: f16, @@ -80,6 +82,11 @@ pub const Key = union(enum) { cf80: [2]f80, cf128: [2]f128, }; + pub const GlobalVarOffset = struct { + name: StringId, + /// Offset in bytes + offset: i64, + }; 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 GlobalVarOffset + global_var_offset, pub const Array = struct { len0: u32, @@ -537,6 +546,29 @@ pub const Tag = enum(u8) { // trailing // [elements_len]Ref }; + + pub const GlobalVarOffset = struct { + name: u32, + piece0: u32, + piece1: u32, + + pub fn get(self: GlobalVarOffset) Key.GlobalVarOffset { + const offset = @as(u64, self.piece0) | (@as(u64, self.piece1) << 32); + return .{ + .name = @enumFromInt(self.name), + .offset = @bitCast(offset), + }; + } + + fn pack(val: Key.GlobalVarOffset) GlobalVarOffset { + const bits: u64 = @bitCast(val.offset); + return .{ + .name = @intFromEnum(val.name), + .piece0 = @truncate(bits), + .piece1 = @truncate(bits >> 32), + }; + } + }; }; pub const PackedU64 = packed struct(u64) { @@ -716,6 +748,12 @@ pub fn put(i: *Interner, gpa: Allocator, key: Key) !Ref { }); i.extra.appendSliceAssumeCapacity(@ptrCast(elems)); }, + .global_var_offset => |reloc| { + i.items.appendAssumeCapacity(.{ + .tag = .global_var_offset, + .data = try i.addExtra(gpa, Tag.GlobalVarOffset.pack(reloc)), + }); + }, .ptr_ty, .noreturn_ty, .void_ty, @@ -848,6 +886,10 @@ pub fn get(i: *const Interner, ref: Ref) Key { .record_ty = @ptrCast(i.extra.items[extra.end..][0..extra.data.elements_len]), }; }, + .global_var_offset => { + const components = i.extraData(Tag.GlobalVarOffset, data); + return .{ .global_var_offset = components.get() }; + }, }; } 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..b726b8b3 --- /dev/null +++ b/test/cases/relocations.c @@ -0,0 +1,44 @@ +//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); + +#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" \ + From f258fd113e01cd9b423aadbd02a4b9e87c257691 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Fri, 27 Sep 2024 13:28:35 -0700 Subject: [PATCH 2/6] Value: reloc cleanup Add better error and overflow handling and add some error messages --- src/aro/Diagnostics/messages.def | 6 ++ src/aro/Parser.zig | 46 +++++--- src/aro/Value.zig | 173 ++++++++++++++++--------------- test/cases/relocations.c | 4 + 4 files changed, 132 insertions(+), 97 deletions(-) diff --git a/src/aro/Diagnostics/messages.def b/src/aro/Diagnostics/messages.def index 5bd5221b..883ae198 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 diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 47e78f50..1daeefcc 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 GlobalVarOffset = @import("backend").Interner.Key.GlobalVarOffset; 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 }), @@ -6669,8 +6688,13 @@ 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, rhs.ty, p.comp) and lhs.ty.signedness(p.comp) != .unsigned) try p.errOverflow(minus.?, lhs); @@ -7029,12 +7053,7 @@ fn offsetofMemberDesignator(p: *Parser, base_ty: Type, want_bits: bool) Error!Re return Result{ .ty = base_ty, .val = val, .node = lhs.node }; } -const Reloc = struct { - global: StringId, - offset: i64, -}; - -fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: i64) !Reloc { +fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: i64) !GlobalVarOffset { const tys = p.nodes.items(.ty); const tags = p.nodes.items(.tag); const data = p.nodes.items(.data); @@ -7051,7 +7070,7 @@ fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: i64) !Reloc { .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)); - return .{ .global = var_name, .offset = offset_so_far }; + return .{ .name = var_name, .offset = offset_so_far }; }, .array_access_expr => { const bin_data = data[@intFromEnum(node)].bin; @@ -7073,7 +7092,8 @@ fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: i64) !Reloc { } } -fn computeOffset(p: *Parser, node: NodeIndex) !Reloc { +/// Compute the offset (in bytes) of an expression from a base pointer. +fn computeOffset(p: *Parser, node: NodeIndex) !GlobalVarOffset { return p.computeOffsetExtra(node, 0); } @@ -7134,14 +7154,14 @@ fn unExpr(p: *Parser) Error!Result { try p.errTok(.addr_of_rvalue, tok); } else if (operand_ty_valid and p.func.ty == null) { // address of global - const reloc: Reloc = p.computeOffset(operand.node) catch |e| switch (e) { + const reloc: GlobalVarOffset = p.computeOffset(operand.node) catch |e| switch (e) { error.InvalidReloc => blk: { try p.errTok(.non_constant_initializer, ampersand_tok); - break :blk .{ .global = .empty, .offset = 0 }; + break :blk .{ .name = .empty, .offset = 0 }; }, else => |er| return er, }; - addr_val = try Value.reloc(reloc.global, reloc.offset, p.comp); + addr_val = try Value.reloc(reloc, p.comp); } if (operand.ty.qual.register) try p.errTok(.addr_of_register, tok); diff --git a/src/aro/Value.zig b/src/aro/Value.zig index 3b18068a..9f6091ee 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -34,8 +34,8 @@ pub fn int(i: anytype, comp: *Compilation) !Value { } } -pub fn reloc(name: StringId, offset: i64, comp: *Compilation) !Value { - return intern(comp, .{ .global_var_offset = .{ .name = name, .offset = offset } }); +pub fn reloc(r: Interner.Key.GlobalVarOffset, comp: *Compilation) !Value { + return intern(comp, .{ .global_var_offset = r }); } pub fn ref(v: Value) Interner.Ref { @@ -249,12 +249,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 == .global_var_offset) 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. @@ -385,11 +387,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 { @@ -477,9 +480,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; } @@ -510,21 +514,6 @@ fn complexAddSub(lhs: Value, rhs: Value, comptime T: type, op: ComplexOp, comp: pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool { const bits: usize = @intCast(ty.bitSizeof(comp).?); - if (ty.isPtr()) blk: { - const lhs_key = comp.interner.get(lhs.ref()); - const rhs_key = comp.interner.get(rhs.ref()); - const rel, const delta = if (lhs_key == .global_var_offset) - .{ lhs_key.global_var_offset, rhs.toInt(i64, comp).? } - else if (rhs_key == .global_var_offset) - .{ rhs_key.global_var_offset, lhs.toInt(i64, comp).? } - else - break :blk; - - const elem_size: i64 = @intCast(ty.elemType().sizeof(comp) orelse 1); - const total_offset = rel.offset + elem_size * delta; - res.* = try reloc(rel.name, total_offset, comp); - return false; - } if (ty.isFloat()) { if (ty.isComplex()) { res.* = switch (bits) { @@ -547,47 +536,53 @@ 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 == .global_var_offset or rhs_key == .global_var_offset) { + const rel, const index = if (lhs_key == .global_var_offset) + .{ lhs_key.global_var_offset, rhs } + else + .{ rhs_key.global_var_offset, 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 = try int(rel.offset, comp); + 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(.{ .name = rel.name, .offset = total_offset.toInt(i64, comp).? }, 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 negate(res: *Value, val: Value, ty: Type, comp: *Compilation) !bool { - return res.sub(zero, val, ty, Type.int, comp); + 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, Type.int, comp); + return res.sub(val, one, ty, undefined, comp); } +/// rhs_ty 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, rhs_ty: Type, comp: *Compilation) !bool { const bits: usize = @intCast(ty.bitSizeof(comp).?); - if (ty.isPtr()) { - const maybe_rel = comp.interner.get(lhs.ref()); - if (maybe_rel == .global_var_offset) { - const rel = maybe_rel.global_var_offset; - const delta = rhs.toInt(i64, comp).?; - - const elem_size: i64 = @intCast(ty.elemType().sizeof(comp) orelse 1); - const total_offset = rel.offset - elem_size * delta; - res.* = try reloc(rel.name, total_offset, comp); - return false; - } - } if (ty.isFloat()) { if (ty.isComplex()) { res.* = switch (bits) { @@ -610,39 +605,48 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, rhs_ty: Type, comp: *C }; res.* = try intern(comp, .{ .float = f }); return false; - } else { - if (rhs_ty.isPtr()) { - const maybe_lhs_reloc = comp.interner.get(lhs.ref()); - const maybe_rhs_reloc = comp.interner.get(rhs.ref()); - if (maybe_lhs_reloc == .global_var_offset and maybe_rhs_reloc == .global_var_offset) { - const lhs_reloc = maybe_lhs_reloc.global_var_offset; - const rhs_reloc = maybe_rhs_reloc.global_var_offset; - if (lhs_reloc.name != rhs_reloc.name) { - res.* = .{}; - return false; - } - const difference, const overflowed = @subWithOverflow(lhs_reloc.offset, rhs_reloc.offset); - const rhs_size: i64 = @intCast(rhs_ty.elemType().sizeof(comp) orelse 1); - res.* = try int(@divTrunc(difference, rhs_size), comp); - return overflowed != 0; - } + } + const lhs_key = comp.interner.get(lhs.ref()); + const rhs_key = comp.interner.get(rhs.ref()); + if (lhs_key == .global_var_offset and rhs_key == .global_var_offset) { + const lhs_reloc = lhs_key.global_var_offset; + const rhs_reloc = rhs_key.global_var_offset; + if (lhs_reloc.name != rhs_reloc.name) { + res.* = .{}; + return false; } - 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 difference, const overflowed = @subWithOverflow(lhs_reloc.offset, rhs_reloc.offset); + const rhs_size: i64 = @intCast(rhs_ty.elemType().sizeof(comp) orelse 1); + res.* = try int(@divTrunc(difference, rhs_size), comp); + return overflowed != 0; + } else if (lhs_key == .global_var_offset) { + const rel = lhs_key.global_var_offset; + + 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, rhs, comp.types.ptrdiff, comp); + const old_offset = try int(rel.offset, comp); + const add_overflow = try total_offset.sub(total_offset, old_offset, comp.types.ptrdiff, undefined, comp); + _ = try total_offset.intCast(comp.types.ptrdiff, comp); + res.* = try reloc(.{ .name = rel.name, .offset = total_offset.toInt(i64, comp).? }, 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.subWrap(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.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 { @@ -957,6 +961,7 @@ pub fn complexConj(val: Value, ty: Type, comp: *Compilation) !Value { return intern(comp, .{ .complex = cf }); } +/// Returns null for values that cannot be compared at compile time (e.g. `&x < &y`) for globals `x` and `y`. pub fn compareExtra(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *const Compilation) ?bool { if (op == .eq) { return lhs.opt_ref == rhs.opt_ref; diff --git a/test/cases/relocations.c b/test/cases/relocations.c index b726b8b3..d275f9bb 100644 --- a/test/cases/relocations.c +++ b/test/cases/relocations.c @@ -38,6 +38,10 @@ struct __attribute__((packed)) Packed { struct Packed packed; _Static_assert(&packed.x - &packed.y == -1); +char *p = (char*)(&x + 100); + +_Static_assert((char*)(&x+100) - (char*)&x == 400,""); + #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" \ From 76f1e64ea886fce7c396b42a8b3540446219e2df Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 5 Oct 2024 21:36:06 -0700 Subject: [PATCH 3/6] Value: use a separate function for comparing pointers --- src/aro/Parser.zig | 11 +++++++++-- src/aro/Value.zig | 38 +++++++++++++++++++++++--------------- test/cases/relocations.c | 2 ++ 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 1daeefcc..8ed91d14 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -6603,7 +6603,11 @@ 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.compareExtra(op, rhs.val, p.comp); + 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); @@ -6634,7 +6638,10 @@ fn compExpr(p: *Parser) Error!Result { .greater_than_equal_expr => .gte, else => unreachable, }; - const res = lhs.val.compareExtra(op, rhs.val, p.comp); + 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); diff --git a/src/aro/Value.zig b/src/aro/Value.zig index 9f6091ee..21efd232 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -626,7 +626,7 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, rhs_ty: Type, comp: *C var total_offset: Value = undefined; const mul_overflow = try total_offset.mul(elem_size, rhs, comp.types.ptrdiff, comp); const old_offset = try int(rel.offset, comp); - const add_overflow = try total_offset.sub(total_offset, old_offset, comp.types.ptrdiff, undefined, comp); + 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(.{ .name = rel.name, .offset = total_offset.toInt(i64, comp).? }, comp); return mul_overflow or add_overflow; @@ -961,13 +961,17 @@ pub fn complexConj(val: Value, ty: Type, comp: *Compilation) !Value { return intern(comp, .{ .complex = cf }); } -/// Returns null for values that cannot be compared at compile time (e.g. `&x < &y`) for globals `x` and `y`. -pub fn compareExtra(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()); @@ -982,6 +986,21 @@ pub fn compareExtra(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: const imag_equal = std.math.compare(lhs.imag(f128, comp), .eq, rhs.imag(f128, comp)); return !real_equal or !imag_equal; } + + var lhs_bigint_space: BigIntSpace = undefined; + var rhs_bigint_space: BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_bigint_space, comp); + const rhs_bigint = rhs.toBigInt(&rhs_bigint_space, comp); + 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 == .global_var_offset and rhs_key == .global_var_offset) { const lhs_reloc = lhs_key.global_var_offset; const rhs_reloc = rhs_key.global_var_offset; @@ -992,19 +1011,8 @@ pub fn compareExtra(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: } return std.math.compare(lhs_reloc.offset, op, rhs_reloc.offset); - } else if (lhs_key == .global_var_offset or rhs_key == .global_var_offset) { - return null; } - - var lhs_bigint_space: BigIntSpace = undefined; - var rhs_bigint_space: BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_bigint_space, comp); - const rhs_bigint = rhs.toBigInt(&rhs_bigint_space, comp); - return lhs_bigint.order(rhs_bigint).compare(op); -} - -pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *const Compilation) bool { - return lhs.compareExtra(op, rhs, comp).?; + return null; } fn twosCompIntLimit(limit: std.math.big.int.TwosCompIntLimit, ty: Type, comp: *Compilation) !Value { diff --git a/test/cases/relocations.c b/test/cases/relocations.c index d275f9bb..0e0a25d6 100644 --- a/test/cases/relocations.c +++ b/test/cases/relocations.c @@ -41,6 +41,8 @@ _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, ""); #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" \ From 4dcec74f070ecc46c04d8c0740175b8643c35038 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 8 Oct 2024 13:02:38 -0700 Subject: [PATCH 4/6] Interner: rename global_var_offset to pointer and store decl node index --- src/aro/Attribute.zig | 2 +- src/aro/Compilation.zig | 4 +++ src/aro/Parser.zig | 19 ++++++------ src/aro/Tree.zig | 2 +- src/aro/Value.zig | 64 +++++++++++++++++----------------------- src/backend/Interner.zig | 36 +++++++++++----------- 6 files changed, 61 insertions(+), 66 deletions(-) diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 01fac90c..30f728ed 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -340,7 +340,7 @@ fn diagnoseField( .array_ty, .vector_ty, .record_ty, - .global_var_offset, + .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/Parser.zig b/src/aro/Parser.zig index 8ed91d14..fa56c7fc 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -26,7 +26,7 @@ const Symbol = SymbolStack.Symbol; const record_layout = @import("record_layout.zig"); const StrInt = @import("StringInterner.zig"); const StringId = StrInt.StringId; -const GlobalVarOffset = @import("backend").Interner.Key.GlobalVarOffset; +const Pointer = @import("backend").Interner.Key.Pointer; const Builtins = @import("Builtins.zig"); const Builtin = Builtins.Builtin; const evalBuiltin = @import("Builtins/eval.zig").eval; @@ -505,9 +505,9 @@ pub fn valueChangedStr(p: *Parser, res: *Result, old_value: Value, int_ty: Type) try w.writeAll(" changes "); if (res.val.isZero(p.comp)) try w.writeAll("non-zero "); try w.writeAll("value from "); - try old_value.print(res.ty, p.comp, p.comp.string_interner.getSlowTypeMapper(), w); + try old_value.print(res.ty, p.comp, w); try w.writeAll(" to "); - try res.val.print(int_ty, p.comp, p.comp.string_interner.getSlowTypeMapper(), w); + try res.val.print(int_ty, p.comp, w); return try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); } @@ -5197,7 +5197,7 @@ pub const Result = struct { const strings_top = p.strings.items.len; defer p.strings.items.len = strings_top; - try res.val.print(res.ty, p.comp, p.comp.string_interner.getSlowTypeMapper(), p.strings.writer()); + try res.val.print(res.ty, p.comp, p.strings.writer()); return try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); } @@ -7060,7 +7060,7 @@ 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: i64) !GlobalVarOffset { +fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: i64) !Pointer { const tys = p.nodes.items(.ty); const tags = p.nodes.items(.tag); const data = p.nodes.items(.data); @@ -7077,7 +7077,8 @@ fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: i64) !GlobalVa .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)); - return .{ .name = var_name, .offset = offset_so_far }; + 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 }; }, .array_access_expr => { const bin_data = data[@intFromEnum(node)].bin; @@ -7100,7 +7101,7 @@ fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: i64) !GlobalVa } /// Compute the offset (in bytes) of an expression from a base pointer. -fn computeOffset(p: *Parser, node: NodeIndex) !GlobalVarOffset { +fn computeOffset(p: *Parser, node: NodeIndex) !Pointer { return p.computeOffsetExtra(node, 0); } @@ -7161,10 +7162,10 @@ fn unExpr(p: *Parser) Error!Result { try p.errTok(.addr_of_rvalue, tok); } else if (operand_ty_valid and p.func.ty == null) { // address of global - const reloc: GlobalVarOffset = p.computeOffset(operand.node) catch |e| switch (e) { + const reloc: Pointer = p.computeOffset(operand.node) catch |e| switch (e) { error.InvalidReloc => blk: { try p.errTok(.non_constant_initializer, ampersand_tok); - break :blk .{ .name = .empty, .offset = 0 }; + break :blk .{ .decl = @intFromEnum(NodeIndex.none), .offset = 0 }; }, else => |er| return er, }; diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index fd639e5d..3bab9453 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -899,7 +899,7 @@ fn dumpNode( if (tree.value_map.get(node)) |val| { try config.setColor(w, LITERAL); try w.writeAll(" (value: "); - try val.print(ty, tree.comp, mapper, w); + try val.print(ty, tree.comp, w); try w.writeByte(')'); } if (tag == .implicit_return and data.return_zero) { diff --git a/src/aro/Value.zig b/src/aro/Value.zig index 21efd232..d6b4d736 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -2,11 +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 StringInterner = backend.StringInterner; -const StringId = StringInterner.StringId; +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"); @@ -34,8 +33,8 @@ pub fn int(i: anytype, comp: *Compilation) !Value { } } -pub fn reloc(r: Interner.Key.GlobalVarOffset, comp: *Compilation) !Value { - return intern(comp, .{ .global_var_offset = r }); +pub fn reloc(r: Interner.Key.Pointer, comp: *Compilation) !Value { + return intern(comp, .{ .pointer = r }); } pub fn ref(v: Value) Interner.Ref { @@ -250,7 +249,7 @@ pub const IntCastChangeKind = enum { 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 == .global_var_offset) return .none; + if (key == .pointer) return .none; const dest_bits: usize = @intCast(dest_ty.bitSizeof(comp).?); const dest_signed = dest_ty.signedness(comp) == .signed; @@ -416,7 +415,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, - .global_var_offset => return false, + .pointer => return false, else => unreachable, } } @@ -539,11 +538,11 @@ pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b } const lhs_key = comp.interner.get(lhs.ref()); const rhs_key = comp.interner.get(rhs.ref()); - if (lhs_key == .global_var_offset or rhs_key == .global_var_offset) { - const rel, const index = if (lhs_key == .global_var_offset) - .{ lhs_key.global_var_offset, rhs } + if (lhs_key == .pointer or rhs_key == .pointer) { + const rel, const index = if (lhs_key == .pointer) + .{ lhs_key.pointer, rhs } else - .{ rhs_key.global_var_offset, lhs }; + .{ rhs_key.pointer, lhs }; const elem_size = try int(ty.elemType().sizeof(comp) orelse 1, comp); var total_offset: Value = undefined; @@ -551,7 +550,7 @@ pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b const old_offset = try int(rel.offset, comp); 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(.{ .name = rel.name, .offset = total_offset.toInt(i64, comp).? }, comp); + res.* = try reloc(.{ .decl = rel.decl, .offset = total_offset.toInt(i64, comp).? }, comp); return mul_overflow or add_overflow; } @@ -608,10 +607,10 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, rhs_ty: Type, comp: *C } const lhs_key = comp.interner.get(lhs.ref()); const rhs_key = comp.interner.get(rhs.ref()); - if (lhs_key == .global_var_offset and rhs_key == .global_var_offset) { - const lhs_reloc = lhs_key.global_var_offset; - const rhs_reloc = rhs_key.global_var_offset; - if (lhs_reloc.name != rhs_reloc.name) { + 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; } @@ -619,8 +618,8 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, rhs_ty: Type, comp: *C const rhs_size: i64 = @intCast(rhs_ty.elemType().sizeof(comp) orelse 1); res.* = try int(@divTrunc(difference, rhs_size), comp); return overflowed != 0; - } else if (lhs_key == .global_var_offset) { - const rel = lhs_key.global_var_offset; + } else if (lhs_key == .pointer) { + const rel = lhs_key.pointer; const elem_size = try int(ty.elemType().sizeof(comp) orelse 1, comp); var total_offset: Value = undefined; @@ -628,7 +627,7 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, rhs_ty: Type, comp: *C const old_offset = try int(rel.offset, comp); 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(.{ .name = rel.name, .offset = total_offset.toInt(i64, comp).? }, comp); + res.* = try reloc(.{ .decl = rel.decl, .offset = total_offset.toInt(i64, comp).? }, comp); return mul_overflow or add_overflow; } @@ -1001,13 +1000,13 @@ pub fn comparePointers(lhs: Value, op: std.math.CompareOperator, rhs: Value, com const lhs_key = comp.interner.get(lhs.ref()); const rhs_key = comp.interner.get(rhs.ref()); - if (lhs_key == .global_var_offset and rhs_key == .global_var_offset) { - const lhs_reloc = lhs_key.global_var_offset; - const rhs_reloc = rhs_key.global_var_offset; + 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.name != rhs_reloc.name) return false, - .neq => if (lhs_reloc.name != rhs_reloc.name) return true, - else => if (lhs_reloc.name != rhs_reloc.name) return null, + .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, } return std.math.compare(lhs_reloc.offset, op, rhs_reloc.offset); @@ -1051,7 +1050,7 @@ pub fn maxInt(ty: Type, comp: *Compilation) !Value { return twosCompIntLimit(.max, ty, comp); } -pub fn print(v: Value, ty: Type, comp: *const Compilation, mapper: StringInterner.TypeMapper, w: anytype) @TypeOf(w).Error!void { +pub fn print(v: Value, ty: Type, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { if (ty.is(.bool)) { return w.writeAll(if (v.isZero(comp)) "false" else "true"); } @@ -1071,16 +1070,7 @@ pub fn print(v: Value, ty: Type, comp: *const Compilation, mapper: StringInterne .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])) }), }, - .global_var_offset => |rel| { - const name = mapper.lookup(rel.name); - try w.print("&{s}", .{name}); - if (rel.offset == 0) return; - if (rel.offset == std.math.minInt(i64)) { - return w.print(" - {d}", .{std.math.maxInt(i64) + 1}); - } - const sign: u8 = if (rel.offset < 0) '-' else '+'; - return w.print(" {c} {d}", .{ sign, @abs(rel.offset) }); - }, + .pointer => {}, else => unreachable, // not a value } } diff --git a/src/backend/Interner.zig b/src/backend/Interner.zig index 531f8191..6c35e3b3 100644 --- a/src/backend/Interner.zig +++ b/src/backend/Interner.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const StringId = @import("StringInterner.zig").StringId; const Allocator = std.mem.Allocator; const assert = std.debug.assert; const BigIntConst = std.math.big.int.Const; @@ -66,7 +65,7 @@ pub const Key = union(enum) { float: Float, complex: Complex, bytes: []const u8, - global_var_offset: GlobalVarOffset, + pointer: Pointer, pub const Float = union(enum) { f16: f16, @@ -82,8 +81,9 @@ pub const Key = union(enum) { cf80: [2]f80, cf128: [2]f128, }; - pub const GlobalVarOffset = struct { - name: StringId, + pub const Pointer = struct { + /// NodeIndex of decl whose address we are offsetting from + decl: u32, /// Offset in bytes offset: i64, }; @@ -310,8 +310,8 @@ pub const Tag = enum(u8) { bytes, /// `data` is `Record` record_ty, - /// `data` is GlobalVarOffset - global_var_offset, + /// `data` is Pointer + pointer, pub const Array = struct { len0: u32, @@ -547,23 +547,23 @@ pub const Tag = enum(u8) { // [elements_len]Ref }; - pub const GlobalVarOffset = struct { - name: u32, + pub const Pointer = struct { + decl: u32, piece0: u32, piece1: u32, - pub fn get(self: GlobalVarOffset) Key.GlobalVarOffset { + pub fn get(self: Pointer) Key.Pointer { const offset = @as(u64, self.piece0) | (@as(u64, self.piece1) << 32); return .{ - .name = @enumFromInt(self.name), + .decl = self.decl, .offset = @bitCast(offset), }; } - fn pack(val: Key.GlobalVarOffset) GlobalVarOffset { + fn pack(val: Key.Pointer) Pointer { const bits: u64 = @bitCast(val.offset); return .{ - .name = @intFromEnum(val.name), + .decl = val.decl, .piece0 = @truncate(bits), .piece1 = @truncate(bits >> 32), }; @@ -748,10 +748,10 @@ pub fn put(i: *Interner, gpa: Allocator, key: Key) !Ref { }); i.extra.appendSliceAssumeCapacity(@ptrCast(elems)); }, - .global_var_offset => |reloc| { + .pointer => |reloc| { i.items.appendAssumeCapacity(.{ - .tag = .global_var_offset, - .data = try i.addExtra(gpa, Tag.GlobalVarOffset.pack(reloc)), + .tag = .pointer, + .data = try i.addExtra(gpa, Tag.Pointer.pack(reloc)), }); }, .ptr_ty, @@ -886,9 +886,9 @@ pub fn get(i: *const Interner, ref: Ref) Key { .record_ty = @ptrCast(i.extra.items[extra.end..][0..extra.data.elements_len]), }; }, - .global_var_offset => { - const components = i.extraData(Tag.GlobalVarOffset, data); - return .{ .global_var_offset = components.get() }; + .pointer => { + const components = i.extraData(Tag.Pointer, data); + return .{ .pointer = components.get() }; }, }; } From 8c4a860635b206bdc104504dbdb66b486de3ce67 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 8 Oct 2024 14:09:25 -0700 Subject: [PATCH 5/6] Interner: use interned values to store pointer offsets instead of i64 --- src/aro/Parser.zig | 26 ++++++++++++------- src/aro/Value.zig | 26 ++++++++++++------- src/backend/Interner.zig | 56 ++++++++++++++++------------------------ 3 files changed, 55 insertions(+), 53 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index fa56c7fc..c0d7706a 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -7060,7 +7060,7 @@ 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: i64) !Pointer { +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); @@ -7078,23 +7078,28 @@ fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: i64) !Pointer .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 }; + 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; - const as_int = index_val.toInt(i64, p.comp).?; - const size = ty.sizeof(p.comp).?; - const this_offset = @as(i64, @intCast(size)) * as_int; - return p.computeOffsetExtra(bin_data.lhs, this_offset + offset_so_far); + 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)); - return p.computeOffsetExtra(member.lhs, offset_so_far + field_offset); + // 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, } @@ -7102,7 +7107,8 @@ fn computeOffsetExtra(p: *Parser, node: NodeIndex, offset_so_far: i64) !Pointer /// Compute the offset (in bytes) of an expression from a base pointer. fn computeOffset(p: *Parser, node: NodeIndex) !Pointer { - return p.computeOffsetExtra(node, 0); + var val: Value = .zero; + return p.computeOffsetExtra(node, &val); } /// unExpr @@ -7165,7 +7171,7 @@ fn unExpr(p: *Parser) Error!Result { 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 = 0 }; + break :blk .{ .decl = @intFromEnum(NodeIndex.none), .offset = .zero }; }, else => |er| return er, }; diff --git a/src/aro/Value.zig b/src/aro/Value.zig index d6b4d736..1148667f 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -42,6 +42,10 @@ pub fn ref(v: Value) Interner.Ref { 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; @@ -547,10 +551,10 @@ pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b 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 = try int(rel.offset, 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.toInt(i64, comp).? }, comp); + res.* = try reloc(.{ .decl = rel.decl, .offset = total_offset.ref() }, comp); return mul_overflow or add_overflow; } @@ -614,20 +618,22 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, rhs_ty: Type, comp: *C res.* = .{}; return false; } - const difference, const overflowed = @subWithOverflow(lhs_reloc.offset, rhs_reloc.offset); - const rhs_size: i64 = @intCast(rhs_ty.elemType().sizeof(comp) orelse 1); - res.* = try int(@divTrunc(difference, rhs_size), comp); - return overflowed != 0; + 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(rhs_ty.elemType().sizeof(comp) orelse 1, comp); + _ = try res.div(res.*, rhs_size, comp.types.ptrdiff, comp); + return overflowed; } else if (lhs_key == .pointer) { const rel = lhs_key.pointer; 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, rhs, comp.types.ptrdiff, comp); - const old_offset = try int(rel.offset, 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.toInt(i64, comp).? }, comp); + res.* = try reloc(.{ .decl = rel.decl, .offset = total_offset.ref() }, comp); return mul_overflow or add_overflow; } @@ -1009,7 +1015,9 @@ pub fn comparePointers(lhs: Value, op: std.math.CompareOperator, rhs: Value, com else => if (lhs_reloc.decl != rhs_reloc.decl) return null, } - return std.math.compare(lhs_reloc.offset, op, rhs_reloc.offset); + const lhs_offset = fromRef(lhs_reloc.offset); + const rhs_offset = fromRef(rhs_reloc.offset); + return lhs_offset.compare(op, rhs_offset, comp); } return null; } diff --git a/src/backend/Interner.zig b/src/backend/Interner.zig index 6c35e3b3..3315a08d 100644 --- a/src/backend/Interner.zig +++ b/src/backend/Interner.zig @@ -85,7 +85,7 @@ pub const Key = union(enum) { /// NodeIndex of decl whose address we are offsetting from decl: u32, /// Offset in bytes - offset: i64, + offset: Ref, }; pub fn hash(key: Key) u32 { @@ -331,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, @@ -546,29 +551,6 @@ pub const Tag = enum(u8) { // trailing // [elements_len]Ref }; - - pub const Pointer = struct { - decl: u32, - piece0: u32, - piece1: u32, - - pub fn get(self: Pointer) Key.Pointer { - const offset = @as(u64, self.piece0) | (@as(u64, self.piece1) << 32); - return .{ - .decl = self.decl, - .offset = @bitCast(offset), - }; - } - - fn pack(val: Key.Pointer) Pointer { - const bits: u64 = @bitCast(val.offset); - return .{ - .decl = val.decl, - .piece0 = @truncate(bits), - .piece1 = @truncate(bits >> 32), - }; - } - }; }; pub const PackedU64 = packed struct(u64) { @@ -638,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); @@ -748,12 +739,6 @@ pub fn put(i: *Interner, gpa: Allocator, key: Key) !Ref { }); i.extra.appendSliceAssumeCapacity(@ptrCast(elems)); }, - .pointer => |reloc| { - i.items.appendAssumeCapacity(.{ - .tag = .pointer, - .data = try i.addExtra(gpa, Tag.Pointer.pack(reloc)), - }); - }, .ptr_ty, .noreturn_ty, .void_ty, @@ -830,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 => { @@ -886,10 +878,6 @@ pub fn get(i: *const Interner, ref: Ref) Key { .record_ty = @ptrCast(i.extra.items[extra.end..][0..extra.data.elements_len]), }; }, - .pointer => { - const components = i.extraData(Tag.Pointer, data); - return .{ .pointer = components.get() }; - }, }; } From 0ea82fd5623a63a7a1be1c8868c17d2137756e89 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 8 Oct 2024 15:53:21 -0700 Subject: [PATCH 6/6] Parser: handle pointer subtraction of zero-size elements --- src/aro/Diagnostics/messages.def | 6 ++++++ src/aro/Parser.zig | 10 ++++++++-- src/aro/Value.zig | 12 ++++++------ test/cases/relocations.c | 8 ++++++++ 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/aro/Diagnostics/messages.def b/src/aro/Diagnostics/messages.def index 883ae198..e4c2ded0 100644 --- a/src/aro/Diagnostics/messages.def +++ b/src/aro/Diagnostics/messages.def @@ -2545,3 +2545,9 @@ auto_type_self_initialized 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 c0d7706a..055311d2 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -5526,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; } @@ -6703,8 +6704,13 @@ fn addExpr(p: *Parser) Error!Result { } } } else { - if (try lhs.val.sub(lhs.val, rhs.val, lhs.ty, rhs.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()) { diff --git a/src/aro/Value.zig b/src/aro/Value.zig index 1148667f..1cf44cde 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -583,8 +583,8 @@ pub fn decrement(res: *Value, val: Value, ty: Type, comp: *Compilation) !bool { return res.sub(val, one, ty, undefined, comp); } -/// rhs_ty 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, rhs_ty: Type, comp: *Compilation) !bool { +/// 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()) { @@ -621,15 +621,15 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, rhs_ty: Type, comp: *C 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(rhs_ty.elemType().sizeof(comp) orelse 1, 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 elem_size = try int(ty.elemType().sizeof(comp) orelse 1, comp); + const lhs_size = try int(elem_size, comp); var total_offset: Value = undefined; - const mul_overflow = try total_offset.mul(elem_size, rhs, comp.types.ptrdiff, comp); + 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); @@ -790,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, Type.int, comp); + _ = try tmp.sub(lhs, tmp, ty, undefined, comp); return tmp; } } diff --git a/test/cases/relocations.c b/test/cases/relocations.c index 0e0a25d6..6c049d22 100644 --- a/test/cases/relocations.c +++ b/test/cases/relocations.c @@ -44,7 +44,15 @@ _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" \