Browse Source

Merge pull request #3934 from laytan/fix-saturating-intrinsics

fix `add_sat` and `sub_sat` intrinsics
Laytan 1 year ago
parent
commit
cb16d2ddaf
3 changed files with 81 additions and 10 deletions
  1. 6 3
      base/intrinsics/intrinsics.odin
  2. 42 1
      src/check_builtin.cpp
  3. 33 6
      src/llvm_backend_proc.cpp

+ 6 - 3
base/intrinsics/intrinsics.odin

@@ -38,9 +38,12 @@ count_leading_zeros  :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim
 reverse_bits         :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
 byte_swap            :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
 
-overflow_add :: proc(lhs, rhs: $T) -> (T, bool) ---
-overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) ---
-overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) ---
+overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
+overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
+overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
+
+add_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
+sub_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
 
 sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) ---
 

+ 42 - 1
src/check_builtin.cpp

@@ -4261,6 +4261,47 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 	case BuiltinProc_overflow_add:
 	case BuiltinProc_overflow_sub:
 	case BuiltinProc_overflow_mul:
+		{
+			Operand x = {};
+			Operand y = {};
+			check_expr(c, &x, ce->args[0]);
+			check_expr(c, &y, ce->args[1]);
+			if (x.mode == Addressing_Invalid) {
+				return false;
+			}
+			if (y.mode == Addressing_Invalid) {
+				return false;
+			}
+			convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false;
+			convert_to_typed(c, &x, y.type);
+			if (is_type_untyped(x.type)) {
+				gbString xts = type_to_string(x.type);
+				error(x.expr, "Expected a typed integer for '%.*s', got %s", LIT(builtin_name), xts);
+				gb_string_free(xts);
+				return false;
+			}
+			if (!is_type_integer(x.type)) {
+				gbString xts = type_to_string(x.type);
+				error(x.expr, "Expected an integer for '%.*s', got %s", LIT(builtin_name), xts);
+				gb_string_free(xts);
+				return false;
+			}
+			Type *ct = core_type(x.type);
+			if (is_type_different_to_arch_endianness(ct)) {
+				GB_ASSERT(ct->kind == Type_Basic);
+				if (ct->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) {
+					gbString xts = type_to_string(x.type);
+					error(x.expr, "Expected an integer which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts);
+					gb_string_free(xts);
+					return false;
+				}
+			}
+
+			operand->mode = Addressing_Value;
+			operand->type = make_optional_ok_type(default_type(x.type));
+		}
+		break;
+
 	case BuiltinProc_add_sat:
 	case BuiltinProc_sub_sat:
 		{
@@ -4300,7 +4341,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 			}
 
 			operand->mode = Addressing_Value;
-			operand->type = make_optional_ok_type(default_type(x.type));
+			operand->type = default_type(x.type);
 		}
 		break;
 

+ 33 - 6
src/llvm_backend_proc.cpp

@@ -2236,8 +2236,6 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 	case BuiltinProc_overflow_add:
 	case BuiltinProc_overflow_sub:
 	case BuiltinProc_overflow_mul:
-	case BuiltinProc_add_sat:
-	case BuiltinProc_sub_sat:
 		{
 			Type *main_type = tv.type;
 			Type *type = main_type;
@@ -2256,16 +2254,12 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 				case BuiltinProc_overflow_add: name = "llvm.uadd.with.overflow"; break;
 				case BuiltinProc_overflow_sub: name = "llvm.usub.with.overflow"; break;
 				case BuiltinProc_overflow_mul: name = "llvm.umul.with.overflow"; break;
-				case BuiltinProc_add_sat:      name = "llvm.uadd.sat"; break;
-				case BuiltinProc_sub_sat:      name = "llvm.usub.sat"; break;
 				}
 			} else {
 				switch (id) {
 				case BuiltinProc_overflow_add: name = "llvm.sadd.with.overflow"; break;
 				case BuiltinProc_overflow_sub: name = "llvm.ssub.with.overflow"; break;
 				case BuiltinProc_overflow_mul: name = "llvm.smul.with.overflow"; break;
-				case BuiltinProc_add_sat:      name = "llvm.sadd.sat"; break;
-				case BuiltinProc_sub_sat:      name = "llvm.ssub.sat"; break;
 				}
 			}
 			LLVMTypeRef types[1] = {lb_type(p->module, type)};
@@ -2291,6 +2285,39 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 			return res;
 		}
 
+	case BuiltinProc_add_sat:
+	case BuiltinProc_sub_sat:
+		{
+			Type *main_type = tv.type;
+			Type *type = main_type;
+
+			lbValue x = lb_build_expr(p, ce->args[0]);
+			lbValue y = lb_build_expr(p, ce->args[1]);
+			x = lb_emit_conv(p, x, type);
+			y = lb_emit_conv(p, y, type);
+
+			char const *name = nullptr;
+			if (is_type_unsigned(type)) {
+				switch (id) {
+				case BuiltinProc_add_sat: name = "llvm.uadd.sat"; break;
+				case BuiltinProc_sub_sat: name = "llvm.usub.sat"; break;
+				}
+			} else {
+				switch (id) {
+				case BuiltinProc_add_sat: name = "llvm.sadd.sat"; break;
+				case BuiltinProc_sub_sat: name = "llvm.ssub.sat"; break;
+				}
+			}
+			LLVMTypeRef types[1] = {lb_type(p->module, type)};
+
+			LLVMValueRef args[2] = { x.value, y.value };
+
+			lbValue res = {};
+			res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types));
+			res.type = type;
+			return res;
+		}
+
 	case BuiltinProc_sqrt:
 		{
 			Type *type = tv.type;