diff --git a/compiler/src/main/pascal/blaise.assembler.x86_64.pas b/compiler/src/main/pascal/blaise.assembler.x86_64.pas index 33f0a03..5f08bee 100644 --- a/compiler/src/main/pascal/blaise.assembler.x86_64.pas +++ b/compiler/src/main/pascal/blaise.assembler.x86_64.pas @@ -543,12 +543,14 @@ begin end else begin - while (P < Length(S)) and (IsAlnum(S[P]) or (S[P]= Ord('.')) - or (S[P]= Ord('_')) or (S[P]= Ord('$')) or (S[P]= Ord('@'))) do - begin - SymName := SymName + Chr(S[P]); + { Any other @SUFFIX (e.g. @PLT, @GOTPCREL) is a relocation qualifier, + NOT part of the symbol name — consume and discard it. @PLT just + selects the PLT32 relocation, which call/jmp already emit by default. + Appending it to the name would create a bogus symbol like + `__libc_start_main@PLT` that no loader resolves. } + P := P + 1; + while (P < Length(S)) and (IsAlnum(S[P]) or (S[P]= Ord('_'))) do P := P + 1; - end; end; end; diff --git a/compiler/src/test/pascal/cp.test.assembler.pas b/compiler/src/test/pascal/cp.test.assembler.pas index 3cdc95f..cfcc79d 100644 --- a/compiler/src/test/pascal/cp.test.assembler.pas +++ b/compiler/src/test/pascal/cp.test.assembler.pas @@ -42,6 +42,7 @@ type procedure TestJmpIndirectMem_FFSlash4; procedure TestEndbr64AndHlt; procedure TestSyscall_0F05; + procedure TestCallPltStripsSuffix; end; { ---- ELF writer unit tests ---- } @@ -126,6 +127,14 @@ implementation var GInternalAsmSkipNoted: Boolean = False; +{ Validity-probe cache for the fallback compiler — see the matching note in + cp.test.cli.pas. The /tmp/fp_blaise2 fallback is a transient fixpoint + artifact that is often stale; a stale binary turns these e2e tests into a + cascade of cryptic failures. Probe once and skip (not fail) on a bad probe. + 0 = not probed, 1 = good, 2 = bad. } +var + GInternalAsmProbeState: Integer = 0; + { ---- TAsmEncodingTests ---- } function TAsmEncodingTests.ContainsBytes(const ABuf, APat: string): Boolean; @@ -222,6 +231,20 @@ begin AssertTrue('0f 05 missing', ContainsBytes(Obj, Chr($0F) + Chr($05))); end; +procedure TAsmEncodingTests.TestCallPltStripsSuffix; +var + Obj: string; +begin + { call foo@PLT must reference symbol 'foo' (with a PLT32 reloc), NOT a bogus + symbol literally named 'foo@PLT' that no loader resolves. The @PLT suffix + is a relocation qualifier, not part of the name. Regression for the + _start migration (call __libc_start_main@PLT). } + Obj := AssembleToBytes('call foo@PLT' + LineEnding); + AssertTrue('symbol foo missing', ContainsBytes(Obj, 'foo')); + AssertTrue('bogus foo@PLT symbol present', + not ContainsBytes(Obj, 'foo@PLT')); +end; + procedure TAsmEncodingTests.TestQuadSymbol_EmitsReloc; var Obj: string; @@ -672,6 +695,9 @@ begin end; function TInternalAsmE2ETests.CompilerAvailable: Boolean; +var + SrcFile, OutFile, CompOut, RunOut: string; + Rc: Integer; begin Result := FileExists(FCompiler) and FileExists(FRTL); if (not Result) and (not GInternalAsmSkipNoted) then @@ -680,7 +706,34 @@ begin WriteLn(StdErr, 'note: TInternalAsmE2ETests skipped — compiler binary "', FCompiler, '" or RTL "', FRTL, '" not found ', '(set BLAISE_QBE_COMPILER to a QBE-backend blaise binary to run them)'); + Exit; end; + if not Result then Exit; + + { Validity probe: compile+run a trivial program with --assembler internal. + A stale fallback binary compiles fine but produces a SIGILL/wrong-output + executable; detect that here and skip rather than emit a cryptic cascade. } + if GInternalAsmProbeState = 0 then + begin + SrcFile := FScratch + 'probe.pas'; + OutFile := FScratch + 'probe'; + WriteFile(SrcFile, 'program p; begin WriteLn(42); end.'); + Rc := Self.RunProc(FCompiler, [ + '--source', SrcFile, '--unit-path', FRTLPath, '--unit-path', FStdlibPath, + '--output', OutFile, '--backend', 'native', '--assembler', 'internal' + ], CompOut); + if (Rc = 0) and (Self.RunProcNoArgs(OutFile, RunOut) = 0) + and (Pos('42', RunOut) >= 0) then + GInternalAsmProbeState := 1 + else + begin + GInternalAsmProbeState := 2; + WriteLn(StdErr, 'note: TInternalAsmE2ETests skipped — compiler binary "', + FCompiler, '" is stale/broken (probe program did not run); ', + 'rebuild it or set BLAISE_QBE_COMPILER to a current binary.'); + end; + end; + Result := GInternalAsmProbeState = 1; end; function TInternalAsmE2ETests.RunProc(const AExe: string; diff --git a/compiler/src/test/pascal/cp.test.cli.pas b/compiler/src/test/pascal/cp.test.cli.pas index 4aa8197..78f1705 100644 --- a/compiler/src/test/pascal/cp.test.cli.pas +++ b/compiler/src/test/pascal/cp.test.cli.pas @@ -94,6 +94,17 @@ implementation var GCLISkipNoted: Boolean = False; +{ Validity-probe cache for the fallback compiler. The fallback path + (/tmp/fp_blaise2) is a transient fixpoint artifact that is frequently STALE — + built by an earlier, possibly-broken compiler. Running the contract tests + against a stale binary produces a cascade of cryptic failures (e.g. SIGILL + from a since-fixed mis-encoding) that look like regressions but are not. + We probe the binary once (compile+run a trivial program) and, if it does not + behave, skip the suite with an actionable message instead of failing. + 0 = not probed, 1 = good, 2 = bad. } +var + GCLIProbeState: Integer = 0; + { ---- helpers ---- } function TCLIContractTests.ProjectRoot: string; @@ -140,6 +151,9 @@ begin end; function TCLIContractTests.CompilerAvailable: Boolean; +var + Out_: string; + EC: Integer; begin Result := FileExists(FCompiler) and FileExists(FRTL); if (not Result) and (not GCLISkipNoted) then @@ -148,7 +162,27 @@ begin WriteLn(StdErr, 'note: TCLIContractTests skipped — compiler binary "', FCompiler, '" or RTL "', FRTL, '" not found ', '(set BLAISE_QBE_COMPILER to a QBE-backend blaise binary to run them)'); + Exit; end; + if not Result then Exit; + + { Validity probe: a stale fallback binary would otherwise turn into a cascade + of cryptic failures. Probe once; on a bad probe, treat as unavailable. } + if GCLIProbeState = 0 then + begin + if CompileRunFull('program p; begin WriteLn(42); end.', 'native', Out_, EC) + and (EC = 0) and (Pos('42', Out_) >= 0) then + GCLIProbeState := 1 + else + begin + GCLIProbeState := 2; + WriteLn(StdErr, 'note: TCLIContractTests skipped — compiler binary "', + FCompiler, '" is stale/broken (probe program did not run); ', + 'rebuild it or set BLAISE_QBE_COMPILER to a current QBE-backend ', + 'blaise binary.'); + end; + end; + Result := GCLIProbeState = 1; end; function TCLIContractTests.RunCompiler(const AArgs: array of string;