Added call graph functionality and analysis/warning of absolute address variable reuse overlap
This commit is contained in:
parent
35e4b77ee3
commit
ebad3c2e16
5 changed files with 608 additions and 3 deletions
|
|
@ -129,10 +129,18 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
||||||
return nil, fmt.Errorf("Unclosed SCRIPT block.")
|
return nil, fmt.Errorf("Unclosed SCRIPT block.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Analyze for overlapping absolute addresses in function call chains
|
||||||
|
c.checkAbsoluteOverlaps()
|
||||||
|
|
||||||
// Assemble final output with headers and footers
|
// Assemble final output with headers and footers
|
||||||
return c.assembleOutput(codeOutput), nil
|
return c.assembleOutput(codeOutput), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkAbsoluteOverlaps analyzes and warns about overlapping absolute addresses
|
||||||
|
func (c *Compiler) checkAbsoluteOverlaps() {
|
||||||
|
c.ctx.FunctionHandler.ReportAbsoluteOverlaps()
|
||||||
|
}
|
||||||
|
|
||||||
// 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) []string {
|
||||||
var output []string
|
var output []string
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,9 @@ func NewCompilerContext(pragma *preproc.Pragma) *CompilerContext {
|
||||||
// FunctionHandler needs references to other components
|
// FunctionHandler needs references to other components
|
||||||
ctx.FunctionHandler = NewFunctionHandler(symTable, generalStack, constStrHandler, pragma)
|
ctx.FunctionHandler = NewFunctionHandler(symTable, generalStack, constStrHandler, pragma)
|
||||||
|
|
||||||
|
// Connect SymbolTable to FunctionHandler for absolute variable notifications
|
||||||
|
symTable.SetFunctionHandler(ctx.FunctionHandler)
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"c65gm/internal/preproc"
|
"c65gm/internal/preproc"
|
||||||
|
|
@ -40,6 +42,10 @@ type FunctionHandler struct {
|
||||||
labelStack *LabelStack
|
labelStack *LabelStack
|
||||||
constStrHandler *ConstantStringHandler
|
constStrHandler *ConstantStringHandler
|
||||||
pragma *preproc.Pragma
|
pragma *preproc.Pragma
|
||||||
|
|
||||||
|
// Absolute address tracking for overlap detection
|
||||||
|
absoluteAddrs map[string]map[uint16]bool // funcName -> set of absolute addresses used
|
||||||
|
callGraph map[string][]string // funcName -> list of functions it calls
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFunctionHandler creates a new function handler
|
// NewFunctionHandler creates a new function handler
|
||||||
|
|
@ -51,6 +57,8 @@ func NewFunctionHandler(st *SymbolTable, ls *LabelStack, csh *ConstantStringHand
|
||||||
labelStack: ls,
|
labelStack: ls,
|
||||||
constStrHandler: csh,
|
constStrHandler: csh,
|
||||||
pragma: pragma,
|
pragma: pragma,
|
||||||
|
absoluteAddrs: make(map[string]map[uint16]bool),
|
||||||
|
callGraph: make(map[string][]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,10 +158,51 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) {
|
||||||
Params: funcParams,
|
Params: funcParams,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Record absolute addresses used by this function
|
||||||
|
fh.recordAbsoluteAddresses(funcName, funcParams)
|
||||||
|
|
||||||
// Generate assembler label
|
// Generate assembler label
|
||||||
return []string{funcName}, nil
|
return []string{funcName}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recordAbsoluteAddresses stores which absolute addresses a function uses
|
||||||
|
func (fh *FunctionHandler) recordAbsoluteAddresses(funcName string, params []*FuncParam) {
|
||||||
|
addrSet := make(map[uint16]bool)
|
||||||
|
|
||||||
|
for _, param := range params {
|
||||||
|
if param.Symbol.IsAbsolute() {
|
||||||
|
addr := param.Symbol.AbsAddr
|
||||||
|
// Mark address as used
|
||||||
|
addrSet[addr] = true
|
||||||
|
// If it's a word, also mark the next byte
|
||||||
|
if param.Symbol.IsWord() {
|
||||||
|
addrSet[addr+1] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only store if function uses absolute addresses
|
||||||
|
if len(addrSet) > 0 {
|
||||||
|
fh.absoluteAddrs[funcName] = addrSet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordAbsoluteVar is called by SymbolTable when an absolute variable is added to a function scope
|
||||||
|
func (fh *FunctionHandler) RecordAbsoluteVar(funcScope string, addr uint16, isWord bool) {
|
||||||
|
if funcScope == "" {
|
||||||
|
return // global variable, skip
|
||||||
|
}
|
||||||
|
|
||||||
|
if fh.absoluteAddrs[funcScope] == nil {
|
||||||
|
fh.absoluteAddrs[funcScope] = make(map[uint16]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
fh.absoluteAddrs[funcScope][addr] = true
|
||||||
|
if isWord {
|
||||||
|
fh.absoluteAddrs[funcScope][addr+1] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// buildComplexParams handles parameter lists that may contain {BYTE x} style declarations
|
// buildComplexParams handles parameter lists that may contain {BYTE x} style declarations
|
||||||
// Tokens like {BYTE x} are spread across multiple tokens and need to be reassembled
|
// Tokens like {BYTE x} are spread across multiple tokens and need to be reassembled
|
||||||
func buildComplexParams(tokens []string) ([]string, error) {
|
func buildComplexParams(tokens []string) ([]string, error) {
|
||||||
|
|
@ -332,6 +381,9 @@ func (fh *FunctionHandler) HandleFuncCall(line preproc.Line) ([]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record the call in the call graph
|
||||||
|
fh.recordCall(funcName)
|
||||||
|
|
||||||
// Generate final assembly
|
// Generate final assembly
|
||||||
asmLines = append(asmLines, inAssigns...)
|
asmLines = append(asmLines, inAssigns...)
|
||||||
asmLines = append(asmLines, fmt.Sprintf(" jsr %s", funcName))
|
asmLines = append(asmLines, fmt.Sprintf(" jsr %s", funcName))
|
||||||
|
|
@ -340,6 +392,31 @@ func (fh *FunctionHandler) HandleFuncCall(line preproc.Line) ([]string, error) {
|
||||||
return asmLines, nil
|
return asmLines, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recordCall tracks which function calls which
|
||||||
|
func (fh *FunctionHandler) recordCall(calledFunc string) {
|
||||||
|
// Get current function (caller)
|
||||||
|
if len(fh.currentFuncs) == 0 {
|
||||||
|
// Call from global scope - not inside a function
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
caller := fh.currentFuncs[len(fh.currentFuncs)-1]
|
||||||
|
|
||||||
|
// Add to call graph
|
||||||
|
if fh.callGraph[caller] == nil {
|
||||||
|
fh.callGraph[caller] = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid duplicates (though they're harmless for our analysis)
|
||||||
|
for _, existing := range fh.callGraph[caller] {
|
||||||
|
if existing == calledFunc {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fh.callGraph[caller] = append(fh.callGraph[caller], calledFunc)
|
||||||
|
}
|
||||||
|
|
||||||
// processLabelArg handles @label arguments
|
// processLabelArg handles @label arguments
|
||||||
func (fh *FunctionHandler) processLabelArg(arg string, param *FuncParam, funcName string, line preproc.Line, inAssigns *[]string) error {
|
func (fh *FunctionHandler) processLabelArg(arg string, param *FuncParam, funcName string, line preproc.Line, inAssigns *[]string) error {
|
||||||
labelName := arg[1:] // strip @
|
labelName := arg[1:] // strip @
|
||||||
|
|
@ -768,3 +845,165 @@ func parseParamSpec(spec string) (ParamDirection, string, bool, string, error) {
|
||||||
|
|
||||||
return direction, varName, isImplicit, implicitDecl, nil
|
return direction, varName, isImplicit, implicitDecl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AbsoluteOverlap represents a detected overlap in absolute addresses
|
||||||
|
type AbsoluteOverlap struct {
|
||||||
|
Func1 string // First function using the address
|
||||||
|
Func2 string // Second function using the address
|
||||||
|
Address uint16 // Overlapping address
|
||||||
|
CallChain []string // Call chain from Func1 to Func2
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnalyzeAbsoluteOverlaps checks for overlapping absolute addresses in call chains
|
||||||
|
func (fh *FunctionHandler) AnalyzeAbsoluteOverlaps() []AbsoluteOverlap {
|
||||||
|
// Use map to deduplicate: key is "func1:func2:addr", value is overlap with shortest chain
|
||||||
|
seen := make(map[string]*AbsoluteOverlap)
|
||||||
|
|
||||||
|
// For each function that uses absolute addresses
|
||||||
|
for funcName, addrSet := range fh.absoluteAddrs {
|
||||||
|
// Get all functions transitively called by this function
|
||||||
|
transitiveAddrs := fh.getTransitiveAddresses(funcName, make(map[string]bool))
|
||||||
|
|
||||||
|
// Check for overlaps
|
||||||
|
for addr := range addrSet {
|
||||||
|
for otherFunc, otherAddrs := range transitiveAddrs {
|
||||||
|
if otherAddrs[addr] {
|
||||||
|
// Found overlap!
|
||||||
|
callChain := fh.findCallChain(funcName, otherFunc)
|
||||||
|
|
||||||
|
// Create unique key for this conflict
|
||||||
|
key := fmt.Sprintf("%s:%s:%04x", funcName, otherFunc, addr)
|
||||||
|
|
||||||
|
// Keep only if this is the first occurrence or has shorter chain
|
||||||
|
if existing, exists := seen[key]; !exists || len(callChain) < len(existing.CallChain) {
|
||||||
|
seen[key] = &AbsoluteOverlap{
|
||||||
|
Func1: funcName,
|
||||||
|
Func2: otherFunc,
|
||||||
|
Address: addr,
|
||||||
|
CallChain: callChain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map to slice
|
||||||
|
var overlaps []AbsoluteOverlap
|
||||||
|
for _, overlap := range seen {
|
||||||
|
overlaps = append(overlaps, *overlap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return overlaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportAbsoluteOverlaps analyzes and reports overlapping absolute addresses to stderr
|
||||||
|
func (fh *FunctionHandler) ReportAbsoluteOverlaps() {
|
||||||
|
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
||||||
|
|
||||||
|
if len(overlaps) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group overlaps by address
|
||||||
|
byAddress := make(map[uint16]map[string]bool)
|
||||||
|
for _, overlap := range overlaps {
|
||||||
|
if byAddress[overlap.Address] == nil {
|
||||||
|
byAddress[overlap.Address] = make(map[string]bool)
|
||||||
|
}
|
||||||
|
byAddress[overlap.Address][overlap.Func1] = true
|
||||||
|
byAddress[overlap.Address][overlap.Func2] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract and sort addresses
|
||||||
|
addresses := make([]uint16, 0, len(byAddress))
|
||||||
|
for addr := range byAddress {
|
||||||
|
addresses = append(addresses, addr)
|
||||||
|
}
|
||||||
|
sort.Slice(addresses, func(i, j int) bool {
|
||||||
|
return addresses[i] < addresses[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "\nWarning: Detected overlapping absolute addresses in function call chains:\n\n")
|
||||||
|
|
||||||
|
// Report each address with its conflicting functions (in sorted order)
|
||||||
|
for _, addr := range addresses {
|
||||||
|
funcs := byAddress[addr]
|
||||||
|
funcList := make([]string, 0, len(funcs))
|
||||||
|
for f := range funcs {
|
||||||
|
funcList = append(funcList, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, " Address $%04X used by: %s\n", addr, strings.Join(funcList, ", "))
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, " %d function(s) share this address in overlapping call chains\n\n", len(funcList))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, " These conflicts may cause data corruption when functions are active simultaneously.\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTransitiveAddresses returns all addresses used by transitively called functions
|
||||||
|
// Returns map: funcName -> set of addresses used by that function
|
||||||
|
func (fh *FunctionHandler) getTransitiveAddresses(funcName string, visited map[string]bool) map[string]map[uint16]bool {
|
||||||
|
result := make(map[string]map[uint16]bool)
|
||||||
|
|
||||||
|
// Avoid infinite recursion
|
||||||
|
if visited[funcName] {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
visited[funcName] = true
|
||||||
|
|
||||||
|
// Get functions directly called by this function
|
||||||
|
for _, calledFunc := range fh.callGraph[funcName] {
|
||||||
|
// Add addresses used by the called function
|
||||||
|
if addrs, exists := fh.absoluteAddrs[calledFunc]; exists {
|
||||||
|
result[calledFunc] = addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively get addresses from transitively called functions
|
||||||
|
transitiveAddrs := fh.getTransitiveAddresses(calledFunc, visited)
|
||||||
|
for f, addrs := range transitiveAddrs {
|
||||||
|
result[f] = addrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// findCallChain finds the call path from funcA to funcB
|
||||||
|
func (fh *FunctionHandler) findCallChain(from, to string) []string {
|
||||||
|
// BFS to find shortest path
|
||||||
|
type node struct {
|
||||||
|
funcName string
|
||||||
|
path []string
|
||||||
|
}
|
||||||
|
|
||||||
|
queue := []node{{funcName: from, path: []string{from}}}
|
||||||
|
visited := make(map[string]bool)
|
||||||
|
|
||||||
|
for len(queue) > 0 {
|
||||||
|
current := queue[0]
|
||||||
|
queue = queue[1:]
|
||||||
|
|
||||||
|
if visited[current.funcName] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
visited[current.funcName] = true
|
||||||
|
|
||||||
|
if current.funcName == to {
|
||||||
|
return current.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explore called functions
|
||||||
|
for _, calledFunc := range fh.callGraph[current.funcName] {
|
||||||
|
if !visited[calledFunc] {
|
||||||
|
newPath := make([]string, len(current.path)+1)
|
||||||
|
copy(newPath, current.path)
|
||||||
|
newPath[len(current.path)] = calledFunc
|
||||||
|
queue = append(queue, node{funcName: calledFunc, path: newPath})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No path found (shouldn't happen if overlap detected correctly)
|
||||||
|
return []string{from, to}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -1074,3 +1075,340 @@ func TestParseImplicitDecl_Absolute(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAbsoluteOverlapDetection(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
code string
|
||||||
|
wantOverlapAddr uint16
|
||||||
|
wantFunc1 string
|
||||||
|
wantFunc2 string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "direct call with overlap",
|
||||||
|
code: `
|
||||||
|
BYTE dummy
|
||||||
|
FUNC funcB ( {BYTE data @ $fa} )
|
||||||
|
FEND
|
||||||
|
|
||||||
|
FUNC funcA ( {BYTE temp @ $fa} )
|
||||||
|
CALL funcB(dummy)
|
||||||
|
FEND
|
||||||
|
`,
|
||||||
|
wantOverlapAddr: 0xFA,
|
||||||
|
wantFunc1: "funcA",
|
||||||
|
wantFunc2: "funcB",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "transitive call with overlap",
|
||||||
|
code: `
|
||||||
|
BYTE dummy
|
||||||
|
FUNC funcC ( {BYTE data @ $fa} )
|
||||||
|
FEND
|
||||||
|
|
||||||
|
FUNC funcB
|
||||||
|
CALL funcC(dummy)
|
||||||
|
FEND
|
||||||
|
|
||||||
|
FUNC funcA ( {BYTE temp @ $fa} )
|
||||||
|
CALL funcB()
|
||||||
|
FEND
|
||||||
|
`,
|
||||||
|
wantOverlapAddr: 0xFA,
|
||||||
|
wantFunc1: "funcA",
|
||||||
|
wantFunc2: "funcC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "word overlap with byte",
|
||||||
|
code: `
|
||||||
|
BYTE dummy
|
||||||
|
FUNC funcB ( {BYTE data @ $fb} )
|
||||||
|
FEND
|
||||||
|
|
||||||
|
FUNC funcA ( {WORD value @ $fa} )
|
||||||
|
CALL funcB(dummy)
|
||||||
|
FEND
|
||||||
|
`,
|
||||||
|
wantOverlapAddr: 0xFB,
|
||||||
|
wantFunc1: "funcA",
|
||||||
|
wantFunc2: "funcB",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
st := NewSymbolTable()
|
||||||
|
ls := NewLabelStack("L")
|
||||||
|
csh := NewConstantStringHandler()
|
||||||
|
pragma := preproc.NewPragma()
|
||||||
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||||
|
|
||||||
|
// Parse code into lines
|
||||||
|
lines := strings.Split(strings.TrimSpace(tt.code), "\n")
|
||||||
|
|
||||||
|
// Process each line
|
||||||
|
for i, lineText := range lines {
|
||||||
|
lineText = strings.TrimSpace(lineText)
|
||||||
|
if lineText == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pline := preproc.Line{
|
||||||
|
Text: lineText,
|
||||||
|
LineNo: i + 1,
|
||||||
|
Filename: "test.c65",
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
|
||||||
|
// Variable declaration - add to symbol table
|
||||||
|
parts := strings.Fields(lineText)
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
varKind := KindByte
|
||||||
|
if strings.ToUpper(parts[0]) == "WORD" {
|
||||||
|
varKind = KindWord
|
||||||
|
}
|
||||||
|
st.AddVar(parts[1], "", varKind, 0)
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(lineText, "FUNC ") {
|
||||||
|
_, err := fh.HandleFuncDecl(pline)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(lineText, "CALL ") {
|
||||||
|
_, err := fh.HandleFuncCall(pline)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
||||||
|
}
|
||||||
|
} else if lineText == "FEND" {
|
||||||
|
fh.EndFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze overlaps
|
||||||
|
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
||||||
|
|
||||||
|
if len(overlaps) == 0 {
|
||||||
|
t.Fatalf("Expected overlap at address $%04X, but none detected", tt.wantOverlapAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the expected overlap
|
||||||
|
found := false
|
||||||
|
for _, overlap := range overlaps {
|
||||||
|
if overlap.Address == tt.wantOverlapAddr &&
|
||||||
|
overlap.Func1 == tt.wantFunc1 &&
|
||||||
|
overlap.Func2 == tt.wantFunc2 {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected overlap at $%04X between %s and %s, got: %+v",
|
||||||
|
tt.wantOverlapAddr, tt.wantFunc1, tt.wantFunc2, overlaps)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAbsoluteLocalVariables(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
code string
|
||||||
|
wantOverlapAddr uint16
|
||||||
|
wantFunc1 string
|
||||||
|
wantFunc2 string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "local absolute variables overlap",
|
||||||
|
code: `
|
||||||
|
FUNC funcB
|
||||||
|
BYTE localB @ $fa
|
||||||
|
FEND
|
||||||
|
|
||||||
|
FUNC funcA
|
||||||
|
BYTE localA @ $fa
|
||||||
|
CALL funcB()
|
||||||
|
FEND
|
||||||
|
`,
|
||||||
|
wantOverlapAddr: 0xFA,
|
||||||
|
wantFunc1: "funcA",
|
||||||
|
wantFunc2: "funcB",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
st := NewSymbolTable()
|
||||||
|
ls := NewLabelStack("L")
|
||||||
|
csh := NewConstantStringHandler()
|
||||||
|
pragma := preproc.NewPragma()
|
||||||
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||||
|
|
||||||
|
// Connect symbol table to function handler
|
||||||
|
st.SetFunctionHandler(fh)
|
||||||
|
|
||||||
|
// Parse code into lines
|
||||||
|
lines := strings.Split(strings.TrimSpace(tt.code), "\n")
|
||||||
|
currentFunc := ""
|
||||||
|
|
||||||
|
// Process each line
|
||||||
|
for i, lineText := range lines {
|
||||||
|
lineText = strings.TrimSpace(lineText)
|
||||||
|
if lineText == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pline := preproc.Line{
|
||||||
|
Text: lineText,
|
||||||
|
LineNo: i + 1,
|
||||||
|
Filename: "test.c65",
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
|
||||||
|
// Parse variable declaration
|
||||||
|
parts := strings.Fields(lineText)
|
||||||
|
if len(parts) >= 4 && parts[2] == "@" {
|
||||||
|
// Absolute variable: BYTE name @ $addr
|
||||||
|
varKind := KindByte
|
||||||
|
if strings.ToUpper(parts[0]) == "WORD" {
|
||||||
|
varKind = KindWord
|
||||||
|
}
|
||||||
|
addrStr := strings.TrimPrefix(parts[3], "$")
|
||||||
|
var addr uint16
|
||||||
|
fmt.Sscanf(addrStr, "%x", &addr)
|
||||||
|
st.AddAbsolute(parts[1], currentFunc, varKind, addr)
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(lineText, "FUNC ") {
|
||||||
|
_, err := fh.HandleFuncDecl(pline)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||||
|
}
|
||||||
|
// Extract function name
|
||||||
|
funcParts := strings.Fields(lineText)
|
||||||
|
if len(funcParts) >= 2 {
|
||||||
|
currentFunc = funcParts[1]
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(lineText, "CALL ") {
|
||||||
|
_, err := fh.HandleFuncCall(pline)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
||||||
|
}
|
||||||
|
} else if lineText == "FEND" {
|
||||||
|
fh.EndFunction()
|
||||||
|
currentFunc = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze overlaps
|
||||||
|
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
||||||
|
|
||||||
|
if len(overlaps) == 0 {
|
||||||
|
t.Fatalf("Expected overlap at address $%04X, but none detected", tt.wantOverlapAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the expected overlap
|
||||||
|
found := false
|
||||||
|
for _, overlap := range overlaps {
|
||||||
|
if overlap.Address == tt.wantOverlapAddr &&
|
||||||
|
overlap.Func1 == tt.wantFunc1 &&
|
||||||
|
overlap.Func2 == tt.wantFunc2 {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected overlap at $%04X between %s and %s, got: %+v",
|
||||||
|
tt.wantOverlapAddr, tt.wantFunc1, tt.wantFunc2, overlaps)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAbsoluteNoOverlap(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
code string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "different addresses",
|
||||||
|
code: `
|
||||||
|
BYTE dummy
|
||||||
|
FUNC funcB ( {BYTE data @ $fb} )
|
||||||
|
FEND
|
||||||
|
|
||||||
|
FUNC funcA ( {BYTE temp @ $fa} )
|
||||||
|
CALL funcB(dummy)
|
||||||
|
FEND
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no call relationship",
|
||||||
|
code: `
|
||||||
|
FUNC funcA ( {BYTE temp @ $fa} )
|
||||||
|
FEND
|
||||||
|
|
||||||
|
FUNC funcB ( {BYTE data @ $fa} )
|
||||||
|
FEND
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
st := NewSymbolTable()
|
||||||
|
ls := NewLabelStack("L")
|
||||||
|
csh := NewConstantStringHandler()
|
||||||
|
pragma := preproc.NewPragma()
|
||||||
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||||
|
|
||||||
|
// Parse code into lines
|
||||||
|
lines := strings.Split(strings.TrimSpace(tt.code), "\n")
|
||||||
|
|
||||||
|
// Process each line
|
||||||
|
for i, lineText := range lines {
|
||||||
|
lineText = strings.TrimSpace(lineText)
|
||||||
|
if lineText == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pline := preproc.Line{
|
||||||
|
Text: lineText,
|
||||||
|
LineNo: i + 1,
|
||||||
|
Filename: "test.c65",
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
|
||||||
|
// Variable declaration - add to symbol table
|
||||||
|
parts := strings.Fields(lineText)
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
varKind := KindByte
|
||||||
|
if strings.ToUpper(parts[0]) == "WORD" {
|
||||||
|
varKind = KindWord
|
||||||
|
}
|
||||||
|
st.AddVar(parts[1], "", varKind, 0)
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(lineText, "FUNC ") {
|
||||||
|
_, err := fh.HandleFuncDecl(pline)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(lineText, "CALL ") {
|
||||||
|
_, err := fh.HandleFuncCall(pline)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
||||||
|
}
|
||||||
|
} else if lineText == "FEND" {
|
||||||
|
fh.EndFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze overlaps
|
||||||
|
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
||||||
|
|
||||||
|
if len(overlaps) > 0 {
|
||||||
|
t.Errorf("Expected no overlaps, but got: %+v", overlaps)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,16 @@ func (s *Symbol) FullName() string {
|
||||||
|
|
||||||
// SymbolTable manages variable and constant declarations
|
// SymbolTable manages variable and constant declarations
|
||||||
type SymbolTable struct {
|
type SymbolTable struct {
|
||||||
symbols []*Symbol // insertion order
|
symbols []*Symbol // insertion order
|
||||||
byFullName map[string]*Symbol // fullname -> symbol
|
byFullName map[string]*Symbol // fullname -> symbol
|
||||||
byScope map[string]map[string]*Symbol // scope -> name -> symbol
|
byScope map[string]map[string]*Symbol // scope -> name -> symbol
|
||||||
|
funcHandler FunctionHandlerInterface // for notifying about absolute variables in functions
|
||||||
|
}
|
||||||
|
|
||||||
|
// FunctionHandlerInterface allows SymbolTable to notify about absolute variables
|
||||||
|
// without creating a circular dependency
|
||||||
|
type FunctionHandlerInterface interface {
|
||||||
|
RecordAbsoluteVar(funcScope string, addr uint16, isWord bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSymbolTable creates a new symbol table
|
// NewSymbolTable creates a new symbol table
|
||||||
|
|
@ -76,6 +83,11 @@ func NewSymbolTable() *SymbolTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetFunctionHandler sets the function handler for absolute variable notifications
|
||||||
|
func (st *SymbolTable) SetFunctionHandler(fh FunctionHandlerInterface) {
|
||||||
|
st.funcHandler = fh
|
||||||
|
}
|
||||||
|
|
||||||
// AddVar adds a regular variable (byte or word)
|
// AddVar adds a regular variable (byte or word)
|
||||||
func (st *SymbolTable) AddVar(name, scope string, kind VarKind, initValue uint16) error {
|
func (st *SymbolTable) AddVar(name, scope string, kind VarKind, initValue uint16) error {
|
||||||
var flags SymbolFlags
|
var flags SymbolFlags
|
||||||
|
|
@ -149,6 +161,11 @@ func (st *SymbolTable) AddAbsolute(name, scope string, kind VarKind, addr uint16
|
||||||
return fmt.Errorf("unknown variable kind: %d", kind)
|
return fmt.Errorf("unknown variable kind: %d", kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify function handler about absolute variable in function scope
|
||||||
|
if st.funcHandler != nil && scope != "" {
|
||||||
|
st.funcHandler.RecordAbsoluteVar(scope, addr, kind == KindWord)
|
||||||
|
}
|
||||||
|
|
||||||
return st.add(&Symbol{
|
return st.add(&Symbol{
|
||||||
Name: name,
|
Name: name,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue