diff --git a/CMakeLists.txt b/CMakeLists.txt
index 98b5cbd6ac..6b44b7032d 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"
@@ -6653,15 +6655,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")
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 92c1aeba8e..65e27821ee 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#}
@@ -7347,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-self-hosted/dep_tokenizer.zig b/src-self-hosted/dep_tokenizer.zig
index 2721944451..f802765bc0 100644
--- a/src-self-hosted/dep_tokenizer.zig
+++ b/src-self-hosted/dep_tokenizer.zig
@@ -998,7 +998,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/src/all_types.hpp b/src/all_types.hpp
index 4be6f57593..774a00fdb7 100644
--- a/src/all_types.hpp
+++ b/src/all_types.hpp
@@ -1419,6 +1419,7 @@ enum BuiltinFnId {
BuiltinFnIdSubWithOverflow,
BuiltinFnIdMulWithOverflow,
BuiltinFnIdShlWithOverflow,
+ BuiltinFnIdMulAdd,
BuiltinFnIdCInclude,
BuiltinFnIdCDefine,
BuiltinFnIdCUndef,
@@ -1446,6 +1447,19 @@ enum BuiltinFnId {
BuiltinFnIdRem,
BuiltinFnIdMod,
BuiltinFnIdSqrt,
+ BuiltinFnIdSin,
+ BuiltinFnIdCos,
+ BuiltinFnIdExp,
+ BuiltinFnIdExp2,
+ BuiltinFnIdLn,
+ BuiltinFnIdLog2,
+ BuiltinFnIdLog10,
+ BuiltinFnIdFabs,
+ BuiltinFnIdFloor,
+ BuiltinFnIdCeil,
+ BuiltinFnIdTrunc,
+ BuiltinFnIdNearbyInt,
+ BuiltinFnIdRound,
BuiltinFnIdTruncate,
BuiltinFnIdIntCast,
BuiltinFnIdFloatCast,
@@ -1567,9 +1581,8 @@ enum ZigLLVMFnId {
ZigLLVMFnIdClz,
ZigLLVMFnIdPopCount,
ZigLLVMFnIdOverflowArithmetic,
- ZigLLVMFnIdFloor,
- ZigLLVMFnIdCeil,
- ZigLLVMFnIdSqrt,
+ ZigLLVMFnIdFMA,
+ ZigLLVMFnIdFloatOp,
ZigLLVMFnIdBswap,
ZigLLVMFnIdBitReverse,
};
@@ -1596,7 +1609,9 @@ 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;
struct {
AddSubMul add_sub_mul;
@@ -2260,6 +2275,8 @@ enum IrInstructionId {
IrInstructionIdOverflowOp,
IrInstructionIdTestErrSrc,
IrInstructionIdTestErrGen,
+ IrInstructionIdMulAdd,
+ IrInstructionIdFloatOp,
IrInstructionIdUnwrapErrCode,
IrInstructionIdUnwrapErrPayload,
IrInstructionIdErrWrapCode,
@@ -2324,7 +2341,6 @@ enum IrInstructionId {
IrInstructionIdAddImplicitReturnType,
IrInstructionIdMergeErrRetTraces,
IrInstructionIdMarkErrRetTracePtr,
- IrInstructionIdSqrt,
IrInstructionIdErrSetCast,
IrInstructionIdToBytes,
IrInstructionIdFromBytes,
@@ -3080,6 +3096,15 @@ struct IrInstructionOverflowOp {
ZigType *result_ptr_type;
};
+struct IrInstructionMulAdd {
+ IrInstruction base;
+
+ IrInstruction *type_value;
+ IrInstruction *op1;
+ IrInstruction *op2;
+ IrInstruction *op3;
+};
+
struct IrInstructionAlignOf {
IrInstruction base;
@@ -3512,11 +3537,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 d3a9376c5d..935ced9491 100644
--- a/src/analyze.cpp
+++ b/src/analyze.cpp
@@ -5736,12 +5736,13 @@ 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:
- 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 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);
case ZigLLVMFnIdBswap:
return (uint32_t)(x.data.bswap.bit_count) * (uint32_t)3661994335;
case ZigLLVMFnIdBitReverse:
@@ -5769,10 +5770,13 @@ 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;
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) &&
diff --git a/src/codegen.cpp b/src/codegen.cpp
index 0942c671ff..7601008e16 100644
--- a/src/codegen.cpp
+++ b/src/codegen.cpp
@@ -808,32 +808,47 @@ 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) {
- assert(type_entry->id == ZigTypeIdFloat);
+static LLVMValueRef get_float_fn(CodeGen *g, ZigType *type_entry, ZigLLVMFnId fn_id, BuiltinFnId op) {
+ 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;
+ key.data.floating.op = op;
auto existing_entry = g->llvm_fn_table.maybe_get(key);
if (existing_entry)
return existing_entry->value;
const char *name;
- if (fn_id == ZigLLVMFnIdFloor) {
- name = "floor";
- } else if (fn_id == ZigLLVMFnIdCeil) {
- name = "ceil";
- } else if (fn_id == ZigLLVMFnIdSqrt) {
- name = "sqrt";
+ uint32_t num_args;
+ 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();
}
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));
@@ -2483,22 +2498,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,
@@ -2574,7 +2584,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, "");
@@ -2596,12 +2606,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);
@@ -2613,7 +2623,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();
}
@@ -5417,13 +5427,28 @@ 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, "");
}
+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, BuiltinFnIdMulAdd);
+ 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;
@@ -5770,8 +5795,10 @@ 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:
return ir_render_array_to_vector(g, executable, (IrInstructionArrayToVector *)instruction);
case IrInstructionIdVectorToArray:
@@ -7356,6 +7383,21 @@ 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);
create_builtin_fn(g, BuiltinFnIdNewStackCall, "newStackCall", SIZE_MAX);
diff --git a/src/ir.cpp b/src/ir.cpp
index 480493d2ce..887e0fc8bf 100644
--- a/src/ir.cpp
+++ b/src/ir.cpp
@@ -777,6 +777,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionTestErrGen *) {
return IrInstructionIdTestErrGen;
}
+static constexpr IrInstructionId ir_instruction_id(IrInstructionMulAdd *) {
+ return IrInstructionIdMulAdd;
+}
+
static constexpr IrInstructionId ir_instruction_id(IrInstructionUnwrapErrCode *) {
return IrInstructionIdUnwrapErrCode;
}
@@ -1037,8 +1041,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 *) {
@@ -2437,6 +2441,75 @@ 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);
+ 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;
@@ -3201,17 +3274,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)
{
@@ -4380,6 +4442,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) {
@@ -4708,6 +4797,19 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
return ir_lval_wrap(irb, scope, bin_op, lval, result_loc);
}
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);
@@ -4719,7 +4821,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, result_loc);
}
case BuiltinFnIdTruncate:
@@ -5043,6 +5145,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, result_loc);
case BuiltinFnIdShlWithOverflow:
return ir_lval_wrap(irb, scope, ir_gen_overflow_op(irb, scope, node, IrOverflowOpShl), lval, result_loc);
+ case BuiltinFnIdMulAdd:
+ return ir_lval_wrap(irb, scope, ir_gen_mul_add(irb, scope, node), lval, result_loc);
case BuiltinFnIdTypeName:
{
AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
@@ -22709,6 +22813,125 @@ static IrInstruction *ir_analyze_instruction_result_ptr(IrAnalyze *ira, IrInstru
return ir_get_ref(ira, &instruction->base, result, true, false);
}
+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, IrInstructionTestErrSrc *instruction) {
IrInstruction *base_ptr = instruction->base_ptr->child;
if (type_is_invalid(base_ptr->value.type))
@@ -24542,70 +24765,254 @@ 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))
+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);
+
+ BuiltinFnId fop = source_instr->op;
+ unsigned bits;
+
+ switch (float_type->id) {
+ case ZigTypeIdComptimeFloat:
+ bits = 128;
+ break;
+ case ZigTypeIdFloat:
+ bits = float_type->data.floating.bit_count;
+ break;
+ default:
+ zig_unreachable();
+ }
+
+ 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) {
+ 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;
- IrInstruction *op = instruction->op->child;
- if (type_is_invalid(op->value.type))
- return ira->codegen->invalid_instruction;
-
- 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)));
+ // 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 *casted_op = ir_implicit_cast(ira, op, float_type);
- if (type_is_invalid(casted_op->value.type))
+ IrInstruction *op1 = instruction->op1->child;
+ if (type_is_invalid(op1->value.type))
return ira->codegen->invalid_instruction;
- if (instr_is_comptime(casted_op)) {
- ConstExprValue *val = ir_resolve_const(ira, casted_op, UndefBad);
- if (!val)
+ 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;
-
- IrInstruction *result = ir_const(ira, &instruction->base, float_type);
- ConstExprValue *out_val = &result->value;
-
- 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();
- }
- } else {
- zig_unreachable();
}
+ 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;
}
@@ -25143,8 +25550,10 @@ static IrInstruction *ir_analyze_instruction_base(IrAnalyze *ira, IrInstruction
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:
return ir_analyze_instruction_int_to_err(ira, (IrInstructionIntToErr *)instruction);
case IrInstructionIdErrToInt:
@@ -25391,7 +25800,8 @@ bool ir_has_side_effects(IrInstruction *instruction) {
case IrInstructionIdCoroFree:
case IrInstructionIdCoroPromise:
case IrInstructionIdPromiseResultType:
- case IrInstructionIdSqrt:
+ case IrInstructionIdFloatOp:
+ case IrInstructionIdMulAdd:
case IrInstructionIdAtomicLoad:
case IrInstructionIdIntCast:
case IrInstructionIdFloatCast:
diff --git a/src/ir.hpp b/src/ir.hpp
index 4fb7552212..597624e2e6 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 9ba7527417..a973079900 100644
--- a/src/ir_print.cpp
+++ b/src/ir_print.cpp
@@ -1563,15 +1563,32 @@ 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, ")");
+}
+
+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, ")");
}
@@ -2053,8 +2070,11 @@ 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);
break;
case IrInstructionIdAtomicLoad:
ir_print_atomic_load(irp, (IrInstructionAtomicLoad *)instruction);
diff --git a/std/fmt.zig b/std/fmt.zig
index 7bf1fa3d4d..7ff6ea8081 100644
--- a/std/fmt.zig
+++ b/std/fmt.zig
@@ -10,6 +10,42 @@ 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 {
+ 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;
+ }
+}
+
+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.
@@ -20,17 +56,30 @@ 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,
+ FormatFillAndAlign,
+ 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 +88,183 @@ pub fn format(
if (start_index < i) {
try output(context, fmt[start_index..i]);
}
- start_index = i;
- state = State.OpenBrace;
- },
+ start_index = i;
+ 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 = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else 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 = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else 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 => {},
},
+ // 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) {
+ 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 +272,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 +293,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 +302,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 +312,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 +333,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 +356,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 +366,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 +382,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 +390,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 +403,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 +423,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 +437,25 @@ 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")) {
+ // 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);
}
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 +463,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 +477,75 @@ 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);
+
+ // 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);
}
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")) {
+ // 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, null, 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 +1038,7 @@ test "parseUnsigned" {
pub const parseFloat = @import("fmt/parse_float.zig").parseFloat;
-test "fmt.parseFloat" {
+test "parseFloat" {
_ = @import("fmt/parse_float.zig");
}
@@ -960,7 +1130,7 @@ test "parse unsigned comptime" {
}
}
-test "fmt.optional" {
+test "optional" {
{
const value: ?i32 = 1234;
try testFmt("optional: 1234\n", "optional: {}\n", value);
@@ -971,7 +1141,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 +1152,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 +1170,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 +1209,7 @@ test "fmt.array" {
}
}
-test "fmt.slice" {
+test "slice" {
{
const value: []const u8 = "abc";
try testFmt("slice: abc\n", "slice: {}\n", value);
@@ -1045,11 +1219,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 +1239,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 +1268,7 @@ test "fmt.struct" {
}
}
-test "fmt.enum" {
+test "enum" {
const Enum = enum {
One,
Two,
@@ -1104,229 +1278,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 +1351,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 +1379,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 +1393,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 +1423,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 +1435,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 +1449,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 +1491,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 +1518,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 +1542,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 +1583,34 @@ 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));
+}
+
+test "positional/alignment/width/precision" {
+ try testFmt("10.0", "{0d: >3.1}", f64(9.999));
+}
diff --git a/std/math/big/int.zig b/std/math/big/int.zig
index 46b1bed9a3..4ad5c92b3f 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/net.zig b/std/net.zig
index 8c4ab399b6..efcbf7000d 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,
diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig
index e88c3de7bd..d6404d43f5 100644
--- a/std/special/build_runner.zig
+++ b/std/special/build_runner.zig
@@ -167,7 +167,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(
@@ -188,7 +188,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/std/special/c.zig b/std/special/c.zig
index 456070f609..15cefbd2a0 100644
--- a/std/special/c.zig
+++ b/std/special/c.zig
@@ -254,19 +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 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/std/special/compiler_rt.zig b/std/special/compiler_rt.zig
index 46607a3adf..914f9dcb00 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,
@@ -471,6 +471,22 @@ test "usesThumb1" {
//etc.
}
+const use_thumb_1_pre_armv6 = usesThumb1PreArmv6(builtin.arch);
+
+fn usesThumb1PreArmv6(arch: builtin.Arch) bool {
+ return switch (arch) {
+ .thumb => |sub_arch| switch (sub_arch) {
+ .v5, .v5te, .v4t => true,
+ else => false,
+ },
+ .thumbeb => |sub_arch| switch (sub_arch) {
+ .v5, .v5te, .v4t => true,
+ else => false,
+ },
+ else => false,
+ };
+}
+
nakedcc fn __aeabi_memcpy() noreturn {
@setRuntimeSafety(false);
if (use_thumb_1) {
@@ -505,7 +521,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 +552,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
diff --git a/std/zig/parse.zig b/std/zig/parse.zig
index 7a8287db0c..da258c9237 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 a004089fec..f78e666779 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;
diff --git a/test/compare_output.zig b/test/compare_output.zig
index ad15ef47b3..79057f3c54 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");
diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig
index f477bb64ed..5cb04966e3 100644
--- a/test/stage1/behavior.zig
+++ b/test/stage1/behavior.zig
@@ -69,6 +69,8 @@ comptime {
_ = @import("behavior/optional.zig");
_ = @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 0000000000..de2f6815a6
--- /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);
+// }
+//}
diff --git a/test/stage1/behavior/muladd.zig b/test/stage1/behavior/muladd.zig
new file mode 100644
index 0000000000..143e6a93e4
--- /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