From 99439bb1a41d49814ed5d1b8994979cb6161b5df Mon Sep 17 00:00:00 2001 From: s-celles Date: Wed, 13 May 2026 18:08:24 +0200 Subject: [PATCH] gen: default GIAC_TYPE_ON_8BITS=1 to fix cross-GCC ABI mismatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The historical bitfield layout of class gen: unsigned char type:5; unsigned char type_unused:3; signed char subtype; unsigned short reserved; is laid out and ACCESSED differently across GCC versions. GCC fuses adjacent bitfield writes (g.type = ...; g.subtype = ...;) into a single wider store using version-dependent bit placements: GCC 8 and GCC 10 produce instruction sequences that disagree on which bits of the resulting word hold `type`. This isn't theoretical. In the libgiac_julia / Giac.jl stack on Windows: - GIAC_jll is built with BinaryBuilder GCC 8 - libgiac_julia_jll (the CxxWrap wrapper) is GCC 10 - When libgiac writes a gen tagged _REAL (3) and the wrapper reads it, the wrapper sees _DOUBLE_ (1) instead. A C++ probe (https://github.com/s-celles/libgiac-julia-wrapper/pull/5) ran on Linux/macOS/Windows in both modes confirmed: with the bitfield, GCC's optimizer fuses writes; the byte layout is correct ONLY when libgiac and consumer use the same GCC. With GIAC_TYPE_ON_8BITS, type is a plain `unsigned char` at offset 0 — no bitfield, no fusion, every compiler emits the same simple byte store/load. The ABI becomes compiler-invariant. Cost: 3 mantissa bits on inline doubles encoded in a gen (48 → 45). sizeof(gen) is unchanged at 64 bits. Per giac's author this is the historical default and the safer cross-compiler path. Override by removing this block in dispatch.h if you need the legacy layout. Fixes: https://github.com/s-celles/Giac.jl/pull/22 Refs: https://github.com/s-celles/libgiac-julia-wrapper/pull/5 --- src/dispatch.h | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/dispatch.h b/src/dispatch.h index cf5eb8b6..04b68ff7 100644 --- a/src/dispatch.h +++ b/src/dispatch.h @@ -23,8 +23,39 @@ namespace giac { #endif // ndef NO_NAMESPACE_GIAC -#if !defined DOUBLEVAL && (defined __amd64 || defined x86_64) && !defined SMARTPTR64 +#if !defined DOUBLEVAL && (defined __amd64 || defined x86_64) && !defined SMARTPTR64 #define DOUBLEVAL 1 +#endif + +// Default the `type` field of class gen to a full 8-bit byte rather +// than the historical `unsigned char type:5; unsigned char type_unused:3;` +// bitfield (see gen.h). +// +// Rationale: the bitfield form is laid out and accessed in slightly +// different ways across GCC versions — the compiler fuses adjacent +// bitfield writes (type + subtype) using version-dependent bit +// placements. When libgiac and a consumer library are compiled with +// different GCC versions, their views of which bits hold `type` can +// disagree, producing a real, user-visible ABI bug: in the +// libgiac_julia / Giac.jl stack on Windows, MPFR _REAL gens come +// back tagged as _DOUBLE_, breaking float() and conversions. +// +// Switching to a full byte costs ~3 bits of mantissa on inline +// doubles stored within a gen (sizeof(gen) is unchanged at 64 bits; +// the inline float field gives up 3 bits to the wider type tag). +// Bounded, harmless, and the historical default for gen.h before +// the bitfield was introduced. Recommended by giac's author for +// portability. +// +// The #ifndef guard lets a build that already pre-defines the macro +// (e.g. via -DGIAC_TYPE_ON_8BITS=1 on the command line or in a +// generated config.h) keep its own definition. To opt back into the +// legacy bitfield layout, remove this block. +// +// See https://github.com/s-celles/libgiac-julia-wrapper/pull/5 +// https://github.com/s-celles/Giac.jl/pull/22 +#ifndef GIAC_TYPE_ON_8BITS +#define GIAC_TYPE_ON_8BITS 1 #endif enum {