Commit 79bc5891c1

Andrew Kelley <andrew@ziglang.org>
2021-09-29 00:45:58
stage2: more arithmetic support
* AIR: add `mod` instruction for modulus division - Implement for LLVM backend * Sema: implement `@mod`, `@rem`, and `%`. * Sema: fix comptime switch evaluation * Sema: implement comptime shift left * Sema: fix the logic inside analyzeArithmetic to handle all the nuances between the different mathematical operations. - Implement comptime wrapping operations
1 parent 1e805df
src/codegen/c.zig
@@ -897,6 +897,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
             // that wrapping is UB.
             .div           => try airBinOp( f, inst, " / "),
             .rem           => try airBinOp( f, inst, " % "),
+            // TODO implement modulus division
+            .mod           => try airBinOp( f, inst, " mod "),
 
             .cmp_eq  => try airBinOp(f, inst, " == "),
             .cmp_gt  => try airBinOp(f, inst, " > "),
src/codegen/llvm.zig
@@ -1244,6 +1244,7 @@ pub const FuncGen = struct {
                 .mulwrap => try self.airMul(inst, true),
                 .div     => try self.airDiv(inst),
                 .rem     => try self.airRem(inst),
+                .mod     => try self.airMod(inst),
                 .ptr_add => try self.airPtrAdd(inst),
                 .ptr_sub => try self.airPtrSub(inst),
 
@@ -2095,6 +2096,34 @@ pub const FuncGen = struct {
         return self.builder.buildURem(lhs, rhs, "");
     }
 
+    fn airMod(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 inst_ty = self.air.typeOfIndex(inst);
+        const inst_llvm_ty = try self.dg.llvmType(inst_ty);
+
+        if (inst_ty.isRuntimeFloat()) {
+            const a = self.builder.buildFRem(lhs, rhs, "");
+            const b = self.builder.buildFAdd(a, rhs, "");
+            const c = self.builder.buildFRem(b, rhs, "");
+            const zero = inst_llvm_ty.constNull();
+            const ltz = self.builder.buildFCmp(.OLT, lhs, zero, "");
+            return self.builder.buildSelect(ltz, c, a, "");
+        }
+        if (inst_ty.isSignedInt()) {
+            const a = self.builder.buildSRem(lhs, rhs, "");
+            const b = self.builder.buildNSWAdd(a, rhs, "");
+            const c = self.builder.buildSRem(b, rhs, "");
+            const zero = inst_llvm_ty.constNull();
+            const ltz = self.builder.buildICmp(.SLT, lhs, zero, "");
+            return self.builder.buildSelect(ltz, c, a, "");
+        }
+        return self.builder.buildURem(lhs, rhs, "");
+    }
+
     fn airPtrAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         if (self.liveness.isUnused(inst))
             return null;
src/Air.zig
@@ -69,10 +69,16 @@ pub const Inst = struct {
         /// is the same as both operands.
         /// Uses the `bin_op` field.
         div,
-        /// Integer or float remainder.
-        /// Both operands are guaranteed to be the same type, and the result type is the same as both operands.
+        /// Integer or float remainder division.
+        /// Both operands are guaranteed to be the same type, and the result type
+        /// is the same as both operands.
         /// Uses the `bin_op` field.
         rem,
+        /// Integer or float modulus division.
+        /// Both operands are guaranteed to be the same type, and the result type
+        /// is the same as both operands.
+        /// Uses the `bin_op` field.
+        mod,
         /// Add an offset to a pointer, returning a new pointer.
         /// The offset is in element type units, not bytes.
         /// Wrapping is undefined behavior.
@@ -568,6 +574,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .mulwrap,
         .div,
         .rem,
+        .mod,
         .bit_and,
         .bit_or,
         .xor,
src/codegen.zig
@@ -832,6 +832,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .mulwrap       => try self.airMulWrap(inst),
                     .div           => try self.airDiv(inst),
                     .rem           => try self.airRem(inst),
+                    .mod           => try self.airMod(inst),
 
                     .cmp_lt  => try self.airCmp(inst, .lt),
                     .cmp_lte => try self.airCmp(inst, .lte),
@@ -1353,6 +1354,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
         }
 
+        fn airMod(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) {
+                else => return self.fail("TODO implement mod for {}", .{self.target.cpu.arch}),
+            };
+            return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+        }
+
         fn airBitAnd(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) {
src/Liveness.zig
@@ -232,6 +232,7 @@ fn analyzeInst(
         .mulwrap,
         .div,
         .rem,
+        .mod,
         .ptr_add,
         .ptr_sub,
         .bit_and,
src/print_air.zig
@@ -110,6 +110,7 @@ const Writer = struct {
             .mulwrap,
             .div,
             .rem,
+            .mod,
             .ptr_add,
             .ptr_sub,
             .bit_and,
src/Sema.zig
@@ -319,8 +319,6 @@ pub fn analyzeBody(
             .div_exact                    => try sema.zirDivExact(block, inst),
             .div_floor                    => try sema.zirDivFloor(block, inst),
             .div_trunc                    => try sema.zirDivTrunc(block, inst),
-            .mod                          => try sema.zirMod(block, inst),
-            .rem                          => try sema.zirRem(block, inst),
             .shl_exact                    => try sema.zirShlExact(block, inst),
             .shr_exact                    => try sema.zirShrExact(block, inst),
             .bit_offset_of                => try sema.zirBitOffsetOf(block, inst),
@@ -363,14 +361,16 @@ pub fn analyzeBody(
             .error_set_decl_anon => try sema.zirErrorSetDecl(block, inst, .anon),
             .error_set_decl_func => try sema.zirErrorSetDecl(block, inst, .func),
 
-            .add     => try sema.zirArithmetic(block, inst),
-            .addwrap => try sema.zirArithmetic(block, inst),
-            .div     => try sema.zirArithmetic(block, inst),
-            .mod_rem => try sema.zirArithmetic(block, inst),
-            .mul     => try sema.zirArithmetic(block, inst),
-            .mulwrap => try sema.zirArithmetic(block, inst),
-            .sub     => try sema.zirArithmetic(block, inst),
-            .subwrap => try sema.zirArithmetic(block, inst),
+            .add     => try sema.zirArithmetic(block, inst, .add),
+            .addwrap => try sema.zirArithmetic(block, inst, .addwrap),
+            .div     => try sema.zirArithmetic(block, inst, .div),
+            .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem),
+            .mod     => try sema.zirArithmetic(block, inst, .mod),
+            .rem     => try sema.zirArithmetic(block, inst, .rem),
+            .mul     => try sema.zirArithmetic(block, inst, .mul),
+            .mulwrap => try sema.zirArithmetic(block, inst, .mulwrap),
+            .sub     => try sema.zirArithmetic(block, inst, .sub),
+            .subwrap => try sema.zirArithmetic(block, inst, .subwrap),
 
             // Instructions that we know to *always* be noreturn based solely on their tag.
             // These functions match the return type of analyzeBody so that we can
@@ -886,6 +886,14 @@ fn failWithUseOfUndef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) Compile
     return sema.mod.fail(&block.base, src, "use of undefined value here causes undefined behavior", .{});
 }
 
+fn failWithDivideByZero(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) CompileError {
+    return sema.mod.fail(&block.base, src, "division by zero here causes undefined behavior", .{});
+}
+
+fn failWithModRemNegative(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, lhs_ty: Type, rhs_ty: Type) CompileError {
+    return sema.mod.fail(&block.base, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ lhs_ty, rhs_ty });
+}
+
 /// Appropriate to call when the coercion has already been done by result
 /// location semantics. Asserts the value fits in the provided `Int` type.
 /// Only supports `Int` types 64 bits or less.
@@ -2366,8 +2374,12 @@ fn resolveBlockBody(
     body: []const Zir.Inst.Index,
     merges: *Scope.Block.Merges,
 ) CompileError!Air.Inst.Ref {
-    _ = try sema.analyzeBody(child_block, body);
-    return sema.analyzeBlockBody(parent_block, src, child_block, merges);
+    if (child_block.is_comptime) {
+        return sema.resolveBody(child_block, body);
+    } else {
+        _ = try sema.analyzeBody(child_block, body);
+        return sema.analyzeBlockBody(parent_block, src, child_block, merges);
+    }
 }
 
 fn analyzeBlockBody(
@@ -5867,23 +5879,36 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
     defer tracy.end();
 
     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 shl", .{});
+    const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs);
+    const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs);
+
+    const runtime_src = if (maybe_lhs_val) |lhs_val| rs: {
+        const lhs_ty = sema.typeOf(lhs);
+
+        if (lhs_val.isUndef()) return sema.addConstUndef(lhs_ty);
+        const rhs_val = maybe_rhs_val orelse break :rs rhs_src;
+        if (rhs_val.isUndef()) return sema.addConstUndef(lhs_ty);
+
+        // If rhs is 0, return lhs without doing any calculations.
+        if (rhs_val.compareWithZero(.eq)) {
+            return sema.addConstant(lhs_ty, lhs_val);
         }
-    }
+        const val = try lhs_val.shl(rhs_val, sema.arena);
+        return sema.addConstant(lhs_ty, val);
+    } else rs: {
+        if (maybe_rhs_val) |rhs_val| {
+            if (rhs_val.isUndef()) return sema.addConstUndef(sema.typeOf(lhs));
+        }
+        break :rs lhs_src;
+    };
 
-    try sema.requireRuntimeBlock(block, src);
+    try sema.requireRuntimeBlock(block, runtime_src);
     return block.addBinOp(.shl, lhs, rhs);
 }
 
@@ -6141,11 +6166,15 @@ fn zirNegate(
     return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src);
 }
 
-fn zirArithmetic(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+fn zirArithmetic(
+    sema: *Sema,
+    block: *Scope.Block,
+    inst: Zir.Inst.Index,
+    zir_tag: Zir.Inst.Tag,
+) CompileError!Air.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const tag_override = block.sema.code.instructions.items(.tag)[inst];
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     sema.src = .{ .node_offset_bin_op = inst_data.src_node };
     const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
@@ -6154,7 +6183,7 @@ fn zirArithmetic(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile
     const lhs = sema.resolveInst(extra.lhs);
     const rhs = sema.resolveInst(extra.rhs);
 
-    return sema.analyzeArithmetic(block, tag_override, lhs, rhs, sema.src, lhs_src, rhs_src);
+    return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src);
 }
 
 fn zirOverflowArithmetic(
@@ -6187,6 +6216,7 @@ fn zirSatArithmetic(
 fn analyzeArithmetic(
     sema: *Sema,
     block: *Scope.Block,
+    /// TODO performance investigation: make this comptime?
     zir_tag: Zir.Inst.Tag,
     lhs: Air.Inst.Ref,
     rhs: Air.Inst.Ref,
@@ -6204,7 +6234,7 @@ fn analyzeArithmetic(
                 lhs_ty.arrayLen(), rhs_ty.arrayLen(),
             });
         }
-        return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{});
+        return sema.mod.fail(&block.base, src, "TODO implement support for vectors in Sema.analyzeArithmetic", .{});
     } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) {
         return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
             lhs_ty, rhs_ty,
@@ -6247,7 +6277,9 @@ fn analyzeArithmetic(
     };
 
     const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
-    const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } });
+    const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
+        .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
+    });
     const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
     const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
 
@@ -6267,86 +6299,499 @@ fn analyzeArithmetic(
         });
     }
 
