Commit 6e6ae8886e

Cody Tapscott <topolarity@tapscott.me>
2022-10-11 20:04:29
stage1: Add softfloat support for `@reduce`
1 parent f0d12dd
Changed files (3)
src/stage1/analyze.cpp
@@ -6358,9 +6358,11 @@ void init_const_float(ZigValue *const_val, ZigType *type, double value) {
                 const_val->data.x_f64 = value;
                 break;
             case 80:
+                zig_double_to_extF80M(value, &const_val->data.x_f80);
+                break;
             case 128:
-                // if we need this, we should add a function that accepts a float128_t param
-                zig_unreachable();
+                zig_double_to_f128M(value, &const_val->data.x_f128);
+                break;
             default:
                 zig_unreachable();
         }
src/stage1/codegen.cpp
@@ -6481,6 +6481,55 @@ static LLVMValueRef ir_render_cmpxchg(CodeGen *g, Stage1Air *executable, Stage1A
     return result_loc;
 }
 
+static LLVMValueRef ir_render_reduced_call(CodeGen *g, LLVMValueRef llvm_fn, LLVMValueRef operand_vector, size_t vector_len, LLVMValueRef accum_init, ZigType *accum_ty) {
+    LLVMTypeRef llvm_usize_ty = g->builtin_types.entry_usize->llvm_type;
+    LLVMValueRef llvm_vector_len = LLVMConstInt(llvm_usize_ty, vector_len, false);
+    LLVMTypeRef llvm_result_ty = LLVMTypeOf(accum_init);
+
+    // Allocate and initialize our mutable variables
+    LLVMValueRef i_ptr = build_alloca(g, g->builtin_types.entry_usize, "i", 0);
+    LLVMBuildStore(g->builder, LLVMConstInt(llvm_usize_ty, 0, false), i_ptr);
+    LLVMValueRef accum_ptr = build_alloca(g, accum_ty, "accum", 0);
+    LLVMBuildStore(g->builder, accum_init, accum_ptr);
+
+    // Setup the loop
+    LLVMBasicBlockRef loop = LLVMAppendBasicBlock(g->cur_fn_val, "ReduceLoop");
+    LLVMBasicBlockRef loop_exit = LLVMAppendBasicBlock(g->cur_fn_val, "AfterReduce");
+    LLVMBuildBr(g->builder, loop);
+    {
+        LLVMPositionBuilderAtEnd(g->builder, loop);
+
+        // while (i < vec.len)
+        LLVMValueRef i = LLVMBuildLoad2(g->builder, llvm_usize_ty, i_ptr, "");
+        LLVMValueRef cond = LLVMBuildICmp(g->builder, LLVMIntULT, i, llvm_vector_len, "");
+        LLVMBasicBlockRef loop_then = LLVMAppendBasicBlock(g->cur_fn_val, "ReduceLoopThen");
+
+        LLVMBuildCondBr(g->builder, cond, loop_then, loop_exit);
+
+        {
+            LLVMPositionBuilderAtEnd(g->builder, loop_then);
+
+            // accum = f(accum, vec[i]);
+            LLVMValueRef accum = LLVMBuildLoad2(g->builder, llvm_result_ty, accum_ptr, "");
+            LLVMValueRef element = LLVMBuildExtractElement(g->builder, operand_vector, i, "");
+            LLVMValueRef params[] {
+                accum,
+                element
+            };
+            LLVMValueRef new_accum = LLVMBuildCall2(g->builder, LLVMGlobalGetValueType(llvm_fn), llvm_fn, params, 2, "");
+            LLVMBuildStore(g->builder, new_accum, accum_ptr);
+
+            // i += 1
+            LLVMValueRef new_i = LLVMBuildAdd(g->builder, i, LLVMConstInt(llvm_usize_ty, 1, false), "");
+            LLVMBuildStore(g->builder, new_i, i_ptr);
+            LLVMBuildBr(g->builder, loop);
+        }
+    }
+
+    LLVMPositionBuilderAtEnd(g->builder, loop_exit);
+    return LLVMBuildLoad2(g->builder, llvm_result_ty, accum_ptr, "");
+}
+
 static LLVMValueRef ir_render_reduce(CodeGen *g, Stage1Air *executable, Stage1AirInstReduce *instruction) {
     LLVMValueRef value = ir_llvm_value(g, instruction->value);
 
@@ -6488,61 +6537,100 @@ static LLVMValueRef ir_render_reduce(CodeGen *g, Stage1Air *executable, Stage1Ai
     assert(value_type->id == ZigTypeIdVector);
     ZigType *scalar_type = value_type->data.vector.elem_type;
 
+    bool float_intrinsics_allowed = true;
+    const char *compiler_rt_type_abbrev = nullptr;
+    const char *math_float_prefix = nullptr;
+    const char *math_float_suffix = nullptr;
+    if ((scalar_type == g->builtin_types.entry_f80 && !target_has_f80(g->zig_target)) ||
+        (scalar_type == g->builtin_types.entry_f128 && !target_long_double_is_f128(g->zig_target)) ||
+        (scalar_type == g->builtin_types.entry_f16 && !target_is_arm(g->zig_target))) {
+        float_intrinsics_allowed = false;
+        compiler_rt_type_abbrev = get_compiler_rt_type_abbrev(scalar_type);
+        math_float_prefix = libc_float_prefix(g, scalar_type);
+        math_float_suffix = libc_float_suffix(g, scalar_type);
+    }
+
     ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &instruction->base));
 
