3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Scalable Matrix Extension (SME)Advent Calendar 2024

Day 19

SME日記その18 SMEが使えるかどうかをElixirから判定する

Last updated at Posted at 2025-01-05

Elixirにおける,SMEが使えるかどうかの判定ロジックを考えてみました.

SMEシリーズ

ElixirでのSMEが使えるかどうかの判定ロジック

  1. macOSでなければfalseを返して終了
  2. sysctl hw.optional.arm を実行し,出力にhw.optional.arm.FEAT_SME: 1hw.optional.arm.FEAT_SME2: 1が含まれていれば,2の結果はtrue,そうでなければ2の結果はfalse
  3. 次のCプログラムを/usr/bin/clang -O2 -march=armv9-a+sme test_sme.c -o test_smeとしてコンパイルし,成功すれば3の結果はtrue,そうでなければ3の結果はfalse
  4. 2と3の結果が共にtrueであれば,SMEを使えるのでtrueを返す.そうでなければfalseを返す.

1・2と,3 は並列に実行しても良い.

#include <arm_sme.h>

__arm_locally_streaming
__arm_new("za")
void test_arm_new(void)
{
}

int main(int argc, char *argv[])
{
  test_arm_new();
}

macOSかどうかの判定

case :os.type() do
  {:unix, :darwin} -> true
  _ -> false
end

sysctl hw.optional.arm の実行判定

case :os.type() do
  {:unix, :darwin} ->
    case System.find_executable("sysctl") do
      nil -> false

      executable -> 
        case System.cmd(executable, ["hw.optional.arm"], into: []) do
          {result, 0} ->
            result 
            |> Enum.map(&String.trim/1)
            |> Enum.filter(&String.match?(&1, ~r/FEAT\_SME/))
            |> Enum.map(&String.split(&1, " "))
            |> Enum.filter(fn [_, v] -> v == "1" end)
            |> Enum.count()
            |> then(& &1 != 0)

          {_, _} -> false
        end
    end
  
  _ -> false
end

Cプログラムのコンパイル

case System.find_executable("/usr/bin/clang") do
  nil -> false

  executable -> 
    base_root = :crypto.strong_rand_bytes(10) |> Base.encode32(case: :lower)
    base = base_root <> ".c"

    File.write(
      Path.join("/tmp", base), 
      """
      #include <arm_sme.h>

      __arm_locally_streaming
      __arm_new("za")
      void test_arm_new(void) {}
      int main(int argc, char *argv[])
      {
        test_arm_new();
      }
      """
    )
    |> case do
      :ok -> 
        System.cmd(
          executable, 
          [
            "-O2", 
            "-Werror",
            "-Wall",
            "-march=armv9-a+sme", 
            base,
            "-o",
            base_root
          ],
          into: [],
          cd: "/tmp",
          stderr_to_stdout: true
        )
        |> case do
          {_, 0} -> true
          _ -> false
        end
      
      {:error, _reason} -> false
    end
end

全体

defmodule SME do
  def available?() do
    {:ok, pid} = Task.Supervisor.start_link()

    task1 = Task.Supervisor.async(pid, fn -> runnable?() end)
    task2 = Task.Supervisor.async(pid, fn -> compilable?() end)
    
    Task.await(task1) and Task.await(task2)
  end

  def runnable?() do
    case :os.type() do
      {:unix, :darwin} -> 
        case execute("sysctl", ["hw.optional.arm"]) do
          {result, 0} -> 
            result
            |> String.split("\n")
            |> Enum.map(&String.trim/1)
            |> Enum.filter(&String.match?(&1, ~r/FEAT\_SME/))
            |> Enum.map(&String.split(&1, " "))
            |> Enum.filter(fn [_, v] -> v == "1" end)
            |> Enum.count()
            |> then(& &1 != 0)

          _ -> false
        end

      _ -> false
    end
  end

  def compilable?() do
    base_root = :crypto.strong_rand_bytes(10) |> Base.encode32(case: :lower)
    base = base_root <> ".c"

    File.write(
      Path.join("/tmp", base), 
      """
      #include <arm_sme.h>

      __arm_locally_streaming
      __arm_new("za")
      void test_arm_new(void) {}
      int main(int argc, char *argv[])
      {
        test_arm_new();
      }
      """
    )
    |> case do
      :ok -> 
        execute("/usr/bin/clang", 
          [
            "-O2", 
            "-Werror",
            "-Wall",
            "-march=armv9-a+sme", 
            base,
            "-o",
            base_root
          ],
          cd: "/tmp",
          stderr_to_stdout: true
        )
        |> case do
          {_, 0} -> true
          _ -> false
        end
      
      {:error, _reason} -> false
    end
  end

  defp execute(executable, options, opts \\ []) do
    System.find_executable(executable)
    |> case do
      nil -> 
        executable
        |> Path.basename()
        |> System.find_executable()
        |> case do
          nil -> false
          executable -> System.cmd(executable, options, opts)
        end

      executable -> System.cmd(executable, options, opts)
    end
  end
end

P.S. このプログラムを踏まえて,Autoconfex を作成しました.

Mix.install([:autoconfex])

defmodule SME do
  def available?() do
    {:ok, pid} = Task.Supervisor.start_link()

    task1 = Task.Supervisor.async(pid, fn -> runnable?() end)
    task2 = Task.Supervisor.async(pid, fn -> compilable?() end)
    
    Task.await(task1) and Task.await(task2)
  end

  def runnable?() do
    case :os.type() do
      {:unix, :darwin} -> 
        case execute("sysctl", ["hw.optional.arm"]) do
          {result, 0} -> 
            result
            |> String.split("\n")
            |> Enum.map(&String.trim/1)
            |> Enum.filter(&String.match?(&1, ~r/FEAT\_SME/))
            |> Enum.map(&String.split(&1, " "))
            |> Enum.filter(fn [_, v] -> v == "1" end)
            |> Enum.count()
            |> then(& &1 != 0)

          _ -> false
        end

      _ -> false
    end
  end

  def compilable?() do
    Autoconfex.compilable_by_cc?(
      "/usr/bin/clang", 
      """
      #include <arm_sme.h>

      __arm_locally_streaming
      __arm_new("za")
      void test_arm_new(void) {}
      int main(int argc, char *argv[])
      {
        test_arm_new();
      }
      """,
      ["-O2", "-march=armv9-a+sme"]
    )
  end

  defp execute(executable, options, opts \\ []) do
    System.find_executable(executable)
    |> case do
      nil -> 
        executable
        |> Path.basename()
        |> System.find_executable()
        |> case do
          nil -> false
          executable -> System.cmd(executable, options, opts)
        end

      executable -> System.cmd(executable, options, opts)
    end
  end
end
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?