feat: folding logical operations

This commit is contained in:
Brett Broadhurst 2026-03-29 04:49:03 -06:00
parent 92e8bcd866
commit 2260ccda25
Failed to generate hash of commit
24 changed files with 310 additions and 151 deletions

View file

@ -35,19 +35,41 @@ pub const Value = struct {
ip_index: InternPool.Index,
pub const Unwrapped = union(enum) {
bool: bool,
int: i64,
float: f64,
fn toFloat(v: Value.Unwrapped) f64 {
pub fn toFloat(v: Unwrapped) f64 {
return switch (v) {
.int => |i| @floatFromInt(i),
.float => |f| f,
.bool => |boolean| @floatFromInt(@intFromBool(boolean)),
.int => |int| @floatFromInt(int),
.float => |float| float,
};
}
pub fn isTruthy(v: Unwrapped) bool {
return switch (v) {
//.null => false,
.bool => |boolean| boolean,
.int => |int| int != 0,
.float => |float| float != 0.0,
//.str => true,
};
}
pub fn coerce(value: Unwrapped) Unwrapped {
return switch (value) {
.int => value,
.float => value,
.bool => |boolean| .{ .int = if (boolean) 1 else 0 },
//else => null,
};
}
};
pub fn unwrap(value: Value, ip: *InternPool) Unwrapped {
switch (ip.values.items[@intFromEnum(value.toInterned())]) {
.bool => |boolean| return .{ .bool = boolean },
.int => |int| return .{ .int = int },
.float => |float| return .{ .float = @bitCast(float) },
.str => @panic("String unwrapping not implemented!"),
@ -279,43 +301,90 @@ pub const Builder = struct {
}
};
fn foldArith(
lhs: Value.Unwrapped,
rhs: Value.Unwrapped,
op: Story.Opcode,
) !Value.Unwrapped {
const l = lhs.coerce();
const r = rhs.coerce();
if (l == .int and r == .int) {
return switch (op) {
.add => .{ .int = l.int +% r.int },
.sub => .{ .int = l.int -% r.int },
.mul => .{ .int = l.int *% r.int },
.div => if (r.int == 0)
error.DivisionByZero
else
.{ .int = @divTrunc(l.int, r.int) },
.mod => if (r.int == 0)
error.DivisionByZero
else
.{ .int = @mod(l.int, r.int) },
else => unreachable,
};
}
const lf = l.toFloat();
const rf = r.toFloat();
return switch (op) {
.add => .{ .float = lf + rf },
.sub => .{ .float = lf - rf },
.mul => .{ .float = lf * rf },
.div => if (rf == 0.0)
error.DivisionByZero
else
.{ .float = lf / rf },
.mod => if (rf == 0.0)
error.DivisionByZero
else
.{ .float = @mod(lf, rf) },
else => unreachable,
};
}
fn foldCmp(
lhs: Value.Unwrapped,
rhs: Value.Unwrapped,
op: Story.Opcode,
) !Value.Unwrapped {
switch (op) {
.cmp_eq => return .{ .bool = std.meta.eql(lhs, rhs) },
.cmp_neq => return .{ .bool = !std.meta.eql(lhs, rhs) },
else => {},
}
const lf = lhs.coerce().toFloat();
const rf = rhs.coerce().toFloat();
const result = switch (op) {
.cmp_lt => lf < rf,
.cmp_gt => lf > rf,
.cmp_lte => lf <= rf,
.cmp_gte => lf >= rf,
else => unreachable,
};
return .{ .bool = result };
}
fn foldConstant(
lhs: Value.Unwrapped,
rhs: Value.Unwrapped,
op: Story.Opcode,
) !Value.Unwrapped {
if (lhs == .int and rhs == .int) {
switch (op) {
.add => return .{ .int = lhs.int +% rhs.int },
.sub => return .{ .int = lhs.int -% rhs.int },
.mul => return .{ .int = lhs.int *% rhs.int },
.div => {
if (rhs.int == 0)
return error.DivisionByZero;
return .{ .int = @divTrunc(lhs.int, rhs.int) };
},
.mod => if (rhs.int == 0)
return error.DivisionByZero
else
return .{ .int = @mod(lhs.int, rhs.int) },
else => unreachable,
}
}
const l = lhs.toFloat();
const r = rhs.toFloat();
switch (op) {
.add => return .{ .float = l + r },
.sub => return .{ .float = l - r },
.mul => return .{ .float = l * r },
.div => if (r == 0.0)
return error.DivisionByZero
else
return .{ .float = l / r },
.mod => if (r == 0.0)
return error.DivisionByZero
else
return .{ .float = @mod(l, r) },
.add,
.sub,
.mul,
.div,
.mod,
=> return foldArith(lhs, rhs, op),
.cmp_eq,
.cmp_neq,
.cmp_lt,
.cmp_gt,
.cmp_lte,
.cmp_gte,
=> return foldCmp(lhs, rhs, op),
else => unreachable,
}
}
@ -351,20 +420,27 @@ fn irUnaryOp(
//const lhs_src: SrcLoc = .{ .src_offset = 0 };
//try sema.analyzeArithmeticArg(builder, lhs, lhs_src);
if (sema.resolveValue(lhs)) |lhs_value| {
const lhs_unwrapped = lhs_value.unwrap(ip);
switch (lhs_unwrapped) {
.int => |int| {
if (sema.resolveValue(lhs)) |lhs_info| {
switch (lhs_info.unwrap(ip)) {
.bool => |boolean| {
const new_value = switch (op) {
.not => return error.TypeError,
.not => !boolean,
.neg => return error.TypeError,
else => unreachable,
};
return .{ .value = ip.getOrPutBool(new_value) };
},
.int => |int| {
const new_value: i64 = switch (op) {
.not => if (int > 0) 0 else 1,
.neg => -int,
else => unreachable,
};
return .{ .value = try ip.getOrPutInt(gpa, new_value) };
},
.float => |float| {
const new_value = switch (op) {
.not => return error.TypeError,
const new_value: f64 = switch (op) {
.not => if (float > 0.0) 0.0 else 1.0,
.neg => -float,
else => unreachable,
};
@ -384,8 +460,9 @@ fn irBinaryOp(
inst: Ir.Inst.Index,
op: Story.Opcode,
) InnerError!ValueInfo {
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin;
const gpa = sema.gpa;
const ip = &sema.module.intern_pool;
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin;
const lhs = sema.resolveInst(data.lhs);
const rhs = sema.resolveInst(data.rhs);
//const lhs_src: SrcLoc = .{ .src_offset = 0 };
@ -395,16 +472,13 @@ fn irBinaryOp(
if (sema.resolveValue(lhs)) |lhs_value| {
if (sema.resolveValue(rhs)) |rhs_value| {
const lhs_unwrap = lhs_value.unwrap(ip);
const rhs_unwrap = rhs_value.unwrap(ip);
switch (try foldConstant(lhs_unwrap, rhs_unwrap, op)) {
.int => |int| return .{
.value = try ip.getOrPutInt(sema.gpa, int),
},
.float => |float| return .{
.value = try ip.getOrPutFloat(sema.gpa, float),
},
}
const lhs_coerced = lhs_value.unwrap(ip).coerce();
const rhs_coerced = rhs_value.unwrap(ip).coerce();
return switch (try foldConstant(lhs_coerced, rhs_coerced, op)) {
.bool => |boolean| .{ .value = ip.getOrPutBool(boolean) },
.int => |int| .{ .value = try ip.getOrPutInt(gpa, int) },
.float => |float| .{ .value = try ip.getOrPutFloat(gpa, float) },
};
}
}
@ -414,6 +488,40 @@ fn irBinaryOp(
return .none;
}
fn irLogicalOp(
sema: *Sema,
builder: *Builder,
inst: Ir.Inst.Index,
logical_or: bool,
) InnerError!ValueInfo {
const ip = &sema.module.intern_pool;
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin;
const lhs = sema.resolveInst(data.lhs);
const rhs = sema.resolveInst(data.rhs);
if (sema.resolveValue(lhs)) |lhs_info| {
const lhs_value = lhs_info.unwrap(ip);
if (sema.resolveValue(rhs)) |_| {
return if (logical_or)
if (lhs_value.isTruthy()) lhs else rhs
else if (!lhs_value.isTruthy()) lhs else rhs;
}
if (logical_or and lhs_value.isTruthy()) return lhs;
if (!logical_or and !lhs_value.isTruthy()) return lhs;
try builder.ensureLoad(rhs);
return .none;
}
const else_label = try builder.addLabel();
try builder.ensureLoad(lhs);
try builder.addFixup(if (logical_or) .jmp_t else .jmp_f, else_label);
try builder.addByteOp(.pop);
try builder.ensureLoad(rhs);
builder.setLabel(else_label);
return .none;
}
fn irDeclRef(
sema: *Sema,
builder: *Builder,
@ -672,7 +780,6 @@ fn irDivert(
sema.gpa,
extra.data.field_name_start,
);
std.debug.print("Target: {any}\n", .{target});
const e = try sema.lookupInNamespace(target.namespace.?, ip_index, callee_src);
switch (e.tag) {
.knot => {
@ -782,12 +889,10 @@ fn analyzeBodyInner(
.mod => try irBinaryOp(sema, builder, inst, .mod),
.neg => try irUnaryOp(sema, builder, inst, .neg),
.not => try irUnaryOp(sema, builder, inst, .not),
.bool_and => try irLogicalOp(sema, builder, inst, false),
.bool_or => try irLogicalOp(sema, builder, inst, true),
.cmp_eq => try irBinaryOp(sema, builder, inst, .cmp_eq),
.cmp_neq => blk: {
const val = try irBinaryOp(sema, builder, inst, .cmp_eq);
try builder.addByteOp(.not);
break :blk val;
},
.cmp_neq => try irBinaryOp(sema, builder, inst, .cmp_neq),
.cmp_lt => try irBinaryOp(sema, builder, inst, .cmp_lt),
.cmp_lte => try irBinaryOp(sema, builder, inst, .cmp_lte),
.cmp_gt => try irBinaryOp(sema, builder, inst, .cmp_gt),