-    LLVMValueRef result_val;
+    char fn_name[64];
+    ZigValue *init_value = nullptr;
     switch (instruction->op) {
         case ReduceOp_and:
             assert(scalar_type->id == ZigTypeIdInt || scalar_type->id == ZigTypeIdBool);
-            result_val = ZigLLVMBuildAndReduce(g->builder, value);
+            return ZigLLVMBuildAndReduce(g->builder, value);
             break;
         case ReduceOp_or:
             assert(scalar_type->id == ZigTypeIdInt || scalar_type->id == ZigTypeIdBool);
-            result_val = ZigLLVMBuildOrReduce(g->builder, value);
+            return ZigLLVMBuildOrReduce(g->builder, value);
             break;
         case ReduceOp_xor:
             assert(scalar_type->id == ZigTypeIdInt || scalar_type->id == ZigTypeIdBool);
-            result_val = ZigLLVMBuildXorReduce(g->builder, value);
+            return ZigLLVMBuildXorReduce(g->builder, value);
             break;
         case ReduceOp_min: {
             if (scalar_type->id == ZigTypeIdInt) {
                 const bool is_signed = scalar_type->data.integral.is_signed;
-                result_val = ZigLLVMBuildIntMinReduce(g->builder, value, is_signed);
+                return ZigLLVMBuildIntMinReduce(g->builder, value, is_signed);
             } else if (scalar_type->id == ZigTypeIdFloat) {
-                result_val = ZigLLVMBuildFPMinReduce(g->builder, value);
+                if (float_intrinsics_allowed) {
+                    return ZigLLVMBuildFPMinReduce(g->builder, value);
+                } else {
+                    snprintf(fn_name, sizeof(fn_name), "%sfmin%s", math_float_prefix, math_float_suffix);
+                    init_value = create_const_float(g, scalar_type, NAN);
+                }
             } else zig_unreachable();
         } break;
         case ReduceOp_max: {
             if (scalar_type->id == ZigTypeIdInt) {
                 const bool is_signed = scalar_type->data.integral.is_signed;
-                result_val = ZigLLVMBuildIntMaxReduce(g->builder, value, is_signed);
+                return ZigLLVMBuildIntMaxReduce(g->builder, value, is_signed);
             } else if (scalar_type->id == ZigTypeIdFloat) {
-                result_val = ZigLLVMBuildFPMaxReduce(g->builder, value);
+                if (float_intrinsics_allowed) {
+                    return ZigLLVMBuildFPMaxReduce(g->builder, value);
+                } else {
+                    snprintf(fn_name, sizeof(fn_name), "%sfmax%s", math_float_prefix, math_float_suffix);
+                    init_value = create_const_float(g, scalar_type, NAN);
+                }
             } else zig_unreachable();
         } break;
         case ReduceOp_add: {
             if (scalar_type->id == ZigTypeIdInt) {
-                result_val = ZigLLVMBuildAddReduce(g->builder, value);
+                return ZigLLVMBuildAddReduce(g->builder, value);
             } else if (scalar_type->id == ZigTypeIdFloat) {
-                LLVMValueRef neutral_value = LLVMConstReal(
-                        get_llvm_type(g, scalar_type), -0.0);
-                result_val = ZigLLVMBuildFPAddReduce(g->builder, neutral_value, value);
+                if (float_intrinsics_allowed) {
+                    LLVMValueRef neutral_value = LLVMConstReal(
+                            get_llvm_type(g, scalar_type), -0.0);
+                    return ZigLLVMBuildFPAddReduce(g->builder, neutral_value, value);
+                } else {
+                    snprintf(fn_name, sizeof(fn_name), "__add%sf3", compiler_rt_type_abbrev);
+                    init_value = create_const_float(g, scalar_type, 0.0);
+                }
             } else zig_unreachable();
         } break;
         case ReduceOp_mul: {
             if (scalar_type->id == ZigTypeIdInt) {
-                result_val = ZigLLVMBuildMulReduce(g->builder, value);
+                return ZigLLVMBuildMulReduce(g->builder, value);
             } else if (scalar_type->id == ZigTypeIdFloat) {
-                LLVMValueRef neutral_value = LLVMConstReal(
-                        get_llvm_type(g, scalar_type), 1.0);
-                result_val = ZigLLVMBuildFPMulReduce(g->builder, neutral_value, value);
+                if (float_intrinsics_allowed) {
+                    LLVMValueRef neutral_value = LLVMConstReal(
+                            get_llvm_type(g, scalar_type), 1.0);
+                    return ZigLLVMBuildFPMulReduce(g->builder, neutral_value, value);
+                } else {
+                    snprintf(fn_name, sizeof(fn_name), "__mul%sf3", compiler_rt_type_abbrev);
+                    init_value = create_const_float(g, scalar_type, 1.0);
+                }
             } else zig_unreachable();
         } break;
         default:
             zig_unreachable();
     }
 
