// Integer saturating arighmetic (with branch-free procedures on x64). // Expects signed values in two's complement. #import "Basic"; #import "Compiler"; #import "Math"; is_signed :: ($t: Type) -> bool { return (cast(*Type_Info_Integer)type_info(t)).signed; } INTEGER_ARITHMETIC_TYPES_CHECK :: #string DONE type_info_x := cast(*Type_Info)Tx; type_info_y := cast(*Type_Info)Ty; if type_info_x.type != .INTEGER || type_info_y.type != .INTEGER return false, "Non integers values passed."; tx := cast(*Type_Info_Integer)type_info_x; ty := cast(*Type_Info_Integer)type_info_y; largest_type := ifx tx.runtime_size > ty.runtime_size then Tx else ifx ty.runtime_size > tx.runtime_size then Ty else ifx tx.signed == ty.signed then Tx else void; // Only allow to add different signedness values if largest type is the signed one (as in JAI). if tx.signed == ty.signed { Tx = largest_type; Ty = largest_type; Tr = largest_type; } else if tx.signed && Tx == largest_type { Ty = largest_type; Tr = largest_type; } else if ty.signed && Ty == largest_type { Tx = largest_type; Tr = largest_type; } else return false, "Number signedness mismatch."; return true; DONE add :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } // #dump { #if USE_GENERIC || CPU != .X64 { // #if #run is_signed(Tr) { // TODO Maybe use this? #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } if (y > 0 && x > MAX - y) then return MAX, true; if (y < 0 && x < MIN - y) then return MIN, true; } else { #if Tr == u8 { MAX :: U8_MAX; } #if Tr == u16 { MAX :: U16_MAX; } #if Tr == u32 { MAX :: U32_MAX; } #if Tr == u64 { MAX :: U64_MAX; } if (x > MAX - y) then return MAX, true; } return x + y, false; } else { #import "String"; result: Tr = ---; saturated: bool = ---; S_ADD_ASM :: #string DONE #asm { // Calculate limit based on x's sign. mov limit: gpr, MAX; mov sign: gpr, x; shr.SIZE sign, BITS; add.SIZE limit, sign; // If sign is 1, then limit will overflow from MAX to MIN. mov result, x; add.SIZE result, y; seto saturated; cmovo result, limit; } DONE #if Tr == s8 #insert #run replace(replace(replace(S_ADD_ASM, ".SIZE", ".b"), "MAX", "127"), "BITS", "7"); #if Tr == s16 #insert #run replace(replace(replace(S_ADD_ASM, ".SIZE", ".w"), "MAX", "32767"), "BITS", "15"); #if Tr == s32 #insert #run replace(replace(replace(S_ADD_ASM, ".SIZE", ".d"), "MAX", "2147483647"), "BITS", "31"); #if Tr == s64 #insert #run replace(replace(replace(S_ADD_ASM, ".SIZE", ".q"), "MAX", "9223372036854775807"), "BITS", "63"); U_ADD_ASM :: #string DONE #asm { add.SIZE x, y; // Add values. mov result, -1; // Pre-set result with unsigned maximum. setc saturated; // Set saturated flag if CF. cmovnc result, x; // Move add-result to result if NOT CF. } DONE #if Tr == u8 #insert #run replace(U_ADD_ASM, ".SIZE", ".b"); #if Tr == u16 #insert #run replace(U_ADD_ASM, ".SIZE", ".w"); #if Tr == u32 #insert #run replace(U_ADD_ASM, ".SIZE", ".d"); #if Tr == u64 #insert #run replace(U_ADD_ASM, ".SIZE", ".q"); return result, saturated; } } sub :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, overflow: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } // #dump { #if USE_GENERIC || CPU != .X64 { #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } if (y < 0 && x > MAX + y) then return MAX, true; if (y > 0 && x < MIN + y) then return MIN, true; } else { if (y > x) then return 0, true; } return x - y, false; } else { #import "String"; result: Tr = ---; saturated: bool = ---; S_SUB_ASM :: #string DONE #asm { // Calculate limit based on x's sign. mov limit: gpr, MAX; mov sign: gpr, x; shr.SIZE sign, BITS; add.SIZE limit, sign; // If sign is 1, then limit will overflow from MAX to MIN. mov result, x; sub.SIZE result, y; seto saturated; cmovo result, limit; } DONE #if Tr == s8 #insert #run replace(replace(replace(S_SUB_ASM, ".SIZE", ".b"), "MAX", "127"), "BITS", "7"); #if Tr == s16 #insert #run replace(replace(replace(S_SUB_ASM, ".SIZE", ".w"), "MAX", "32767"), "BITS", "15"); #if Tr == s32 #insert #run replace(replace(replace(S_SUB_ASM, ".SIZE", ".d"), "MAX", "2147483647"), "BITS", "31"); #if Tr == s64 #insert #run replace(replace(replace(S_SUB_ASM, ".SIZE", ".q"), "MAX", "9223372036854775807"), "BITS", "63"); U_SUB_ASM :: #string DONE #asm { mov limit: gpr, 0; mov result, x; sub.SIZE result, y; setc saturated; cmovc result, limit; } DONE #if Tr == u8 #insert #run replace(U_SUB_ASM, ".SIZE", ".b"); #if Tr == u16 #insert #run replace(U_SUB_ASM, ".SIZE", ".w"); #if Tr == u32 #insert #run replace(U_SUB_ASM, ".SIZE", ".d"); #if Tr == u64 #insert #run replace(U_SUB_ASM, ".SIZE", ".q"); return result, saturated; } } mul :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, overflow: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } // #dump { #if USE_GENERIC || CPU != .X64 { // #if #run is_signed(Tr) { // TODO Maybe use this? #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } if x == 0 || y == 0 then return 0, false; if x > 0 && y > 0 && x > MAX / y then return MAX, true; if x < 0 && y < 0 && x < MAX / y then return MAX, true; if (y < 0 && x > 0 && y < MIN / x) || (x < 0 && y > 0 && x < MIN / y) then return MIN, true; } else { #if Tr == u8 { MAX :: U8_MAX; } #if Tr == u16 { MAX :: U16_MAX; } #if Tr == u32 { MAX :: U32_MAX; } #if Tr == u64 { MAX :: U64_MAX; } if x == 0 || y == 0 then return 0, false; if x > MAX / y then return MAX, true; } return x * y, false; } else { #import "String"; result: Tr = ---; saturated: bool = ---; S_MUL_ASM :: #string DONE #asm { result === a; // TODO Try changing to non-aregister to see if we're using the single-argument version of imul. // Calculate limit based on (x^y)'s sign. mov limit: gpr, MAX; mov sign: gpr, x; xor sign, y; shr.SIZE sign, BITS; add.SIZE limit, sign; // If sign is 1, then limit will overflow from MAX to MIN. mov result, x; imul.SIZE result, y; seto saturated; cmovo result, limit; } DONE #if Tr == s8 #insert #run replace(replace(replace(S_MUL_ASM, ".SIZE", ".b"), "MAX", "127"), "BITS", "7"); #if Tr == s16 #insert #run replace(replace(replace(S_MUL_ASM, ".SIZE", ".w"), "MAX", "32767"), "BITS", "15"); #if Tr == s32 #insert #run replace(replace(replace(S_MUL_ASM, ".SIZE", ".d"), "MAX", "2147483647"), "BITS", "31"); #if Tr == s64 #insert #run replace(replace(replace(S_MUL_ASM, ".SIZE", ".q"), "MAX", "9223372036854775807"), "BITS", "63"); U_MUL_ASM :: #string DONE #asm { result === a; mov result, x; mul.SIZE r_d:, result, y; // TODO Try to use same as below (remove r_d) setc saturated; sbb limit:, limit; // If CF: limit = -1 (all bits set); otherwise: limit = 0. or result, limit; } DONE U_MUL_ASM_8BITS :: #string DONE #asm { result === a; mov result, x; mul.SIZE result, y; setc saturated; sbb limit:, limit; // If CF: limit = -1 (all bits set); otherwise: limit = 0. or result, limit; } DONE #if Tr == u8 #insert #run replace(U_MUL_ASM_8BITS, ".SIZE", ".b"); #if Tr == u16 #insert #run replace(U_MUL_ASM, ".SIZE", ".w"); #if Tr == u32 #insert #run replace(U_MUL_ASM, ".SIZE", ".d"); #if Tr == u64 #insert #run replace(U_MUL_ASM, ".SIZE", ".q"); return result, saturated; } } div :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, remainder: Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } //#dump { #if USE_GENERIC || CPU != .X64 { // #if #run is_signed(Tr) { // TODO Maybe use this? #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } #if Tr == s16 { MAX :: S16_MAX; MIN :: S16_MIN; } #if Tr == s32 { MAX :: S32_MAX; MIN :: S32_MIN; } #if Tr == s64 { MAX :: S64_MAX; MIN :: S64_MIN; } if x == MIN && y == -1 then return MAX, -1, true; } result := x / y; remainder := x - (y * result); return result, remainder, false; } else { #import "String"; result: Tr = ---; remainder: Tr = ---; saturated: bool = ---; S_DIV_ASM :: #string DONE #asm { result === a; remainder === d; // Detect div(MIN/-1) and flag it on ZF. mov xT: gpr, MIN; // TODO Rename xT to x_test mov xV: gpr, x; // TODO Rename xV to x_val xor.SIZE xT, xV; mov yT: gpr, y; xor.SIZE yT, -1; or.SIZE xT, yT; mov limit: gpr, LIMIT; mov result, x; cmovz result, limit; // If ZF: limit dividend to MIN-1. mov.SIZE saturated, 0; // Clear register up to the size used on last operation "sub.SIZE"" setz saturated; SIGN_EXT remainder, result; // Prepare dividend high bits. idiv.SIZE remainder, result, y; // If saturated: remainder = 0 - 1; otherwise: remainder = x - 0. sub.SIZE remainder, saturated; } DONE S_DIV_ASM_8BITS :: #string DONE #asm { result === a; remainder === d; // Detect div(MIN/-1) and flag it on ZF. mov t_x: gpr, x; mov t_y: gpr, y; xor.SIZE t_x, MIN; xor.SIZE t_y, -1; or.SIZE t_x, t_y; mov limit: gpr, LIMIT; mov result, x; cmovz result, limit; // If ZF: limit dividend to MIN-1. cbw result; // Sign-extension. setz saturated; idiv.SIZE result, y; // Extract remainder from result's high bits. mov remainder, result; sar remainder, 8; // If saturated: remainder = 0 - 1; otherwise: remainder = x - 0. sub.SIZE remainder, saturated; } DONE #if Tr == s8 #insert #run replace(replace(replace(S_DIV_ASM_8BITS, ".SIZE", ".b"), "MIN", "-128"), "LIMIT", "-127"); #if Tr == s16 #insert #run replace(replace(replace(replace(S_DIV_ASM, ".SIZE", ".w"), "MIN", "-32768"), "LIMIT", "-32767"), "SIGN_EXT", "cwd"); #if Tr == s32 #insert #run replace(replace(replace(replace(S_DIV_ASM, ".SIZE", ".d"), "MIN", "-2147483648"), "LIMIT", "-2147483647"), "SIGN_EXT", "cdq"); #if Tr == s64 #insert #run replace(replace(replace(replace(S_DIV_ASM, ".SIZE", ".q"), "MIN", "-9223372036854775808"), "LIMIT", "-9223372036854775807"), "SIGN_EXT", "cqo"); U_DIV_ASM :: #string DONE #asm { result === a; remainder === d; mov saturated, 0; mov result, x; mov remainder, 0; // Prepare dividend high bits. div.SIZE remainder, result, y; } DONE U_DIV_ASM_8BITS :: #string DONE #asm { result === a; remainder === d; mov saturated, 0; movzxbw result, x; // Move zero-extended byte to word. div.SIZE result, y; // Extract remainder from result's high bits. mov remainder, result; sar remainder, 8; } DONE #if Tr == u8 #insert #run replace(U_DIV_ASM_8BITS, ".SIZE", ".b"); #if Tr == u16 #insert #run replace(U_DIV_ASM, ".SIZE", ".w"); #if Tr == u32 #insert #run replace(U_DIV_ASM, ".SIZE", ".d"); #if Tr == u64 #insert #run replace(U_DIV_ASM, ".SIZE", ".q"); return result, remainder, saturated; } }