aboutsummaryrefslogtreecommitdiff
path: root/src/value.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2021-10-04 23:30:04 -0700
committerAndrew Kelley <andrew@ziglang.org>2021-10-04 23:30:04 -0700
commit78902db68bbd400f6d84b65280c31d417105f2a8 (patch)
tree593851ca76e95033abe7afee171cccc659b76f53 /src/value.zig
parent598db831f3ea1267d469162db1a54c2d62ff3e87 (diff)
downloadzig-78902db68bbd400f6d84b65280c31d417105f2a8.tar.gz
zig-78902db68bbd400f6d84b65280c31d417105f2a8.zip
stage2: fix comptime `@bitCast`
Before, Sema for comptime `@bitCast` would return the same Value but change the Type. This gave invalid results because, for example, an integer Value when the Type is a float would be interpreted numerically, but `@bitCast` needs it to reinterpret how they would be stored in memory. This requires a mechanism to serialize a Value to a byte buffer and deserialize a Value from a byte buffer. Not done yet, but needs to happen: comptime dereferencing a pointer to a Decl needs to perform a comptime bitcast on the loaded value. Currently the value is silently wrong in the same way that `@bitCast` was silently wrong before this commit. The logic in Value for handling readFromMemory for large integers is only correct for small integers. It needs to be fleshed out for proper big integers. As part of this change: * std.math.big.Int: initial implementations of readTwosComplement and writeTwosComplement. They only support bit_count <= 128 so far and panic otherwise. * compiler-rt: move the compareXf2 exports over to the stage2 section. Even with the improvements in this commit, I'm still seeing test failures in the widening behavior tests; more investigation is needed.
Diffstat (limited to 'src/value.zig')
-rw-r--r--src/value.zig122
1 files changed, 112 insertions, 10 deletions
diff --git a/src/value.zig b/src/value.zig
index f5d7b8b05e..90d98bd539 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -938,30 +938,132 @@ pub const Value = extern union {
pub fn toBool(self: Value) bool {
return switch (self.tag()) {
- .bool_true => true,
+ .bool_true, .one => true,
.bool_false, .zero => false,
else => unreachable,
};
}
+ pub fn bitCast(
+ val: Value,
+ old_ty: Type,
+ new_ty: Type,
+ target: Target,
+ gpa: *Allocator,
+ arena: *Allocator,
+ ) !Value {
+ // For types with well-defined memory layouts, we serialize them a byte buffer,
+ // then deserialize to the new type.
+ const buffer = try gpa.alloc(u8, old_ty.abiSize(target));
+ defer gpa.free(buffer);
+ val.writeToMemory(old_ty, target, buffer);
+ return Value.readFromMemory(new_ty, target, buffer, arena);
+ }
+
+ pub fn writeToMemory(val: Value, ty: Type, target: Target, buffer: []u8) void {
+ switch (ty.zigTypeTag()) {
+ .Int => {
+ var bigint_buffer: BigIntSpace = undefined;
+ const bigint = val.toBigInt(&bigint_buffer);
+ const bits = ty.intInfo(target).bits;
+ bigint.writeTwosComplement(buffer, bits, target.cpu.arch.endian());
+ },
+ .Float => switch (ty.floatBits(target)) {
+ 16 => return floatWriteToMemory(f16, val.toFloat(f16), target, buffer),
+ 32 => return floatWriteToMemory(f32, val.toFloat(f32), target, buffer),
+ 64 => return floatWriteToMemory(f64, val.toFloat(f64), target, buffer),
+ 128 => return floatWriteToMemory(f128, val.toFloat(f128), target, buffer),
+ else => unreachable,
+ },
+ else => @panic("TODO implement writeToMemory for more types"),
+ }
+ }
+
+ pub fn readFromMemory(ty: Type, target: Target, buffer: []const u8, arena: *Allocator) !Value {
+ switch (ty.zigTypeTag()) {
+ .Int => {
+ const int_info = ty.intInfo(target);
+ const endian = target.cpu.arch.endian();
+ // TODO use a correct amount of limbs
+ const limbs_buffer = try arena.alloc(std.math.big.Limb, 2);
+ var bigint = BigIntMutable.init(limbs_buffer, 0);
+ bigint.readTwosComplement(buffer, int_info.bits, endian, int_info.signedness);
+ // TODO if it fits in 64 bits then use one of those tags
+
+ const result_limbs = bigint.limbs[0..bigint.len];
+ if (bigint.positive) {
+ return Value.Tag.int_big_positive.create(arena, result_limbs);
+ } else {
+ return Value.Tag.int_big_negative.create(arena, result_limbs);
+ }
+ },
+ .Float => switch (ty.floatBits(target)) {
+ 16 => return Value.Tag.float_16.create(arena, floatReadFromMemory(f16, target, buffer)),
+ 32 => return Value.Tag.float_32.create(arena, floatReadFromMemory(f32, target, buffer)),
+ 64 => return Value.Tag.float_64.create(arena, floatReadFromMemory(f64, target, buffer)),
+ 128 => return Value.Tag.float_128.create(arena, floatReadFromMemory(f128, target, buffer)),
+ else => unreachable,
+ },
+ else => @panic("TODO implement readFromMemory for more types"),
+ }
+ }
+
+ fn floatWriteToMemory(comptime F: type, f: F, target: Target, buffer: []u8) void {
+ const Int = @Type(.{ .Int = .{
+ .signedness = .unsigned,
+ .bits = @typeInfo(F).Float.bits,
+ } });
+ const int = @bitCast(Int, f);
+ std.mem.writeInt(Int, buffer[0..@sizeOf(Int)], int, target.cpu.arch.endian());
+ }
+
+ fn floatReadFromMemory(comptime F: type, target: Target, buffer: []const u8) F {
+ const Int = @Type(.{ .Int = .{
+ .signedness = .unsigned,
+ .bits = @typeInfo(F).Float.bits,
+ } });
+ const int = std.mem.readInt(Int, buffer[0..@sizeOf(Int)], target.cpu.arch.endian());
+ return @bitCast(F, int);
+ }
+
/// Asserts that the value is a float or an integer.
- pub fn toFloat(self: Value, comptime T: type) T {
- return switch (self.tag()) {
- .float_16 => @floatCast(T, self.castTag(.float_16).?.data),
- .float_32 => @floatCast(T, self.castTag(.float_32).?.data),
- .float_64 => @floatCast(T, self.castTag(.float_64).?.data),
- .float_128 => @floatCast(T, self.castTag(.float_128).?.data),
+ pub fn toFloat(val: Value, comptime T: type) T {
+ return switch (val.tag()) {
+ .float_16 => @floatCast(T, val.castTag(.float_16).?.data),
+ .float_32 => @floatCast(T, val.castTag(.float_32).?.data),
+ .float_64 => @floatCast(T, val.castTag(.float_64).?.data),
+ .float_128 => @floatCast(T, val.castTag(.float_128).?.data),
.zero => 0,
.one => 1,
- .int_u64 => @intToFloat(T, self.castTag(.int_u64).?.data),
- .int_i64 => @intToFloat(T, self.castTag(.int_i64).?.data),
+ .int_u64 => @intToFloat(T, val.castTag(.int_u64).?.data),
+ .int_i64 => @intToFloat(T, val.castTag(.int_i64).?.data),
- .int_big_positive, .int_big_negative => @panic("big int to f128"),
+ .int_big_positive => @floatCast(T, bigIntToFloat(val.castTag(.int_big_positive).?.data, true)),
+ .int_big_negative => @floatCast(T, bigIntToFloat(val.castTag(.int_big_negative).?.data, false)),
else => unreachable,
};
}
+ /// TODO move this to std lib big int code
+ fn bigIntToFloat(limbs: []const std.math.big.Limb, positive: bool) f128 {
+ if (limbs.len == 0) return 0;
+
+ const base = std.math.maxInt(std.math.big.Limb) + 1;
+ var result: f128 = 0;
+ var i: usize = limbs.len;
+ while (i != 0) {
+ i -= 1;
+ const limb: f128 = @intToFloat(f128, limbs[i]);
+ result = @mulAdd(f128, base, limb, result);
+ }
+ if (positive) {
+ return result;
+ } else {
+ return -result;
+ }
+ }
+
pub fn clz(val: Value, ty: Type, target: Target) u64 {
const ty_bits = ty.intInfo(target).bits;
switch (val.tag()) {