-    return result_val;
+
+    LLVMValueRef llvm_init_value = gen_const_val(g, init_value, "");
+    uint32_t vector_len = value_type->data.vector.len;
+    LLVMTypeRef llvm_scalar_type = get_llvm_type(g, scalar_type);
+    const LLVMValueRef llvm_fn = get_soft_float_fn(g, fn_name, 2, llvm_scalar_type, llvm_scalar_type);
+    return ir_render_reduced_call(g, llvm_fn, value, vector_len, llvm_init_value, scalar_type);
 }
 
 static LLVMValueRef ir_render_fence(CodeGen *g, Stage1Air *executable, Stage1AirInstFence *instruction) {
src/stage1/softfloat.hpp
@@ -21,6 +21,20 @@ static inline float16_t zig_double_to_f16(double x) {
     return f64_to_f16(y);
 }
 
+static inline void zig_double_to_extF80M(double x, extFloat80_t *result) {
+    float64_t y;
+    static_assert(sizeof(x) == sizeof(y), "");
+    memcpy(&y, &x, sizeof(x));
+    f64_to_extF80M(y, result);
+}
+
+static inline void zig_double_to_f128M(double x, float128_t *result) {
+    float64_t y;
+    static_assert(sizeof(x) == sizeof(y), "");
+    memcpy(&y, &x, sizeof(x));
+    f64_to_f128M(y, result);
+}
+
 
 // Return value is safe to coerce to float even when |x| is NaN or Infinity.
 static inline double zig_f16_to_double(float16_t x) {