diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-10-04 23:30:04 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-10-04 23:30:04 -0700 |
| commit | 78902db68bbd400f6d84b65280c31d417105f2a8 (patch) | |
| tree | 593851ca76e95033abe7afee171cccc659b76f53 /src/value.zig | |
| parent | 598db831f3ea1267d469162db1a54c2d62ff3e87 (diff) | |
| download | zig-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.zig | 122 |
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()) { |
