Commit 2e6ce11eb2

Jacob G-W <jacoblevgw@gmail.com>
2021-08-19 04:22:12
stage2: implement shr and boilerplate for shl
This implements it in the llvm and c backends. x86_64 will have to be a little more work.
1 parent 7e7d67d
src/codegen/llvm/bindings.zig
@@ -290,6 +290,14 @@ pub const Builder = opaque {
     pub const getInsertBlock = LLVMGetInsertBlock;
     extern fn LLVMGetInsertBlock(Builder: *const Builder) *const BasicBlock;
 
+    pub const buildZExt = LLVMBuildZExt;
+    extern fn LLVMBuildZExt(
+        *const Builder,
+        Value: *const Value,
+        DestTy: *const Type,
+        Name: [*:0]const u8,
+    ) *const Value;
+
     pub const buildCall = LLVMBuildCall;
     extern fn LLVMBuildCall(
         *const Builder,
@@ -381,6 +389,15 @@ pub const Builder = opaque {
     pub const buildAnd = LLVMBuildAnd;
     extern fn LLVMBuildAnd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
 
+    pub const buildLShr = LLVMBuildLShr;
+    extern fn LLVMBuildLShr(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildAShr = LLVMBuildAShr;
+    extern fn LLVMBuildAShr(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildShl = LLVMBuildShl;
+    extern fn LLVMBuildShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
     pub const buildOr = LLVMBuildOr;
     extern fn LLVMBuildOr(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
 
src/codegen/c.zig
@@ -871,6 +871,9 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
             .bit_or     => try airBinOp(o, inst, " | "),
             .xor        => try airBinOp(o, inst, " ^ "),
 
+            .shr        => try airBinOp(o, inst, " >> "),
+            .shl        => try airBinOp(o, inst, " << "),
+
             .not        => try airNot(  o, inst),
 
             .optional_payload     => try airOptionalPayload(o, inst),
src/codegen/llvm.zig
@@ -993,6 +993,9 @@ pub const FuncGen = struct {
                 .bit_or, .bool_or   => try self.airOr(inst),
                 .xor                => try self.airXor(inst),
 
+                .shl                => try self.airShl(inst),
+                .shr                => try self.airShr(inst),
+
                 .cmp_eq  => try self.airCmp(inst, .eq),
                 .cmp_gt  => try self.airCmp(inst, .gt),
                 .cmp_gte => try self.airCmp(inst, .gte),
@@ -1736,6 +1739,35 @@ pub const FuncGen = struct {
         return self.builder.buildXor(lhs, rhs, "");
     }
 
+    fn airShl(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        return self.builder.buildShl(lhs, rhs, "");
+    }
+
+    fn airShr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        const lhs_type = self.air.typeOf(bin_op.lhs);
+        const tg = self.dg.module.getTarget();
+        const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
+            self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
+        else
+            rhs;
+
+        if (self.air.typeOfIndex(inst).isSignedInt()) {
+            return self.builder.buildAShr(lhs, casted_rhs, "");
+        } else {
+            return self.builder.buildLShr(lhs, casted_rhs, "");
+        }
+    }
+
     fn airIntCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         if (self.liveness.isUnused(inst))
             return null;
src/Air.zig
@@ -94,6 +94,12 @@ pub const Inst = struct {
         /// Result type is the same as both operands.
         /// Uses the `bin_op` field.
         bit_or,
+        /// Shift right. `>>`
+        /// Uses the `bin_op` field.
+        shr,
+        /// Shift left. `<<`
+        /// Uses the `bin_op` field.
+        shl,
         /// Bitwise XOR. `^`
         /// Uses the `bin_op` field.
         xor,
@@ -445,6 +451,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .xor,
         .ptr_add,
         .ptr_sub,
+        .shr,
+        .shl,
         => return air.typeOf(datas[inst].bin_op.lhs),
 
         .cmp_lt,
src/codegen.zig
@@ -822,6 +822,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .bit_and  => try self.airBitAnd(inst),
                     .bit_or   => try self.airBitOr(inst),
                     .xor      => try self.airXor(inst),
+                    .shr      => try self.airShr(inst),
+                    .shl      => try self.airShl(inst),
 
                     .alloc           => try self.airAlloc(inst),
                     .arg             => try self.airArg(inst),
@@ -1270,6 +1272,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
         }
 
+        fn airShl(self: *Self, inst: Air.Inst.Index) !void {
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+                .arm, .armeb => try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .shl),
+                else => return self.fail("TODO implement shl for {}", .{self.target.cpu.arch}),
+            };
+            return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+        }
+
+        fn airShr(self: *Self, inst: Air.Inst.Index) !void {
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+                .arm, .armeb => try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .shr),
+                else => return self.fail("TODO implement shr for {}", .{self.target.cpu.arch}),
+            };
+            return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+        }
+
         fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) !void {
             const ty_op = self.air.instructions.items(.data)[inst].ty_op;
             const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
src/Liveness.zig
@@ -249,6 +249,8 @@ fn analyzeInst(
         .ptr_slice_elem_val,
         .ptr_elem_val,
         .ptr_ptr_elem_val,
+        .shl,
+        .shr,
         => {
             const o = inst_datas[inst].bin_op;
             return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none });
src/print_air.zig
@@ -127,6 +127,8 @@ const Writer = struct {
             .ptr_slice_elem_val,
             .ptr_elem_val,
             .ptr_ptr_elem_val,
+            .shl,
+            .shr,
             => try w.writeBinOp(s, inst),
 
             .is_null,
src/Sema.zig
@@ -5303,8 +5303,25 @@ fn zirShr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
     const tracy = trace(@src());
     defer tracy.end();
 
-    _ = inst;
-    return sema.mod.fail(&block.base, sema.src, "TODO implement zirShr", .{});
+    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+    const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+    const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+    const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+    const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+    const lhs = sema.resolveInst(extra.lhs);
+    const rhs = sema.resolveInst(extra.rhs);
+
+    if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| {
+        if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| {
+            if (lhs_val.isUndef() or rhs_val.isUndef()) {
+                return sema.addConstUndef(sema.typeOf(lhs));
+            }
+            return sema.mod.fail(&block.base, src, "TODO implement comptime shr", .{});
+        }
+    }
+
+    try sema.requireRuntimeBlock(block, src);
+    return block.addBinOp(.shr, lhs, rhs);
 }
 
 fn zirBitwise(
@@ -6001,13 +6018,33 @@ fn zirTypeofElem(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile
 fn zirTypeofLog2IntType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const src = inst_data.src();
-    return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirTypeofLog2IntType", .{});
+    const operand = sema.resolveInst(inst_data.operand);
+    const operand_ty = sema.typeOf(operand);
+    return sema.log2IntType(block, operand_ty, src);
 }
 
 fn zirLog2IntType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const src = inst_data.src();
-    return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirLog2IntType", .{});
+    const operand = try sema.resolveType(block, src, inst_data.operand);
+    return sema.log2IntType(block, operand, src);
+}
+
+fn log2IntType(sema: *Sema, block: *Scope.Block, operand: Type, src: LazySrcLoc) CompileError!Air.Inst.Ref {
+    if (operand.zigTypeTag() != .Int) return sema.mod.fail(
+        &block.base,
+        src,
+        "bit shifting operation expected integer type, found '{}'",
+        .{operand},
+    );
+
+    var count: u16 = 0;
+    var s = operand.bitSize(sema.mod.getTarget()) - 1;
+    while (s != 0) : (s >>= 1) {
+        count += 1;
+    }
+    const res = try Module.makeIntType(sema.arena, .unsigned, count);
+    return sema.addType(res);
 }
 
 fn zirTypeofPeer(
test/stage2/cbe.zig
@@ -808,6 +808,20 @@ pub fn addCases(ctx: *TestContext) !void {
         });
     }
 
+    {
+        var case = ctx.exeUsingLlvmBackend("shift right", linux_x64);
+
+        case.addCompareOutput(
+            \\pub export fn main() void {
+            \\    var i: u32 = 16;
+            \\    assert(i >> 1, 8);
+            \\}
+            \\fn assert(a: u32, b: u32) void {
+            \\    if (a != b) unreachable;
+            \\}
+        , "");
+    }
+
     {
         var case = ctx.exeFromCompiledC("inferred error sets", .{});
 
test/stage2/llvm.zig
@@ -28,6 +28,20 @@ pub fn addCases(ctx: *TestContext) !void {
         , "");
     }
 
+    {
+        var case = ctx.exeUsingLlvmBackend("shift right", linux_x64);
+
+        case.addCompareOutput(
+            \\pub export fn main() void {
+            \\    var i: u32 = 16;
+            \\    assert(i >> 1, 8);
+            \\}
+            \\fn assert(a: u32, b: u32) void {
+            \\    if (a != b) unreachable;
+            \\}
+        , "");
+    }
+
     {
         var case = ctx.exeUsingLlvmBackend("llvm hello world", linux_x64);