tuples
This commit is contained in:
473
src/codegen.zig
473
src/codegen.zig
@@ -196,6 +196,10 @@ pub const CodeGen = struct {
|
||||
global_mutable_vars: std.StringHashMap(NamedValue),
|
||||
// Declared return types for non-generic functions (preserves signedness lost by LLVM round-trip)
|
||||
function_return_types: std.StringHashMap(Type),
|
||||
// Tuple alloca → type mapping (since tuples are anonymous, we track their types by alloca pointer)
|
||||
tuple_alloca_types: std.AutoHashMap(usize, Type),
|
||||
// UFCS alias map: alias name → target function name
|
||||
ufcs_aliases: std.StringHashMap([]const u8),
|
||||
// Target configuration (triple, cpu, opt level, lib paths, linker)
|
||||
target_config: TargetConfig = .{},
|
||||
// Cached primitive LLVM types (initialized once in init(), avoids repeated FFI calls)
|
||||
@@ -405,6 +409,8 @@ pub const CodeGen = struct {
|
||||
.foreign_name_map = std.StringHashMap([]const u8).init(allocator),
|
||||
.global_mutable_vars = std.StringHashMap(NamedValue).init(allocator),
|
||||
.function_return_types = std.StringHashMap(Type).init(allocator),
|
||||
.tuple_alloca_types = std.AutoHashMap(usize, Type).init(allocator),
|
||||
.ufcs_aliases = std.StringHashMap([]const u8).init(allocator),
|
||||
.target_config = target_config,
|
||||
.cached_i1 = c.LLVMInt1TypeInContext(ctx),
|
||||
.cached_i8 = c.LLVMInt8TypeInContext(ctx),
|
||||
@@ -576,6 +582,14 @@ pub const CodeGen = struct {
|
||||
.pointer_type, .many_pointer_type, .function_type => self.ptrType(),
|
||||
.any_type => self.getAnyStructType(),
|
||||
.meta_type => self.ptrType(),
|
||||
.tuple_type => |info| {
|
||||
const n: c_uint = @intCast(info.field_types.len);
|
||||
const field_llvm_types = self.allocator.alloc(c.LLVMTypeRef, info.field_types.len) catch unreachable;
|
||||
for (info.field_types, 0..) |ft, i| {
|
||||
field_llvm_types[i] = self.typeToLLVM(ft);
|
||||
}
|
||||
return c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, n, 0);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1197,6 +1211,9 @@ pub const CodeGen = struct {
|
||||
.var_decl => |vd| {
|
||||
try self.registerGlobalVar(vd);
|
||||
},
|
||||
.ufcs_alias => |ua| {
|
||||
try self.ufcs_aliases.put(ua.name, ua.target);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
@@ -1476,6 +1493,18 @@ pub const CodeGen = struct {
|
||||
.return_type = ret_ptr,
|
||||
} };
|
||||
}
|
||||
// Tuple type: (T1, T2) or (T1,)
|
||||
if (tn.data == .tuple_type_expr) {
|
||||
const tte = tn.data.tuple_type_expr;
|
||||
const field_types = self.allocator.alloc(Type, tte.field_types.len) catch return .void_type;
|
||||
for (tte.field_types, 0..) |ft, i| {
|
||||
field_types[i] = self.resolveType(ft);
|
||||
}
|
||||
return .{ .tuple_type = .{
|
||||
.field_types = field_types,
|
||||
.field_names = tte.field_names,
|
||||
} };
|
||||
}
|
||||
// Parameterized type: Vector(N, T) or generic struct instantiation
|
||||
if (tn.data == .parameterized_type_expr) {
|
||||
const pte = tn.data.parameterized_type_expr;
|
||||
@@ -2357,6 +2386,9 @@ pub const CodeGen = struct {
|
||||
const sname = self.resolveAlias(ret_type.struct_type);
|
||||
const info = try self.getStructInfo(sname);
|
||||
return c.LLVMBuildLoad2(self.builder, info.llvm_type, raw_val, "retval");
|
||||
} else if (ret_type.isTuple()) {
|
||||
const llvm_ty = self.typeToLLVM(ret_type);
|
||||
return c.LLVMBuildLoad2(self.builder, llvm_ty, raw_val, "retval");
|
||||
} else if (ret_type.isUnion()) {
|
||||
const uname = ret_type.union_type;
|
||||
const resolved = self.resolveAlias(uname);
|
||||
@@ -2716,6 +2748,39 @@ pub const CodeGen = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Tuple-typed variable
|
||||
if (sx_ty.isTuple()) {
|
||||
const llvm_ty = self.typeToLLVM(sx_ty);
|
||||
if (vd.value) |val| {
|
||||
if (val.data == .undef_literal) {
|
||||
// Zero-initialized tuple
|
||||
const alloca = try self.buildNamedAlloca(llvm_ty, vd.name);
|
||||
self.storeNull(llvm_ty, alloca);
|
||||
try self.registerVariable(vd.name, alloca, sx_ty);
|
||||
return null;
|
||||
}
|
||||
if (val.data == .tuple_literal) {
|
||||
// Tuple literal — use its alloca directly
|
||||
const lit_alloca = try self.genTupleLiteral(val.data.tuple_literal);
|
||||
try self.registerVariable(vd.name, lit_alloca, sx_ty);
|
||||
return null;
|
||||
}
|
||||
// General expression (e.g., function call returning a tuple, or tuple op)
|
||||
const result = try self.genExpr(val);
|
||||
// If the result is already a tuple alloca (from concat/repeat/etc), use it directly
|
||||
if (self.tuple_alloca_types.contains(@intFromPtr(result))) {
|
||||
try self.registerVariable(vd.name, result, sx_ty);
|
||||
return null;
|
||||
}
|
||||
// Otherwise it's a loaded struct value (e.g., from function call) — store into an alloca
|
||||
const alloca = try self.buildNamedAlloca(llvm_ty, vd.name);
|
||||
_ = c.LLVMBuildStore(self.builder, result, alloca);
|
||||
try self.registerVariable(vd.name, alloca, sx_ty);
|
||||
return null;
|
||||
}
|
||||
return self.emitErrorFmt("tuple variable '{s}' must be initialized", .{vd.name});
|
||||
}
|
||||
|
||||
// Union-typed variable (tagged enum or C-style union)
|
||||
if (sx_ty.isUnion()) {
|
||||
const uname = self.resolveAlias(sx_ty.union_type);
|
||||
@@ -2977,6 +3042,22 @@ pub const CodeGen = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Tuple-typed constant: tuple literal returns alloca, use directly
|
||||
if (sx_ty.isTuple()) {
|
||||
if (cd.value.data == .tuple_literal) {
|
||||
const lit_alloca = try self.genTupleLiteral(cd.value.data.tuple_literal);
|
||||
try self.registerVariable(cd.name, lit_alloca, sx_ty);
|
||||
return null;
|
||||
}
|
||||
// General expression (e.g., function call returning a tuple)
|
||||
const val = try self.genExpr(cd.value);
|
||||
const llvm_ty = self.typeToLLVM(sx_ty);
|
||||
const alloca = try self.buildNamedAlloca(llvm_ty, cd.name);
|
||||
_ = c.LLVMBuildStore(self.builder, val, alloca);
|
||||
try self.registerVariable(cd.name, alloca, sx_ty);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Function pointer typed constant
|
||||
if (sx_ty.isFunctionType()) {
|
||||
const llvm_ty = self.ptrType();
|
||||
@@ -3422,6 +3503,31 @@ pub const CodeGen = struct {
|
||||
return self.genStringComparison(binop.op, lhs, rhs);
|
||||
}
|
||||
|
||||
// Tuple comparison: element-wise
|
||||
if (lhs_ty.isTuple() and rhs_ty.isTuple() and
|
||||
(binop.op == .eq or binop.op == .neq or binop.op == .lt or binop.op == .lte or binop.op == .gt or binop.op == .gte))
|
||||
{
|
||||
return self.genTupleComparison(binop.op, binop.lhs, binop.rhs, lhs_ty, rhs_ty);
|
||||
}
|
||||
|
||||
// Tuple concatenation: tuple + tuple
|
||||
if (lhs_ty.isTuple() and rhs_ty.isTuple() and binop.op == .add) {
|
||||
return self.genTupleConcat(binop.lhs, binop.rhs, lhs_ty, rhs_ty);
|
||||
}
|
||||
|
||||
// Tuple repetition: tuple * int
|
||||
if (lhs_ty.isTuple() and rhs_ty.isInt() and binop.op == .mul) {
|
||||
return self.genTupleRepeat(binop.lhs, binop.rhs, lhs_ty);
|
||||
}
|
||||
|
||||
// Membership: value in tuple
|
||||
if (binop.op == .in_op) {
|
||||
if (rhs_ty.isTuple()) {
|
||||
return self.genTupleMembership(binop.lhs, binop.rhs, rhs_ty);
|
||||
}
|
||||
return self.emitError("'in' requires a tuple on the right side");
|
||||
}
|
||||
|
||||
const lhs = try self.genExprAsType(binop.lhs, result_type);
|
||||
const rhs = try self.genExprAsType(binop.rhs, result_type);
|
||||
return self.genBinaryOp(binop.op, lhs, rhs, result_type);
|
||||
@@ -3470,6 +3576,9 @@ pub const CodeGen = struct {
|
||||
const ctx_name: ?[]const u8 = if (self.current_return_type.isStruct()) self.current_return_type.struct_type else null;
|
||||
return self.genStructLiteral(sl, ctx_name);
|
||||
},
|
||||
.tuple_literal => |tl| {
|
||||
return self.genTupleLiteral(tl);
|
||||
},
|
||||
.array_literal => |al| {
|
||||
// Typed array/vector/slice literal: Type.[elems]
|
||||
if (al.type_expr) |te| {
|
||||
@@ -3539,6 +3648,10 @@ pub const CodeGen = struct {
|
||||
.const_decl => |cd| {
|
||||
return self.genConstDecl(cd);
|
||||
},
|
||||
.ufcs_alias => |ua| {
|
||||
try self.ufcs_aliases.put(ua.name, ua.target);
|
||||
return null;
|
||||
},
|
||||
.assignment => |asgn| {
|
||||
return self.genAssignment(asgn);
|
||||
},
|
||||
@@ -4181,6 +4294,60 @@ pub const CodeGen = struct {
|
||||
return alloca;
|
||||
}
|
||||
|
||||
/// Resolve a field name or numeric index to a tuple field index.
|
||||
fn resolveTupleFieldIndex(info: Type.TupleTypeInfo, field: []const u8) ?usize {
|
||||
// Try numeric index first: "0", "1", etc.
|
||||
if (std.fmt.parseInt(usize, field, 10)) |idx| {
|
||||
if (idx < info.field_types.len) return idx;
|
||||
} else |_| {}
|
||||
// Try named lookup
|
||||
if (info.field_names) |names| {
|
||||
for (names, 0..) |name, i| {
|
||||
if (std.mem.eql(u8, name, field)) return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genTupleLiteral(self: *CodeGen, tl: ast.TupleLiteral) anyerror!c.LLVMValueRef {
|
||||
const n = tl.elements.len;
|
||||
// Infer types for each element
|
||||
const field_types = try self.allocator.alloc(Type, n);
|
||||
const field_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, n);
|
||||
for (tl.elements, 0..) |elem, i| {
|
||||
field_types[i] = self.inferType(elem.value);
|
||||
field_llvm_types[i] = self.typeToLLVM(field_types[i]);
|
||||
}
|
||||
|
||||
// Build anonymous LLVM struct type
|
||||
const llvm_type = c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, @intCast(n), 0);
|
||||
|
||||
// Alloca and store each element
|
||||
const alloca = self.buildEntryBlockAlloca(llvm_type, "tuple");
|
||||
for (tl.elements, 0..) |elem, i| {
|
||||
const val = try self.genExprAsType(elem.value, field_types[i]);
|
||||
self.storeStructField(llvm_type, alloca, @intCast(i), val);
|
||||
}
|
||||
|
||||
// Store the tuple type info for later field access
|
||||
const field_names = if (tl.elements[0].name != null) blk: {
|
||||
const names = try self.allocator.alloc([]const u8, n);
|
||||
for (tl.elements, 0..) |elem, i| {
|
||||
names[i] = elem.name orelse "";
|
||||
}
|
||||
break :blk @as(?[]const []const u8, names);
|
||||
} else null;
|
||||
|
||||
// Register this alloca as having tuple type
|
||||
const tuple_ty = Type{ .tuple_type = .{
|
||||
.field_names = field_names,
|
||||
.field_types = field_types,
|
||||
} };
|
||||
try self.tuple_alloca_types.put(@intFromPtr(alloca), tuple_ty);
|
||||
|
||||
return alloca;
|
||||
}
|
||||
|
||||
/// Generate an array literal as an alloca with elements stored via GEP.
|
||||
/// If target_ty is provided, elements are converted to the array's element type.
|
||||
/// Otherwise, element type is inferred from the first element.
|
||||
@@ -5270,6 +5437,14 @@ pub const CodeGen = struct {
|
||||
// GEP to payload area, load as variant type
|
||||
return self.loadStructField(info.llvm_type, entry.ptr, info.payload_field_index, self.typeToLLVM(variant_ty));
|
||||
}
|
||||
if (entry.ty.isTuple()) {
|
||||
const ti = entry.ty.tuple_type;
|
||||
const idx = resolveTupleFieldIndex(ti, fa.field) orelse
|
||||
return self.emitErrorFmt("no field '{s}' in tuple", .{fa.field});
|
||||
const llvm_ty = self.typeToLLVM(entry.ty);
|
||||
const field_llvm_ty = self.typeToLLVM(ti.field_types[idx]);
|
||||
return self.loadStructField(llvm_ty, entry.ptr, @intCast(idx), field_llvm_ty);
|
||||
}
|
||||
if (entry.ty.isVector()) {
|
||||
const vec_val = self.loadTyped(entry.ty, entry.ptr, "vec_load");
|
||||
return self.genVectorExtract(vec_val, fa.field);
|
||||
@@ -5334,6 +5509,21 @@ pub const CodeGen = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (obj_ty.isTuple()) {
|
||||
const ti = obj_ty.tuple_type;
|
||||
const idx = resolveTupleFieldIndex(ti, fa.field) orelse
|
||||
return self.emitErrorFmt("no field '{s}' in tuple", .{fa.field});
|
||||
const llvm_ty = self.typeToLLVM(obj_ty);
|
||||
const field_llvm_ty = self.typeToLLVM(ti.field_types[idx]);
|
||||
// If obj is a tuple literal, genExpr returned an alloca pointer directly
|
||||
if (fa.object.data == .tuple_literal) {
|
||||
return self.loadStructField(llvm_ty, obj_val, @intCast(idx), field_llvm_ty);
|
||||
}
|
||||
// Otherwise obj_val is a loaded struct value — store into tmp and GEP
|
||||
const tmp = self.buildEntryBlockAlloca(llvm_ty, "tmp_tuple");
|
||||
_ = c.LLVMBuildStore(self.builder, obj_val, tmp);
|
||||
return self.loadStructField(llvm_ty, tmp, @intCast(idx), field_llvm_ty);
|
||||
}
|
||||
return self.emitError("field access on non-struct/non-vector expression");
|
||||
}
|
||||
|
||||
@@ -5524,7 +5714,7 @@ pub const CodeGen = struct {
|
||||
.gte => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOGE, lhs, rhs, "getmp") else if (is_unsigned) c.LLVMBuildICmp(b, c.LLVMIntUGE, lhs, rhs, "getmp") else c.LLVMBuildICmp(b, c.LLVMIntSGE, lhs, rhs, "getmp"),
|
||||
.bit_and => c.LLVMBuildAnd(b, lhs, rhs, "bandtmp"),
|
||||
.bit_or => c.LLVMBuildOr(b, lhs, rhs, "bortmp"),
|
||||
.and_op, .or_op => unreachable,
|
||||
.and_op, .or_op, .in_op => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5577,6 +5767,198 @@ pub const CodeGen = struct {
|
||||
return phi;
|
||||
}
|
||||
|
||||
fn genTupleComparison(self: *CodeGen, op: ast.BinaryOp.Op, lhs_node: *ast.Node, rhs_node: *ast.Node, lhs_ty: Type, rhs_ty: Type) !c.LLVMValueRef {
|
||||
const lhs_info = lhs_ty.tuple_type;
|
||||
const rhs_info = rhs_ty.tuple_type;
|
||||
const n = lhs_info.field_types.len;
|
||||
if (n != rhs_info.field_types.len) return self.emitError("tuple comparison requires same field count");
|
||||
|
||||
const lhs_val = try self.genExpr(lhs_node);
|
||||
const rhs_val = try self.genExpr(rhs_node);
|
||||
const lhs_llvm_ty = self.typeToLLVM(lhs_ty);
|
||||
const rhs_llvm_ty = self.typeToLLVM(rhs_ty);
|
||||
const i1_ty = self.i1Type();
|
||||
|
||||
// Equality: AND-reduce field-wise == comparisons
|
||||
if (op == .eq or op == .neq) {
|
||||
var result = c.LLVMConstInt(i1_ty, 1, 0); // start with true
|
||||
for (0..n) |i| {
|
||||
const field_ty = lhs_info.field_types[i];
|
||||
const field_llvm_ty = self.typeToLLVM(field_ty);
|
||||
const lf = self.loadStructField(lhs_llvm_ty, lhs_val, @intCast(i), field_llvm_ty);
|
||||
const rf = self.loadStructField(rhs_llvm_ty, rhs_val, @intCast(i), field_llvm_ty);
|
||||
const cmp = self.genBinaryOp(.eq, lf, rf, field_ty);
|
||||
result = c.LLVMBuildAnd(self.builder, result, cmp, "tuple_eq_and");
|
||||
}
|
||||
if (op == .neq) return c.LLVMBuildNot(self.builder, result, "tuple_neq");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Lexicographic comparison: chain of basic blocks
|
||||
// For < : at each field, if lhs.i < rhs.i → true, if lhs.i > rhs.i → false, else continue
|
||||
// For > : swap the sense
|
||||
// For <= : same as < but tie → true
|
||||
// For >= : same as > but tie → true
|
||||
const is_less = (op == .lt or op == .lte);
|
||||
const tie_result: u64 = if (op == .lte or op == .gte) 1 else 0;
|
||||
|
||||
const merge_bb = self.appendBB("tup.cmp.merge");
|
||||
const phi_count = 2 * n + 1;
|
||||
const phi_vals = try self.allocator.alloc(c.LLVMValueRef, phi_count);
|
||||
const phi_bbs = try self.allocator.alloc(c.LLVMBasicBlockRef, phi_count);
|
||||
var phi_idx: usize = 0;
|
||||
|
||||
for (0..n) |i| {
|
||||
const field_ty = lhs_info.field_types[i];
|
||||
const field_llvm_ty = self.typeToLLVM(field_ty);
|
||||
const lf = self.loadStructField(lhs_llvm_ty, lhs_val, @intCast(i), field_llvm_ty);
|
||||
const rf = self.loadStructField(rhs_llvm_ty, rhs_val, @intCast(i), field_llvm_ty);
|
||||
|
||||
// Check if lhs.i < rhs.i (or > if !is_less)
|
||||
const less_op: ast.BinaryOp.Op = if (is_less) .lt else .gt;
|
||||
const cmp_less = self.genBinaryOp(less_op, lf, rf, field_ty);
|
||||
phi_vals[phi_idx] = c.LLVMConstInt(i1_ty, 1, 0); // true
|
||||
phi_bbs[phi_idx] = self.getCurrentBlock();
|
||||
phi_idx += 1;
|
||||
const next_bb = self.appendBB("tup.cmp.next");
|
||||
_ = c.LLVMBuildCondBr(self.builder, cmp_less, merge_bb, next_bb);
|
||||
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, next_bb);
|
||||
|
||||
// Check if lhs.i > rhs.i (or < if !is_less)
|
||||
const greater_op: ast.BinaryOp.Op = if (is_less) .gt else .lt;
|
||||
const cmp_greater = self.genBinaryOp(greater_op, lf, rf, field_ty);
|
||||
phi_vals[phi_idx] = c.LLVMConstInt(i1_ty, 0, 0); // false
|
||||
phi_bbs[phi_idx] = self.getCurrentBlock();
|
||||
phi_idx += 1;
|
||||
const eq_bb = self.appendBB("tup.cmp.eq");
|
||||
_ = c.LLVMBuildCondBr(self.builder, cmp_greater, merge_bb, eq_bb);
|
||||
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, eq_bb);
|
||||
}
|
||||
|
||||
// All fields equal — tie
|
||||
phi_vals[phi_idx] = c.LLVMConstInt(i1_ty, tie_result, 0);
|
||||
phi_bbs[phi_idx] = self.getCurrentBlock();
|
||||
phi_idx += 1;
|
||||
_ = c.LLVMBuildBr(self.builder, merge_bb);
|
||||
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, merge_bb);
|
||||
const phi = c.LLVMBuildPhi(self.builder, i1_ty, "tup_cmp");
|
||||
c.LLVMAddIncoming(phi, phi_vals.ptr, @ptrCast(phi_bbs.ptr), @intCast(phi_idx));
|
||||
return phi;
|
||||
}
|
||||
|
||||
fn genTupleConcat(self: *CodeGen, lhs_node: *ast.Node, rhs_node: *ast.Node, lhs_ty: Type, rhs_ty: Type) !c.LLVMValueRef {
|
||||
const lhs_info = lhs_ty.tuple_type;
|
||||
const rhs_info = rhs_ty.tuple_type;
|
||||
const n_lhs = lhs_info.field_types.len;
|
||||
const n_rhs = rhs_info.field_types.len;
|
||||
const n_total = n_lhs + n_rhs;
|
||||
|
||||
// Build new tuple type
|
||||
const field_types = try self.allocator.alloc(Type, n_total);
|
||||
const field_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, n_total);
|
||||
for (0..n_lhs) |i| {
|
||||
field_types[i] = lhs_info.field_types[i];
|
||||
field_llvm_types[i] = self.typeToLLVM(field_types[i]);
|
||||
}
|
||||
for (0..n_rhs) |i| {
|
||||
field_types[n_lhs + i] = rhs_info.field_types[i];
|
||||
field_llvm_types[n_lhs + i] = self.typeToLLVM(field_types[n_lhs + i]);
|
||||
}
|
||||
|
||||
const result_llvm_ty = c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, @intCast(n_total), 0);
|
||||
const alloca = self.buildEntryBlockAlloca(result_llvm_ty, "tuple_cat");
|
||||
|
||||
// Copy fields from lhs
|
||||
const lhs_val = try self.genExpr(lhs_node);
|
||||
const lhs_llvm_ty = self.typeToLLVM(lhs_ty);
|
||||
for (0..n_lhs) |i| {
|
||||
const fv = self.loadStructField(lhs_llvm_ty, lhs_val, @intCast(i), field_llvm_types[i]);
|
||||
self.storeStructField(result_llvm_ty, alloca, @intCast(i), fv);
|
||||
}
|
||||
|
||||
// Copy fields from rhs
|
||||
const rhs_val = try self.genExpr(rhs_node);
|
||||
const rhs_llvm_ty = self.typeToLLVM(rhs_ty);
|
||||
for (0..n_rhs) |i| {
|
||||
const fv = self.loadStructField(rhs_llvm_ty, rhs_val, @intCast(i), field_llvm_types[n_lhs + i]);
|
||||
self.storeStructField(result_llvm_ty, alloca, @intCast(n_lhs + i), fv);
|
||||
}
|
||||
|
||||
// Register tuple type
|
||||
const result_ty = Type{ .tuple_type = .{ .field_types = field_types, .field_names = null } };
|
||||
try self.tuple_alloca_types.put(@intFromPtr(alloca), result_ty);
|
||||
return alloca;
|
||||
}
|
||||
|
||||
fn genTupleRepeat(self: *CodeGen, tuple_node: *ast.Node, count_node: *ast.Node, tuple_ty: Type) !c.LLVMValueRef {
|
||||
// Count must be a comptime int literal
|
||||
const count: usize = switch (count_node.data) {
|
||||
.int_literal => |il| @intCast(il.value),
|
||||
else => return self.emitError("tuple repetition count must be a compile-time integer literal"),
|
||||
};
|
||||
if (count == 0) return self.emitError("tuple repetition count must be positive");
|
||||
|
||||
const info = tuple_ty.tuple_type;
|
||||
const n_fields = info.field_types.len;
|
||||
const n_total = n_fields * count;
|
||||
|
||||
// Build new tuple type
|
||||
const field_types = try self.allocator.alloc(Type, n_total);
|
||||
const field_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, n_total);
|
||||
for (0..count) |r| {
|
||||
for (0..n_fields) |f| {
|
||||
field_types[r * n_fields + f] = info.field_types[f];
|
||||
field_llvm_types[r * n_fields + f] = self.typeToLLVM(info.field_types[f]);
|
||||
}
|
||||
}
|
||||
|
||||
const result_llvm_ty = c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, @intCast(n_total), 0);
|
||||
const alloca = self.buildEntryBlockAlloca(result_llvm_ty, "tuple_rep");
|
||||
|
||||
// Generate tuple value once
|
||||
const tuple_val = try self.genExpr(tuple_node);
|
||||
const tuple_llvm_ty = self.typeToLLVM(tuple_ty);
|
||||
|
||||
// Copy fields for each repetition
|
||||
for (0..count) |r| {
|
||||
for (0..n_fields) |f| {
|
||||
const fv = self.loadStructField(tuple_llvm_ty, tuple_val, @intCast(f), field_llvm_types[r * n_fields + f]);
|
||||
self.storeStructField(result_llvm_ty, alloca, @intCast(r * n_fields + f), fv);
|
||||
}
|
||||
}
|
||||
|
||||
const result_ty = Type{ .tuple_type = .{ .field_types = field_types, .field_names = null } };
|
||||
try self.tuple_alloca_types.put(@intFromPtr(alloca), result_ty);
|
||||
return alloca;
|
||||
}
|
||||
|
||||
fn genTupleMembership(self: *CodeGen, value_node: *ast.Node, tuple_node: *ast.Node, tuple_ty: Type) !c.LLVMValueRef {
|
||||
const info = tuple_ty.tuple_type;
|
||||
const n = info.field_types.len;
|
||||
if (n == 0) return c.LLVMConstInt(self.i1Type(), 0, 0);
|
||||
|
||||
const value = try self.genExpr(value_node);
|
||||
const tuple_val = try self.genExpr(tuple_node);
|
||||
const tuple_llvm_ty = self.typeToLLVM(tuple_ty);
|
||||
const value_ty = self.inferType(value_node);
|
||||
const i1_ty = self.i1Type();
|
||||
|
||||
// OR-reduce: (field0 == val) OR (field1 == val) OR ...
|
||||
var result = c.LLVMConstInt(i1_ty, 0, 0); // start with false
|
||||
for (0..n) |i| {
|
||||
const field_ty = info.field_types[i];
|
||||
const field_llvm_ty = self.typeToLLVM(field_ty);
|
||||
const fv = self.loadStructField(tuple_llvm_ty, tuple_val, @intCast(i), field_llvm_ty);
|
||||
const common_ty = Type.widen(value_ty, field_ty);
|
||||
const cmp = self.genBinaryOp(.eq, value, fv, common_ty);
|
||||
result = c.LLVMBuildOr(self.builder, result, cmp, "tuple_in_or");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn genShortCircuitOp(self: *CodeGen, binop: ast.BinaryOp, is_and: bool) !c.LLVMValueRef {
|
||||
const lhs_val = self.valueToBool(try self.genExpr(binop.lhs));
|
||||
const lhs_bb = self.getCurrentBlock();
|
||||
@@ -5681,16 +6063,46 @@ pub const CodeGen = struct {
|
||||
|
||||
// UFCS: obj.method(args...) → method(obj, args...)
|
||||
const method_name = fa.field;
|
||||
const method_z = self.allocator.dupeZ(u8, method_name) catch method_name;
|
||||
if (self.generic_templates.contains(method_name) or
|
||||
const resolved_method = self.ufcs_aliases.get(method_name) orelse method_name;
|
||||
const method_z = self.allocator.dupeZ(u8, resolved_method) catch resolved_method;
|
||||
if (self.generic_templates.contains(resolved_method) or
|
||||
c.LLVMGetNamedFunction(self.module, method_z.ptr) != null)
|
||||
{
|
||||
// Check if receiver is a tuple — if so, splat its elements as leading args
|
||||
const receiver_type = self.inferType(fa.object);
|
||||
const is_tuple = receiver_type == .tuple_type;
|
||||
|
||||
if (is_tuple) {
|
||||
const tuple_info = receiver_type.tuple_type;
|
||||
const n_tuple = tuple_info.field_types.len;
|
||||
var ufcs_args = try self.allocator.alloc(*Node, n_tuple + call_node.args.len);
|
||||
// Create synthetic field_access nodes for each tuple element
|
||||
for (0..n_tuple) |i| {
|
||||
const syn_node = try self.allocator.create(ast.Node);
|
||||
var idx_buf: [20]u8 = undefined;
|
||||
const idx_str = std.fmt.bufPrint(&idx_buf, "{d}", .{i}) catch "0";
|
||||
const field_name = try self.allocator.dupe(u8, idx_str);
|
||||
syn_node.* = .{
|
||||
.span = fa.object.span,
|
||||
.data = .{ .field_access = .{ .object = fa.object, .field = field_name } },
|
||||
};
|
||||
ufcs_args[i] = syn_node;
|
||||
}
|
||||
for (call_node.args, 0..) |arg, i| {
|
||||
ufcs_args[n_tuple + i] = arg;
|
||||
}
|
||||
return self.genCallByName(resolved_method, .{
|
||||
.callee = call_node.callee,
|
||||
.args = ufcs_args,
|
||||
});
|
||||
}
|
||||
|
||||
var ufcs_args = try self.allocator.alloc(*Node, call_node.args.len + 1);
|
||||
ufcs_args[0] = fa.object;
|
||||
for (call_node.args, 0..) |arg, i| {
|
||||
ufcs_args[i + 1] = arg;
|
||||
}
|
||||
return self.genCallByName(method_name, .{
|
||||
return self.genCallByName(resolved_method, .{
|
||||
.callee = call_node.callee,
|
||||
.args = ufcs_args,
|
||||
});
|
||||
@@ -5704,6 +6116,11 @@ pub const CodeGen = struct {
|
||||
}
|
||||
|
||||
fn genCallByName(self: *CodeGen, callee_name: []const u8, call_node: ast.Call) !c.LLVMValueRef {
|
||||
// Resolve UFCS alias: add :: ufcs num_add; → redirect "add" to "num_add"
|
||||
if (self.ufcs_aliases.get(callee_name)) |resolved| {
|
||||
return self.genCallByName(resolved, call_node);
|
||||
}
|
||||
|
||||
// Check if this is a generic function call
|
||||
if (self.generic_templates.get(callee_name)) |template| {
|
||||
return self.genGenericCall(callee_name, template, call_node);
|
||||
@@ -7424,10 +7841,36 @@ pub const CodeGen = struct {
|
||||
.comptime_expr => |ct| self.inferType(ct.expr),
|
||||
.binary_op => |binop| {
|
||||
switch (binop.op) {
|
||||
.eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op => return .boolean,
|
||||
.eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op, .in_op => return .boolean,
|
||||
else => {
|
||||
const lhs_ty = self.inferType(binop.lhs);
|
||||
const rhs_ty = self.inferType(binop.rhs);
|
||||
// Tuple concatenation: (A, B) + (C, D) → (A, B, C, D)
|
||||
if (lhs_ty.isTuple() and rhs_ty.isTuple() and binop.op == .add) {
|
||||
const li = lhs_ty.tuple_type;
|
||||
const ri = rhs_ty.tuple_type;
|
||||
const n = li.field_types.len + ri.field_types.len;
|
||||
const ft = self.allocator.alloc(Type, n) catch return .void_type;
|
||||
for (0..li.field_types.len) |i| ft[i] = li.field_types[i];
|
||||
for (0..ri.field_types.len) |i| ft[li.field_types.len + i] = ri.field_types[i];
|
||||
return .{ .tuple_type = .{ .field_types = ft, .field_names = null } };
|
||||
}
|
||||
// Tuple repetition: (A, B) * 3 → (A, B, A, B, A, B)
|
||||
if (lhs_ty.isTuple() and rhs_ty.isInt() and binop.op == .mul) {
|
||||
const li = lhs_ty.tuple_type;
|
||||
const count: usize = switch (binop.rhs.data) {
|
||||
.int_literal => |il| @intCast(il.value),
|
||||
else => return .void_type,
|
||||
};
|
||||
const n = li.field_types.len * count;
|
||||
const ft = self.allocator.alloc(Type, n) catch return .void_type;
|
||||
for (0..count) |r| {
|
||||
for (0..li.field_types.len) |f| {
|
||||
ft[r * li.field_types.len + f] = li.field_types[f];
|
||||
}
|
||||
}
|
||||
return .{ .tuple_type = .{ .field_types = ft, .field_names = null } };
|
||||
}
|
||||
return Type.widen(lhs_ty, rhs_ty);
|
||||
},
|
||||
}
|
||||
@@ -7628,6 +8071,12 @@ pub const CodeGen = struct {
|
||||
if (obj_ty.isVector()) {
|
||||
return obj_ty.vectorElementType() orelse Type.s(64);
|
||||
}
|
||||
if (obj_ty.isTuple()) {
|
||||
const ti = obj_ty.tuple_type;
|
||||
if (resolveTupleFieldIndex(ti, fa.field)) |idx| {
|
||||
return ti.field_types[idx];
|
||||
}
|
||||
}
|
||||
if (obj_ty.isStruct()) {
|
||||
if (self.lookupStructInfo(obj_ty.struct_type)) |info| {
|
||||
if (self.findNameIndex(info.field_names, fa.field)) |idx| {
|
||||
@@ -7688,6 +8137,20 @@ pub const CodeGen = struct {
|
||||
if (sl.struct_name) |sname| return .{ .struct_type = sname };
|
||||
return Type.s(64);
|
||||
},
|
||||
.tuple_literal => |tl| {
|
||||
const field_types = self.allocator.alloc(Type, tl.elements.len) catch return Type.s(64);
|
||||
for (tl.elements, 0..) |elem, i| {
|
||||
field_types[i] = self.inferType(elem.value);
|
||||
}
|
||||
const field_names: ?[]const []const u8 = if (tl.elements.len > 0 and tl.elements[0].name != null) blk: {
|
||||
const names = self.allocator.alloc([]const u8, tl.elements.len) catch return Type.s(64);
|
||||
for (tl.elements, 0..) |elem, i| {
|
||||
names[i] = elem.name orelse "";
|
||||
}
|
||||
break :blk names;
|
||||
} else null;
|
||||
return .{ .tuple_type = .{ .field_names = field_names, .field_types = field_types } };
|
||||
},
|
||||
.while_expr, .for_expr, .break_expr, .continue_expr => .void_type,
|
||||
else => Type.s(64),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user