From bbfb53d52411cc5b1f560293c757bff252e1e06f Mon Sep 17 00:00:00 2001 From: Shawn Landden Date: Wed, 19 Jun 2019 12:06:42 -0500 Subject: [PATCH 01/15] c: add fma and fmaf --- std/special/c.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/std/special/c.zig b/std/special/c.zig index 456070f609620b2cc58a90b8a95ab75fb9d35de6..b3cf54619feccad4bb236d02ace956fe95bcc5f4 100644 --- a/std/special/c.zig +++ b/std/special/c.zig @@ -266,7 +266,12 @@ export fn floor(x: f64) f64 { export fn ceil(x: f64) f64 { return math.ceil(x); } - +export fn fma(a: f64, b: f64, c: f64) f64 { + return math.fma(f64, a, b, c); +} +export fn fmaf(a: f32, b: f32, c: f32) f32 { + return math.fma(f32, a, b, c); +} fn generic_fmod(comptime T: type, x: T, y: T) T { @setRuntimeSafety(false); -- 2.54.0 From fce2d2d18be279359dcd75254506d46085c59aaf Mon Sep 17 00:00:00 2001 From: Shawn Landden Date: Tue, 18 Jun 2019 17:28:49 -0500 Subject: [PATCH 02/15] stage1: add support for @mulAdd fused-multiply-add for floats and vectors of floats Not all of the softfloat library is being built.... Vector support is very buggy at the moment, but should work when the bugs are fixed. (as I had the same code working with another vector function, that hasn't been merged yet). --- CMakeLists.txt | 2 + doc/langref.html.in | 7 ++ src/all_types.hpp | 13 +++ src/analyze.cpp | 7 +- src/codegen.cpp | 46 ++++++++- src/ir.cpp | 171 ++++++++++++++++++++++++++++++++ src/ir_print.cpp | 19 ++++ test/stage1/behavior/muladd.zig | 34 +++++++ 8 files changed, 292 insertions(+), 7 deletions(-) create mode 100644 test/stage1/behavior/muladd.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index d6f8176e4096c491579169880f55197470b758ff..6a06afc9fabaaa5a5726b5573e6e57eea456382c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -389,6 +389,8 @@ set(EMBEDDED_SOFTFLOAT_SOURCES "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/s_subMagsF32.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/s_subMagsF64.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/s_tryPropagateNaNF128M.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_mulAdd.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f128M_mulAdd.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/softfloat_state.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/ui32_to_f128M.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/ui64_to_f128M.c" diff --git a/doc/langref.html.in b/doc/langref.html.in index 5ddd572e514ff8899e0bf220516b863471a2573c..9b95946256da548e253dbbf7a5b507849b3e4496 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -6259,6 +6259,13 @@ comptime { This function is only valid within function scope.

+ {#header_close#} + {#header_open|@mulAdd#} +
{#syntax#}@mulAdd(comptime T: type, a: T, b: T, c: T) T{#endsyntax#}
+

+ Fused multiply add (for floats), similar to {#syntax#}(a * b) + c{#endsyntax#}, except + only rounds once, and is thus more accurate. +

{#header_close#} {#header_open|@byteSwap#} diff --git a/src/all_types.hpp b/src/all_types.hpp index 5aa1c78ea1efc9a31fa7a5dfd84da2cd313b7734..83df71b95f487b30cc7c084f1952ba2534ac1853 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1406,6 +1406,7 @@ enum BuiltinFnId { BuiltinFnIdSubWithOverflow, BuiltinFnIdMulWithOverflow, BuiltinFnIdShlWithOverflow, + BuiltinFnIdMulAdd, BuiltinFnIdCInclude, BuiltinFnIdCDefine, BuiltinFnIdCUndef, @@ -1554,6 +1555,7 @@ enum ZigLLVMFnId { ZigLLVMFnIdClz, ZigLLVMFnIdPopCount, ZigLLVMFnIdOverflowArithmetic, + ZigLLVMFnIdFMA, ZigLLVMFnIdFloor, ZigLLVMFnIdCeil, ZigLLVMFnIdSqrt, @@ -1584,6 +1586,7 @@ struct ZigLLVMFnKey { } pop_count; struct { uint32_t bit_count; + uint32_t vector_len; // 0 means not a vector } floating; struct { AddSubMul add_sub_mul; @@ -2235,6 +2238,7 @@ enum IrInstructionId { IrInstructionIdHandle, IrInstructionIdAlignOf, IrInstructionIdOverflowOp, + IrInstructionIdMulAdd, IrInstructionIdTestErr, IrInstructionIdUnwrapErrCode, IrInstructionIdUnwrapErrPayload, @@ -3038,6 +3042,15 @@ struct IrInstructionOverflowOp { ZigType *result_ptr_type; }; +struct IrInstructionMulAdd { + IrInstruction base; + + IrInstruction *type_value; + IrInstruction *op1; + IrInstruction *op2; + IrInstruction *op3; +}; + struct IrInstructionAlignOf { IrInstruction base; diff --git a/src/analyze.cpp b/src/analyze.cpp index c7e35367c33e262be1fa4d247c4be91df293c05c..bff740cd52cfafe93bca64c273664ede6bf7eb1d 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -5737,11 +5737,11 @@ uint32_t zig_llvm_fn_key_hash(ZigLLVMFnKey x) { case ZigLLVMFnIdPopCount: return (uint32_t)(x.data.clz.bit_count) * (uint32_t)101195049; case ZigLLVMFnIdFloor: - return (uint32_t)(x.data.floating.bit_count) * (uint32_t)1899859168; case ZigLLVMFnIdCeil: - return (uint32_t)(x.data.floating.bit_count) * (uint32_t)1953839089; case ZigLLVMFnIdSqrt: - return (uint32_t)(x.data.floating.bit_count) * (uint32_t)2225366385; + case ZigLLVMFnIdFMA: + return (uint32_t)(x.data.floating.bit_count) * ((uint32_t)x.id + 1025) + + (uint32_t)(x.data.floating.vector_len) * (((uint32_t)x.id << 5) + 1025); case ZigLLVMFnIdBswap: return (uint32_t)(x.data.bswap.bit_count) * (uint32_t)3661994335; case ZigLLVMFnIdBitReverse: @@ -5772,6 +5772,7 @@ bool zig_llvm_fn_key_eql(ZigLLVMFnKey a, ZigLLVMFnKey b) { case ZigLLVMFnIdFloor: case ZigLLVMFnIdCeil: case ZigLLVMFnIdSqrt: + case ZigLLVMFnIdFMA: return a.data.floating.bit_count == b.data.floating.bit_count; case ZigLLVMFnIdOverflowArithmetic: return (a.data.overflow_arithmetic.bit_count == b.data.overflow_arithmetic.bit_count) && diff --git a/src/codegen.cpp b/src/codegen.cpp index 3dd6995c61ccca380332e5e802be9697c62e46d2..6691652a5e04a25b63d5e6ee4a2b891d3bc0e298 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -807,31 +807,51 @@ static LLVMValueRef get_int_overflow_fn(CodeGen *g, ZigType *operand_type, AddSu } static LLVMValueRef get_float_fn(CodeGen *g, ZigType *type_entry, ZigLLVMFnId fn_id) { - assert(type_entry->id == ZigTypeIdFloat); + assert(type_entry->id == ZigTypeIdFloat || + type_entry->id == ZigTypeIdVector); + + bool is_vector = (type_entry->id == ZigTypeIdVector); + ZigType *float_type = is_vector ? type_entry->data.vector.elem_type : type_entry; ZigLLVMFnKey key = {}; key.id = fn_id; - key.data.floating.bit_count = (uint32_t)type_entry->data.floating.bit_count; + key.data.floating.bit_count = (uint32_t)float_type->data.floating.bit_count; + key.data.floating.vector_len = is_vector ? (uint32_t)type_entry->data.vector.len : 0; auto existing_entry = g->llvm_fn_table.maybe_get(key); if (existing_entry) return existing_entry->value; const char *name; + uint32_t num_args; if (fn_id == ZigLLVMFnIdFloor) { name = "floor"; + num_args = 1; } else if (fn_id == ZigLLVMFnIdCeil) { name = "ceil"; + num_args = 1; } else if (fn_id == ZigLLVMFnIdSqrt) { name = "sqrt"; + num_args = 1; + } else if (fn_id == ZigLLVMFnIdFMA) { + name = "fma"; + num_args = 3; } else { zig_unreachable(); } char fn_name[64]; - sprintf(fn_name, "llvm.%s.f%" ZIG_PRI_usize "", name, type_entry->data.floating.bit_count); + if (is_vector) + sprintf(fn_name, "llvm.%s.v%" PRIu32 "f%" PRIu32, name, key.data.floating.vector_len, key.data.floating.bit_count); + else + sprintf(fn_name, "llvm.%s.f%" PRIu32, name, key.data.floating.bit_count); LLVMTypeRef float_type_ref = get_llvm_type(g, type_entry); - LLVMTypeRef fn_type = LLVMFunctionType(float_type_ref, &float_type_ref, 1, false); + LLVMTypeRef return_elem_types[3] = { + float_type_ref, + float_type_ref, + float_type_ref, + }; + LLVMTypeRef fn_type = LLVMFunctionType(float_type_ref, return_elem_types, num_args, false); LLVMValueRef fn_val = LLVMAddFunction(g->module, fn_name, fn_type); assert(LLVMGetIntrinsicID(fn_val)); @@ -5437,6 +5457,21 @@ static LLVMValueRef ir_render_sqrt(CodeGen *g, IrExecutable *executable, IrInstr return LLVMBuildCall(g->builder, fn_val, &op, 1, ""); } +static LLVMValueRef ir_render_mul_add(CodeGen *g, IrExecutable *executable, IrInstructionMulAdd *instruction) { + LLVMValueRef op1 = ir_llvm_value(g, instruction->op1); + LLVMValueRef op2 = ir_llvm_value(g, instruction->op2); + LLVMValueRef op3 = ir_llvm_value(g, instruction->op3); + assert(instruction->base.value.type->id == ZigTypeIdFloat || + instruction->base.value.type->id == ZigTypeIdVector); + LLVMValueRef fn_val = get_float_fn(g, instruction->base.value.type, ZigLLVMFnIdFMA); + LLVMValueRef args[3] = { + op1, + op2, + op3, + }; + return LLVMBuildCall(g->builder, fn_val, args, 3, ""); +} + static LLVMValueRef ir_render_bswap(CodeGen *g, IrExecutable *executable, IrInstructionBswap *instruction) { LLVMValueRef op = ir_llvm_value(g, instruction->op); ZigType *int_type = instruction->base.value.type; @@ -5781,6 +5816,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, return ir_render_mark_err_ret_trace_ptr(g, executable, (IrInstructionMarkErrRetTracePtr *)instruction); case IrInstructionIdSqrt: return ir_render_sqrt(g, executable, (IrInstructionSqrt *)instruction); + case IrInstructionIdMulAdd: + return ir_render_mul_add(g, executable, (IrInstructionMulAdd *)instruction); case IrInstructionIdArrayToVector: return ir_render_array_to_vector(g, executable, (IrInstructionArrayToVector *)instruction); case IrInstructionIdVectorToArray: @@ -7398,6 +7435,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdRem, "rem", 2); create_builtin_fn(g, BuiltinFnIdMod, "mod", 2); create_builtin_fn(g, BuiltinFnIdSqrt, "sqrt", 2); + create_builtin_fn(g, BuiltinFnIdMulAdd, "mulAdd", 4); create_builtin_fn(g, BuiltinFnIdInlineCall, "inlineCall", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdNoInlineCall, "noInlineCall", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdNewStackCall, "newStackCall", SIZE_MAX); diff --git a/src/ir.cpp b/src/ir.cpp index 5c09e48b2d7c209d32f71c253b4aef54e148c944..c2c6cb615416f984fb8356514bdc41e59b246580 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -747,6 +747,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionTestErr *) { return IrInstructionIdTestErr; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionMulAdd *) { + return IrInstructionIdMulAdd; +} + static constexpr IrInstructionId ir_instruction_id(IrInstructionUnwrapErrCode *) { return IrInstructionIdUnwrapErrCode; } @@ -2308,6 +2312,22 @@ static IrInstruction *ir_build_overflow_op(IrBuilder *irb, Scope *scope, AstNode return &instruction->base; } +static IrInstruction *ir_build_mul_add(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *type_value, IrInstruction *op1, IrInstruction *op2, IrInstruction *op3) { + IrInstructionMulAdd *instruction = ir_build_instruction(irb, scope, source_node); + instruction->type_value = type_value; + instruction->op1 = op1; + instruction->op2 = op2; + instruction->op3 = op3; + + ir_ref_instruction(type_value, irb->current_basic_block); + ir_ref_instruction(op1, irb->current_basic_block); + ir_ref_instruction(op2, irb->current_basic_block); + ir_ref_instruction(op3, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_build_align_of(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *type_value) { IrInstructionAlignOf *instruction = ir_build_instruction(irb, scope, source_node); instruction->type_value = type_value; @@ -4028,6 +4048,33 @@ static IrInstruction *ir_gen_overflow_op(IrBuilder *irb, Scope *scope, AstNode * return ir_build_overflow_op(irb, scope, node, op, type_value, op1, op2, result_ptr, nullptr); } +static IrInstruction *ir_gen_mul_add(IrBuilder *irb, Scope *scope, AstNode *node) { + assert(node->type == NodeTypeFnCallExpr); + + AstNode *type_node = node->data.fn_call_expr.params.at(0); + AstNode *op1_node = node->data.fn_call_expr.params.at(1); + AstNode *op2_node = node->data.fn_call_expr.params.at(2); + AstNode *op3_node = node->data.fn_call_expr.params.at(3); + + IrInstruction *type_value = ir_gen_node(irb, type_node, scope); + if (type_value == irb->codegen->invalid_instruction) + return irb->codegen->invalid_instruction; + + IrInstruction *op1 = ir_gen_node(irb, op1_node, scope); + if (op1 == irb->codegen->invalid_instruction) + return irb->codegen->invalid_instruction; + + IrInstruction *op2 = ir_gen_node(irb, op2_node, scope); + if (op2 == irb->codegen->invalid_instruction) + return irb->codegen->invalid_instruction; + + IrInstruction *op3 = ir_gen_node(irb, op3_node, scope); + if (op3 == irb->codegen->invalid_instruction) + return irb->codegen->invalid_instruction; + + return ir_build_mul_add(irb, scope, node, type_value, op1, op2, op3); +} + static IrInstruction *ir_gen_this(IrBuilder *irb, Scope *orig_scope, AstNode *node) { for (Scope *it_scope = orig_scope; it_scope != nullptr; it_scope = it_scope->parent) { if (it_scope->id == ScopeIdDecls) { @@ -4687,6 +4734,8 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_lval_wrap(irb, scope, ir_gen_overflow_op(irb, scope, node, IrOverflowOpMul), lval); case BuiltinFnIdShlWithOverflow: return ir_lval_wrap(irb, scope, ir_gen_overflow_op(irb, scope, node, IrOverflowOpShl), lval); + case BuiltinFnIdMulAdd: + return ir_lval_wrap(irb, scope, ir_gen_mul_add(irb, scope, node), lval); case BuiltinFnIdTypeName: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -21185,6 +21234,125 @@ static IrInstruction *ir_analyze_instruction_overflow_op(IrAnalyze *ira, IrInstr return result; } +static void ir_eval_mul_add(IrAnalyze *ira, IrInstructionMulAdd *source_instr, ZigType *float_type, + ConstExprValue *op1, ConstExprValue *op2, ConstExprValue *op3, ConstExprValue *out_val) { + if (float_type->id == ZigTypeIdComptimeFloat) { + f128M_mulAdd(&out_val->data.x_bigfloat.value, &op1->data.x_bigfloat.value, &op2->data.x_bigfloat.value, + &op3->data.x_bigfloat.value); + } else if (float_type->id == ZigTypeIdFloat) { + switch (float_type->data.floating.bit_count) { + case 16: + out_val->data.x_f16 = f16_mulAdd(op1->data.x_f16, op2->data.x_f16, op3->data.x_f16); + break; + case 32: + out_val->data.x_f32 = fmaf(op1->data.x_f32, op2->data.x_f32, op3->data.x_f32); + break; + case 64: + out_val->data.x_f64 = fma(op1->data.x_f64, op2->data.x_f64, op3->data.x_f64); + break; + case 128: + f128M_mulAdd(&op1->data.x_f128, &op2->data.x_f128, &op3->data.x_f128, &out_val->data.x_f128); + break; + default: + zig_unreachable(); + } + } else { + zig_unreachable(); + } +} + +static IrInstruction *ir_analyze_instruction_mul_add(IrAnalyze *ira, IrInstructionMulAdd *instruction) { + IrInstruction *type_value = instruction->type_value->child; + if (type_is_invalid(type_value->value.type)) + return ira->codegen->invalid_instruction; + + ZigType *expr_type = ir_resolve_type(ira, type_value); + if (type_is_invalid(expr_type)) + return ira->codegen->invalid_instruction; + + // Only allow float types, and vectors of floats. + ZigType *float_type = (expr_type->id == ZigTypeIdVector) ? expr_type->data.vector.elem_type : expr_type; + if (float_type->id != ZigTypeIdFloat) { + ir_add_error(ira, type_value, + buf_sprintf("expected float or vector of float type, found '%s'", buf_ptr(&float_type->name))); + return ira->codegen->invalid_instruction; + } + + IrInstruction *op1 = instruction->op1->child; + if (type_is_invalid(op1->value.type)) + return ira->codegen->invalid_instruction; + + IrInstruction *casted_op1 = ir_implicit_cast(ira, op1, expr_type); + if (type_is_invalid(casted_op1->value.type)) + return ira->codegen->invalid_instruction; + + IrInstruction *op2 = instruction->op2->child; + if (type_is_invalid(op2->value.type)) + return ira->codegen->invalid_instruction; + + IrInstruction *casted_op2 = ir_implicit_cast(ira, op2, expr_type); + if (type_is_invalid(casted_op2->value.type)) + return ira->codegen->invalid_instruction; + + IrInstruction *op3 = instruction->op3->child; + if (type_is_invalid(op3->value.type)) + return ira->codegen->invalid_instruction; + + IrInstruction *casted_op3 = ir_implicit_cast(ira, op3, expr_type); + if (type_is_invalid(casted_op3->value.type)) + return ira->codegen->invalid_instruction; + + if (instr_is_comptime(casted_op1) && + instr_is_comptime(casted_op2) && + instr_is_comptime(casted_op3)) { + ConstExprValue *op1_const = ir_resolve_const(ira, casted_op1, UndefBad); + if (!op1_const) + return ira->codegen->invalid_instruction; + ConstExprValue *op2_const = ir_resolve_const(ira, casted_op2, UndefBad); + if (!op2_const) + return ira->codegen->invalid_instruction; + ConstExprValue *op3_const = ir_resolve_const(ira, casted_op3, UndefBad); + if (!op3_const) + return ira->codegen->invalid_instruction; + + IrInstruction *result = ir_const(ira, &instruction->base, expr_type); + ConstExprValue *out_val = &result->value; + + if (expr_type->id == ZigTypeIdVector) { + expand_undef_array(ira->codegen, op1_const); + expand_undef_array(ira->codegen, op2_const); + expand_undef_array(ira->codegen, op3_const); + out_val->special = ConstValSpecialUndef; + expand_undef_array(ira->codegen, out_val); + size_t len = expr_type->data.vector.len; + for (size_t i = 0; i < len; i += 1) { + ConstExprValue *float_operand_op1 = &op1_const->data.x_array.data.s_none.elements[i]; + ConstExprValue *float_operand_op2 = &op2_const->data.x_array.data.s_none.elements[i]; + ConstExprValue *float_operand_op3 = &op3_const->data.x_array.data.s_none.elements[i]; + ConstExprValue *float_out_val = &out_val->data.x_array.data.s_none.elements[i]; + assert(float_operand_op1->type == float_type); + assert(float_operand_op2->type == float_type); + assert(float_operand_op3->type == float_type); + assert(float_out_val->type == float_type); + ir_eval_mul_add(ira, instruction, float_type, + op1_const, op2_const, op3_const, float_out_val); + float_out_val->type = float_type; + } + out_val->type = expr_type; + out_val->special = ConstValSpecialStatic; + } else { + ir_eval_mul_add(ira, instruction, float_type, op1_const, op2_const, op3_const, out_val); + } + return result; + } + + IrInstruction *result = ir_build_mul_add(&ira->new_irb, + instruction->base.scope, instruction->base.source_node, + type_value, casted_op1, casted_op2, casted_op3); + result->value.type = expr_type; + return result; +} + static IrInstruction *ir_analyze_instruction_test_err(IrAnalyze *ira, IrInstructionTestErr *instruction) { IrInstruction *value = instruction->value->child; if (type_is_invalid(value->value.type)) @@ -23596,6 +23764,8 @@ static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructio return ir_analyze_instruction_mark_err_ret_trace_ptr(ira, (IrInstructionMarkErrRetTracePtr *)instruction); case IrInstructionIdSqrt: return ir_analyze_instruction_sqrt(ira, (IrInstructionSqrt *)instruction); + case IrInstructionIdMulAdd: + return ir_analyze_instruction_mul_add(ira, (IrInstructionMulAdd *)instruction); case IrInstructionIdIntToErr: return ir_analyze_instruction_int_to_err(ira, (IrInstructionIntToErr *)instruction); case IrInstructionIdErrToInt: @@ -23835,6 +24005,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdCoroPromise: case IrInstructionIdPromiseResultType: case IrInstructionIdSqrt: + case IrInstructionIdMulAdd: case IrInstructionIdAtomicLoad: case IrInstructionIdIntCast: case IrInstructionIdFloatCast: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index bf9ced89c5eee36a513003366813c68f71495b4e..e205c8e067c039e0fa381e93e2df822c202f1045 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -1439,6 +1439,22 @@ static void ir_print_sqrt(IrPrint *irp, IrInstructionSqrt *instruction) { fprintf(irp->f, ")"); } +static void ir_print_mul_add(IrPrint *irp, IrInstructionMulAdd *instruction) { + fprintf(irp->f, "@mulAdd("); + if (instruction->type_value != nullptr) { + ir_print_other_instruction(irp, instruction->type_value); + } else { + fprintf(irp->f, "null"); + } + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->op1); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->op2); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->op3); + fprintf(irp->f, ")"); +} + static void ir_print_decl_var_gen(IrPrint *irp, IrInstructionDeclVarGen *decl_var_instruction) { ZigVar *var = decl_var_instruction->var; const char *var_or_const = decl_var_instruction->var->gen_is_const ? "const" : "var"; @@ -1905,6 +1921,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdSqrt: ir_print_sqrt(irp, (IrInstructionSqrt *)instruction); break; + case IrInstructionIdMulAdd: + ir_print_mul_add(irp, (IrInstructionMulAdd *)instruction); + break; case IrInstructionIdAtomicLoad: ir_print_atomic_load(irp, (IrInstructionAtomicLoad *)instruction); break; diff --git a/test/stage1/behavior/muladd.zig b/test/stage1/behavior/muladd.zig new file mode 100644 index 0000000000000000000000000000000000000000..143e6a93e431e2c120b646ec3d8bef801e9b3e2a --- /dev/null +++ b/test/stage1/behavior/muladd.zig @@ -0,0 +1,34 @@ +const expect = @import("std").testing.expect; + +test "@mulAdd" { + comptime testMulAdd(); + testMulAdd(); +} + +fn testMulAdd() void { + { + var a: f16 = 5.5; + var b: f16 = 2.5; + var c: f16 = 6.25; + expect(@mulAdd(f16, a, b, c) == 20); + } + { + var a: f32 = 5.5; + var b: f32 = 2.5; + var c: f32 = 6.25; + expect(@mulAdd(f32, a, b, c) == 20); + } + { + var a: f64 = 5.5; + var b: f64 = 2.5; + var c: f64 = 6.25; + expect(@mulAdd(f64, a, b, c) == 20); + } + // Awaits implementation in libm.zig + //{ + // var a: f16 = 5.5; + // var b: f128 = 2.5; + // var c: f128 = 6.25; + // expect(@mulAdd(f128, a, b, c) == 20); + //} +} \ No newline at end of file -- 2.54.0 From 39ad072a84af006ab89b72177524a859a50bb662 Mon Sep 17 00:00:00 2001 From: Shawn Landden Date: Wed, 19 Jun 2019 11:48:30 -0500 Subject: [PATCH 03/15] test: include muladd.zig in behavior tests --- test/stage1/behavior.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index 707d46fd8cc89d420d3b3177bff308dccb7fa1ee..10e7c1a09beb6ff3a6d622da1f9a061f51b05d4f 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -70,6 +70,7 @@ comptime { _ = @import("behavior/optional.zig"); _ = @import("behavior/pointers.zig"); _ = @import("behavior/popcount.zig"); + _ = @import("behavior/muladd.zig"); _ = @import("behavior/ptrcast.zig"); _ = @import("behavior/pub_enum.zig"); _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig"); -- 2.54.0 From 11526b6e9da75ac682e59fbc2a37a738b8a23d6f Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Thu, 20 Jun 2019 20:07:43 +1200 Subject: [PATCH 04/15] breaking: Add positional, precision and width support to std.fmt This removes the odd width and precision specifiers found and replacing them with the more consistent api described in #1358. Take the following example: {1:5.9} This refers to the first argument (0-indexed) in the argument list. It will be printed with a minimum width of 5 and will have a precision of 9 (if applicable). Not all types correctly use these parameters just yet. There are still some missing gaps to fill in. Fill characters and alignment have yet to be implemented. --- src-self-hosted/dep_tokenizer.zig | 2 +- std/fmt.zig | 712 +++++++++++++++--------------- std/math/big/int.zig | 1 + std/special/build_runner.zig | 4 +- test/compare_output.zig | 2 +- 5 files changed, 354 insertions(+), 367 deletions(-) diff --git a/src-self-hosted/dep_tokenizer.zig b/src-self-hosted/dep_tokenizer.zig index 796202499a8d1bdaa6421b32ea95b7bd6fbf3c29..882cf92c3d0ad1121bced282d4cab3479e748f10 100644 --- a/src-self-hosted/dep_tokenizer.zig +++ b/src-self-hosted/dep_tokenizer.zig @@ -999,7 +999,7 @@ fn printCharValues(out: var, bytes: []const u8) !void { fn printUnderstandableChar(out: var, char: u8) !void { if (!std.ascii.isPrint(char) or char == ' ') { - std.fmt.format(out.context, anyerror, out.output, "\\x{X2}", char) catch {}; + std.fmt.format(out.context, anyerror, out.output, "\\x{X:2}", char) catch {}; } else { try out.write("'"); try out.write([_]u8{printable_char_tab[char]}); diff --git a/std/fmt.zig b/std/fmt.zig index 7bf1fa3d4df3c22ce70b89d0e4aaed4b10fdb18e..4d9ef168fb23b3e48e57c36f67db14b70aaf6c0b 100644 --- a/std/fmt.zig +++ b/std/fmt.zig @@ -10,6 +10,22 @@ const lossyCast = std.math.lossyCast; pub const default_max_depth = 3; +pub const FormatOptions = struct { + precision: ?usize = null, + width: ?usize = null, +}; + +fn nextArg(comptime used_pos_args: *u32, comptime maybe_pos_arg: ?comptime_int, comptime next_arg: *comptime_int) comptime_int { + if (maybe_pos_arg) |pos_arg| { + used_pos_args.* |= 1 << pos_arg; + return pos_arg; + } else { + const arg = next_arg.*; + next_arg.* += 1; + return arg; + } +} + /// Renders fmt string with args, calling output with slices of bytes. /// If `output` returns an error, the error is returned from `format` and /// `output` is not called again. @@ -20,17 +36,29 @@ pub fn format( comptime fmt: []const u8, args: ..., ) Errors!void { + const ArgSetType = @IntType(false, 32); + if (args.len > ArgSetType.bit_count) { + @compileError("32 arguments max are supported per format call"); + } + const State = enum { Start, - OpenBrace, + Positional, CloseBrace, - FormatString, + Specifier, + FormatWidth, + FormatPrecision, Pointer, }; comptime var start_index = 0; comptime var state = State.Start; comptime var next_arg = 0; + comptime var maybe_pos_arg: ?comptime_int = null; + comptime var used_pos_args: ArgSetType = 0; + comptime var specifier_start = 0; + comptime var specifier_end = 0; + comptime var options = FormatOptions{}; inline for (fmt) |c, i| { switch (state) { @@ -39,58 +67,165 @@ pub fn format( if (start_index < i) { try output(context, fmt[start_index..i]); } + start_index = i; - state = State.OpenBrace; + specifier_start = i + 1; + specifier_end = i + 1; + maybe_pos_arg = null; + state = .Positional; + options = FormatOptions{}; }, - '}' => { if (start_index < i) { try output(context, fmt[start_index..i]); } - state = State.CloseBrace; + state = .CloseBrace; }, else => {}, }, - .OpenBrace => switch (c) { + .Positional => switch (c) { '{' => { - state = State.Start; + state = .Start; start_index = i; }, + '*' => { + state = .Pointer; + }, + ':' => { + state = .FormatWidth; + specifier_end = i; + }, + '0'...'9' => { + if (maybe_pos_arg == null) { + maybe_pos_arg = 0; + } + + maybe_pos_arg.? *= 10; + maybe_pos_arg.? += c - '0'; + specifier_start = i + 1; + + if (maybe_pos_arg.? >= args.len) { + @compileError("Positional value refers to non-existent argument"); + } + }, '}' => { - try formatType(args[next_arg], fmt[0..0], context, Errors, output, default_max_depth); - next_arg += 1; - state = State.Start; + const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + + try formatType( + args[arg_to_print], + fmt[0..0], + options, + context, + Errors, + output, + default_max_depth, + ); + + state = .Start; start_index = i + 1; }, - '*' => state = State.Pointer, else => { - state = State.FormatString; + state = .Specifier; + specifier_start = i; }, }, .CloseBrace => switch (c) { '}' => { - state = State.Start; + state = .Start; start_index = i; }, else => @compileError("Single '}' encountered in format string"), }, - .FormatString => switch (c) { + .Specifier => switch (c) { + ':' => { + specifier_end = i; + state = .FormatWidth; + }, '}' => { - const s = start_index + 1; - try formatType(args[next_arg], fmt[s..i], context, Errors, output, default_max_depth); - next_arg += 1; - state = State.Start; + const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + + try formatType( + args[arg_to_print], + fmt[specifier_start..i], + options, + context, + Errors, + output, + default_max_depth, + ); + state = .Start; start_index = i + 1; }, else => {}, }, + .FormatWidth => switch (c) { + '0'...'9' => { + if (options.width == null) { + options.width = 0; + } + + options.width.? *= 10; + options.width.? += c - '0'; + }, + '.' => { + state = .FormatPrecision; + }, + '}' => { + const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + + try formatType( + args[arg_to_print], + fmt[specifier_start..specifier_end], + options, + context, + Errors, + output, + default_max_depth, + ); + state = .Start; + start_index = i + 1; + }, + else => { + @compileError("Unexpected character in width value: " ++ [_]u8{c}); + }, + }, + .FormatPrecision => switch (c) { + '0'...'9' => { + if (options.precision == null) { + options.precision = 0; + } + + options.precision.? *= 10; + options.precision.? += c - '0'; + }, + '}' => { + const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + + try formatType( + args[arg_to_print], + fmt[specifier_start..specifier_end], + options, + context, + Errors, + output, + default_max_depth, + ); + state = .Start; + start_index = i + 1; + }, + else => { + @compileError("Unexpected character in precision value: " ++ [_]u8{c}); + }, + }, .Pointer => switch (c) { '}' => { - try output(context, @typeName(@typeOf(args[next_arg]).Child)); + const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + + try output(context, @typeName(@typeOf(args[arg_to_print]).Child)); try output(context, "@"); - try formatInt(@ptrToInt(args[next_arg]), 16, false, 0, context, Errors, output); - next_arg += 1; - state = State.Start; + try formatInt(@ptrToInt(args[arg_to_print]), 16, false, 0, context, Errors, output); + + state = .Start; start_index = i + 1; }, else => @compileError("Unexpected format character after '*'"), @@ -98,7 +233,13 @@ pub fn format( } } comptime { - if (args.len != next_arg) { + // All arguments must have been printed but we allow mixing positional and fixed to achieve this. + var i: usize = 0; + inline while (i < next_arg) : (i += 1) { + used_pos_args |= 1 << i; + } + + if (@popCount(ArgSetType, used_pos_args) != args.len) { @compileError("Unused arguments"); } if (state != State.Start) { @@ -113,6 +254,7 @@ pub fn format( pub fn formatType( value: var, comptime fmt: []const u8, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -121,7 +263,7 @@ pub fn formatType( const T = @typeOf(value); switch (@typeInfo(T)) { .ComptimeInt, .Int, .Float => { - return formatValue(value, fmt, context, Errors, output); + return formatValue(value, fmt, options, context, Errors, output); }, .Void => { return output(context, "void"); @@ -131,16 +273,16 @@ pub fn formatType( }, .Optional => { if (value) |payload| { - return formatType(payload, fmt, context, Errors, output, max_depth); + return formatType(payload, fmt, options, context, Errors, output, max_depth); } else { return output(context, "null"); } }, .ErrorUnion => { if (value) |payload| { - return formatType(payload, fmt, context, Errors, output, max_depth); + return formatType(payload, fmt, options, context, Errors, output, max_depth); } else |err| { - return formatType(err, fmt, context, Errors, output, max_depth); + return formatType(err, fmt, options, context, Errors, output, max_depth); } }, .ErrorSet => { @@ -152,16 +294,16 @@ pub fn formatType( }, .Enum => { if (comptime std.meta.trait.hasFn("format")(T)) { - return value.format(fmt, context, Errors, output); + return value.format(fmt, options, context, Errors, output); } try output(context, @typeName(T)); try output(context, "."); - return formatType(@tagName(value), "", context, Errors, output, max_depth); + return formatType(@tagName(value), "", options, context, Errors, output, max_depth); }, .Union => { if (comptime std.meta.trait.hasFn("format")(T)) { - return value.format(fmt, context, Errors, output); + return value.format(fmt, options, context, Errors, output); } try output(context, @typeName(T)); @@ -175,7 +317,7 @@ pub fn formatType( try output(context, " = "); inline for (info.fields) |u_field| { if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) { - try formatType(@field(value, u_field.name), "", context, Errors, output, max_depth - 1); + try formatType(@field(value, u_field.name), "", options, context, Errors, output, max_depth - 1); } } try output(context, " }"); @@ -185,7 +327,7 @@ pub fn formatType( }, .Struct => { if (comptime std.meta.trait.hasFn("format")(T)) { - return value.format(fmt, context, Errors, output); + return value.format(fmt, options, context, Errors, output); } try output(context, @typeName(T)); @@ -201,7 +343,7 @@ pub fn formatType( } try output(context, @memberName(T, field_i)); try output(context, " = "); - try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output, max_depth - 1); + try formatType(@field(value, @memberName(T, field_i)), "", options, context, Errors, output, max_depth - 1); } try output(context, " }"); }, @@ -209,12 +351,12 @@ pub fn formatType( .One => switch (@typeInfo(ptr_info.child)) { builtin.TypeId.Array => |info| { if (info.child == u8) { - return formatText(value, fmt, context, Errors, output); + return formatText(value, fmt, options, context, Errors, output); } return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); }, builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { - return formatType(value.*, fmt, context, Errors, output, max_depth); + return formatType(value.*, fmt, options, context, Errors, output, max_depth); }, else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)), }, @@ -222,17 +364,17 @@ pub fn formatType( if (ptr_info.child == u8) { if (fmt.len > 0 and fmt[0] == 's') { const len = mem.len(u8, value); - return formatText(value[0..len], fmt, context, Errors, output); + return formatText(value[0..len], fmt, options, context, Errors, output); } } return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); }, .Slice => { if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { - return formatText(value, fmt, context, Errors, output); + return formatText(value, fmt, options, context, Errors, output); } if (ptr_info.child == u8) { - return formatText(value, fmt, context, Errors, output); + return formatText(value, fmt, options, context, Errors, output); } return format(context, Errors, output, "{}@{x}", @typeName(ptr_info.child), @ptrToInt(value.ptr)); }, @@ -242,7 +384,7 @@ pub fn formatType( }, .Array => |info| { if (info.child == u8) { - return formatText(value, fmt, context, Errors, output); + return formatText(value, fmt, options, context, Errors, output); } return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(&value)); }, @@ -256,28 +398,23 @@ pub fn formatType( fn formatValue( value: var, comptime fmt: []const u8, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { - if (fmt.len > 0 and fmt[0] == 'B') { - comptime var width: ?usize = null; - if (fmt.len > 1) { - if (fmt[1] == 'i') { - if (fmt.len > 2) { - width = comptime (parseUnsigned(usize, fmt[2..], 10) catch unreachable); - } - return formatBytes(value, width, 1024, context, Errors, output); - } - width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); - } - return formatBytes(value, width, 1000, context, Errors, output); + if (comptime std.mem.eql(u8, fmt, "B")) { + if (options.width) |w| return formatBytes(value, w, 1000, context, Errors, output); + return formatBytes(value, null, 1000, context, Errors, output); + } else if (comptime std.mem.eql(u8, fmt, "Bi")) { + if (options.width) |w| return formatBytes(value, w, 1024, context, Errors, output); + return formatBytes(value, null, 1024, context, Errors, output); } const T = @typeOf(value); switch (@typeId(T)) { - .Float => return formatFloatValue(value, fmt, context, Errors, output), - .Int, .ComptimeInt => return formatIntValue(value, fmt, context, Errors, output), + .Float => return formatFloatValue(value, fmt, options, context, Errors, output), + .Int, .ComptimeInt => return formatIntValue(value, fmt, options, context, Errors, output), else => comptime unreachable, } } @@ -285,13 +422,13 @@ fn formatValue( pub fn formatIntValue( value: var, comptime fmt: []const u8, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { comptime var radix = 10; comptime var uppercase = false; - comptime var width = 0; const int_value = if (@typeOf(value) == comptime_int) blk: { const Int = math.IntFittingRange(value, value); @@ -299,83 +436,72 @@ pub fn formatIntValue( } else value; - if (fmt.len > 0) { - switch (fmt[0]) { - 'c' => { - if (@typeOf(int_value).bit_count <= 8) { - if (fmt.len > 1) - @compileError("Unknown format character: " ++ [_]u8{fmt[1]}); - return formatAsciiChar(u8(int_value), context, Errors, output); - } - }, - 'b' => { - radix = 2; - uppercase = false; - width = 0; - }, - 'd' => { - radix = 10; - uppercase = false; - width = 0; - }, - 'x' => { - radix = 16; - uppercase = false; - width = 0; - }, - 'X' => { - radix = 16; - uppercase = true; - width = 0; - }, - else => @compileError("Unknown format character: " ++ [_]u8{fmt[0]}), + if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) { + radix = 10; + uppercase = false; + } else if (comptime std.mem.eql(u8, fmt, "c")) { + if (@typeOf(int_value).bit_count <= 8) { + return formatAsciiChar(u8(int_value), context, Errors, output); + } else { + @compileError("Cannot print integer that is larger than 8 bits as a ascii"); } - if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); + } else if (comptime std.mem.eql(u8, fmt, "b")) { + radix = 2; + uppercase = false; + } else if (comptime std.mem.eql(u8, fmt, "x")) { + radix = 16; + uppercase = false; + } else if (comptime std.mem.eql(u8, fmt, "X")) { + radix = 16; + uppercase = true; + } else { + @compileError("Unknown format string: '" ++ fmt ++ "'"); } - return formatInt(int_value, radix, uppercase, width, context, Errors, output); + + if (options.width) |w| return formatInt(int_value, radix, uppercase, w, context, Errors, output); + return formatInt(int_value, radix, uppercase, 0, context, Errors, output); } fn formatFloatValue( value: var, comptime fmt: []const u8, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { - comptime var width: ?usize = null; - comptime var float_fmt = 'e'; - if (fmt.len > 0) { - float_fmt = fmt[0]; - if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); - } - - switch (float_fmt) { - 'e' => try formatFloatScientific(value, width, context, Errors, output), - '.' => try formatFloatDecimal(value, width, context, Errors, output), - else => @compileError("Unknown format character: " ++ [_]u8{float_fmt}), + if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { + if (options.precision) |p| return formatFloatScientific(value, p, context, Errors, output); + return formatFloatScientific(value, null, context, Errors, output); + } else if (comptime std.mem.eql(u8, fmt, "d")) { + if (options.precision) |p| return formatFloatDecimal(value, p, context, Errors, output); + return formatFloatDecimal(value, options.precision, context, Errors, output); + } else { + @compileError("Unknown format string: '" ++ fmt ++ "'"); } } pub fn formatText( bytes: []const u8, comptime fmt: []const u8, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { - if (fmt.len > 0) { - if (fmt[0] == 's') { - comptime var width = 0; - if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); - return formatBuf(bytes, width, context, Errors, output); - } else if ((fmt[0] == 'x') or (fmt[0] == 'X')) { - for (bytes) |c| { - try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output); - } - return; - } else @compileError("Unknown format character: " ++ [_]u8{fmt[0]}); + if (fmt.len == 0) { + return output(context, bytes); + } else if (comptime std.mem.eql(u8, fmt, "s")) { + if (options.width) |w| return formatBuf(bytes, w, context, Errors, output); + return formatBuf(bytes, 0, context, Errors, output); + } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { + for (bytes) |c| { + try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output); + } + return; + } else { + @compileError("Unknown format string: '" ++ fmt ++ "'"); } - return output(context, bytes); } pub fn formatAsciiChar( @@ -868,7 +994,7 @@ test "parseUnsigned" { pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; -test "fmt.parseFloat" { +test "parseFloat" { _ = @import("fmt/parse_float.zig"); } @@ -960,7 +1086,7 @@ test "parse unsigned comptime" { } } -test "fmt.optional" { +test "optional" { { const value: ?i32 = 1234; try testFmt("optional: 1234\n", "optional: {}\n", value); @@ -971,7 +1097,7 @@ test "fmt.optional" { } } -test "fmt.error" { +test "error" { { const value: anyerror!i32 = 1234; try testFmt("error union: 1234\n", "error union: {}\n", value); @@ -982,14 +1108,14 @@ test "fmt.error" { } } -test "fmt.int.small" { +test "int.small" { { const value: u3 = 0b101; try testFmt("u3: 5\n", "u3: {}\n", value); } } -test "fmt.int.specifier" { +test "int.specifier" { { const value: u8 = 'a'; try testFmt("u8: a\n", "u8: {c}\n", value); @@ -1000,27 +1126,31 @@ test "fmt.int.specifier" { } } -test "fmt.buffer" { +test "int.padded" { + try testFmt("u8: '0001'", "u8: '{:4}'", u8(1)); +} + +test "buffer" { { var buf1: [32]u8 = undefined; var context = BufPrintContext{ .remaining = buf1[0..] }; - try formatType(1234, "", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); + try formatType(1234, "", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); var res = buf1[0 .. buf1.len - context.remaining.len]; testing.expect(mem.eql(u8, res, "1234")); context = BufPrintContext{ .remaining = buf1[0..] }; - try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); + try formatType('a', "c", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); res = buf1[0 .. buf1.len - context.remaining.len]; testing.expect(mem.eql(u8, res, "a")); context = BufPrintContext{ .remaining = buf1[0..] }; - try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); + try formatType(0b1100, "b", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); res = buf1[0 .. buf1.len - context.remaining.len]; testing.expect(mem.eql(u8, res, "1100")); } } -test "fmt.array" { +test "array" { { const value: [3]u8 = "abc"; try testFmt("array: abc\n", "array: {}\n", value); @@ -1035,7 +1165,7 @@ test "fmt.array" { } } -test "fmt.slice" { +test "slice" { { const value: []const u8 = "abc"; try testFmt("slice: abc\n", "slice: {}\n", value); @@ -1045,11 +1175,11 @@ test "fmt.slice" { try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", value); } - try testFmt("buf: Test \n", "buf: {s5}\n", "Test"); + try testFmt("buf: Test \n", "buf: {s:5}\n", "Test"); try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test"); } -test "fmt.pointer" { +test "pointer" { { const value = @intToPtr(*i32, 0xdeadbeef); try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", value); @@ -1065,17 +1195,17 @@ test "fmt.pointer" { } } -test "fmt.cstr" { +test "cstr" { try testFmt("cstr: Test C\n", "cstr: {s}\n", c"Test C"); - try testFmt("cstr: Test C \n", "cstr: {s10}\n", c"Test C"); + try testFmt("cstr: Test C \n", "cstr: {s:10}\n", c"Test C"); } -test "fmt.filesize" { +test "filesize" { try testFmt("file size: 63MiB\n", "file size: {Bi}\n", usize(63 * 1024 * 1024)); - try testFmt("file size: 66.06MB\n", "file size: {B2}\n", usize(63 * 1024 * 1024)); + try testFmt("file size: 66.06MB\n", "file size: {B:2}\n", usize(63 * 1024 * 1024)); } -test "fmt.struct" { +test "struct" { { const Struct = struct { field: u8, @@ -1094,7 +1224,7 @@ test "fmt.struct" { } } -test "fmt.enum" { +test "enum" { const Enum = enum { One, Two, @@ -1104,229 +1234,71 @@ test "fmt.enum" { try testFmt("enum: Enum.Two\n", "enum: {}\n", &value); } -test "fmt.float.scientific" { - { - var buf1: [32]u8 = undefined; - const value: f32 = 1.34; - const result = try bufPrint(buf1[0..], "f32: {e}\n", value); - testing.expect(mem.eql(u8, result, "f32: 1.34000003e+00\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = 12.34; - const result = try bufPrint(buf1[0..], "f32: {e}\n", value); - testing.expect(mem.eql(u8, result, "f32: 1.23400001e+01\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = -12.34e10; - const result = try bufPrint(buf1[0..], "f64: {e}\n", value); - testing.expect(mem.eql(u8, result, "f64: -1.234e+11\n")); - } - { - // This fails on release due to a minor rounding difference. - // --release-fast outputs 9.999960000000001e-40 vs. the expected. - // TODO fix this, it should be the same in Debug and ReleaseFast - if (builtin.mode == builtin.Mode.Debug) { - var buf1: [32]u8 = undefined; - const value: f64 = 9.999960e-40; - const result = try bufPrint(buf1[0..], "f64: {e}\n", value); - testing.expect(mem.eql(u8, result, "f64: 9.99996e-40\n")); - } - } +test "float.scientific" { + try testFmt("f32: 1.34000003e+00", "f32: {e}", f32(1.34)); + try testFmt("f32: 1.23400001e+01", "f32: {e}", f32(12.34)); + try testFmt("f64: -1.234e+11", "f64: {e}", f64(-12.34e10)); + try testFmt("f64: 9.99996e-40", "f64: {e}", f64(9.999960e-40)); } -test "fmt.float.scientific.precision" { - { - var buf1: [32]u8 = undefined; - const value: f64 = 1.409706e-42; - const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 1.40971e-42\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = @bitCast(f32, u32(814313563)); - const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 1.00000e-09\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = @bitCast(f32, u32(1006632960)); - const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 7.81250e-03\n")); - } - { - // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05. - // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. - var buf1: [32]u8 = undefined; - const value: f64 = @bitCast(f32, u32(1203982400)); - const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 1.00001e+05\n")); - } +test "float.scientific.precision" { + try testFmt("f64: 1.40971e-42", "f64: {e:.5}", f64(1.409706e-42)); + try testFmt("f64: 1.00000e-09", "f64: {e:.5}", f64(@bitCast(f32, u32(814313563)))); + try testFmt("f64: 7.81250e-03", "f64: {e:.5}", f64(@bitCast(f32, u32(1006632960)))); + // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05. + // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. + try testFmt("f64: 1.00001e+05", "f64: {e:.5}", f64(@bitCast(f32, u32(1203982400)))); } -test "fmt.float.special" { - { - var buf1: [32]u8 = undefined; - const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64); - testing.expect(mem.eql(u8, result, "f64: nan\n")); - } +test "float.special" { + try testFmt("f64: nan", "f64: {}", math.nan_f64); + // negative nan is not defined by IEE 754, + // and ARM thus normalizes it to positive nan if (builtin.arch != builtin.Arch.arm) { - // negative nan is not defined by IEE 754, - // and ARM thus normalizes it to positive nan - var buf1: [32]u8 = undefined; - const result = try bufPrint(buf1[0..], "f64: {}\n", -math.nan_f64); - testing.expect(mem.eql(u8, result, "f64: -nan\n")); - } - { - var buf1: [32]u8 = undefined; - const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64); - testing.expect(mem.eql(u8, result, "f64: inf\n")); - } - { - var buf1: [32]u8 = undefined; - const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64); - testing.expect(mem.eql(u8, result, "f64: -inf\n")); + try testFmt("f64: -nan", "f64: {}", -math.nan_f64); } + try testFmt("f64: inf", "f64: {}", math.inf_f64); + try testFmt("f64: -inf", "f64: {}", -math.inf_f64); } -test "fmt.float.decimal" { - { - var buf1: [64]u8 = undefined; - const value: f64 = 1.52314e+29; - const result = try bufPrint(buf1[0..], "f64: {.}\n", value); - testing.expect(mem.eql(u8, result, "f64: 152314000000000000000000000000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = 1.1234; - const result = try bufPrint(buf1[0..], "f32: {.1}\n", value); - testing.expect(mem.eql(u8, result, "f32: 1.1\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = 1234.567; - const result = try bufPrint(buf1[0..], "f32: {.2}\n", value); - testing.expect(mem.eql(u8, result, "f32: 1234.57\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = -11.1234; - const result = try bufPrint(buf1[0..], "f32: {.4}\n", value); - // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). - // -11.12339... is rounded back up to -11.1234 - testing.expect(mem.eql(u8, result, "f32: -11.1234\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = 91.12345; - const result = try bufPrint(buf1[0..], "f32: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f32: 91.12345\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 91.12345678901235; - const result = try bufPrint(buf1[0..], "f64: {.10}\n", value); - testing.expect(mem.eql(u8, result, "f64: 91.1234567890\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 0.0; - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 0.00000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 5.700; - const result = try bufPrint(buf1[0..], "f64: {.0}\n", value); - testing.expect(mem.eql(u8, result, "f64: 6\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 9.999; - const result = try bufPrint(buf1[0..], "f64: {.1}\n", value); - testing.expect(mem.eql(u8, result, "f64: 10.0\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 1.0; - const result = try bufPrint(buf1[0..], "f64: {.3}\n", value); - testing.expect(mem.eql(u8, result, "f64: 1.000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 0.0003; - const result = try bufPrint(buf1[0..], "f64: {.8}\n", value); - testing.expect(mem.eql(u8, result, "f64: 0.00030000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 1.40130e-45; - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 0.00000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 9.999960e-40; - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 0.00000\n")); - } +test "float.decimal" { + try testFmt("f64: 152314000000000000000000000000", "f64: {d}", f64(1.52314e+29)); + try testFmt("f32: 1.1", "f32: {d:.1}", f32(1.1234)); + try testFmt("f32: 1234.57", "f32: {d:.2}", f32(1234.567)); + // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). + // -11.12339... is rounded back up to -11.1234 + try testFmt("f32: -11.1234", "f32: {d:.4}", f32(-11.1234)); + try testFmt("f32: 91.12345", "f32: {d:.5}", f32(91.12345)); + try testFmt("f64: 91.1234567890", "f64: {d:.10}", f64(91.12345678901235)); + try testFmt("f64: 0.00000", "f64: {d:.5}", f64(0.0)); + try testFmt("f64: 6", "f64: {d:.0}", f64(5.700)); + try testFmt("f64: 10.0", "f64: {d:.1}", f64(9.999)); + try testFmt("f64: 1.000", "f64: {d:.3}", f64(1.0)); + try testFmt("f64: 0.00030000", "f64: {d:.8}", f64(0.0003)); + try testFmt("f64: 0.00000", "f64: {d:.5}", f64(1.40130e-45)); + try testFmt("f64: 0.00000", "f64: {d:.5}", f64(9.999960e-40)); } -test "fmt.float.libc.sanity" { - { - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(916964781))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 0.00001\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(925353389))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 0.00001\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(1036831278))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 0.10000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(1065353133))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 1.00000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(1092616192))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 10.00000\n")); - } +test "float.libc.sanity" { + try testFmt("f64: 0.00001", "f64: {d:.5}", f64(@bitCast(f32, u32(916964781)))); + try testFmt("f64: 0.00001", "f64: {d:.5}", f64(@bitCast(f32, u32(925353389)))); + try testFmt("f64: 0.10000", "f64: {d:.5}", f64(@bitCast(f32, u32(1036831278)))); + try testFmt("f64: 1.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1065353133)))); + try testFmt("f64: 10.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1092616192)))); + // libc differences - { - var buf1: [32]u8 = undefined; - // This is 0.015625 exactly according to gdb. We thus round down, - // however glibc rounds up for some reason. This occurs for all - // floats of the form x.yyyy25 on a precision point. - const value: f64 = f64(@bitCast(f32, u32(1015021568))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 0.01563\n")); - } - // std-windows-x86_64-Debug-bare test case fails - { - // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 - // also rounds to 630 so I'm inclined to believe libc is not - // optimal here. - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(1518338049))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - testing.expect(mem.eql(u8, result, "f64: 18014400656965630.00000\n")); - } + // + // This is 0.015625 exactly according to gdb. We thus round down, + // however glibc rounds up for some reason. This occurs for all + // floats of the form x.yyyy25 on a precision point. + try testFmt("f64: 0.01563", "f64: {d:.5}", f64(@bitCast(f32, u32(1015021568)))); + // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 + // also rounds to 630 so I'm inclined to believe libc is not + // optimal here. + try testFmt("f64: 18014400656965630.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1518338049)))); } -test "fmt.custom" { +test "custom" { const Vec2 = struct { const SelfType = @This(); x: f32, @@ -1335,20 +1307,17 @@ test "fmt.custom" { pub fn format( self: SelfType, comptime fmt: []const u8, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { - switch (fmt.len) { - 0 => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y), - 1 => switch (fmt[0]) { - //point format - 'p' => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y), - //dimension format - 'd' => return std.fmt.format(context, Errors, output, "{.3}x{.3}", self.x, self.y), - else => unreachable, - }, - else => unreachable, + if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { + return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", self.x, self.y); + } else if (comptime std.mem.eql(u8, fmt, "d")) { + return std.fmt.format(context, Errors, output, "{d:.3}x{d:.3}", self.x, self.y); + } else { + @compileError("Unknown format character: '" ++ fmt ++ "'"); } } }; @@ -1366,7 +1335,7 @@ test "fmt.custom" { try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value); } -test "fmt.struct" { +test "struct" { const S = struct { a: u32, b: anyerror, @@ -1380,7 +1349,7 @@ test "fmt.struct" { try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst); } -test "fmt.union" { +test "union" { const TU = union(enum) { float: f32, int: u32, @@ -1410,7 +1379,7 @@ test "fmt.union" { testing.expect(mem.eql(u8, uu_result[0..3], "EU@")); } -test "fmt.enum" { +test "enum" { const E = enum { One, Two, @@ -1422,7 +1391,7 @@ test "fmt.enum" { try testFmt("E.Two", "{}", inst); } -test "fmt.struct.self-referential" { +test "struct.self-referential" { const S = struct { const SelfType = @This(); a: ?*SelfType, @@ -1436,7 +1405,7 @@ test "fmt.struct.self-referential" { try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst); } -test "fmt.bytes.hex" { +test "bytes.hex" { const some_bytes = "\xCA\xFE\xBA\xBE"; try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes); try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes); @@ -1478,7 +1447,7 @@ pub fn trim(buf: []const u8) []const u8 { return buf[start..end]; } -test "fmt.trim" { +test "trim" { testing.expect(mem.eql(u8, "abc", trim("\n abc \t"))); testing.expect(mem.eql(u8, "", trim(" "))); testing.expect(mem.eql(u8, "", trim(""))); @@ -1505,22 +1474,22 @@ pub fn hexToBytes(out: []u8, input: []const u8) !void { } } -test "fmt.hexToBytes" { +test "hexToBytes" { const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706"; var pb: [32]u8 = undefined; try hexToBytes(pb[0..], test_hex_str); try testFmt(test_hex_str, "{X}", pb); } -test "fmt.formatIntValue with comptime_int" { +test "formatIntValue with comptime_int" { const value: comptime_int = 123456789123456789; var buf = try std.Buffer.init(std.debug.global_allocator, ""); - try formatIntValue(value, "", &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append); + try formatIntValue(value, "", FormatOptions{}, &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append); assert(mem.eql(u8, buf.toSlice(), "123456789123456789")); } -test "fmt.formatType max_depth" { +test "formatType max_depth" { const Vec2 = struct { const SelfType = @This(); x: f32, @@ -1529,11 +1498,16 @@ test "fmt.formatType max_depth" { pub fn format( self: SelfType, comptime fmt: []const u8, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { - return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y); + if (fmt.len == 0) { + return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", self.x, self.y); + } else { + @compileError("Unknown format string: '" ++ fmt ++ "'"); + } } }; const E = enum { @@ -1565,18 +1539,30 @@ test "fmt.formatType max_depth" { inst.tu.ptr = &inst.tu; var buf0 = try std.Buffer.init(std.debug.global_allocator, ""); - try formatType(inst, "", &buf0, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0); + try formatType(inst, "", FormatOptions{}, &buf0, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0); assert(mem.eql(u8, buf0.toSlice(), "S{ ... }")); var buf1 = try std.Buffer.init(std.debug.global_allocator, ""); - try formatType(inst, "", &buf1, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1); + try formatType(inst, "", FormatOptions{}, &buf1, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1); assert(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }")); var buf2 = try std.Buffer.init(std.debug.global_allocator, ""); - try formatType(inst, "", &buf2, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2); + try formatType(inst, "", FormatOptions{}, &buf2, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2); assert(mem.eql(u8, buf2.toSlice(), "S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }")); var buf3 = try std.Buffer.init(std.debug.global_allocator, ""); - try formatType(inst, "", &buf3, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3); + try formatType(inst, "", FormatOptions{}, &buf3, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3); assert(mem.eql(u8, buf3.toSlice(), "S{ .a = S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ .ptr = TU{ ... } } }, .e = E.Two, .vec = (10.200,2.220) }")); } + +test "positional" { + try testFmt("2 1 0", "{2} {1} {0}", usize(0), usize(1), usize(2)); + try testFmt("2 1 0", "{2} {1} {}", usize(0), usize(1), usize(2)); + try testFmt("0 0", "{0} {0}", usize(0)); + try testFmt("0 1", "{} {1}", usize(0), usize(1)); + try testFmt("1 0 0 1", "{1} {} {0} {}", usize(0), usize(1)); +} + +test "positional with specifier" { + try testFmt("10.0", "{0d:.1}", f64(9.999)); +} diff --git a/std/math/big/int.zig b/std/math/big/int.zig index 46b1bed9a3aae9d2d26d9f236ec948e803f1aa2c..4ad5c92b3f5e57e56cd37ba10138b9316e56c890 100644 --- a/std/math/big/int.zig +++ b/std/math/big/int.zig @@ -519,6 +519,7 @@ pub const Int = struct { pub fn format( self: Int, comptime fmt: []const u8, + comptime options: std.fmt.FormatOptions, context: var, comptime FmtError: type, output: fn (@typeOf(context), []const u8) FmtError!void, diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig index a0a18d21d24ba83708ebeaaf86cd603c101b2fad..9e23f60d8c030357b1178eaeda2c74f9aff5730c 100644 --- a/std/special/build_runner.zig +++ b/std/special/build_runner.zig @@ -170,7 +170,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void { const allocator = builder.allocator; for (builder.top_level_steps.toSliceConst()) |top_level_step| { - try out_stream.print(" {s22} {}\n", top_level_step.step.name, top_level_step.description); + try out_stream.print(" {s:22} {}\n", top_level_step.step.name, top_level_step.description); } try out_stream.write( @@ -191,7 +191,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void { for (builder.available_options_list.toSliceConst()) |option| { const name = try fmt.allocPrint(allocator, " -D{}=[{}]", option.name, Builder.typeIdName(option.type_id)); defer allocator.free(name); - try out_stream.print("{s24} {}\n", name, option.description); + try out_stream.print("{s:24} {}\n", name, option.description); } } diff --git a/test/compare_output.zig b/test/compare_output.zig index ad15ef47b39edf2b5cb883daae3bdfdb27326cc2..79057f3c54b45dae36676379a3b457bac6e00185 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -122,7 +122,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ \\pub fn main() void { \\ const stdout = &(io.getStdOut() catch unreachable).outStream().stream; - \\ stdout.print("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a')) catch unreachable; + \\ stdout.print("Hello, world!\n{d:4} {x:3} {c}\n", u32(12), u16(0x12), u8('a')) catch unreachable; \\} , "Hello, world!\n0012 012 a\n"); -- 2.54.0 From 948dc7b304f966d8402f63261a8b8dd43691647a Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Fri, 21 Jun 2019 20:23:53 +1200 Subject: [PATCH 05/15] Link formatting workaround to issue --- std/fmt.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/std/fmt.zig b/std/fmt.zig index 4d9ef168fb23b3e48e57c36f67db14b70aaf6c0b..038efbfb7fd1ade85192d0c972ac96489b237f3e 100644 --- a/std/fmt.zig +++ b/std/fmt.zig @@ -404,9 +404,11 @@ fn formatValue( output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { if (comptime std.mem.eql(u8, fmt, "B")) { + // TODO https://github.com/ziglang/zig/issues/2725 if (options.width) |w| return formatBytes(value, w, 1000, context, Errors, output); return formatBytes(value, null, 1000, context, Errors, output); } else if (comptime std.mem.eql(u8, fmt, "Bi")) { + // TODO https://github.com/ziglang/zig/issues/2725 if (options.width) |w| return formatBytes(value, w, 1024, context, Errors, output); return formatBytes(value, null, 1024, context, Errors, output); } @@ -458,6 +460,7 @@ pub fn formatIntValue( @compileError("Unknown format string: '" ++ fmt ++ "'"); } + // TODO https://github.com/ziglang/zig/issues/2725 if (options.width) |w| return formatInt(int_value, radix, uppercase, w, context, Errors, output); return formatInt(int_value, radix, uppercase, 0, context, Errors, output); } @@ -471,11 +474,13 @@ fn formatFloatValue( output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { + // TODO https://github.com/ziglang/zig/issues/2725 if (options.precision) |p| return formatFloatScientific(value, p, context, Errors, output); return formatFloatScientific(value, null, context, Errors, output); } else if (comptime std.mem.eql(u8, fmt, "d")) { + // TODO https://github.com/ziglang/zig/issues/2725 if (options.precision) |p| return formatFloatDecimal(value, p, context, Errors, output); - return formatFloatDecimal(value, options.precision, context, Errors, output); + return formatFloatDecimal(value, null, context, Errors, output); } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); } -- 2.54.0 From ebde2ff899c16612c7ff58df61f3946be47c51c8 Mon Sep 17 00:00:00 2001 From: Shawn Landden Date: Fri, 21 Jun 2019 08:43:10 -0500 Subject: [PATCH 06/15] stage1: update fn_key_eql() for @mulAdd() on vectors --- src/analyze.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index bff740cd52cfafe93bca64c273664ede6bf7eb1d..15b42c7f9dade3ae6f25f9eae79c8b62340c3df1 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -5772,8 +5772,10 @@ bool zig_llvm_fn_key_eql(ZigLLVMFnKey a, ZigLLVMFnKey b) { case ZigLLVMFnIdFloor: case ZigLLVMFnIdCeil: case ZigLLVMFnIdSqrt: - case ZigLLVMFnIdFMA: return a.data.floating.bit_count == b.data.floating.bit_count; + case ZigLLVMFnIdFMA: + return a.data.floating.bit_count == b.data.floating.bit_count && + a.data.floating.vector_len == b.data.floating.vector_len; case ZigLLVMFnIdOverflowArithmetic: return (a.data.overflow_arithmetic.bit_count == b.data.overflow_arithmetic.bit_count) && (a.data.overflow_arithmetic.add_sub_mul == b.data.overflow_arithmetic.add_sub_mul) && -- 2.54.0 From 71e014caecaa54fdd8a0516710d2d9597da41398 Mon Sep 17 00:00:00 2001 From: Shawn Landden Date: Fri, 21 Jun 2019 16:18:59 -0500 Subject: [PATCH 07/15] stage1: add @sin @cos @exp @exp2 @ln @log2 @log10 @fabs @floor @ceil @trunc @round and expand @sqrt This revealed that the accuracy of ln is not as good as the current algorithm in musl and glibc, and should be ported again. v2: actually include tests v3: fix reversal of in and out arguments on f128M_sqrt() add test for @sqrt on comptime_float do not include @nearbyInt() until it works on all targets. --- doc/langref.html.in | 85 ++++++- src/all_types.hpp | 26 ++- src/analyze.cpp | 15 +- src/codegen.cpp | 68 +++--- src/ir.cpp | 369 +++++++++++++++++++++++++------ src/ir.hpp | 1 + src/ir_print.cpp | 11 +- src/util.cpp | 1 + std/special/c.zig | 44 ++-- test/stage1/behavior.zig | 1 + test/stage1/behavior/floatop.zig | 243 ++++++++++++++++++++ 11 files changed, 726 insertions(+), 138 deletions(-) create mode 100644 test/stage1/behavior/floatop.zig diff --git a/doc/langref.html.in b/doc/langref.html.in index 9b95946256da548e253dbbf7a5b507849b3e4496..30fe9a36485cc11a400eef73ccefb971ba6a1c48 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7354,10 +7354,91 @@ test "@setRuntimeSafety" {
{#syntax#}@sqrt(comptime T: type, value: T) T{#endsyntax#}

Performs the square root of a floating point number. Uses a dedicated hardware instruction - when available. Currently only supports f32 and f64 at runtime. f128 at runtime is TODO. + when available. Supports f16, f32, f64, and f128, as well as vectors.

+ {#header_close#} + {#header_open|@sin#} +
{#syntax#}@sin(comptime T: type, value: T) T{#endsyntax#}

- This is a low-level intrinsic. Most code can use {#syntax#}std.math.sqrt{#endsyntax#} instead. + Sine trigometric function on a floating point number. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@cos#} +
{#syntax#}@cos(comptime T: type, value: T) T{#endsyntax#}
+

+ Cosine trigometric function on a floating point number. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@exp#} +
{#syntax#}@exp(comptime T: type, value: T) T{#endsyntax#}
+

+ Base-e exponential function on a floating point number. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@exp2#} +
{#syntax#}@exp2(comptime T: type, value: T) T{#endsyntax#}
+

+ Base-2 exponential function on a floating point number. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@ln#} +
{#syntax#}@ln(comptime T: type, value: T) T{#endsyntax#}
+

+ Returns the natural logarithm of a floating point number. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@log2#} +
{#syntax#}@log2(comptime T: type, value: T) T{#endsyntax#}
+

+ Returns the logarithm to the base 2 of a floating point number. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@log10#} +
{#syntax#}@log10(comptime T: type, value: T) T{#endsyntax#}
+

+ Returns the logarithm to the base 10 of a floating point number. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@fabs#} +
{#syntax#}@fabs(comptime T: type, value: T) T{#endsyntax#}
+

+ Returns the absolute value of a floating point number. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@floor#} +
{#syntax#}@floor(comptime T: type, value: T) T{#endsyntax#}
+

+ Returns the largest integral value not greater than the given floating point number. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@ceil#} +
{#syntax#}@ceil(comptime T: type, value: T) T{#endsyntax#}
+

+ Returns the largest integral value not less than the given floating point number. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@trunc#} +
{#syntax#}@trunc(comptime T: type, value: T) T{#endsyntax#}
+

+ Rounds the given floating point number to an integer, towards zero. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64. +

+ {#header_close#} + {#header_open|@round#} +
{#syntax#}@round(comptime T: type, value: T) T{#endsyntax#}
+

+ Rounds the given floating point number to an integer, away from zero. Uses a dedicated hardware instruction + when available. Currently supports f32 and f64.

{#header_close#} diff --git a/src/all_types.hpp b/src/all_types.hpp index 83df71b95f487b30cc7c084f1952ba2534ac1853..6595218bcf4ed8c8f00f2fe5f0e7ee0043bf64b7 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1434,6 +1434,19 @@ enum BuiltinFnId { BuiltinFnIdRem, BuiltinFnIdMod, BuiltinFnIdSqrt, + BuiltinFnIdSin, + BuiltinFnIdCos, + BuiltinFnIdExp, + BuiltinFnIdExp2, + BuiltinFnIdLn, + BuiltinFnIdLog2, + BuiltinFnIdLog10, + BuiltinFnIdFabs, + BuiltinFnIdFloor, + BuiltinFnIdCeil, + BuiltinFnIdTrunc, + BuiltinFnIdNearbyInt, + BuiltinFnIdRound, BuiltinFnIdTruncate, BuiltinFnIdIntCast, BuiltinFnIdFloatCast, @@ -1556,9 +1569,7 @@ enum ZigLLVMFnId { ZigLLVMFnIdPopCount, ZigLLVMFnIdOverflowArithmetic, ZigLLVMFnIdFMA, - ZigLLVMFnIdFloor, - ZigLLVMFnIdCeil, - ZigLLVMFnIdSqrt, + ZigLLVMFnIdFloatOp, ZigLLVMFnIdBswap, ZigLLVMFnIdBitReverse, }; @@ -1585,6 +1596,7 @@ struct ZigLLVMFnKey { uint32_t bit_count; } pop_count; struct { + BuiltinFnId op; uint32_t bit_count; uint32_t vector_len; // 0 means not a vector } floating; @@ -2239,6 +2251,7 @@ enum IrInstructionId { IrInstructionIdAlignOf, IrInstructionIdOverflowOp, IrInstructionIdMulAdd, + IrInstructionIdFloatOp, IrInstructionIdTestErr, IrInstructionIdUnwrapErrCode, IrInstructionIdUnwrapErrPayload, @@ -2300,7 +2313,6 @@ enum IrInstructionId { IrInstructionIdAddImplicitReturnType, IrInstructionIdMergeErrRetTraces, IrInstructionIdMarkErrRetTracePtr, - IrInstructionIdSqrt, IrInstructionIdErrSetCast, IrInstructionIdToBytes, IrInstructionIdFromBytes, @@ -3474,11 +3486,13 @@ struct IrInstructionMarkErrRetTracePtr { IrInstruction *err_ret_trace_ptr; }; -struct IrInstructionSqrt { +// For float ops which take a single argument +struct IrInstructionFloatOp { IrInstruction base; + BuiltinFnId op; IrInstruction *type; - IrInstruction *op; + IrInstruction *op1; }; struct IrInstructionCheckRuntimeScope { diff --git a/src/analyze.cpp b/src/analyze.cpp index 15b42c7f9dade3ae6f25f9eae79c8b62340c3df1..13b35e0aff4325874ffd72264abf553c98012921 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -5736,9 +5736,10 @@ uint32_t zig_llvm_fn_key_hash(ZigLLVMFnKey x) { return (uint32_t)(x.data.clz.bit_count) * (uint32_t)2428952817; case ZigLLVMFnIdPopCount: return (uint32_t)(x.data.clz.bit_count) * (uint32_t)101195049; - case ZigLLVMFnIdFloor: - case ZigLLVMFnIdCeil: - case ZigLLVMFnIdSqrt: + case ZigLLVMFnIdFloatOp: + return (uint32_t)(x.data.floating.bit_count) * ((uint32_t)x.id + 1025) + + (uint32_t)(x.data.floating.vector_len) * (((uint32_t)x.id << 5) + 1025) + + (uint32_t)(x.data.floating.op) * (uint32_t)43789879; case ZigLLVMFnIdFMA: return (uint32_t)(x.data.floating.bit_count) * ((uint32_t)x.id + 1025) + (uint32_t)(x.data.floating.vector_len) * (((uint32_t)x.id << 5) + 1025); @@ -5769,10 +5770,10 @@ bool zig_llvm_fn_key_eql(ZigLLVMFnKey a, ZigLLVMFnKey b) { return a.data.bswap.bit_count == b.data.bswap.bit_count; case ZigLLVMFnIdBitReverse: return a.data.bit_reverse.bit_count == b.data.bit_reverse.bit_count; - case ZigLLVMFnIdFloor: - case ZigLLVMFnIdCeil: - case ZigLLVMFnIdSqrt: - return a.data.floating.bit_count == b.data.floating.bit_count; + case ZigLLVMFnIdFloatOp: + return a.data.floating.bit_count == b.data.floating.bit_count && + a.data.floating.vector_len == b.data.floating.vector_len && + a.data.floating.op == b.data.floating.op; case ZigLLVMFnIdFMA: return a.data.floating.bit_count == b.data.floating.bit_count && a.data.floating.vector_len == b.data.floating.vector_len; diff --git a/src/codegen.cpp b/src/codegen.cpp index 6691652a5e04a25b63d5e6ee4a2b891d3bc0e298..41caa29dbd55b83f7fd2afc0770cee60c4a5b270 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -806,7 +806,7 @@ static LLVMValueRef get_int_overflow_fn(CodeGen *g, ZigType *operand_type, AddSu return fn_val; } -static LLVMValueRef get_float_fn(CodeGen *g, ZigType *type_entry, ZigLLVMFnId fn_id) { +static LLVMValueRef get_float_fn(CodeGen *g, ZigType *type_entry, ZigLLVMFnId fn_id, BuiltinFnId op) { assert(type_entry->id == ZigTypeIdFloat || type_entry->id == ZigTypeIdVector); @@ -817,6 +817,7 @@ static LLVMValueRef get_float_fn(CodeGen *g, ZigType *type_entry, ZigLLVMFnId fn key.id = fn_id; key.data.floating.bit_count = (uint32_t)float_type->data.floating.bit_count; key.data.floating.vector_len = is_vector ? (uint32_t)type_entry->data.vector.len : 0; + key.data.floating.op = op; auto existing_entry = g->llvm_fn_table.maybe_get(key); if (existing_entry) @@ -824,18 +825,12 @@ static LLVMValueRef get_float_fn(CodeGen *g, ZigType *type_entry, ZigLLVMFnId fn const char *name; uint32_t num_args; - if (fn_id == ZigLLVMFnIdFloor) { - name = "floor"; - num_args = 1; - } else if (fn_id == ZigLLVMFnIdCeil) { - name = "ceil"; - num_args = 1; - } else if (fn_id == ZigLLVMFnIdSqrt) { - name = "sqrt"; - num_args = 1; - } else if (fn_id == ZigLLVMFnIdFMA) { + if (fn_id == ZigLLVMFnIdFMA) { name = "fma"; num_args = 3; + } else if (fn_id == ZigLLVMFnIdFloatOp) { + name = float_op_to_name(op, true); + num_args = 1; } else { zig_unreachable(); } @@ -2480,22 +2475,17 @@ static LLVMValueRef gen_overflow_shr_op(CodeGen *g, ZigType *type_entry, return result; } -static LLVMValueRef gen_floor(CodeGen *g, LLVMValueRef val, ZigType *type_entry) { - if (type_entry->id == ZigTypeIdInt) +static LLVMValueRef gen_float_op(CodeGen *g, LLVMValueRef val, ZigType *type_entry, BuiltinFnId op) { + if ((op == BuiltinFnIdCeil || + op == BuiltinFnIdFloor) && + type_entry->id == ZigTypeIdInt) return val; + assert(type_entry->id == ZigTypeIdFloat); - LLVMValueRef floor_fn = get_float_fn(g, type_entry, ZigLLVMFnIdFloor); + LLVMValueRef floor_fn = get_float_fn(g, type_entry, ZigLLVMFnIdFloatOp, op); return LLVMBuildCall(g->builder, floor_fn, &val, 1, ""); } -static LLVMValueRef gen_ceil(CodeGen *g, LLVMValueRef val, ZigType *type_entry) { - if (type_entry->id == ZigTypeIdInt) - return val; - - LLVMValueRef ceil_fn = get_float_fn(g, type_entry, ZigLLVMFnIdCeil); - return LLVMBuildCall(g->builder, ceil_fn, &val, 1, ""); -} - enum DivKind { DivKindFloat, DivKindTrunc, @@ -2571,7 +2561,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast return result; case DivKindExact: if (want_runtime_safety) { - LLVMValueRef floored = gen_floor(g, result, type_entry); + LLVMValueRef floored = gen_float_op(g, result, type_entry, BuiltinFnIdFloor); LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactOk"); LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail"); LLVMValueRef ok_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, floored, result, ""); @@ -2593,12 +2583,12 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMBuildCondBr(g->builder, ltz, ltz_block, gez_block); LLVMPositionBuilderAtEnd(g->builder, ltz_block); - LLVMValueRef ceiled = gen_ceil(g, result, type_entry); + LLVMValueRef ceiled = gen_float_op(g, result, type_entry, BuiltinFnIdCeil); LLVMBasicBlockRef ceiled_end_block = LLVMGetInsertBlock(g->builder); LLVMBuildBr(g->builder, end_block); LLVMPositionBuilderAtEnd(g->builder, gez_block); - LLVMValueRef floored = gen_floor(g, result, type_entry); + LLVMValueRef floored = gen_float_op(g, result, type_entry, BuiltinFnIdFloor); LLVMBasicBlockRef floored_end_block = LLVMGetInsertBlock(g->builder); LLVMBuildBr(g->builder, end_block); @@ -2610,7 +2600,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast return phi; } case DivKindFloor: - return gen_floor(g, result, type_entry); + return gen_float_op(g, result, type_entry, BuiltinFnIdFloor); } zig_unreachable(); } @@ -5450,10 +5440,10 @@ static LLVMValueRef ir_render_mark_err_ret_trace_ptr(CodeGen *g, IrExecutable *e return nullptr; } -static LLVMValueRef ir_render_sqrt(CodeGen *g, IrExecutable *executable, IrInstructionSqrt *instruction) { - LLVMValueRef op = ir_llvm_value(g, instruction->op); +static LLVMValueRef ir_render_float_op(CodeGen *g, IrExecutable *executable, IrInstructionFloatOp *instruction) { + LLVMValueRef op = ir_llvm_value(g, instruction->op1); assert(instruction->base.value.type->id == ZigTypeIdFloat); - LLVMValueRef fn_val = get_float_fn(g, instruction->base.value.type, ZigLLVMFnIdSqrt); + LLVMValueRef fn_val = get_float_fn(g, instruction->base.value.type, ZigLLVMFnIdFloatOp, instruction->op); return LLVMBuildCall(g->builder, fn_val, &op, 1, ""); } @@ -5463,7 +5453,7 @@ static LLVMValueRef ir_render_mul_add(CodeGen *g, IrExecutable *executable, IrIn LLVMValueRef op3 = ir_llvm_value(g, instruction->op3); assert(instruction->base.value.type->id == ZigTypeIdFloat || instruction->base.value.type->id == ZigTypeIdVector); - LLVMValueRef fn_val = get_float_fn(g, instruction->base.value.type, ZigLLVMFnIdFMA); + LLVMValueRef fn_val = get_float_fn(g, instruction->base.value.type, ZigLLVMFnIdFMA, BuiltinFnIdMulAdd); LLVMValueRef args[3] = { op1, op2, @@ -5814,8 +5804,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, return ir_render_merge_err_ret_traces(g, executable, (IrInstructionMergeErrRetTraces *)instruction); case IrInstructionIdMarkErrRetTracePtr: return ir_render_mark_err_ret_trace_ptr(g, executable, (IrInstructionMarkErrRetTracePtr *)instruction); - case IrInstructionIdSqrt: - return ir_render_sqrt(g, executable, (IrInstructionSqrt *)instruction); + case IrInstructionIdFloatOp: + return ir_render_float_op(g, executable, (IrInstructionFloatOp *)instruction); case IrInstructionIdMulAdd: return ir_render_mul_add(g, executable, (IrInstructionMulAdd *)instruction); case IrInstructionIdArrayToVector: @@ -7435,6 +7425,20 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdRem, "rem", 2); create_builtin_fn(g, BuiltinFnIdMod, "mod", 2); create_builtin_fn(g, BuiltinFnIdSqrt, "sqrt", 2); + create_builtin_fn(g, BuiltinFnIdSin, "sin", 2); + create_builtin_fn(g, BuiltinFnIdCos, "cos", 2); + create_builtin_fn(g, BuiltinFnIdExp, "exp", 2); + create_builtin_fn(g, BuiltinFnIdExp2, "exp2", 2); + create_builtin_fn(g, BuiltinFnIdLn, "ln", 2); + create_builtin_fn(g, BuiltinFnIdLog2, "log2", 2); + create_builtin_fn(g, BuiltinFnIdLog10, "log10", 2); + create_builtin_fn(g, BuiltinFnIdFabs, "fabs", 2); + create_builtin_fn(g, BuiltinFnIdFloor, "floor", 2); + create_builtin_fn(g, BuiltinFnIdCeil, "ceil", 2); + create_builtin_fn(g, BuiltinFnIdTrunc, "trunc", 2); + //Needs library support on Windows + //create_builtin_fn(g, BuiltinFnIdNearbyInt, "nearbyInt", 2); + create_builtin_fn(g, BuiltinFnIdRound, "round", 2); create_builtin_fn(g, BuiltinFnIdMulAdd, "mulAdd", 4); create_builtin_fn(g, BuiltinFnIdInlineCall, "inlineCall", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdNoInlineCall, "noInlineCall", SIZE_MAX); diff --git a/src/ir.cpp b/src/ir.cpp index c2c6cb615416f984fb8356514bdc41e59b246580..50d2a0686809fd832ce98e10c7a00972710d36ad 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -991,8 +991,8 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionMarkErrRetTraceP return IrInstructionIdMarkErrRetTracePtr; } -static constexpr IrInstructionId ir_instruction_id(IrInstructionSqrt *) { - return IrInstructionIdSqrt; +static constexpr IrInstructionId ir_instruction_id(IrInstructionFloatOp *) { + return IrInstructionIdFloatOp; } static constexpr IrInstructionId ir_instruction_id(IrInstructionCheckRuntimeScope *) { @@ -2312,6 +2312,59 @@ static IrInstruction *ir_build_overflow_op(IrBuilder *irb, Scope *scope, AstNode return &instruction->base; } + +//TODO Powi, Pow, minnum, maxnum, maximum, minimum, copysign, +// lround, llround, lrint, llrint +// So far this is only non-complicated type functions. +const char *float_op_to_name(BuiltinFnId op, bool llvm_name) { + const bool b = llvm_name; + + switch (op) { + case BuiltinFnIdSqrt: + return "sqrt"; + case BuiltinFnIdSin: + return "sin"; + case BuiltinFnIdCos: + return "cos"; + case BuiltinFnIdExp: + return "exp"; + case BuiltinFnIdExp2: + return "exp2"; + case BuiltinFnIdLn: + return b ? "log" : "ln"; + case BuiltinFnIdLog10: + return "log10"; + case BuiltinFnIdLog2: + return "log2"; + case BuiltinFnIdFabs: + return "fabs"; + case BuiltinFnIdFloor: + return "floor"; + case BuiltinFnIdCeil: + return "ceil"; + case BuiltinFnIdTrunc: + return "trunc"; + case BuiltinFnIdNearbyInt: + return b ? "nearbyint" : "nearbyInt"; + case BuiltinFnIdRound: + return "round"; + default: + zig_unreachable(); + } +} + +static IrInstruction *ir_build_float_op(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *type, IrInstruction *op1, BuiltinFnId op) { + IrInstructionFloatOp *instruction = ir_build_instruction(irb, scope, source_node); + instruction->type = type; + instruction->op1 = op1; + instruction->op = op; + + if (type != nullptr) ir_ref_instruction(type, irb->current_basic_block); + ir_ref_instruction(op1, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_build_mul_add(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *type_value, IrInstruction *op1, IrInstruction *op2, IrInstruction *op3) { IrInstructionMulAdd *instruction = ir_build_instruction(irb, scope, source_node); @@ -3033,17 +3086,6 @@ static IrInstruction *ir_build_mark_err_ret_trace_ptr(IrBuilder *irb, Scope *sco return &instruction->base; } -static IrInstruction *ir_build_sqrt(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *type, IrInstruction *op) { - IrInstructionSqrt *instruction = ir_build_instruction(irb, scope, source_node); - instruction->type = type; - instruction->op = op; - - if (type != nullptr) ir_ref_instruction(type, irb->current_basic_block); - ir_ref_instruction(op, irb->current_basic_block); - - return &instruction->base; -} - static IrInstruction *ir_build_has_decl(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *container, IrInstruction *name) { @@ -4400,6 +4442,19 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_lval_wrap(irb, scope, bin_op, lval); } case BuiltinFnIdSqrt: + case BuiltinFnIdSin: + case BuiltinFnIdCos: + case BuiltinFnIdExp: + case BuiltinFnIdExp2: + case BuiltinFnIdLn: + case BuiltinFnIdLog2: + case BuiltinFnIdLog10: + case BuiltinFnIdFabs: + case BuiltinFnIdFloor: + case BuiltinFnIdCeil: + case BuiltinFnIdTrunc: + case BuiltinFnIdNearbyInt: + case BuiltinFnIdRound: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); @@ -4411,7 +4466,7 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo if (arg1_value == irb->codegen->invalid_instruction) return arg1_value; - IrInstruction *ir_sqrt = ir_build_sqrt(irb, scope, node, arg0_value, arg1_value); + IrInstruction *ir_sqrt = ir_build_float_op(irb, scope, node, arg0_value, arg1_value, builtin_fn->id); return ir_lval_wrap(irb, scope, ir_sqrt, lval); } case BuiltinFnIdTruncate: @@ -23214,70 +23269,248 @@ static IrInstruction *ir_analyze_instruction_mark_err_ret_trace_ptr(IrAnalyze *i return result; } -static IrInstruction *ir_analyze_instruction_sqrt(IrAnalyze *ira, IrInstructionSqrt *instruction) { - ZigType *float_type = ir_resolve_type(ira, instruction->type->child); - if (type_is_invalid(float_type)) - return ira->codegen->invalid_instruction; +static void ir_eval_float_op(IrAnalyze *ira, IrInstructionFloatOp *source_instr, ZigType *float_type, + ConstExprValue *op, ConstExprValue *out_val) { + assert(ira && source_instr && float_type && out_val && op); + assert(float_type->id == ZigTypeIdFloat || + float_type->id == ZigTypeIdComptimeFloat); - IrInstruction *op = instruction->op->child; - if (type_is_invalid(op->value.type)) - return ira->codegen->invalid_instruction; + BuiltinFnId fop = source_instr->op; + unsigned bits; - bool ok_type = float_type->id == ZigTypeIdComptimeFloat || float_type->id == ZigTypeIdFloat; - if (!ok_type) { - ir_add_error(ira, instruction->type, buf_sprintf("@sqrt does not support type '%s'", buf_ptr(&float_type->name))); - return ira->codegen->invalid_instruction; - } - - IrInstruction *casted_op = ir_implicit_cast(ira, op, float_type); - if (type_is_invalid(casted_op->value.type)) - return ira->codegen->invalid_instruction; - - if (instr_is_comptime(casted_op)) { - ConstExprValue *val = ir_resolve_const(ira, casted_op, UndefBad); - if (!val) - return ira->codegen->invalid_instruction; - - IrInstruction *result = ir_const(ira, &instruction->base, float_type); - ConstExprValue *out_val = &result->value; + if (float_type->id == ZigTypeIdComptimeFloat) { + bits = 128; + } else if (float_type->id == ZigTypeIdFloat) + bits = float_type->data.floating.bit_count; + switch (bits) { + case 16: { + switch (fop) { + case BuiltinFnIdSqrt: + out_val->data.x_f16 = f16_sqrt(op->data.x_f16); + break; + case BuiltinFnIdSin: + case BuiltinFnIdCos: + case BuiltinFnIdExp: + case BuiltinFnIdExp2: + case BuiltinFnIdLn: + case BuiltinFnIdLog10: + case BuiltinFnIdLog2: + case BuiltinFnIdFabs: + case BuiltinFnIdFloor: + case BuiltinFnIdCeil: + case BuiltinFnIdTrunc: + case BuiltinFnIdNearbyInt: + case BuiltinFnIdRound: + zig_panic("unimplemented f16 builtin"); + default: + zig_unreachable(); + }; + break; + }; + case 32: { + switch (fop) { + case BuiltinFnIdSqrt: + out_val->data.x_f32 = sqrtf(op->data.x_f32); + break; + case BuiltinFnIdSin: + out_val->data.x_f32 = sinf(op->data.x_f32); + break; + case BuiltinFnIdCos: + out_val->data.x_f32 = cosf(op->data.x_f32); + break; + case BuiltinFnIdExp: + out_val->data.x_f32 = expf(op->data.x_f32); + break; + case BuiltinFnIdExp2: + out_val->data.x_f32 = exp2f(op->data.x_f32); + break; + case BuiltinFnIdLn: + out_val->data.x_f32 = logf(op->data.x_f32); + break; + case BuiltinFnIdLog10: + out_val->data.x_f32 = log10f(op->data.x_f32); + break; + case BuiltinFnIdLog2: + out_val->data.x_f32 = log2f(op->data.x_f32); + break; + case BuiltinFnIdFabs: + out_val->data.x_f32 = fabsf(op->data.x_f32); + break; + case BuiltinFnIdFloor: + out_val->data.x_f32 = floorf(op->data.x_f32); + break; + case BuiltinFnIdCeil: + out_val->data.x_f32 = ceilf(op->data.x_f32); + break; + case BuiltinFnIdTrunc: + out_val->data.x_f32 = truncf(op->data.x_f32); + break; + case BuiltinFnIdNearbyInt: + out_val->data.x_f32 = nearbyintf(op->data.x_f32); + break; + case BuiltinFnIdRound: + out_val->data.x_f32 = roundf(op->data.x_f32); + break; + default: + zig_unreachable(); + }; + break; + }; + case 64: { + switch (fop) { + case BuiltinFnIdSqrt: + out_val->data.x_f64 = sqrt(op->data.x_f64); + break; + case BuiltinFnIdSin: + out_val->data.x_f64 = sin(op->data.x_f64); + break; + case BuiltinFnIdCos: + out_val->data.x_f64 = cos(op->data.x_f64); + break; + case BuiltinFnIdExp: + out_val->data.x_f64 = exp(op->data.x_f64); + break; + case BuiltinFnIdExp2: + out_val->data.x_f64 = exp2(op->data.x_f64); + break; + case BuiltinFnIdLn: + out_val->data.x_f64 = log(op->data.x_f64); + break; + case BuiltinFnIdLog10: + out_val->data.x_f64 = log10(op->data.x_f64); + break; + case BuiltinFnIdLog2: + out_val->data.x_f64 = log2(op->data.x_f64); + break; + case BuiltinFnIdFabs: + out_val->data.x_f64 = fabs(op->data.x_f64); + break; + case BuiltinFnIdFloor: + out_val->data.x_f64 = floor(op->data.x_f64); + break; + case BuiltinFnIdCeil: + out_val->data.x_f64 = ceil(op->data.x_f64); + break; + case BuiltinFnIdTrunc: + out_val->data.x_f64 = trunc(op->data.x_f64); + break; + case BuiltinFnIdNearbyInt: + out_val->data.x_f64 = nearbyint(op->data.x_f64); + break; + case BuiltinFnIdRound: + out_val->data.x_f64 = round(op->data.x_f64); + break; + default: + zig_unreachable(); + } + break; + }; + case 128: { + float128_t *out, *in; if (float_type->id == ZigTypeIdComptimeFloat) { - bigfloat_sqrt(&out_val->data.x_bigfloat, &val->data.x_bigfloat); - } else if (float_type->id == ZigTypeIdFloat) { - switch (float_type->data.floating.bit_count) { - case 16: - out_val->data.x_f16 = f16_sqrt(val->data.x_f16); - break; - case 32: - out_val->data.x_f32 = sqrtf(val->data.x_f32); - break; - case 64: - out_val->data.x_f64 = sqrt(val->data.x_f64); - break; - case 128: - f128M_sqrt(&val->data.x_f128, &out_val->data.x_f128); - break; - default: - zig_unreachable(); - } + out = &out_val->data.x_bigfloat.value; + in = &op->data.x_bigfloat.value; } else { + out = &out_val->data.x_f128; + in = &op->data.x_f128; + } + switch (fop) { + case BuiltinFnIdSqrt: + f128M_sqrt(in, out); + break; + case BuiltinFnIdNearbyInt: + case BuiltinFnIdSin: + case BuiltinFnIdCos: + case BuiltinFnIdExp: + case BuiltinFnIdExp2: + case BuiltinFnIdLn: + case BuiltinFnIdLog10: + case BuiltinFnIdLog2: + case BuiltinFnIdFabs: + case BuiltinFnIdFloor: + case BuiltinFnIdCeil: + case BuiltinFnIdTrunc: + case BuiltinFnIdRound: + zig_panic("unimplemented f128 builtin"); + default: zig_unreachable(); } + break; + }; + default: + zig_unreachable(); + } +} +static IrInstruction *ir_analyze_instruction_float_op(IrAnalyze *ira, IrInstructionFloatOp *instruction) { + IrInstruction *type = instruction->type->child; + if (type_is_invalid(type->value.type)) + return ira->codegen->invalid_instruction; + + ZigType *expr_type = ir_resolve_type(ira, type); + if (type_is_invalid(expr_type)) + return ira->codegen->invalid_instruction; + + // Only allow float types, and vectors of floats. + ZigType *float_type = (expr_type->id == ZigTypeIdVector) ? expr_type->data.vector.elem_type : expr_type; + if (float_type->id != ZigTypeIdFloat && float_type->id != ZigTypeIdComptimeFloat) { + ir_add_error(ira, instruction->type, buf_sprintf("@%s does not support type '%s'", float_op_to_name(instruction->op, false), buf_ptr(&float_type->name))); + return ira->codegen->invalid_instruction; + } + + IrInstruction *op1 = instruction->op1->child; + if (type_is_invalid(op1->value.type)) + return ira->codegen->invalid_instruction; + + IrInstruction *casted_op1 = ir_implicit_cast(ira, op1, float_type); + if (type_is_invalid(casted_op1->value.type)) + return ira->codegen->invalid_instruction; + + if (instr_is_comptime(casted_op1)) { + // Our comptime 16-bit and 128-bit support is quite limited. + if ((float_type->id == ZigTypeIdComptimeFloat || + float_type->data.floating.bit_count == 16 || + float_type->data.floating.bit_count == 128) && + instruction->op != BuiltinFnIdSqrt) { + ir_add_error(ira, instruction->type, buf_sprintf("@%s does not support type '%s'", float_op_to_name(instruction->op, false), buf_ptr(&float_type->name))); + return ira->codegen->invalid_instruction; + } + + ConstExprValue *op1_const = ir_resolve_const(ira, casted_op1, UndefBad); + if (!op1_const) + return ira->codegen->invalid_instruction; + + IrInstruction *result = ir_const(ira, &instruction->base, expr_type); + ConstExprValue *out_val = &result->value; + + if (expr_type->id == ZigTypeIdVector) { + expand_undef_array(ira->codegen, op1_const); + out_val->special = ConstValSpecialUndef; + expand_undef_array(ira->codegen, out_val); + size_t len = expr_type->data.vector.len; + for (size_t i = 0; i < len; i += 1) { + ConstExprValue *float_operand_op1 = &op1_const->data.x_array.data.s_none.elements[i]; + ConstExprValue *float_out_val = &out_val->data.x_array.data.s_none.elements[i]; + assert(float_operand_op1->type == float_type); + assert(float_out_val->type == float_type); + ir_eval_float_op(ira, instruction, float_type, + op1_const, float_out_val); + float_out_val->type = float_type; + } + out_val->type = expr_type; + out_val->special = ConstValSpecialStatic; + } else { + ir_eval_float_op(ira, instruction, float_type, op1_const, out_val); + } return result; } ir_assert(float_type->id == ZigTypeIdFloat, &instruction->base); - if (float_type->data.floating.bit_count != 16 && - float_type->data.floating.bit_count != 32 && - float_type->data.floating.bit_count != 64) { - ir_add_error(ira, instruction->type, buf_sprintf("compiler TODO: add implementation of sqrt for '%s'", buf_ptr(&float_type->name))); - return ira->codegen->invalid_instruction; - } - IrInstruction *result = ir_build_sqrt(&ira->new_irb, instruction->base.scope, - instruction->base.source_node, nullptr, casted_op); - result->value.type = float_type; + IrInstruction *result = ir_build_float_op(&ira->new_irb, instruction->base.scope, + instruction->base.source_node, nullptr, casted_op1, instruction->op); + result->value.type = expr_type; return result; } @@ -23762,8 +23995,8 @@ static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructio return ir_analyze_instruction_merge_err_ret_traces(ira, (IrInstructionMergeErrRetTraces *)instruction); case IrInstructionIdMarkErrRetTracePtr: return ir_analyze_instruction_mark_err_ret_trace_ptr(ira, (IrInstructionMarkErrRetTracePtr *)instruction); - case IrInstructionIdSqrt: - return ir_analyze_instruction_sqrt(ira, (IrInstructionSqrt *)instruction); + case IrInstructionIdFloatOp: + return ir_analyze_instruction_float_op(ira, (IrInstructionFloatOp *)instruction); case IrInstructionIdMulAdd: return ir_analyze_instruction_mul_add(ira, (IrInstructionMulAdd *)instruction); case IrInstructionIdIntToErr: @@ -24004,7 +24237,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdCoroFree: case IrInstructionIdCoroPromise: case IrInstructionIdPromiseResultType: - case IrInstructionIdSqrt: + case IrInstructionIdFloatOp: case IrInstructionIdMulAdd: case IrInstructionIdAtomicLoad: case IrInstructionIdIntCast: diff --git a/src/ir.hpp b/src/ir.hpp index 4fb75522122f236329385d1445c428355982fcd0..597624e2e67448c1d42fe34154d97def52c54e96 100644 --- a/src/ir.hpp +++ b/src/ir.hpp @@ -26,5 +26,6 @@ bool ir_has_side_effects(IrInstruction *instruction); struct IrAnalyze; ConstExprValue *const_ptr_pointee(IrAnalyze *ira, CodeGen *codegen, ConstExprValue *const_val, AstNode *source_node); +const char *float_op_to_name(BuiltinFnId op, bool llvm_name); #endif diff --git a/src/ir_print.cpp b/src/ir_print.cpp index e205c8e067c039e0fa381e93e2df822c202f1045..165d9b473946b4e758b3d094447f7407098fc4ee 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -1427,15 +1427,16 @@ static void ir_print_mark_err_ret_trace_ptr(IrPrint *irp, IrInstructionMarkErrRe fprintf(irp->f, ")"); } -static void ir_print_sqrt(IrPrint *irp, IrInstructionSqrt *instruction) { - fprintf(irp->f, "@sqrt("); +static void ir_print_float_op(IrPrint *irp, IrInstructionFloatOp *instruction) { + + fprintf(irp->f, "@%s(", float_op_to_name(instruction->op, false)); if (instruction->type != nullptr) { ir_print_other_instruction(irp, instruction->type); } else { fprintf(irp->f, "null"); } fprintf(irp->f, ","); - ir_print_other_instruction(irp, instruction->op); + ir_print_other_instruction(irp, instruction->op1); fprintf(irp->f, ")"); } @@ -1918,8 +1919,8 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdMarkErrRetTracePtr: ir_print_mark_err_ret_trace_ptr(irp, (IrInstructionMarkErrRetTracePtr *)instruction); break; - case IrInstructionIdSqrt: - ir_print_sqrt(irp, (IrInstructionSqrt *)instruction); + case IrInstructionIdFloatOp: + ir_print_float_op(irp, (IrInstructionFloatOp *)instruction); break; case IrInstructionIdMulAdd: ir_print_mul_add(irp, (IrInstructionMulAdd *)instruction); diff --git a/src/util.cpp b/src/util.cpp index 9a6a3829934d5b18b3e4692adf679e7aba64f316..f85565806f2b72513d8b8634a4f0da45f82e44ff 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -13,6 +13,7 @@ #include "userland.h" void zig_panic(const char *format, ...) { + abort(); va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); diff --git a/std/special/c.zig b/std/special/c.zig index b3cf54619feccad4bb236d02ace956fe95bcc5f4..15cefbd2a0974f5ab1222b9eee503ae5818808af 100644 --- a/std/special/c.zig +++ b/std/special/c.zig @@ -254,24 +254,32 @@ export fn fmod(x: f64, y: f64) f64 { // TODO add intrinsics for these (and probably the double version too) // and have the math stuff use the intrinsic. same as @mod and @rem -export fn floorf(x: f32) f32 { - return math.floor(x); -} -export fn ceilf(x: f32) f32 { - return math.ceil(x); -} -export fn floor(x: f64) f64 { - return math.floor(x); -} -export fn ceil(x: f64) f64 { - return math.ceil(x); -} -export fn fma(a: f64, b: f64, c: f64) f64 { - return math.fma(f64, a, b, c); -} -export fn fmaf(a: f32, b: f32, c: f32) f32 { - return math.fma(f32, a, b, c); -} +export fn floorf(x: f32) f32 {return math.floor(x);} +export fn ceilf(x: f32) f32 {return math.ceil(x);} +export fn floor(x: f64) f64 {return math.floor(x);} +export fn ceil(x: f64) f64 {return math.ceil(x);} +export fn fma(a: f64, b: f64, c: f64) f64 {return math.fma(f64, a, b, c);} +export fn fmaf(a: f32, b: f32, c: f32) f32 {return math.fma(f32, a, b, c);} +export fn sin(a: f64) f64 {return math.sin(a);} +export fn sinf(a: f32) f32 {return math.sin(a);} +export fn cos(a: f64) f64 {return math.cos(a);} +export fn cosf(a: f32) f32 {return math.cos(a);} +export fn exp(a: f64) f64 {return math.exp(a);} +export fn expf(a: f32) f32 {return math.exp(a);} +export fn exp2(a: f64) f64 {return math.exp2(a);} +export fn exp2f(a: f32) f32 {return math.exp2(a);} +export fn log(a: f64) f64 {return math.ln(a);} +export fn logf(a: f32) f32 {return math.ln(a);} +export fn log2(a: f64) f64 {return math.log2(a);} +export fn log2f(a: f32) f32 {return math.log2(a);} +export fn log10(a: f64) f64 {return math.log10(a);} +export fn log10f(a: f32) f32 {return math.log10(a);} +export fn fabs(a: f64) f64 {return math.fabs(a);} +export fn fabsf(a: f32) f32 {return math.fabs(a);} +export fn trunc(a: f64) f64 {return math.trunc(a);} +export fn truncf(a: f32) f32 {return math.trunc(a);} +export fn round(a: f64) f64 {return math.round(a);} +export fn roundf(a: f32) f32 {return math.round(a);} fn generic_fmod(comptime T: type, x: T, y: T) T { @setRuntimeSafety(false); diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index 10e7c1a09beb6ff3a6d622da1f9a061f51b05d4f..efefed33ba345539342f926838df3e64dc7170a0 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -71,6 +71,7 @@ comptime { _ = @import("behavior/pointers.zig"); _ = @import("behavior/popcount.zig"); _ = @import("behavior/muladd.zig"); + _ = @import("behavior/floatop.zig"); _ = @import("behavior/ptrcast.zig"); _ = @import("behavior/pub_enum.zig"); _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig"); diff --git a/test/stage1/behavior/floatop.zig b/test/stage1/behavior/floatop.zig new file mode 100644 index 0000000000000000000000000000000000000000..de2f6815a6231c8da112bd63e65fb97bef5b2d2a --- /dev/null +++ b/test/stage1/behavior/floatop.zig @@ -0,0 +1,243 @@ +const expect = @import("std").testing.expect; +const pi = @import("std").math.pi; +const e = @import("std").math.e; + +test "@sqrt" { + comptime testSqrt(); + testSqrt(); +} + +fn testSqrt() void { + { + var a: f16 = 4; + expect(@sqrt(f16, a) == 2); + } + { + var a: f32 = 9; + expect(@sqrt(f32, a) == 3); + } + { + var a: f64 = 25; + expect(@sqrt(f64, a) == 5); + } + { + const a: comptime_float = 25.0; + expect(@sqrt(comptime_float, a) == 5.0); + } + // Waiting on a c.zig implementation + //{ + // var a: f128 = 49; + // expect(@sqrt(f128, a) == 7); + //} +} + +test "@sin" { + comptime testSin(); + testSin(); +} + +fn testSin() void { + // TODO - this is actually useful and should be implemented + // (all the trig functions for f16) + // but will probably wait till self-hosted + //{ + // var a: f16 = pi; + // expect(@sin(f16, a/2) == 1); + //} + { + var a: f32 = 0; + expect(@sin(f32, a) == 0); + } + { + var a: f64 = 0; + expect(@sin(f64, a) == 0); + } + // TODO + //{ + // var a: f16 = pi; + // expect(@sqrt(f128, a/2) == 1); + //} +} + +test "@cos" { + comptime testCos(); + testCos(); +} + +fn testCos() void { + { + var a: f32 = 0; + expect(@cos(f32, a) == 1); + } + { + var a: f64 = 0; + expect(@cos(f64, a) == 1); + } +} + +test "@exp" { + comptime testExp(); + testExp(); +} + +fn testExp() void { + { + var a: f32 = 0; + expect(@exp(f32, a) == 1); + } + { + var a: f64 = 0; + expect(@exp(f64, a) == 1); + } +} + +test "@exp2" { + comptime testExp2(); + testExp2(); +} + +fn testExp2() void { + { + var a: f32 = 2; + expect(@exp2(f32, a) == 4); + } + { + var a: f64 = 2; + expect(@exp2(f64, a) == 4); + } +} + +test "@ln" { + // Old musl (and glibc?), and our current math.ln implementation do not return 1 + // so also accept those values. + comptime testLn(); + testLn(); +} + +fn testLn() void { + { + var a: f32 = e; + expect(@ln(f32, a) == 1 or @ln(f32, a) == @bitCast(f32, u32(0x3f7fffff))); + } + { + var a: f64 = e; + expect(@ln(f64, a) == 1 or @ln(f64, a) == @bitCast(f64, u64(0x3ff0000000000000))); + } +} + +test "@log2" { + comptime testLog2(); + testLog2(); +} + +fn testLog2() void { + { + var a: f32 = 4; + expect(@log2(f32, a) == 2); + } + { + var a: f64 = 4; + expect(@log2(f64, a) == 2); + } +} + +test "@log10" { + comptime testLog10(); + testLog10(); +} + +fn testLog10() void { + { + var a: f32 = 100; + expect(@log10(f32, a) == 2); + } + { + var a: f64 = 1000; + expect(@log10(f64, a) == 3); + } +} + +test "@fabs" { + comptime testFabs(); + testFabs(); +} + +fn testFabs() void { + { + var a: f32 = -2.5; + var b: f32 = 2.5; + expect(@fabs(f32, a) == 2.5); + expect(@fabs(f32, b) == 2.5); + } + { + var a: f64 = -2.5; + var b: f64 = 2.5; + expect(@fabs(f64, a) == 2.5); + expect(@fabs(f64, b) == 2.5); + } +} + +test "@floor" { + comptime testFloor(); + testFloor(); +} + +fn testFloor() void { + { + var a: f32 = 2.1; + expect(@floor(f32, a) == 2); + } + { + var a: f64 = 3.5; + expect(@floor(f64, a) == 3); + } +} + +test "@ceil" { + comptime testCeil(); + testCeil(); +} + +fn testCeil() void { + { + var a: f32 = 2.1; + expect(@ceil(f32, a) == 3); + } + { + var a: f64 = 3.5; + expect(@ceil(f64, a) == 4); + } +} + +test "@trunc" { + comptime testTrunc(); + testTrunc(); +} + +fn testTrunc() void { + { + var a: f32 = 2.1; + expect(@trunc(f32, a) == 2); + } + { + var a: f64 = -3.5; + expect(@trunc(f64, a) == -3); + } +} + +// This is waiting on library support for the Windows build (not sure why the other's don't need it) +//test "@nearbyInt" { +// comptime testNearbyInt(); +// testNearbyInt(); +//} + +//fn testNearbyInt() void { +// { +// var a: f32 = 2.1; +// expect(@nearbyInt(f32, a) == 2); +// } +// { +// var a: f64 = -3.75; +// expect(@nearbyInt(f64, a) == -4); +// } +//} -- 2.54.0 From ca3660f6bf3a1f8d77692acf72eefe148802d342 Mon Sep 17 00:00:00 2001 From: emekoi Date: Sun, 23 Jun 2019 01:15:04 -0500 Subject: [PATCH 08/15] increase stack size for mingw --- CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a6339290f997126cd868da05f22fb77a038f5195..e20e5d5fcd7eddd3f7c255dbd4e6915a482a334d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6653,15 +6653,18 @@ set(OPTIMIZED_C_FLAGS "-std=c99 -O3") set(EXE_LDFLAGS " ") if(MSVC) set(EXE_LDFLAGS "/STACK:16777216") -elseif(ZIG_STATIC) +elseif(MINGW) + set(EXE_LDFLAGS "${EXE_LDFLAGS} -Wl,--stack,16777216") +endif() + +if(ZIG_STATIC) if(APPLE) set(EXE_LDFLAGS "-static-libgcc -static-libstdc++") else() set(EXE_LDFLAGS "-static") endif() -else() - set(EXE_LDFLAGS " ") endif() + if(ZIG_TEST_COVERAGE) set(EXE_CFLAGS "${EXE_CFLAGS} -fprofile-arcs -ftest-coverage") set(EXE_LDFLAGS "${EXE_LDFLAGS} -fprofile-arcs -ftest-coverage") -- 2.54.0 From 7f4de2dfdbeac4457e6e3170dad9ba9bf67ac9c8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 23 Jun 2019 17:14:10 -0400 Subject: [PATCH 09/15] remove stray abort --- src/util.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/util.cpp b/src/util.cpp index f85565806f2b72513d8b8634a4f0da45f82e44ff..9a6a3829934d5b18b3e4692adf679e7aba64f316 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -13,7 +13,6 @@ #include "userland.h" void zig_panic(const char *format, ...) { - abort(); va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); -- 2.54.0 From de2b0cd722ca8fe98d16c86825db4cb2a70931c6 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Mon, 24 Jun 2019 15:30:28 -0400 Subject: [PATCH 10/15] fix compile error when building zig w/ clang errors as reported on macOS w/ Xcode 10.1, 10.2 and 11.0: src/ir.cpp:23285:16: error: variable 'bits' is used uninitialized whenever 'if' condition is false [-Werror,-Wsometimes-uninitialized] } else if (float_type->id == ZigTypeIdFloat) ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/ir.cpp:23288:13: note: uninitialized use occurs here switch (bits) { ^~~~ src/ir.cpp:23285:12: note: remove the 'if' if its condition is always true } else if (float_type->id == ZigTypeIdFloat) ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/ir.cpp:23281:18: note: initialize the variable 'bits' to silence this warning unsigned bits; --- src/ir.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 5a4a53b80448cbb0ea21b829fdcf73e0b4cda5b5..0c793166528a79271a9967ccca7c8ea9d5c48ca8 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -23280,10 +23280,16 @@ static void ir_eval_float_op(IrAnalyze *ira, IrInstructionFloatOp *source_instr, BuiltinFnId fop = source_instr->op; unsigned bits; - if (float_type->id == ZigTypeIdComptimeFloat) { + switch (float_type->id) { + case ZigTypeIdComptimeFloat: bits = 128; - } else if (float_type->id == ZigTypeIdFloat) + break; + case ZigTypeIdFloat: bits = float_type->data.floating.bit_count; + break; + default: + zig_unreachable(); + } switch (bits) { case 16: { -- 2.54.0 From 08e8d30dd642e42d8b8b16f43b487dbf42adb5ba Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Tue, 25 Jun 2019 17:11:04 +1200 Subject: [PATCH 11/15] Add parsing of fill and alignment in std.format These options are now available to use when printing, however nothing currently makes use of these. --- std/fmt.zig | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/std/fmt.zig b/std/fmt.zig index 038efbfb7fd1ade85192d0c972ac96489b237f3e..7ff6ea80817aa616b8cddea62b2f6dd392d0890d 100644 --- a/std/fmt.zig +++ b/std/fmt.zig @@ -10,9 +10,17 @@ const lossyCast = std.math.lossyCast; pub const default_max_depth = 3; +pub const Alignment = enum { + Left, + Center, + Right, +}; + pub const FormatOptions = struct { precision: ?usize = null, width: ?usize = null, + alignment: ?Alignment = null, + fill: u8 = ' ', }; fn nextArg(comptime used_pos_args: *u32, comptime maybe_pos_arg: ?comptime_int, comptime next_arg: *comptime_int) comptime_int { @@ -26,6 +34,18 @@ fn nextArg(comptime used_pos_args: *u32, comptime maybe_pos_arg: ?comptime_int, } } +fn peekIsAlign(comptime fmt: []const u8) bool { + // Should only be called during a state transition to the format segment. + std.debug.assert(fmt[0] == ':'); + + inline for (([_]u8{ 1, 2 })[0..]) |i| { + if (fmt.len > i and (fmt[i] == '<' or fmt[i] == '^' or fmt[i] == '>')) { + return true; + } + } + return false; +} + /// Renders fmt string with args, calling output with slices of bytes. /// If `output` returns an error, the error is returned from `format` and /// `output` is not called again. @@ -46,6 +66,7 @@ pub fn format( Positional, CloseBrace, Specifier, + FormatFillAndAlign, FormatWidth, FormatPrecision, Pointer, @@ -92,7 +113,7 @@ pub fn format( state = .Pointer; }, ':' => { - state = .FormatWidth; + state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; specifier_end = i; }, '0'...'9' => { @@ -139,7 +160,7 @@ pub fn format( .Specifier => switch (c) { ':' => { specifier_end = i; - state = .FormatWidth; + state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; }, '}' => { const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); @@ -158,6 +179,24 @@ pub fn format( }, else => {}, }, + // Only entered if the format string contains a fill/align segment. + .FormatFillAndAlign => switch (c) { + '<' => { + options.alignment = Alignment.Left; + state = .FormatWidth; + }, + '^' => { + options.alignment = Alignment.Center; + state = .FormatWidth; + }, + '>' => { + options.alignment = Alignment.Right; + state = .FormatWidth; + }, + else => { + options.fill = c; + }, + }, .FormatWidth => switch (c) { '0'...'9' => { if (options.width == null) { @@ -1571,3 +1610,7 @@ test "positional" { test "positional with specifier" { try testFmt("10.0", "{0d:.1}", f64(9.999)); } + +test "positional/alignment/width/precision" { + try testFmt("10.0", "{0d: >3.1}", f64(9.999)); +} -- 2.54.0 From cd02630da82b6b0a59583160b12c286a7aec7f8c Mon Sep 17 00:00:00 2001 From: Carter Sande Date: Mon, 24 Jun 2019 22:32:50 -0700 Subject: [PATCH 12/15] compiler-rt: Support thumb versions older than armv6 Add versions of __aeabi_memset and __aeabi_memclr which do not use mov instructions between low registers, as this is unsupported on thumbv4t and thumbv5. --- std/special/compiler_rt.zig | 43 +++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/std/special/compiler_rt.zig b/std/special/compiler_rt.zig index 46607a3adfc92750c85b4828d92a7ec34fa50702..95867d6952ca69757ac3adf7cbed4817831f33dc 100644 --- a/std/special/compiler_rt.zig +++ b/std/special/compiler_rt.zig @@ -471,6 +471,28 @@ test "usesThumb1" { //etc. } +const use_thumb_1_pre_armv6 = usesThumb1PreArmv6(builtin.arch); + +fn usesThumb1PreArmv6(arch: builtin.Arch) bool { + return switch (arch) { + .thumb => switch (arch.thumb) { + .v5, + .v5te, + .v4t, + => true, + else => false, + }, + .thumbeb => switch (arch.thumbeb) { + .v5, + .v5te, + .v4t, + => true, + else => false, + }, + else => false, + }; +} + nakedcc fn __aeabi_memcpy() noreturn { @setRuntimeSafety(false); if (use_thumb_1) { @@ -505,7 +527,16 @@ nakedcc fn __aeabi_memmove() noreturn { nakedcc fn __aeabi_memset() noreturn { @setRuntimeSafety(false); - if (use_thumb_1) { + if (use_thumb_1_pre_armv6) { + asm volatile ( + \\ eors r1, r2 + \\ eors r2, r1 + \\ eors r1, r2 + \\ push {r7, lr} + \\ b memset + \\ pop {r7, pc} + ); + } else if (use_thumb_1) { asm volatile ( \\ mov r3, r1 \\ mov r1, r2 @@ -527,7 +558,15 @@ nakedcc fn __aeabi_memset() noreturn { nakedcc fn __aeabi_memclr() noreturn { @setRuntimeSafety(false); - if (use_thumb_1) { + if (use_thumb_1_pre_armv6) { + asm volatile ( + \\ adds r2, r1, #0 + \\ movs r1, #0 + \\ push {r7, lr} + \\ bl memset + \\ pop {r7, pc} + ); + } else if (use_thumb_1) { asm volatile ( \\ mov r2, r1 \\ movs r1, #0 -- 2.54.0 From f9e26d98711ead110a4c2d2f3032978a5a8c2d9d Mon Sep 17 00:00:00 2001 From: Carter Sande Date: Tue, 25 Jun 2019 22:54:10 -0700 Subject: [PATCH 13/15] compiler-rt: use more idiomatic switch syntax --- std/special/compiler_rt.zig | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/std/special/compiler_rt.zig b/std/special/compiler_rt.zig index 95867d6952ca69757ac3adf7cbed4817831f33dc..914f9dcb008edafd2de85ebbc3b3f7323afd7622 100644 --- a/std/special/compiler_rt.zig +++ b/std/special/compiler_rt.zig @@ -405,15 +405,15 @@ const use_thumb_1 = usesThumb1(builtin.arch); fn usesThumb1(arch: builtin.Arch) bool { return switch (arch) { - .arm => switch (arch.arm) { + .arm => |sub_arch| switch (sub_arch) { .v6m => true, else => false, }, - .armeb => switch (arch.armeb) { + .armeb => |sub_arch| switch (sub_arch) { .v6m => true, else => false, }, - .thumb => switch (arch.thumb) { + .thumb => |sub_arch| switch (sub_arch) { .v5, .v5te, .v4t, @@ -423,7 +423,7 @@ fn usesThumb1(arch: builtin.Arch) bool { => true, else => false, }, - .thumbeb => switch (arch.thumbeb) { + .thumbeb => |sub_arch| switch (sub_arch) { .v5, .v5te, .v4t, @@ -475,18 +475,12 @@ const use_thumb_1_pre_armv6 = usesThumb1PreArmv6(builtin.arch); fn usesThumb1PreArmv6(arch: builtin.Arch) bool { return switch (arch) { - .thumb => switch (arch.thumb) { - .v5, - .v5te, - .v4t, - => true, + .thumb => |sub_arch| switch (sub_arch) { + .v5, .v5te, .v4t => true, else => false, }, - .thumbeb => switch (arch.thumbeb) { - .v5, - .v5te, - .v4t, - => true, + .thumbeb => |sub_arch| switch (sub_arch) { + .v5, .v5te, .v4t => true, else => false, }, else => false, -- 2.54.0 From fa42c99d82448c74a657d16e0f2e5f9877e364c0 Mon Sep 17 00:00:00 2001 From: Vexu <15308111+Vexu@users.noreply.github.com> Date: Mon, 24 Jun 2019 10:29:58 +0300 Subject: [PATCH 14/15] fixed IfTypeExpr parsing --- std/zig/parse.zig | 4 ++-- std/zig/parser_test.zig | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/std/zig/parse.zig b/std/zig/parse.zig index 7a8287db0c4bd62ac4638240c9723d8ec308d4d1..da258c9237b578f312a9880248b1f64feb7de67c 100644 --- a/std/zig/parse.zig +++ b/std/zig/parse.zig @@ -2833,8 +2833,8 @@ fn parseIf(arena: *Allocator, it: *TokenIterator, tree: *Tree, bodyParseFn: Node const else_token = eatToken(it, .Keyword_else) orelse return node; const payload = try parsePayload(arena, it, tree); - const else_expr = try expectNode(arena, it, tree, parseExpr, AstError{ - .ExpectedExpr = AstError.ExpectedExpr{ .token = it.index }, + const else_expr = try expectNode(arena, it, tree, bodyParseFn, AstError{ + .InvalidToken = AstError.InvalidToken{ .token = it.index }, }); const else_node = try arena.create(Node.Else); else_node.* = Node.Else{ diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 281f09d57b2771b2357a94392cdc0d7f991a4f67..3998ca5070572e41089ade73fd3cf2a047bbd74e 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -2234,6 +2234,18 @@ test "zig fmt: multiline string in array" { ); } +test "zig fmt: if type expr" { + try testCanonical( + \\const mycond = true; + \\pub fn foo() if (mycond) i32 else void { + \\ if (mycond) { + \\ return 42; + \\ } + \\} + \\ + ); +} + const std = @import("std"); const mem = std.mem; const warn = std.debug.warn; -- 2.54.0 From 07c0d484eec327ca3bd1e3ce081c1ced230536a9 Mon Sep 17 00:00:00 2001 From: Luna Date: Wed, 26 Jun 2019 13:40:26 -0300 Subject: [PATCH 15/15] net: quickfix on non-existing Address.family --- std/net.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/std/net.zig b/std/net.zig index 8c4ab399b6b46cdcec09519a6257b4d7057cc43e..efcbf7000d441acf3de3ab1d12a56f2531706f3d 100644 --- a/std/net.zig +++ b/std/net.zig @@ -33,7 +33,6 @@ pub const Address = struct { pub fn initIp6(ip6: *const Ip6Addr, _port: u16) Address { return Address{ - .family = os.AF_INET6, .os_addr = os.sockaddr{ .in6 = os.sockaddr_in6{ .family = os.AF_INET6, -- 2.54.0