From cba7a9521a3a2fb64df4cd22b7924d5204d896ef Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Wed, 26 Aug 2026 22:14:34 +0200 Subject: [PATCH 1/5] CI: Update to ubuntu-26.04 This solves an issue with po4a that does not translates all the documents. However, some build jobs fail now and will be fixed in the following commits. --- .github/workflows/ci.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72b2bdca727..db4c1afee33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ env: jobs: rip-and-test: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 45 steps: - name: Dump GitHub context @@ -57,7 +57,7 @@ jobs: .github/scripts/verify-clean-repo.sh rip-headless: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 45 steps: - name: Dump GitHub context @@ -109,7 +109,7 @@ jobs: .github/scripts/verify-clean-repo.sh rip-rtai: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 45 steps: - name: Dump GitHub context @@ -138,7 +138,7 @@ jobs: .github/scripts/verify-clean-repo.sh rip-and-test-clang: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 45 steps: - name: Dump GitHub context @@ -172,7 +172,7 @@ jobs: .github/scripts/verify-clean-repo.sh cppcheck: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 45 steps: - name: Checkout repository @@ -192,7 +192,7 @@ jobs: scripts/cppcheck.sh shellcheck: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 45 steps: - name: Checkout repository @@ -215,7 +215,7 @@ jobs: scripts/shellcheck.sh htmldocs: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 60 steps: - name: Dump GitHub context @@ -241,15 +241,15 @@ jobs: run: scripts/manpage-name-check.py --enforce docs/build/man - name: Check for derived-id section references run: scripts/docs-anchor-check.py --enforce - - name: Verify no untracked or modified files after build - run: | - #*.po and documentation.pot are modifyed by build. Ignore them for now. - .github/scripts/verify-clean-repo.sh ':(exclude)docs/po/*.po' ':(exclude)docs/po/documentation.pot' - name: HTML checks run: | set -x #-w sets warn only, remove to generate a CI failure on error scripts/htmlcheck.sh -w + - name: Verify no untracked or modified files after build + run: | + #*.po and documentation.pot are modified by build. Ignore them for now. + .github/scripts/verify-clean-repo.sh ':(exclude)docs/po/*.po' ':(exclude)docs/po/documentation.pot' - name: Tar linuxcnc-doc run: | set -x @@ -267,7 +267,7 @@ jobs: timeout-minutes: 75 strategy: matrix: - runner: ["ubuntu-24.04", "ubuntu-24.04-arm"] + runner: ["ubuntu-26.04", "ubuntu-26.04-arm"] image: ["debian:bookworm", "debian:trixie", "debian:sid"] include: - allow_fail: False @@ -357,7 +357,7 @@ jobs: if-no-files-found: error package-indep: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 75 strategy: matrix: @@ -410,7 +410,7 @@ jobs: .github/scripts/build-package-indep.sh - name: Verify no untracked or modified files after build run: | - #*.po and documentation.pot are modifyed by build. Ignore them for now. + #*.po and documentation.pot are modified by build. Ignore them for now. .github/scripts/verify-clean-repo.sh ':(exclude)VERSION' ':(exclude)debian/changelog' ':(exclude)docs/po/*.po' ':(exclude)docs/po/documentation.pot' - name: Install debian packages run: | @@ -447,7 +447,7 @@ jobs: if: (github.event_name == 'release' && github.event.action == 'published') || startsWith(github.ref, 'refs/tags/') permissions: contents: write - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - name: Download artifacts uses: actions/download-artifact@v8 From fcab10fbdb3e7a7d71bc7c3cca7cabf6fc3b7f25 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Wed, 26 Aug 2026 23:13:58 +0200 Subject: [PATCH 2/5] CI: Update to ubuntu-26.04: cppcheck cppcheck passes now. Replace #ifdef AAAAA with #if 0 so cppcheck does not check unused code. "make headers" before cppckeck, so include is populated. --- .github/scripts/build-headers.sh | 11 +++++++++++ .github/workflows/ci.yml | 7 +++++-- src/emc/rs274ngc/gcodemodule.cc | 2 +- src/emc/sai/driver.cc | 2 +- src/hal/classicladder/classicladder_gtk.c | 2 +- src/hal/classicladder/drawing.c | 2 +- src/hal/classicladder/vars_names.c | 2 +- 7 files changed, 21 insertions(+), 7 deletions(-) create mode 100755 .github/scripts/build-headers.sh diff --git a/.github/scripts/build-headers.sh b/.github/scripts/build-headers.sh new file mode 100755 index 00000000000..da4b339d46c --- /dev/null +++ b/.github/scripts/build-headers.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -eu #Needed so CI fails when anything is wrong +set -x + +#Any arguments are passed to configure + +cd src +./autogen.sh +./configure "$@" --disable-check-runtime-deps --enable-werror +make headers diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db4c1afee33..09d9d18b36f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -183,9 +183,12 @@ jobs: - name: Install dependencies run: | set -x - .github/scripts/use-main-ubuntu-mirror.sh - sudo apt-get -q update + .github/scripts/install-deps.sh sudo apt-get --yes install cppcheck + - name: Build headers + run: | + set -x + .github/scripts/build-headers.sh --with-realtime=uspace - name: Cppcheck run: | set -x diff --git a/src/emc/rs274ngc/gcodemodule.cc b/src/emc/rs274ngc/gcodemodule.cc index 3b15edea612..86772706c03 100644 --- a/src/emc/rs274ngc/gcodemodule.cc +++ b/src/emc/rs274ngc/gcodemodule.cc @@ -374,7 +374,7 @@ class MoveBatch { // have still been handed over once, and re-delivering them from a later // flush would duplicate them in the consumer's program. count_ = 0; - PyObject *view = PyMemoryView_FromMemory((char*)buf_, + PyObject *view = PyMemoryView_FromMemory(reinterpret_cast(buf_), (Py_ssize_t)n * ROW * sizeof(double), PyBUF_READ); if(!view) { interp_error ++; return; } PyObject *result = callmethod(callback, "move_batch", "O", view); diff --git a/src/emc/sai/driver.cc b/src/emc/sai/driver.cc index b0275236c3a..5f4ad25aa81 100644 --- a/src/emc/sai/driver.cc +++ b/src/emc/sai/driver.cc @@ -564,7 +564,7 @@ int main (int argc, char ** argv) go_flag = 0; #ifdef TOOL_NML //{ - tool_nml_register((CANON_TOOL_TABLE*)& _sai._tools); + tool_nml_register(& _sai._tools); #else //}{ const int random_toolchanger = 0; // sai gets its OWN mmap. tool_mmap_creator() opens the file O_TRUNC, and it diff --git a/src/hal/classicladder/classicladder_gtk.c b/src/hal/classicladder/classicladder_gtk.c index b78690638d5..85a46c33a6b 100644 --- a/src/hal/classicladder/classicladder_gtk.c +++ b/src/hal/classicladder/classicladder_gtk.c @@ -1059,7 +1059,7 @@ void MainSectionWindowInitGtk() //no more used since menu/toolbar added... -#ifdef AAAAAAAAAAAAAAAAAAAAAA +#if 0 hboxbottom = gtk_hbox_new (FALSE,0); gtk_container_add (GTK_CONTAINER (vbox), hboxbottom); gtk_widget_show(hboxbottom); diff --git a/src/hal/classicladder/drawing.c b/src/hal/classicladder/drawing.c index 9a6f65b363f..35d24d39b01 100644 --- a/src/hal/classicladder/drawing.c +++ b/src/hal/classicladder/drawing.c @@ -352,7 +352,7 @@ void DrawElement( cairo_t * cr,int x,int y,int Width,int Height,StrElement Eleme char DisplayColorState = ( DrawingOption==DRAW_NORMAL && !EditDatas.ModeEdit && InfosGene->LadderState==STATE_RUN ); //Cairo.... -#ifdef AAAAAAAAAAAAA +#if 0 GdkGC * DynaGcOff; GdkGC * TheGc; GdkColor DynaGdkColor; diff --git a/src/hal/classicladder/vars_names.c b/src/hal/classicladder/vars_names.c index 97f569ea617..6fae792db71 100644 --- a/src/hal/classicladder/vars_names.c +++ b/src/hal/classicladder/vars_names.c @@ -42,7 +42,7 @@ //===OLD CODE NO MORE USED=== //with names directly in it... //to be deleted, but some precise errors codes aren't present in the new function... -#ifdef AAAAAAAAA +#if 0 char * CreateVarName(int Type, int Offset) { From 03c74e4f63c8d7241d049ca20043d5f29d2b7cd2 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Wed, 26 Aug 2026 23:21:55 +0200 Subject: [PATCH 3/5] CI: Update to ubuntu-26.04: clang build clang needs -latomic: See https://en.cppreference.com/cpp/atomic/atomic Quote: On GCC and Clang, some of the functionality described here requires linking against -latomic. --- src/rtapi/Submakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rtapi/Submakefile b/src/rtapi/Submakefile index bb36cc34260..87890fbcae6 100644 --- a/src/rtapi/Submakefile +++ b/src/rtapi/Submakefile @@ -14,7 +14,7 @@ $(call TOOBJSDEPS, $(RTAPI_APP_SRCS)): EXTRAFLAGS += -DSIM \ -UULAPI -DRTAPI -pthread ../bin/rtapi_app: $(call TOOBJS, $(RTAPI_APP_SRCS)) $(ECHO) Linking $(notdir $@) - $(Q)$(CXX) -rdynamic -o $@ $^ $(LIBDL) -pthread -lrt -lfmt $(LIBUDEV_LIBS) -ldl -lcap $(LDFLAGS) + $(Q)$(CXX) -rdynamic -o $@ $^ $(LIBDL) -pthread -lrt -lfmt -latomic $(LIBUDEV_LIBS) -ldl -lcap $(LDFLAGS) TARGETS += ../bin/rtapi_app USPACE_POSIX_SRCS := rtapi/uspace_posix.cc From 9285f9b78544853bd89736e412b8ce0c3814fd49 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Fri, 28 Aug 2026 11:01:08 +0800 Subject: [PATCH 4/5] interp_g7x: build with -ffp-contract=off, fix latent geometry bugs The g7x offsetting code takes discrete threshold branches on iterated floating-point values (segment deletion at radius<1e-3, monotonicity, arc insertion at gap>1e-2), so a 1-ulp rounding difference can flip a branch and diverge the whole toolpath. GCC defaults to -ffp-contract=fast; when targeting x86-64-v3 (ubuntu-26.04 CI runner gcc default, --with-arch-64=x86-64-v3) this emits FMA instructions and changes rounding, failing g71-with-g70 and g71-endless-loop_2 in CI. Verified: rebuilding only interp_g7x.o with -march=x86-64-v3 fails, -ffp-contract=off passes; -O0/-O2/-O3/arch without FMA are all byte-identical, so contraction is the only variable. Disable FP contraction for interp_g7x.o (same semantics as ISO C's default). Investigating the divergence also exposed latent bugs that the flipped branches merely reach: - erase(p--) on a list iterator at begin() is undefined behavior; in practice it ends the loop early, skipping offsets for all remaining segments. Use the p=erase(p) idiom (two sites). - round_segment::intersect_end(round_segment*) silently clamped |cosB|>1 (non-intersecting circles) and set p's end to a point on the wrong circle, producing arcs the interpreter itself rejects. Trim to the closest points on the line of centers instead. - the add_distance() accumulation loop requires exact FP equality; under some roundings the step becomes too small to change the sum and the loop never terminates. Break when the sum stops changing. - the add_distance() junction snap moves arc endpoints off their circle; refuse to snap across a gap larger than 1e-3 instead of manufacturing an invalid arc. All fixes keep byte-identical output on the g71 tests with the baseline arch; they only change behavior in cases that previously produced undefined or geometrically invalid results. --- src/emc/rs274ngc/Submakefile | 7 +++ src/emc/rs274ngc/interp_g7x.cc | 89 ++++++++++++++++++++-------------- 2 files changed, 60 insertions(+), 36 deletions(-) diff --git a/src/emc/rs274ngc/Submakefile b/src/emc/rs274ngc/Submakefile index 28e974a36c5..5dc003d1557 100644 --- a/src/emc/rs274ngc/Submakefile +++ b/src/emc/rs274ngc/Submakefile @@ -35,6 +35,13 @@ LIBRS274SRCS += emc/nml_intf/modal_state.cc USERSRCS += $(LIBRS274SRCS) $(call TOOBJSDEPS, $(LIBRS274SRCS)) : EXTRAFLAGS+=-fPIC $(BOOST_DEBUG_FLAGS) + +# interp_g7x.cc takes discrete threshold branches on iterated floating-point +# values; FMA contraction (GCC default -ffp-contract=fast when targeting +# x86-64-v3, as on the ubuntu-26.04 CI runners) changes rounding enough to +# flip branches and diverge the G71 toolpath. Disable contraction for this +# file so codegen is independent of the compiler default arch. +$(call TOOBJS, emc/rs274ngc/interp_g7x.cc): EXTRAFLAGS += -ffp-contract=off $(call TOOBJSDEPS, emc/rs274ngc/pyinterp1.cc emc/rs274ngc/pyparamclass.cc emc/rs274ngc/pyinterp1.cc emc/rs274ngc/pyblock.cc emc/rs274ngc/pyarrays.cc emc/rs274ngc/interpmodule.cc): EXTRAFLAGS += $(SILENCE_BOOST_INTERNAL_DIAGNOSTICS_FLAGS) TARGETS += ../lib/librs274.so ../lib/librs274.so.0 diff --git a/src/emc/rs274ngc/interp_g7x.cc b/src/emc/rs274ngc/interp_g7x.cc index 32daff532cf..177907c5d7d 100644 --- a/src/emc/rs274ngc/interp_g7x.cc +++ b/src/emc/rs274ngc/interp_g7x.cc @@ -114,7 +114,7 @@ class straight_segment:public segment { void climb_only(std::complex&,motion_base*) override; void draw(motion_base *out) override { out->straight_move(end); } void offset(double distance) override { - std::complex d=I*distance*(start-end)/abs(start-end); + std::complex d=I*distance*(start-end)/std::abs(start-end); start+=d; end+=d; } @@ -124,7 +124,7 @@ class straight_segment:public segment { std::unique_ptr dup() override { return std::make_unique(*this); } - double radius() override { return abs(start-end); } + double radius() override { return std::abs(start-end); } }; void straight_segment::intersection_z(double x, intersections_t &is) @@ -146,7 +146,7 @@ bool straight_segment::climb(std::complex &location, ) { if(end.imag()+tolerancetolerance) + if(std::abs(location-start)>tolerance) throw(std::string("How did we get here?")); output->straight_move(end); location=end; @@ -218,8 +218,8 @@ class round_segment:public segment { void climb_only(std::complex&,motion_base*) override; void draw(motion_base *out) override { out->circular_move(ccw,center,end);} void offset(double distance) override { - double factor=(abs(start-center)+(ccw? 1:-1)*distance) - /abs(start-center); + double factor=(std::abs(start-center)+(ccw? 1:-1)*distance) + /std::abs(start-center); start=factor*(start-center)+center; end=factor*(end-center)+center; } @@ -230,7 +230,7 @@ class round_segment:public segment { std::unique_ptr dup() override { return std::make_unique(*this); } - double radius() override { return std::min(abs(start-end),abs(start-center)); } + double radius() override { return std::min(std::abs(start-end),std::abs(start-center)); } void flip_imag() override { ccw=!ccw; start=conj(start); end=conj(end); center=conj(center); } void flip_real() override { ccw=!ccw; start=-conj(start); @@ -271,7 +271,7 @@ inline bool round_segment::on_segment(std::complex p) void round_segment::intersection_z(double x, intersections_t &is) { std::complex r=start-center; - double s=-(x-abs(r)-center.imag())*(x+abs(r)-center.imag()); + double s=-(x-std::abs(r)-center.imag())*(x+std::abs(r)-center.imag()); if(s<-tolerance) return; if(s<0) @@ -291,7 +291,7 @@ bool round_segment::climb(std::complex &location, if(!ccw) { // G2 if(location.real()>center.real()) return 1; - if(abs(location-end)>1e-3) + if(std::abs(location-end)>1e-3) output->circular_move(ccw,center,end); location=end; return 0; @@ -301,9 +301,9 @@ bool round_segment::climb(std::complex &location, std::complex ep=end; if(end.real()1e-3) + if(std::abs(location-ep)>1e-3) output->circular_move(ccw,center,ep); location=ep; return 1; @@ -313,7 +313,7 @@ bool round_segment::climb(std::complex &location, bool round_segment::dive(std::complex &location, double x, motion_base *output, bool ) { - if(abs(location-end) &location, return 0; } else if(is.front() &location, return; location.real(is.back()); output->straight_move(location); - if(abs(location-end)>1e-3) + if(std::abs(location-end)>1e-3) output->circular_move(ccw,center,end); location=end; return; @@ -382,9 +382,9 @@ void round_segment::climb_only(std::complex &location, std::complex ep=end; if(end.real()1e-3) + if(std::abs(location-ep)>1e-3) output->circular_move(ccw,center,ep); location=ep; return; @@ -406,7 +406,7 @@ void round_segment::intersect(segment *p) void straight_segment::intersect_end(straight_segment *p) { // correct end of p and start of this - auto rot=conj(start-end)/abs(start-end); + auto rot=conj(start-end)/std::abs(start-end); auto ps=(p->start-end)*rot; auto pe=(p->end-end)*rot; if(imag(ps-pe)==0) { @@ -421,10 +421,10 @@ void straight_segment::intersect_end(round_segment *p) { // correct end of p and start of this // (arc followed by a straight - if(abs(start-p->end)end)end-end)*rot; auto pc=(p->center-end)*rot; @@ -437,7 +437,7 @@ void straight_segment::intersect_end(round_segment *p) auto s1=(real(pc)+b)/rot+end; auto s2=(real(pc)-b)/rot+end; - if(abs(start-s1)end=s1; else start=p->end=s2; @@ -447,10 +447,10 @@ void round_segment::intersect_end(straight_segment *p) { // correct end of p and start of this // (straight followed by an arc) - if(abs(start-p->end)end)start-p->end)/abs(p->start-p->end); + auto rot=conj(p->start-p->end)/std::abs(p->start-p->end); auto pe=(end-p->end)*rot; auto pc=(center-p->end)*rot; @@ -463,7 +463,7 @@ void round_segment::intersect_end(straight_segment *p) auto s1=(real(pc)+b)/rot+p->end; auto s2=(real(pc)-b)/rot+p->end; - if(abs(start-s1)end=s1; else start=p->end=s2; @@ -472,13 +472,19 @@ void round_segment::intersect_end(straight_segment *p) void round_segment::intersect_end(round_segment *p) { // correct end of p and start of this - auto a=abs(start-center); - auto b=abs(p->start-p->center); - auto c=abs(center-p->center); + auto a=std::abs(start-center); + auto b=std::abs(p->start-p->center); + auto c=std::abs(center-p->center); auto cosB=(c*c+a*a-b*b)/2.0/a/c; + if(std::abs(cosB)>1) { + // circles do not intersect: trim to the closest points on the + // line of centers so each end stays on its own circle + auto u=(p->center-center)/c; + p->end=p->center-b*u; + start=center+a*u; + return; + } double cosB2=cosB*cosB; - if(cosB2>1) - cosB2=1; std::complex rot(cosB,sqrt(1-cosB2)); auto is=rot*(p->center-center)/c*a+center; p->end=start=is; @@ -623,9 +629,11 @@ class g7x:public std::list> { auto p(h); --p; (*h)->do_finish((*p).get(),(*(++h)).get()); } - for(auto p=begin(); p!=end(); ++p) + for(auto p=begin(); p!=end();) if((*p)->radius()<1e-3) - erase(p--); + p=erase(p); + else + ++p; } bool should_rotate_paths() { @@ -811,7 +819,7 @@ void g7x::pocket(int cycle, std::complex location, iterator p, if(p==ip) // Hitting the diving curve at the starting point. continue; - else if(abs(location-(*ip)->sp())sp())offset(max_distance); - if((*p)->radius()<1e-3) - erase(p--); - } + for(auto p=begin(); p!=end();) { + (*p)->offset(max_distance); + if((*p)->radius()<1e-3) + p=erase(p); + else + ++p; + } for(auto p=begin(); p!=--end(); ++p) { auto n=p; ++n; @@ -896,10 +906,17 @@ void g7x::add_distance(double distance) { } else (*p)->intersect(n->get()); } - current_distance+=max_distance; + double previous_distance=current_distance; + current_distance+=max_distance; + if(current_distance==previous_distance) + break; // remaining step below FP resolution; avoid endless loop } for(auto p=begin(); p!=--end(); ++p) { auto n=p; ++n; + auto gap=std::abs((*p)->ep()-(*n)->sp()); + if(gap>1e-3) + throw(std::string("Profile junction gap too large: ") + +to_string((*p)->ep())); auto mid=((*p)->ep()+(*n)->sp())/2.0; (*p)->ep()=(*n)->sp()=mid; } From 40e4fa88a14e9bf583987f9414ceb757f5694246 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Fri, 28 Aug 2026 18:01:23 +0800 Subject: [PATCH 5/5] interp_g7x: epsilon exit in add_distance accumulation loop The exact-equality termination misses the LSB-crawl case, where current_distance advances by one ulp per iteration and never compares equal. Exit on std::abs(distance-current_distance)<=1e-9 instead; the stall break stays as a backstop. Output bit-identical on the g71 tests. Noticed by BsAtHome. --- src/emc/rs274ngc/interp_g7x.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emc/rs274ngc/interp_g7x.cc b/src/emc/rs274ngc/interp_g7x.cc index 177907c5d7d..b1f13337280 100644 --- a/src/emc/rs274ngc/interp_g7x.cc +++ b/src/emc/rs274ngc/interp_g7x.cc @@ -858,7 +858,7 @@ void g7x::add_distance(double distance) { auto of(std::move(front())); pop_front(); double current_distance=0; - while(current_distance!=distance) { + while(std::abs(distance-current_distance)>1e-9) { double max_distance=1e9; for(auto &p : *this) max_distance=std::min(max_distance,p->radius()/2);