Commit ddf14323ea

Andrew Kelley <andrew@ziglang.org>
2021-08-02 01:13:58
stage2: implement `@truncate`
1 parent 6ae0825
src/codegen/llvm/bindings.zig
@@ -423,6 +423,14 @@ pub const Builder = opaque {
         Idx: c_uint,
         Name: [*:0]const u8,
     ) *const Value;
+
+    pub const buildTrunc = LLVMBuildTrunc;
+    extern fn LLVMBuildTrunc(
+        *const Builder,
+        Val: *const Value,
+        DestTy: *const Type,
+        Name: [*:0]const u8,
+    ) *const Value;
 };
 
 pub const IntPredicate = enum(c_int) {
src/codegen/c.zig
@@ -900,6 +900,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
             .call             => try airCall(o, inst),
             .dbg_stmt         => try airDbgStmt(o, inst),
             .intcast          => try airIntCast(o, inst),
+            .trunc            => try airTrunc(o, inst),
             .bool_to_int      => try airBoolToInt(o, inst),
             .load             => try airLoad(o, inst),
             .ret              => try airRet(o, inst),
@@ -1038,7 +1039,7 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue {
         return CValue.none;
 
     const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const from = try o.resolveInst(ty_op.operand);
+    const operand = try o.resolveInst(ty_op.operand);
 
     const writer = o.writer();
     const inst_ty = o.air.typeOfIndex(inst);
@@ -1046,11 +1047,21 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue {
     try writer.writeAll(" = (");
     try o.dg.renderType(writer, inst_ty);
     try writer.writeAll(")");
-    try o.writeCValue(writer, from);
+    try o.writeCValue(writer, operand);
     try writer.writeAll(";\n");
     return local;
 }
 
+fn airTrunc(o: *Object, inst: Air.Inst.Index) !CValue {
+    if (o.liveness.isUnused(inst))
+        return CValue.none;
+
+    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
+    const operand = try o.resolveInst(ty_op.operand);
+    _ = operand;
+    return o.dg.fail("TODO: C backend: airTrunc", .{});
+}
+
 fn airBoolToInt(o: *Object, inst: Air.Inst.Index) !CValue {
     if (o.liveness.isUnused(inst))
         return CValue.none;
src/codegen/llvm.zig
@@ -960,6 +960,7 @@ pub const FuncGen = struct {
                 .call       => try self.airCall(inst),
                 .cond_br    => try self.airCondBr(inst),
                 .intcast    => try self.airIntCast(inst),
+                .trunc      => try self.airTrunc(inst),
                 .floatcast  => try self.airFloatCast(inst),
                 .ptrtoint   => try self.airPtrToInt(inst),
                 .load       => try self.airLoad(inst),
@@ -1615,6 +1616,16 @@ pub const FuncGen = struct {
         return self.builder.buildIntCast2(operand, try self.dg.llvmType(inst_ty), llvm.Bool.fromBool(signed), "");
     }
 
+    fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+        const operand = try self.resolveInst(ty_op.operand);
+        const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
+        return self.builder.buildTrunc(operand, dest_llvm_ty, "");
+    }
+
     fn airFloatCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         if (self.liveness.isUnused(inst))
             return null;
src/Air.zig
@@ -206,10 +206,16 @@ pub const Inst = struct {
         /// Convert from one float type to another.
         /// Uses the `ty_op` field.
         floatcast,
-        /// TODO audit uses of this. We should have explicit instructions for integer
-        /// widening and truncating.
+        /// Returns an integer with a different type than the operand. The new type may have
+        /// fewer, the same, or more bits than the operand type. However, the instruction
+        /// guarantees that the same integer value fits in both types.
+        /// See `trunc` for integer truncation.
         /// Uses the `ty_op` field.
         intcast,
+        /// Truncate higher bits from an integer, resulting in an integer with the same
+        /// sign but an equal or smaller number of bits.
+        /// Uses the `ty_op` field.
+        trunc,
         /// ?T => T. If the value is null, undefined behavior.
         /// Uses the `ty_op` field.
         optional_payload,
@@ -452,6 +458,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .load,
         .floatcast,
         .intcast,
+        .trunc,
         .optional_payload,
         .optional_payload_ptr,
         .wrap_optional,
src/codegen.zig
@@ -835,6 +835,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .dbg_stmt        => try self.airDbgStmt(inst),
                     .floatcast       => try self.airFloatCast(inst),
                     .intcast         => try self.airIntCast(inst),
+                    .trunc           => try self.airTrunc(inst),
                     .bool_to_int     => try self.airBoolToInt(inst),
                     .is_non_null     => try self.airIsNonNull(inst),
                     .is_non_null_ptr => try self.airIsNonNullPtr(inst),
@@ -1109,6 +1110,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
         }
 
+        fn airTrunc(self: *Self, inst: Air.Inst.Index) !void {
+            const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+            if (self.liveness.isUnused(inst))
+                return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none });
+
+            const operand = try self.resolveInst(ty_op.operand);
+            _ = operand;
+            const result: MCValue = switch (arch) {
+                else => return self.fail("TODO implement trunc for {}", .{self.target.cpu.arch}),
+            };
+            return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
+        }
+
         fn airBoolToInt(self: *Self, inst: Air.Inst.Index) !void {
             const un_op = self.air.instructions.items(.data)[inst].un_op;
             const operand = try self.resolveInst(un_op);
src/Liveness.zig
@@ -264,6 +264,7 @@ fn analyzeInst(
         .load,
         .floatcast,
         .intcast,
+        .trunc,
         .optional_payload,
         .optional_payload_ptr,
         .wrap_optional,
src/Module.zig
@@ -4033,244 +4033,6 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) Co
     return error.AnalysisFail;
 }
 
-pub fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
-    // TODO is this a performance issue? maybe we should try the operation without
-    // resorting to BigInt first.
-    var lhs_space: Value.BigIntSpace = undefined;
-    var rhs_space: Value.BigIntSpace = undefined;
-    const lhs_bigint = lhs.toBigInt(&lhs_space);
-    const rhs_bigint = rhs.toBigInt(&rhs_space);
-    const limbs = try allocator.alloc(
-        std.math.big.Limb,
-        std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
-    );
-    var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
-    result_bigint.add(lhs_bigint, rhs_bigint);
-    const result_limbs = result_bigint.limbs[0..result_bigint.len];
-
-    if (result_bigint.positive) {
-        return Value.Tag.int_big_positive.create(allocator, result_limbs);
-    } else {
-        return Value.Tag.int_big_negative.create(allocator, result_limbs);
-    }
-}
-
-pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
-    // TODO is this a performance issue? maybe we should try the operation without
-    // resorting to BigInt first.
-    var lhs_space: Value.BigIntSpace = undefined;
-    var rhs_space: Value.BigIntSpace = undefined;
-    const lhs_bigint = lhs.toBigInt(&lhs_space);
-    const rhs_bigint = rhs.toBigInt(&rhs_space);
-    const limbs = try allocator.alloc(
-        std.math.big.Limb,
-        std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
-    );
-    var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
-    result_bigint.sub(lhs_bigint, rhs_bigint);
-    const result_limbs = result_bigint.limbs[0..result_bigint.len];
-
-    if (result_bigint.positive) {
-        return Value.Tag.int_big_positive.create(allocator, result_limbs);
-    } else {
-        return Value.Tag.int_big_negative.create(allocator, result_limbs);
-    }
-}
-
-pub fn intDiv(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
-    // TODO is this a performance issue? maybe we should try the operation without
-    // resorting to BigInt first.
-    var lhs_space: Value.BigIntSpace = undefined;
-    var rhs_space: Value.BigIntSpace = undefined;
-    const lhs_bigint = lhs.toBigInt(&lhs_space);
-    const rhs_bigint = rhs.toBigInt(&rhs_space);
-    const limbs_q = try allocator.alloc(
-        std.math.big.Limb,
-        lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1,
-    );
-    const limbs_r = try allocator.alloc(
-        std.math.big.Limb,
-        lhs_bigint.limbs.len,
-    );
-    const limbs_buffer = try allocator.alloc(
-        std.math.big.Limb,
-        std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
-    );
-    var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined };
-    var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined };
-    result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null);
-    const result_limbs = result_q.limbs[0..result_q.len];
-
-    if (result_q.positive) {
-        return Value.Tag.int_big_positive.create(allocator, result_limbs);
-    } else {
-        return Value.Tag.int_big_negative.create(allocator, result_limbs);
-    }
-}
-
-pub fn intMul(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
-    // TODO is this a performance issue? maybe we should try the operation without
-    // resorting to BigInt first.
-    var lhs_space: Value.BigIntSpace = undefined;
-    var rhs_space: Value.BigIntSpace = undefined;
-    const lhs_bigint = lhs.toBigInt(&lhs_space);
-    const rhs_bigint = rhs.toBigInt(&rhs_space);
-    const limbs = try allocator.alloc(
-        std.math.big.Limb,
-        lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1,
-    );
-    var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
-    var limbs_buffer = try allocator.alloc(
-        std.math.big.Limb,
-        std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1),
-    );
-    defer allocator.free(limbs_buffer);
-    result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator);
-    const result_limbs = result_bigint.limbs[0..result_bigint.len];
-
-    if (result_bigint.positive) {
-        return Value.Tag.int_big_positive.create(allocator, result_limbs);
-    } else {
-        return Value.Tag.int_big_negative.create(allocator, result_limbs);
-    }
-}
-
-pub fn floatAdd(
-    arena: *Allocator,
-    float_type: Type,
-    src: LazySrcLoc,
-    lhs: Value,
-    rhs: Value,
-) !Value {
-    _ = src;
-    switch (float_type.tag()) {
-        .f16 => {
-            @panic("TODO add __trunctfhf2 to compiler-rt");
-            //const lhs_val = lhs.toFloat(f16);
-            //const rhs_val = rhs.toFloat(f16);
-            //return Value.Tag.float_16.create(arena, lhs_val + rhs_val);
-        },
-        .f32 => {
-            const lhs_val = lhs.toFloat(f32);
-            const rhs_val = rhs.toFloat(f32);
-            return Value.Tag.float_32.create(arena, lhs_val + rhs_val);
-        },
-        .f64 => {
-            const lhs_val = lhs.toFloat(f64);
-            const rhs_val = rhs.toFloat(f64);
-            return Value.Tag.float_64.create(arena, lhs_val + rhs_val);
-        },
-        .f128, .comptime_float, .c_longdouble => {
-            const lhs_val = lhs.toFloat(f128);
-            const rhs_val = rhs.toFloat(f128);
-            return Value.Tag.float_128.create(arena, lhs_val + rhs_val);
-        },
-        else => unreachable,
-    }
-}
-
-pub fn floatSub(
-    arena: *Allocator,
-    float_type: Type,
-    src: LazySrcLoc,
-    lhs: Value,
-    rhs: Value,
-) !Value {
-    _ = src;
-    switch (float_type.tag()) {
-        .f16 => {
-            @panic("TODO add __trunctfhf2 to compiler-rt");
-            //const lhs_val = lhs.toFloat(f16);
-            //const rhs_val = rhs.toFloat(f16);
-            //return Value.Tag.float_16.create(arena, lhs_val - rhs_val);
-        },
-        .f32 => {
-            const lhs_val = lhs.toFloat(f32);
-            const rhs_val = rhs.toFloat(f32);
-            return Value.Tag.float_32.create(arena, lhs_val - rhs_val);
-        },
-        .f64 => {
-            const lhs_val = lhs.toFloat(f64);
-            const rhs_val = rhs.toFloat(f64);
-            return Value.Tag.float_64.create(arena, lhs_val - rhs_val);
-        },
-        .f128, .comptime_float, .c_longdouble => {
-            const lhs_val = lhs.toFloat(f128);
-            const rhs_val = rhs.toFloat(f128);
-            return Value.Tag.float_128.create(arena, lhs_val - rhs_val);
-        },
-        else => unreachable,
-    }
-}
-
-pub fn floatDiv(
-    arena: *Allocator,
-    float_type: Type,
-    src: LazySrcLoc,
-    lhs: Value,
-    rhs: Value,
-) !Value {
-    _ = src;
-    switch (float_type.tag()) {
-        .f16 => {
-            @panic("TODO add __trunctfhf2 to compiler-rt");
-            //const lhs_val = lhs.toFloat(f16);
-            //const rhs_val = rhs.toFloat(f16);
-            //return Value.Tag.float_16.create(arena, lhs_val / rhs_val);
-        },
-        .f32 => {
-            const lhs_val = lhs.toFloat(f32);
-            const rhs_val = rhs.toFloat(f32);
-            return Value.Tag.float_32.create(arena, lhs_val / rhs_val);
-        },
-        .f64 => {
-            const lhs_val = lhs.toFloat(f64);
-            const rhs_val = rhs.toFloat(f64);
-            return Value.Tag.float_64.create(arena, lhs_val / rhs_val);
-        },
-        .f128, .comptime_float, .c_longdouble => {
-            const lhs_val = lhs.toFloat(f128);
-            const rhs_val = rhs.toFloat(f128);
-            return Value.Tag.float_128.create(arena, lhs_val / rhs_val);
-        },
-        else => unreachable,
-    }
-}
-
-pub fn floatMul(
-    arena: *Allocator,
-    float_type: Type,
-    src: LazySrcLoc,
-    lhs: Value,
-    rhs: Value,
-) !Value {
-    _ = src;
-    switch (float_type.tag()) {
-        .f16 => {
-            @panic("TODO add __trunctfhf2 to compiler-rt");
-            //const lhs_val = lhs.toFloat(f16);
-            //const rhs_val = rhs.toFloat(f16);
-            //return Value.Tag.float_16.create(arena, lhs_val * rhs_val);
-        },
-        .f32 => {
-            const lhs_val = lhs.toFloat(f32);
-            const rhs_val = rhs.toFloat(f32);
-            return Value.Tag.float_32.create(arena, lhs_val * rhs_val);
-        },
-        .f64 => {
-            const lhs_val = lhs.toFloat(f64);
-            const rhs_val = rhs.toFloat(f64);
-            return Value.Tag.float_64.create(arena, lhs_val * rhs_val);
-        },
-        .f128, .comptime_float, .c_longdouble => {
-            const lhs_val = lhs.toFloat(f128);
-            const rhs_val = rhs.toFloat(f128);
-            return Value.Tag.float_128.create(arena, lhs_val * rhs_val);
-        },
-        else => unreachable,
-    }
-}
-
 pub fn simplePtrType(
     arena: *Allocator,
     elem_ty: Type,
src/print_air.zig
@@ -151,6 +151,7 @@ const Writer = struct {
             .load,
             .floatcast,
             .intcast,
+            .trunc,
             .optional_payload,
             .optional_payload_ptr,
             .wrap_optional,
src/Sema.zig
@@ -3479,27 +3479,8 @@ fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
     const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs);
     const operand = sema.resolveInst(extra.rhs);
 
-    const dest_is_comptime_int = switch (dest_type.zigTypeTag()) {
-        .ComptimeInt => true,
-        .Int => false,
-        else => return sema.mod.fail(
-            &block.base,
-            dest_ty_src,
-            "expected integer type, found '{}'",
-            .{dest_type},
-        ),
-    };
-
-    const operand_ty = sema.typeOf(operand);
-    switch (operand_ty.zigTypeTag()) {
-        .ComptimeInt, .Int => {},
-        else => return sema.mod.fail(
-            &block.base,
-            operand_src,
-            "expected integer type, found '{}'",
-            .{operand_ty},
-        ),
-    }
+    const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_type);
+    _ = try sema.requireIntegerType(block, operand_src, sema.typeOf(operand));
 
     if (try sema.isComptimeKnown(block, operand_src, operand)) {
         return sema.coerce(block, dest_type, operand, operand_src);
@@ -4951,30 +4932,30 @@ fn analyzeArithmetic(
             const value = switch (zir_tag) {
                 .add => blk: {
                     const val = if (is_int)
-                        try Module.intAdd(sema.arena, lhs_val, rhs_val)
+                        try lhs_val.intAdd(rhs_val, sema.arena)
                     else
-                        try Module.floatAdd(sema.arena, scalar_type, src, lhs_val, rhs_val);
+                        try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena);
                     break :blk val;
                 },
                 .sub => blk: {
                     const val = if (is_int)
-                        try Module.intSub(sema.arena, lhs_val, rhs_val)
+                        try lhs_val.intSub(rhs_val, sema.arena)
                     else
-                        try Module.floatSub(sema.arena, scalar_type, src, lhs_val, rhs_val);
+                        try lhs_val.floatSub(rhs_val, scalar_type, sema.arena);
                     break :blk val;
                 },
                 .div => blk: {
                     const val = if (is_int)
-                        try Module.intDiv(sema.arena, lhs_val, rhs_val)
+                        try lhs_val.intDiv(rhs_val, sema.arena)
                     else
-                        try Module.floatDiv(sema.arena, scalar_type, src, lhs_val, rhs_val);
+                        try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena);
                     break :blk val;
                 },
                 .mul => blk: {
                     const val = if (is_int)
-                        try Module.intMul(sema.arena, lhs_val, rhs_val)
+                        try lhs_val.intMul(rhs_val, sema.arena)
                     else
-                        try Module.floatMul(sema.arena, scalar_type, src, lhs_val, rhs_val);
+                        try lhs_val.floatMul(rhs_val, scalar_type, sema.arena);
                     break :blk val;
                 },
                 else => return sema.mod.fail(&block.base, src, "TODO Implement arithmetic operand '{s}'", .{@tagName(zir_tag)}),
@@ -6173,7 +6154,62 @@ fn zirPtrCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
 fn zirTruncate(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
-    return sema.mod.fail(&block.base, src, "TODO: Sema.zirTruncate", .{});
+    const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+    const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+    const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+    const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs);
+    const operand = sema.resolveInst(extra.rhs);
+    const operand_ty = sema.typeOf(operand);
+    const mod = sema.mod;
+    const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_ty);
+    const src_is_comptime_int = try sema.requireIntegerType(block, operand_src, operand_ty);
+
+    if (dest_is_comptime_int) {
+        return sema.coerce(block, dest_ty, operand, operand_src);
+    }
+
+    const target = mod.getTarget();
+    const src_info = operand_ty.intInfo(target);
+    const dest_info = dest_ty.intInfo(target);
+
+    if (src_info.bits == 0 or dest_info.bits == 0) {
+        return sema.addConstant(dest_ty, Value.initTag(.zero));
+    }
+
+    if (!src_is_comptime_int) {
+        if (src_info.signedness != dest_info.signedness) {
+            return mod.fail(&block.base, operand_src, "expected {s} integer type, found '{}'", .{
+                @tagName(dest_info.signedness), operand_ty,
+            });
+        }
+        if (src_info.bits > 0 and src_info.bits < dest_info.bits) {
+            const msg = msg: {
+                const msg = try mod.errMsg(
+                    &block.base,
+                    src,
+                    "destination type '{}' has more bits than source type '{}'",
+                    .{ dest_ty, operand_ty },
+                );
+                errdefer msg.destroy(mod.gpa);
+                try mod.errNote(&block.base, dest_ty_src, msg, "destination type has {d} bits", .{
+                    dest_info.bits,
+                });
+                try mod.errNote(&block.base, operand_src, msg, "source type has {d} bits", .{
+                    src_info.bits,
+                });
+                break :msg msg;
+            };
+            return mod.failWithOwnedErrorMsg(&block.base, msg);
+        }
+    }
+
+    if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
+        if (val.isUndef()) return sema.addConstUndef(dest_ty);
+        return sema.addConstant(dest_ty, try val.intTrunc(sema.arena, dest_info.bits));
+    }
+
+    try sema.requireRuntimeBlock(block, src);
+    return block.addTyOp(.trunc, dest_ty, operand);
 }
 
 fn zirAlignCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -6594,6 +6630,14 @@ fn requireRuntimeBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void
     try sema.requireFunctionBlock(block, src);
 }
 
+fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !bool {
+    switch (ty.zigTypeTag()) {
+        .ComptimeInt => return true,
+        .Int => return false,
+        else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}),
+    }
+}
+
 fn validateVarType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void {
     if (!ty.isValidVarType(false)) {
         return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{ty});
src/value.zig
@@ -1407,6 +1407,244 @@ pub const Value = extern union {
         };
     }
 
+    pub fn intAdd(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+        // TODO is this a performance issue? maybe we should try the operation without
+        // resorting to BigInt first.
+        var lhs_space: Value.BigIntSpace = undefined;
+        var rhs_space: Value.BigIntSpace = undefined;
+        const lhs_bigint = lhs.toBigInt(&lhs_space);
+        const rhs_bigint = rhs.toBigInt(&rhs_space);
+        const limbs = try allocator.alloc(
+            std.math.big.Limb,
+            std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
+        );
+        var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
+        result_bigint.add(lhs_bigint, rhs_bigint);
+        const result_limbs = result_bigint.limbs[0..result_bigint.len];
+
+        if (result_bigint.positive) {
+            return Value.Tag.int_big_positive.create(allocator, result_limbs);
+        } else {
+            return Value.Tag.int_big_negative.create(allocator, result_limbs);
+        }
+    }
+
+    pub fn intSub(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+        // TODO is this a performance issue? maybe we should try the operation without
+        // resorting to BigInt first.
+        var lhs_space: Value.BigIntSpace = undefined;
+        var rhs_space: Value.BigIntSpace = undefined;
+        const lhs_bigint = lhs.toBigInt(&lhs_space);
+        const rhs_bigint = rhs.toBigInt(&rhs_space);
+        const limbs = try allocator.alloc(
+            std.math.big.Limb,
+            std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
+        );
+        var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
+        result_bigint.sub(lhs_bigint, rhs_bigint);
+        const result_limbs = result_bigint.limbs[0..result_bigint.len];
+
+        if (result_bigint.positive) {
+            return Value.Tag.int_big_positive.create(allocator, result_limbs);
+        } else {
+            return Value.Tag.int_big_negative.create(allocator, result_limbs);
+        }
+    }
+
+    pub fn intDiv(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+        // TODO is this a performance issue? maybe we should try the operation without
+        // resorting to BigInt first.
+        var lhs_space: Value.BigIntSpace = undefined;
+        var rhs_space: Value.BigIntSpace = undefined;
+        const lhs_bigint = lhs.toBigInt(&lhs_space);
+        const rhs_bigint = rhs.toBigInt(&rhs_space);
+        const limbs_q = try allocator.alloc(
+            std.math.big.Limb,
+            lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1,
+        );
+        const limbs_r = try allocator.alloc(
+            std.math.big.Limb,
+            lhs_bigint.limbs.len,
+        );
+        const limbs_buffer = try allocator.alloc(
+            std.math.big.Limb,
+            std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
+        );
+        var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined };
+        var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined };
+        result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null);
+        const result_limbs = result_q.limbs[0..result_q.len];
+
+        if (result_q.positive) {
+            return Value.Tag.int_big_positive.create(allocator, result_limbs);
+        } else {
+            return Value.Tag.int_big_negative.create(allocator, result_limbs);
+        }
+    }
+
+    pub fn intMul(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+        // TODO is this a performance issue? maybe we should try the operation without
+        // resorting to BigInt first.
+        var lhs_space: Value.BigIntSpace = undefined;
+        var rhs_space: Value.BigIntSpace = undefined;
+        const lhs_bigint = lhs.toBigInt(&lhs_space);
+        const rhs_bigint = rhs.toBigInt(&rhs_space);
+        const limbs = try allocator.alloc(
+            std.math.big.Limb,
+            lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1,
+        );
+        var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
+        var limbs_buffer = try allocator.alloc(
+            std.math.big.Limb,
+            std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1),
+        );
+        defer allocator.free(limbs_buffer);
+        result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator);
+        const result_limbs = result_bigint.limbs[0..result_bigint.len];
+
+        if (result_bigint.positive) {
+            return Value.Tag.int_big_positive.create(allocator, result_limbs);
+        } else {
+            return Value.Tag.int_big_negative.create(allocator, result_limbs);
+        }
+    }
+
+    pub fn intTrunc(val: Value, arena: *Allocator, bits: u16) !Value {
+        const x = val.toUnsignedInt(); // TODO: implement comptime truncate on big ints
+        if (bits == 64) return val;
+        const mask = (@as(u64, 1) << @intCast(u6, bits)) - 1;
+        const truncated = x & mask;
+        return Tag.int_u64.create(arena, truncated);
+    }
+
+    pub fn floatAdd(
+        lhs: Value,
+        rhs: Value,
+        float_type: Type,
+        arena: *Allocator,
+    ) !Value {
+        switch (float_type.tag()) {
+            .f16 => {
+                @panic("TODO add __trunctfhf2 to compiler-rt");
+                //const lhs_val = lhs.toFloat(f16);
+                //const rhs_val = rhs.toFloat(f16);
+                //return Value.Tag.float_16.create(arena, lhs_val + rhs_val);
+            },
+            .f32 => {
+                const lhs_val = lhs.toFloat(f32);
+                const rhs_val = rhs.toFloat(f32);
+                return Value.Tag.float_32.create(arena, lhs_val + rhs_val);
+            },
+            .f64 => {
+                const lhs_val = lhs.toFloat(f64);
+                const rhs_val = rhs.toFloat(f64);
+                return Value.Tag.float_64.create(arena, lhs_val + rhs_val);
+            },
+            .f128, .comptime_float, .c_longdouble => {
+                const lhs_val = lhs.toFloat(f128);
+                const rhs_val = rhs.toFloat(f128);
+                return Value.Tag.float_128.create(arena, lhs_val + rhs_val);
+            },
+            else => unreachable,
+        }
+    }
+
+    pub fn floatSub(
+        lhs: Value,
+        rhs: Value,
+        float_type: Type,
+        arena: *Allocator,
+    ) !Value {
+        switch (float_type.tag()) {
+            .f16 => {
+                @panic("TODO add __trunctfhf2 to compiler-rt");
+                //const lhs_val = lhs.toFloat(f16);
+                //const rhs_val = rhs.toFloat(f16);
+                //return Value.Tag.float_16.create(arena, lhs_val - rhs_val);
+            },
+            .f32 => {
+                const lhs_val = lhs.toFloat(f32);
+                const rhs_val = rhs.toFloat(f32);
+                return Value.Tag.float_32.create(arena, lhs_val - rhs_val);
+            },
+            .f64 => {
+                const lhs_val = lhs.toFloat(f64);
+                const rhs_val = rhs.toFloat(f64);
+                return Value.Tag.float_64.create(arena, lhs_val - rhs_val);
+            },
+            .f128, .comptime_float, .c_longdouble => {
+                const lhs_val = lhs.toFloat(f128);
+                const rhs_val = rhs.toFloat(f128);
+                return Value.Tag.float_128.create(arena, lhs_val - rhs_val);
+            },
+            else => unreachable,
+        }
+    }
+
+    pub fn floatDiv(
+        lhs: Value,
+        rhs: Value,
+        float_type: Type,
+        arena: *Allocator,
+    ) !Value {
+        switch (float_type.tag()) {
+            .f16 => {
+                @panic("TODO add __trunctfhf2 to compiler-rt");
+                //const lhs_val = lhs.toFloat(f16);
+                //const rhs_val = rhs.toFloat(f16);
+                //return Value.Tag.float_16.create(arena, lhs_val / rhs_val);
+            },
+            .f32 => {
+                const lhs_val = lhs.toFloat(f32);
+                const rhs_val = rhs.toFloat(f32);
+                return Value.Tag.float_32.create(arena, lhs_val / rhs_val);
+            },
+            .f64 => {
+                const lhs_val = lhs.toFloat(f64);
+                const rhs_val = rhs.toFloat(f64);
+                return Value.Tag.float_64.create(arena, lhs_val / rhs_val);
+            },
+            .f128, .comptime_float, .c_longdouble => {
+                const lhs_val = lhs.toFloat(f128);
+                const rhs_val = rhs.toFloat(f128);
+                return Value.Tag.float_128.create(arena, lhs_val / rhs_val);
+            },
+            else => unreachable,
+        }
+    }
+
+    pub fn floatMul(
+        lhs: Value,
+        rhs: Value,
+        float_type: Type,
+        arena: *Allocator,
+    ) !Value {
+        switch (float_type.tag()) {
+            .f16 => {
+                @panic("TODO add __trunctfhf2 to compiler-rt");
+                //const lhs_val = lhs.toFloat(f16);
+                //const rhs_val = rhs.toFloat(f16);
+                //return Value.Tag.float_16.create(arena, lhs_val * rhs_val);
+            },
+            .f32 => {
+                const lhs_val = lhs.toFloat(f32);
+                const rhs_val = rhs.toFloat(f32);
+                return Value.Tag.float_32.create(arena, lhs_val * rhs_val);
+            },
+            .f64 => {
+                const lhs_val = lhs.toFloat(f64);
+                const rhs_val = rhs.toFloat(f64);
+                return Value.Tag.float_64.create(arena, lhs_val * rhs_val);
+            },
+            .f128, .comptime_float, .c_longdouble => {
+                const lhs_val = lhs.toFloat(f128);
+                const rhs_val = rhs.toFloat(f128);
+                return Value.Tag.float_128.create(arena, lhs_val * rhs_val);
+            },
+            else => unreachable,
+        }
+    }
+
     /// This type is not copyable since it may contain pointers to its inner data.
     pub const Payload = struct {
         tag: Tag,
test/behavior/basic.zig
@@ -1,3 +1,6 @@
+const std = @import("std");
+const expect = std.testing.expect;
+
 // normal comment
 
 /// this is a documentation comment
@@ -7,3 +10,11 @@ fn emptyFunctionWithComments() void {}
 test "empty function with comments" {
     emptyFunctionWithComments();
 }
+
+test "truncate" {
+    try expect(testTruncate(0x10fd) == 0xfd);
+    comptime try expect(testTruncate(0x10fd) == 0xfd);
+}
+fn testTruncate(x: u32) u8 {
+    return @truncate(u8, x);
+}
test/behavior/misc.zig
@@ -5,13 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings;
 const mem = std.mem;
 const builtin = @import("builtin");
 
-test "truncate" {
-    try expect(testTruncate(0x10fd) == 0xfd);
-}
-fn testTruncate(x: u32) u8 {
-    return @truncate(u8, x);
-}
-
 fn first4KeysOfHomeRow() []const u8 {
     return "aoeu";
 }