| author | |
| committer | |
| log | ddf14323ea9b2c75ac5ed286525d27730a192b53 |
| tree | a8b586e69bef580747553f63da828f947f28aa21 |
| parent | 6ae0825e7f87fc9b73a4b968964196b0e164f062 |
12 files changed, 380 insertions(+), 279 deletions(-)
src/Air.zig+9-2| ... | ... | @@ -206,10 +206,16 @@ pub const Inst = struct { |
| 206 | 206 | /// Convert from one float type to another. |
| 207 | 207 | /// Uses the `ty_op` field. |
| 208 | 208 | floatcast, |
| 209 | /// TODO audit uses of this. We should have explicit instructions for integer | |
| 210 | /// widening and truncating. | |
| 209 | /// Returns an integer with a different type than the operand. The new type may have | |
| 210 | /// fewer, the same, or more bits than the operand type. However, the instruction | |
| 211 | /// guarantees that the same integer value fits in both types. | |
| 212 | /// See `trunc` for integer truncation. | |
| 211 | 213 | /// Uses the `ty_op` field. |
| 212 | 214 | intcast, |
| 215 | /// Truncate higher bits from an integer, resulting in an integer with the same | |
| 216 | /// sign but an equal or smaller number of bits. | |
| 217 | /// Uses the `ty_op` field. | |
| 218 | trunc, | |
| 213 | 219 | /// ?T => T. If the value is null, undefined behavior. |
| 214 | 220 | /// Uses the `ty_op` field. |
| 215 | 221 | optional_payload, |
| ... | ... | @@ -452,6 +458,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { |
| 452 | 458 | .load, |
| 453 | 459 | .floatcast, |
| 454 | 460 | .intcast, |
| 461 | .trunc, | |
| 455 | 462 | .optional_payload, |
| 456 | 463 | .optional_payload_ptr, |
| 457 | 464 | .wrap_optional, |
src/Liveness.zig+1| ... | ... | @@ -264,6 +264,7 @@ fn analyzeInst( |
| 264 | 264 | .load, |
| 265 | 265 | .floatcast, |
| 266 | 266 | .intcast, |
| 267 | .trunc, | |
| 267 | 268 | .optional_payload, |
| 268 | 269 | .optional_payload_ptr, |
| 269 | 270 | .wrap_optional, |
src/Module.zig-238| ... | ... | @@ -4033,244 +4033,6 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) Co |
| 4033 | 4033 | return error.AnalysisFail; |
| 4034 | 4034 | } |
| 4035 | 4035 | |
| 4036 | pub fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { | |
| 4037 | // TODO is this a performance issue? maybe we should try the operation without | |
| 4038 | // resorting to BigInt first. | |
| 4039 | var lhs_space: Value.BigIntSpace = undefined; | |
| 4040 | var rhs_space: Value.BigIntSpace = undefined; | |
| 4041 | const lhs_bigint = lhs.toBigInt(&lhs_space); | |
| 4042 | const rhs_bigint = rhs.toBigInt(&rhs_space); | |
| 4043 | const limbs = try allocator.alloc( | |
| 4044 | std.math.big.Limb, | |
| 4045 | std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, | |
| 4046 | ); | |
| 4047 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | |
| 4048 | result_bigint.add(lhs_bigint, rhs_bigint); | |
| 4049 | const result_limbs = result_bigint.limbs[0..result_bigint.len]; | |
| 4050 | ||
| 4051 | if (result_bigint.positive) { | |
| 4052 | return Value.Tag.int_big_positive.create(allocator, result_limbs); | |
| 4053 | } else { | |
| 4054 | return Value.Tag.int_big_negative.create(allocator, result_limbs); | |
| 4055 | } | |
| 4056 | } | |
| 4057 | ||
| 4058 | pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { | |
| 4059 | // TODO is this a performance issue? maybe we should try the operation without | |
| 4060 | // resorting to BigInt first. | |
| 4061 | var lhs_space: Value.BigIntSpace = undefined; | |
| 4062 | var rhs_space: Value.BigIntSpace = undefined; | |
| 4063 | const lhs_bigint = lhs.toBigInt(&lhs_space); | |
| 4064 | const rhs_bigint = rhs.toBigInt(&rhs_space); | |
| 4065 | const limbs = try allocator.alloc( | |
| 4066 | std.math.big.Limb, | |
| 4067 | std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, | |
| 4068 | ); | |
| 4069 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | |
| 4070 | result_bigint.sub(lhs_bigint, rhs_bigint); | |
| 4071 | const result_limbs = result_bigint.limbs[0..result_bigint.len]; | |
| 4072 | ||
| 4073 | if (result_bigint.positive) { | |
| 4074 | return Value.Tag.int_big_positive.create(allocator, result_limbs); | |
| 4075 | } else { | |
| 4076 | return Value.Tag.int_big_negative.create(allocator, result_limbs); | |
| 4077 | } | |
| 4078 | } | |
| 4079 | ||
| 4080 | pub fn intDiv(allocator: *Allocator, lhs: Value, rhs: Value) !Value { | |
| 4081 | // TODO is this a performance issue? maybe we should try the operation without | |
| 4082 | // resorting to BigInt first. | |
| 4083 | var lhs_space: Value.BigIntSpace = undefined; | |
| 4084 | var rhs_space: Value.BigIntSpace = undefined; | |
| 4085 | const lhs_bigint = lhs.toBigInt(&lhs_space); | |
| 4086 | const rhs_bigint = rhs.toBigInt(&rhs_space); | |
| 4087 | const limbs_q = try allocator.alloc( | |
| 4088 | std.math.big.Limb, | |
| 4089 | lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, | |
| 4090 | ); | |
| 4091 | const limbs_r = try allocator.alloc( | |
| 4092 | std.math.big.Limb, | |
| 4093 | lhs_bigint.limbs.len, | |
| 4094 | ); | |
| 4095 | const limbs_buffer = try allocator.alloc( | |
| 4096 | std.math.big.Limb, | |
| 4097 | std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len), | |
| 4098 | ); | |
| 4099 | var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined }; | |
| 4100 | var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined }; | |
| 4101 | result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); | |
| 4102 | const result_limbs = result_q.limbs[0..result_q.len]; | |
| 4103 | ||
| 4104 | if (result_q.positive) { | |
| 4105 | return Value.Tag.int_big_positive.create(allocator, result_limbs); | |
| 4106 | } else { | |
| 4107 | return Value.Tag.int_big_negative.create(allocator, result_limbs); | |
| 4108 | } | |
| 4109 | } | |
| 4110 | ||
| 4111 | pub fn intMul(allocator: *Allocator, lhs: Value, rhs: Value) !Value { | |
| 4112 | // TODO is this a performance issue? maybe we should try the operation without | |
| 4113 | // resorting to BigInt first. | |
| 4114 | var lhs_space: Value.BigIntSpace = undefined; | |
| 4115 | var rhs_space: Value.BigIntSpace = undefined; | |
| 4116 | const lhs_bigint = lhs.toBigInt(&lhs_space); | |
| 4117 | const rhs_bigint = rhs.toBigInt(&rhs_space); | |
| 4118 | const limbs = try allocator.alloc( | |
| 4119 | std.math.big.Limb, | |
| 4120 | lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, | |
| 4121 | ); | |
| 4122 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | |
| 4123 | var limbs_buffer = try allocator.alloc( | |
| 4124 | std.math.big.Limb, | |
| 4125 | std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1), | |
| 4126 | ); | |
| 4127 | defer allocator.free(limbs_buffer); | |
| 4128 | result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator); | |
| 4129 | const result_limbs = result_bigint.limbs[0..result_bigint.len]; | |
| 4130 | ||
| 4131 | if (result_bigint.positive) { | |
| 4132 | return Value.Tag.int_big_positive.create(allocator, result_limbs); | |
| 4133 | } else { | |
| 4134 | return Value.Tag.int_big_negative.create(allocator, result_limbs); | |
| 4135 | } | |
| 4136 | } | |
| 4137 | ||
| 4138 | pub fn floatAdd( | |
| 4139 | arena: *Allocator, | |
| 4140 | float_type: Type, | |
| 4141 | src: LazySrcLoc, | |
| 4142 | lhs: Value, | |
| 4143 | rhs: Value, | |
| 4144 | ) !Value { | |
| 4145 | _ = src; | |
| 4146 | switch (float_type.tag()) { | |
| 4147 | .f16 => { | |
| 4148 | @panic("TODO add __trunctfhf2 to compiler-rt"); | |
| 4149 | //const lhs_val = lhs.toFloat(f16); | |
| 4150 | //const rhs_val = rhs.toFloat(f16); | |
| 4151 | //return Value.Tag.float_16.create(arena, lhs_val + rhs_val); | |
| 4152 | }, | |
| 4153 | .f32 => { | |
| 4154 | const lhs_val = lhs.toFloat(f32); | |
| 4155 | const rhs_val = rhs.toFloat(f32); | |
| 4156 | return Value.Tag.float_32.create(arena, lhs_val + rhs_val); | |
| 4157 | }, | |
| 4158 | .f64 => { | |
| 4159 | const lhs_val = lhs.toFloat(f64); | |
| 4160 | const rhs_val = rhs.toFloat(f64); | |
| 4161 | return Value.Tag.float_64.create(arena, lhs_val + rhs_val); | |
| 4162 | }, | |
| 4163 | .f128, .comptime_float, .c_longdouble => { | |
| 4164 | const lhs_val = lhs.toFloat(f128); | |
| 4165 | const rhs_val = rhs.toFloat(f128); | |
| 4166 | return Value.Tag.float_128.create(arena, lhs_val + rhs_val); | |
| 4167 | }, | |
| 4168 | else => unreachable, | |
| 4169 | } | |
| 4170 | } | |
| 4171 | ||
| 4172 | pub fn floatSub( | |
| 4173 | arena: *Allocator, | |
| 4174 | float_type: Type, | |
| 4175 | src: LazySrcLoc, | |
| 4176 | lhs: Value, | |
| 4177 | rhs: Value, | |
| 4178 | ) !Value { | |
| 4179 | _ = src; | |
| 4180 | switch (float_type.tag()) { | |
| 4181 | .f16 => { | |
| 4182 | @panic("TODO add __trunctfhf2 to compiler-rt"); | |
| 4183 | //const lhs_val = lhs.toFloat(f16); | |
| 4184 | //const rhs_val = rhs.toFloat(f16); | |
| 4185 | //return Value.Tag.float_16.create(arena, lhs_val - rhs_val); | |
| 4186 | }, | |
| 4187 | .f32 => { | |
| 4188 | const lhs_val = lhs.toFloat(f32); | |
| 4189 | const rhs_val = rhs.toFloat(f32); | |
| 4190 | return Value.Tag.float_32.create(arena, lhs_val - rhs_val); | |
| 4191 | }, | |
| 4192 | .f64 => { | |
| 4193 | const lhs_val = lhs.toFloat(f64); | |
| 4194 | const rhs_val = rhs.toFloat(f64); | |
| 4195 | return Value.Tag.float_64.create(arena, lhs_val - rhs_val); | |
| 4196 | }, | |
| 4197 | .f128, .comptime_float, .c_longdouble => { | |
| 4198 | const lhs_val = lhs.toFloat(f128); | |
| 4199 | const rhs_val = rhs.toFloat(f128); | |
| 4200 | return Value.Tag.float_128.create(arena, lhs_val - rhs_val); | |
| 4201 | }, | |
| 4202 | else => unreachable, | |
| 4203 | } | |
| 4204 | } | |
| 4205 | ||
| 4206 | pub fn floatDiv( | |
| 4207 | arena: *Allocator, | |
| 4208 | float_type: Type, | |
| 4209 | src: LazySrcLoc, | |
| 4210 | lhs: Value, | |
| 4211 | rhs: Value, | |
| 4212 | ) !Value { | |
| 4213 | _ = src; | |
| 4214 | switch (float_type.tag()) { | |
| 4215 | .f16 => { | |
| 4216 | @panic("TODO add __trunctfhf2 to compiler-rt"); | |
| 4217 | //const lhs_val = lhs.toFloat(f16); | |
| 4218 | //const rhs_val = rhs.toFloat(f16); | |
| 4219 | //return Value.Tag.float_16.create(arena, lhs_val / rhs_val); | |
| 4220 | }, | |
| 4221 | .f32 => { | |
| 4222 | const lhs_val = lhs.toFloat(f32); | |
| 4223 | const rhs_val = rhs.toFloat(f32); | |
| 4224 | return Value.Tag.float_32.create(arena, lhs_val / rhs_val); | |
| 4225 | }, | |
| 4226 | .f64 => { | |
| 4227 | const lhs_val = lhs.toFloat(f64); | |
| 4228 | const rhs_val = rhs.toFloat(f64); | |
| 4229 | return Value.Tag.float_64.create(arena, lhs_val / rhs_val); | |
| 4230 | }, | |
| 4231 | .f128, .comptime_float, .c_longdouble => { | |
| 4232 | const lhs_val = lhs.toFloat(f128); | |
| 4233 | const rhs_val = rhs.toFloat(f128); | |
| 4234 | return Value.Tag.float_128.create(arena, lhs_val / rhs_val); | |
| 4235 | }, | |
| 4236 | else => unreachable, | |
| 4237 | } | |
| 4238 | } | |
| 4239 | ||
| 4240 | pub fn floatMul( | |
| 4241 | arena: *Allocator, | |
| 4242 | float_type: Type, | |
| 4243 | src: LazySrcLoc, | |
| 4244 | lhs: Value, | |
| 4245 | rhs: Value, | |
| 4246 | ) !Value { | |
| 4247 | _ = src; | |
| 4248 | switch (float_type.tag()) { | |
| 4249 | .f16 => { | |
| 4250 | @panic("TODO add __trunctfhf2 to compiler-rt"); | |
| 4251 | //const lhs_val = lhs.toFloat(f16); | |
| 4252 | //const rhs_val = rhs.toFloat(f16); | |
| 4253 | //return Value.Tag.float_16.create(arena, lhs_val * rhs_val); | |
| 4254 | }, | |
| 4255 | .f32 => { | |
| 4256 | const lhs_val = lhs.toFloat(f32); | |
| 4257 | const rhs_val = rhs.toFloat(f32); | |
| 4258 | return Value.Tag.float_32.create(arena, lhs_val * rhs_val); | |
| 4259 | }, | |
| 4260 | .f64 => { | |
| 4261 | const lhs_val = lhs.toFloat(f64); | |
| 4262 | const rhs_val = rhs.toFloat(f64); | |
| 4263 | return Value.Tag.float_64.create(arena, lhs_val * rhs_val); | |
| 4264 | }, | |
| 4265 | .f128, .comptime_float, .c_longdouble => { | |
| 4266 | const lhs_val = lhs.toFloat(f128); | |
| 4267 | const rhs_val = rhs.toFloat(f128); | |
| 4268 | return Value.Tag.float_128.create(arena, lhs_val * rhs_val); | |
| 4269 | }, | |
| 4270 | else => unreachable, | |
| 4271 | } | |
| 4272 | } | |
| 4273 | ||
| 4274 | 4036 | pub fn simplePtrType( |
| 4275 | 4037 | arena: *Allocator, |
| 4276 | 4038 | elem_ty: Type, |
src/Sema.zig+74-30| ... | ... | @@ -3479,27 +3479,8 @@ fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr |
| 3479 | 3479 | const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs); |
| 3480 | 3480 | const operand = sema.resolveInst(extra.rhs); |
| 3481 | 3481 | |
| 3482 | const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { | |
| 3483 | .ComptimeInt => true, | |
| 3484 | .Int => false, | |
| 3485 | else => return sema.mod.fail( | |
| 3486 | &block.base, | |
| 3487 | dest_ty_src, | |
| 3488 | "expected integer type, found '{}'", | |
| 3489 | .{dest_type}, | |
| 3490 | ), | |
| 3491 | }; | |
| 3492 | ||
| 3493 | const operand_ty = sema.typeOf(operand); | |
| 3494 | switch (operand_ty.zigTypeTag()) { | |
| 3495 | .ComptimeInt, .Int => {}, | |
| 3496 | else => return sema.mod.fail( | |
| 3497 | &block.base, | |
| 3498 | operand_src, | |
| 3499 | "expected integer type, found '{}'", | |
| 3500 | .{operand_ty}, | |
| 3501 | ), | |
| 3502 | } | |
| 3482 | const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_type); | |
| 3483 | _ = try sema.requireIntegerType(block, operand_src, sema.typeOf(operand)); | |
| 3503 | 3484 | |
| 3504 | 3485 | if (try sema.isComptimeKnown(block, operand_src, operand)) { |
| 3505 | 3486 | return sema.coerce(block, dest_type, operand, operand_src); |
| ... | ... | @@ -4951,30 +4932,30 @@ fn analyzeArithmetic( |
| 4951 | 4932 | const value = switch (zir_tag) { |
| 4952 | 4933 | .add => blk: { |
| 4953 | 4934 | const val = if (is_int) |
| 4954 | try Module.intAdd(sema.arena, lhs_val, rhs_val) | |
| 4935 | try lhs_val.intAdd(rhs_val, sema.arena) | |
| 4955 | 4936 | else |
| 4956 | try Module.floatAdd(sema.arena, scalar_type, src, lhs_val, rhs_val); | |
| 4937 | try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena); | |
| 4957 | 4938 | break :blk val; |
| 4958 | 4939 | }, |
| 4959 | 4940 | .sub => blk: { |
| 4960 | 4941 | const val = if (is_int) |
| 4961 | try Module.intSub(sema.arena, lhs_val, rhs_val) | |
| 4942 | try lhs_val.intSub(rhs_val, sema.arena) | |
| 4962 | 4943 | else |
| 4963 | try Module.floatSub(sema.arena, scalar_type, src, lhs_val, rhs_val); | |
| 4944 | try lhs_val.floatSub(rhs_val, scalar_type, sema.arena); | |
| 4964 | 4945 | break :blk val; |
| 4965 | 4946 | }, |
| 4966 | 4947 | .div => blk: { |
| 4967 | 4948 | const val = if (is_int) |
| 4968 | try Module.intDiv(sema.arena, lhs_val, rhs_val) | |
| 4949 | try lhs_val.intDiv(rhs_val, sema.arena) | |
| 4969 | 4950 | else |
| 4970 | try Module.floatDiv(sema.arena, scalar_type, src, lhs_val, rhs_val); | |
| 4951 | try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena); | |
| 4971 | 4952 | break :blk val; |
| 4972 | 4953 | }, |
| 4973 | 4954 | .mul => blk: { |
| 4974 | 4955 | const val = if (is_int) |
| 4975 | try Module.intMul(sema.arena, lhs_val, rhs_val) | |
| 4956 | try lhs_val.intMul(rhs_val, sema.arena) | |
| 4976 | 4957 | else |
| 4977 | try Module.floatMul(sema.arena, scalar_type, src, lhs_val, rhs_val); | |
| 4958 | try lhs_val.floatMul(rhs_val, scalar_type, sema.arena); | |
| 4978 | 4959 | break :blk val; |
| 4979 | 4960 | }, |
| 4980 | 4961 | else => return sema.mod.fail(&block.base, src, "TODO Implement arithmetic operand '{s}'", .{@tagName(zir_tag)}), |
| ... | ... | @@ -6173,7 +6154,62 @@ fn zirPtrCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr |
| 6173 | 6154 | fn zirTruncate(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 6174 | 6155 | const inst_data = sema.code.instructions.items(.data)[inst].pl_node; |
| 6175 | 6156 | const src = inst_data.src(); |
| 6176 | return sema.mod.fail(&block.base, src, "TODO: Sema.zirTruncate", .{}); | |
| 6157 | const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; | |
| 6158 | const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; | |
| 6159 | const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; | |
| 6160 | const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); | |
| 6161 | const operand = sema.resolveInst(extra.rhs); | |
| 6162 | const operand_ty = sema.typeOf(operand); | |
| 6163 | const mod = sema.mod; | |
| 6164 | const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_ty); | |
| 6165 | const src_is_comptime_int = try sema.requireIntegerType(block, operand_src, operand_ty); | |
| 6166 | ||
| 6167 | if (dest_is_comptime_int) { | |
| 6168 | return sema.coerce(block, dest_ty, operand, operand_src); | |
| 6169 | } | |
| 6170 | ||
| 6171 | const target = mod.getTarget(); | |
| 6172 | const src_info = operand_ty.intInfo(target); | |
| 6173 | const dest_info = dest_ty.intInfo(target); | |
| 6174 | ||
| 6175 | if (src_info.bits == 0 or dest_info.bits == 0) { | |
| 6176 | return sema.addConstant(dest_ty, Value.initTag(.zero)); | |
| 6177 | } | |
| 6178 | ||
| 6179 | if (!src_is_comptime_int) { | |
| 6180 | if (src_info.signedness != dest_info.signedness) { | |
| 6181 | return mod.fail(&block.base, operand_src, "expected {s} integer type, found '{}'", .{ | |
| 6182 | @tagName(dest_info.signedness), operand_ty, | |
| 6183 | }); | |
| 6184 | } | |
| 6185 | if (src_info.bits > 0 and src_info.bits < dest_info.bits) { | |
| 6186 | const msg = msg: { | |
| 6187 | const msg = try mod.errMsg( | |
| 6188 | &block.base, | |
| 6189 | src, | |
| 6190 | "destination type '{}' has more bits than source type '{}'", | |
| 6191 | .{ dest_ty, operand_ty }, | |
| 6192 | ); | |
| 6193 | errdefer msg.destroy(mod.gpa); | |
| 6194 | try mod.errNote(&block.base, dest_ty_src, msg, "destination type has {d} bits", .{ | |
| 6195 | dest_info.bits, | |
| 6196 | }); | |
| 6197 | try mod.errNote(&block.base, operand_src, msg, "source type has {d} bits", .{ | |
| 6198 | src_info.bits, | |
| 6199 | }); | |
| 6200 | break :msg msg; | |
| 6201 | }; | |
| 6202 | return mod.failWithOwnedErrorMsg(&block.base, msg); | |
| 6203 | } | |
| 6204 | } | |
| 6205 | ||
| 6206 | if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { | |
| 6207 | if (val.isUndef()) return sema.addConstUndef(dest_ty); | |
| 6208 | return sema.addConstant(dest_ty, try val.intTrunc(sema.arena, dest_info.bits)); | |
| 6209 | } | |
| 6210 | ||
| 6211 | try sema.requireRuntimeBlock(block, src); | |
| 6212 | return block.addTyOp(.trunc, dest_ty, operand); | |
| 6177 | 6213 | } |
| 6178 | 6214 | |
| 6179 | 6215 | fn zirAlignCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| ... | ... | @@ -6594,6 +6630,14 @@ fn requireRuntimeBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void |
| 6594 | 6630 | try sema.requireFunctionBlock(block, src); |
| 6595 | 6631 | } |
| 6596 | 6632 | |
| 6633 | fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !bool { | |
| 6634 | switch (ty.zigTypeTag()) { | |
| 6635 | .ComptimeInt => return true, | |
| 6636 | .Int => return false, | |
| 6637 | else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}), | |
| 6638 | } | |
| 6639 | } | |
| 6640 | ||
| 6597 | 6641 | fn validateVarType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void { |
| 6598 | 6642 | if (!ty.isValidVarType(false)) { |
| 6599 | 6643 | return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{ty}); |
src/codegen.zig+14| ... | ... | @@ -835,6 +835,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 835 | 835 | .dbg_stmt => try self.airDbgStmt(inst), |
| 836 | 836 | .floatcast => try self.airFloatCast(inst), |
| 837 | 837 | .intcast => try self.airIntCast(inst), |
| 838 | .trunc => try self.airTrunc(inst), | |
| 838 | 839 | .bool_to_int => try self.airBoolToInt(inst), |
| 839 | 840 | .is_non_null => try self.airIsNonNull(inst), |
| 840 | 841 | .is_non_null_ptr => try self.airIsNonNullPtr(inst), |
| ... | ... | @@ -1109,6 +1110,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1109 | 1110 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); |
| 1110 | 1111 | } |
| 1111 | 1112 | |
| 1113 | fn airTrunc(self: *Self, inst: Air.Inst.Index) !void { | |
| 1114 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | |
| 1115 | if (self.liveness.isUnused(inst)) | |
| 1116 | return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none }); | |
| 1117 | ||
| 1118 | const operand = try self.resolveInst(ty_op.operand); | |
| 1119 | _ = operand; | |
| 1120 | const result: MCValue = switch (arch) { | |
| 1121 | else => return self.fail("TODO implement trunc for {}", .{self.target.cpu.arch}), | |
| 1122 | }; | |
| 1123 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); | |
| 1124 | } | |
| 1125 | ||
| 1112 | 1126 | fn airBoolToInt(self: *Self, inst: Air.Inst.Index) !void { |
| 1113 | 1127 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| 1114 | 1128 | const operand = try self.resolveInst(un_op); |
src/codegen/c.zig+13-2| ... | ... | @@ -900,6 +900,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM |
| 900 | 900 | .call => try airCall(o, inst), |
| 901 | 901 | .dbg_stmt => try airDbgStmt(o, inst), |
| 902 | 902 | .intcast => try airIntCast(o, inst), |
| 903 | .trunc => try airTrunc(o, inst), | |
| 903 | 904 | .bool_to_int => try airBoolToInt(o, inst), |
| 904 | 905 | .load => try airLoad(o, inst), |
| 905 | 906 | .ret => try airRet(o, inst), |
| ... | ... | @@ -1038,7 +1039,7 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { |
| 1038 | 1039 | return CValue.none; |
| 1039 | 1040 | |
| 1040 | 1041 | const ty_op = o.air.instructions.items(.data)[inst].ty_op; |
| 1041 | const from = try o.resolveInst(ty_op.operand); | |
| 1042 | const operand = try o.resolveInst(ty_op.operand); | |
| 1042 | 1043 | |
| 1043 | 1044 | const writer = o.writer(); |
| 1044 | 1045 | const inst_ty = o.air.typeOfIndex(inst); |
| ... | ... | @@ -1046,11 +1047,21 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { |
| 1046 | 1047 | try writer.writeAll(" = ("); |
| 1047 | 1048 | try o.dg.renderType(writer, inst_ty); |
| 1048 | 1049 | try writer.writeAll(")"); |
| 1049 | try o.writeCValue(writer, from); | |
| 1050 | try o.writeCValue(writer, operand); | |
| 1050 | 1051 | try writer.writeAll(";\n"); |
| 1051 | 1052 | return local; |
| 1052 | 1053 | } |
| 1053 | 1054 | |
| 1055 | fn airTrunc(o: *Object, inst: Air.Inst.Index) !CValue { | |
| 1056 | if (o.liveness.isUnused(inst)) | |
| 1057 | return CValue.none; | |
| 1058 | ||
| 1059 | const ty_op = o.air.instructions.items(.data)[inst].ty_op; | |
| 1060 | const operand = try o.resolveInst(ty_op.operand); | |
| 1061 | _ = operand; | |
| 1062 | return o.dg.fail("TODO: C backend: airTrunc", .{}); | |
| 1063 | } | |
| 1064 | ||
| 1054 | 1065 | fn airBoolToInt(o: *Object, inst: Air.Inst.Index) !CValue { |
| 1055 | 1066 | if (o.liveness.isUnused(inst)) |
| 1056 | 1067 | return CValue.none; |
src/codegen/llvm.zig+11| ... | ... | @@ -960,6 +960,7 @@ pub const FuncGen = struct { |
| 960 | 960 | .call => try self.airCall(inst), |
| 961 | 961 | .cond_br => try self.airCondBr(inst), |
| 962 | 962 | .intcast => try self.airIntCast(inst), |
| 963 | .trunc => try self.airTrunc(inst), | |
| 963 | 964 | .floatcast => try self.airFloatCast(inst), |
| 964 | 965 | .ptrtoint => try self.airPtrToInt(inst), |
| 965 | 966 | .load => try self.airLoad(inst), |
| ... | ... | @@ -1615,6 +1616,16 @@ pub const FuncGen = struct { |
| 1615 | 1616 | return self.builder.buildIntCast2(operand, try self.dg.llvmType(inst_ty), llvm.Bool.fromBool(signed), ""); |
| 1616 | 1617 | } |
| 1617 | 1618 | |
| 1619 | fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { | |
| 1620 | if (self.liveness.isUnused(inst)) | |
| 1621 | return null; | |
| 1622 | ||
| 1623 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | |
| 1624 | const operand = try self.resolveInst(ty_op.operand); | |
| 1625 | const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst)); | |
| 1626 | return self.builder.buildTrunc(operand, dest_llvm_ty, ""); | |
| 1627 | } | |
| 1628 | ||
| 1618 | 1629 | fn airFloatCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { |
| 1619 | 1630 | if (self.liveness.isUnused(inst)) |
| 1620 | 1631 | return null; |
src/codegen/llvm/bindings.zig+8| ... | ... | @@ -423,6 +423,14 @@ pub const Builder = opaque { |
| 423 | 423 | Idx: c_uint, |
| 424 | 424 | Name: [*:0]const u8, |
| 425 | 425 | ) *const Value; |
| 426 | ||
| 427 | pub const buildTrunc = LLVMBuildTrunc; | |
| 428 | extern fn LLVMBuildTrunc( | |
| 429 | *const Builder, | |
| 430 | Val: *const Value, | |
| 431 | DestTy: *const Type, | |
| 432 | Name: [*:0]const u8, | |
| 433 | ) *const Value; | |
| 426 | 434 | }; |
| 427 | 435 | |
| 428 | 436 | pub const IntPredicate = enum(c_int) { |
src/print_air.zig+1| ... | ... | @@ -151,6 +151,7 @@ const Writer = struct { |
| 151 | 151 | .load, |
| 152 | 152 | .floatcast, |
| 153 | 153 | .intcast, |
| 154 | .trunc, | |
| 154 | 155 | .optional_payload, |
| 155 | 156 | .optional_payload_ptr, |
| 156 | 157 | .wrap_optional, |
src/value.zig+238| ... | ... | @@ -1407,6 +1407,244 @@ pub const Value = extern union { |
| 1407 | 1407 | }; |
| 1408 | 1408 | } |
| 1409 | 1409 | |
| 1410 | pub fn intAdd(lhs: Value, rhs: Value, allocator: *Allocator) !Value { | |
| 1411 | // TODO is this a performance issue? maybe we should try the operation without | |
| 1412 | // resorting to BigInt first. | |
| 1413 | var lhs_space: Value.BigIntSpace = undefined; | |
| 1414 | var rhs_space: Value.BigIntSpace = undefined; | |
| 1415 | const lhs_bigint = lhs.toBigInt(&lhs_space); | |
| 1416 | const rhs_bigint = rhs.toBigInt(&rhs_space); | |
| 1417 | const limbs = try allocator.alloc( | |
| 1418 | std.math.big.Limb, | |
| 1419 | std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, | |
| 1420 | ); | |
| 1421 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | |
| 1422 | result_bigint.add(lhs_bigint, rhs_bigint); | |
| 1423 | const result_limbs = result_bigint.limbs[0..result_bigint.len]; | |
| 1424 | ||
| 1425 | if (result_bigint.positive) { | |
| 1426 | return Value.Tag.int_big_positive.create(allocator, result_limbs); | |
| 1427 | } else { | |
| 1428 | return Value.Tag.int_big_negative.create(allocator, result_limbs); | |
| 1429 | } | |
| 1430 | } | |
| 1431 | ||
| 1432 | pub fn intSub(lhs: Value, rhs: Value, allocator: *Allocator) !Value { | |
| 1433 | // TODO is this a performance issue? maybe we should try the operation without | |
| 1434 | // resorting to BigInt first. | |
| 1435 | var lhs_space: Value.BigIntSpace = undefined; | |
| 1436 | var rhs_space: Value.BigIntSpace = undefined; | |
| 1437 | const lhs_bigint = lhs.toBigInt(&lhs_space); | |
| 1438 | const rhs_bigint = rhs.toBigInt(&rhs_space); | |
| 1439 | const limbs = try allocator.alloc( | |
| 1440 | std.math.big.Limb, | |
| 1441 | std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, | |
| 1442 | ); | |
| 1443 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | |
| 1444 | result_bigint.sub(lhs_bigint, rhs_bigint); | |
| 1445 | const result_limbs = result_bigint.limbs[0..result_bigint.len]; | |
| 1446 | ||
| 1447 | if (result_bigint.positive) { | |
| 1448 | return Value.Tag.int_big_positive.create(allocator, result_limbs); | |
| 1449 | } else { | |
| 1450 | return Value.Tag.int_big_negative.create(allocator, result_limbs); | |
| 1451 | } | |
| 1452 | } | |
| 1453 | ||
| 1454 | pub fn intDiv(lhs: Value, rhs: Value, allocator: *Allocator) !Value { | |
| 1455 | // TODO is this a performance issue? maybe we should try the operation without | |
| 1456 | // resorting to BigInt first. | |
| 1457 | var lhs_space: Value.BigIntSpace = undefined; | |
| 1458 | var rhs_space: Value.BigIntSpace = undefined; | |
| 1459 | const lhs_bigint = lhs.toBigInt(&lhs_space); | |
| 1460 | const rhs_bigint = rhs.toBigInt(&rhs_space); | |
| 1461 | const limbs_q = try allocator.alloc( | |
| 1462 | std.math.big.Limb, | |
| 1463 | lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, | |
| 1464 | ); | |
| 1465 | const limbs_r = try allocator.alloc( | |
| 1466 | std.math.big.Limb, | |
| 1467 | lhs_bigint.limbs.len, | |
| 1468 | ); | |
| 1469 | const limbs_buffer = try allocator.alloc( | |
| 1470 | std.math.big.Limb, | |
| 1471 | std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len), | |
| 1472 | ); | |
| 1473 | var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined }; | |
| 1474 | var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined }; | |
| 1475 | result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); | |
| 1476 | const result_limbs = result_q.limbs[0..result_q.len]; | |
| 1477 | ||
| 1478 | if (result_q.positive) { | |
| 1479 | return Value.Tag.int_big_positive.create(allocator, result_limbs); | |
| 1480 | } else { | |
| 1481 | return Value.Tag.int_big_negative.create(allocator, result_limbs); | |
| 1482 | } | |
| 1483 | } | |
| 1484 | ||
| 1485 | pub fn intMul(lhs: Value, rhs: Value, allocator: *Allocator) !Value { | |
| 1486 | // TODO is this a performance issue? maybe we should try the operation without | |
| 1487 | // resorting to BigInt first. | |
| 1488 | var lhs_space: Value.BigIntSpace = undefined; | |
| 1489 | var rhs_space: Value.BigIntSpace = undefined; | |
| 1490 | const lhs_bigint = lhs.toBigInt(&lhs_space); | |
| 1491 | const rhs_bigint = rhs.toBigInt(&rhs_space); | |
| 1492 | const limbs = try allocator.alloc( | |
| 1493 | std.math.big.Limb, | |
| 1494 | lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, | |
| 1495 | ); | |
| 1496 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | |
| 1497 | var limbs_buffer = try allocator.alloc( | |
| 1498 | std.math.big.Limb, | |
| 1499 | std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1), | |
| 1500 | ); | |
| 1501 | defer allocator.free(limbs_buffer); | |
| 1502 | result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator); | |
| 1503 | const result_limbs = result_bigint.limbs[0..result_bigint.len]; | |
| 1504 | ||
| 1505 | if (result_bigint.positive) { | |
| 1506 | return Value.Tag.int_big_positive.create(allocator, result_limbs); | |
| 1507 | } else { | |
| 1508 | return Value.Tag.int_big_negative.create(allocator, result_limbs); | |
| 1509 | } | |
| 1510 | } | |
| 1511 | ||
| 1512 | pub fn intTrunc(val: Value, arena: *Allocator, bits: u16) !Value { | |
| 1513 | const x = val.toUnsignedInt(); // TODO: implement comptime truncate on big ints | |
| 1514 | if (bits == 64) return val; | |
| 1515 | const mask = (@as(u64, 1) << @intCast(u6, bits)) - 1; | |
| 1516 | const truncated = x & mask; | |
| 1517 | return Tag.int_u64.create(arena, truncated); | |
| 1518 | } | |
| 1519 | ||
| 1520 | pub fn floatAdd( | |
| 1521 | lhs: Value, | |
| 1522 | rhs: Value, | |
| 1523 | float_type: Type, | |
| 1524 | arena: *Allocator, | |
| 1525 | ) !Value { | |
| 1526 | switch (float_type.tag()) { | |
| 1527 | .f16 => { | |
| 1528 | @panic("TODO add __trunctfhf2 to compiler-rt"); | |
| 1529 | //const lhs_val = lhs.toFloat(f16); | |
| 1530 | //const rhs_val = rhs.toFloat(f16); | |
| 1531 | //return Value.Tag.float_16.create(arena, lhs_val + rhs_val); | |
| 1532 | }, | |
| 1533 | .f32 => { | |
| 1534 | const lhs_val = lhs.toFloat(f32); | |
| 1535 | const rhs_val = rhs.toFloat(f32); | |
| 1536 | return Value.Tag.float_32.create(arena, lhs_val + rhs_val); | |
| 1537 | }, | |
| 1538 | .f64 => { | |
| 1539 | const lhs_val = lhs.toFloat(f64); | |
| 1540 | const rhs_val = rhs.toFloat(f64); | |
| 1541 | return Value.Tag.float_64.create(arena, lhs_val + rhs_val); | |
| 1542 | }, | |
| 1543 | .f128, .comptime_float, .c_longdouble => { | |
| 1544 | const lhs_val = lhs.toFloat(f128); | |
| 1545 | const rhs_val = rhs.toFloat(f128); | |
| 1546 | return Value.Tag.float_128.create(arena, lhs_val + rhs_val); | |
| 1547 | }, | |
| 1548 | else => unreachable, | |
| 1549 | } | |
| 1550 | } | |
| 1551 | ||
| 1552 | pub fn floatSub( | |
| 1553 | lhs: Value, | |
| 1554 | rhs: Value, | |
| 1555 | float_type: Type, | |
| 1556 | arena: *Allocator, | |
| 1557 | ) !Value { | |
| 1558 | switch (float_type.tag()) { | |
| 1559 | .f16 => { | |
| 1560 | @panic("TODO add __trunctfhf2 to compiler-rt"); | |
| 1561 | //const lhs_val = lhs.toFloat(f16); | |
| 1562 | //const rhs_val = rhs.toFloat(f16); | |
| 1563 | //return Value.Tag.float_16.create(arena, lhs_val - rhs_val); | |
| 1564 | }, | |
| 1565 | .f32 => { | |
| 1566 | const lhs_val = lhs.toFloat(f32); | |
| 1567 | const rhs_val = rhs.toFloat(f32); | |
| 1568 | return Value.Tag.float_32.create(arena, lhs_val - rhs_val); | |
| 1569 | }, | |
| 1570 | .f64 => { | |
| 1571 | const lhs_val = lhs.toFloat(f64); | |
| 1572 | const rhs_val = rhs.toFloat(f64); | |
| 1573 | return Value.Tag.float_64.create(arena, lhs_val - rhs_val); | |
| 1574 | }, | |
| 1575 | .f128, .comptime_float, .c_longdouble => { | |
| 1576 | const lhs_val = lhs.toFloat(f128); | |
| 1577 | const rhs_val = rhs.toFloat(f128); | |
| 1578 | return Value.Tag.float_128.create(arena, lhs_val - rhs_val); | |
| 1579 | }, | |
| 1580 | else => unreachable, | |
| 1581 | } | |
| 1582 | } | |
| 1583 | ||
| 1584 | pub fn floatDiv( | |
| 1585 | lhs: Value, | |
| 1586 | rhs: Value, | |
| 1587 | float_type: Type, | |
| 1588 | arena: *Allocator, | |
| 1589 | ) !Value { | |
| 1590 | switch (float_type.tag()) { | |
| 1591 | .f16 => { | |
| 1592 | @panic("TODO add __trunctfhf2 to compiler-rt"); | |
| 1593 | //const lhs_val = lhs.toFloat(f16); | |
| 1594 | //const rhs_val = rhs.toFloat(f16); | |
| 1595 | //return Value.Tag.float_16.create(arena, lhs_val / rhs_val); | |
| 1596 | }, | |
| 1597 | .f32 => { | |
| 1598 | const lhs_val = lhs.toFloat(f32); | |
| 1599 | const rhs_val = rhs.toFloat(f32); | |
| 1600 | return Value.Tag.float_32.create(arena, lhs_val / rhs_val); | |
| 1601 | }, | |
| 1602 | .f64 => { | |
| 1603 | const lhs_val = lhs.toFloat(f64); | |
| 1604 | const rhs_val = rhs.toFloat(f64); | |
| 1605 | return Value.Tag.float_64.create(arena, lhs_val / rhs_val); | |
| 1606 | }, | |
| 1607 | .f128, .comptime_float, .c_longdouble => { | |
| 1608 | const lhs_val = lhs.toFloat(f128); | |
| 1609 | const rhs_val = rhs.toFloat(f128); | |
| 1610 | return Value.Tag.float_128.create(arena, lhs_val / rhs_val); | |
| 1611 | }, | |
| 1612 | else => unreachable, | |
| 1613 | } | |
| 1614 | } | |
| 1615 | ||
| 1616 | pub fn floatMul( | |
| 1617 | lhs: Value, | |
| 1618 | rhs: Value, | |
| 1619 | float_type: Type, | |
| 1620 | arena: *Allocator, | |
| 1621 | ) !Value { | |
| 1622 | switch (float_type.tag()) { | |
| 1623 | .f16 => { | |
| 1624 | @panic("TODO add __trunctfhf2 to compiler-rt"); | |
| 1625 | //const lhs_val = lhs.toFloat(f16); | |
| 1626 | //const rhs_val = rhs.toFloat(f16); | |
| 1627 | //return Value.Tag.float_16.create(arena, lhs_val * rhs_val); | |
| 1628 | }, | |
| 1629 | .f32 => { | |
| 1630 | const lhs_val = lhs.toFloat(f32); | |
| 1631 | const rhs_val = rhs.toFloat(f32); | |
| 1632 | return Value.Tag.float_32.create(arena, lhs_val * rhs_val); | |
| 1633 | }, | |
| 1634 | .f64 => { | |
| 1635 | const lhs_val = lhs.toFloat(f64); | |
| 1636 | const rhs_val = rhs.toFloat(f64); | |
| 1637 | return Value.Tag.float_64.create(arena, lhs_val * rhs_val); | |
| 1638 | }, | |
| 1639 | .f128, .comptime_float, .c_longdouble => { | |
| 1640 | const lhs_val = lhs.toFloat(f128); | |
| 1641 | const rhs_val = rhs.toFloat(f128); | |
| 1642 | return Value.Tag.float_128.create(arena, lhs_val * rhs_val); | |
| 1643 | }, | |
| 1644 | else => unreachable, | |
| 1645 | } | |
| 1646 | } | |
| 1647 | ||
| 1410 | 1648 | /// This type is not copyable since it may contain pointers to its inner data. |
| 1411 | 1649 | pub const Payload = struct { |
| 1412 | 1650 | tag: Tag, |
test/behavior/basic.zig+11| ... | ... | @@ -1,3 +1,6 @@ |
| 1 | const std = @import("std"); | |
| 2 | const expect = std.testing.expect; | |
| 3 | ||
| 1 | 4 | // normal comment |
| 2 | 5 | |
| 3 | 6 | /// this is a documentation comment |
| ... | ... | @@ -7,3 +10,11 @@ fn emptyFunctionWithComments() void {} |
| 7 | 10 | test "empty function with comments" { |
| 8 | 11 | emptyFunctionWithComments(); |
| 9 | 12 | } |
| 13 | ||
| 14 | test "truncate" { | |
| 15 | try expect(testTruncate(0x10fd) == 0xfd); | |
| 16 | comptime try expect(testTruncate(0x10fd) == 0xfd); | |
| 17 | } | |
| 18 | fn testTruncate(x: u32) u8 { | |
| 19 | return @truncate(u8, x); | |
| 20 | } |
test/behavior/misc.zig-7| ... | ... | @@ -5,13 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings; |
| 5 | 5 | const mem = std.mem; |
| 6 | 6 | const builtin = @import("builtin"); |
| 7 | 7 | |
| 8 | test "truncate" { | |
| 9 | try expect(testTruncate(0x10fd) == 0xfd); | |
| 10 | } | |
| 11 | fn testTruncate(x: u32) u8 { | |
| 12 | return @truncate(u8, x); | |
| 13 | } | |
| 14 | ||
| 15 | 8 | fn first4KeysOfHomeRow() []const u8 { |
| 16 | 9 | return "aoeu"; |
| 17 | 10 | } |