Also removing all function vars, consts and labels when a function is removed on pragma _P_REMOVE_UNUSED

This commit is contained in:
Mattias Hansson 2026-04-13 21:28:16 +02:00
parent e5ee83d3dd
commit bc6fdaf8f2
4 changed files with 79 additions and 39 deletions

View file

@ -223,8 +223,11 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
// Analyze for overlapping absolute addresses in function call chains // Analyze for overlapping absolute addresses in function call chains
c.checkAbsoluteOverlaps() c.checkAbsoluteOverlaps()
// Check for unused variables and print warnings // Get functions with _P_REMOVE_UNUSED pragma (for suppressing variable warnings)
warnings := c.ctx.SymbolTable.CheckUnused() funcsWithRemovePragma := c.ctx.FunctionHandler.GetFunctionsWithRemovePragma()
// Check for unused variables and print warnings (skip variables in functions with remove pragma)
warnings := c.ctx.SymbolTable.CheckUnused(funcsWithRemovePragma)
for _, warning := range warnings { for _, warning := range warnings {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", warning) _, _ = fmt.Fprintf(os.Stderr, "%s\n", warning)
} }
@ -236,10 +239,10 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
} }
// Remove unused functions with _P_REMOVE_UNUSED pragma // Remove unused functions with _P_REMOVE_UNUSED pragma
codeOutput = c.removeUnusedFunctions(codeOutput) codeOutput, removedFuncs := c.removeUnusedFunctions(codeOutput)
// Assemble final output with headers and footers // Assemble final output with headers and footers
return c.assembleOutput(codeOutput), nil return c.assembleOutput(codeOutput, removedFuncs), nil
} }
// printErrorWithContext prints an error with source code context to stderr // printErrorWithContext prints an error with source code context to stderr
@ -454,10 +457,11 @@ func findPipeOutsideStrings(line string, startFrom int) int {
} }
// removeUnusedFunctions removes functions marked with _P_REMOVE_UNUSED pragma from assembly output // removeUnusedFunctions removes functions marked with _P_REMOVE_UNUSED pragma from assembly output
func (c *Compiler) removeUnusedFunctions(codeLines []string) []string { // Returns the filtered code lines and a map of removed function names
func (c *Compiler) removeUnusedFunctions(codeLines []string) ([]string, map[string]bool) {
toRemove := c.ctx.FunctionHandler.GetFunctionsToRemove() toRemove := c.ctx.FunctionHandler.GetFunctionsToRemove()
if len(toRemove) == 0 { if len(toRemove) == 0 {
return codeLines return codeLines, nil
} }
var result []string var result []string
@ -515,11 +519,11 @@ func (c *Compiler) removeUnusedFunctions(codeLines []string) []string {
result = append(result, line) result = append(result, line)
i++ i++
} }
return result return result, toRemove
} }
// assembleOutput combines all generated sections into final assembly // assembleOutput combines all generated sections into final assembly
func (c *Compiler) assembleOutput(codeLines []string) []string { func (c *Compiler) assembleOutput(codeLines []string, removedFuncs map[string]bool) []string {
var output []string var output []string
// Header comment // Header comment
@ -527,12 +531,12 @@ func (c *Compiler) assembleOutput(codeLines []string) []string {
output = append(output, "") output = append(output, "")
// Constants section // Constants section
if constLines := GenerateConstants(c.ctx.SymbolTable); len(constLines) > 0 { if constLines := GenerateConstants(c.ctx.SymbolTable, removedFuncs); len(constLines) > 0 {
output = append(output, constLines...) output = append(output, constLines...)
} }
// Absolute addresses section // Absolute addresses section
if absLines := GenerateAbsolutes(c.ctx.SymbolTable); len(absLines) > 0 { if absLines := GenerateAbsolutes(c.ctx.SymbolTable, removedFuncs); len(absLines) > 0 {
output = append(output, absLines...) output = append(output, absLines...)
} }
@ -543,7 +547,7 @@ func (c *Compiler) assembleOutput(codeLines []string) []string {
output = append(output, "") output = append(output, "")
// Variables section // Variables section
if varLines := GenerateVariables(c.ctx.SymbolTable); len(varLines) > 0 { if varLines := GenerateVariables(c.ctx.SymbolTable, removedFuncs); len(varLines) > 0 {
output = append(output, varLines...) output = append(output, varLines...)
} }

View file

@ -1064,3 +1064,21 @@ func (fh *FunctionHandler) GetFunctionsToRemove() map[string]bool {
return toRemove return toRemove
} }
// GetFunctionsWithRemovePragma returns a map of function names that have _P_REMOVE_UNUSED pragma enabled
// This is used to suppress variable warnings for functions marked for removal
func (fh *FunctionHandler) GetFunctionsWithRemovePragma() map[string]bool {
withPragma := make(map[string]bool)
for _, funcDecl := range fh.functions {
if fh.pragma != nil {
pragmaSet := fh.pragma.GetPragmaSetByIndex(funcDecl.Line.PragmaSetIndex)
removeValue := pragmaSet.GetPragma("_P_REMOVE_UNUSED")
if removeValue != "" && removeValue != "0" {
withPragma[funcDecl.Name] = true
}
}
}
return withPragma
}

View file

@ -339,7 +339,8 @@ func (st *SymbolTable) ConstantLookupFunc(currentScopes []string) func(string) (
// CheckUnused returns warnings for unused variables // CheckUnused returns warnings for unused variables
// Returns slice of warning messages for regular variables (not constants, not absolutes) that were never used // Returns slice of warning messages for regular variables (not constants, not absolutes) that were never used
func (st *SymbolTable) CheckUnused() []string { // excludeFuncs is a map of function names that will be removed (e.g., have _P_REMOVE_UNUSED pragma)
func (st *SymbolTable) CheckUnused(excludeFuncs map[string]bool) []string {
var warnings []string var warnings []string
for _, sym := range st.symbols { for _, sym := range st.symbols {
// Skip constants and absolute variables (they shouldn't track usage) // Skip constants and absolute variables (they shouldn't track usage)
@ -353,6 +354,11 @@ func (st *SymbolTable) CheckUnused() []string {
continue continue
} }
// Skip variables in functions that will be removed
if sym.Scope != "" && excludeFuncs != nil && excludeFuncs[sym.Scope] {
continue
}
// Check if pragma indicates we should ignore unused warnings for this variable // Check if pragma indicates we should ignore unused warnings for this variable
if st.pragma != nil { if st.pragma != nil {
pragmaSet := st.pragma.GetPragmaSetByIndex(sym.Line.PragmaSetIndex) pragmaSet := st.pragma.GetPragmaSetByIndex(sym.Line.PragmaSetIndex)
@ -412,7 +418,7 @@ func (s *Symbol) String() string {
// Code generation functions for ACME assembler syntax // Code generation functions for ACME assembler syntax
// GenerateConstants generates constant definitions (name = $value) // GenerateConstants generates constant definitions (name = $value)
func GenerateConstants(st *SymbolTable) []string { func GenerateConstants(st *SymbolTable, excludeScopes map[string]bool) []string {
var lines []string var lines []string
hasConsts := false hasConsts := false
@ -420,6 +426,10 @@ func GenerateConstants(st *SymbolTable) []string {
if !sym.IsConst() { if !sym.IsConst() {
continue continue
} }
// Skip constants scoped to removed functions
if excludeScopes != nil && sym.Scope != "" && excludeScopes[sym.Scope] {
continue
}
hasConsts = true hasConsts = true
var line string var line string
@ -450,7 +460,7 @@ func GenerateConstants(st *SymbolTable) []string {
} }
// GenerateAbsolutes generates absolute address assignments (name = $addr) // GenerateAbsolutes generates absolute address assignments (name = $addr)
func GenerateAbsolutes(st *SymbolTable) []string { func GenerateAbsolutes(st *SymbolTable, excludeScopes map[string]bool) []string {
var lines []string var lines []string
hasAbsolutes := false hasAbsolutes := false
@ -458,6 +468,10 @@ func GenerateAbsolutes(st *SymbolTable) []string {
if !sym.IsAbsolute() { if !sym.IsAbsolute() {
continue continue
} }
// Skip absolutes scoped to removed functions
if excludeScopes != nil && sym.Scope != "" && excludeScopes[sym.Scope] {
continue
}
hasAbsolutes = true hasAbsolutes = true
var line string var line string
@ -488,7 +502,7 @@ func GenerateAbsolutes(st *SymbolTable) []string {
} }
// GenerateVariables generates variable declarations (name !8 $value) // GenerateVariables generates variable declarations (name !8 $value)
func GenerateVariables(st *SymbolTable) []string { func GenerateVariables(st *SymbolTable, excludeScopes map[string]bool) []string {
var lines []string var lines []string
hasVars := false hasVars := false
@ -497,6 +511,10 @@ func GenerateVariables(st *SymbolTable) []string {
if sym.IsConst() || sym.IsAbsolute() { if sym.IsConst() || sym.IsAbsolute() {
continue continue
} }
// Skip variables scoped to removed functions
if excludeScopes != nil && sym.Scope != "" && excludeScopes[sym.Scope] {
continue
}
hasVars = true hasVars = true
var line string var line string

View file

@ -490,7 +490,7 @@ func TestGenerateConstants(t *testing.T) {
st.AddConst("SIZE", "", KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddConst("SIZE", "", KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("notconst", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) // should be skipped st.AddVar("notconst", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) // should be skipped
lines := GenerateConstants(st) lines := GenerateConstants(st, nil)
if len(lines) == 0 { if len(lines) == 0 {
t.Fatal("expected output lines") t.Fatal("expected output lines")
@ -531,7 +531,7 @@ func TestGenerateAbsolutes(t *testing.T) {
// Regular var (should be skipped) // Regular var (should be skipped)
st.AddVar("regular", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddVar("regular", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
lines := GenerateAbsolutes(st) lines := GenerateAbsolutes(st, nil)
if len(lines) == 0 { if len(lines) == 0 {
t.Fatal("expected output lines") t.Fatal("expected output lines")
@ -580,7 +580,7 @@ func TestGenerateVariables(t *testing.T) {
// Absolute (should be skipped) // Absolute (should be skipped)
st.AddAbsolute("SKIP2", "", KindByte, 0x80, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddAbsolute("SKIP2", "", KindByte, 0x80, preproc.Line{Filename: "test.c65", LineNo: 1})
lines := GenerateVariables(st) lines := GenerateVariables(st, nil)
if len(lines) == 0 { if len(lines) == 0 {
t.Fatal("expected output lines") t.Fatal("expected output lines")
@ -624,23 +624,23 @@ func TestGenerateEmpty(t *testing.T) {
st := NewSymbolTable() st := NewSymbolTable()
// Empty table // Empty table
if lines := GenerateConstants(st); lines != nil { if lines := GenerateConstants(st, nil); lines != nil {
t.Error("expected nil for empty constants") t.Error("expected nil for empty constants")
} }
if lines := GenerateAbsolutes(st); lines != nil { if lines := GenerateAbsolutes(st, nil); lines != nil {
t.Error("expected nil for empty absolutes") t.Error("expected nil for empty absolutes")
} }
if lines := GenerateVariables(st); lines != nil { if lines := GenerateVariables(st, nil); lines != nil {
t.Error("expected nil for empty variables") t.Error("expected nil for empty variables")
} }
// Only variables (no constants/absolutes) // Only variables (no constants/absolutes)
st.AddVar("test", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddVar("test", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
if lines := GenerateConstants(st); lines != nil { if lines := GenerateConstants(st, nil); lines != nil {
t.Error("expected nil when no constants exist") t.Error("expected nil when no constants exist")
} }
if lines := GenerateAbsolutes(st); lines != nil { if lines := GenerateAbsolutes(st, nil); lines != nil {
t.Error("expected nil when no absolutes exist") t.Error("expected nil when no absolutes exist")
} }
} }
@ -652,7 +652,7 @@ func TestGenerateScopedVariables(t *testing.T) {
st.AddVar("local", "main", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddVar("local", "main", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("nested", "main_helper", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddVar("nested", "main_helper", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
lines := GenerateVariables(st) lines := GenerateVariables(st, nil)
output := strings.Join(lines, "\n") output := strings.Join(lines, "\n")
// Check full names are used // Check full names are used
@ -674,9 +674,9 @@ func TestGenerateHexLowercase(t *testing.T) {
st.AddAbsolute("ADDR", "", KindWord, 0xDEAD, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddAbsolute("ADDR", "", KindWord, 0xDEAD, preproc.Line{Filename: "test.c65", LineNo: 1})
st.AddVar("VAR", "", KindWord, 0xBEEF, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddVar("VAR", "", KindWord, 0xBEEF, preproc.Line{Filename: "test.c65", LineNo: 1})
constLines := GenerateConstants(st) constLines := GenerateConstants(st, nil)
absLines := GenerateAbsolutes(st) absLines := GenerateAbsolutes(st, nil)
varLines := GenerateVariables(st) varLines := GenerateVariables(st, nil)
output := strings.Join(append(append(constLines, absLines...), varLines...), "\n") output := strings.Join(append(append(constLines, absLines...), varLines...), "\n")
@ -716,7 +716,7 @@ func TestUsageTracking(t *testing.T) {
} }
// Check that warning is generated // Check that warning is generated
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 1 { if len(warnings) != 1 {
t.Fatalf("CheckUnused() returned %d warnings, want 1", len(warnings)) t.Fatalf("CheckUnused() returned %d warnings, want 1", len(warnings))
} }
@ -748,7 +748,7 @@ func TestUsageTracking(t *testing.T) {
} }
// Check that no warning is generated // Check that no warning is generated
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 0 { if len(warnings) != 0 {
t.Errorf("CheckUnused() returned %d warnings, want 0: %v", len(warnings), warnings) t.Errorf("CheckUnused() returned %d warnings, want 0: %v", len(warnings), warnings)
} }
@ -764,7 +764,7 @@ func TestUsageTracking(t *testing.T) {
} }
// Check that no warning is generated // Check that no warning is generated
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 0 { if len(warnings) != 0 {
t.Errorf("CheckUnused() returned %d warnings for constant, want 0: %v", len(warnings), warnings) t.Errorf("CheckUnused() returned %d warnings for constant, want 0: %v", len(warnings), warnings)
} }
@ -786,7 +786,7 @@ func TestUsageTracking(t *testing.T) {
} }
// Check that no warning is generated // Check that no warning is generated
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 0 { if len(warnings) != 0 {
t.Errorf("CheckUnused() returned %d warnings for absolute variable, want 0: %v", len(warnings), warnings) t.Errorf("CheckUnused() returned %d warnings for absolute variable, want 0: %v", len(warnings), warnings)
} }
@ -821,7 +821,7 @@ func TestUsageTracking(t *testing.T) {
st.Lookup("local_used", []string{"myFunc"}) st.Lookup("local_used", []string{"myFunc"})
// Check warnings // Check warnings
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 2 { if len(warnings) != 2 {
t.Fatalf("CheckUnused() returned %d warnings, want 2: %v", len(warnings), warnings) t.Fatalf("CheckUnused() returned %d warnings, want 2: %v", len(warnings), warnings)
} }
@ -873,7 +873,7 @@ func TestUsageTracking(t *testing.T) {
} }
// No warnings should be generated // No warnings should be generated
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 0 { if len(warnings) != 0 {
t.Errorf("CheckUnused() returned %d warnings for used variable, want 0", len(warnings)) t.Errorf("CheckUnused() returned %d warnings for used variable, want 0", len(warnings))
} }
@ -900,7 +900,7 @@ func TestUsageTracking(t *testing.T) {
} }
// Warning should be generated // Warning should be generated
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 1 { if len(warnings) != 1 {
t.Errorf("CheckUnused() returned %d warnings, want 1", len(warnings)) t.Errorf("CheckUnused() returned %d warnings, want 1", len(warnings))
} }
@ -922,7 +922,7 @@ func TestUsageTracking(t *testing.T) {
st.Lookup("used2", []string{"func1"}) st.Lookup("used2", []string{"func1"})
// Check warnings // Check warnings
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 2 { if len(warnings) != 2 {
t.Fatalf("CheckUnused() returned %d warnings, want 2: %v", len(warnings), warnings) t.Fatalf("CheckUnused() returned %d warnings, want 2: %v", len(warnings), warnings)
} }
@ -970,7 +970,7 @@ func TestUsageTracking(t *testing.T) {
} }
// Since we haven't used it, it should generate a warning // Since we haven't used it, it should generate a warning
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 1 { if len(warnings) != 1 {
t.Errorf("CheckUnused() returned %d warnings for label reference, want 1", len(warnings)) t.Errorf("CheckUnused() returned %d warnings for label reference, want 1", len(warnings))
} }
@ -979,7 +979,7 @@ func TestUsageTracking(t *testing.T) {
st.Lookup("handler", []string{}) st.Lookup("handler", []string{})
// Now no warning should be generated // Now no warning should be generated
warnings = st.CheckUnused() warnings = st.CheckUnused(nil)
if len(warnings) != 0 { if len(warnings) != 0 {
t.Errorf("CheckUnused() returned %d warnings for used label reference, want 0", len(warnings)) t.Errorf("CheckUnused() returned %d warnings for used label reference, want 0", len(warnings))
} }
@ -1022,7 +1022,7 @@ func TestUsageTracking(t *testing.T) {
} }
// Warning should be generated for the variable // Warning should be generated for the variable
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 1 { if len(warnings) != 1 {
t.Errorf("CheckUnused() returned %d warnings, want 1", len(warnings)) t.Errorf("CheckUnused() returned %d warnings, want 1", len(warnings))
} }
@ -1067,7 +1067,7 @@ func TestUsageTracking(t *testing.T) {
} }
// No warnings should be generated // No warnings should be generated
warnings := st.CheckUnused() warnings := st.CheckUnused(nil)
if len(warnings) != 0 { if len(warnings) != 0 {
t.Errorf("CheckUnused() returned %d warnings for constants/absolutes, want 0", len(warnings)) t.Errorf("CheckUnused() returned %d warnings for constants/absolutes, want 0", len(warnings))
} }