Skip to content

Preserve signed zero in floating unary negation - #152

Merged
itsfuad merged 1 commit into
mainfrom
fix/floating-unary-negation
Oct 4, 2026
Merged

itsfuad merged 1 commit into
mainfrom
fix/floating-unary-negation

Conversation

@itsfuad

@itsfuad itsfuad commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

Summary

  • What changed: Emit LLVM fneg for floating unary minus, add regressions, extend the existing float fixture, and specify signed-zero behavior.
  • Why this change is needed: Runtime -(+0.0) produced +0.0, while constant folding produced -0.0; their reciprocal results disagreed.
  • Owning phase/package/component: Existing emitValueExpr unary-minus branch in internal/backend/llvm.
  • Scope boundaries / non-goals: Five-file unary-negation repair, regression coverage, and operator specification.

Production ownership

  • Current non-test production consumer(s) of each new production symbol: No new production symbols. Existing GenerateLLVMIR MIR Assign emission calls emitValueExpr.
  • Execution path showing where the new behavior is reached in production: Checked unary expression -> expression lowering -> MIR Unary in Assign -> LLVM emitValueExpr.
  • Existing implementation reused, extended, replaced, or intentionally left unchanged: Reuse emitRef, isFloatType, nextValue, and debug-tagged line inside withLLVMLocation. Replace only floating subtraction from positive zero. Canonical FoldUnary and NewFloat remain the constant-evaluation owners; the fixture reuses Matches.
  • Wrappers, aliases, duplicate paths, compatibility shims, or experimental scaffolding removed: None introduced or required. The incorrect floating fsub branch is replaced directly.
  • Any new production symbol without a current production consumer: None.

Design and behavior

  • Previous behavior: fsub float/double 0.0, value loses the negative-zero result for a positive-zero operand.
  • New behavior: fneg reverses sign at the existing operand width, including signed zeros and infinities. NaN remains NaN; no payload/sign encoding guarantee is added.
  • Key invariants preserved: Operand evaluation once, scalar rejection, typed SSA/result layout, exact debug source location, integer negation, and binary subtraction.
  • Intentional behavioral changes: Runtime floating unary minus now agrees with constant folding for signed zero.
  • Error / edge-case handling: Both float widths, finite signs, subnormals, infinities, NaN classification, double negation, f32 rounding, and malformed non-scalar operands have regression coverage.
  • Why this implementation belongs in this layer rather than another phase/package: Semantic checking and constant folding already implement the intended rule. LLVM emission selected the incorrect opcode.

Validation

Run and report the exact commands used. Do not summarize a command as passing unless it was actually executed.

env CCACHE_DISABLE=1 GOCACHE=/tmp/omnirush/peeper-go-cache go test -count=1 ./internal/backend/llvm
PASS (1.479s). Clang 22.1.8 compiled all six unary width/type cases to objects for Linux 386/amd64; no verifier skips.

env CCACHE_DISABLE=1 GOCACHE=/tmp/omnirush/peeper-go-cache go test -count=1 ./internal/constvalue ./internal/ir ./internal/ir/exprlower ./internal/ir/mir ./internal/pipeline
PASS for all six packages.

env CCACHE_DISABLE=1 GOCACHE=/tmp/omnirush/peeper-go-cache go test -count=1 ./...
PASS for full Go suite; executable fixtures ran separately below.

env CCACHE_DISABLE=1 GOCACHE=/tmp/omnirush/peeper-go-cache go vet ./...
PASS, no output.

env CCACHE_DISABLE=1 GOCACHE=/tmp/omnirush/peeper-go-cache go run ./scripts/bundle.go
PASS; fresh post-fix bundle.

env CCACHE_DISABLE=1 GOCACHE=/tmp/omnirush/peeper-go-cache PEEPER_BIN="$PWD/build/bin/peeper" go test -count=1 -run "^TestFixtureContracts$/^runtime_float_remainder$" -v ./x_test
PASS (0.337s); all 32 remainder cases and 24 new negation cases execute successfully.

env CCACHE_DISABLE=1 GOCACHE=/tmp/omnirush/peeper-go-cache PEEPER_BIN="$PWD/build/bin/peeper" go test -count=1 ./x_test
PASS (17.123s), full executable fixture suite.

gofmt -d internal/backend/llvm/instruction_emit.go internal/backend/llvm/emitter_test.go internal/constvalue/value_test.go
PASS, no formatting diff.

git diff --check
PASS.
  • Targeted tests added or updated: TestGenerateLLVMIRLowersUnaryNegation, TestLLVMEmitterRejectsNonScalarFloatNegation, TestFoldFloatUnaryNegation, and existing runtime_float_remainder fixture. Before repair, four f32/f64 emission cases failed and the freshly bundled fixture exited 33. All 20 new constant-folding cases already passed on baseline. After repair, emission and executable regressions pass.
  • Existing regression coverage exercised: Full Go suite and executable positive/negative source fixtures. No new rejected source semantics require another negative fixture.
  • Manual verification, if applicable: Source ownership, debug emission path, complete base diff, and commit contents inspected. Local validation ran on Linux/amd64 with Go 1.26.8 and Clang 22.1.8.
  • Checks not run: Native Windows/macOS/arm64 and Linux/386 execution were not run locally. Clang object compilation is not native runtime execution; existing CI supplies the wider native matrix.

Diff sanity

  • Unrelated production changes: None.
  • Duplicated logic introduced: No duplicate production semantic owner or fixture comparator. Test-only Clang setup and float-result assertions follow existing nearby boilerplate.
  • Dead or unreachable code introduced: None.
  • Temporary/debug code remaining: None.
  • Comments/docs that became stale because of this change: Operator specification updated with floating unary sign inversion and signed-zero behavior.

Risks and follow-up

  • Known risks or assumptions: Signed-zero semantics rely on preserving the absence of fast-math/nsz flags. Parameter-backed runtime functions prevent literal folding from hiding the original emitter defect.
  • Behavior intentionally changed: Runtime negation of positive zero produces negative zero for f32/f64, matching folded expressions.
  • Behavior intentionally not changed: Integer negation, binary floating subtraction, constant rounding/publication, diagnostics, and phase/cache ownership.
  • Remaining gaps: Wider native execution awaits CI. No tactical bridge or temporary production instrumentation remains.
  • Linked issues / follow-up work: No matching open negation/signed-zero issue found; no separate implementation follow-up required.
  • Anything that should block merge: Required CI and a non-author human collaborator approval of the current head.

Emit LLVM fneg through existing SSA/debug output and preserve scalar checks. Reuse float fixture comparison for signed-zero and exceptional-value regressions.

Validation passed: focused and full Go tests, vet, fresh bundle, executable fixtures, and Clang object checks for 32-/64-bit targets.
@itsfuad itsfuad added this to the 0.2 Language Foundations milestone Oct 4, 2026
@itsfuad
itsfuad enabled auto-merge October 4, 2026 08:30
@itsfuad
itsfuad disabled auto-merge October 4, 2026 08:32
@itsfuad
itsfuad merged commit 5a3444e into main Oct 4, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant