Skip to content

Commit 33e77f1

Browse files
committed
stage2: implement @clz and @ctz
Also improve the LLVM backend to support lowering bigints to LLVM values. Moves over a bunch of math.zig test cases to the "passing for stage2" section.
1 parent 7efc2a0 commit 33e77f1

File tree

12 files changed

+379
-216
lines changed

12 files changed

+379
-216
lines changed

src/Air.zig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ pub const Inst = struct {
160160
/// Result type is the return type of the function being called.
161161
/// Uses the `pl_op` field with the `Call` payload. operand is the callee.
162162
call,
163+
/// Count leading zeroes of an integer according to its representation in twos complement.
164+
/// Result type will always be an unsigned integer big enough to fit the answer.
165+
/// Uses the `ty_op` field.
166+
clz,
167+
/// Count trailing zeroes of an integer according to its representation in twos complement.
168+
/// Result type will always be an unsigned integer big enough to fit the answer.
169+
/// Uses the `ty_op` field.
170+
ctz,
163171

164172
/// `<`. Result type is always bool.
165173
/// Uses the `bin_op` field.
@@ -669,6 +677,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
669677
.float_to_int,
670678
.int_to_float,
671679
.get_union_tag,
680+
.clz,
681+
.ctz,
672682
=> return air.getRefType(datas[inst].ty_op.ty),
673683

674684
.loop,

src/Liveness.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ fn analyzeInst(
304304
.float_to_int,
305305
.int_to_float,
306306
.get_union_tag,
307+
.clz,
308+
.ctz,
307309
=> {
308310
const o = inst_datas[inst].ty_op;
309311
return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });

src/Sema.zig

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4611,8 +4611,8 @@ fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
46114611
const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs);
46124612
const operand = sema.resolveInst(extra.rhs);
46134613

4614-
const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_type);
4615-
_ = try sema.requireIntegerType(block, operand_src, sema.typeOf(operand));
4614+
const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_type);
4615+
_ = try sema.checkIntType(block, operand_src, sema.typeOf(operand));
46164616

46174617
if (try sema.isComptimeKnown(block, operand_src, operand)) {
46184618
return sema.coerce(block, dest_type, operand, operand_src);
@@ -8384,7 +8384,7 @@ fn zirIntToFloat(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile
83848384
const operand = sema.resolveInst(extra.rhs);
83858385
const operand_ty = sema.typeOf(operand);
83868386

8387-
try sema.checkIntType(block, ty_src, dest_ty);
8387+
_ = try sema.checkIntType(block, ty_src, dest_ty);
83888388
try sema.checkFloatType(block, operand_src, operand_ty);
83898389

83908390
if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
@@ -8493,8 +8493,8 @@ fn zirTruncate(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
84938493
const operand = sema.resolveInst(extra.rhs);
84948494
const operand_ty = sema.typeOf(operand);
84958495
const mod = sema.mod;
8496-
const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_ty);
8497-
const src_is_comptime_int = try sema.requireIntegerType(block, operand_src, operand_ty);
8496+
const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_ty);
8497+
const src_is_comptime_int = try sema.checkIntType(block, operand_src, operand_ty);
84988498

84998499
if (dest_is_comptime_int) {
85008500
return sema.coerce(block, dest_ty, operand, operand_src);
@@ -8552,14 +8552,56 @@ fn zirAlignCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
85528552

85538553
fn zirClz(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
85548554
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
8555-
const src = inst_data.src();
8556-
return sema.mod.fail(&block.base, src, "TODO: Sema.zirClz", .{});
8555+
const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
8556+
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
8557+
const operand = sema.resolveInst(inst_data.operand);
8558+
const operand_ty = sema.typeOf(operand);
8559+
// TODO implement support for vectors
8560+
if (operand_ty.zigTypeTag() != .Int) {
8561+
return sema.mod.fail(&block.base, ty_src, "expected integer type, found '{}'", .{
8562+
operand_ty,
8563+
});
8564+
}
8565+
const target = sema.mod.getTarget();
8566+
const bits = operand_ty.intInfo(target).bits;
8567+
if (bits == 0) return Air.Inst.Ref.zero;
8568+
8569+
const result_ty = try Type.smallestUnsignedInt(sema.arena, bits);
8570+
8571+
const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
8572+
if (val.isUndef()) return sema.addConstUndef(result_ty);
8573+
return sema.addIntUnsigned(result_ty, val.clz(operand_ty, target));
8574+
} else operand_src;
8575+
8576+
try sema.requireRuntimeBlock(block, runtime_src);
8577+
return block.addTyOp(.clz, result_ty, operand);
85578578
}
85588579

85598580
fn zirCtz(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
85608581
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
8561-
const src = inst_data.src();
8562-
return sema.mod.fail(&block.base, src, "TODO: Sema.zirCtz", .{});
8582+
const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
8583+
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
8584+
const operand = sema.resolveInst(inst_data.operand);
8585+
const operand_ty = sema.typeOf(operand);
8586+
// TODO implement support for vectors
8587+
if (operand_ty.zigTypeTag() != .Int) {
8588+
return sema.mod.fail(&block.base, ty_src, "expected integer type, found '{}'", .{
8589+
operand_ty,
8590+
});
8591+
}
8592+
const target = sema.mod.getTarget();
8593+
const bits = operand_ty.intInfo(target).bits;
8594+
if (bits == 0) return Air.Inst.Ref.zero;
8595+
8596+
const result_ty = try Type.smallestUnsignedInt(sema.arena, bits);
8597+
8598+
const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
8599+
if (val.isUndef()) return sema.addConstUndef(result_ty);
8600+
return sema.mod.fail(&block.base, operand_src, "TODO: implement comptime @ctz", .{});
8601+
} else operand_src;
8602+
8603+
try sema.requireRuntimeBlock(block, runtime_src);
8604+
return block.addTyOp(.ctz, result_ty, operand);
85638605
}
85648606