-    if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
-        if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
-            if (lhs_val.isUndef() or rhs_val.isUndef()) {
-                return sema.addConstUndef(resolved_type);
-            }
-            // incase rhs is 0, simply return lhs without doing any calculations
-            // TODO Once division is implemented we should throw an error when dividing by 0.
-            if (rhs_val.compareWithZero(.eq)) {
-                switch (zir_tag) {
-                    .add, .addwrap, .sub, .subwrap => {
-                        return sema.addConstant(scalar_type, lhs_val);
-                    },
-                    else => {},
+    const target = sema.mod.getTarget();
+    const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs);
+    const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs);
+    const rs: struct { src: LazySrcLoc, air_tag: Air.Inst.Tag } = rs: {
+        switch (zir_tag) {
+            .add => {
+                // For integers:
+                // If either of the operands are zero, then the other operand is
+                // returned, even if it is undefined.
+                // If either of the operands are undefined, it's a compile error
+                // because there is a possible value for which the addition would
+                // overflow (max_int), causing illegal behavior.
+                // For floats: either operand being undef makes the result undef.
+                if (maybe_lhs_val) |lhs_val| {
+                    if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
+                        return casted_rhs;
+                    }
                 }
-            }
-
-            const value = switch (zir_tag) {
-                .add => blk: {
-                    const val = if (is_int)
-                        try lhs_val.intAdd(rhs_val, sema.arena)
-                    else
-                        try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena);
-                    break :blk val;
-                },
-                .sub => blk: {
-                    const val = if (is_int)
-                        try lhs_val.intSub(rhs_val, sema.arena)
-                    else
-                        try lhs_val.floatSub(rhs_val, scalar_type, sema.arena);
-                    break :blk val;
-                },
-                .div => blk: {
-                    const val = if (is_int)
-                        try lhs_val.intDiv(rhs_val, sema.arena)
-                    else
-                        try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena);
-                    break :blk val;
-                },
-                .mul => blk: {
-                    const val = if (is_int)
-                        try lhs_val.intMul(rhs_val, sema.arena)
-                    else
-                        try lhs_val.floatMul(rhs_val, scalar_type, sema.arena);
-                    break :blk val;
-                },
-                else => return sema.mod.fail(&block.base, src, "TODO implement comptime arithmetic for operand '{s}'", .{@tagName(zir_tag)}),
-            };
-
-            log.debug("{s}({}, {}) result: {}", .{ @tagName(zir_tag), lhs_val, rhs_val, value });
-
-            return sema.addConstant(scalar_type, value);
-        } else {
-            try sema.requireRuntimeBlock(block, rhs_src);
-        }
-    } else {
-        try sema.requireRuntimeBlock(block, lhs_src);
-    }
+                if (maybe_rhs_val) |rhs_val| {
+                    if (rhs_val.isUndef()) {
+                        if (is_int) {
+                            return sema.failWithUseOfUndef(block, rhs_src);
+                        } else {
+                            return sema.addConstUndef(scalar_type);
+                        }
+                    }
+                    if (rhs_val.compareWithZero(.eq)) {
+                        return casted_lhs;
+                    }
+                }
+                if (maybe_lhs_val) |lhs_val| {
+                    if (lhs_val.isUndef()) {
+                        if (is_int) {
+                            return sema.failWithUseOfUndef(block, lhs_src);
+                        } else {
+                            return sema.addConstUndef(scalar_type);
+                        }
+                    }
+                    if (maybe_rhs_val) |rhs_val| {
+                        if (is_int) {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.intAdd(rhs_val, sema.arena),
+                            );
+                        } else {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena),
+                            );
+                        }
+                    } else break :rs .{ .src = rhs_src, .air_tag = .add };
+                } else break :rs .{ .src = lhs_src, .air_tag = .add };
+            },
+            .addwrap => {
+                // Integers only; floats are checked above.
+                // If either of the operands are zero, then the other operand is
+                // returned, even if it is undefined.
+                // If either of the operands are undefined, the result is undefined.
+                if (maybe_lhs_val) |lhs_val| {
+                    if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
+                        return casted_rhs;
+                    }
+                }
+                if (maybe_rhs_val) |rhs_val| {
+                    if (rhs_val.isUndef()) {
+                        return sema.addConstUndef(scalar_type);
+                    }
+                    if (rhs_val.compareWithZero(.eq)) {
+                        return casted_lhs;
+                    }
+                    if (maybe_lhs_val) |lhs_val| {
+                        return sema.addConstant(
+                            scalar_type,
+                            try lhs_val.numberAddWrap(rhs_val, scalar_type, sema.arena, target),
+                        );
+                    } else break :rs .{ .src = lhs_src, .air_tag = .addwrap };
+                } else break :rs .{ .src = rhs_src, .air_tag = .addwrap };
+            },
+            .sub => {
+                // For integers:
+                // If the rhs is zero, then the other operand is
+                // returned, even if it is undefined.
+                // If either of the operands are undefined, it's a compile error
+                // because there is a possible value for which the subtraction would
+                // overflow, causing illegal behavior.
+                // For floats: either operand being undef makes the result undef.
+                if (maybe_rhs_val) |rhs_val| {
+                    if (rhs_val.isUndef()) {
+                        if (is_int) {
+                            return sema.failWithUseOfUndef(block, rhs_src);
+                        } else {
+                            return sema.addConstUndef(scalar_type);
+                        }
+                    }
+                    if (rhs_val.compareWithZero(.eq)) {
+                        return casted_lhs;
+                    }
+                }
+                if (maybe_lhs_val) |lhs_val| {
+                    if (lhs_val.isUndef()) {
+                        if (is_int) {
+                            return sema.failWithUseOfUndef(block, lhs_src);
+                        } else {
+                            return sema.addConstUndef(scalar_type);
+                        }
+                    }
+                    if (maybe_rhs_val) |rhs_val| {
+                        if (is_int) {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.intSub(rhs_val, sema.arena),
+                            );
+                        } else {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.floatSub(rhs_val, scalar_type, sema.arena),
+                            );
+                        }
+                    } else break :rs .{ .src = rhs_src, .air_tag = .sub };
+                } else break :rs .{ .src = lhs_src, .air_tag = .sub };
+            },
+            .subwrap => {
+                // Integers only; floats are checked above.
+                // If the RHS is zero, then the other operand is returned, even if it is undefined.
+                // If either of the operands are undefined, the result is undefined.
+                if (maybe_rhs_val) |rhs_val| {
+                    if (rhs_val.isUndef()) {
+                        return sema.addConstUndef(scalar_type);
+                    }
+                    if (rhs_val.compareWithZero(.eq)) {
+                        return casted_lhs;
+                    }
+                }
+                if (maybe_lhs_val) |lhs_val| {
+                    if (lhs_val.isUndef()) {
+                        return sema.addConstUndef(scalar_type);
+                    }
+                    if (maybe_rhs_val) |rhs_val| {
+                        return sema.addConstant(
+                            scalar_type,
+                            try lhs_val.numberSubWrap(rhs_val, scalar_type, sema.arena, target),
+                        );
+                    } else break :rs .{ .src = rhs_src, .air_tag = .subwrap };
+                } else break :rs .{ .src = lhs_src, .air_tag = .subwrap };
+            },
+            .div => {
+                // For integers:
+                // If the lhs is zero, then zero is returned regardless of rhs.
+                // If the rhs is zero, compile error for division by zero.
+                // If the rhs is undefined, compile error because there is a possible
+                // value (zero) for which the division would be illegal behavior.
+                // If the lhs is undefined:
+                //   * if lhs type is signed:
+                //     * if rhs is comptime-known and not -1, result is undefined
+                //     * if rhs is -1 or runtime-known, compile error because there is a
+                //        possible value (-min_int * -1)  for which division would be
+                //        illegal behavior.
+                //   * if lhs type is unsigned, undef is returned regardless of rhs.
+                // For floats:
+                // If the rhs is zero, compile error for division by zero.
+                // If the rhs is undefined, compile error because there is a possible
+                // value (zero) for which the division would be illegal behavior.
+                // If the lhs is undefined, result is undefined.
+                if (maybe_lhs_val) |lhs_val| {
+                    if (!lhs_val.isUndef()) {
+                        if (lhs_val.compareWithZero(.eq)) {
+                            return sema.addConstant(scalar_type, Value.zero);
+                        }
+                    }
+                }
+                if (maybe_rhs_val) |rhs_val| {
+                    if (rhs_val.isUndef()) {
+                        return sema.failWithUseOfUndef(block, rhs_src);
+                    }
+                    if (rhs_val.compareWithZero(.eq)) {
+                        return sema.failWithDivideByZero(block, rhs_src);
+                    }
+                }
+                if (maybe_lhs_val) |lhs_val| {
+                    if (lhs_val.isUndef()) {
+                        if (lhs_ty.isSignedInt() and rhs_ty.isSignedInt()) {
+                            if (maybe_rhs_val) |rhs_val| {
+                                if (rhs_val.compare(.neq, Value.negative_one, scalar_type)) {
+                                    return sema.addConstUndef(scalar_type);
+                                }
+                            }
+                            return sema.failWithUseOfUndef(block, rhs_src);
+                        }
+                        return sema.addConstUndef(scalar_type);
+                    }
 
-    if (zir_tag == .mod_rem) {
-        const dirty_lhs = lhs_ty.isSignedInt() or lhs_ty.isRuntimeFloat();
-        const dirty_rhs = rhs_ty.isSignedInt() or rhs_ty.isRuntimeFloat();
-        if (dirty_lhs or dirty_rhs) {
-            return sema.mod.fail(&block.base, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ lhs_ty, rhs_ty });
+                    if (maybe_rhs_val) |rhs_val| {
+                        if (is_int) {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.intDiv(rhs_val, sema.arena),
+                            );
+                        } else {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena),
+                            );
+                        }
+                    } else break :rs .{ .src = rhs_src, .air_tag = .div };
+                } else break :rs .{ .src = lhs_src, .air_tag = .div };
+            },
+            .mul => {
+                // For integers:
+                // If either of the operands are zero, the result is zero.
+                // If either of the operands are one, the result is the other
+                // operand, even if it is undefined.
+                // If either of the operands are undefined, it's a compile error
+                // because there is a possible value for which the addition would
+                // overflow (max_int), causing illegal behavior.
+                // For floats: either operand being undef makes the result undef.
+                if (maybe_lhs_val) |lhs_val| {
+                    if (!lhs_val.isUndef()) {
+                        if (lhs_val.compareWithZero(.eq)) {
+                            return sema.addConstant(scalar_type, Value.zero);
+                        }
+                        if (lhs_val.compare(.eq, Value.one, scalar_type)) {
+                            return casted_rhs;
+                        }
+                    }
+                }
+                if (maybe_rhs_val) |rhs_val| {
+                    if (rhs_val.isUndef()) {
+                        if (is_int) {
+                            return sema.failWithUseOfUndef(block, rhs_src);
+                        } else {
+                            return sema.addConstUndef(scalar_type);
+                        }
+                    }
+                    if (rhs_val.compareWithZero(.eq)) {
+                        return sema.addConstant(scalar_type, Value.zero);
+                    }
+                    if (rhs_val.compare(.eq, Value.one, scalar_type)) {
+                        return casted_lhs;
+                    }
+                    if (maybe_lhs_val) |lhs_val| {
+                        if (lhs_val.isUndef()) {
+                            if (is_int) {
+                                return sema.failWithUseOfUndef(block, lhs_src);
+                            } else {
+                                return sema.addConstUndef(scalar_type);
+                            }
+                        }
+                        if (is_int) {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.intMul(rhs_val, sema.arena),
+                            );
+                        } else {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.floatMul(rhs_val, scalar_type, sema.arena),
+                            );
+                        }
+                    } else break :rs .{ .src = lhs_src, .air_tag = .mul };
+                } else break :rs .{ .src = rhs_src, .air_tag = .mul };
+            },
+            .mulwrap => {
+                // Integers only; floats are handled above.
+                // If either of the operands are zero, the result is zero.
+                // If either of the operands are one, the result is the other
+                // operand, even if it is undefined.
+                // If either of the operands are undefined, the result is undefined.
+                if (maybe_lhs_val) |lhs_val| {
+                    if (!lhs_val.isUndef()) {
+                        if (lhs_val.compareWithZero(.eq)) {
+                            return sema.addConstant(scalar_type, Value.zero);
+                        }
+                        if (lhs_val.compare(.eq, Value.one, scalar_type)) {
+                            return casted_rhs;
+                        }
+                    }
+                }
+                if (maybe_rhs_val) |rhs_val| {
+                    if (rhs_val.isUndef()) {
+                        return sema.addConstUndef(scalar_type);
+                    }
+                    if (rhs_val.compareWithZero(.eq)) {
+                        return sema.addConstant(scalar_type, Value.zero);
+                    }
+                    if (rhs_val.compare(.eq, Value.one, scalar_type)) {
+                        return casted_lhs;
+                    }
+                    if (maybe_lhs_val) |lhs_val| {
+                        if (lhs_val.isUndef()) {
+                            return sema.addConstUndef(scalar_type);
+                        }
+                        return sema.addConstant(
+                            scalar_type,
+                            try lhs_val.numberMulWrap(rhs_val, scalar_type, sema.arena, target),
+                        );
+                    } else break :rs .{ .src = lhs_src, .air_tag = .mulwrap };
+                } else break :rs .{ .src = rhs_src, .air_tag = .mulwrap };
+            },
+            .mod_rem => {
+                // For integers:
+                // Either operand being undef is a compile error because there exists
+                // a possible value (TODO what is it?) that would invoke illegal behavior.
+                // TODO: can lhs zero be handled better?
+                // TODO: can lhs undef be handled better?
+                //
+                // For floats:
+                // If the rhs is zero, compile error for division by zero.
+                // If the rhs is undefined, compile error because there is a possible
+                // value (zero) for which the division would be illegal behavior.
+                // If the lhs is undefined, result is undefined.
+                //
+                // For either one: if the result would be different between @mod and @rem,
+                // then emit a compile error saying you have to pick one.
+                if (is_int) {
+                    if (maybe_lhs_val) |lhs_val| {
+                        if (lhs_val.isUndef()) {
+                            return sema.failWithUseOfUndef(block, lhs_src);
+                        }
+                        if (lhs_val.compareWithZero(.lt)) {
+                            return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
+                        }
+                    } else if (lhs_ty.isSignedInt()) {
+                        return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
+                    }
+                    if (maybe_rhs_val) |rhs_val| {
+                        if (rhs_val.isUndef()) {
+                            return sema.failWithUseOfUndef(block, rhs_src);
+                        }
+                        if (rhs_val.compareWithZero(.eq)) {
+                            return sema.failWithDivideByZero(block, rhs_src);
+                        }
+                        if (rhs_val.compareWithZero(.lt)) {
+                            return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
+                        }
+                        if (maybe_lhs_val) |lhs_val| {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.intRem(rhs_val, sema.arena),
+                            );
+                        }
+                        break :rs .{ .src = lhs_src, .air_tag = .rem };
+                    } else if (rhs_ty.isSignedInt()) {
+                        return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
+                    } else {
+                        break :rs .{ .src = rhs_src, .air_tag = .rem };
+                    }
+                }
+                // float operands
+                if (maybe_rhs_val) |rhs_val| {
+                    if (rhs_val.isUndef()) {
+                        return sema.failWithUseOfUndef(block, rhs_src);
+                    }
+                    if (rhs_val.compareWithZero(.eq)) {
+                        return sema.failWithDivideByZero(block, rhs_src);
+                    }
+                    if (rhs_val.compareWithZero(.lt)) {
+                        return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
+                    }
+                    if (maybe_lhs_val) |lhs_val| {
+                        if (lhs_val.isUndef() or lhs_val.compareWithZero(.lt)) {
+                            return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
+                        }
+                        return sema.addConstant(
+                            scalar_type,
+                            try lhs_val.floatRem(rhs_val, sema.arena),
+                        );
+                    } else {
+                        return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
+                    }
+                } else {
+                    return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
+                }
+            },
+            .rem => {
+                // For integers:
+                // Either operand being undef is a compile error because there exists
+                // a possible value (TODO what is it?) that would invoke illegal behavior.
+                // TODO: can lhs zero be handled better?
+                // TODO: can lhs undef be handled better?
+                //
+                // For floats:
+                // If the rhs is zero, compile error for division by zero.
+                // If the rhs is undefined, compile error because there is a possible
+                // value (zero) for which the division would be illegal behavior.
+                // If the lhs is undefined, result is undefined.
+                if (is_int) {
+                    if (maybe_lhs_val) |lhs_val| {
+                        if (lhs_val.isUndef()) {
+                            return sema.failWithUseOfUndef(block, lhs_src);
+                        }
+                    }
+                    if (maybe_rhs_val) |rhs_val| {
+                        if (rhs_val.isUndef()) {
+                            return sema.failWithUseOfUndef(block, rhs_src);
+                        }
+                        if (rhs_val.compareWithZero(.eq)) {
+                            return sema.failWithDivideByZero(block, rhs_src);
+                        }
+                        if (maybe_lhs_val) |lhs_val| {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.intRem(rhs_val, sema.arena),
+                            );
+                        }
+                        break :rs .{ .src = lhs_src, .air_tag = .rem };
+                    } else {
+                        break :rs .{ .src = rhs_src, .air_tag = .rem };
+                    }
+                }
+                // float operands
+                if (maybe_rhs_val) |rhs_val| {
+                    if (rhs_val.isUndef()) {
+                        return sema.failWithUseOfUndef(block, rhs_src);
+                    }
+                    if (rhs_val.compareWithZero(.eq)) {
+                        return sema.failWithDivideByZero(block, rhs_src);
+                    }
+                }
+                if (maybe_lhs_val) |lhs_val| {
+                    if (lhs_val.isUndef()) {
+                        return sema.addConstUndef(scalar_type);
+                    }
+                    if (maybe_rhs_val) |rhs_val| {
+                        return sema.addConstant(
+                            scalar_type,
+                            try lhs_val.floatRem(rhs_val, sema.arena),
+                        );
+                    } else break :rs .{ .src = rhs_src, .air_tag = .rem };
+                } else break :rs .{ .src = lhs_src, .air_tag = .rem };
+            },
+            .mod => {
+                // For integers:
+                // Either operand being undef is a compile error because there exists
+                // a possible value (TODO what is it?) that would invoke illegal behavior.
+                // TODO: can lhs zero be handled better?
+                // TODO: can lhs undef be handled better?
+                //
+                // For floats:
+                // If the rhs is zero, compile error for division by zero.
+                // If the rhs is undefined, compile error because there is a possible
+                // value (zero) for which the division would be illegal behavior.
+                // If the lhs is undefined, result is undefined.
+                if (is_int) {
+                    if (maybe_lhs_val) |lhs_val| {
+                        if (lhs_val.isUndef()) {
+                            return sema.failWithUseOfUndef(block, lhs_src);
+                        }
+                    }
+                    if (maybe_rhs_val) |rhs_val| {
+                        if (rhs_val.isUndef()) {
+                            return sema.failWithUseOfUndef(block, rhs_src);
+                        }
+                        if (rhs_val.compareWithZero(.eq)) {
+                            return sema.failWithDivideByZero(block, rhs_src);
+                        }
+                        if (maybe_lhs_val) |lhs_val| {
+                            return sema.addConstant(
+                                scalar_type,
+                                try lhs_val.intMod(rhs_val, sema.arena),
+                            );
+                        }
+                        break :rs .{ .src = lhs_src, .air_tag = .mod };
+                    } else {
+                        break :rs .{ .src = rhs_src, .air_tag = .mod };
+                    }
+                }
+                // float operands
+                if (maybe_rhs_val) |rhs_val| {
+                    if (rhs_val.isUndef()) {
+                        return sema.failWithUseOfUndef(block, rhs_src);
+                    }
+                    if (rhs_val.compareWithZero(.eq)) {
+                        return sema.failWithDivideByZero(block, rhs_src);
+                    }
+                }
+                if (maybe_lhs_val) |lhs_val| {
+                    if (lhs_val.isUndef()) {
+                        return sema.addConstUndef(scalar_type);
+                    }
+                    if (maybe_rhs_val) |rhs_val| {
+                        return sema.addConstant(
+                            scalar_type,
+                            try lhs_val.floatMod(rhs_val, sema.arena),
+                        );
+                    } else break :rs .{ .src = rhs_src, .air_tag = .mod };
+                } else break :rs .{ .src = lhs_src, .air_tag = .mod };
+            },
+            else => unreachable,
         }
