repos / gbc

GBC - Go B Compiler
git clone https://github.com/xplshn/gbc.git

gbc / pkg / codegen
xplshn  ·  2025-09-10

codegen.go

Go
   1package codegen
   2
   3import (
   4	"fmt"
   5
   6	"github.com/xplshn/gbc/pkg/ast"
   7	"github.com/xplshn/gbc/pkg/config"
   8	"github.com/xplshn/gbc/pkg/ir"
   9	"github.com/xplshn/gbc/pkg/token"
  10	"github.com/xplshn/gbc/pkg/util"
  11)
  12
  13type symbolType int
  14
  15const (
  16	symVar symbolType = iota
  17	symFunc
  18	symLabel
  19	symType
  20	symExtrn
  21)
  22
  23type symbol struct {
  24	Name        string
  25	Type        symbolType
  26	BxType      *ast.BxType
  27	IRVal       ir.Value
  28	IsVector    bool
  29	IsByteArray bool
  30	StackOffset int64
  31	Next        *symbol
  32	Node        *ast.Node
  33}
  34
  35type scope struct{ Symbols *symbol; Parent *scope }
  36
  37type autoVarInfo struct{ Node *ast.Node; Size int64 }
  38
  39type Context struct {
  40	prog             *ir.Program
  41	inlineAsm        string
  42	tempCount        int
  43	labelCount       int
  44	currentScope     *scope
  45	currentFunc      *ir.Func
  46	currentBlock     *ir.BasicBlock
  47	breakLabel       *ir.Label
  48	continueLabel    *ir.Label
  49	wordSize         int
  50	stackAlign       int
  51	isTypedPass      bool
  52	cfg              *config.Config
  53	switchCaseLabels map[*ast.Node]*ir.Label
  54}
  55
  56func NewContext(cfg *config.Config) *Context {
  57	return &Context{
  58		prog: &ir.Program{
  59			Strings:       make(map[string]string),
  60			ExtrnFuncs:    make([]string, 0),
  61			ExtrnVars:     make(map[string]bool),
  62			WordSize:      cfg.WordSize,
  63			GlobalSymbols: make(map[string]*ast.Node),
  64		},
  65		currentScope:     newScope(nil),
  66		wordSize:         cfg.WordSize,
  67		stackAlign:       cfg.StackAlignment,
  68		isTypedPass:      cfg.IsFeatureEnabled(config.FeatTyped),
  69		cfg:              cfg,
  70		switchCaseLabels: make(map[*ast.Node]*ir.Label),
  71	}
  72}
  73
  74func newScope(parent *scope) *scope { return &scope{Parent: parent} }
  75
  76func (ctx *Context) enterScope() { ctx.currentScope = newScope(ctx.currentScope) }
  77func (ctx *Context) exitScope() {
  78	if ctx.currentScope.Parent != nil { ctx.currentScope = ctx.currentScope.Parent }
  79}
  80
  81func (ctx *Context) findSymbol(name string) *symbol {
  82	return ctx.findSymbolOfType(name, -1, false) // -1 means any type except symType
  83}
  84
  85func (ctx *Context) findTypeSymbol(name string) *symbol {
  86	return ctx.findSymbolOfType(name, symType, false)
  87}
  88
  89func (ctx *Context) findSymbolInCurrentScope(name string) *symbol {
  90	return ctx.findSymbolOfType(name, -1, true) // any type, current scope only
  91}
  92
  93func (ctx *Context) findSymbolOfType(name string, wantType symbolType, currentOnly bool) *symbol {
  94	for s := ctx.currentScope; s != nil; s = s.Parent {
  95		for sym := s.Symbols; sym != nil; sym = sym.Next {
  96			if sym.Name == name {
  97				if wantType == -1 || (wantType == symType) == (sym.Type == symType) { return sym }
  98			}
  99		}
 100		if currentOnly { break }
 101	}
 102	return nil
 103}
 104
 105func (ctx *Context) addSymbol(name string, symType symbolType, bxType *ast.BxType, isVector bool, node *ast.Node) *symbol {
 106	var irVal ir.Value
 107	switch symType {
 108	case symVar:
 109		if ctx.currentScope.Parent == nil {
 110			irVal = &ir.Global{Name: name}
 111		} else {
 112			irVal = ctx.newTemp()
 113			if t, ok := irVal.(*ir.Temporary); ok {
 114				t.Name = name
 115			}
 116		}
 117	case symFunc, symExtrn: irVal = &ir.Global{Name: name}
 118	case symLabel: irVal = &ir.Label{Name: name}
 119	}
 120
 121	sym := &symbol{
 122		Name: name, Type: symType, BxType: bxType, IRVal: irVal,
 123		IsVector: isVector, Next: ctx.currentScope.Symbols, Node: node,
 124	}
 125	ctx.currentScope.Symbols = sym
 126	return sym
 127}
 128
 129func (ctx *Context) newTemp() *ir.Temporary {
 130	t := &ir.Temporary{ID: ctx.tempCount}
 131	ctx.tempCount++
 132	return t
 133}
 134
 135func (ctx *Context) newLabel() *ir.Label {
 136	l := &ir.Label{Name: fmt.Sprintf("L%d", ctx.labelCount)}
 137	ctx.labelCount++
 138	return l
 139}
 140
 141func (ctx *Context) startBlock(label *ir.Label) {
 142	block := &ir.BasicBlock{Label: label}
 143	ctx.currentFunc.Blocks = append(ctx.currentFunc.Blocks, block)
 144	ctx.currentBlock = block
 145}
 146
 147func (ctx *Context) addInstr(instr *ir.Instruction) {
 148	if ctx.currentBlock == nil { ctx.startBlock(ctx.newLabel()) }
 149	ctx.currentBlock.Instructions = append(ctx.currentBlock.Instructions, instr)
 150}
 151
 152func (ctx *Context) addString(value string) ir.Value {
 153	if label, ok := ctx.prog.Strings[value]; ok { return &ir.Global{Name: label} }
 154	label := fmt.Sprintf("str%d", len(ctx.prog.Strings))
 155	ctx.prog.Strings[value] = label
 156	return &ir.Global{Name: label}
 157}
 158
 159func (ctx *Context) evalConstExpr(node *ast.Node) (int64, bool) {
 160	if node == nil { return 0, false }
 161	folded := ast.FoldConstants(node)
 162	if folded.Type == ast.Number { return folded.Data.(ast.NumberNode).Value, true }
 163	if folded.Type == ast.Ident {
 164		identName := folded.Data.(ast.IdentNode).Name
 165		sym := ctx.findSymbol(identName)
 166		if sym != nil && sym.Node != nil && sym.Node.Type == ast.VarDecl {
 167			decl := sym.Node.Data.(ast.VarDeclNode)
 168			if len(decl.InitList) == 1 {
 169				if decl.InitList[0] == node { return 0, false }
 170				return ctx.evalConstExpr(decl.InitList[0])
 171			}
 172		}
 173	}
 174	return 0, false
 175}
 176
 177func (ctx *Context) getSizeof(typ *ast.BxType) int64 {
 178	if typ == nil || typ.Kind == ast.TYPE_UNTYPED { return int64(ctx.wordSize) }
 179
 180	switch typ.Kind {
 181	case ast.TYPE_VOID: return 0
 182	case ast.TYPE_POINTER: return int64(ctx.wordSize)
 183	case ast.TYPE_ARRAY:
 184		elemSize := ctx.getSizeof(typ.Base)
 185		var arrayLen int64 = 1
 186		if typ.ArraySize != nil {
 187			if val, ok := ctx.evalConstExpr(typ.ArraySize); ok {
 188				arrayLen = val
 189			} else {
 190				util.Error(typ.ArraySize.Tok, "Array size must be a constant expression")
 191			}
 192		}
 193		return elemSize * arrayLen
 194	case ast.TYPE_PRIMITIVE, ast.TYPE_LITERAL_INT:
 195		resolver := ir.NewTypeSizeResolver(ctx.wordSize)
 196		if size := resolver.GetTypeSize(typ.Name); size > 0 {
 197			return size
 198		}
 199		// Fallback for user-defined types
 200		if sym := ctx.findTypeSymbol(typ.Name); sym != nil {
 201			return ctx.getSizeof(sym.BxType)
 202		}
 203		return int64(ctx.wordSize)
 204	case ast.TYPE_ENUM:
 205		return ctx.getSizeof(ast.TypeInt)
 206	case ast.TYPE_FLOAT, ast.TYPE_LITERAL_FLOAT:
 207		if typ.Kind == ast.TYPE_LITERAL_FLOAT {
 208			return int64(ctx.wordSize)
 209		}
 210		resolver := ir.NewTypeSizeResolver(ctx.wordSize)
 211		return resolver.GetTypeSize(typ.Name)
 212	case ast.TYPE_STRUCT:
 213		var totalSize, maxAlign int64 = 0, 1
 214		for _, field := range typ.Fields {
 215			fieldData := field.Data.(ast.VarDeclNode)
 216			fieldAlign := ctx.getAlignof(fieldData.Type)
 217			if fieldAlign > maxAlign {
 218				maxAlign = fieldAlign
 219			}
 220			totalSize = util.AlignUp(totalSize, fieldAlign)
 221			totalSize += ctx.getSizeof(fieldData.Type)
 222		}
 223		if maxAlign == 0 {
 224			maxAlign = 1
 225		}
 226		return util.AlignUp(totalSize, maxAlign)
 227	}
 228	return int64(ctx.wordSize)
 229}
 230
 231func (ctx *Context) getAlignof(typ *ast.BxType) int64 {
 232	if typ == nil { return int64(ctx.wordSize) }
 233
 234	if (typ.Kind == ast.TYPE_PRIMITIVE || typ.Kind == ast.TYPE_STRUCT) && typ.Name != "" {
 235		if sym := ctx.findTypeSymbol(typ.Name); sym != nil {
 236			if sym.BxType != typ { return ctx.getAlignof(sym.BxType) }
 237		}
 238	}
 239
 240	if typ.Kind == ast.TYPE_UNTYPED { return int64(ctx.wordSize) }
 241	switch typ.Kind {
 242	case ast.TYPE_VOID: return 1
 243	case ast.TYPE_POINTER: return int64(ctx.wordSize)
 244	case ast.TYPE_ARRAY: return ctx.getAlignof(typ.Base)
 245	case ast.TYPE_PRIMITIVE, ast.TYPE_FLOAT, ast.TYPE_ENUM, ast.TYPE_LITERAL_INT, ast.TYPE_LITERAL_FLOAT: return ctx.getSizeof(typ)
 246	case ast.TYPE_STRUCT:
 247		var maxAlign int64 = 1
 248		for _, field := range typ.Fields {
 249			fieldAlign := ctx.getAlignof(field.Data.(ast.VarDeclNode).Type)
 250			if fieldAlign > maxAlign { maxAlign = fieldAlign }
 251		}
 252		return maxAlign
 253	}
 254	return int64(ctx.wordSize)
 255}
 256
 257func (ctx *Context) GenerateIR(root *ast.Node) (*ir.Program, string) {
 258	ctx.collectGlobals(root)
 259	ctx.collectStrings(root)
 260	if !ctx.isTypedPass {
 261		ctx.findByteArrays(root)
 262	}
 263	ctx.codegenStmt(root)
 264
 265	ctx.prog.BackendTempCount = ctx.tempCount
 266	return ctx.prog, ctx.inlineAsm
 267}
 268
 269func walkAST(node *ast.Node, visitor func(n *ast.Node)) {
 270	if node == nil { return }
 271	visitor(node)
 272
 273	switch d := node.Data.(type) {
 274	case ast.AssignNode:
 275		walkAST(d.Lhs, visitor)
 276		walkAST(d.Rhs, visitor)
 277	case ast.BinaryOpNode:
 278		walkAST(d.Left, visitor)
 279		walkAST(d.Right, visitor)
 280	case ast.UnaryOpNode:
 281		walkAST(d.Expr, visitor)
 282	case ast.PostfixOpNode:
 283		walkAST(d.Expr, visitor)
 284	case ast.IndirectionNode:
 285		walkAST(d.Expr, visitor)
 286	case ast.AddressOfNode:
 287		walkAST(d.LValue, visitor)
 288	case ast.TernaryNode:
 289		walkAST(d.Cond, visitor)
 290		walkAST(d.ThenExpr, visitor)
 291		walkAST(d.ElseExpr, visitor)
 292	case ast.SubscriptNode:
 293		walkAST(d.Array, visitor)
 294		walkAST(d.Index, visitor)
 295	case ast.FuncCallNode:
 296		walkAST(d.FuncExpr, visitor)
 297		for _, arg := range d.Args {
 298			walkAST(arg, visitor)
 299		}
 300	case ast.FuncDeclNode:
 301		walkAST(d.Body, visitor)
 302	case ast.VarDeclNode:
 303		for _, init := range d.InitList {
 304			walkAST(init, visitor)
 305		}
 306		walkAST(d.SizeExpr, visitor)
 307	case ast.MultiVarDeclNode:
 308		for _, decl := range d.Decls {
 309			walkAST(decl, visitor)
 310		}
 311	case ast.IfNode:
 312		walkAST(d.Cond, visitor)
 313		walkAST(d.ThenBody, visitor)
 314		walkAST(d.ElseBody, visitor)
 315	case ast.WhileNode:
 316		walkAST(d.Cond, visitor)
 317		walkAST(d.Body, visitor)
 318	case ast.ReturnNode:
 319		walkAST(d.Expr, visitor)
 320	case ast.BlockNode:
 321		for _, s := range d.Stmts {
 322			walkAST(s, visitor)
 323		}
 324	case ast.SwitchNode:
 325		walkAST(d.Expr, visitor)
 326		walkAST(d.Body, visitor)
 327	case ast.CaseNode:
 328		for _, v := range d.Values {
 329			walkAST(v, visitor)
 330		}
 331		walkAST(d.Body, visitor)
 332	case ast.DefaultNode:
 333		walkAST(d.Body, visitor)
 334	case ast.LabelNode:
 335		walkAST(d.Stmt, visitor)
 336	}
 337}
 338
 339func (ctx *Context) collectGlobals(node *ast.Node) {
 340	if node == nil {
 341		return
 342	}
 343
 344	switch node.Type {
 345	case ast.Block:
 346		for _, stmt := range node.Data.(ast.BlockNode).Stmts {
 347			ctx.collectGlobals(stmt)
 348		}
 349	case ast.VarDecl:
 350		if ctx.currentScope.Parent == nil {
 351			d := node.Data.(ast.VarDeclNode)
 352			existingSym := ctx.findSymbolInCurrentScope(d.Name)
 353			if existingSym == nil {
 354				ctx.addSymbol(d.Name, symVar, d.Type, d.IsVector, node)
 355			} else if existingSym.Type == symFunc || existingSym.Type == symExtrn {
 356				util.Warn(ctx.cfg, config.WarnExtra, node.Tok, "Definition of '%s' overrides previous external declaration", d.Name)
 357				existingSym.Type, existingSym.IsVector, existingSym.BxType, existingSym.Node = symVar, d.IsVector, d.Type, node
 358			} else if existingSym.Type == symVar {
 359				util.Warn(ctx.cfg, config.WarnExtra, node.Tok, "Redefinition of variable '%s'", d.Name)
 360				existingSym.IsVector, existingSym.BxType, existingSym.Node = d.IsVector, d.Type, node
 361			}
 362		}
 363	case ast.MultiVarDecl:
 364		if ctx.currentScope.Parent == nil {
 365			for _, decl := range node.Data.(ast.MultiVarDeclNode).Decls {
 366				ctx.collectGlobals(decl)
 367			}
 368		}
 369	case ast.FuncDecl:
 370		d := node.Data.(ast.FuncDeclNode)
 371		ctx.prog.GlobalSymbols[d.Name] = node
 372		existingSym := ctx.findSymbolInCurrentScope(d.Name)
 373		if existingSym == nil {
 374			ctx.addSymbol(d.Name, symFunc, d.ReturnType, false, node)
 375		} else if existingSym.Type != symFunc {
 376			util.Warn(ctx.cfg, config.WarnExtra, node.Tok, "Redefinition of '%s' as a function", d.Name)
 377			existingSym.Type, existingSym.IsVector, existingSym.BxType, existingSym.Node = symFunc, false, d.ReturnType, node
 378		}
 379	case ast.ExtrnDecl:
 380		d := node.Data.(ast.ExtrnDeclNode)
 381		for _, nameNode := range d.Names {
 382			name := nameNode.Data.(ast.IdentNode).Name
 383			if ctx.findSymbolInCurrentScope(name) == nil {
 384				ctx.addSymbol(name, symExtrn, ast.TypeUntyped, false, nameNode)
 385				isAlreadyExtrn := false
 386				for _, extrnName := range ctx.prog.ExtrnFuncs {
 387					if extrnName == name {
 388						isAlreadyExtrn = true
 389						break
 390					}
 391				}
 392				if !isAlreadyExtrn {
 393					ctx.prog.ExtrnFuncs = append(ctx.prog.ExtrnFuncs, name)
 394				}
 395			}
 396		}
 397	case ast.TypeDecl:
 398		d := node.Data.(ast.TypeDeclNode)
 399		if ctx.findSymbolInCurrentScope(d.Name) == nil {
 400			ctx.addSymbol(d.Name, symType, d.Type, false, node)
 401		}
 402	case ast.EnumDecl:
 403		d := node.Data.(ast.EnumDeclNode)
 404		if ctx.findSymbolInCurrentScope(d.Name) == nil {
 405			enumType := &ast.BxType{Kind: ast.TYPE_ENUM, Name: d.Name, EnumMembers: d.Members, Base: ast.TypeInt}
 406			ctx.addSymbol(d.Name, symType, enumType, false, node)
 407		}
 408		for _, memberNode := range d.Members {
 409			ctx.collectGlobals(memberNode)
 410		}
 411	}
 412}
 413
 414func (ctx *Context) findByteArrays(root *ast.Node) {
 415	for {
 416		changedInPass := false
 417		visitor := func(n *ast.Node) {
 418			if n == nil {
 419				return
 420			}
 421			switch n.Type {
 422			case ast.VarDecl:
 423				d := n.Data.(ast.VarDeclNode)
 424				if d.IsVector && len(d.InitList) == 1 && d.InitList[0].Type == ast.String {
 425					if sym := ctx.findSymbol(d.Name); sym != nil && !sym.IsByteArray {
 426						sym.IsByteArray = true
 427						changedInPass = true
 428					}
 429				}
 430			case ast.Assign:
 431				d := n.Data.(ast.AssignNode)
 432				if d.Lhs.Type != ast.Ident {
 433					return
 434				}
 435				lhsSym := ctx.findSymbol(d.Lhs.Data.(ast.IdentNode).Name)
 436				if lhsSym == nil || lhsSym.IsByteArray {
 437					return
 438				}
 439				rhsIsByteArray := false
 440				switch d.Rhs.Type {
 441				case ast.String:
 442					rhsIsByteArray = true
 443				case ast.Ident:
 444					if rhsSym := ctx.findSymbol(d.Rhs.Data.(ast.IdentNode).Name); rhsSym != nil && rhsSym.IsByteArray {
 445						rhsIsByteArray = true
 446					}
 447				}
 448				if rhsIsByteArray {
 449					lhsSym.IsByteArray = true
 450					changedInPass = true
 451				}
 452			}
 453		}
 454		walkAST(root, visitor)
 455		if !changedInPass {
 456			break
 457		}
 458	}
 459}
 460
 461func (ctx *Context) collectStrings(root *ast.Node) {
 462	walkAST(root, func(n *ast.Node) {
 463		if n != nil && n.Type == ast.String {
 464			ctx.addString(n.Data.(ast.StringNode).Value)
 465		}
 466	})
 467}
 468
 469func (ctx *Context) genLoad(addr ir.Value, typ *ast.BxType) ir.Value {
 470	res := ctx.newTemp()
 471	loadType := ir.GetType(typ, ctx.wordSize)
 472	ctx.addInstr(&ir.Instruction{Op: ir.OpLoad, Typ: loadType, Result: res, Args: []ir.Value{addr}})
 473	return res
 474}
 475
 476func (ctx *Context) genStore(addr, value ir.Value, typ *ast.BxType) {
 477	storeType := ir.GetType(typ, ctx.wordSize)
 478	ctx.addInstr(&ir.Instruction{Op: ir.OpStore, Typ: storeType, Args: []ir.Value{value, addr}})
 479}
 480
 481func (ctx *Context) codegenMemberAccessAddr(node *ast.Node) ir.Value {
 482	d := node.Data.(ast.MemberAccessNode)
 483	structType := d.Expr.Typ
 484
 485	if structType == nil {
 486		if d.Expr.Type == ast.Ident {
 487			if sym := ctx.findSymbol(d.Expr.Data.(ast.IdentNode).Name); sym != nil {
 488				structType = sym.BxType
 489			}
 490		}
 491	}
 492
 493	if structType == nil {
 494		util.Error(node.Tok, "internal: cannot determine type of struct for member access")
 495		return nil
 496	}
 497
 498	var structAddr ir.Value
 499	if structType.Kind == ast.TYPE_POINTER {
 500		structAddr, _ = ctx.codegenExpr(d.Expr)
 501	} else {
 502		// Check if this is a struct parameter (which is passed as pointer)
 503		if d.Expr.Type == ast.Ident {
 504			name := d.Expr.Data.(ast.IdentNode).Name
 505			if sym := ctx.findSymbol(name); sym != nil {
 506				// Check if this is a function parameter that has struct type
 507				isStructParam := false
 508				if sym.Node != nil && sym.Node.Parent != nil && sym.Node.Parent.Type == ast.FuncDecl {
 509					// Resolve the struct type
 510					paramStructType := structType
 511					if paramStructType != nil && paramStructType.Kind != ast.TYPE_STRUCT && paramStructType.Name != "" {
 512						if typeSym := ctx.findTypeSymbol(paramStructType.Name); typeSym != nil && typeSym.BxType.Kind == ast.TYPE_STRUCT {
 513							paramStructType = typeSym.BxType
 514						}
 515					}
 516					if paramStructType != nil && paramStructType.Kind == ast.TYPE_STRUCT {
 517						isStructParam = true
 518					}
 519				}
 520
 521				if isStructParam {
 522					// For struct parameters, use the parameter value directly (it's already a pointer)
 523					structAddr, _ = ctx.codegenExpr(d.Expr)
 524				} else {
 525					structAddr = ctx.codegenLvalue(d.Expr)
 526				}
 527			} else {
 528				structAddr = ctx.codegenLvalue(d.Expr)
 529			}
 530		} else {
 531			structAddr = ctx.codegenLvalue(d.Expr)
 532		}
 533	}
 534
 535	baseType := structType
 536	if baseType.Kind == ast.TYPE_POINTER {
 537		baseType = baseType.Base
 538	}
 539
 540	if baseType.Kind != ast.TYPE_STRUCT && baseType.Name != "" {
 541		if sym := ctx.findTypeSymbol(baseType.Name); sym != nil && sym.BxType.Kind == ast.TYPE_STRUCT {
 542			baseType = sym.BxType
 543		}
 544	}
 545
 546	if baseType.Kind != ast.TYPE_STRUCT {
 547		util.Error(node.Tok, "internal: member access on non-struct type '%s'", baseType.Name)
 548		return nil
 549	}
 550
 551	var offset int64
 552	found := false
 553	memberName := d.Member.Data.(ast.IdentNode).Name
 554
 555	for _, fieldNode := range baseType.Fields {
 556		fieldData := fieldNode.Data.(ast.VarDeclNode)
 557		fieldAlign := ctx.getAlignof(fieldData.Type)
 558
 559		offset = util.AlignUp(offset, fieldAlign)
 560
 561		if fieldData.Name == memberName {
 562			found = true
 563			break
 564		}
 565		offset += ctx.getSizeof(fieldData.Type)
 566	}
 567
 568	if !found {
 569		util.Error(node.Tok, "internal: could not find member '%s' during codegen", memberName)
 570		return nil
 571	}
 572
 573	if offset == 0 {
 574		return structAddr
 575	}
 576
 577	resultAddr := ctx.newTemp()
 578	ctx.addInstr(&ir.Instruction{
 579		Op:     ir.OpAdd,
 580		Typ:    ir.GetType(nil, ctx.wordSize),
 581		Result: resultAddr,
 582		Args:   []ir.Value{structAddr, &ir.Const{Value: offset}},
 583	})
 584	return resultAddr
 585}
 586
 587func (ctx *Context) codegenLvalue(node *ast.Node) ir.Value {
 588	if node == nil {
 589		util.Error(token.Token{}, "Internal error: null l-value node in codegen")
 590		return nil
 591	}
 592	switch node.Type {
 593	case ast.Ident:
 594		name := node.Data.(ast.IdentNode).Name
 595		sym := ctx.findSymbol(name)
 596		if sym == nil {
 597			util.Warn(ctx.cfg, config.WarnImplicitDecl, node.Tok, "Implicit declaration of variable '%s'", name)
 598			sym = ctx.addSymbol(name, symVar, ast.TypeUntyped, false, node)
 599		}
 600		if sym.Type == symFunc {
 601			util.Error(node.Tok, "Cannot assign to function '%s'", name)
 602			return nil
 603		}
 604		return sym.IRVal
 605	case ast.Indirection:
 606		res, _ := ctx.codegenExpr(node.Data.(ast.IndirectionNode).Expr)
 607		return res
 608	case ast.Subscript:
 609		return ctx.codegenSubscriptAddr(node)
 610	case ast.MemberAccess:
 611		return ctx.codegenMemberAccessAddr(node)
 612	case ast.FuncCall:
 613		if node.Typ != nil && node.Typ.Kind == ast.TYPE_STRUCT {
 614			res, _ := ctx.codegenExpr(node)
 615			return res
 616		}
 617	}
 618	util.Error(node.Tok, "Expression is not a valid l-value")
 619	return nil
 620}
 621
 622func (ctx *Context) codegenLogicalCond(node *ast.Node, trueL, falseL *ir.Label) {
 623	if node.Type == ast.BinaryOp {
 624		d := node.Data.(ast.BinaryOpNode)
 625		if d.Op == token.OrOr {
 626			newFalseL := ctx.newLabel()
 627			ctx.codegenLogicalCond(d.Left, trueL, newFalseL)
 628			ctx.startBlock(newFalseL)
 629			ctx.codegenLogicalCond(d.Right, trueL, falseL)
 630			return
 631		}
 632		if d.Op == token.AndAnd {
 633			newTrueL := ctx.newLabel()
 634			ctx.codegenLogicalCond(d.Left, newTrueL, falseL)
 635			ctx.startBlock(newTrueL)
 636			ctx.codegenLogicalCond(d.Right, trueL, falseL)
 637			return
 638		}
 639	}
 640
 641	condVal, _ := ctx.codegenExpr(node)
 642	ctx.addInstr(&ir.Instruction{Op: ir.OpJnz, Args: []ir.Value{condVal, trueL, falseL}})
 643	ctx.currentBlock = nil
 644}
 645
 646func (ctx *Context) codegenExpr(node *ast.Node) (result ir.Value, terminates bool) {
 647	if node == nil {
 648		return &ir.Const{Value: 0}, false
 649	}
 650
 651	switch node.Type {
 652	case ast.Number:
 653		return &ir.Const{Value: node.Data.(ast.NumberNode).Value}, false
 654	case ast.FloatNumber:
 655		typ := ir.GetType(node.Typ, ctx.wordSize)
 656		return &ir.FloatConst{Value: node.Data.(ast.FloatNumberNode).Value, Typ: typ}, false
 657	case ast.String:
 658		return ctx.addString(node.Data.(ast.StringNode).Value), false
 659	case ast.Nil:
 660		return &ir.Const{Value: 0}, false
 661	case ast.Ident:
 662		return ctx.codegenIdent(node)
 663	case ast.Assign:
 664		return ctx.codegenAssign(node)
 665	case ast.MultiAssign:
 666		return ctx.codegenMultiAssign(node)
 667	case ast.BinaryOp:
 668		return ctx.codegenBinaryOp(node)
 669	case ast.UnaryOp:
 670		return ctx.codegenUnaryOp(node)
 671	case ast.PostfixOp:
 672		return ctx.codegenPostfixOp(node)
 673	case ast.Indirection:
 674		return ctx.codegenIndirection(node)
 675	case ast.Subscript:
 676		addr := ctx.codegenSubscriptAddr(node)
 677		return ctx.genLoad(addr, node.Typ), false
 678	case ast.AddressOf:
 679		return ctx.codegenAddressOf(node)
 680	case ast.FuncCall:
 681		return ctx.codegenFuncCall(node)
 682	case ast.TypeCast:
 683		return ctx.codegenTypeCast(node)
 684	case ast.TypeOf:
 685		return ctx.codegenTypeOf(node)
 686	case ast.Ternary:
 687		return ctx.codegenTernary(node)
 688	case ast.AutoAlloc:
 689		return ctx.codegenAutoAlloc(node)
 690	case ast.StructLiteral:
 691		return ctx.codegenStructLiteral(node)
 692	case ast.ArrayLiteral:
 693		return ctx.codegenArrayLiteral(node)
 694	case ast.MemberAccess:
 695		addr := ctx.codegenMemberAccessAddr(node)
 696		if addr == nil {
 697			return nil, true
 698		}
 699		return ctx.genLoad(addr, node.Typ), false
 700	}
 701	util.Error(node.Tok, "Internal error: unhandled expression type in codegen: %v", node.Type)
 702	return nil, true
 703}
 704
 705func (ctx *Context) codegenStmt(node *ast.Node) (terminates bool) {
 706	if node == nil {
 707		return false
 708	}
 709	switch node.Type {
 710	case ast.Block:
 711		isRealBlock := !node.Data.(ast.BlockNode).IsSynthetic
 712		if isRealBlock {
 713			ctx.enterScope()
 714		}
 715		var blockTerminates bool
 716		for _, stmt := range node.Data.(ast.BlockNode).Stmts {
 717			if blockTerminates {
 718				isLabel := stmt.Type == ast.Label || stmt.Type == ast.Case || stmt.Type == ast.Default
 719				if !isLabel {
 720					util.Warn(ctx.cfg, config.WarnUnreachableCode, stmt.Tok, "Unreachable code")
 721					continue
 722				}
 723				blockTerminates = false
 724				ctx.currentBlock = nil
 725			}
 726			blockTerminates = ctx.codegenStmt(stmt)
 727		}
 728		if isRealBlock {
 729			ctx.exitScope()
 730		}
 731		return blockTerminates
 732
 733	case ast.FuncDecl:
 734		ctx.codegenFuncDecl(node)
 735		return false
 736	case ast.VarDecl:
 737		ctx.codegenVarDecl(node)
 738		return false
 739	case ast.MultiVarDecl:
 740		for _, decl := range node.Data.(ast.MultiVarDeclNode).Decls {
 741			ctx.codegenVarDecl(decl)
 742		}
 743		return false
 744	case ast.TypeDecl, ast.Directive:
 745		return false
 746	case ast.EnumDecl:
 747		// Process enum members as global variable declarations
 748		d := node.Data.(ast.EnumDeclNode)
 749		for _, memberNode := range d.Members {
 750			ctx.codegenVarDecl(memberNode)
 751		}
 752		return false
 753	case ast.ExtrnDecl:
 754		d := node.Data.(ast.ExtrnDeclNode)
 755		for _, nameNode := range d.Names {
 756			name := nameNode.Data.(ast.IdentNode).Name
 757			if ctx.findSymbol(name) == nil {
 758				ctx.addSymbol(name, symExtrn, ast.TypeUntyped, false, nameNode)
 759			}
 760		}
 761		return false
 762	case ast.Return:
 763		return ctx.codegenReturn(node)
 764	case ast.If:
 765		return ctx.codegenIf(node)
 766	case ast.While:
 767		return ctx.codegenWhile(node)
 768	case ast.Switch:
 769		return ctx.codegenSwitch(node)
 770	case ast.Label:
 771		d := node.Data.(ast.LabelNode)
 772		label := &ir.Label{Name: d.Name}
 773		if ctx.currentBlock != nil {
 774			ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{label}})
 775		}
 776		ctx.startBlock(label)
 777		return ctx.codegenStmt(d.Stmt)
 778
 779	case ast.Goto:
 780		d := node.Data.(ast.GotoNode)
 781		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{&ir.Label{Name: d.Label}}})
 782		ctx.currentBlock = nil
 783		return true
 784
 785	case ast.Break:
 786		if ctx.breakLabel == nil {
 787			util.Error(node.Tok, "'break' not in a loop or switch")
 788		}
 789		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{ctx.breakLabel}})
 790		ctx.currentBlock = nil
 791		return true
 792
 793	case ast.Continue:
 794		if ctx.continueLabel == nil {
 795			util.Error(node.Tok, "'continue' not in a loop")
 796		}
 797		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{ctx.continueLabel}})
 798		ctx.currentBlock = nil
 799		return true
 800
 801	case ast.Case:
 802		if label, ok := ctx.switchCaseLabels[node]; ok {
 803			if ctx.currentBlock != nil {
 804				ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{label}})
 805			}
 806			ctx.startBlock(label)
 807			return ctx.codegenStmt(node.Data.(ast.CaseNode).Body)
 808		}
 809		util.Error(node.Tok, "'case' statement not properly nested in a switch context")
 810		return false
 811
 812	case ast.Default:
 813		if label, ok := ctx.switchCaseLabels[node]; ok {
 814			if ctx.currentBlock != nil {
 815				ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{label}})
 816			}
 817			ctx.startBlock(label)
 818			return ctx.codegenStmt(node.Data.(ast.DefaultNode).Body)
 819		}
 820		util.Error(node.Tok, "'default' statement not properly nested in a switch context")
 821		return false
 822
 823	default:
 824		// Expression statements are not allowed at global scope
 825		if ctx.currentFunc == nil {
 826			util.Error(node.Tok, "Expression statements are not allowed at global scope")
 827			return false
 828		}
 829		_, terminates := ctx.codegenExpr(node)
 830		return terminates
 831	}
 832}
 833
 834func (ctx *Context) codegenSwitch(node *ast.Node) bool {
 835	d := node.Data.(ast.SwitchNode)
 836	switchVal, _ := ctx.codegenExpr(d.Expr)
 837	endLabel := ctx.newLabel()
 838	var defaultTarget *ir.Label
 839
 840	oldBreak := ctx.breakLabel
 841	ctx.breakLabel = endLabel
 842	defer func() { ctx.breakLabel = oldBreak }()
 843
 844	caseLabels := make(map[*ast.Node]*ir.Label)
 845	var caseOrder []*ast.Node
 846	var findCasesRecursive func(*ast.Node)
 847	findCasesRecursive = func(n *ast.Node) {
 848		if n == nil || (n.Type == ast.Switch && n != node) {
 849			return
 850		}
 851		if n.Type == ast.Case || n.Type == ast.Default {
 852			if _, exists := caseLabels[n]; !exists {
 853				label := ctx.newLabel()
 854				caseLabels[n] = label
 855				caseOrder = append(caseOrder, n)
 856				if n.Type == ast.Default {
 857					if defaultTarget != nil {
 858						util.Error(n.Tok, "multiple default labels in switch")
 859					}
 860					defaultTarget = label
 861				}
 862			}
 863		}
 864		switch data := n.Data.(type) {
 865		case ast.BlockNode:
 866			for _, stmt := range data.Stmts {
 867				findCasesRecursive(stmt)
 868			}
 869		case ast.IfNode:
 870			findCasesRecursive(data.ThenBody)
 871			findCasesRecursive(data.ElseBody)
 872		case ast.WhileNode:
 873			findCasesRecursive(data.Body)
 874		case ast.LabelNode:
 875			findCasesRecursive(data.Stmt)
 876		case ast.CaseNode:
 877			findCasesRecursive(data.Body)
 878		case ast.DefaultNode:
 879			findCasesRecursive(data.Body)
 880		}
 881	}
 882	findCasesRecursive(d.Body)
 883
 884	if defaultTarget == nil {
 885		defaultTarget = endLabel
 886	}
 887
 888	for _, caseStmt := range caseOrder {
 889		if caseStmt.Type == ast.Case {
 890			caseData := caseStmt.Data.(ast.CaseNode)
 891			bodyLabel := caseLabels[caseStmt]
 892			nextCaseCheck := ctx.newLabel()
 893
 894			var finalCond ir.Value
 895			for i, valueExpr := range caseData.Values {
 896				caseVal, _ := ctx.codegenExpr(valueExpr)
 897				cmpRes := ctx.newTemp()
 898				ctx.addInstr(&ir.Instruction{Op: ir.OpCEq, Typ: ir.GetType(nil, ctx.wordSize), OperandType: ir.GetType(d.Expr.Typ, ctx.wordSize), Result: cmpRes, Args: []ir.Value{switchVal, caseVal}})
 899				if i == 0 {
 900					finalCond = cmpRes
 901				} else {
 902					newFinalCond := ctx.newTemp()
 903					ctx.addInstr(&ir.Instruction{Op: ir.OpOr, Typ: ir.GetType(nil, ctx.wordSize), Result: newFinalCond, Args: []ir.Value{finalCond, cmpRes}})
 904					finalCond = newFinalCond
 905				}
 906			}
 907
 908			if finalCond != nil {
 909				ctx.addInstr(&ir.Instruction{Op: ir.OpJnz, Args: []ir.Value{finalCond, bodyLabel, nextCaseCheck}})
 910			} else {
 911				ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{nextCaseCheck}})
 912			}
 913			ctx.startBlock(nextCaseCheck)
 914		}
 915	}
 916	ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{defaultTarget}})
 917	ctx.currentBlock = nil
 918
 919	oldCaseLabels := ctx.switchCaseLabels
 920	ctx.switchCaseLabels = caseLabels
 921	defer func() { ctx.switchCaseLabels = oldCaseLabels }()
 922
 923	terminates := ctx.codegenStmt(d.Body)
 924
 925	if ctx.currentBlock != nil && !terminates {
 926		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endLabel}})
 927	}
 928
 929	ctx.startBlock(endLabel)
 930	return false
 931}
 932
 933func (ctx *Context) findAllAutosInFunc(node *ast.Node, autoVars *[]autoVarInfo, definedNames map[string]bool) {
 934	if node == nil {
 935		return
 936	}
 937	if node.Type == ast.VarDecl {
 938		varData := node.Data.(ast.VarDeclNode)
 939		if !definedNames[varData.Name] {
 940			definedNames[varData.Name] = true
 941			var size int64
 942			if varData.Type != nil && varData.Type.Kind != ast.TYPE_UNTYPED {
 943				size = ctx.getSizeof(varData.Type)
 944			} else {
 945				if varData.IsVector {
 946					dataSizeInWords := int64(0)
 947					if varData.SizeExpr != nil {
 948						folded := ast.FoldConstants(varData.SizeExpr)
 949						if folded.Type != ast.Number {
 950							util.Error(node.Tok, "Local vector size must be a constant expression")
 951						}
 952						dataSizeInWords = folded.Data.(ast.NumberNode).Value
 953					} else if len(varData.InitList) == 1 && varData.InitList[0].Type == ast.String {
 954						strLen := int64(len(varData.InitList[0].Data.(ast.StringNode).Value))
 955						numBytes := strLen + 1
 956						dataSizeInWords = (numBytes + int64(ctx.wordSize) - 1) / int64(ctx.wordSize)
 957					} else {
 958						dataSizeInWords = int64(len(varData.InitList))
 959					}
 960					size = int64(ctx.wordSize) + dataSizeInWords*int64(ctx.wordSize)
 961				} else {
 962					size = int64(ctx.wordSize)
 963				}
 964			}
 965			*autoVars = append(*autoVars, autoVarInfo{Node: node, Size: size})
 966		}
 967	}
 968
 969	switch d := node.Data.(type) {
 970	case ast.IfNode:
 971		ctx.findAllAutosInFunc(d.ThenBody, autoVars, definedNames)
 972		ctx.findAllAutosInFunc(d.ElseBody, autoVars, definedNames)
 973	case ast.WhileNode:
 974		ctx.findAllAutosInFunc(d.Body, autoVars, definedNames)
 975	case ast.BlockNode:
 976		for _, s := range d.Stmts {
 977			ctx.findAllAutosInFunc(s, autoVars, definedNames)
 978		}
 979	case ast.MultiVarDeclNode:
 980		for _, decl := range d.Decls {
 981			ctx.findAllAutosInFunc(decl, autoVars, definedNames)
 982		}
 983	case ast.SwitchNode:
 984		ctx.findAllAutosInFunc(d.Body, autoVars, definedNames)
 985	case ast.CaseNode:
 986		ctx.findAllAutosInFunc(d.Body, autoVars, definedNames)
 987	case ast.DefaultNode:
 988		ctx.findAllAutosInFunc(d.Body, autoVars, definedNames)
 989	case ast.LabelNode:
 990		ctx.findAllAutosInFunc(d.Stmt, autoVars, definedNames)
 991	}
 992}
 993
 994func (ctx *Context) codegenFuncDecl(node *ast.Node) {
 995	d := node.Data.(ast.FuncDeclNode)
 996	if d.Body != nil && d.Body.Type == ast.AsmStmt {
 997		asmCode := d.Body.Data.(ast.AsmStmtNode).Code
 998		ctx.inlineAsm += fmt.Sprintf(".globl %s\n%s:\n\t%s\n", d.Name, d.Name, asmCode)
 999		return
1000	}
1001	if d.Body == nil {
1002		return
1003	}
1004
1005	irReturnType := ir.GetType(d.ReturnType, ctx.wordSize)
1006	fn := &ir.Func{
1007		Name: d.Name, ReturnType: irReturnType, AstReturnType: d.ReturnType,
1008		HasVarargs: d.HasVarargs, AstParams: d.Params, Node: node,
1009	}
1010	ctx.prog.Funcs = append(ctx.prog.Funcs, fn)
1011
1012	prevFunc := ctx.currentFunc
1013	ctx.currentFunc = fn
1014	defer func() { ctx.currentFunc = prevFunc }()
1015
1016	ctx.enterScope()
1017	defer ctx.exitScope()
1018
1019	ctx.tempCount = 0
1020	ctx.startBlock(&ir.Label{Name: "start"})
1021
1022	for i, p := range d.Params {
1023		var name string
1024		var typ *ast.BxType
1025		if d.IsTyped {
1026			paramData := p.Data.(ast.VarDeclNode)
1027			name, typ = paramData.Name, paramData.Type
1028		} else {
1029			name = p.Data.(ast.IdentNode).Name
1030		}
1031		paramVal := &ir.Temporary{Name: name, ID: i}
1032		fn.Params = append(fn.Params, &ir.Param{
1033			Name: name,
1034			Typ:  ir.GetType(typ, ctx.wordSize),
1035			Val:  paramVal,
1036		})
1037	}
1038
1039	var paramInfos []autoVarInfo
1040	for _, p := range d.Params {
1041		paramInfos = append(paramInfos, autoVarInfo{Node: p, Size: int64(ctx.wordSize)})
1042	}
1043	for i, j := 0, len(paramInfos)-1; i < j; i, j = i+1, j-1 {
1044		paramInfos[i], paramInfos[j] = paramInfos[j], paramInfos[i]
1045	}
1046
1047	definedInFunc := make(map[string]bool)
1048	for _, p := range d.Params {
1049		var name string
1050		if d.IsTyped {
1051			name = p.Data.(ast.VarDeclNode).Name
1052		} else {
1053			name = p.Data.(ast.IdentNode).Name
1054		}
1055		definedInFunc[name] = true
1056	}
1057	var autoVars []autoVarInfo
1058	ctx.findAllAutosInFunc(d.Body, &autoVars, definedInFunc)
1059
1060	for i, j := 0, len(autoVars)-1; i < j; i, j = i+1, j-1 {
1061		autoVars[i], autoVars[j] = autoVars[j], autoVars[i]
1062	}
1063
1064	allLocals := append(paramInfos, autoVars...)
1065
1066	var totalFrameSize int64
1067	for _, av := range allLocals {
1068		totalFrameSize += av.Size
1069	}
1070
1071	var framePtr ir.Value
1072	if totalFrameSize > 0 {
1073		align := int64(ctx.stackAlign)
1074		totalFrameSize = (totalFrameSize + align - 1) &^ (align - 1)
1075		framePtr = ctx.newTemp()
1076		ctx.addInstr(&ir.Instruction{
1077			Op:     ir.OpAlloc,
1078			Typ:    ir.GetType(nil, ctx.wordSize),
1079			Result: framePtr,
1080			Args:   []ir.Value{&ir.Const{Value: totalFrameSize}},
1081			Align:  ctx.stackAlign,
1082		})
1083	}
1084
1085	var currentOffset int64
1086	for i, local := range allLocals {
1087		isParam := i < len(paramInfos)
1088
1089		var name string
1090		var typ *ast.BxType
1091		var isVec bool
1092		if local.Node.Type == ast.Ident {
1093			name = local.Node.Data.(ast.IdentNode).Name
1094			if d.Name == "main" && isParam {
1095				originalIndex := -1
1096				for j, p := range d.Params {
1097					if p == local.Node {
1098						originalIndex = j
1099						break
1100					}
1101				}
1102				if originalIndex == 1 {
1103					isVec = true
1104				}
1105			}
1106		} else {
1107			varData := local.Node.Data.(ast.VarDeclNode)
1108			name, typ, isVec = varData.Name, varData.Type, varData.IsVector
1109		}
1110
1111		sym := ctx.addSymbol(name, symVar, typ, isVec, local.Node)
1112		sym.StackOffset = currentOffset
1113
1114		addr := ctx.newTemp()
1115		ctx.addInstr(&ir.Instruction{
1116			Op:     ir.OpAdd,
1117			Typ:    ir.GetType(nil, ctx.wordSize),
1118			Result: addr,
1119			Args:   []ir.Value{framePtr, &ir.Const{Value: currentOffset}},
1120		})
1121		sym.IRVal = addr
1122
1123		if isParam {
1124			var origParamIndex int = -1
1125			for j, p := range d.Params {
1126				if p == local.Node {
1127					origParamIndex = j
1128					break
1129				}
1130			}
1131
1132			if origParamIndex != -1 {
1133				paramVal := fn.Params[origParamIndex].Val
1134				ctx.genStore(sym.IRVal, paramVal, typ)
1135			}
1136		} else {
1137			if isVec && (typ == nil || typ.Kind == ast.TYPE_UNTYPED) {
1138				storageAddr := ctx.newTemp()
1139				ctx.addInstr(&ir.Instruction{
1140					Op:     ir.OpAdd,
1141					Typ:    ir.GetType(nil, ctx.wordSize),
1142					Result: storageAddr,
1143					Args:   []ir.Value{addr, &ir.Const{Value: int64(ctx.wordSize)}},
1144				})
1145				ctx.genStore(addr, storageAddr, nil)
1146			}
1147		}
1148		currentOffset += local.Size
1149	}
1150
1151	bodyTerminates := ctx.codegenStmt(d.Body)
1152
1153	if !bodyTerminates {
1154		if d.ReturnType != nil && d.ReturnType.Kind == ast.TYPE_VOID {
1155			ctx.addInstr(&ir.Instruction{Op: ir.OpRet})
1156		} else {
1157			ctx.addInstr(&ir.Instruction{Op: ir.OpRet, Args: []ir.Value{&ir.Const{Value: 0}}})
1158		}
1159	}
1160}
1161
1162func (ctx *Context) codegenGlobalConst(node *ast.Node) ir.Value {
1163	folded := ast.FoldConstants(node)
1164	switch folded.Type {
1165	case ast.Number: return &ir.Const{Value: folded.Data.(ast.NumberNode).Value}
1166	case ast.FloatNumber:
1167		typ := ir.GetType(folded.Typ, ctx.wordSize)
1168		return &ir.FloatConst{Value: folded.Data.(ast.FloatNumberNode).Value, Typ: typ}
1169	case ast.String: return ctx.addString(folded.Data.(ast.StringNode).Value)
1170	case ast.Nil: return &ir.Const{Value: 0}
1171	case ast.Ident:
1172		name := folded.Data.(ast.IdentNode).Name
1173		sym := ctx.findSymbol(name)
1174		if sym == nil {
1175			util.Error(node.Tok, "Undefined symbol '%s' in global initializer", name)
1176			return nil
1177		}
1178		// Try to evaluate as a constant expression (for enum constants)
1179		if val, ok := ctx.evalConstExpr(folded); ok {
1180			return &ir.Const{Value: val}
1181		}
1182		return sym.IRVal
1183	case ast.AddressOf:
1184		lval := folded.Data.(ast.AddressOfNode).LValue
1185		if lval.Type != ast.Ident {
1186			util.Error(lval.Tok, "Global initializer must be the address of a global symbol")
1187			return nil
1188		}
1189		name := lval.Data.(ast.IdentNode).Name
1190		sym := ctx.findSymbol(name)
1191		if sym == nil {
1192			util.Error(lval.Tok, "Undefined symbol '%s' in global initializer", name)
1193			return nil
1194		}
1195		return sym.IRVal
1196	default:
1197		util.Error(node.Tok, "Global initializer must be a constant expression")
1198		return nil
1199	}
1200}
1201
1202func (ctx *Context) codegenVarDecl(node *ast.Node) {
1203	d := node.Data.(ast.VarDeclNode)
1204	sym := ctx.findSymbol(d.Name)
1205	if sym == nil {
1206		if ctx.currentFunc == nil {
1207			sym = ctx.addSymbol(d.Name, symVar, d.Type, d.IsVector, node)
1208		} else {
1209			util.Error(node.Tok, "Internal error: symbol '%s' not found during declaration", d.Name)
1210			return
1211		}
1212	}
1213
1214	if ctx.currentFunc == nil {
1215		ctx.codegenGlobalVarDecl(d, sym)
1216	} else {
1217		ctx.codegenLocalVarDecl(d, sym)
1218	}
1219}
1220
1221func (ctx *Context) codegenLocalVarDecl(d ast.VarDeclNode, sym *symbol) {
1222	if len(d.InitList) == 0 {
1223		return
1224	}
1225
1226	if d.IsVector || (d.Type != nil && d.Type.Kind == ast.TYPE_ARRAY) {
1227		vectorPtr, _ := ctx.codegenExpr(&ast.Node{Type: ast.Ident, Data: ast.IdentNode{Name: d.Name}, Tok: sym.Node.Tok})
1228
1229		if len(d.InitList) == 1 && d.InitList[0].Type == ast.String {
1230			strVal := d.InitList[0].Data.(ast.StringNode).Value
1231			strLabel := ctx.addString(strVal)
1232			sizeToCopy := len(strVal) + 1
1233			ctx.addInstr(&ir.Instruction{
1234				Op:   ir.OpBlit,
1235				Args: []ir.Value{strLabel, vectorPtr, &ir.Const{Value: int64(sizeToCopy)}},
1236			})
1237		} else {
1238			for i, initExpr := range d.InitList {
1239				offset := int64(i) * int64(ctx.wordSize)
1240				elemAddr := ctx.newTemp()
1241				ctx.addInstr(&ir.Instruction{
1242					Op:     ir.OpAdd,
1243					Typ:    ir.GetType(nil, ctx.wordSize),
1244					Result: elemAddr,
1245					Args:   []ir.Value{vectorPtr, &ir.Const{Value: offset}},
1246				})
1247				rval, _ := ctx.codegenExpr(initExpr)
1248				ctx.genStore(elemAddr, rval, nil)
1249			}
1250		}
1251		return
1252	}
1253
1254	initExpr := d.InitList[0]
1255	varType := d.Type
1256	if d.IsDefine && initExpr.Typ != nil {
1257		varType = initExpr.Typ
1258	} else if (varType == nil || varType.Kind == ast.TYPE_UNTYPED) && initExpr.Typ != nil {
1259		varType = initExpr.Typ
1260	}
1261
1262	if sym.BxType == nil || sym.BxType.Kind == ast.TYPE_UNTYPED {
1263		sym.BxType = varType
1264	}
1265
1266	// Resolve named struct types to their actual definitions
1267	if varType != nil && varType.Kind != ast.TYPE_STRUCT && varType.Name != "" {
1268		if typeSym := ctx.findTypeSymbol(varType.Name); typeSym != nil && typeSym.BxType.Kind == ast.TYPE_STRUCT {
1269			varType = typeSym.BxType
1270		}
1271	}
1272
1273	if varType != nil && varType.Kind == ast.TYPE_STRUCT {
1274		rvalPtr, _ := ctx.codegenExpr(initExpr)
1275		lvalAddr := sym.IRVal
1276		size := ctx.getSizeof(varType)
1277		ctx.addInstr(&ir.Instruction{
1278			Op:   ir.OpBlit,
1279			Args: []ir.Value{rvalPtr, lvalAddr, &ir.Const{Value: size}},
1280		})
1281	} else {
1282		rval, _ := ctx.codegenExpr(initExpr)
1283		ctx.genStore(sym.IRVal, rval, varType)
1284	}
1285}
1286
1287func (ctx *Context) codegenGlobalVarDecl(d ast.VarDeclNode, sym *symbol) {
1288	globalData := &ir.Data{
1289		Name:    sym.IRVal.(*ir.Global).Name,
1290		Align:   int(ctx.getAlignof(d.Type)),
1291		AstType: d.Type,
1292	}
1293
1294	if d.Type != nil && d.Type.Kind == ast.TYPE_STRUCT && len(d.InitList) == 0 {
1295		structSize := ctx.getSizeof(d.Type)
1296		if structSize > 0 {
1297			globalData.Items = append(globalData.Items, ir.DataItem{Typ: ir.TypeB, Count: int(structSize)})
1298		}
1299		if len(globalData.Items) > 0 {
1300			ctx.prog.Globals = append(ctx.prog.Globals, globalData)
1301		}
1302		return
1303	}
1304
1305	isUntypedStringVec := d.IsVector && (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) &&
1306		len(d.InitList) == 1 && d.InitList[0].Type == ast.String
1307
1308	var elemType ir.Type
1309	if isUntypedStringVec {
1310		elemType = ir.TypeB
1311	} else {
1312		elemType = ir.GetType(d.Type, ctx.wordSize)
1313		if d.Type != nil && d.Type.Kind == ast.TYPE_ARRAY {
1314			elemType = ir.GetType(d.Type.Base, ctx.wordSize)
1315		}
1316	}
1317
1318	var numElements int64
1319	var sizeNode *ast.Node
1320	if d.Type != nil && d.Type.Kind == ast.TYPE_ARRAY {
1321		sizeNode = d.Type.ArraySize
1322	} else if d.IsVector {
1323		sizeNode = d.SizeExpr
1324	}
1325
1326	if sizeNode != nil {
1327		if val, ok := ctx.evalConstExpr(sizeNode); ok {
1328			numElements = val
1329		} else {
1330			util.Error(sizeNode.Tok, "Global array size must be a constant expression")
1331		}
1332	} else {
1333		numElements = int64(len(d.InitList))
1334	}
1335	if numElements == 0 && !d.IsVector && len(d.InitList) == 0 {
1336		numElements = 1
1337	}
1338
1339	if len(d.InitList) > 0 {
1340		for _, init := range d.InitList {
1341			val := ctx.codegenGlobalConst(init)
1342			itemType := elemType
1343			if _, ok := val.(*ir.Global); ok {
1344				itemType = ir.TypePtr
1345			}
1346			globalData.Items = append(globalData.Items, ir.DataItem{Typ: itemType, Value: val})
1347		}
1348		initializedElements := int64(len(d.InitList))
1349		if numElements > initializedElements {
1350			globalData.Items = append(globalData.Items, ir.DataItem{Typ: elemType, Count: int(numElements - initializedElements)})
1351		}
1352	} else if numElements > 0 {
1353		globalData.Items = append(globalData.Items, ir.DataItem{Typ: elemType, Count: int(numElements)})
1354	}
1355
1356	if len(globalData.Items) > 0 {
1357		ctx.prog.Globals = append(ctx.prog.Globals, globalData)
1358	}
1359}