From 1c250fb13e47c6a1ab71cc0017ff4bfae8674680 Mon Sep 17 00:00:00 2001 From: Graeme Geldenhuys Date: Sat, 27 Jun 2026 12:05:27 +0100 Subject: [PATCH] refactor(codegen): hoist symbol mangler to blaise.codegen (F5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QBEMangle (TCodeGenQBE method) and NativeMangle (native free function) applied the same metacharacter mapping: '<' ',' ' ' -> '_', drop '>', and '$' '@' '^' -> '_D_' '_V_' '_P_'. Two copies of the symbol-naming rule is a link-time-breakage risk if one side gains a case the other lacks. Hoist a single CodegenMangle into blaise.codegen (distinct name, per the F4 method-binding gotcha); both backends delegate. The shared version keeps QBEMangle's clean-name fast-path (no per-character concat when no metacharacter is present) and reads bytes via StrAt — so NativeMangle now also uses the stage-stable StrAt instead of the Ord(S[i]) idiom the house rules warn against. Cross-backend dedup, no behaviour change: native .s AND QBE IR both byte-identical on a generics + overload program (TList, TList, Show$i/Show$S → TList_Integer_*, Show_D_i/Show_D_S). All four fixpoints — which mangle the entire compiler's generic/overload symbol set — and the full suite on both test runners are unchanged. --- .../pascal/blaise.codegen.native.x86_64.pas | 21 ++------ compiler/src/main/pascal/blaise.codegen.pas | 48 ++++++++++++++++++- .../src/main/pascal/blaise.codegen.qbe.pas | 39 ++------------- 3 files changed, 53 insertions(+), 55 deletions(-) diff --git a/compiler/src/main/pascal/blaise.codegen.native.x86_64.pas b/compiler/src/main/pascal/blaise.codegen.native.x86_64.pas index c4421e7..8ddc6fc 100644 --- a/compiler/src/main/pascal/blaise.codegen.native.x86_64.pas +++ b/compiler/src/main/pascal/blaise.codegen.native.x86_64.pas @@ -1670,25 +1670,10 @@ end; { Mangle a name for use as an assembly label: replace characters that are invalid in label names with underscores, matching QBE backend's QBEMangle. } function NativeMangle(const AName: string): string; -var - I, C: Integer; begin - Result := ''; - for I := 0 to Length(AName) - 1 do - begin - C := Ord(AName[I]); - case C of - 60: Result := Result + '_'; { '<' } - 62: ; { '>' — skip } - 44: Result := Result + '_'; { ',' } - 32: Result := Result + '_'; { ' ' — e.g. 'class of T' } - 36: Result := Result + '_D_'; { '$' } - 64: Result := Result + '_V_'; { '@' } - 94: Result := Result + '_P_'; { '^' } - else - Result := Result + Chr(C); - end; - end; + { Delegates to the shared backend-neutral mangler in blaise.codegen + (formerly a per-character twin of the QBE backend's QBEMangle). } + Result := CodegenMangle(AName); end; function TX86_64Backend.ClassSymName(const AClassName: string): string; diff --git a/compiler/src/main/pascal/blaise.codegen.pas b/compiler/src/main/pascal/blaise.codegen.pas index 72c57d2..67af953 100644 --- a/compiler/src/main/pascal/blaise.codegen.pas +++ b/compiler/src/main/pascal/blaise.codegen.pas @@ -27,7 +27,7 @@ unit blaise.codegen; interface uses - uAST, uSymbolTable, uDebugFacts, blaise.codegen.target; + uAST, uSymbolTable, uDebugFacts, blaise.codegen.target, uStrCompat; type TRecReturnClass = ( @@ -133,6 +133,13 @@ function RecretClassify(ARec: TRecordTypeDesc; twins ExprOwnsRef / NativeExprOwnsRef). } function ArcExprOwnsRef(AExpr: TASTExpr): Boolean; +{ Mangle a Blaise symbol name into an assembler-legal identifier, shared by + both backends (formerly QBEMangle / NativeMangle). Replaces the generic/ + overload metacharacters '<' ',' ' ' -> '_', drops '>', and maps the type-code + sigils '$' '@' '^' to '_D_' '_V_' '_P_'. A clean name (no metacharacter) is + returned unchanged via a fast pre-scan that avoids per-character concat. } +function CodegenMangle(const AName: string): string; + implementation function RecretManagedClean(ARec: TRecordTypeDesc): Boolean; @@ -357,4 +364,43 @@ begin end; end; +function CodegenMangle(const AName: string): string; +var + I, C: Integer; + Clean: Boolean; +begin + { Fast path: the overwhelming majority of names contain none of the mangled + characters — return the input unchanged instead of rebuilding it with one + concat (and its ARC churn) per character. } + Clean := True; + for I := 0 to Length(AName) - 1 do + begin + C := StrAt(AName, I); + if (C = 60) or (C = 62) or (C = 44) or (C = 36) or (C = 64) or + (C = 94) or (C = 32) then + begin + Clean := False; + break; + end; + end; + if Clean then + Exit(AName); + Result := ''; + for I := 0 to Length(AName) - 1 do + begin + C := StrAt(AName, I); + case C of + 60: Result := Result + '_'; { '<' } + 62: ; { '>' — skip } + 44: Result := Result + '_'; { ',' } + 32: Result := Result + '_'; { ' ' — e.g. 'class of T' } + 36: Result := Result + '_D_'; { '$' — overload delimiter } + 64: Result := Result + '_V_'; { '@' — var-param prefix } + 94: Result := Result + '_P_'; { '^' — pointer prefix } + else + Result := Result + Chr(C); + end; + end; +end; + end. diff --git a/compiler/src/main/pascal/blaise.codegen.qbe.pas b/compiler/src/main/pascal/blaise.codegen.qbe.pas index a8ef401..7bdec5f 100644 --- a/compiler/src/main/pascal/blaise.codegen.qbe.pas +++ b/compiler/src/main/pascal/blaise.codegen.qbe.pas @@ -13382,43 +13382,10 @@ begin end; function TCodeGenQBE.QBEMangle(const AName: string): string; -var - I: Integer; - C: Integer; - Clean: Boolean; begin - { Fast path: the overwhelming majority of names contain none of the - mangled characters — return the input unchanged instead of rebuilding - it with one concat (and its ARC churn) per character. } - Clean := True; - for I := 0 to Length(AName) - 1 do - begin - C := StrAt(AName, I); - if (C = 60) or (C = 62) or (C = 44) or (C = 36) or (C = 64) or - (C = 94) or (C = 32) then - begin - Clean := False; - break; - end; - end; - if Clean then - Exit(AName); - Result := ''; - for I := 0 to Length(AName) - 1 do - begin - C := StrAt(AName, I); - case C of - 60: Result := Result + '_'; { '<' } - 62: ; { '>' — skip } - 44: Result := Result + '_'; { ',' } - 32: Result := Result + '_'; { ' ' — e.g. 'class of T' } - 36: Result := Result + '_D_'; { '$' — overload delimiter } - 64: Result := Result + '_V_'; { '@' — var-param prefix } - 94: Result := Result + '_P_'; { '^' — pointer prefix } - else - Result := Result + Chr(C); - end; - end; + { Delegates to the shared backend-neutral mangler in blaise.codegen + (formerly a per-character twin of the native backend's NativeMangle). } + Result := CodegenMangle(AName); end; function TCodeGenQBE.MethodEmitName(AMDecl: TMethodDecl;