mirror of
https://github.com/ziglang/zig.git
synced 2025-12-20 21:23:06 +00:00
602 lines
21 KiB
Zig
Vendored
602 lines
21 KiB
Zig
Vendored
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const Compilation = @import("Compilation.zig");
|
|
const Type = @import("Type.zig");
|
|
|
|
const Value = @This();
|
|
|
|
pub const ByteRange = struct {
|
|
start: u32,
|
|
end: u32,
|
|
|
|
pub fn len(self: ByteRange) u32 {
|
|
return self.end - self.start;
|
|
}
|
|
|
|
pub fn trim(self: ByteRange, amount: u32) ByteRange {
|
|
std.debug.assert(self.start <= self.end - amount);
|
|
return .{ .start = self.start, .end = self.end - amount };
|
|
}
|
|
|
|
pub fn slice(self: ByteRange, all_bytes: []const u8) []const u8 {
|
|
return all_bytes[self.start..self.end];
|
|
}
|
|
};
|
|
|
|
tag: Tag = .unavailable,
|
|
data: union {
|
|
none: void,
|
|
int: u64,
|
|
float: f64,
|
|
bytes: ByteRange,
|
|
} = .{ .none = {} },
|
|
|
|
const Tag = enum {
|
|
unavailable,
|
|
nullptr_t,
|
|
/// int is used to store integer, boolean and pointer values
|
|
int,
|
|
float,
|
|
bytes,
|
|
};
|
|
|
|
pub fn zero(v: Value) Value {
|
|
return switch (v.tag) {
|
|
.int => int(0),
|
|
.float => float(0),
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
pub fn one(v: Value) Value {
|
|
return switch (v.tag) {
|
|
.int => int(1),
|
|
.float => float(1),
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
pub fn int(v: anytype) Value {
|
|
if (@TypeOf(v) == comptime_int or @typeInfo(@TypeOf(v)).Int.signedness == .unsigned)
|
|
return .{ .tag = .int, .data = .{ .int = v } }
|
|
else
|
|
return .{ .tag = .int, .data = .{ .int = @bitCast(@as(i64, v)) } };
|
|
}
|
|
|
|
pub fn float(v: anytype) Value {
|
|
return .{ .tag = .float, .data = .{ .float = v } };
|
|
}
|
|
|
|
pub fn bytes(start: u32, end: u32) Value {
|
|
return .{ .tag = .bytes, .data = .{ .bytes = .{ .start = start, .end = end } } };
|
|
}
|
|
|
|
pub fn signExtend(v: Value, old_ty: Type, comp: *Compilation) i64 {
|
|
const size = old_ty.sizeof(comp).?;
|
|
return switch (size) {
|
|
1 => v.getInt(i8),
|
|
2 => v.getInt(i16),
|
|
4 => v.getInt(i32),
|
|
8 => v.getInt(i64),
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
/// Number of bits needed to hold `v` which is of type `ty`.
|
|
/// Asserts that `v` is not negative
|
|
pub fn minUnsignedBits(v: Value, ty: Type, comp: *const Compilation) usize {
|
|
assert(v.compare(.gte, Value.int(0), ty, comp));
|
|
return switch (ty.sizeof(comp).?) {
|
|
1 => 8 - @clz(v.getInt(u8)),
|
|
2 => 16 - @clz(v.getInt(u16)),
|
|
4 => 32 - @clz(v.getInt(u32)),
|
|
8 => 64 - @clz(v.getInt(u64)),
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
test "minUnsignedBits" {
|
|
const Test = struct {
|
|
fn checkIntBits(comp: *const Compilation, specifier: Type.Specifier, v: u64, expected: usize) !void {
|
|
const val = Value.int(v);
|
|
try std.testing.expectEqual(expected, val.minUnsignedBits(.{ .specifier = specifier }, comp));
|
|
}
|
|
};
|
|
|
|
var comp = Compilation.init(std.testing.allocator);
|
|
defer comp.deinit();
|
|
comp.target = (try std.zig.CrossTarget.parse(.{ .arch_os_abi = "x86_64-linux-gnu" })).toTarget();
|
|
|
|
try Test.checkIntBits(&comp, .int, 0, 0);
|
|
try Test.checkIntBits(&comp, .int, 1, 1);
|
|
try Test.checkIntBits(&comp, .int, 2, 2);
|
|
try Test.checkIntBits(&comp, .int, std.math.maxInt(i8), 7);
|
|
try Test.checkIntBits(&comp, .int, std.math.maxInt(u8), 8);
|
|
try Test.checkIntBits(&comp, .int, std.math.maxInt(i16), 15);
|
|
try Test.checkIntBits(&comp, .int, std.math.maxInt(u16), 16);
|
|
try Test.checkIntBits(&comp, .int, std.math.maxInt(i32), 31);
|
|
try Test.checkIntBits(&comp, .uint, std.math.maxInt(u32), 32);
|
|
try Test.checkIntBits(&comp, .long, std.math.maxInt(i64), 63);
|
|
try Test.checkIntBits(&comp, .ulong, std.math.maxInt(u64), 64);
|
|
try Test.checkIntBits(&comp, .long_long, std.math.maxInt(i64), 63);
|
|
try Test.checkIntBits(&comp, .ulong_long, std.math.maxInt(u64), 64);
|
|
}
|
|
|
|
/// Minimum number of bits needed to represent `v` in 2's complement notation
|
|
/// Asserts that `v` is negative.
|
|
pub fn minSignedBits(v: Value, ty: Type, comp: *const Compilation) usize {
|
|
assert(v.compare(.lt, Value.int(0), ty, comp));
|
|
return switch (ty.sizeof(comp).?) {
|
|
1 => 8 - @clz(~v.getInt(u8)) + 1,
|
|
2 => 16 - @clz(~v.getInt(u16)) + 1,
|
|
4 => 32 - @clz(~v.getInt(u32)) + 1,
|
|
8 => 64 - @clz(~v.getInt(u64)) + 1,
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
test "minSignedBits" {
|
|
const Test = struct {
|
|
fn checkIntBits(comp: *const Compilation, specifier: Type.Specifier, v: i64, expected: usize) !void {
|
|
const val = Value.int(v);
|
|
try std.testing.expectEqual(expected, val.minSignedBits(.{ .specifier = specifier }, comp));
|
|
}
|
|
};
|
|
|
|
var comp = Compilation.init(std.testing.allocator);
|
|
defer comp.deinit();
|
|
comp.target = (try std.zig.CrossTarget.parse(.{ .arch_os_abi = "x86_64-linux-gnu" })).toTarget();
|
|
|
|
for ([_]Type.Specifier{ .int, .long, .long_long }) |specifier| {
|
|
try Test.checkIntBits(&comp, specifier, -1, 1);
|
|
try Test.checkIntBits(&comp, specifier, -2, 2);
|
|
try Test.checkIntBits(&comp, specifier, -10, 5);
|
|
try Test.checkIntBits(&comp, specifier, -101, 8);
|
|
|
|
try Test.checkIntBits(&comp, specifier, std.math.minInt(i8), 8);
|
|
try Test.checkIntBits(&comp, specifier, std.math.minInt(i16), 16);
|
|
try Test.checkIntBits(&comp, specifier, std.math.minInt(i32), 32);
|
|
}
|
|
|
|
try Test.checkIntBits(&comp, .long, std.math.minInt(i64), 64);
|
|
try Test.checkIntBits(&comp, .long_long, std.math.minInt(i64), 64);
|
|
}
|
|
|
|
pub const FloatToIntChangeKind = enum {
|
|
/// value did not change
|
|
none,
|
|
/// floating point number too small or large for destination integer type
|
|
out_of_range,
|
|
/// tried to convert a NaN or Infinity
|
|
overflow,
|
|
/// fractional value was converted to zero
|
|
nonzero_to_zero,
|
|
/// fractional part truncated
|
|
value_changed,
|
|
};
|
|
|
|
fn floatToIntExtra(comptime FloatTy: type, int_ty_signedness: std.builtin.Signedness, int_ty_size: u16, v: *Value) FloatToIntChangeKind {
|
|
const float_val = v.getFloat(FloatTy);
|
|
const was_zero = float_val == 0;
|
|
const had_fraction = std.math.modf(float_val).fpart != 0;
|
|
|
|
switch (int_ty_signedness) {
|
|
inline else => |signedness| switch (int_ty_size) {
|
|
inline 1, 2, 4, 8 => |bytecount| {
|
|
const IntTy = std.meta.Int(signedness, bytecount * 8);
|
|
|
|
const intVal = std.math.lossyCast(IntTy, float_val);
|
|
v.* = int(intVal);
|
|
if (!was_zero and v.isZero()) return .nonzero_to_zero;
|
|
if (float_val <= std.math.minInt(IntTy) or float_val >= std.math.maxInt(IntTy)) return .out_of_range;
|
|
if (had_fraction) return .value_changed;
|
|
return .none;
|
|
},
|
|
else => unreachable,
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Converts the stored value from a float to an integer.
|
|
/// `.unavailable` value remains unchanged.
|
|
pub fn floatToInt(v: *Value, old_ty: Type, new_ty: Type, comp: *Compilation) FloatToIntChangeKind {
|
|
assert(old_ty.isFloat());
|
|
if (v.tag == .unavailable) return .none;
|
|
if (new_ty.is(.bool)) {
|
|
const was_zero = v.isZero();
|
|
const was_one = v.getFloat(f64) == 1.0;
|
|
v.toBool();
|
|
if (was_zero or was_one) return .none;
|
|
return .value_changed;
|
|
} else if (new_ty.isUnsignedInt(comp) and v.data.float < 0) {
|
|
v.* = int(0);
|
|
return .out_of_range;
|
|
} else if (!std.math.isFinite(v.data.float)) {
|
|
v.tag = .unavailable;
|
|
return .overflow;
|
|
}
|
|
const old_size = old_ty.sizeof(comp).?;
|
|
const new_size: u16 = @intCast(new_ty.sizeof(comp).?);
|
|
if (new_ty.isUnsignedInt(comp)) switch (old_size) {
|
|
1 => unreachable, // promoted to int
|
|
2 => unreachable, // promoted to int
|
|
4 => return floatToIntExtra(f32, .unsigned, new_size, v),
|
|
8 => return floatToIntExtra(f64, .unsigned, new_size, v),
|
|
else => unreachable,
|
|
} else switch (old_size) {
|
|
1 => unreachable, // promoted to int
|
|
2 => unreachable, // promoted to int
|
|
4 => return floatToIntExtra(f32, .signed, new_size, v),
|
|
8 => return floatToIntExtra(f64, .signed, new_size, v),
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
/// Converts the stored value from an integer to a float.
|
|
/// `.unavailable` value remains unchanged.
|
|
pub fn intToFloat(v: *Value, old_ty: Type, new_ty: Type, comp: *Compilation) void {
|
|
assert(old_ty.isInt());
|
|
if (v.tag == .unavailable) return;
|
|
if (!new_ty.isReal() or new_ty.sizeof(comp).? > 8) {
|
|
v.tag = .unavailable;
|
|
} else if (old_ty.isUnsignedInt(comp)) {
|
|
v.* = float(@as(f64, @floatFromInt(v.data.int)));
|
|
} else {
|
|
v.* = float(@as(f64, @floatFromInt(@as(i64, @bitCast(v.data.int)))));
|
|
}
|
|
}
|
|
|
|
/// Truncates or extends bits based on type.
|
|
/// old_ty is only used for size.
|
|
pub fn intCast(v: *Value, old_ty: Type, new_ty: Type, comp: *Compilation) void {
|
|
// assert(old_ty.isInt() and new_ty.isInt());
|
|
if (v.tag == .unavailable) return;
|
|
if (new_ty.is(.bool)) return v.toBool();
|
|
if (!old_ty.isUnsignedInt(comp)) {
|
|
const size = new_ty.sizeof(comp).?;
|
|
switch (size) {
|
|
1 => v.* = int(@as(u8, @truncate(@as(u64, @bitCast(v.signExtend(old_ty, comp)))))),
|
|
2 => v.* = int(@as(u16, @truncate(@as(u64, @bitCast(v.signExtend(old_ty, comp)))))),
|
|
4 => v.* = int(@as(u32, @truncate(@as(u64, @bitCast(v.signExtend(old_ty, comp)))))),
|
|
8 => return,
|
|
else => unreachable,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Converts the stored value from an integer to a float.
|
|
/// `.unavailable` value remains unchanged.
|
|
pub fn floatCast(v: *Value, old_ty: Type, new_ty: Type, comp: *Compilation) void {
|
|
assert(old_ty.isFloat() and new_ty.isFloat());
|
|
if (v.tag == .unavailable) return;
|
|
const size = new_ty.sizeof(comp).?;
|
|
if (!new_ty.isReal() or size > 8) {
|
|
v.tag = .unavailable;
|
|
} else if (size == 32) {
|
|
v.* = float(@as(f32, @floatCast(v.data.float)));
|
|
}
|
|
}
|
|
|
|
/// Truncates data.int to one bit
|
|
pub fn toBool(v: *Value) void {
|
|
if (v.tag == .unavailable) return;
|
|
const res = v.getBool();
|
|
v.* = int(@intFromBool(res));
|
|
}
|
|
|
|
pub fn isZero(v: Value) bool {
|
|
return switch (v.tag) {
|
|
.unavailable => false,
|
|
.nullptr_t => false,
|
|
.int => v.data.int == 0,
|
|
.float => v.data.float == 0,
|
|
.bytes => false,
|
|
};
|
|
}
|
|
|
|
pub fn getBool(v: Value) bool {
|
|
return switch (v.tag) {
|
|
.unavailable => unreachable,
|
|
.nullptr_t => false,
|
|
.int => v.data.int != 0,
|
|
.float => v.data.float != 0,
|
|
.bytes => true,
|
|
};
|
|
}
|
|
|
|
pub fn getInt(v: Value, comptime T: type) T {
|
|
if (T == u64) return v.data.int;
|
|
return if (@typeInfo(T).Int.signedness == .unsigned)
|
|
@truncate(v.data.int)
|
|
else
|
|
@truncate(@as(i64, @bitCast(v.data.int)));
|
|
}
|
|
|
|
pub fn getFloat(v: Value, comptime T: type) T {
|
|
if (T == f64) return v.data.float;
|
|
return @floatCast(v.data.float);
|
|
}
|
|
|
|
const bin_overflow = struct {
|
|
inline fn addInt(comptime T: type, out: *Value, a: Value, b: Value) bool {
|
|
const a_val = a.getInt(T);
|
|
const b_val = b.getInt(T);
|
|
const sum, const overflowed = @addWithOverflow(a_val, b_val);
|
|
out.* = int(sum);
|
|
return overflowed != 0;
|
|
}
|
|
inline fn addFloat(comptime T: type, aa: Value, bb: Value) Value {
|
|
const a_val = aa.getFloat(T);
|
|
const b_val = bb.getFloat(T);
|
|
return float(a_val + b_val);
|
|
}
|
|
|
|
inline fn subInt(comptime T: type, out: *Value, a: Value, b: Value) bool {
|
|
const a_val = a.getInt(T);
|
|
const b_val = b.getInt(T);
|
|
const difference, const overflowed = @subWithOverflow(a_val, b_val);
|
|
out.* = int(difference);
|
|
return overflowed != 0;
|
|
}
|
|
inline fn subFloat(comptime T: type, aa: Value, bb: Value) Value {
|
|
const a_val = aa.getFloat(T);
|
|
const b_val = bb.getFloat(T);
|
|
return float(a_val - b_val);
|
|
}
|
|
|
|
inline fn mulInt(comptime T: type, out: *Value, a: Value, b: Value) bool {
|
|
const a_val = a.getInt(T);
|
|
const b_val = b.getInt(T);
|
|
const product, const overflowed = @mulWithOverflow(a_val, b_val);
|
|
out.* = int(product);
|
|
return overflowed != 0;
|
|
}
|
|
inline fn mulFloat(comptime T: type, aa: Value, bb: Value) Value {
|
|
const a_val = aa.getFloat(T);
|
|
const b_val = bb.getFloat(T);
|
|
return float(a_val * b_val);
|
|
}
|
|
|
|
const FT = fn (*Value, Value, Value, Type, *Compilation) bool;
|
|
fn getOp(comptime intFunc: anytype, comptime floatFunc: anytype) FT {
|
|
return struct {
|
|
fn op(res: *Value, a: Value, b: Value, ty: Type, comp: *Compilation) bool {
|
|
const size = ty.sizeof(comp).?;
|
|
if (@TypeOf(floatFunc) != @TypeOf(null) and ty.isFloat()) {
|
|
res.* = switch (size) {
|
|
4 => floatFunc(f32, a, b),
|
|
8 => floatFunc(f64, a, b),
|
|
else => unreachable,
|
|
};
|
|
return false;
|
|
}
|
|
|
|
if (ty.isUnsignedInt(comp)) switch (size) {
|
|
1 => return intFunc(u8, res, a, b),
|
|
2 => return intFunc(u16, res, a, b),
|
|
4 => return intFunc(u32, res, a, b),
|
|
8 => return intFunc(u64, res, a, b),
|
|
else => unreachable,
|
|
} else switch (size) {
|
|
1 => return intFunc(u8, res, a, b),
|
|
2 => return intFunc(u16, res, a, b),
|
|
4 => return intFunc(i32, res, a, b),
|
|
8 => return intFunc(i64, res, a, b),
|
|
else => unreachable,
|
|
}
|
|
}
|
|
}.op;
|
|
}
|
|
};
|
|
|
|
pub const add = bin_overflow.getOp(bin_overflow.addInt, bin_overflow.addFloat);
|
|
pub const sub = bin_overflow.getOp(bin_overflow.subInt, bin_overflow.subFloat);
|
|
pub const mul = bin_overflow.getOp(bin_overflow.mulInt, bin_overflow.mulFloat);
|
|
|
|
const bin_ops = struct {
|
|
inline fn divInt(comptime T: type, aa: Value, bb: Value) Value {
|
|
const a_val = aa.getInt(T);
|
|
const b_val = bb.getInt(T);
|
|
return int(@divTrunc(a_val, b_val));
|
|
}
|
|
inline fn divFloat(comptime T: type, aa: Value, bb: Value) Value {
|
|
const a_val = aa.getFloat(T);
|
|
const b_val = bb.getFloat(T);
|
|
return float(a_val / b_val);
|
|
}
|
|
|
|
inline fn remInt(comptime T: type, a: Value, b: Value) Value {
|
|
const a_val = a.getInt(T);
|
|
const b_val = b.getInt(T);
|
|
|
|
if (@typeInfo(T).Int.signedness == .signed) {
|
|
if (a_val == std.math.minInt(T) and b_val == -1) {
|
|
return Value{ .tag = .unavailable, .data = .{ .none = {} } };
|
|
} else {
|
|
if (b_val > 0) return int(@rem(a_val, b_val));
|
|
return int(a_val - @divTrunc(a_val, b_val) * b_val);
|
|
}
|
|
} else {
|
|
return int(a_val % b_val);
|
|
}
|
|
}
|
|
|
|
inline fn orInt(comptime T: type, a: Value, b: Value) Value {
|
|
const a_val = a.getInt(T);
|
|
const b_val = b.getInt(T);
|
|
return int(a_val | b_val);
|
|
}
|
|
inline fn xorInt(comptime T: type, a: Value, b: Value) Value {
|
|
const a_val = a.getInt(T);
|
|
const b_val = b.getInt(T);
|
|
return int(a_val ^ b_val);
|
|
}
|
|
inline fn andInt(comptime T: type, a: Value, b: Value) Value {
|
|
const a_val = a.getInt(T);
|
|
const b_val = b.getInt(T);
|
|
return int(a_val & b_val);
|
|
}
|
|
|
|
inline fn shl(comptime T: type, a: Value, b: Value) Value {
|
|
const ShiftT = std.math.Log2Int(T);
|
|
const info = @typeInfo(T).Int;
|
|
const UT = std.meta.Int(.unsigned, info.bits);
|
|
const b_val = b.getInt(T);
|
|
|
|
if (b_val > std.math.maxInt(ShiftT)) {
|
|
return if (info.signedness == .unsigned)
|
|
int(@as(UT, std.math.maxInt(UT)))
|
|
else
|
|
int(@as(T, std.math.minInt(T)));
|
|
}
|
|
const amt: ShiftT = @truncate(@as(UT, @bitCast(b_val)));
|
|
const a_val = a.getInt(T);
|
|
return int(a_val << amt);
|
|
}
|
|
inline fn shr(comptime T: type, a: Value, b: Value) Value {
|
|
const ShiftT = std.math.Log2Int(T);
|
|
const UT = std.meta.Int(.unsigned, @typeInfo(T).Int.bits);
|
|
|
|
const b_val = b.getInt(T);
|
|
if (b_val > std.math.maxInt(ShiftT)) return Value.int(0);
|
|
|
|
const amt: ShiftT = @truncate(@as(UT, @intCast(b_val)));
|
|
const a_val = a.getInt(T);
|
|
return int(a_val >> amt);
|
|
}
|
|
|
|
const FT = fn (Value, Value, Type, *Compilation) Value;
|
|
fn getOp(comptime intFunc: anytype, comptime floatFunc: anytype) FT {
|
|
return struct {
|
|
fn op(a: Value, b: Value, ty: Type, comp: *Compilation) Value {
|
|
const size = ty.sizeof(comp).?;
|
|
if (@TypeOf(floatFunc) != @TypeOf(null) and ty.isFloat()) {
|
|
switch (size) {
|
|
4 => return floatFunc(f32, a, b),
|
|
8 => return floatFunc(f64, a, b),
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
if (ty.isUnsignedInt(comp)) switch (size) {
|
|
1 => unreachable, // promoted to int
|
|
2 => unreachable, // promoted to int
|
|
4 => return intFunc(u32, a, b),
|
|
8 => return intFunc(u64, a, b),
|
|
else => unreachable,
|
|
} else switch (size) {
|
|
1 => unreachable, // promoted to int
|
|
2 => unreachable, // promoted to int
|
|
4 => return intFunc(i32, a, b),
|
|
8 => return intFunc(i64, a, b),
|
|
else => unreachable,
|
|
}
|
|
}
|
|
}.op;
|
|
}
|
|
};
|
|
|
|
/// caller guarantees rhs != 0
|
|
pub const div = bin_ops.getOp(bin_ops.divInt, bin_ops.divFloat);
|
|
/// caller guarantees rhs != 0
|
|
/// caller guarantees lhs != std.math.minInt(T) OR rhs != -1
|
|
pub const rem = bin_ops.getOp(bin_ops.remInt, null);
|
|
|
|
pub const bitOr = bin_ops.getOp(bin_ops.orInt, null);
|
|
pub const bitXor = bin_ops.getOp(bin_ops.xorInt, null);
|
|
pub const bitAnd = bin_ops.getOp(bin_ops.andInt, null);
|
|
|
|
pub const shl = bin_ops.getOp(bin_ops.shl, null);
|
|
pub const shr = bin_ops.getOp(bin_ops.shr, null);
|
|
|
|
pub fn bitNot(v: Value, ty: Type, comp: *Compilation) Value {
|
|
const size = ty.sizeof(comp).?;
|
|
var out: Value = undefined;
|
|
if (ty.isUnsignedInt(comp)) switch (size) {
|
|
1 => unreachable, // promoted to int
|
|
2 => unreachable, // promoted to int
|
|
4 => out = int(~v.getInt(u32)),
|
|
8 => out = int(~v.getInt(u64)),
|
|
else => unreachable,
|
|
} else switch (size) {
|
|
1 => unreachable, // promoted to int
|
|
2 => unreachable, // promoted to int
|
|
4 => out = int(~v.getInt(i32)),
|
|
8 => out = int(~v.getInt(i64)),
|
|
else => unreachable,
|
|
}
|
|
return out;
|
|
}
|
|
|
|
pub fn compare(a: Value, op: std.math.CompareOperator, b: Value, ty: Type, comp: *const Compilation) bool {
|
|
assert(a.tag == b.tag);
|
|
if (a.tag == .nullptr_t) {
|
|
return switch (op) {
|
|
.eq => true,
|
|
.neq => false,
|
|
else => unreachable,
|
|
};
|
|
}
|
|
const S = struct {
|
|
inline fn doICompare(comptime T: type, aa: Value, opp: std.math.CompareOperator, bb: Value) bool {
|
|
const a_val = aa.getInt(T);
|
|
const b_val = bb.getInt(T);
|
|
return std.math.compare(a_val, opp, b_val);
|
|
}
|
|
inline fn doFCompare(comptime T: type, aa: Value, opp: std.math.CompareOperator, bb: Value) bool {
|
|
const a_val = aa.getFloat(T);
|
|
const b_val = bb.getFloat(T);
|
|
return std.math.compare(a_val, opp, b_val);
|
|
}
|
|
};
|
|
const size = ty.sizeof(comp).?;
|
|
switch (a.tag) {
|
|
.unavailable => return true,
|
|
.int => if (ty.isUnsignedInt(comp)) switch (size) {
|
|
1 => return S.doICompare(u8, a, op, b),
|
|
2 => return S.doICompare(u16, a, op, b),
|
|
4 => return S.doICompare(u32, a, op, b),
|
|
8 => return S.doICompare(u64, a, op, b),
|
|
else => unreachable,
|
|
} else switch (size) {
|
|
1 => return S.doICompare(i8, a, op, b),
|
|
2 => return S.doICompare(i16, a, op, b),
|
|
4 => return S.doICompare(i32, a, op, b),
|
|
8 => return S.doICompare(i64, a, op, b),
|
|
else => unreachable,
|
|
},
|
|
.float => switch (size) {
|
|
4 => return S.doFCompare(f32, a, op, b),
|
|
8 => return S.doFCompare(f64, a, op, b),
|
|
else => unreachable,
|
|
},
|
|
else => @panic("TODO"),
|
|
}
|
|
return false;
|
|
}
|
|
|
|
pub fn hash(v: Value) u64 {
|
|
switch (v.tag) {
|
|
.unavailable => unreachable,
|
|
.int => return std.hash.Wyhash.hash(0, std.mem.asBytes(&v.data.int)),
|
|
else => @panic("TODO"),
|
|
}
|
|
}
|
|
|
|
pub fn dump(v: Value, ty: Type, comp: *Compilation, strings: []const u8, w: anytype) !void {
|
|
switch (v.tag) {
|
|
.unavailable => try w.writeAll("unavailable"),
|
|
.int => if (ty.is(.bool) and comp.langopts.standard.atLeast(.c2x)) {
|
|
try w.print("{s}", .{if (v.isZero()) "false" else "true"});
|
|
} else if (ty.isUnsignedInt(comp)) {
|
|
try w.print("{d}", .{v.data.int});
|
|
} else {
|
|
try w.print("{d}", .{v.signExtend(ty, comp)});
|
|
},
|
|
.bytes => try w.print("\"{s}\"", .{v.data.bytes.slice(strings)}),
|
|
// std.fmt does @as instead of @floatCast
|
|
.float => try w.print("{d}", .{@as(f64, @floatCast(v.data.float))}),
|
|
else => try w.print("({s})", .{@tagName(v.tag)}),
|
|
}
|
|
}
|