aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--articles/2021-10-11_reproducible_development_environment_teensy.org195
-rw-r--r--articles/2023-12-26_benefiting_from_deliberately_failing_linkage.org243
-rw-r--r--flake.lock6
-rw-r--r--flake.nix2
l---------tags/cpp/2021-10-11_reproducible_development_environment_teensy.org1
l---------tags/cpp/2023-12-26_benefiting_from_deliberately_failing_linkage.org1
l---------tags/development/2021-10-11_reproducible_development_environment_teensy.org1
l---------tags/development/2023-12-26_benefiting_from_deliberately_failing_linkage.org1
l---------tags/english/2021-10-11_reproducible_development_environment_teensy.org1
l---------tags/english/2023-12-26_benefiting_from_deliberately_failing_linkage.org1
l---------tags/nix/2018-06-23_nixos_gpu_assorted_topics.md1
l---------tags/nix/2021-10-11_reproducible_development_environment_teensy.org1
12 files changed, 450 insertions, 4 deletions
diff --git a/articles/2021-10-11_reproducible_development_environment_teensy.org b/articles/2021-10-11_reproducible_development_environment_teensy.org
new file mode 100644
index 0000000..e0fd626
--- /dev/null
+++ b/articles/2021-10-11_reproducible_development_environment_teensy.org
@@ -0,0 +1,195 @@
+* Reproducible development environment for Teensy
+So for a change of scenery I recently started to mess around with microcontrollers again.
+Since the last time that I had any real contact with this area was probably around a decade ago --- programming an [[https://www.dlr.de/rm/en/desktopdefault.aspx/tabid-14006/#gallery/34068][ASURO]] robot --- I started basically from scratch.
+Driven by the goal of building and programming a fancy mechanical keyboard (as it seems to be the trendy thing to do) I chose the Arduino-compatible [[https://www.pjrc.com/store/teensy40.html][Teensy 4.0]]
+board. While I appreciate the rich and accessible software ecosystem for this platform, I don't really want to use some special IDE, applying amongst other things[fn:0]
+weird non-standard preprocessing to my code. In this vein it would also be nice to use my accustomed [[https://nixos.org][Nix-based]] toolchain which leads me to this article.
+
+Roughly following what [[https://rzetterberg.github.io/teensy-development-on-nixos.html][others did]] for Teensy 3.1 while adapting it to Teensy 4.0 and Nix flakes it is simple to build and flash
+some basic C++ programs onto a USB-attached board. The adapted version of the Arduino library is available on [[https://github.com/PaulStoffregen/cores][Github]] and can
+be compiled into a shared library using flags
+
+#+BEGIN_SRC make
+MCU = IMXRT1062
+MCU_DEF = ARDUINO_TEENSY40
+
+OPTIONS = -DF_CPU=600000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH
+OPTIONS += -D__$(MCU)__ -DARDUINO=10813 -DTEENSYDUINO=154 -D$(MCU_DEF)
+
+CPU_OPTIONS = -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 -mthumb
+
+CPPFLAGS = -Wall -g -O2 $(CPU_OPTIONS) -MMD $(OPTIONS) -ffunction-sections -fdata-sections
+CXXFLAGS = -felide-constructors -fno-exceptions -fpermissive -fno-rtti -Wno-error=narrowing -I@TEENSY_INCLUDE@
+#+END_SRC
+
+included into a run-of-the-mill Makefile and relying on the =arm-none-eabi-gcc= compiler. Correspondingly, the
+derivation for the core library [[http://code.kummerlaender.eu/teensy-env/tree/core.nix?id=44c1837717f748b891df1a6c88a72ec3a51470ce][=core.nix=]] is straight forward. It clones a given version of the library repository,
+jumps to the =teensy4= directory, deletes the example =main.cpp= file to exclude it from the library and applies a Makefile
+adapted from the default one. For the result only headers, common flags and the linker script =IMXRT1062.ld=
+are exported.
+
+As existing Arduino /sketches/ commonly consist of a single C++ file (ignoring some non-standard stuff for later) most
+builds can be handled generically by a mapping of =*.cpp= files into flashable =*.hex= files. This is realized by the following
+function based on the =teensy-core= derivation and a [[http://code.kummerlaender.eu/teensy-env/tree/Makefile.default?id=44c1837717f748b891df1a6c88a72ec3a51470ce][default makefile]]:
+
+#+BEGIN_SRC nix
+build = name: source: pkgs.stdenv.mkDerivation rec {
+ inherit name;
+
+ src = source;
+
+ buildInputs = with pkgs; [
+ gcc-arm-embedded
+ teensy-core
+ ];
+
+ buildPhase = ''
+ export CC=arm-none-eabi-gcc
+ export CXX=arm-none-eabi-g++
+ export OBJCOPY=arm-none-eabi-objcopy
+ export SIZE=arm-none-eabi-size
+
+ cp ${./Makefile.default} Makefile
+ export TEENSY_PATH=${teensy-core}
+ make
+ '';
+
+ installPhase = ''
+ mkdir $out
+ cp *.hex $out/
+ '';
+};
+#+END_SRC
+
+The derivation yielded by =build "test" ./test= results in a =result= directory containing a =*.hex= file for each
+C++ file contained in the =test= directory. Adding a =loader= function to be used in convenient =nix flake run=
+commands
+
+#+BEGIN_SRC nix
+loader = name: path: pkgs.writeScript name ''
+ #!/bin/sh
+ ${pkgs.teensy-loader-cli}/bin/teensy-loader-cli --mcu=TEENSY40 -w ${path}
+'';
+#+END_SRC
+
+a reproducible build of the canonical /blink/ example[fn:1] is realized using:
+
+#+BEGIN_SRC sh
+nix flake clone git+https://code.kummerlaender.eu/teensy-env --dest .
+nix run .#flash-blink
+#+END_SRC
+
+Expanding on this, the =teensy-env= flake also provides convenient =image(With)= functions for building
+programs that depend on additional Arduino libraries such as for controlling servos. E.g. the build
+of a program =test.cpp= placed in a =src= folder
+
+#+BEGIN_SRC cpp
+#include <Arduino.h>
+#include <Servo.h>
+
+extern "C" int main(void) {
+ Servo servo;
+ // Servo connected to PWM-capable pin 1
+ servo.attach(1);
+ while (true) {
+ // Match potentiometer connected to analog pin 7
+ servo.write(map(analogRead(7), 0, 1023, 0, 180));
+ delay(20);
+ }
+}
+#+END_SRC
+
+is fully described by the flake:
+
+#+BEGIN_SRC nix
+{
+ description = "Servo Test";
+
+ inputs = {
+ teensy-env.url = git+https://code.kummerlaender.eu/teensy-env;
+ };
+
+ outputs = { self, teensy-env }: let
+ image = teensy-env.custom.imageWith
+ (with teensy-env.custom.teensy-extras; [ servo ]);
+
+ in {
+ defaultPackage.x86_64-linux = image.build "servotest" ./src;
+ };
+}
+#+END_SRC
+
+At first I expected the build of [[http://www.ulisp.com/][uLisp]][fn:2] to proceed equally smoothly as this implementation of Lisp
+for microcontrollers is provided as a single [[https://raw.githubusercontent.com/technoblogy/ulisp-arm/master/ulisp-arm.ino][=ulisp-arm.ino=]] file. However, the =*.ino= extension
+is not just for show here as beyond even the replacement of =main= by =loop= and =setup= --- which
+would be easy to fix --- it relies on further non-standard preprocessing offered by the
+Arduino toolchain. I quickly aborted my efforts towards patching in e.g. the forward-declarations
+which are automagically added during the build (is it really such a hurdle to at least declare stuff before
+referring to it… oh well) and instead followed a less pure approach using =arduino-cli= to access
+the actual Arduino preprocessor.
+
+#+BEGIN_SRC sh
+arduino-cli core install arduino:samd
+arduino-cli compile --fqbn arduino:samd:arduino_zero_native --preprocess ulisp-arm.ino > ulisp-arm.cpp
+#+END_SRC
+
+The problematic line w.r.t. to reproducible builds in Nix is the installation of the =arduino:samd= toolchain
+which requires network access and wants to install stuff to home. Pulling in arbitrary stuff over the
+network is of course not something one wants to do in an isolated and hopefully reproducible build
+environment which is why this kind of stuff is heavily restricted in common Nix derivations. Luckily
+it is possible to misuse (?) a fixed-output derivation to describe the preprocessing of =ulisp-arm.ino=
+into a standard C++ =ulisp-arm.cpp= compilable using the GCC toolchain.
+
+The relevant file [[https://code.kummerlaender.eu/teensy-env/tree/ulisp.nix?id=44c1837717f748b891df1a6c88a72ec3a51470ce][=ulisp.nix=]] pulls in the uLisp source from Github and calls =arduino-cli= to install
+its toolchain to a temporary home folder followed by preprocessing the source into the derivation's
+output. The relevant lines for turning this into a fixed-output derivation are
+
+#+BEGIN_SRC nix
+outputHashMode = "flat";
+outputHashAlgo = "sha256";
+outputHash = "mutVLBFSpTXgUzu594zZ3akR/Z7e9n5SytU6WoQ6rKA=";
+#+END_SRC
+
+to declare the hash of the resulting file. After this point building and flashing uLisp using the =teensy-env=
+flake works the same as for any C++ program. The two additional /SPI/ and /Wire/ library dependencies are
+added easily using =imageWith=:
+
+#+BEGIN_SRC nix
+teensy-ulisp = let
+ ulisp-source = import ./ulisp.nix { inherit pkgs; };
+ ulisp-deps = with teensy-extras; [ spi wire ];
+in (imageWith ulisp-deps).build
+ "teensy-ulisp"
+ (pkgs.linkFarmFromDrvs "ulisp" [ ulisp-source ]);
+#+END_SRC
+
+So we are now able to build and flash uLisp onto a conveniently attached Teensy 4.0 board using only:
+
+#+BEGIN_SRC sh
+nix flake clone git+https://code.kummerlaender.eu/teensy-env --dest .
+nix run .#flash-ulisp
+#+END_SRC
+
+Connecting finally via serial terminal =screen /dev/ttyACM0 9600= we end up in a LISP environment where we
+can play around with the microcontroller at our leisure without reflashing.
+
+#+BEGIN_SRC lisp
+59999> (* 21 2)
+42
+
+59999> (defun blink (&optional x)
+ (pinmode 13 t)
+ (digitalwrite 13 x)
+ (delay 1000)
+ (blink (not x)))
+
+59966> (blink)
+#+END_SRC
+
+As always, the code of everything discussed here is available via Git on [[https://code.kummerlaender.eu/teensy-env][code.kummerlaender.eu]].
+While I only focused on Teensy 4.0 it should be easy to adapt to other versions by changing the
+compiler flags using [[https://github.com/PaulStoffregen/cores][PaulStoffregen/cores]] as a reference.
+
+[fn:0] e.g. forcing me to patch my XMonad [[http://code.kummerlaender.eu/nixos_home/tree/gui/conf/xmonad.hs][config]] to even get a usable UI…
+[fn:1] Simply flashing the on-board LED periodically
+[fn:2] Interactive development using a Lisp REPL on a microcontroller, how much more can you really ask for?
diff --git a/articles/2023-12-26_benefiting_from_deliberately_failing_linkage.org b/articles/2023-12-26_benefiting_from_deliberately_failing_linkage.org
new file mode 100644
index 0000000..a67999d
--- /dev/null
+++ b/articles/2023-12-26_benefiting_from_deliberately_failing_linkage.org
@@ -0,0 +1,243 @@
+* Benefiting from deliberately failing linkage
+Realizing that I have not written anything here for two /years/ lets just start writing again[fn:-1]:
+Compilation times for template-heavy C++ codebases such as the one at [[https://openlb.net][the center of my daily life]] can be a real pain.
+This mostly got worse since I started to really get my hands dirty in its depths during the [[https://www.helmholtz-hirse.de/series/2022_12_01-seminar_9.html][extensive refactoring]] towards SIMD and GPU support[fn:0].
+The current sad high point in compilation times was reached when compiling the first GPU-enabled simulation cases: More than 100 seconds for a single compile on my not too shabby system.
+This article will detail how I significantly reduced this on the build system level while gaining useful features.
+
+#+BEGIN_SRC bash
+λ ~/p/c/o/e/t/nozzle3d (openlb-env-cuda-env) • time make
+make -C ../../.. core
+make[1]: Entering directory '/home/common/projects/contrib/openlb-master'
+make[1]: Nothing to be done for 'core'.
+make[1]: Leaving directory '/home/common/projects/contrib/openlb-master'
+nvcc -pthread --forward-unknown-to-host-compiler -x cu -O3 -std=c++17 --generate-code=arch=compute_75,code=[compute_75,sm_75] --extended-lambda --expt-relaxed-constexpr -rdc=true -DPLATFORM_CPU_SISD -DPLATFORM_GPU_CUDA -DDEFAULT_FLOATING_POINT_TYPE=float -I../../../src -c -o nozzle3d.o nozzle3d.cpp
+nvcc nozzle3d.o -o nozzle3d -lolbcore -lpthread -lz -ltinyxml -L/run/opengl-driver/lib -lcuda -lcudadevrt -lcudart -L../../../build/lib
+________________________________________________________
+Executed in 112.27 secs fish external
+ usr time 109.46 secs 149.00 micros 109.46 secs
+ sys time 2.42 secs 76.00 micros 2.42 secs
+#+END_SRC
+
+Even when considering that this compiles many dozens of individual CUDA kernels for multiple run-time selectable physical models and boundary conditions in addition to the simulation scaffold[fn:1] it still takes too long for comfortably iterating during development.
+Needless to say, things did not improve when I started working on heterogeneous execution and the single executable needed to also contain vectorized versions of all models for execution on CPUs in addition to MPI and OpenMP routines.
+Even worse, you really want to use Intel's C++ compilers when running CPU-based simulations on Intel-based clusters[fn:2] which plainly is not possible in such a /homogeneous/ compiler setup where everyhing has to pass through =nvcc=.
+
+#+BEGIN_SRC bash
+λ ~/p/c/o/e/t/nozzle3d (openlb-env-gcc-env) • time make
+g++ -O3 -Wall -march=native -mtune=native -std=c++17 -pthread -DPLATFORM_CPU_SISD -I../../../src -c -o nozzle3d.o nozzle3d.cpp
+g++ nozzle3d.o -o nozzle3d -lolbcore -lpthread -lz -ltinyxml -L../../../build/lib
+________________________________________________________
+Executed in 31.77 secs fish external
+ usr time 31.21 secs 0.00 micros 31.21 secs
+ sys time 0.55 secs 693.00 micros 0.55 secs
+#+END_SRC
+
+Comparing the GPU build to the previous CPU-only compilation time of around 32 seconds -- while nothing to write home about -- it was still clear that time would be best spent on separating out the CUDA side of things, both to mitigate its performance impact and to enabled a /mixed/ compiler environment.
+
+[fn:-1] …and do my part in feeding the LLM training machine :-)
+[fn:0] Definitely a double edged sword: On the one side it enables concise DSL-like compositions of physical models while supporting automatic code optimization and efficient execution accross heterogeneous hardware. On the other side my much younger, Pascal-fluent, self would not be happy with how cryptic and unmaintainable many of my listings can look to the outsider.
+In any case, OpenLB as a heavily templatized and meta-programmed C++ software library is a foundational design decision.
+[fn:1] Data structures, pre- and post-processing logic, IO routines, ...
+[fn:2] Commonly improving performance by quite a few percent
+
+** Requirements
+Firstly, any solution would need to exist within the existing plain Makefile based build system[fn:3] and should not complicate the existing build workflow for our users[fn:4].
+Secondly, it should allow for defining completely different compilers and configuration flags for the CPU- and the GPU-side of the application.
+The intial driving force of speeding up GPU-targeted compilation would then be satisfied as a side effect due to the ability of only recompiling the CPU-side of things as long as no new physical models are introduced. This restriction is useful in the present context as GPU kernels execute the computationally expensive part, i.e. the actual simulation, but generally do not change often during development of new simulation cases after the initial choice of physical model.
+
+[fn:3] Which was a deliberate design decision in order to minimize dependencies considering the minimal build complexity required by OpenLB as a plain CPU-only MPI code. While this could of course be reconsidered in the face of increased target complexity it was not the time to open that bottle.
+[fn:4] Mostly domain experts from process engineering, physics or mathematics without much experience in software engineering.
+
+** Approach
+Following the requirements, a basic approach is to split the application into two compilation units: One containing only the CPU-implementation consisting of the high level algorithmic structure, pre- and post-processing, communication logic, CPU-targeted simulation kernels and calls to the GPU code.
+The other containing only the GPU code consisting of CUDA kernels and their immediate wrappers called from the CPU-side of things -- i.e. only those parts that truly need to be compiled using NVIDIA's =nvcc=.
+Given two separated files =cpustuff.cpp= and =gpustuff.cu= it would be easy to compile them using separate configurations and then link them together into a single executable.
+The main implementation problem is how to generate two such separated compilation units that can be cleanly linked together, i.e. without duplicating symbols and similar hurdles.
+
+** Implementation
+In days past the build system actually contained an option for such separated compilation: termed as the /pre-compiled mode/ in OpenLB speak.
+This mode consisted of a somewhat rough and leaky separation between interface and implementation headers that was augmented by many hand-written C++ files containing explicit template instantiations of the aforementioned implementations for certain common arguments.
+These C++ files could then be compiled once into a shared library that was linked to the application unit compiled without access to the implementation headers.
+While this worked it was always a struggle to keep these files maintained.
+Additionally any benefit for the, at that time CPU-only, codebase was negligible and in the end not worth the effort any more causing it to be dropped somewhere on the road to release 1.4.
+
+Nevertheless, the basic approach of compiling a shared libary of explicit template instantiations is sound if we can find a way to automatically generate the instantiations per-case instead of manually maintaining them.
+A starting point for this is to take a closer look at the linker errors produced when compiling a simulation case including only the interface headers for the GPU code.
+These errors contain partial signatures of all relevant methods from plain function calls
+
+#+BEGIN_SRC bash
+λ ~/p/c/o/e/l/cavity3dBenchmark (openlb-env-gcc-openmpi-cuda-env) • mpic++ cavity3d.o -lpthread -lz -ltinyxml -L../../../build/lib -lolbcore
+cavity3d.cpp:(...): undefined reference to `olb::gpu::cuda::device::synchronize()'
+#+END_SRC
+
+to bulk and boundary collision operator constructions
+
+#+BEGIN_SRC bash
+cavity3d.cpp:(...): undefined reference to `olb::ConcreteBlockCollisionO<float, olb::descriptors::D3Q19<>, (olb::Platform)2, olb::dynamics::Tuple<float, olb::descriptors::D3Q19<>, olb::momenta::Tuple<olb::momenta::BulkDensity, olb::momenta::BulkMomentum, olb::momenta::BulkStress, olb::momenta::DefineToNEq>, olb::equilibria::SecondOrder, olb::collision::BGK, olb::dynamics::DefaultCombination> >::ConcreteBlockCollisionO()'
+#+END_SRC
+
+as well as core data structure accessors:
+
+#+BEGIN_SRC bash
+cavity3d.cpp:(.text._ZN3olb20ConcreteBlockLatticeIfNS_11descriptors5D3Q19IJEEELNS_8PlatformE2EE21getPopulationPointersEj[_ZN3olb20ConcreteBlockLatticeIfNS_11descriptors5D3Q19IJEEELNS_8PlatformE2EE21getPopulationPointersEj]+0x37): undefined reference to `olb::gpu::cuda::CyclicColumn<float>::operator[](unsigned long)'
+#+END_SRC
+
+These errors are easily turned into a sorted list of unique missing symbols using basic piping
+
+#+BEGIN_SRC makefile
+build/missing.txt: $(OBJ_FILES)
+ $(CXX) $^ $(LDFLAGS) -lolbcore 2>&1 \
+ | grep -oP ".*undefined reference to \`\K[^']+\)" \
+ | sort \
+ | uniq > $@
+#+END_SRC
+
+which only assumes that the locale is set to english and -- surprisingly -- works consistently accross any relevant C++ compilers[fn:5], likely due to all of them using either the GNU Linker or a drop-in compatible alternative thereto.
+The resulting plain list of C++ method signatures hints at the reasonably structured and consistent template /language/ employed by OpenLB:
+
+#+BEGIN_SRC cpp
+olb::ConcreteBlockCollisionO<float, olb::descriptors::D3Q19<>, (olb::Platform)2, olb::CombinedRLBdynamics<float, olb::descriptors::D3Q19<>, olb::dynamics::Tuple<float, olb::descriptors::D3Q19<>, olb::momenta::Tuple<olb::momenta::BulkDensity, olb::momenta::BulkMomentum, olb::momenta::BulkStress, olb::momenta::DefineToNEq>, olb::equilibria::SecondOrder, olb::collision::BGK, olb::dynamics::DefaultCombination>, olb::momenta::Tuple<olb::momenta::VelocityBoundaryDensity<0, -1>, olb::momenta::FixedVelocityMomentumGeneric, olb::momenta::RegularizedBoundaryStress<0, -1>, olb::momenta::DefineSeparately> > >::ConcreteBlockCollisionO()
+olb::gpu::cuda::CyclicColumn<float>::operator[](unsigned long)
+olb::gpu::cuda::device::synchronize()
+// [...]
+#+END_SRC
+
+For example, local cell models -- /Dynamics/ in OpenLB speak -- are mostly implemented as tuples of momenta, equilibrium functions and collision operators[fn:6].
+All such relevant classes tend to follow a consistent structure in what methods with which arguments and return types they implement.
+We can use this domain knowledge of our codebase to transform the incomplete signatures in our new =missing.txt= into a full list of explicit template instantiations written in valid C++.
+
+#+BEGIN_SRC makefile
+build/olbcuda.cu: build/missing.txt
+# Generate includes of the case source
+# (replaceable by '#include <olb.h>' if no custom operators are implemented in the application)
+ echo -e '$(CPP_FILES:%=\n#include "../%")' > $@
+# Transform missing symbols into explicit template instantiations by:
+# - filtering for a set of known and automatically instantiable methods
+# - excluding destructors
+# - dropping resulting empty lines
+# - adding the explicit instantiation prefix (all supported methods are void, luckily)
+ cat build/missing.txt \
+ | grep '$(subst $() $(),\|,$(EXPLICIT_METHOD_INSTANTIATION))' \
+ | grep -wv '.*\~.*\|FieldTypeRegistry()' \
+ | xargs -0 -n1 | grep . \
+ | sed -e 's/.*/template void &;/' -e 's/void void/void/' >> $@
+# - filtering for a set of known and automatically instantiable classes
+# - dropping method cruft and wrapping into explicit class instantiation
+# - removing duplicates
+ cat build/missing.txt \
+ | grep '.*\($(subst $() $(),\|,$(EXPLICIT_CLASS_INSTANTIATION))\)<' \
+ | sed -e 's/\.*>::.*/>/' -e 's/.*/template class &;/' -e 's/class void/class/' \
+ | sort | uniq >> $@
+#+END_SRC
+
+Note that this is only possible due to full knowledge of and control over the target codebase.
+In case this is not clear already: In no way do I recommend that this approach be followed in a more general context[fn:7].
+It was only the quickest and most maintainable approach to achieving the stated requirements given the particulars of OpenLB.
+
+As soon as the build system dumped the first =olbcuda.cu= file into the =build= directory I thought that all that remained was to compile this into a shared library and link it all together.
+However, the resulting shared library contained not only the explicitly instantiated symbols but also additional stuff that they required.
+This caused quite a few duplicate symbol errors when I tried to link the library and the main executable.
+While linking could still be forced by ignoring these errors, the resulting executable was not running properly.
+This is where I encountered something unfamiliar to me: linker version scripts.
+
+The same as for basically every question one encounters in the context of such fundamental software as GNU =ld=, first released alongside the other GNU Binutils in the 80s, a solution has long since been developed.
+For our particular problem the solution are /linker version scripts/.
+
+#+BEGIN_SRC
+LIBOLBCUDA { global:
+/* list of mangeled symbols to globally expose [...] */
+_ZGVZN3olb9utilities14TypeIndexedMapIPNS_12AnyFieldTypeIfNS_11descriptors5D3Q19IJEEELNS_8PlatformE0EEENS_17FieldTypeRegistryIfS5_LS6_0EEEE9get_indexINS_18OperatorParametersINS_19CombinedRLBdynamicsIfS5_NS_8dynamics5TupleIfS5_NS_7momenta5TupleINSH_11BulkDensityENSH_12BulkMomentumENSH_10BulkStressENSH_11DefineToNEqEEENS_10equilibria11SecondOrderENS_9collision3BGKENSF_18DefaultCombinationEEENSI_INSH_18InnerEdgeDensity3DILi0ELi1ELi1EEENSH_28FixedVelocityMomentumGenericENSH_17InnerEdgeStress3DILi0ELi1ELi1EEENSH_16DefineSeparatelyEEEEEEEEEmvE5index;
+local: *;
+};
+#+END_SRC
+
+Such a file can be passed to the linker via the =--version-script= argument and can be used to control which symbols the shared library should expose.
+For our /mixed/ build mode the generation of this script is realized as an additional Makefile target:
+
+#+BEGIN_SRC makefile
+build/olbcuda.version: $(CUDA_OBJ_FILES)
+ echo 'LIBOLBCUDA { global: ' > $@
+# Declare exposed explicitly instantiated symbols to prevent duplicate definitions by:
+# - filtering for the set of automatically instantiated classes
+# - excluding CPU_SISD symbols (we only instantiate GPU_CUDA-related symbols)
+# - dropping the shared library location information
+# - postfixing by semicolons
+ nm $(CUDA_OBJ_FILES) \
+ | grep '$(subst $() $(),\|,$(EXPLICIT_CLASS_INSTANTIATION))\|cuda.*device\|checkPlatform' \
+ | grep -wv '.*sisd.*' \
+ | cut -c 20- \
+ | sed 's/$$/;/' >> $@
+ echo 'local: *; };' >> $@
+#+END_SRC
+
+Note that we do not need to manually mangle the symbols in our =olbcuda.cu= but can simply read them from the library's object file using the =nm= utility.
+The two instances of =grep= are again the point where knowledge of the code base is inserted[fn:8].
+
+At this point all that is left is to link it all together using some final build targets:
+
+#+BEGIN_SRC makefile
+libolbcuda.so: $(CUDA_OBJ_FILES) build/olbcuda.version
+ $(CUDA_CXX) $(CUDA_CXXFLAGS) -Xlinker --version-script=build/olbcuda.version -shared $(CUDA_OBJ_FILES) -o $@
+
+$(EXAMPLE): $(OBJ_FILES) libolbcuda.so
+ $(CXX) $(OBJ_FILES) -o $@ $(LDFLAGS) -L . -lolbcuda -lolbcore $(CUDA_LDFLAGS)
+ #+END_SRC
+
+Here the shared library is compiled using the separately defined =CUDA_CXX= compiler and associated flags while the example case is compiled using =CXX=, realizing the required mixed compiler setup.
+For the final target we can now define a mode that only recompiles the main application while reusing the shared library:
+
+#+BEGIN_SRC makefile
+$(EXAMPLE)-no-cuda-recompile: $(OBJ_FILES)
+ $(CXX) $^ -o $(EXAMPLE) $(LDFLAGS) -L . -lolbcuda -lolbcore $(CUDA_LDFLAGS)
+
+.PHONY: no-cuda-recompile
+no-cuda-recompile: $(EXAMPLE)-no-cuda-recompile
+#+END_SRC
+
+While the initial compile of both the main CPU application and the GPU shared library any additional recompile using =make no-cuda-recompile= is sped up significantly.
+For example the following full compilation of a heterogeneous application with MPI, OpenMP, AVX-512 Vectorization on CPU and CUDA on GPU takes around 115 seconds:
+
+#+BEGIN_SRC bash
+λ ~/p/c/o/e/t/nozzle3d (openlb-env-gcc-openmpi-cuda-env) • time make
+mpic++ -O3 -Wall -march=native -mtune=native -std=c++17 -pthread -DPARALLEL_MODE_MPI -DPLATFORM_CPU_SISD -DPLATFORM_GPU_CUDA -DDEFAULT_FLOATING_POINT_TYPE=float -I../../../src -c -o nozzle3d.o nozzle3d.cpp
+mpic++ nozzle3d.o -lpthread -lz -ltinyxml -L../../../build/lib -lolbcore 2>&1 | grep -oP ".*undefined reference to \`\K[^']+\)" | sort | uniq > build/missing.txt
+nvcc -O3 -std=c++17 --generate-code=arch=compute_75,code=[compute_75,sm_75] --extended-lambda --expt-relaxed-constexpr -rdc=true -I../../../src -DPARALLEL_MODE_MPI -DPLATFORM_CPU_SISD -DPLATFORM_GPU_CUDA -Xcompiler -fPIC -c -o build/olbcuda.o build/olbcuda.cu
+nvcc -O3 -std=c++17 --generate-code=arch=compute_75,code=[compute_75,sm_75] --extended-lambda --expt-relaxed-constexpr -rdc=true -Xlinker --version-script=build/olbcuda.version -shared build/olbcuda.o -o libolbcuda.so
+mpic++ nozzle3d.o -o nozzle3d -lpthread -lz -ltinyxml -L../../../build/lib -L . -lolbcuda -lolbcore -L/run/opengl-driver/lib -lcuda -lcudadevrt -lcudart
+________________________________________________________
+Executed in 115.34 secs fish external
+ usr time 112.68 secs 370.00 micros 112.68 secs
+ sys time 2.68 secs 120.00 micros 2.68 secs
+#+END_SRC
+
+Meanwhile any additional compilation without introduction of new physical models (leading to the instantiation of additional GPU kernels) using =make no-cuda-recompile= takes /just/ 37 seconds:
+
+#+BEGIN_SRC bash
+λ ~/p/c/o/e/t/nozzle3d (openlb-env-gcc-openmpi-cuda-env) • time make no-cuda-recompile
+mpic++ -O3 -Wall -march=native -mtune=native -std=c++17 -pthread -DPARALLEL_MODE_MPI -DPLATFORM_CPU_SISD -DPLATFORM_GPU_CUDA -DDEFAULT_FLOATING_POINT_TYPE=float -I../../../src -c -o nozzle3d.o nozzle3d.cpp
+mpic++ nozzle3d.o -o nozzle3d -lpthread -lz -ltinyxml -L../../../build/lib -L . -lolbcuda -lolbcore -L/run/opengl-driver/lib -lcuda -lcudadevrt -lcudart
+________________________________________________________
+Executed in 36.47 secs fish external
+ usr time 35.71 secs 0.00 micros 35.71 secs
+ sys time 0.75 secs 564.00 micros 0.75 secs
+#+END_SRC
+
+This speedup of ~3 for most compiles during iterative development alone is worth the effort of introducing this new mode.
+Additionally, the logs also already showcase /mixed compilation/ as the CPU side of things is compiled using =mpic++= resp. GNU C++ while the shared libary is compiled using =nvcc=. This extends seamlessly to more complex setups combining MPI, OpenMP, AVX-512 vectorization on CPU and CUDA on GPU in a single application.
+
+[fn:5] Which spans various versions of GCC, Clang, Intel C++ and NVIDIA =nvcc=
+[fn:6] Momenta representing how to compute macroscopic quantities such as density and velocity, equilibrium representing the /undistrubed/ representation of said quantities in terms of population values and the collision operator representing the specific function used to /relax/ the current population towards this equilibrium. For more details on LBM see e.g. my articles on [[/article/fun_with_compute_shaders_and_fluid_dynamics/][Fun with Compute Shaders and Fluid Dynamics]], a [[/article/year_of_lbm/][Year of LBM]]
+or even my just-in-time visualized [[https://literatelb.org][literate implementation]].
+[fn:7] However, implementing such a explicit instantiation generator that works for any C++ project could be an interesting project for… somebody.
+[fn:8] Now that I write about it this could probably be modified to automatically and eliminate conflicts by only exposing the symbols that are missing from the main application
+
+** Conclusion
+All in all this approach turned out to be unexpectedly stable and portable accross systems and compilers from laptops to supercomputers.
+While it certainly is not the most beautiful thing I ever implemented, to say the least, it is very workable in practice and noticeably eases day to day development.
+In any case, the mixed compilation mode was included in [[https://www.openlb.net/news/openlb-release-1-6-available-for-download/][OpenLB release 1.6]] and has worked without a hitch since then.
+The mixed compilation mode is also isolated to just a few optional Makefile targets and did not require any changes to the actual codebase -- meaning that it can just quietly be dropped should a better solution for the requirements come along.
+
+For the potentially empty set of people that have read this far, are interested in CFD simulations using LBM and did not run screaming from the rather /pragmatic/ build solution presented here:
+If you want to spend a week learning about LBM theory and OpenLB practice from invited lecturers at the top of the field as well as my colleagues and me, our upcoming [[https://www.openlb.net/spring-school-2024/][Spring School]] may be of interest.
+Having taken place for quite a few years now at diverse locations such as Berlin, Tunisia, Krakow and Greenwich the 2024 rendition will take place at the historical /Heidelberger Akademie der Wissenschaften/ in March. I'd be happy to meet you there!
diff --git a/flake.lock b/flake.lock
index 6dc505e..54a7dc0 100644
--- a/flake.lock
+++ b/flake.lock
@@ -8,15 +8,15 @@
"locked": {
"lastModified": 1632774638,
"narHash": "sha256-hz6dPX6dSDKtjsIp90avvas42H0iftou1IVyXb8b4x4=",
- "ref": "master",
+ "ref": "refs/heads/master",
"rev": "45934403433b4359828a4ccbb3f611995a56e811",
"revCount": 252,
"type": "git",
- "url": "https://code.kummerlaender.eu/blog.kummerlaender.eu"
+ "url": "ssh://git@code.kummerlaender.eu/blog.kummerlaender.eu"
},
"original": {
"type": "git",
- "url": "https://code.kummerlaender.eu/blog.kummerlaender.eu"
+ "url": "ssh://git@code.kummerlaender.eu/blog.kummerlaender.eu"
}
},
"nixpkgs": {
diff --git a/flake.nix b/flake.nix
index 5241ae6..74fb096 100644
--- a/flake.nix
+++ b/flake.nix
@@ -3,7 +3,7 @@
inputs = {
stable.url = github:NixOS/nixpkgs/nixos-21.05;
- cms.url = git+https://code.kummerlaender.eu/blog.kummerlaender.eu;
+ cms.url = git+ssh://git@code.kummerlaender.eu/blog.kummerlaender.eu;
};
outputs = { self, stable, cms, ... }: {
diff --git a/tags/cpp/2021-10-11_reproducible_development_environment_teensy.org b/tags/cpp/2021-10-11_reproducible_development_environment_teensy.org
new file mode 120000
index 0000000..e2c6e77
--- /dev/null
+++ b/tags/cpp/2021-10-11_reproducible_development_environment_teensy.org
@@ -0,0 +1 @@
+../../articles/2021-10-11_reproducible_development_environment_teensy.org \ No newline at end of file
diff --git a/tags/cpp/2023-12-26_benefiting_from_deliberately_failing_linkage.org b/tags/cpp/2023-12-26_benefiting_from_deliberately_failing_linkage.org
new file mode 120000
index 0000000..aff1a44
--- /dev/null
+++ b/tags/cpp/2023-12-26_benefiting_from_deliberately_failing_linkage.org
@@ -0,0 +1 @@
+../../articles/2023-12-26_benefiting_from_deliberately_failing_linkage.org \ No newline at end of file
diff --git a/tags/development/2021-10-11_reproducible_development_environment_teensy.org b/tags/development/2021-10-11_reproducible_development_environment_teensy.org
new file mode 120000
index 0000000..e2c6e77
--- /dev/null
+++ b/tags/development/2021-10-11_reproducible_development_environment_teensy.org
@@ -0,0 +1 @@
+../../articles/2021-10-11_reproducible_development_environment_teensy.org \ No newline at end of file
diff --git a/tags/development/2023-12-26_benefiting_from_deliberately_failing_linkage.org b/tags/development/2023-12-26_benefiting_from_deliberately_failing_linkage.org
new file mode 120000
index 0000000..aff1a44
--- /dev/null
+++ b/tags/development/2023-12-26_benefiting_from_deliberately_failing_linkage.org
@@ -0,0 +1 @@
+../../articles/2023-12-26_benefiting_from_deliberately_failing_linkage.org \ No newline at end of file
diff --git a/tags/english/2021-10-11_reproducible_development_environment_teensy.org b/tags/english/2021-10-11_reproducible_development_environment_teensy.org
new file mode 120000
index 0000000..e2c6e77
--- /dev/null
+++ b/tags/english/2021-10-11_reproducible_development_environment_teensy.org
@@ -0,0 +1 @@
+../../articles/2021-10-11_reproducible_development_environment_teensy.org \ No newline at end of file
diff --git a/tags/english/2023-12-26_benefiting_from_deliberately_failing_linkage.org b/tags/english/2023-12-26_benefiting_from_deliberately_failing_linkage.org
new file mode 120000
index 0000000..aff1a44
--- /dev/null
+++ b/tags/english/2023-12-26_benefiting_from_deliberately_failing_linkage.org
@@ -0,0 +1 @@
+../../articles/2023-12-26_benefiting_from_deliberately_failing_linkage.org \ No newline at end of file
diff --git a/tags/nix/2018-06-23_nixos_gpu_assorted_topics.md b/tags/nix/2018-06-23_nixos_gpu_assorted_topics.md
new file mode 120000
index 0000000..1ae287a
--- /dev/null
+++ b/tags/nix/2018-06-23_nixos_gpu_assorted_topics.md
@@ -0,0 +1 @@
+../../articles/2018-06-23_nixos_gpu_assorted_topics.md \ No newline at end of file
diff --git a/tags/nix/2021-10-11_reproducible_development_environment_teensy.org b/tags/nix/2021-10-11_reproducible_development_environment_teensy.org
new file mode 120000
index 0000000..e2c6e77
--- /dev/null
+++ b/tags/nix/2021-10-11_reproducible_development_environment_teensy.org
@@ -0,0 +1 @@
+../../articles/2021-10-11_reproducible_development_environment_teensy.org \ No newline at end of file