#import "Basic"; #import "Compiler"; #import "Math"; // TODO Comparing implementaitons using dump #run test_math_ext(); test_math_ext :: () { set_build_options_dc(.{do_output=false}); // main :: () { write_strings("=====================\n", "--- Test Math_Ext ---\n"); // Different signals: only works if signaled variable is higher. /* #run cena(); cena :: () { a: s64 = -232; b: u32 = 4; c := a+b; print("\n\n--- --- ---\ntttt : % : % + % = %\n--- --- ---\n\n", type_of(c), a, b, c); } */ test_op :: (op: string, x: $Tx, y: $Ty, r: $Tr, t: Type, o: bool) -> errors_found: int #expand { #import "String"; #insert #run replace("tr, to := OP(cast(Tx)x, cast(Ty)y);", "OP", op); print("%_%(%, %) = % : %\n", op, t, x, y, r, o); error := 0; if r != tr { error += 1; print(" > incorrect result value: got % expected %\n", tr, r); }; if t != type_of(tr) { error += 1; print(" > incorrect result type: got % expected %\n", type_of(tr), t); }; if o != to { error += 1; print(" > incorrect overflow flag: got % expected %\n", to, o); }; return error; } errors := 0; // Test signed add. errors += test_op("add", cast( s8) S8_MAX, cast( s8)1, S8_MAX, s8, true); errors += test_op("add", cast(s16)S16_MAX, cast( u8)1, S16_MAX, s16, true); errors += test_op("add", cast(s32)S32_MAX, cast(s32)1, S32_MAX, s32, true); errors += test_op("add", cast(s64)S64_MAX, cast(u32)1, S64_MAX, s64, true); errors += test_op("add", cast( s8) S8_MAX, cast( s8) S8_MIN, -1, s8, false); errors += test_op("add", cast(s16)S16_MAX, cast(s16)S16_MIN, -1, s16, false); errors += test_op("add", cast(s32)S32_MAX, cast(s32)S32_MIN, -1, s32, false); errors += test_op("add", cast(s64)S64_MAX, cast(s64)S64_MIN, -1, s64, false); // Test unsigned add. errors += test_op("add", cast( u8) U8_MAX, cast( u8)1, U8_MAX, u8, true); errors += test_op("add", cast(u16)U16_MAX, cast(u16)1, U16_MAX, u16, true); errors += test_op("add", cast(u32)U32_MAX, cast(u32)1, U32_MAX, u32, true); errors += test_op("add", cast(u64)U64_MAX, cast(u64)1, U64_MAX, u64, true); errors += test_op("add", cast( u8) U8_MAX, cast( u8)0, U8_MAX, u8, false); errors += test_op("add", cast(u16)U16_MAX, cast(u16)0, U16_MAX, u16, false); errors += test_op("add", cast(u32)U32_MAX, cast(u32)0, U32_MAX, u32, false); errors += test_op("add", cast(u64)U64_MAX, cast(u64)0, U64_MAX, u64, false); // Test signed sub. errors += test_op("sub", cast( s8) S8_MIN, cast( s8)1, S8_MIN, s8, true); errors += test_op("sub", cast(s16)S16_MIN, cast( u8)1, S16_MIN, s16, true); errors += test_op("sub", cast(s32)S32_MIN, cast(s32)1, S32_MIN, s32, true); errors += test_op("sub", cast(s64)S64_MIN, cast(u32)1, S64_MIN, s64, true); errors += test_op("sub", cast( s8)-1, cast( s8) S8_MAX, S8_MIN, s8, false); errors += test_op("sub", cast(s16)-1, cast(s16)S16_MAX, S16_MIN, s16, false); errors += test_op("sub", cast(s32)-1, cast(s32)S32_MAX, S32_MIN, s32, false); errors += test_op("sub", cast(s64)-1, cast(s64)S64_MAX, S64_MIN, s64, false); // Test unsigned sub. errors += test_op("sub", cast( u8)1, cast( u8) U8_MAX, 0, u8, true); errors += test_op("sub", cast(u16)1, cast(u16)U16_MAX, 0, u16, true); errors += test_op("sub", cast(u32)1, cast(u32)U32_MAX, 0, u32, true); errors += test_op("sub", cast(u64)1, cast(u64)U64_MAX, 0, u64, true); errors += test_op("sub", cast( u8) U8_MAX, cast( u8)0, U8_MAX, u8, false); errors += test_op("sub", cast(u16)U16_MAX, cast(u16)0, U16_MAX, u16, false); errors += test_op("sub", cast(u32)U32_MAX, cast(u32)0, U32_MAX, u32, false); errors += test_op("sub", cast(u64)U64_MAX, cast(u64)0, U64_MAX, u64, false); // errors += test_add(cast(s32)66, cast(s64)-2, 64, s64, false); // errors += test_add(cast(u32)66, cast(s64)4, 70, s64, false); // errors += test_add(cast(s32)S32_MAX, cast(s64)1, 2147483648, s64, false); // errors += test_add(cast(s32)S32_MAX, cast(s32)1, S32_MAX, s32, true); // errors += test_add(cast(s64)S64_MAX, cast(s64)0, S64_MAX, s64, false); // errors += test_add(cast(s64)9223372036854775806, cast(s64)1, S64_MAX, s64, false); // errors += test_add(cast(s64)9223372036854775806, cast(s64)2, S64_MAX, s64, true); // errors += test_add(cast(u8)7, cast(u8)1, 8, u8, false); // errors += test_add(cast(u8)U8_MAX, cast(u8)1, U8_MAX, u8, true); // errors += test_add(cast(u16)10, cast(u8)3, 13, u16, false); // errors += test_add(cast(u8)1, cast(u16)U16_MAX, U16_MAX, u16, true); if errors > 0 print("# Found % %!\n", errors, ifx errors == 1 then "error" else "errors"); else print(" No errors found.\n"); /* PERFORMANCE TEST #import "Random"; best_generic: float; best_asm: float; for 0..100 { size, time_generic, time_asm := performance_test(); perf_generic := cast(float)size/cast(float)to_microseconds(time_generic); perf_asm := cast(float)size/cast(float)to_microseconds(time_asm); best_generic = max(best_generic, perf_generic); best_asm = max(best_asm, perf_asm); } print("method : ops/usec\ngeneric : %\nasm : %\n", best_generic, best_asm); performance_test :: () -> sum_size: s64, time_generic: Apollo_Time, time_asm: Apollo_Time { SUM_SIZE := 2000000; numbers: [..] s64; array_reserve(*numbers, SUM_SIZE); for 0..SUM_SIZE-1 { array_add(*numbers, cast(s64)random_get()); } sum := 0; start := current_time_monotonic(); for numbers sum = add_bad(sum, it); time := current_time_monotonic() - start; sum_asm := 0; start_asm := current_time_monotonic(); for numbers sum_asm = add(sum_asm, it); time_asm := current_time_monotonic() - start_asm; assert(sum == sum_asm); return SUM_SIZE, time, time_asm; } */ } 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) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } // #dump { #if 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; return x + y, false; } 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; 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 { mov limit: gpr, MAX; mov result, x; add.SIZE result, y; setc saturated; cmovc result, limit; } DONE #if Tr == u8 #insert #run replace(replace(U_ADD_ASM, ".SIZE", ".b"), "MAX", "255"); #if Tr == u16 #insert #run replace(replace(U_ADD_ASM, ".SIZE", ".w"), "MAX", "65535"); #if Tr == u32 #insert #run replace(replace(U_ADD_ASM, ".SIZE", ".d"), "MAX", "4294967295"); #if Tr == u64 #insert #run replace(replace(U_ADD_ASM, ".SIZE", ".q"), "MAX", "18446744073709551615"); return result, saturated; } } sub :: (x: $Tx, y: $Ty) -> result: $Tr, overflow: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } //#dump { #if 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; return x - y, false; } 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; 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; } }