nissy-core

The "engine" of nissy, including the H48 optimal solver.
git clone https://git.tronto.net/nissy-core
Download | Log | Files | Refs | README | LICENSE

build (11378B)


      1 #!/bin/sh
      2 
      3 # Build system for nissy, run './build help' for info on how to use this.
      4 
      5 # The variables below can be used to personalize the build system. For example,
      6 # to compile with clang instead of the default cc one can use:
      7 #
      8 #   CC=clang ./build
      9 # 
     10 # Each variable also has a counterpart that starts with NISSY_BUILD_*. These
     11 # other variables can be set, for example, in your shell configuration file
     12 # to change the default build options. They are still overwritten by the values
     13 # of the non-NISSY_BUILD_* variables, if set.
     14 # For example, on a system that supports the addressed and undefined behavior
     15 # sanitizers, one can set
     16 #
     17 #   export NISSY_BUILD_SANITIZE="address,undefined"
     18 #
     19 # so that these sanitizers will be enabled when building nissy in debug mode.
     20 # If later the same user wants to build a debug version without sanitizers,
     21 # they may use
     22 #
     23 #   SANITIZE="" ./build debug
     24 #
     25 # And the empty string value will take precedence.
     26 
     27 # Specify the C compiler to use.
     28 CC=${CC-${NISSY_BUILD_CC:-"cc"}}
     29 
     30 # Specify the C++ compiler for the C++ examples.
     31 CXX=${CXX-${NISSY_BUILD_CXX:-"c++"}}
     32 
     33 # Specify the compiler to use when building for WASM.
     34 EMCC=${EMCC-${NISSY_BUILD_EMCC:-"emcc"}}
     35 
     36 # Specify the nodejs command for running tests for WASM.
     37 NODE=${NODE-${NISSY_BUILD_NODE:-"node"}}
     38 
     39 # The maximum number of threads to use for multi-threaded operations.
     40 # This is also used as default value in case an operation allows
     41 # specifying how many threads to use.
     42 # The number n must be between 1 and 128.
     43 # The default value is 16.
     44 THREADS=${THREADS-${NISSY_BUILD_THREADS}}
     45 
     46 detectthreads() {
     47 	# TODO: detect based on system
     48 	# Is 'getconf NPROCESSORD_ONLN' portable? Is it threads or cores?
     49 	echo "16"
     50 }
     51 
     52 [ -z "$THREADS" ] && THREADS="$(detectthreads)"
     53 
     54 # You can use this variable to build for a different architecture, for example
     55 # if you want to cross-compile or to use the portable version.
     56 # The value must be one of "AVX2", "NEON", "PORTABLE". If the value is not
     57 # specified, the script will automatically detect the best architecture for the
     58 # system. The architecture "PORTABLE" will work on any system.
     59 ARCH=${ARCH-${NISSY_BUILD_ARCH}}
     60 
     61 greparch() {
     62 	$CC -march=native -dM -E - </dev/null 2>/dev/null | grep "$1"
     63 }
     64 
     65 detectarch() {
     66 	[ -n "$(greparch __AVX2__)" ] && detected="AVX2"
     67 	[ -n "$(greparch __ARM_NEON)" ] && detected="NEON"
     68 	[ -z "$detected" ] && detected="PORTABLE"
     69 	echo "$detected"
     70 }
     71 
     72 [ -z "$ARCH" ] && ARCH="$(detectarch)"
     73 
     74 # SANITIZE="option1,option2,..." adds the options "-fsanitize=option1",
     75 # "-fsanitize=option2", ... to the C compiler command in debug mode.
     76 # By default none is used because these options are not available on all
     77 # systems. It is advisable to set the NISSY_BUILD_SANITIZE options in your
     78 # shell's configuration file if your system does support them.
     79 SANITIZE=${SANITIZE-${NISSY_BUILD_SANITIZE:-""}}
     80 
     81 # Optimization flags.
     82 OPTIMIZE=${OPTIMIZE-${NISSY_BUILD_OPTIMIZE:-"-O3"}}
     83 
     84 validate_command() {
     85 	command="$1"
     86 	if ! (command -v "$command" >/dev/null 2>&1) ; then
     87 		echo "Error: command '$command' not found"
     88 		exit 1
     89 	fi
     90 }
     91 
     92 validate_threads() {
     93 	threads="$1"
     94 	if [ "$threads" -le 0 ] || [ "$threads" -gt 128 ]; then
     95 		echo "Error: number of threads must be between 1 and 128"
     96 		exit 1
     97 	fi
     98 }
     99 
    100 validate_arch() {
    101 	arch="$1"
    102 	case "$arch" in
    103 	AVX2|NEON|PORTABLE)
    104 		;;
    105 	*)
    106 		echo "Error: architecture '$arch' not supported"
    107 		echo "Supported architectures: AVX2, NEON, PORTABLE"
    108 		exit 1
    109 		;;
    110 	esac
    111 }
    112 
    113 parsesanitize() {
    114 	# Use the user-specified comma-separated sanitizers
    115 	for opt in $(echo "$1" | tr ',' '\n'); do
    116 		printf -- '-fsanitize=%s ' "$opt"
    117 	done
    118 }
    119 
    120 # Build flags
    121 CFLAGS="-std=c11 -fPIC -D_POSIX_C_SOURCE=199309L -pthread"
    122 [ "$ARCH" = "AVX2" ] && CFLAGS="$CFLAGS -mavx2"
    123 WFLAGS="-pedantic -Wall -Wextra -Wno-unused-parameter -Wno-unused-function"
    124 OFLAGS="$OPTIMIZE"
    125 DFLAGS="-DDEBUG -g3 $(parsesanitize "$SANITIZE")"
    126 MFLAGS="-DTHREADS=$THREADS -D$ARCH"
    127 CPPFLAGS="-std=c++20 -pthread"
    128 
    129 # TODO:
    130 # MEMORY64 is supported on Firefox (from version 134) and Chrome (from 133),
    131 # but not on Safari (nor on e.g. Firefox 128 ESR, current default on Debian).
    132 # See also https://webassembly.org/features
    133 # When it becomes widely available, we can support it by adding -sMEMORY64
    134 # to the WASMCFLAGS, WASMCPPFLAGS and WASMLINKFLAGS variables below, and
    135 # -sMAXIMUM_MEMORY=10737418240 to WASMLINKFLAGS. This way we can enable
    136 # solvers larger than h48h6k2 in the web version.
    137 
    138 # TODO:
    139 # The options below have to be adjusted when native WASM_SIMD is implemented.
    140 
    141 # Build flags for emscripten (WASM target)
    142 WASMCFLAGS="-std=c11 -fPIC -D_POSIX_C_SOURCE=199309L -pthread
    143             -mfpu=neon -mrelaxed-simd"
    144 WASMCPPFLAGS="-std=c++20 -pthread"
    145 WASMDBGFLAGS="-sASSERTIONS"
    146 WASMMFLAGS="-DTHREADS=$THREADS -DNEON"
    147 WASMLINKFLAGS="--no-entry -sEXPORT_NAME='Nissy' -sMODULARIZE 
    148 	-sEXPORTED_RUNTIME_METHODS=addFunction,UTF8ToString
    149 	-sALLOW_TABLE_GROWTH
    150 	-sALLOW_MEMORY_GROWTH -sSTACK_SIZE=5MB -sPTHREAD_POOL_SIZE=$THREADS
    151 	-sFETCH -sASYNCIFY -sLINKABLE -sEXPORT_ALL"
    152 
    153 if (command -v "python3-config" >/dev/null 2>&1) ; then
    154 	PYTHON3_INCLUDES="$(python3-config --includes)"
    155 	PYTHON3="version $(echo "$PYTHON3_INCLUDES" | sed 's/.*3\./3./')"
    156 else
    157 	PYTHON3_INCLUDES=""
    158 	PYTHON3="Not found, Python shell won't be available"
    159 fi
    160 
    161 build_help() {
    162 	echo "Build system for nissy. Usage:"
    163 	echo ""
    164 	echo "$0 TARGET             # build the given TARGET"
    165 	echo "$0                    # same as '$0 nissy'"
    166 	echo "$0 test [PATTERN]     # run unit tests (matching PATTERN)"
    167 	echo "$0 webtest [PATTERN]  # same as test, but for WASM build"
    168 	echo "$0 tool PATTERN       # run the tool matching PATTERN"
    169 	echo ""
    170 	echo "targets:"
    171 	echo ""
    172 	echo "nissy       Build the nissy.o object file"
    173 	echo "lib         Build the static library libnissy.a"
    174 	echo "sharedlib   Build the shared library libnissy.so"
    175 	echo "python      Build the Python module for nissy"
    176 	echo "shell       Build a basic nissy shell (./run)"
    177 	echo "web         Build the WebAssembly / JavaScript module for nissy"
    178 	echo "cpp FILES   Build and run the given FILES including cpp/nissy.h"
    179 	echo ""
    180 	echo "help        Show this help message"
    181 	echo "config      Show build configuration and exit"
    182 	echo "clean       Remove all build files"
    183 }
    184 
    185 build_config() {
    186 	echo "Compiler: $CC"
    187 	echo "Architecture: $ARCH"
    188 	echo "Max threads: $THREADS"
    189 	echo "Optimization options: $OFLAGS"
    190 	echo "Debug flags: $DFLAGS"
    191 	echo ""
    192 
    193 	echo "Optional features:"
    194 
    195 	printf '%s' "WASM compiler: "
    196 	if (validate_command "$EMCC" >/dev/null); then
    197 		echo "$EMCC"
    198 	else
    199 		echo "Not found, web target won't be available"
    200 	fi
    201 	
    202 	printf '%s' "nodejs executable: "
    203 	if (validate_command "$NODE" >/dev/null); then
    204 		echo "$NODE"
    205 	else
    206 		echo "Not found, unit tests won't be available for web target"
    207 	fi
    208 
    209 	echo "Python bindings: $PYTHON3"
    210 }
    211 
    212 run() {
    213 	echo "$@"
    214 	$@
    215 }
    216 
    217 odflags() {
    218 	if [ "$debug" = "yes" ]; then
    219 		echo "$DFLAGS"
    220 	else
    221 		echo "$OFLAGS"
    222 	fi
    223 }
    224 
    225 build_clean() {
    226 	run rm -rf -- *.o *.so *.a run runtest runtool runcpp \
    227 	    web/nissy_web_module.* web/http/nissy_web_module.*
    228 }
    229 
    230 build_nissy() {
    231 	validate_command "$CC"
    232 	validate_threads "$THREADS"
    233 	validate_arch "$ARCH"
    234 
    235 	run $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -c -o nissy.o src/nissy.c
    236 }
    237 
    238 build_lib() {
    239 	build_nissy
    240 	run ar rcs libnissy.a nissy.o
    241 }
    242 
    243 build_sharedlib() {
    244 	validate_command "$CC"
    245 	validate_threads "$THREADS"
    246 	validate_arch "$ARCH"
    247 
    248 	run $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -shared -c -o nissy.o \
    249 		src/nissy.c
    250 }
    251 
    252 build_shell() {
    253 	build_nissy || exit 1
    254 	run $CC $CFLAGS $WFLAGS $(odflags) -o run nissy.o shell/shell.c
    255 }
    256 
    257 build_python() {
    258 	if [ -z "$PYTHON3_INCLUDES" ]; then
    259 		echo "Python3 development headers could not be located"
    260 		echo "Cannot build python module"
    261 		exit 1
    262 	fi
    263 	build_nissy || exit 1
    264 	run $CC $CFLAGS $WFLAGS $PYTHON3_INCLUDES $(odflags) -shared \
    265 		-o nissy_python_module.so nissy.o python/nissy_module.c
    266 }
    267 
    268 build_cpp() {
    269 	validate_command "$CXX"
    270 	if [ -z "$@" ]; then
    271 		echo "Please provide one or more valid C++ source files"
    272 		echo "usage: ./build cpp FILES"
    273 	fi
    274 
    275 	build_nissy || exit 1
    276 	run $CXX -std=c++20 -o runcpp cpp/nissy.cpp nissy.o $@
    277 	run ./runcpp
    278 }
    279 
    280 build_web() {
    281 	validate_command "$EMCC"
    282 	validate_threads "$THREADS"
    283 	validate_arch "$ARCH"
    284 
    285 	obj="nissy_web_module"
    286 
    287 	if [ "$debug" = "yes" ]; then
    288 		ODFLAGS="$DFLAGS -sASSERTIONS"
    289 	else
    290 		ODFLAGS="$OFLAGS"
    291 	fi
    292 
    293 	run $EMCC $WASMCFLAGS $WFLAGS $WASMMFLAGS $ODFLAGS -c \
    294 		-o nissy.o src/nissy.c || exit 1
    295 	run $EMCC -lembind -lidbfs.js \
    296 		$WASMCPPFLAGS $ODFLAGS $WASMLINKFLAGS -o web/"$obj".mjs \
    297 		cpp/nissy.cpp web/storage.cpp web/adapter.cpp nissy.o || exit 1
    298 	cp web/"$obj".mjs web/http/
    299 	cp web/"$obj".wasm web/http/
    300 }
    301 
    302 dotest() {
    303 	testout="test/last.out"
    304 	testerr="test/last.err"
    305 
    306 	# Verify that $t is a directory and it starts with three digits
    307 	if [ ! -d "$t" ] || ! (basename "$t" | grep -Eq '^[0-9]{3}'); then
    308 		return 0
    309 	fi
    310 
    311 	# Verify that the test is selected by the given pattern
    312 	if [ -n "$pattern" ] && ! (echo "$t" | grep -q "$pattern"); then
    313 		return 0
    314 	fi
    315 
    316 	$testbuild "$t"/*.c nissy.o || exit 1
    317 	for cin in "$t"/*.in; do
    318 		c="$(echo "$cin" | sed 's/\.in//')"
    319 		cout="$c.out"
    320 		printf '%s: ' "$c"
    321 		$testrun < "$cin" > "$testout" 2> "$testerr"
    322 		if diff "$cout" "$testout"; then
    323 			printf "OK\n"
    324 		else
    325 			printf "Test failed! stderr:\n"
    326 			cat "$testerr"
    327 			exit 1
    328 		fi
    329 	done
    330 }
    331 
    332 build_test_generic() {
    333 	if [ -n "$1" ]; then
    334 		pattern="$1"
    335 		shift
    336 	fi
    337 	debug="yes"
    338 	build_$obj || exit 1
    339 	for t in test/*; do
    340 		dotest || exit 1
    341 	done
    342 	echo "All tests passed!"
    343 }
    344 
    345 build_test() {
    346 	obj="nissy"
    347 	testobj="runtest"
    348 	testbuild="$CC $CFLAGS $WFLAGS $DFLAGS $MFLAGS -o $testobj"
    349 	testrun="./$testobj"
    350 	build_test_generic $@
    351 	rm -f runtest
    352 }
    353 
    354 build_webtest() {
    355 	obj="web"
    356 	testobj="runtest.js"
    357 	testbuild="$EMCC $WASMCFLAGS $WFLAGS $WASMMFLAGS $DFLAGS -o $testobj"
    358 	testrun="$NODE $testobj"
    359 	build_test_generic $@
    360 	rm -f runtest.js runtest.wasm
    361 }
    362 
    363 build_tool() {
    364 	pattern="$1"
    365 
    366 	if [ -z "$pattern" ]; then
    367 		echo "Please provide a valid PATTERN to select a tool"
    368 		echo "usage: ./build tool PATTERN"
    369 		exit 1
    370 	fi
    371 	shift
    372 
    373 	# Select a single tool matched by the given pattern
    374 	for t in tools/*; do
    375 		if [ -d "$t" ] && (echo "$t" | grep -q "$pattern"); then
    376 			toolname="$(basename "$t" .c)"
    377 			break
    378 		fi
    379 	done
    380 	if [ -z "$toolname" ]; then
    381 		echo "pattern '$pattern' does not match any tool"
    382 		exit 1
    383 	fi
    384 
    385 	build_nissy
    386 
    387 	results="tools/results"
    388 	last="$results/last.out"
    389 	date="$(date +'%Y-%m-%d-%H-%M-%S')"
    390 	file="$results/$toolname-$date.txt"
    391 
    392 	$CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -o runtool "$t"/*.c nissy.o \
    393 		|| exit 1
    394 
    395 	(
    396 		date +'%Y-%m-%d %H:%M'
    397 		echo ""
    398 		echo "=========== Running tool  ==========="
    399 		echo "tool name: $toolname"
    400 		echo ""
    401 		echo "======== nissy build command ========"
    402 		echo "$CC $CFLAGS $WFLAGS $MFLAGS $(odflags)"
    403 		echo ""
    404 		echo "======== tool build command  ========"
    405 		echo "$CC $CFLAGS $WFLAGS $MFLAGS $(odflags)"
    406 		echo ""
    407 		echo "============ tool output ============"
    408 		./runtool $@
    409 	) | tee "$file" "$last"
    410 	rm -f runtool
    411 }
    412 
    413 if [ "$1" = "-d" ]; then
    414 	debug="yes"
    415 	shift
    416 fi
    417 
    418 target="$1"
    419 if [ -z "$target" ]; then
    420 	target="nissy"
    421 else
    422 	shift
    423 fi
    424 
    425 case "$target" in
    426 help|config|clean|\
    427 nissy|lib|sharedlib|shell|python|cpp|web|test|webtest|tool)
    428 	mkdir -p tables tools/results
    429 	(build_"$target" $@) || exit 1
    430 	exit 0
    431 	;;
    432 *)
    433 	echo "Target '$target' unavailable, run '$0 help' for info"
    434 	exit 1
    435 	;;
    436 esac