-    }
-
-    const air_tag: Air.Inst.Tag = switch (zir_tag) {
-        .add => .add,
-        .addwrap => .addwrap,
-        .sub => .sub,
-        .subwrap => .subwrap,
-        .mul => .mul,
-        .mulwrap => .mulwrap,
-        .div => .div,
-        .mod_rem => .rem,
-        .rem => .rem,
-        else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for operand '{s}'", .{@tagName(zir_tag)}),
     };
 
-    return block.addBinOp(air_tag, casted_lhs, casted_rhs);
+    try sema.requireRuntimeBlock(block, rs.src);
+    return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs);
 }
 
 fn zirLoad(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -7401,7 +7846,7 @@ fn analyzeRet(
 fn floatOpAllowed(tag: Zir.Inst.Tag) bool {
     // extend this swich as additional operators are implemented
     return switch (tag) {
-        .add, .sub, .mul, .div => true,
+        .add, .sub, .mul, .div, .mod, .rem, .mod_rem => true,
         else => false,
     };
 }
@@ -8068,16 +8513,6 @@ fn zirDivTrunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
     return sema.mod.fail(&block.base, src, "TODO: Sema.zirDivTrunc", .{});
 }
 
-fn zirMod(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.zirMod", .{});
-}
-
-fn zirRem(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
-    return sema.zirArithmetic(block, inst);
-}
-
 fn zirShlExact(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();
src/value.zig
@@ -1616,6 +1616,34 @@ pub const Value = extern union {
         return result;
     }
 
+    /// Supports both floats and ints; handles undefined.
+    pub fn numberMulWrap(
+        lhs: Value,
+        rhs: Value,
+        ty: Type,
+        arena: *Allocator,
+        target: Target,
+    ) !Value {
+        if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+
+        if (ty.isAnyFloat()) {
+            return floatMul(lhs, rhs, ty, arena);
+        }
+        const result = try intMul(lhs, rhs, arena);
+
+        const max = try ty.maxInt(arena, target);
+        if (compare(result, .gt, max, ty)) {
+            @panic("TODO comptime wrapping integer multiplication");
+        }
+
+        const min = try ty.minInt(arena, target);
+        if (compare(result, .lt, min, ty)) {
+            @panic("TODO comptime wrapping integer multiplication");
+        }
+
+        return result;
+    }
+
     /// Supports both floats and ints; handles undefined.
     pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value {
         if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
@@ -1840,6 +1868,82 @@ pub const Value = extern union {
         }
     }
 
+    pub fn intRem(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_r.limbs[0..result_r.len];
+
+        if (result_r.positive) {
+            return Value.Tag.int_big_positive.create(allocator, result_limbs);
+        } else {
+            return Value.Tag.int_big_negative.create(allocator, result_limbs);
+        }
+    }
+
+    pub fn intMod(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.divFloor(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null);
+        const result_limbs = result_r.limbs[0..result_r.len];
+
+        if (result_r.positive) {
+            return Value.Tag.int_big_positive.create(allocator, result_limbs);
+        } else {
+            return Value.Tag.int_big_negative.create(allocator, result_limbs);
+        }
+    }
+
+    pub fn floatRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+        _ = lhs;
+        _ = rhs;
+        _ = allocator;
+        @panic("TODO implement Value.floatRem");
+    }
+
+    pub fn floatMod(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+        _ = lhs;
+        _ = rhs;
+        _ = allocator;
+        @panic("TODO implement Value.floatMod");
+    }
+
     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.
@@ -1875,6 +1979,31 @@ pub const Value = extern union {
         return Tag.int_u64.create(arena, truncated);
     }
 
+    pub fn shl(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;
+        const lhs_bigint = lhs.toBigInt(&lhs_space);
+        const shift = rhs.toUnsignedInt();
+        const limbs = try allocator.alloc(
+            std.math.big.Limb,
+            lhs_bigint.limbs.len + (shift / (@sizeOf(std.math.big.Limb) * 8)) + 1,
+        );
+        var result_bigint = BigIntMutable{
+            .limbs = limbs,
+            .positive = undefined,
+            .len = undefined,
+        };
+        result_bigint.shiftLeft(lhs_bigint, shift);
+        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 shr(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
         // TODO is this a performance issue? maybe we should try the operation without
         // resorting to BigInt first.
@@ -2227,4 +2356,13 @@ pub const Value = extern union {
         /// are possible without using an allocator.
         limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb,
     };
+
+    pub const zero = initTag(.zero);
+    pub const one = initTag(.one);
+    pub const negative_one: Value = .{ .ptr_otherwise = &negative_one_payload.base };
+};
+
+var negative_one_payload: Value.Payload.I64 = .{
+    .base = .{ .tag = .int_i64 },
+    .data = -1,
 };
src/Zir.zig
@@ -395,17 +395,6 @@ pub const Inst = struct {
         /// Merge two error sets into one, `E1 || E2`.
         /// Uses the `pl_node` field with payload `Bin`.
         merge_error_sets,
-        /// Ambiguously remainder division or modulus. If the computation would possibly have
-        /// a different value depending on whether the operation is remainder division or modulus,
-        /// a compile error is emitted. Otherwise the computation is performed.
-        /// Uses the `pl_node` union field. Payload is `Bin`.
-        mod_rem,
-        /// Arithmetic multiplication. Asserts no integer overflow.
-        /// Uses the `pl_node` union field. Payload is `Bin`.
-        mul,
-        /// Twos complement wrapping integer multiplication.
-        /// Uses the `pl_node` union field. Payload is `Bin`.
-        mulwrap,
         /// Turns an R-Value into a const L-Value. In other words, it takes a value,
         /// stores it in a memory location, and returns a const pointer to it. If the value
         /// is `comptime`, the memory location is global static constant data. Otherwise,
@@ -828,6 +817,17 @@ pub const Inst = struct {
         /// Implements the `@rem` builtin.
         /// Uses the `pl_node` union field with payload `Bin`.
         rem,
+        /// Ambiguously remainder division or modulus. If the computation would possibly have
+        /// a different value depending on whether the operation is remainder division or modulus,
+        /// a compile error is emitted. Otherwise the computation is performed.
+        /// Uses the `pl_node` union field. Payload is `Bin`.
+        mod_rem,
+        /// Arithmetic multiplication. Asserts no integer overflow.
+        /// Uses the `pl_node` union field. Payload is `Bin`.
+        mul,
+        /// Twos complement wrapping integer multiplication.
+        /// Uses the `pl_node` union field. Payload is `Bin`.
+        mulwrap,
 
         /// Integer shift-left. Zeroes are shifted in from the right hand side.
         /// Uses the `pl_node` union field. Payload is `Bin`.
test/behavior/math.zig
@@ -6,171 +6,6 @@ const maxInt = std.math.maxInt;
 const minInt = std.math.minInt;
 const mem = std.mem;
 
-test "division" {
-    try testDivision();
-    comptime try testDivision();
-}
-fn testDivision() !void {
-    try expect(div(u32, 13, 3) == 4);
-    try expect(div(f16, 1.0, 2.0) == 0.5);
-    try expect(div(f32, 1.0, 2.0) == 0.5);
-
-    try expect(divExact(u32, 55, 11) == 5);
-    try expect(divExact(i32, -55, 11) == -5);
-    try expect(divExact(f16, 55.0, 11.0) == 5.0);
-    try expect(divExact(f16, -55.0, 11.0) == -5.0);
-    try expect(divExact(f32, 55.0, 11.0) == 5.0);
-    try expect(divExact(f32, -55.0, 11.0) == -5.0);
-
-    try expect(divFloor(i32, 5, 3) == 1);
-    try expect(divFloor(i32, -5, 3) == -2);
-    try expect(divFloor(f16, 5.0, 3.0) == 1.0);
-    try expect(divFloor(f16, -5.0, 3.0) == -2.0);
-    try expect(divFloor(f32, 5.0, 3.0) == 1.0);
-    try expect(divFloor(f32, -5.0, 3.0) == -2.0);
-    try expect(divFloor(i32, -0x80000000, -2) == 0x40000000);
-    try expect(divFloor(i32, 0, -0x80000000) == 0);
-    try expect(divFloor(i32, -0x40000001, 0x40000000) == -2);
-    try expect(divFloor(i32, -0x80000000, 1) == -0x80000000);
-    try expect(divFloor(i32, 10, 12) == 0);
-    try expect(divFloor(i32, -14, 12) == -2);
-    try expect(divFloor(i32, -2, 12) == -1);
-
-    try expect(divTrunc(i32, 5, 3) == 1);
-    try expect(divTrunc(i32, -5, 3) == -1);
-    try expect(divTrunc(f16, 5.0, 3.0) == 1.0);
-    try expect(divTrunc(f16, -5.0, 3.0) == -1.0);
-    try expect(divTrunc(f32, 5.0, 3.0) == 1.0);
-    try expect(divTrunc(f32, -5.0, 3.0) == -1.0);
-    try expect(divTrunc(f64, 5.0, 3.0) == 1.0);
-    try expect(divTrunc(f64, -5.0, 3.0) == -1.0);
-    try expect(divTrunc(i32, 10, 12) == 0);
-    try expect(divTrunc(i32, -14, 12) == -1);
-    try expect(divTrunc(i32, -2, 12) == 0);
-
-    try expect(mod(i32, 10, 12) == 10);
-    try expect(mod(i32, -14, 12) == 10);
-    try expect(mod(i32, -2, 12) == 10);
-
-    comptime {
-        try expect(
-            1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600,
-        );
-        try expect(
-            @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600,
-        );
-        try expect(
-            1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2,
-        );
-        try expect(
-            @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2,
-        );
-        try expect(
-            @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2,
-        );
-        try expect(
-            @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2,
-        );
-        try expect(
-            4126227191251978491697987544882340798050766755606969681711 % 10 == 1,
-        );
-    }
-}
-fn div(comptime T: type, a: T, b: T) T {
-    return a / b;
-}
-fn divExact(comptime T: type, a: T, b: T) T {
-    return @divExact(a, b);
-}
-fn divFloor(comptime T: type, a: T, b: T) T {
-    return @divFloor(a, b);
-}
-fn divTrunc(comptime T: type, a: T, b: T) T {
-    return @divTrunc(a, b);
-}
-fn mod(comptime T: type, a: T, b: T) T {
-    return @mod(a, b);
-}
-
-test "@addWithOverflow" {
-    var result: u8 = undefined;
-    try expect(@addWithOverflow(u8, 250, 100, &result));
-    try expect(!@addWithOverflow(u8, 100, 150, &result));
-    try expect(result == 250);
-}
-
-// TODO test mulWithOverflow
-// TODO test subWithOverflow
-
-test "@shlWithOverflow" {
-    var result: u16 = undefined;
-    try expect(@shlWithOverflow(u16, 0b0010111111111111, 3, &result));
-    try expect(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result));
-    try expect(result == 0b1011111111111100);
-}
-
-test "@*WithOverflow with u0 values" {
-    var result: u0 = undefined;
-    try expect(!@addWithOverflow(u0, 0, 0, &result));
-    try expect(!@subWithOverflow(u0, 0, 0, &result));
-    try expect(!@mulWithOverflow(u0, 0, 0, &result));
-    try expect(!@shlWithOverflow(u0, 0, 0, &result));
-}
-
-test "@clz" {
-    try testClz();
-    comptime try testClz();
-}
-
-fn testClz() !void {
-    try expect(@clz(u8, 0b10001010) == 0);
-    try expect(@clz(u8, 0b00001010) == 4);
-    try expect(@clz(u8, 0b00011010) == 3);
-    try expect(@clz(u8, 0b00000000) == 8);
-    try expect(@clz(u128, 0xffffffffffffffff) == 64);
-    try expect(@clz(u128, 0x10000000000000000) == 63);
-}
-
-test "@clz vectors" {
-    try testClzVectors();
-    comptime try testClzVectors();
-}
-
-fn testClzVectors() !void {
-    @setEvalBranchQuota(10_000);
-    try expectEqual(@clz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 0)));
-    try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00001010))), @splat(64, @as(u4, 4)));
-    try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00011010))), @splat(64, @as(u4, 3)));
-    try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8)));
-    try expectEqual(@clz(u128, @splat(64, @as(u128, 0xffffffffffffffff))), @splat(64, @as(u8, 64)));
-    try expectEqual(@clz(u128, @splat(64, @as(u128, 0x10000000000000000))), @splat(64, @as(u8, 63)));
-}
-
-test "@ctz" {
-    try testCtz();
-    comptime try testCtz();
-}
-
-fn testCtz() !void {
-    try expect(@ctz(u8, 0b10100000) == 5);
-    try expect(@ctz(u8, 0b10001010) == 1);
-    try expect(@ctz(u8, 0b00000000) == 8);
-    try expect(@ctz(u16, 0b00000000) == 16);
-}
-
-test "@ctz vectors" {
-    try testClzVectors();
-    comptime try testClzVectors();
-}
-
-fn testCtzVectors() !void {
-    @setEvalBranchQuota(10_000);
-    try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10100000))), @splat(64, @as(u4, 5)));
-    try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 1)));
-    try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8)));
-    try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16)));
-}
-
 test "assignment operators" {
     var i: u32 = 0;
     i += 5;
@@ -218,686 +53,3 @@ fn testThreeExprInARow(f: bool, t: bool) !void {
 fn assertFalse(b: bool) !void {
     try expect(!b);
 }
-
-test "const number literal" {
-    const one = 1;
-    const eleven = ten + one;
-
-    try expect(eleven == 11);
-}
-const ten = 10;
-
-test "unsigned wrapping" {
-    try testUnsignedWrappingEval(maxInt(u32));
-    comptime try testUnsignedWrappingEval(maxInt(u32));
-}
-fn testUnsignedWrappingEval(x: u32) !void {
-    const zero = x +% 1;
-    try expect(zero == 0);
-    const orig = zero -% 1;
-    try expect(orig == maxInt(u32));
-}
-
-test "signed wrapping" {
-    try testSignedWrappingEval(maxInt(i32));
-    comptime try testSignedWrappingEval(maxInt(i32));
-}
-fn testSignedWrappingEval(x: i32) !void {
-    const min_val = x +% 1;
-    try expect(min_val == minInt(i32));
-    const max_val = min_val -% 1;
-    try expect(max_val == maxInt(i32));
-}
-
-test "signed negation wrapping" {
-    try testSignedNegationWrappingEval(minInt(i16));
-    comptime try testSignedNegationWrappingEval(minInt(i16));
-}
-fn testSignedNegationWrappingEval(x: i16) !void {
-    try expect(x == -32768);
-    const neg = -%x;
-    try expect(neg == -32768);
-}
-
-test "unsigned negation wrapping" {
-    try testUnsignedNegationWrappingEval(1);
-    comptime try testUnsignedNegationWrappingEval(1);
-}
-fn testUnsignedNegationWrappingEval(x: u16) !void {
-    try expect(x == 1);
-    const neg = -%x;
-    try expect(neg == maxInt(u16));
-}
-
-test "unsigned 64-bit division" {
-    try test_u64_div();
-    comptime try test_u64_div();
-}
-fn test_u64_div() !void {
-    const result = divWithResult(1152921504606846976, 34359738365);
-    try expect(result.quotient == 33554432);
-    try expect(result.remainder == 100663296);
-}
-fn divWithResult(a: u64, b: u64) DivResult {
-    return DivResult{
-        .quotient = a / b,
-        .remainder = a % b,
-    };
-}
-const DivResult = struct {
-    quotient: u64,
-    remainder: u64,
-};
-
-test "binary not" {
-    try expect(comptime x: {
-        break :x ~@as(u16, 0b1010101010101010) == 0b0101010101010101;
-    });
-    try expect(comptime x: {
-        break :x ~@as(u64, 2147483647) == 18446744071562067968;
-    });
-    try testBinaryNot(0b1010101010101010);
-}
-
-fn testBinaryNot(x: u16) !void {
-    try expect(~x == 0b0101010101010101);
-}
-
-test "small int addition" {
-    var x: u2 = 0;
-    try expect(x == 0);
-
-    x += 1;
-    try expect(x == 1);
-
-    x += 1;
-    try expect(x == 2);
-
-    x += 1;
-    try expect(x == 3);
-
-    var result: @TypeOf(x) = 3;
-    try expect(@addWithOverflow(@TypeOf(x), x, 1, &result));
-
-    try expect(result == 0);
-}
-
-test "float equality" {
-    const x: f64 = 0.012;
-    const y: f64 = x + 1.0;
-
-    try testFloatEqualityImpl(x, y);
-    comptime try testFloatEqualityImpl(x, y);
-}
-
-fn testFloatEqualityImpl(x: f64, y: f64) !void {
-    const y2 = x + 1.0;
-    try expect(y == y2);
-}
-
-test "allow signed integer division/remainder when values are comptime known and positive or exact" {
-    try expect(5 / 3 == 1);
-    try expect(-5 / -3 == 1);
-    try expect(-6 / 3 == -2);
-
-    try expect(5 % 3 == 2);
-    try expect(-6 % 3 == 0);
-}
-
-test "hex float literal parsing" {
-    comptime try expect(0x1.0 == 1.0);
-}
-
-test "quad hex float literal parsing in range" {
-    const a = 0x1.af23456789bbaaab347645365cdep+5;
-    const b = 0x1.dedafcff354b6ae9758763545432p-9;
-    const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534;
-    const d = 0x1.edcbff8ad76ab5bf46463233214fp-435;
-    if (false) {
-        a;
-        b;
-        c;
-        d;
-    }
-}
-
-test "quad hex float literal parsing accurate" {
-    const a: f128 = 0x1.1111222233334444555566667777p+0;
-
-    // implied 1 is dropped, with an exponent of 0 (0x3fff) after biasing.
-    const expected: u128 = 0x3fff1111222233334444555566667777;
-    try expect(@bitCast(u128, a) == expected);
-
-    // non-normalized
-    const b: f128 = 0x11.111222233334444555566667777p-4;
-    try expect(@bitCast(u128, b) == expected);
-
-    const S = struct {
-        fn doTheTest() !void {
-            {
-                var f: f128 = 0x1.2eab345678439abcdefea56782346p+5;
-                try expect(@bitCast(u128, f) == 0x40042eab345678439abcdefea5678234);
-            }
-            {
-                var f: f128 = 0x1.edcb34a235253948765432134674fp-1;
-                try expect(@bitCast(u128, f) == 0x3ffeedcb34a235253948765432134674);
-            }
-            {
-                var f: f128 = 0x1.353e45674d89abacc3a2ebf3ff4ffp-50;
-                try expect(@bitCast(u128, f) == 0x3fcd353e45674d89abacc3a2ebf3ff50);
-            }
-            {
-                var f: f128 = 0x1.ed8764648369535adf4be3214567fp-9;
-                try expect(@bitCast(u128, f) == 0x3ff6ed8764648369535adf4be3214568);
-            }
-            const exp2ft = [_]f64{
-                0x1.6a09e667f3bcdp-1,
-                0x1.7a11473eb0187p-1,
-                0x1.8ace5422aa0dbp-1,
-                0x1.9c49182a3f090p-1,
-                0x1.ae89f995ad3adp-1,
-                0x1.c199bdd85529cp-1,
-                0x1.d5818dcfba487p-1,
-                0x1.ea4afa2a490dap-1,
-                0x1.0000000000000p+0,
-                0x1.0b5586cf9890fp+0,
-                0x1.172b83c7d517bp+0,
-                0x1.2387a6e756238p+0,
-                0x1.306fe0a31b715p+0,
-                0x1.3dea64c123422p+0,
-                0x1.4bfdad5362a27p+0,
-                0x1.5ab07dd485429p+0,
-                0x1.8p23,
-                0x1.62e430p-1,
-                0x1.ebfbe0p-3,
-                0x1.c6b348p-5,
-                0x1.3b2c9cp-7,
-                0x1.0p127,
-                -0x1.0p-149,
-            };
-
-            const answers = [_]u64{
-                0x3fe6a09e667f3bcd,
-                0x3fe7a11473eb0187,
-                0x3fe8ace5422aa0db,
-                0x3fe9c49182a3f090,
-                0x3feae89f995ad3ad,
-                0x3fec199bdd85529c,
-                0x3fed5818dcfba487,
-                0x3feea4afa2a490da,
-                0x3ff0000000000000,
-                0x3ff0b5586cf9890f,
-                0x3ff172b83c7d517b,
-                0x3ff2387a6e756238,
-                0x3ff306fe0a31b715,
-                0x3ff3dea64c123422,
-                0x3ff4bfdad5362a27,
-                0x3ff5ab07dd485429,
-                0x4168000000000000,
-                0x3fe62e4300000000,
-                0x3fcebfbe00000000,
-                0x3fac6b3480000000,
-                0x3f83b2c9c0000000,
-                0x47e0000000000000,
-                0xb6a0000000000000,
-            };
-
-            for (exp2ft) |x, i| {
-                try expect(@bitCast(u64, x) == answers[i]);
-            }
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "underscore separator parsing" {
-    try expect(0_0_0_0 == 0);
-    try expect(1_234_567 == 1234567);
-    try expect(001_234_567 == 1234567);
-    try expect(0_0_1_2_3_4_5_6_7 == 1234567);
-
-    try expect(0b0_0_0_0 == 0);
-    try expect(0b1010_1010 == 0b10101010);
-    try expect(0b0000_1010_1010 == 0b10101010);
-    try expect(0b1_0_1_0_1_0_1_0 == 0b10101010);
-
-    try expect(0o0_0_0_0 == 0);
-    try expect(0o1010_1010 == 0o10101010);
-    try expect(0o0000_1010_1010 == 0o10101010);
-    try expect(0o1_0_1_0_1_0_1_0 == 0o10101010);
-
-    try expect(0x0_0_0_0 == 0);
-    try expect(0x1010_1010 == 0x10101010);
-    try expect(0x0000_1010_1010 == 0x10101010);
-    try expect(0x1_0_1_0_1_0_1_0 == 0x10101010);
-
-    try expect(123_456.789_000e1_0 == 123456.789000e10);
-    try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10);
-
-    try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10);
-    try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10);
-}
-
-test "hex float literal within range" {
-    const a = 0x1.0p16383;
-    const b = 0x0.1p16387;
-    const c = 0x1.0p-16382;
-    if (false) {
-        a;
-        b;
-        c;
-    }
-}
-
-test "truncating shift left" {
-    try testShlTrunc(maxInt(u16));
-    comptime try testShlTrunc(maxInt(u16));
-}
-fn testShlTrunc(x: u16) !void {
-    const shifted = x << 1;
-    try expect(shifted == 65534);
-}
-
-test "truncating shift right" {
-    try testShrTrunc(maxInt(u16));
-    comptime try testShrTrunc(maxInt(u16));
-}
-fn testShrTrunc(x: u16) !void {
-    const shifted = x >> 1;
-    try expect(shifted == 32767);
-}
-
-test "exact shift left" {
-    try testShlExact(0b00110101);
-    comptime try testShlExact(0b00110101);
-}
-fn testShlExact(x: u8) !void {
-    const shifted = @shlExact(x, 2);
-    try expect(shifted == 0b11010100);
-}
-
-test "exact shift right" {
-    try testShrExact(0b10110100);
-    comptime try testShrExact(0b10110100);
-}
-fn testShrExact(x: u8) !void {
-    const shifted = @shrExact(x, 2);
-    try expect(shifted == 0b00101101);
-}
-
-test "shift left/right on u0 operand" {
-    const S = struct {
-        fn doTheTest() !void {
-            var x: u0 = 0;
-            var y: u0 = 0;
-            try expectEqual(@as(u0, 0), x << 0);
-            try expectEqual(@as(u0, 0), x >> 0);
-            try expectEqual(@as(u0, 0), x << y);
-            try expectEqual(@as(u0, 0), x >> y);
-            try expectEqual(@as(u0, 0), @shlExact(x, 0));
-            try expectEqual(@as(u0, 0), @shrExact(x, 0));
-            try expectEqual(@as(u0, 0), @shlExact(x, y));
-            try expectEqual(@as(u0, 0), @shrExact(x, y));
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "comptime_int addition" {
-    comptime {
-        try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950);
-        try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380);
-    }
-}
-
-test "comptime_int multiplication" {
-    comptime {
-        try expect(
-            45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567,
-        );
-        try expect(
-            594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016,
-        );
-    }
-}
-
-test "comptime_int shifting" {
-    comptime {
-        try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000);
-    }
-}
-
-test "comptime_int multi-limb shift and mask" {
-    comptime {
-        var a = 0xefffffffa0000001eeeeeeefaaaaaaab;
-
-        try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab);
-        a >>= 32;
-        try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef);
-        a >>= 32;
-        try expect(@as(u32, a & 0xffffffff) == 0xa0000001);
-        a >>= 32;
-        try expect(@as(u32, a & 0xffffffff) == 0xefffffff);
-        a >>= 32;
-
-        try expect(a == 0);
-    }
-}
-
-test "comptime_int multi-limb partial shift right" {
-    comptime {
-        var a = 0x1ffffffffeeeeeeee;
-        a >>= 16;
-        try expect(a == 0x1ffffffffeeee);
-    }
-}
-
-test "xor" {
-    try test_xor();
-    comptime try test_xor();
-}
-
-fn test_xor() !void {
-    try expect(0xFF ^ 0x00 == 0xFF);
-    try expect(0xF0 ^ 0x0F == 0xFF);
-    try expect(0xFF ^ 0xF0 == 0x0F);
-    try expect(0xFF ^ 0x0F == 0xF0);
-    try expect(0xFF ^ 0xFF == 0x00);
-}
-
-test "comptime_int xor" {
-    comptime {
-        try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
-        try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
-        try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF);
-        try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000);
-        try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000);
-        try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
-        try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF);
-        try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000);
-    }
-}
-
-test "f128" {
-    try test_f128();
-    comptime try test_f128();
-}
-
-fn make_f128(x: f128) f128 {
-    return x;
-}
-
-fn test_f128() !void {
-    try expect(@sizeOf(f128) == 16);
-    try expect(make_f128(1.0) == 1.0);
-    try expect(make_f128(1.0) != 1.1);
-    try expect(make_f128(1.0) > 0.9);
-    try expect(make_f128(1.0) >= 0.9);
-    try expect(make_f128(1.0) >= 1.0);
-    try should_not_be_zero(1.0);
-}
-
-fn should_not_be_zero(x: f128) !void {
-    try expect(x != 0.0);
-}
-
-test "comptime float rem int" {
-    comptime {
-        var x = @as(f32, 1) % 2;
-        try expect(x == 1.0);
-    }
-}
-
-test "remainder division" {
-    comptime try remdiv(f16);
-    comptime try remdiv(f32);
-    comptime try remdiv(f64);
-    comptime try remdiv(f128);
-    try remdiv(f16);
-    try remdiv(f64);
-    try remdiv(f128);
-}
-
-fn remdiv(comptime T: type) !void {
-    try expect(@as(T, 1) == @as(T, 1) % @as(T, 2));
-    try expect(@as(T, 1) == @as(T, 7) % @as(T, 3));
-}
-
-test "@sqrt" {
-    try testSqrt(f64, 12.0);
-    comptime try testSqrt(f64, 12.0);
-    try testSqrt(f32, 13.0);
-    comptime try testSqrt(f32, 13.0);
-    try testSqrt(f16, 13.0);
-    comptime try testSqrt(f16, 13.0);
-
-    const x = 14.0;
-    const y = x * x;
-    const z = @sqrt(y);
-    comptime try expect(z == x);
-}
-
-fn testSqrt(comptime T: type, x: T) !void {
-    try expect(@sqrt(x * x) == x);
-}
-
-test "@fabs" {
-    try testFabs(f128, 12.0);
-    comptime try testFabs(f128, 12.0);
-    try testFabs(f64, 12.0);
-    comptime try testFabs(f64, 12.0);
-    try testFabs(f32, 12.0);
-    comptime try testFabs(f32, 12.0);
-    try testFabs(f16, 12.0);
-    comptime try testFabs(f16, 12.0);
-
-    const x = 14.0;
-    const y = -x;
-    const z = @fabs(y);
-    comptime try expectEqual(x, z);
-}
-
-fn testFabs(comptime T: type, x: T) !void {
-    const y = -x;
-    const z = @fabs(y);
-    try expectEqual(x, z);
-}
-
-test "@floor" {
-    // FIXME: Generates a floorl function call
-    // testFloor(f128, 12.0);
-    comptime try testFloor(f128, 12.0);
-    try testFloor(f64, 12.0);
-    comptime try testFloor(f64, 12.0);
-    try testFloor(f32, 12.0);
-    comptime try testFloor(f32, 12.0);
-    try testFloor(f16, 12.0);
-    comptime try testFloor(f16, 12.0);
-
-    const x = 14.0;
-    const y = x + 0.7;
-    const z = @floor(y);
-    comptime try expectEqual(x, z);
-}
-
-fn testFloor(comptime T: type, x: T) !void {
-    const y = x + 0.6;
-    const z = @floor(y);
-    try expectEqual(x, z);
-}
-
-test "@ceil" {
-    // FIXME: Generates a ceill function call
-    //testCeil(f128, 12.0);
-    comptime try testCeil(f128, 12.0);
-    try testCeil(f64, 12.0);
-    comptime try testCeil(f64, 12.0);
-    try testCeil(f32, 12.0);
-    comptime try testCeil(f32, 12.0);
-    try testCeil(f16, 12.0);
-    comptime try testCeil(f16, 12.0);
-
-    const x = 14.0;
-    const y = x - 0.7;
-    const z = @ceil(y);
-    comptime try expectEqual(x, z);
-}
-
-fn testCeil(comptime T: type, x: T) !void {
-    const y = x - 0.8;
-    const z = @ceil(y);
-    try expectEqual(x, z);
-}
-
-test "@trunc" {
-    // FIXME: Generates a truncl function call
-    //testTrunc(f128, 12.0);
-    comptime try testTrunc(f128, 12.0);
-    try testTrunc(f64, 12.0);
-    comptime try testTrunc(f64, 12.0);
-    try testTrunc(f32, 12.0);
-    comptime try testTrunc(f32, 12.0);
-    try testTrunc(f16, 12.0);
-    comptime try testTrunc(f16, 12.0);
-
-    const x = 14.0;
-    const y = x + 0.7;
-    const z = @trunc(y);
-    comptime try expectEqual(x, z);
-}
-
-fn testTrunc(comptime T: type, x: T) !void {
-    {
-        const y = x + 0.8;
-        const z = @trunc(y);
-        try expectEqual(x, z);
-    }
-
-    {
-        const y = -x - 0.8;
-        const z = @trunc(y);
-        try expectEqual(-x, z);
-    }
-}
-
-test "@round" {
-    // FIXME: Generates a roundl function call
-    //testRound(f128, 12.0);
-    comptime try testRound(f128, 12.0);
-    try testRound(f64, 12.0);
-    comptime try testRound(f64, 12.0);
-    try testRound(f32, 12.0);
-    comptime try testRound(f32, 12.0);
-    try testRound(f16, 12.0);
-    comptime try testRound(f16, 12.0);
-
-    const x = 14.0;
-    const y = x + 0.4;
-    const z = @round(y);
-    comptime try expectEqual(x, z);
-}
-
-fn testRound(comptime T: type, x: T) !void {
-    const y = x - 0.5;
-    const z = @round(y);
-    try expectEqual(x, z);
-}
-
-test "comptime_int param and return" {
-    const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702);
-    try expect(a == 137114567242441932203689521744947848950);
-
-    const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768);
-    try expect(b == 985095453608931032642182098849559179469148836107390954364380);
-}
-
-fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int {
-    return a + b;
-}
-
-test "vector integer addition" {
-    const S = struct {
-        fn doTheTest() !void {
-            var a: std.meta.Vector(4, i32) = [_]i32{ 1, 2, 3, 4 };
-            var b: std.meta.Vector(4, i32) = [_]i32{ 5, 6, 7, 8 };
-            var result = a + b;
-            var result_array: [4]i32 = result;
-            const expected = [_]i32{ 6, 8, 10, 12 };
-            try expectEqualSlices(i32, &expected, &result_array);
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "NaN comparison" {
-    try testNanEqNan(f16);
-    try testNanEqNan(f32);
-    try testNanEqNan(f64);
-    try testNanEqNan(f128);
-    comptime try testNanEqNan(f16);
-    comptime try testNanEqNan(f32);
-    comptime try testNanEqNan(f64);
-    comptime try testNanEqNan(f128);
-}
-
-fn testNanEqNan(comptime F: type) !void {
-    var nan1 = std.math.nan(F);
-    var nan2 = std.math.nan(F);
-    try expect(nan1 != nan2);
-    try expect(!(nan1 == nan2));
-    try expect(!(nan1 > nan2));
-    try expect(!(nan1 >= nan2));
-    try expect(!(nan1 < nan2));
-    try expect(!(nan1 <= nan2));
-}
-
-test "128-bit multiplication" {
-    var a: i128 = 3;
-    var b: i128 = 2;
-    var c = a * b;
-    try expect(c == 6);
-}
-
-test "vector comparison" {
-    const S = struct {
-        fn doTheTest() !void {
-            var a: std.meta.Vector(6, i32) = [_]i32{ 1, 3, -1, 5, 7, 9 };
-            var b: std.meta.Vector(6, i32) = [_]i32{ -1, 3, 0, 6, 10, -10 };
-            try expect(mem.eql(bool, &@as([6]bool, a < b), &[_]bool{ false, false, true, true, true, false }));
-            try expect(mem.eql(bool, &@as([6]bool, a <= b), &[_]bool{ false, true, true, true, true, false }));
-            try expect(mem.eql(bool, &@as([6]bool, a == b), &[_]bool{ false, true, false, false, false, false }));
-            try expect(mem.eql(bool, &@as([6]bool, a != b), &[_]bool{ true, false, true, true, true, true }));
-            try expect(mem.eql(bool, &@as([6]bool, a > b), &[_]bool{ true, false, false, false, false, true }));
-            try expect(mem.eql(bool, &@as([6]bool, a >= b), &[_]bool{ true, true, false, false, false, true }));
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "compare undefined literal with comptime_int" {
-    var x = undefined == 1;
-    // x is now undefined with type bool
-    x = true;
-    try expect(x);
-}
-
-test "signed zeros are represented properly" {
-    const S = struct {
-        fn doTheTest() !void {
-            inline for ([_]type{ f16, f32, f64, f128 }) |T| {
-                const ST = std.meta.Int(.unsigned, @typeInfo(T).Float.bits);
-                var as_fp_val = -@as(T, 0.0);
-                var as_uint_val = @bitCast(ST, as_fp_val);
-                // Ensure the sign bit is set.
-                try expect(as_uint_val >> (@typeInfo(T).Float.bits - 1) == 1);
-            }
-        }
-    };
-
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
test/behavior/math_stage1.zig
@@ -0,0 +1,855 @@
+const std = @import("std");
+const expect = std.testing.expect;
+const expectEqual = std.testing.expectEqual;
+const expectEqualSlices = std.testing.expectEqualSlices;
+const maxInt = std.math.maxInt;
+const minInt = std.math.minInt;
+const mem = std.mem;
+
+test "division" {
+    try testDivision();
+    comptime try testDivision();
+}
+fn testDivision() !void {
+    try expect(div(u32, 13, 3) == 4);
+    try expect(div(f16, 1.0, 2.0) == 0.5);
+    try expect(div(f32, 1.0, 2.0) == 0.5);
+
+    try expect(divExact(u32, 55, 11) == 5);
+    try expect(divExact(i32, -55, 11) == -5);
+    try expect(divExact(f16, 55.0, 11.0) == 5.0);
+    try expect(divExact(f16, -55.0, 11.0) == -5.0);
+    try expect(divExact(f32, 55.0, 11.0) == 5.0);
+    try expect(divExact(f32, -55.0, 11.0) == -5.0);
+
+    try expect(divFloor(i32, 5, 3) == 1);
+    try expect(divFloor(i32, -5, 3) == -2);
+    try expect(divFloor(f16, 5.0, 3.0) == 1.0);
+    try expect(divFloor(f16, -5.0, 3.0) == -2.0);
+    try expect(divFloor(f32, 5.0, 3.0) == 1.0);
+    try expect(divFloor(f32, -5.0, 3.0) == -2.0);
+    try expect(divFloor(i32, -0x80000000, -2) == 0x40000000);
+    try expect(divFloor(i32, 0, -0x80000000) == 0);
+    try expect(divFloor(i32, -0x40000001, 0x40000000) == -2);
+    try expect(divFloor(i32, -0x80000000, 1) == -0x80000000);
+    try expect(divFloor(i32, 10, 12) == 0);
+    try expect(divFloor(i32, -14, 12) == -2);
+    try expect(divFloor(i32, -2, 12) == -1);
+
+    try expect(divTrunc(i32, 5, 3) == 1);
+    try expect(divTrunc(i32, -5, 3) == -1);
+    try expect(divTrunc(f16, 5.0, 3.0) == 1.0);
+    try expect(divTrunc(f16, -5.0, 3.0) == -1.0);
+    try expect(divTrunc(f32, 5.0, 3.0) == 1.0);
+    try expect(divTrunc(f32, -5.0, 3.0) == -1.0);
+    try expect(divTrunc(f64, 5.0, 3.0) == 1.0);
+    try expect(divTrunc(f64, -5.0, 3.0) == -1.0);
+    try expect(divTrunc(i32, 10, 12) == 0);
+    try expect(divTrunc(i32, -14, 12) == -1);
+    try expect(divTrunc(i32, -2, 12) == 0);
+
+    try expect(mod(i32, 10, 12) == 10);
+    try expect(mod(i32, -14, 12) == 10);
+    try expect(mod(i32, -2, 12) == 10);
+
+    comptime {
+        try expect(
+            1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600,
+        );
+        try expect(
+            @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600,
+        );
+        try expect(
+            1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2,
+        );
+        try expect(
+            @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2,
+        );
+        try expect(
+            @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2,
+        );
+        try expect(
+            @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2,
+        );
+        try expect(
+            4126227191251978491697987544882340798050766755606969681711 % 10 == 1,
+        );
+    }
+}
+fn div(comptime T: type, a: T, b: T) T {
+    return a / b;
+}
+fn divExact(comptime T: type, a: T, b: T) T {
+    return @divExact(a, b);
+}
+fn divFloor(comptime T: type, a: T, b: T) T {
+    return @divFloor(a, b);
+}
+fn divTrunc(comptime T: type, a: T, b: T) T {
+    return @divTrunc(a, b);
+}
+fn mod(comptime T: type, a: T, b: T) T {
+    return @mod(a, b);
+}
+
+test "@addWithOverflow" {
+    var result: u8 = undefined;
+    try expect(@addWithOverflow(u8, 250, 100, &result));
+    try expect(!@addWithOverflow(u8, 100, 150, &result));
+    try expect(result == 250);
+}
+
+// TODO test mulWithOverflow
+// TODO test subWithOverflow
+
+test "@shlWithOverflow" {
+    var result: u16 = undefined;
+    try expect(@shlWithOverflow(u16, 0b0010111111111111, 3, &result));
+    try expect(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result));
+    try expect(result == 0b1011111111111100);
+}
+
+test "@*WithOverflow with u0 values" {
+    var result: u0 = undefined;
+    try expect(!@addWithOverflow(u0, 0, 0, &result));
+    try expect(!@subWithOverflow(u0, 0, 0, &result));
+    try expect(!@mulWithOverflow(u0, 0, 0, &result));
+    try expect(!@shlWithOverflow(u0, 0, 0, &result));
+}
+
+test "@clz" {
+    try testClz();
+    comptime try testClz();
+}
+
+fn testClz() !void {
+    try expect(@clz(u8, 0b10001010) == 0);
+    try expect(@clz(u8, 0b00001010) == 4);
+    try expect(@clz(u8, 0b00011010) == 3);
+    try expect(@clz(u8, 0b00000000) == 8);
+    try expect(@clz(u128, 0xffffffffffffffff) == 64);
+    try expect(@clz(u128, 0x10000000000000000) == 63);
+}
+
+test "@clz vectors" {
+    try testClzVectors();
+    comptime try testClzVectors();
+}
+
+fn testClzVectors() !void {
+    @setEvalBranchQuota(10_000);
+    try expectEqual(@clz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 0)));
+    try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00001010))), @splat(64, @as(u4, 4)));
+    try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00011010))), @splat(64, @as(u4, 3)));
+    try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8)));
+    try expectEqual(@clz(u128, @splat(64, @as(u128, 0xffffffffffffffff))), @splat(64, @as(u8, 64)));
+    try expectEqual(@clz(u128, @splat(64, @as(u128, 0x10000000000000000))), @splat(64, @as(u8, 63)));
+}
+
+test "@ctz" {
+    try testCtz();
+    comptime try testCtz();
+}
+
+fn testCtz() !void {
+    try expect(@ctz(u8, 0b10100000) == 5);
+    try expect(@ctz(u8, 0b10001010) == 1);
+    try expect(@ctz(u8, 0b00000000) == 8);
+    try expect(@ctz(u16, 0b00000000) == 16);
+}
+
+test "@ctz vectors" {
+    try testClzVectors();
+    comptime try testClzVectors();
+}
+
+fn testCtzVectors() !void {
+    @setEvalBranchQuota(10_000);
+    try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10100000))), @splat(64, @as(u4, 5)));
+    try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 1)));
+    try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8)));
+    try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16)));
+}
+
+test "const number literal" {
+    const one = 1;
+    const eleven = ten + one;
+
+    try expect(eleven == 11);
+}
+const ten = 10;
+
+test "unsigned wrapping" {
+    try testUnsignedWrappingEval(maxInt(u32));
+    comptime try testUnsignedWrappingEval(maxInt(u32));
+}
+fn testUnsignedWrappingEval(x: u32) !void {
+    const zero = x +% 1;
+    try expect(zero == 0);
+    const orig = zero -% 1;
+    try expect(orig == maxInt(u32));
+}
+
+test "signed wrapping" {
+    try testSignedWrappingEval(maxInt(i32));
+    comptime try testSignedWrappingEval(maxInt(i32));
+}
+fn testSignedWrappingEval(x: i32) !void {
+    const min_val = x +% 1;
+    try expect(min_val == minInt(i32));
+    const max_val = min_val -% 1;
+    try expect(max_val == maxInt(i32));
+}
+
+test "signed negation wrapping" {
+    try testSignedNegationWrappingEval(minInt(i16));
+    comptime try testSignedNegationWrappingEval(minInt(i16));
+}
+fn testSignedNegationWrappingEval(x: i16) !void {
+    try expect(x == -32768);
+    const neg = -%x;
+    try expect(neg == -32768);
+}
+
+test "unsigned negation wrapping" {
+    try testUnsignedNegationWrappingEval(1);
+    comptime try testUnsignedNegationWrappingEval(1);
+}
+fn testUnsignedNegationWrappingEval(x: u16) !void {
+    try expect(x == 1);
+    const neg = -%x;
+    try expect(neg == maxInt(u16));
+}
+
+test "unsigned 64-bit division" {
+    try test_u64_div();
+    comptime try test_u64_div();
+}
+fn test_u64_div() !void {
+    const result = divWithResult(1152921504606846976, 34359738365);
+    try expect(result.quotient == 33554432);
+    try expect(result.remainder == 100663296);
+}
+fn divWithResult(a: u64, b: u64) DivResult {
+    return DivResult{
+        .quotient = a / b,
+        .remainder = a % b,
+    };
+}
+const DivResult = struct {
+    quotient: u64,
+    remainder: u64,
+};
+
+test "binary not" {
+    try expect(comptime x: {
+        break :x ~@as(u16, 0b1010101010101010) == 0b0101010101010101;
+    });
+    try expect(comptime x: {
+        break :x ~@as(u64, 2147483647) == 18446744071562067968;
+    });
+    try testBinaryNot(0b1010101010101010);
+}
+
+fn testBinaryNot(x: u16) !void {
+    try expect(~x == 0b0101010101010101);
+}
+
+test "small int addition" {
+    var x: u2 = 0;
+    try expect(x == 0);
+
+    x += 1;
+    try expect(x == 1);
+
+    x += 1;
+    try expect(x == 2);
+
+    x += 1;
+    try expect(x == 3);
+
+    var result: @TypeOf(x) = 3;
+    try expect(@addWithOverflow(@TypeOf(x), x, 1, &result));
+
+    try expect(result == 0);
+}
+
+test "float equality" {
+    const x: f64 = 0.012;
+    const y: f64 = x + 1.0;
+
+    try testFloatEqualityImpl(x, y);
+    comptime try testFloatEqualityImpl(x, y);
+}
+
+fn testFloatEqualityImpl(x: f64, y: f64) !void {
+    const y2 = x + 1.0;
+    try expect(y == y2);
+}
+
+test "allow signed integer division/remainder when values are comptime known and positive or exact" {
+    try expect(5 / 3 == 1);
+    try expect(-5 / -3 == 1);
+    try expect(-6 / 3 == -2);
+
+    try expect(5 % 3 == 2);
+    try expect(-6 % 3 == 0);
+}
+
+test "hex float literal parsing" {
+    comptime try expect(0x1.0 == 1.0);
+}
+
+test "quad hex float literal parsing in range" {
+    const a = 0x1.af23456789bbaaab347645365cdep+5;
+    const b = 0x1.dedafcff354b6ae9758763545432p-9;
+    const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534;
+    const d = 0x1.edcbff8ad76ab5bf46463233214fp-435;
+    if (false) {
+        a;
+        b;
+        c;
+        d;
+    }
+}
+
+test "quad hex float literal parsing accurate" {
+    const a: f128 = 0x1.1111222233334444555566667777p+0;
+
+    // implied 1 is dropped, with an exponent of 0 (0x3fff) after biasing.
+    const expected: u128 = 0x3fff1111222233334444555566667777;
+    try expect(@bitCast(u128, a) == expected);
+
+    // non-normalized
+    const b: f128 = 0x11.111222233334444555566667777p-4;
+    try expect(@bitCast(u128, b) == expected);
+
+    const S = struct {
+        fn doTheTest() !void {
+            {
+                var f: f128 = 0x1.2eab345678439abcdefea56782346p+5;
+                try expect(@bitCast(u128, f) == 0x40042eab345678439abcdefea5678234);
+            }
+            {
+                var f: f128 = 0x1.edcb34a235253948765432134674fp-1;
+                try expect(@bitCast(u128, f) == 0x3ffeedcb34a235253948765432134674);
+            }
+            {
+                var f: f128 = 0x1.353e45674d89abacc3a2ebf3ff4ffp-50;
+                try expect(@bitCast(u128, f) == 0x3fcd353e45674d89abacc3a2ebf3ff50);
+            }
+            {
+                var f: f128 = 0x1.ed8764648369535adf4be3214567fp-9;
+                try expect(@bitCast(u128, f) == 0x3ff6ed8764648369535adf4be3214568);
+            }
+            const exp2ft = [_]f64{
+                0x1.6a09e667f3bcdp-1,
+                0x1.7a11473eb0187p-1,
+                0x1.8ace5422aa0dbp-1,
+                0x1.9c49182a3f090p-1,
+                0x1.ae89f995ad3adp-1,
+                0x1.c199bdd85529cp-1,
+                0x1.d5818dcfba487p-1,
+                0x1.ea4afa2a490dap-1,
+                0x1.0000000000000p+0,
+                0x1.0b5586cf9890fp+0,
+                0x1.172b83c7d517bp+0,
+                0x1.2387a6e756238p+0,
+                0x1.306fe0a31b715p+0,
+                0x1.3dea64c123422p+0,
+                0x1.4bfdad5362a27p+0,
+                0x1.5ab07dd485429p+0,
+                0x1.8p23,
+                0x1.62e430p-1,
+                0x1.ebfbe0p-3,
+                0x1.c6b348p-5,
+                0x1.3b2c9cp-7,
+                0x1.0p127,
+                -0x1.0p-149,
+            };
+
+            const answers = [_]u64{
+                0x3fe6a09e667f3bcd,
+                0x3fe7a11473eb0187,
+                0x3fe8ace5422aa0db,
+                0x3fe9c49182a3f090,
+                0x3feae89f995ad3ad,
+                0x3fec199bdd85529c,
+                0x3fed5818dcfba487,
+                0x3feea4afa2a490da,
+                0x3ff0000000000000,
+                0x3ff0b5586cf9890f,
+                0x3ff172b83c7d517b,
+                0x3ff2387a6e756238,
+                0x3ff306fe0a31b715,
+                0x3ff3dea64c123422,
+                0x3ff4bfdad5362a27,
+                0x3ff5ab07dd485429,
+                0x4168000000000000,
+                0x3fe62e4300000000,
+                0x3fcebfbe00000000,
+                0x3fac6b3480000000,
+                0x3f83b2c9c0000000,
+                0x47e0000000000000,
+                0xb6a0000000000000,
+            };
+
+            for (exp2ft) |x, i| {
+                try expect(@bitCast(u64, x) == answers[i]);
+            }
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "underscore separator parsing" {
+    try expect(0_0_0_0 == 0);
+    try expect(1_234_567 == 1234567);
+    try expect(001_234_567 == 1234567);
+    try expect(0_0_1_2_3_4_5_6_7 == 1234567);
+
+    try expect(0b0_0_0_0 == 0);
+    try expect(0b1010_1010 == 0b10101010);
+    try expect(0b0000_1010_1010 == 0b10101010);
+    try expect(0b1_0_1_0_1_0_1_0 == 0b10101010);
+
+    try expect(0o0_0_0_0 == 0);
+    try expect(0o1010_1010 == 0o10101010);
+    try expect(0o0000_1010_1010 == 0o10101010);
+    try expect(0o1_0_1_0_1_0_1_0 == 0o10101010);
+
+    try expect(0x0_0_0_0 == 0);
+    try expect(0x1010_1010 == 0x10101010);
+    try expect(0x0000_1010_1010 == 0x10101010);
+    try expect(0x1_0_1_0_1_0_1_0 == 0x10101010);
+
+    try expect(123_456.789_000e1_0 == 123456.789000e10);
+    try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10);
+
+    try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10);
+    try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10);
+}
+
+test "hex float literal within range" {
+    const a = 0x1.0p16383;
+    const b = 0x0.1p16387;
+    const c = 0x1.0p-16382;
+    if (false) {
+        a;
+        b;
+        c;
+    }
+}
+
+test "truncating shift left" {
+    try testShlTrunc(maxInt(u16));
+    comptime try testShlTrunc(maxInt(u16));
+}
+fn testShlTrunc(x: u16) !void {
+    const shifted = x << 1;
+    try expect(shifted == 65534);
+}
+
+test "truncating shift right" {
+    try testShrTrunc(maxInt(u16));
+    comptime try testShrTrunc(maxInt(u16));
+}
+fn testShrTrunc(x: u16) !void {
+    const shifted = x >> 1;
+    try expect(shifted == 32767);
+}
+
+test "exact shift left" {
+    try testShlExact(0b00110101);
+    comptime try testShlExact(0b00110101);
+}
+fn testShlExact(x: u8) !void {
+    const shifted = @shlExact(x, 2);
+    try expect(shifted == 0b11010100);
+}
+
+test "exact shift right" {
+    try testShrExact(0b10110100);
+    comptime try testShrExact(0b10110100);
+}
+fn testShrExact(x: u8) !void {
+    const shifted = @shrExact(x, 2);
+    try expect(shifted == 0b00101101);
+}
+
+test "shift left/right on u0 operand" {
+    const S = struct {
+        fn doTheTest() !void {
+            var x: u0 = 0;
+            var y: u0 = 0;
+            try expectEqual(@as(u0, 0), x << 0);
+            try expectEqual(@as(u0, 0), x >> 0);
+            try expectEqual(@as(u0, 0), x << y);
+            try expectEqual(@as(u0, 0), x >> y);
+            try expectEqual(@as(u0, 0), @shlExact(x, 0));
+            try expectEqual(@as(u0, 0), @shrExact(x, 0));
+            try expectEqual(@as(u0, 0), @shlExact(x, y));
+            try expectEqual(@as(u0, 0), @shrExact(x, y));
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "comptime_int addition" {
+    comptime {
+        try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950);
+        try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380);
+    }
+}
+
+test "comptime_int multiplication" {
+    comptime {
+        try expect(
+            45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567,
+        );
+        try expect(
+            594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016,
+        );
+    }
+}
+
+test "comptime_int shifting" {
+    comptime {
+        try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000);
+    }
+}
+
+test "comptime_int multi-limb shift and mask" {
+    comptime {
+        var a = 0xefffffffa0000001eeeeeeefaaaaaaab;
+
+        try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab);
+        a >>= 32;
+        try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef);
+        a >>= 32;
+        try expect(@as(u32, a & 0xffffffff) == 0xa0000001);
+        a >>= 32;
+        try expect(@as(u32, a & 0xffffffff) == 0xefffffff);
+        a >>= 32;
+
+        try expect(a == 0);
+    }
+}
+
+test "comptime_int multi-limb partial shift right" {
+    comptime {
+        var a = 0x1ffffffffeeeeeeee;
+        a >>= 16;
+        try expect(a == 0x1ffffffffeeee);
+    }
+}
+
+test "xor" {
+    try test_xor();
+    comptime try test_xor();
+}
+
+fn test_xor() !void {
+    try expect(0xFF ^ 0x00 == 0xFF);
+    try expect(0xF0 ^ 0x0F == 0xFF);
+    try expect(0xFF ^ 0xF0 == 0x0F);
+    try expect(0xFF ^ 0x0F == 0xF0);
+    try expect(0xFF ^ 0xFF == 0x00);
+}
+
+test "comptime_int xor" {
+    comptime {
+        try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
+        try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
+        try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF);
+        try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000);
+        try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000);
+        try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
+        try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF);
+        try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000);
+    }
+}
+
+test "f128" {
+    try test_f128();
+    comptime try test_f128();
+}
+
+fn make_f128(x: f128) f128 {
+    return x;
+}
+
+fn test_f128() !void {
+    try expect(@sizeOf(f128) == 16);
+    try expect(make_f128(1.0) == 1.0);
+    try expect(make_f128(1.0) != 1.1);
+    try expect(make_f128(1.0) > 0.9);
+    try expect(make_f128(1.0) >= 0.9);
+    try expect(make_f128(1.0) >= 1.0);
+    try should_not_be_zero(1.0);
+}
+
+fn should_not_be_zero(x: f128) !void {
+    try expect(x != 0.0);
+}
+
+test "comptime float rem int" {
+    comptime {
+        var x = @as(f32, 1) % 2;
+        try expect(x == 1.0);
+    }
+}
+
+test "remainder division" {
+    comptime try remdiv(f16);
+    comptime try remdiv(f32);
+    comptime try remdiv(f64);
+    comptime try remdiv(f128);
+    try remdiv(f16);
+    try remdiv(f64);
+    try remdiv(f128);
+}
+
+fn remdiv(comptime T: type) !void {
+    try expect(@as(T, 1) == @as(T, 1) % @as(T, 2));
+    try expect(@as(T, 1) == @as(T, 7) % @as(T, 3));
+}
+
+test "@sqrt" {
+    try testSqrt(f64, 12.0);
+    comptime try testSqrt(f64, 12.0);
+    try testSqrt(f32, 13.0);
+    comptime try testSqrt(f32, 13.0);
+    try testSqrt(f16, 13.0);
+    comptime try testSqrt(f16, 13.0);
+
+    const x = 14.0;
+    const y = x * x;
+    const z = @sqrt(y);
+    comptime try expect(z == x);
+}
+
+fn testSqrt(comptime T: type, x: T) !void {
+    try expect(@sqrt(x * x) == x);
+}
+
+test "@fabs" {
+    try testFabs(f128, 12.0);
+    comptime try testFabs(f128, 12.0);
+    try testFabs(f64, 12.0);
+    comptime try testFabs(f64, 12.0);
+    try testFabs(f32, 12.0);
+    comptime try testFabs(f32, 12.0);
+    try testFabs(f16, 12.0);
+    comptime try testFabs(f16, 12.0);
+
+    const x = 14.0;
+    const y = -x;
+    const z = @fabs(y);
+    comptime try expectEqual(x, z);
+}
+
+fn testFabs(comptime T: type, x: T) !void {
+    const y = -x;
+    const z = @fabs(y);
+    try expectEqual(x, z);
+}
+
+test "@floor" {
+    // FIXME: Generates a floorl function call
+    // testFloor(f128, 12.0);
+    comptime try testFloor(f128, 12.0);
+    try testFloor(f64, 12.0);
+    comptime try testFloor(f64, 12.0);
+    try testFloor(f32, 12.0);
+    comptime try testFloor(f32, 12.0);
+    try testFloor(f16, 12.0);
+    comptime try testFloor(f16, 12.0);
+
+    const x = 14.0;
+    const y = x + 0.7;
+    const z = @floor(y);
+    comptime try expectEqual(x, z);
+}
+
+fn testFloor(comptime T: type, x: T) !void {
+    const y = x + 0.6;
+    const z = @floor(y);
+    try expectEqual(x, z);
+}
+
+test "@ceil" {
+    // FIXME: Generates a ceill function call
+    //testCeil(f128, 12.0);
+    comptime try testCeil(f128, 12.0);
+    try testCeil(f64, 12.0);
+    comptime try testCeil(f64, 12.0);
+    try testCeil(f32, 12.0);
+    comptime try testCeil(f32, 12.0);
+    try testCeil(f16, 12.0);
+    comptime try testCeil(f16, 12.0);
+
+    const x = 14.0;
+    const y = x - 0.7;
+    const z = @ceil(y);
+    comptime try expectEqual(x, z);
+}
+
+fn testCeil(comptime T: type, x: T) !void {
+    const y = x - 0.8;
+    const z = @ceil(y);
+    try expectEqual(x, z);
+}
+
+test "@trunc" {
+    // FIXME: Generates a truncl function call
+    //testTrunc(f128, 12.0);
+    comptime try testTrunc(f128, 12.0);
+    try testTrunc(f64, 12.0);
+    comptime try testTrunc(f64, 12.0);
+    try testTrunc(f32, 12.0);
+    comptime try testTrunc(f32, 12.0);
+    try testTrunc(f16, 12.0);
+    comptime try testTrunc(f16, 12.0);
+
+    const x = 14.0;
+    const y = x + 0.7;
+    const z = @trunc(y);
+    comptime try expectEqual(x, z);
+}
+
+fn testTrunc(comptime T: type, x: T) !void {
+    {
+        const y = x + 0.8;
+        const z = @trunc(y);
+        try expectEqual(x, z);
+    }
+
+    {
+        const y = -x - 0.8;
+        const z = @trunc(y);
+        try expectEqual(-x, z);
+    }
+}
+
+test "@round" {
+    // FIXME: Generates a roundl function call
+    //testRound(f128, 12.0);
+    comptime try testRound(f128, 12.0);
+    try testRound(f64, 12.0);
+    comptime try testRound(f64, 12.0);
+    try testRound(f32, 12.0);
+    comptime try testRound(f32, 12.0);
+    try testRound(f16, 12.0);
+    comptime try testRound(f16, 12.0);
+
+    const x = 14.0;
+    const y = x + 0.4;
+    const z = @round(y);
+    comptime try expectEqual(x, z);
+}
+
+fn testRound(comptime T: type, x: T) !void {
+    const y = x - 0.5;
+    const z = @round(y);
+    try expectEqual(x, z);
+}
+
+test "comptime_int param and return" {
+    const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702);
+    try expect(a == 137114567242441932203689521744947848950);
+
+    const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768);
+    try expect(b == 985095453608931032642182098849559179469148836107390954364380);
+}
+
+fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int {
+    return a + b;
+}
+
+test "vector integer addition" {
+    const S = struct {
+        fn doTheTest() !void {
+            var a: std.meta.Vector(4, i32) = [_]i32{ 1, 2, 3, 4 };
+            var b: std.meta.Vector(4, i32) = [_]i32{ 5, 6, 7, 8 };
+            var result = a + b;
+            var result_array: [4]i32 = result;
+            const expected = [_]i32{ 6, 8, 10, 12 };
+            try expectEqualSlices(i32, &expected, &result_array);
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "NaN comparison" {
+    try testNanEqNan(f16);
+    try testNanEqNan(f32);
+    try testNanEqNan(f64);
+    try testNanEqNan(f128);
+    comptime try testNanEqNan(f16);
+    comptime try testNanEqNan(f32);
+    comptime try testNanEqNan(f64);
+    comptime try testNanEqNan(f128);
+}
+
+fn testNanEqNan(comptime F: type) !void {
+    var nan1 = std.math.nan(F);
+    var nan2 = std.math.nan(F);
+    try expect(nan1 != nan2);
+    try expect(!(nan1 == nan2));
+    try expect(!(nan1 > nan2));
+    try expect(!(nan1 >= nan2));
+    try expect(!(nan1 < nan2));
+    try expect(!(nan1 <= nan2));
+}
+
+test "128-bit multiplication" {
+    var a: i128 = 3;
+    var b: i128 = 2;
+    var c = a * b;
+    try expect(c == 6);
+}
+
+test "vector comparison" {
+    const S = struct {
+        fn doTheTest() !void {
+            var a: std.meta.Vector(6, i32) = [_]i32{ 1, 3, -1, 5, 7, 9 };
+            var b: std.meta.Vector(6, i32) = [_]i32{ -1, 3, 0, 6, 10, -10 };
+            try expect(mem.eql(bool, &@as([6]bool, a < b), &[_]bool{ false, false, true, true, true, false }));
+            try expect(mem.eql(bool, &@as([6]bool, a <= b), &[_]bool{ false, true, true, true, true, false }));
+            try expect(mem.eql(bool, &@as([6]bool, a == b), &[_]bool{ false, true, false, false, false, false }));
+            try expect(mem.eql(bool, &@as([6]bool, a != b), &[_]bool{ true, false, true, true, true, true }));
+            try expect(mem.eql(bool, &@as([6]bool, a > b), &[_]bool{ true, false, false, false, false, true }));
+            try expect(mem.eql(bool, &@as([6]bool, a >= b), &[_]bool{ true, true, false, false, false, true }));
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "compare undefined literal with comptime_int" {
+    var x = undefined == 1;
+    // x is now undefined with type bool
+    x = true;
+    try expect(x);
+}
+
+test "signed zeros are represented properly" {
+    const S = struct {
+        fn doTheTest() !void {
+            inline for ([_]type{ f16, f32, f64, f128 }) |T| {
+                const ST = std.meta.Int(.unsigned, @typeInfo(T).Float.bits);
+                var as_fp_val = -@as(T, 0.0);
+                var as_uint_val = @bitCast(ST, as_fp_val);
+                // Ensure the sign bit is set.
+                try expect(as_uint_val >> (@typeInfo(T).Float.bits - 1) == 1);
+            }
+        }
+    };
+
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
test/behavior.zig
@@ -10,6 +10,7 @@ test {
     _ = @import("behavior/eval.zig");
     _ = @import("behavior/generics.zig");
     _ = @import("behavior/if.zig");
+    _ = @import("behavior/math.zig");
     _ = @import("behavior/member_func.zig");
     _ = @import("behavior/pointers.zig");
     _ = @import("behavior/sizeof_and_typeof.zig");
@@ -119,7 +120,7 @@ test {
         _ = @import("behavior/incomplete_struct_param_tld.zig");
         _ = @import("behavior/inttoptr.zig");
         _ = @import("behavior/ir_block_deps.zig");
-        _ = @import("behavior/math.zig");
+        _ = @import("behavior/math_stage1.zig");
         _ = @import("behavior/maximum_minimum.zig");
         _ = @import("behavior/merge_error_sets.zig");
         _ = @import("behavior/misc.zig");