From d455d73ed43fcd797ad76535cf14f60d41393c99 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Oct 2025 22:17:36 -0400 Subject: [PATCH] Comptime eval updates number literals --- src/eval/comptime_evaluator.zig | 81 +++++++++++++++++++ src/eval/test/comptime_eval_test.zig | 116 +++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) diff --git a/src/eval/comptime_evaluator.zig b/src/eval/comptime_evaluator.zig index c7df320123..c7f9069957 100644 --- a/src/eval/comptime_evaluator.zig +++ b/src/eval/comptime_evaluator.zig @@ -272,9 +272,90 @@ pub const ComptimeEvaluator = struct { const layout_cache = &self.interpreter.runtime_layout_store; defer result.decref(layout_cache, ops); + // Try to fold the result to a constant expression + self.tryFoldConstant(def_idx, result) catch { + // If folding fails, just continue - the original expression is still valid + }; + return EvalResult.success; } + /// Attempts to replace an expression with its constant-folded result + fn tryFoldConstant(self: *ComptimeEvaluator, def_idx: CIR.Def.Idx, stack_value: eval_mod.StackValue) !void { + const def = self.env.store.getDef(def_idx); + const expr_idx = def.expr; + const region = self.env.store.getExprRegion(expr_idx); + + // Convert StackValue to CIR expression based on layout + const layout = stack_value.layout; + + // Check if this is a scalar type (including integers) + if (layout.tag != .scalar) { + return error.NotImplemented; // Don't fold non-scalar types yet + } + + const scalar_tag = layout.data.scalar.tag; + const new_expr_idx = switch (scalar_tag) { + .int => blk: { + // Extract integer value + const value = stack_value.asI128(); + const precision = layout.data.scalar.data.int; + + // Map precision to NumKind + const num_kind: CIR.NumKind = switch (precision) { + .i8 => .i8, + .i16 => .i16, + .i32 => .i32, + .i64 => .i64, + .i128 => .i128, + .u8 => .u8, + .u16 => .u16, + .u32 => .u32, + .u64 => .u64, + .u128 => .u128, + }; + + // Create IntValue + const int_value = CIR.IntValue{ + .bytes = @bitCast(value), + .kind = switch (precision) { + .u8, .u16, .u32, .u64, .u128 => .u128, + .i8, .i16, .i32, .i64, .i128 => .i128, + }, + }; + + // Create e_num expression + const folded_expr = CIR.Expr{ + .e_num = .{ + .value = int_value, + .kind = num_kind, + }, + }; + + // Add the new expression to the store + break :blk try self.env.store.addExpr(folded_expr, region); + }, + else => return error.NotImplemented, // Don't fold other scalar types yet + }; + + // CRITICAL: We need to maintain the type information for the new expression. + // We're adding a new CIR node AFTER type checking has completed, so we need + // to create a new type variable for it that redirects to the original expression's type. + // This ensures the 1-to-1 mapping between CIR nodes and type variables is maintained. + const original_type_var = ModuleEnv.varFrom(expr_idx); + + // Create a new type variable that redirects to the original's type + // This properly extends the types store + _ = try self.env.types.freshRedirect(original_type_var); + + // Replace the expr field in the Def + // The Def is stored in extra_data with expr at index 1 + const nid: CIR.Node.Idx = @enumFromInt(@intFromEnum(def_idx)); + const node = self.env.store.nodes.get(nid); + const extra_start = node.data_1; + self.env.store.extra_data.items.items[extra_start + 1] = @intFromEnum(new_expr_idx); + } + /// Helper to report a problem and track allocated message fn reportProblem( self: *ComptimeEvaluator, diff --git a/src/eval/test/comptime_eval_test.zig b/src/eval/test/comptime_eval_test.zig index 993211fcc8..ab7a7cbf34 100644 --- a/src/eval/test/comptime_eval_test.zig +++ b/src/eval/test/comptime_eval_test.zig @@ -604,3 +604,119 @@ test "comptime eval - crash halts within single def" { try testing.expectEqual(@as(u32, 1), summary.crashed); try testing.expectEqual(@as(usize, 1), result.problems.len()); } + +// Constant folding tests + +test "comptime eval - constant folding simple addition" { + const src = "x = 1 + 1"; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + // Should evaluate successfully + try testing.expectEqual(@as(u32, 1), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); + + // Verify the expression was folded to a constant + const defs = result.module_env.store.sliceDefs(result.module_env.all_defs); + try testing.expectEqual(@as(usize, 1), defs.len); + + const def = result.module_env.store.getDef(defs[0]); + const expr = result.module_env.store.getExpr(def.expr); + + // The expression should now be e_num with value 2 + try testing.expect(expr == .e_num); + const value = expr.e_num.value.toI128(); + try testing.expectEqual(@as(i128, 2), value); +} + +test "comptime eval - constant folding multiplication" { + const src = "x = 21 * 2"; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + try testing.expectEqual(@as(u32, 1), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); + + // Verify the expression was folded + const defs = result.module_env.store.sliceDefs(result.module_env.all_defs); + const def = result.module_env.store.getDef(defs[0]); + const expr = result.module_env.store.getExpr(def.expr); + + try testing.expect(expr == .e_num); + const value = expr.e_num.value.toI128(); + try testing.expectEqual(@as(i128, 42), value); +} + +test "comptime eval - constant folding preserves literal" { + const src = "x = 42"; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + try testing.expectEqual(@as(u32, 1), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); + + // The expression should stay as e_num with value 42 + const defs = result.module_env.store.sliceDefs(result.module_env.all_defs); + const def = result.module_env.store.getDef(defs[0]); + const expr = result.module_env.store.getExpr(def.expr); + + try testing.expect(expr == .e_num); + const value = expr.e_num.value.toI128(); + try testing.expectEqual(@as(i128, 42), value); +} + +test "comptime eval - constant folding multiple defs" { + const src = + \\a = 10 + 5 + \\b = 20 * 2 + \\c = 100 - 58 + ; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + try testing.expectEqual(@as(u32, 3), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); + + // Verify all expressions were folded + const defs = result.module_env.store.sliceDefs(result.module_env.all_defs); + try testing.expectEqual(@as(usize, 3), defs.len); + + // Check a = 15 + { + const def = result.module_env.store.getDef(defs[0]); + const expr = result.module_env.store.getExpr(def.expr); + try testing.expect(expr == .e_num); + const value = expr.e_num.value.toI128(); + try testing.expectEqual(@as(i128, 15), value); + } + + // Check b = 40 + { + const def = result.module_env.store.getDef(defs[1]); + const expr = result.module_env.store.getExpr(def.expr); + try testing.expect(expr == .e_num); + const value = expr.e_num.value.toI128(); + try testing.expectEqual(@as(i128, 40), value); + } + + // Check c = 42 + { + const def = result.module_env.store.getDef(defs[2]); + const expr = result.module_env.store.getExpr(def.expr); + try testing.expect(expr == .e_num); + const value = expr.e_num.value.toI128(); + try testing.expectEqual(@as(i128, 42), value); + } +}