Using Gauche in GitHub Actions
I created a GitHub action to install Gauche in the runner, so that you can use Gauche in subsequent steps: setup-gauche. Currently the action works on Linux and OSX.
To use the action, simply say
uses: shirok/setup-gauche@v3 in your job steps (check the latest version number in the setup-gauche page). The following is an excerpt of
.github/workflow/main.yml of Gauche-mecab:
jobs: build-and-test-linux: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v3 - uses: shirok/setup-gauche@v3 - name: Install dependencies run: | sudo apt install -y libmecab-dev mecab-ipadic-utf8 - name: Build and check run: | ./configure make make -s check make -s check-dep
Gauche is installed in standard path (
/usr on Linux,
/usr/local on OSX) so that you can build Gauche extensions or run Gauche applications without any extra settings.
By default, it installs the latest release. You can choose a specific version of Gauche to install via
gauche-version input parameter; specifically, saying 'snapshot' installs the latest snapshot (prerelease) build, if there's any newer than the latest release.
Running gosh without installing
Recently I wrote some test scripts in Gauche for a project that didn't use Gauche in particular. I could've kicked get-gauche script during
make check to install Gauche locally as needed, but that seemed a bit of overkill, especially it was just for small test scripts.
Then I thought, well, I already have a Docker image. If I can feed a local script to it...
So here it is. I included in a Docker image a small script
which chdirs into
/home/app and run gosh. If you mount local cwd on
/home/app, the scripts, libraries and data in it are all visible to
gosh in the Docker:
docker run --rm -ti -v `pwd`:/home/app practicalscheme/gauche gosh-script TEST-SCRIPT
Or, you can use run-gosh-in-docker.sh script.
You can't acceses local resources other than the filesystem below the current directory, and you can't use extra libraries. But for the simple tasks this is enough.
See README in Gauche-docker-image for the details.
Is this an Undefined Behavior?
Automated tests of Gauche HEAD on Windows platform started failing since several days ago. The log showed SHA1 digest result didn't match. It's weird, for I haven't touched that part of code for long time.
I isolated the reproducible condition. It happens with the fairly
new gcc (11.2.0) with
-O2. It doesn't exhibit without optimization,
nor with gcc 10.2.0 or other previous versions of gcc I have.
The problematic code is Aaron D. Gifford's SHA implementation sha2.c ( http://www.aarongifford.com/computers/sha.html ). It was last updated in January 2004, so it's pretty old, but I think it's still widely used.
I narrowed down the problem to around here:
/* Set the bit count: */ #if BYTE_ORDER == LITTLE_ENDIAN /* Convert FROM host byte order */ REVERSE64(context->s1.bitcount,context->s1.bitcount); #endif void *buf56 = &context->s1.buffer; *(sha_word64*)buf56 = context->s1.bitcount; /* Final transform: */ SHA1_Internal_Transform(context, (sha_word32*)context->s1.buffer);
In our case,
is a macro to swap the byte order of a 64bit word.
context->s1.buffer is an array of unsigned chars.
What it does is to store 64bit-word of
bitcount into the
from 56th octet in the network byte order, and calls
It compiles to this code with optimization:
25ca75e9d: 48 8b 53 18 mov 0x18(%rbx),%rdx 25ca75ea1: 48 0f ca bswap %rdx 25ca75ea4: 48 89 53 18 mov %rdx,0x18(%rbx) 25ca75ea8: 48 89 d9 mov %rbx,%rcx 25ca75eab: 4c 89 e2 mov %r12,%rdx 25ca75eae: e8 8d fa ff ff call 25ca75940 <SHA1_Internal_Transform>
%rbx contains the pointer to
context->s1.buffer. The first three instructions swap the
64bit word. (By the way,
REVERSE64 macro is written with shifts and
bitmasks. Gcc cleverly figures out its intent and
replaces the whole expression by a
The next three instruction is the calling sequence of
Wait. There're no instructions emitted to store
*buf56. I checked the assembly after this but there're no
instructions for the store either.
If I insert a dummy external function call before
SHA1_Internal_Transform like this:
/* Set the bit count: */ #if BYTE_ORDER == LITTLE_ENDIAN /* Convert FROM host byte order */ REVERSE64(context->s1.bitcount,context->s1.bitcount); #endif void *buf56 = &context->s1.buffer; *(sha_word64*)buf56 = context->s1.bitcount; puts("foo"); /* Final transform: */ SHA1_Internal_Transform(context, (sha_word32*)context->s1.buffer);
Then the storing to
*buf56 appears (
mov %rdx, 0x58(%rbx)):
25ca75e9d: 48 8b 53 18 mov 0x18(%rbx),%rdx 25ca75ea1: 48 0f ca bswap %rdx 25ca75ea4: 48 89 53 18 mov %rdx,0x18(%rbx) 25ca75ea8: 48 8d 0d 8f c7 00 00 lea 0xc78f(%rip),%rcx # 25ca8263e <.rdata+0x9e> 25ca75eaf: 48 89 53 58 mov %rdx,0x58(%rbx) 25ca75eb3: e8 60 2e 00 00 call 25ca78d18 <puts> 25ca75eb8: 4c 89 e2 mov %r12,%rdx 25ca75ebb: 48 89 d9 mov %rbx,%rcx 25ca75ebe: e8 7d fa ff ff call 25ca75940 <SHA1_Internal_Transform>
Now, accessing type punned pointer can break the strict aliasing rule.
The gcc might have figured the storing
*buf56 had nothing to do with
But I feel there still needs to be
a leap that it completely eliminates the store instruction.
*(sha_word64*)buf56 = context->s1.bitcount triggers
Undefined Behavior? That's why gcc is entitled to remove that code?
Better test failure report
I keep Gauche's test framework ref:gauche.test intentionally simple--a test evaluates a given expression and compares its result with the expected result; if they don't agree, reports it. That's all.
It doesn't have fancy knobs and dials, but it does the job. Fancy features can be written using Gauche's other features; e.g. if you need setup/teardown, you can just wrap tests with
I prefer this kind of explicit code to fat frameworks in which you need to track down its documents and (sometimes) implementation to know what exactly is done.
However, there has been one frustration: I can't easily change how the test failure is reported. Especially, when a test yields a large amount of results and it doesn't agree with expected one, it is hard to tell where is the difference, by looking at the entire expected and actual results.
Now I can have it. See the following test:
(test* "Beatrice" ;; expected '("What fire is in mine ears? Can this be true?" "Stand I condemned for pride and scorn so much?" "Contempt, farewell, and maiden pride, adieu!" "No glory lives behind the back of such.") ;; actual "What fire is in mine ears? Can this be true?\n\ Stand I condemn'd for pride and scorn so much?\n\ Contempt, farewell! and maiden pride, adieu!\n\ No glory lives behind the back of such.\n" test-check-diff ; check test-report-failure-diff) ; report
The expected text and the actual text have slight difference. This reports the difference in unified diff format.
ERROR: GOT diffs: --- expected +++ actual @@ -1,4 +1,4 @@ What fire is in mine ears? Can this be true? -Stand I condemned for pride and scorn so much? -Contempt, farewell, and maiden pride, adieu! +Stand I condemn'd for pride and scorn so much? +Contempt, farewell! and maiden pride, adieu! No glory lives behind the back of such.
The third argument of
test* is to compare the expected and
actual result. If you prepare expected text in one big string,
you can just use the default one;
test-check-diff adds a bit
of convenience by accepting a few different formats.
The fourth argument is the main addition. It accepts a report
proceudre which is called when the expected result and the actual
result didn't match, with three arguments, **message**,
**expected-result** and **acutual-result**. The **message**
argument is the first argument passed to
module to display the difference of the results in diff format
You can customize reporting as you wish. Another custom reporting we'd like to have is to show difference of tree structures.
Please refer to the manual for the details. (Before releasing 0.9.11, you can view the draft document.
Gauche supports IEEE754 negative zero -0.0. It simply wraps an IEEE754 double as a scheme object, so mostly it just works as specified in IEEE754 (and as supported by the underlying math library). Or, so we thought.
Let's recap the behavior of -0.0. It's numerically indistinguishable from 0.0 (so, it is not "an infinitely small value less than zero"):
(= -0.0 0.0) ⇒ #t (< -0.0 0.0) ⇒ #f (zero? -0.0) ⇒ #t
But it can make a difference when there's a functon f(x) such that it is discontinuous at x = 0, and f(x) goes to different values when x approaches to zero from positive side or negative side.
(/ 0.0) ⇒ +inf.0 (/ -0.0) ⇒ -inf.0
For arithmetic primitive procedures, we simply pass unboxed double to the underlying math functions, so we didn't think we need to handle -0.0 specially.
The first wakeup call was this article via HackerNews:
It talks about writing
abs in Java, but every time I saw articles like
this I just try it out on Gauche, and alas!
;; Gauche 0.9.10 (abs -0.0) ⇒ -0.0 ; Ouch!
Yeah, the culprit was the C implementation of
abs, the gist of which was:
if (x < 0.0) return -x; else return x;
-0.0 doesn't satisfy
x < 0.0 so it was returned without negation.
The easy fix is to use
if (signbit(x)) return -x;
I reported the fix on Twitter, then somebody raised an issue: What about
(eqv? -0.0 0.0)?
My initial reaction was that it should be
(= -0.0 0.0)
#t. In fact, R5RS states this:
#tif: ... obj1 and obj2 are both numbers, are numerically equal (see
=...), and are either both exact or both inexact.
However, I realized that R7RS has more subtle definition.
#fif: ... obj1 and obj2 are both inexact numbers such that either they are numerically unequal (in the sense of
=), or they do not yield the same results (...) when passed as arguments to any other procedure that can be defined as a finite composition of Scheme's standard arithmetic procedures, ...
Clearly, -0.0 and 0.0 don't yield the same results when passed to
so it should return
#f. (It is also mentioned in 6.2.4
that -0.0 is distinct from 0.0 in a sense of
Fix for this is a bit involved. When I fixed
eqv?, a bunch of
tests started failing. It looks like some
inexact integer division routines in the tests yield -0.0, and
are compared to 0.0 with
equal?, which should follow
if arguments are numbers.
It turned out that the root cause was rounding primitives returning -0.0:
;; Gauche 0.9.10 (ceiling -0.5) ⇒ -0.0
Although this itself is plausible, in most of the cases when you're thinking of integers (exact or inexact), you want to treat zero as zero. Certainly you don't want to deal with two distint zeros in quotients or remainders. The choices would be either leave the rounding primitives as are and fix the integer divisions, or change the rounding primitives altogether. I choose the latter.
The fixes are in the HEAD now.
;; Gauche 0.9.11 (eqv? -0.0 0.0) ⇒ #f (ceiling -0.5) ⇒ 0.0