85658607
fn zirPopCount(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -8616,17 +8658,12 @@ fn zirOffsetOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
86168658
return sema.mod.fail(&block.base, src, "TODO: Sema.zirOffsetOf", .{});
86178659
}
86188660

8619-
fn checkIntType(
8620-
sema: *Sema,
8621-
block: *Scope.Block,
8622-
ty_src: LazySrcLoc,
8623-
ty: Type,
8624-
) CompileError!void {
8661+
/// Returns `true` if the type was a comptime_int.
8662+
fn checkIntType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!bool {
86258663
switch (ty.zigTypeTag()) {
8626-
.ComptimeInt, .Int => {},
8627-
else => return sema.mod.fail(&block.base, ty_src, "expected integer type, found '{}'", .{
8628-
ty,
8629-
}),
8664+
.ComptimeInt => return true,
8665+
.Int => return false,
8666+
else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}),
86308667
}
86318668
}
86328669

@@ -9416,14 +9453,6 @@ fn requireRuntimeBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void
94169453
try sema.requireFunctionBlock(block, src);
94179454
}
94189455

9419-
fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !bool {
9420-
switch (ty.zigTypeTag()) {
9421-
.ComptimeInt => return true,
9422-
.Int => return false,
9423-
else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}),
9424-
}
9425-
}
9426-
94279456
/// Emit a compile error if type cannot be used for a runtime variable.
94289457
fn validateVarType(
94299458
sema: *Sema,

src/codegen.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
896896
.memset => try self.airMemset(inst),
897897
.set_union_tag => try self.airSetUnionTag(inst),
898898
.get_union_tag => try self.airGetUnionTag(inst),
899+
.clz => try self.airClz(inst),
900+
.ctz => try self.airCtz(inst),
899901

900902
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
901903
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -1606,6 +1608,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
16061608
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
16071609
}
16081610

1611+
fn airClz(self: *Self, inst: Air.Inst.Index) !void {
1612+
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1613+
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
1614+
else => return self.fail("TODO implement airClz for {}", .{self.target.cpu.arch}),
1615+
};
1616+
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1617+
}
1618+
1619+
fn airCtz(self: *Self, inst: Air.Inst.Index) !void {
1620+
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1621+
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
1622+
else => return self.fail("TODO implement airCtz for {}", .{self.target.cpu.arch}),
1623+
};
1624+
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1625+
}
1626+
16091627
fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
16101628
if (!self.liveness.operandDies(inst, op_index))
16111629
return false;

