Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the main script POSIX-compatible #14

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ license.

## Authors

* Stefan Tauner (POSIX version)
* Henrik Bengtsson (expanded on [Gilles' implementation])
* StackExchange user [Gilles]
* StackExchange user [gioele]
Expand Down
93 changes: 47 additions & 46 deletions tests/x86-64-level.sh
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,12 @@ for truth in $(seq 0 "$((${#cpu_flags[@]} - 1))"); do
>&2 echo "ERROR: Exit code is non-zero: ${exit_code}"
nerrors=$((nerrors + 1))
fi

if [[ "${level}" -ne "${truth}" ]]; then
>&2 echo "ERROR: Unexpected level: ${level} != ${truth}"
nerrors=$((nerrors + 1))
fi
fi

## Outputs nothing to stderr
stderr=$( { >&2 x86-64-level <<< "flags: ${flags}" > /dev/null; } 2>&1 )
if [[ -n ${stderr} ]]; then
Expand All @@ -196,22 +196,22 @@ for level in -1 0 5 100; do
>&2 echo "ERROR: Exit code should be non-zero: ${exit_code}"
nerrors=$((nerrors + 1))
fi

if [[ -z ${stderr} ]]; then
>&2 echo "ERROR: No error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then
>&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'"
nerrors=$((nerrors + 1))
fi

if ! grep -q -E "^ERROR: .*out of range.* ${level}" <<< "${stderr}"; then
>&2 echo "ERROR: Unexpected error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

## Outputs nothing to stdout
stdout=$(x86-64-level --assert="${level}" 2> /dev/null)
if [[ -n ${stdout} ]]; then
Expand All @@ -230,22 +230,22 @@ for level in 1.2 world; do
>&2 echo "ERROR: Exit code should be non-zero: ${exit_code}"
nerrors=$((nerrors + 1))
fi

if [[ -z ${stderr} ]]; then
>&2 echo "ERROR: No error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then
>&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

if ! grep -q -E "^ERROR: .*does not specify an integer.* ${level}" <<< "${stderr}"; then
>&2 echo "ERROR: Unexpected error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

## Outputs nothing to stdout
stdout=$(x86-64-level --assert="${level}" 2> /dev/null)
if [[ -n ${stdout} ]]; then
Expand All @@ -268,17 +268,17 @@ fi
if [[ -z ${stderr} ]]; then
>&2 echo "ERROR: No error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then
>&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

if ! grep -q -E "^ERROR: .*must not be empty" <<< "${stderr}"; then
>&2 echo "ERROR: Unexpected error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

## Outputs nothing to stdout
stdout=$(x86-64-level --assert="" 2> /dev/null)
Expand All @@ -299,17 +299,17 @@ fi
if [[ -z ${stderr} ]]; then
>&2 echo "ERROR: No error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then
>&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

if ! grep -q -E "^ERROR: .*Input data is empty" <<< "${stderr}"; then
>&2 echo "ERROR: Unexpected error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi
fi

## Outputs nothing to stdout
stdout=$( x86-64-level - <<< '' 2> /dev/null )
Expand All @@ -319,36 +319,37 @@ if [[ -n ${stdout} ]]; then
fi


echo "* x86-64-level - <<< 'flags: AVX' (exception)"
stderr=$(x86-64-level - <<< 'flags: AVX' 2>&1)
exit_code=$?
if [[ ${exit_code} -eq 0 ]]; then
>&2 echo "ERROR: Exit code is not non-zero: ${exit_code}"
nerrors=$((nerrors + 1))
fi

if [[ -z ${stderr} ]]; then
>&2 echo "ERROR: No error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi
for flags in "AVX2" "avx@"; do
echo "* x86-64-level - <<< 'flags: ${flags}' (exception)"
stderr=$(x86-64-level - <<< "flags: ${flags}" 2>&1)
exit_code=$?
if [[ ${exit_code} -eq 0 ]]; then
>&2 echo "ERROR: Exit code is not non-zero: ${exit_code}"
nerrors=$((nerrors + 1))
fi

if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then
>&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'"
nerrors=$((nerrors + 1))
fi
if [[ -z ${stderr} ]]; then
>&2 echo "ERROR: No error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi

if ! grep -q -E "^ERROR: .*format of the CPU flags" <<< "${stderr}"; then
>&2 echo "ERROR: Unexpected error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi
if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then
>&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'"
nerrors=$((nerrors + 1))
fi

## Outputs nothing to stdout
stdout=$( x86-64-level - <<< 'flags: AVX' 2> /dev/null )
if [[ -n ${stdout} ]]; then
>&2 echo "ERROR: Detected output to standard output: ${stdout}"
nerrors=$((nerrors + 1))
fi
if ! grep -q -E "^ERROR: .*format of the CPU flags" <<< "${stderr}"; then
>&2 echo "ERROR: Unexpected error message: '${stderr}'"
nerrors=$((nerrors + 1))
fi

## Outputs nothing to stdout
stdout=$( x86-64-level - <<< 'flags: AVX' 2> /dev/null )
if [[ -n ${stdout} ]]; then
>&2 echo "ERROR: Detected output to standard output: ${stdout}"
nerrors=$((nerrors + 1))
fi
done


#--------------------------------------------------------------------------
Expand Down
110 changes: 53 additions & 57 deletions x86-64-level
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
#' Get the x86-64 Microarchitecture Level on the Current Machine
#'
#' Queries the CPU information to infer which level of x86-64
Expand Down Expand Up @@ -46,6 +46,7 @@
#' Source code: https://github.com/ucsf-wynton/wynton-tools
#'
#' Authors:
#' * Stefan Tauner (POSIX version)
#' * Henrik Bengtsson (expanded on Gilles implementation [2])
#' * StackExchange user 'Gilles'
#' <https://stackexchange.com/users/164368/>
Expand Down Expand Up @@ -73,34 +74,31 @@ version() {
#---------------------------------------------------------------------
data=
read_input() {
if [[ -z ${data} ]]; then
if [ -z "${data}" ]; then
if ${stdin}; then
data=$(< /dev/stdin)
data=$(cat /dev/stdin)
else
data=$(< /proc/cpuinfo)
data=$(cat /proc/cpuinfo)
fi
if [[ -z ${data} ]]; then
if [ -z "${data}" ]; then
echo >&2 "ERROR: Input data is empty"
exit 1
fi
fi
}

get_cpu_name() {
local name
bfr=$(grep -E "^model name[[:space:]]*:" <<< "${data}")
name=$(echo "${bfr}" | head -n 1)
name=$(echo "${data}" | grep -E "^model name[[:space:]]*:" | head -n 1)
name="${name#model name*:}"
echo "${name## }"
}


get_cpu_flags() {
local flags
flags=$(grep "^flags[[:space:]]*:" <<< "${data}" | head -n 1)
flags=$(echo "${data}" | grep "^flags[[:space:]]*:" | head -n 1)
flags="${flags#*:}"
flags="${flags## }"
if grep -v -q -E "^[[:lower:][:digit:]_ ]+$" <<< "${flags}"; then
if [ "${flags}" != "${flags#*[!a-z0-9_ ]*}" ]; then
echo >&2 "ERROR: Cannot reliably infer the CPU x86-64 level, because the format of the CPU flags comprise of other symbols than only lower-case letters, digits, and underscores: '${flags}'"
exit 1
fi
Expand All @@ -115,10 +113,6 @@ validate_cpu_flags() {


has_cpu_flags() {
local flag
local msg
local cpu_name

for flag; do
## Note, it's important to keep a trailing space
case " ${flags} " in
Expand All @@ -129,7 +123,7 @@ has_cpu_flags() {
if ${verbose}; then
msg="Identified x86-64-v${level}, because x86-64-v$((level + 1)) requires '${flag}', which is not supported by this CPU"
cpu_name=$(get_cpu_name)
[[ -n ${cpu_name} ]] && msg="${msg} [${cpu_name}]"
[ -n "${cpu_name}" ] && msg="${msg} [${cpu_name}]"
echo >&2 "${msg}"
fi
return 1
Expand All @@ -144,15 +138,15 @@ level=0
determine_cpu_version() {
## x86-64-v0 (can this happen?)
level=0

## x86-64-v1
has_cpu_flags lm cmov cx8 fpu fxsr mmx syscall sse2 || return 0
level=1

## x86-64-v2
has_cpu_flags cx16 lahf_lm popcnt sse4_1 sse4_2 ssse3 || return 0
level=2

## x86-64-v3
has_cpu_flags avx avx2 bmi1 bmi2 f16c fma abm movbe xsave || return 0
level=3
Expand All @@ -166,7 +160,7 @@ determine_cpu_version() {
report_cpu_version() {
read_input
flags=$(get_cpu_flags)
if [[ -z ${flags} ]]; then
if [ -z "${flags}" ]; then
echo >&2 "ERROR: No CPU 'flags' entry in the input data"
exit 1
fi
Expand All @@ -183,57 +177,59 @@ stdin=false
assert=

# Parse command-line options
while [[ $# -gt 0 ]]; do
while [ $# -gt 0 ]; do
case $1 in
## Options (--flags):
if [[ "$1" == "--help" ]]; then
help
exit 0
elif [[ "$1" == "--version" ]]; then
version
exit 0
elif [[ "$1" == "--verbose" ]]; then
verbose=true
elif [[ "$1" == "-" ]]; then
stdin=true
elif [[ "$1" == "--assert" ]]; then
verbose=true
--help)
help
exit 0;;
--version)
version
exit 0;;
--verbose)
verbose=true;;
-)
stdin=true;;
## Options (--key=value):
elif [[ "$1" =~ ^--.*=.*$ ]]; then
key=${1//--}
key=${key//=*}
value=${1//--[[:alpha:]]*=}
if [[ -z ${value} ]]; then
echo >&2 "ERROR: Option '--${key}' must not be empty"
exit 2
fi
if [[ "${key}" == "assert" ]]; then
assert=${value}
if [[ ! ${assert} =~ ^-?[0-9]+$ ]]; then
echo >&2 "ERROR: Option --assert does not specify an integer: ${assert}"
--*=*)
pair=${1#--}
key=${pair%%=*}
value=${pair#*=}
if [ -z "${value}" ]; then
echo >&2 "ERROR: Option '--${key}' must not be empty"
exit 2
elif [[ ${assert} -lt 1 ]] || [[ ${assert} -gt 4 ]]; then
echo >&2 "ERROR: Option --assert is out of range [1,4]: ${assert}"
fi
if [ "${key}" = "assert" ]; then
case $value in
'' | *[!0-9-]*)
echo >&2 "ERROR: Option --assert does not specify an integer: ${value}"
exit 2
esac
if [ "${value}" -lt 1 ] || [ "${value}" -gt 4 ]; then
echo >&2 "ERROR: Option --assert is out of range [1,4]: ${value}"
exit 2
fi
assert=${value}
else
echo >&2 "ERROR: Unknown option: $key"
exit 2
fi
else
;;
*)
echo >&2 "ERROR: Unknown option: $1"
exit 2
fi
else
echo >&2 "ERROR: Unknown option: $1"
exit 2
fi
exit 2;;
esac
shift
done

if [[ -n ${assert} ]]; then
if [ -n "${assert}" ]; then
validate_cpu_flags
version=$(report_cpu_version)
if [[ ${version} -lt ${assert} ]]; then
if [ "${version}" -lt "${assert}" ]; then
read_input
cpu_info=$(get_cpu_name)
[[ -n ${cpu_info} ]] && cpu_info=" [${cpu_info}]"
>&2 echo "The CPU${cpu_info} on this host ('${HOSTNAME}') supports x86-64-v${version}, which is less than the required x86-64-v${assert}"
[ -n "${cpu_info}" ] && cpu_info=" [${cpu_info}]"
>&2 echo "The CPU${cpu_info} on this host ('$(hostname)') supports x86-64-v${version}, which is less than the required x86-64-v${assert}"
exit 1
fi
else
Expand Down
Loading