// All of the architecture-specific extensions we care to look for.
// Note: Checking for impossible extensions will always turn into a no-op (ex. NEON support on x64).
ISA_Extension :: enum {
   // x64
   sse2;
   sse3;
   sse41;
   sse42;
   ssse3;

   avx;
   avx2;
   avx512cd;
   avx512f;
   avx512vnni;

   // arm64
   neon;

   // riscv
   rv64ip;
}

// Returns true if the extension is supported by the current architecture.
arch_supports :: inline ($ext: ISA_Extension) -> bool {
   lazy_init_cpu_info();
   return arch_has_extension(ext);
}

// Returns true if any of the extensions are supported by the current architecture.
arch_supports_any :: inline ($extensions: ..ISA_Extension) -> bool {
   lazy_init_cpu_info();

   res: bool;
   #insert -> string {
      b: basic.String_Builder;
      for extensions {
         basic.print_to_builder(*b, "res ||= arch_has_extension(.%);\n", it);
      }
      return basic.builder_to_string(*b);
   }
   return res;
}

// Returns true if all of the extensions are supported by the current architecture.
arch_supports_all :: inline ($extensions: ..ISA_Extension) -> bool {
   lazy_init_cpu_info();

   res: bool;
   #insert -> string {
      b: basic.String_Builder;
      for extensions {
         basic.print_to_builder(*b, "res &&= arch_has_extension(.%);\n", it);
      }
      return basic.builder_to_string(*b);
   }
   return res;
}


#scope_file;

// @note(judah): this assumes lazy_init_cpu_info was already called
arch_has_extension :: inline ($ext: ISA_Extension) -> bool {
   #if CPU == {
      case .X64; #if ext == {
         case .sse2;       return x64.check_feature(info.feature_leaves, .SSE2);
         case .sse3;       return x64.check_feature(info.feature_leaves, .SSE3);
         case .sse41;      return x64.check_feature(info.feature_leaves, .SSE4_1);
         case .sse42;      return x64.check_feature(info.feature_leaves, .SSE4_2);
         case .ssse3;      return x64.check_feature(info.feature_leaves, .SSSE3);
         case .avx;        return x64.check_feature(info.feature_leaves, .AVX);
         case .avx2;       return x64.check_feature(info.feature_leaves, .AVX2);
         case .avx512cd;   return x64.check_feature(info.feature_leaves, .AVX512CD);
         case .avx512f;    return x64.check_feature(info.feature_leaves, .AVX512F);
         case .avx512vnni; return x64.check_feature(info.feature_leaves, .AVX512_VNNI);
      }
      case .ARM64; #if ext == {
         case .neon;
            return true; // @note(judah): it's safe to assume neon support if we're on arm (for now)
      }
   }

   return false;
}

info_initialized := false;

lazy_init_cpu_info :: () #expand {
   if info_initialized return;
   info_initialized = true;

   #if CPU == .X64 {
      info = x64.get_cpu_info();
   }
}

#if CPU == {
   case .X64;
      info: x64.Cpu_X86;
      x64 :: #import "Machine_X64"; // @future
}


basic :: #import "Basic"; // @future