src/codegen/c.zig

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
962962
.memcpy => try airMemcpy(f, inst),
963963
.set_union_tag => try airSetUnionTag(f, inst),
964964
.get_union_tag => try airGetUnionTag(f, inst),
965+
.clz => try airBuiltinCall(f, inst, "clz"),
966+
.ctz => try airBuiltinCall(f, inst, "ctz"),
965967

966968
.int_to_float,
967969
.float_to_int,
@@ -2075,6 +2077,23 @@ fn airSimpleCast(f: *Function, inst: Air.Inst.Index) !CValue {
20752077
return local;
20762078
}
20772079

2080+
fn airBuiltinCall(f: *Function, inst: Air.Inst.Index, fn_name: [*:0]const u8) !CValue {
2081+
if (f.liveness.isUnused(inst)) return CValue.none;
2082+
2083+
const inst_ty = f.air.typeOfIndex(inst);
2084+
const local = try f.allocLocal(inst_ty, .Const);
2085+
const ty_op = f.air.instructions.items(.data)[inst].ty_op;
2086+
const writer = f.object.writer();
2087+
const operand = try f.resolveInst(ty_op.operand);
2088+
2089+
// TODO implement the function in zig.h and call it here
2090+
2091+
try writer.print(" = {s}(", .{fn_name});
2092+
try f.writeCValue(writer, operand);
2093+
try writer.writeAll(");\n");
2094+
return local;
2095+
}
2096+
20782097
fn airCmpxchg(f: *Function, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue {
20792098
const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
20802099
const extra = f.air.extraData(Air.Cmpxchg, ty_pl.payload).data;

src/codegen/llvm.zig

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -889,14 +889,14 @@ pub const DeclGen = struct {
889889
const llvm_type = try self.llvmType(tv.ty);
890890
if (bigint.eqZero()) return llvm_type.constNull();
891891

892-
if (bigint.limbs.len != 1) {
893-
return self.todo("implement bigger bigint", .{});
894-
}
895-
const llvm_int = llvm_type.constInt(bigint.limbs[0], .False);
892+
const unsigned_val = if (bigint.limbs.len == 1)
893+
llvm_type.constInt(bigint.limbs[0], .False)
894+
else
895+
llvm_type.constIntOfArbitraryPrecision(@intCast(c_uint, bigint.limbs.len), bigint.limbs.ptr);
896896
if (!bigint.positive) {
897-
return llvm.constNeg(llvm_int);
897+
return llvm.constNeg(unsigned_val);
898898
}
899-
return llvm_int;
899+
return unsigned_val;
900900
},
901901
.Enum => {
902902
const llvm_type = try self.llvmType(tv.ty);
@@ -1310,6 +1310,8 @@ pub const FuncGen = struct {
13101310
.memcpy => try self.airMemcpy(inst),
13111311
.set_union_tag => try self.airSetUnionTag(inst),
13121312
.get_union_tag => try self.airGetUnionTag(inst),
1313+
.clz => try self.airClzCtz(inst, "ctlz"),
1314+
.ctz => try self.airClzCtz(inst, "cttz"),
13131315

13141316
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
13151317
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -2699,6 +2701,41 @@ pub const FuncGen = struct {
26992701
return self.builder.buildExtractValue(un, 1, "");
27002702
}
27012703

2704+
fn airClzCtz(self: *FuncGen, inst: Air.Inst.Index, prefix: [*:0]const u8) !?*const llvm.Value {
2705+
if (self.liveness.isUnused(inst)) return null;
2706+
2707+
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2708+
const operand_ty = self.air.typeOf(ty_op.operand);
2709+
const operand = try self.resolveInst(ty_op.operand);
2710+
const target = self.dg.module.getTarget();
2711+
const bits = operand_ty.intInfo(target).bits;
2712+
2713+
var fn_name_buf: [100]u8 = undefined;
2714+
const llvm_fn_name = std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.i{d}", .{
2715+
prefix, bits,
2716+
}) catch unreachable;
2717+
const llvm_i1 = self.context.intType(1);
2718+
const fn_val = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
2719+
const operand_llvm_ty = try self.dg.llvmType(operand_ty);
2720+
const param_types = [_]*const llvm.Type{ operand_llvm_ty, llvm_i1 };
2721+
const fn_type = llvm.functionType(operand_llvm_ty, &param_types, param_types.len, .False);
2722+
break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
2723+
};
2724+
2725+
const params = [_]*const llvm.Value{ operand, llvm_i1.constNull() };
2726+
const wrong_size_result = self.builder.buildCall(fn_val, &params, params.len, "");
2727+
const result_ty = self.air.typeOfIndex(inst);
2728+
const result_llvm_ty = try self.dg.llvmType(result_ty);
2729+
const result_bits = result_ty.intInfo(target).bits;
2730+
if (bits > result_bits) {
2731+
return self.builder.buildTrunc(wrong_size_result, result_llvm_ty, "");
2732+
} else if (bits < result_bits) {
2733+
return self.builder.buildZExt(wrong_size_result, result_llvm_ty, "");
2734+
} else {
2735+
return wrong_size_result;
2736+
}
2737+
}
2738+
27022739
fn fieldPtr(
27032740
self: *FuncGen,
27042741
inst: Air.Inst.Index,

src/codegen/llvm/bindings.zig

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ pub const Type = opaque {
172172
pub const constInt = LLVMConstInt;
173173
extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: Bool) *const Value;
174174

175+
pub const constIntOfArbitraryPrecision = LLVMConstIntOfArbitraryPrecision;
176+
extern fn LLVMConstIntOfArbitraryPrecision(IntTy: *const Type, NumWords: c_uint, Words: [*]const u64) *const Value;
177+
175178
pub const constReal = LLVMConstReal;
176179
extern fn LLVMConstReal(RealTy: *const Type, N: f64) *const Value;
177180

@@ -300,7 +303,7 @@ extern fn LLVMGetInlineAsm(
300303
pub const functionType = LLVMFunctionType;
301304
extern fn LLVMFunctionType(
302305
ReturnType: *const Type,
303-
ParamTypes: [*]*const Type,
306+
ParamTypes: [*]const *const Type,
304307
ParamCount: c_uint,
305308
IsVarArg: Bool,
306309
) *const Type;
@@ -346,7 +349,7 @@ pub const Builder = opaque {
346349
extern fn LLVMBuildCall(
347350
*const Builder,
348351
Fn: *const Value,
349-
Args: [*]*const Value,
352+
Args: [*]const *const Value,
350353
NumArgs: c_uint,
351354
Name: [*:0]const u8,
352355
) *const Value;

src/print_air.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ const Writer = struct {
186186
.int_to_float,
187187
.float_to_int,
188188
.get_union_tag,
189+
.clz,
190+
.ctz,
189191
=> try w.writeTyOp(s, inst),
190192

191193
.block,

src/type.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3902,16 +3902,16 @@ pub const Type = extern union {
39023902
const bits = bits: {
39033903
if (max == 0) break :bits 0;
39043904
const base = std.math.log2(max);
3905-
const upper = (@as(u64, 1) << base) - 1;
3905+
const upper = (@as(u64, 1) << @intCast(u6, base)) - 1;
39063906
break :bits base + @boolToInt(upper < max);
39073907
};
3908-
return switch (bits) {
3908+
return switch (@intCast(u16, bits)) {
39093909
1 => initTag(.u1),
39103910
8 => initTag(.u8),
39113911
16 => initTag(.u16),
39123912
32 => initTag(.u32),
39133913
64 => initTag(.u64),
3914-
else => return Tag.int_unsigned.create(arena, bits),
3914+
else => |b| return Tag.int_unsigned.create(arena, b),
39153915
};
39163916
}
39173917
};

0 commit comments

Comments
 (0)