repos / gbc

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

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

codegen_helpers.go

Go
  1package codegen
  2
  3import (
  4	"github.com/xplshn/gbc/pkg/ast"
  5	"github.com/xplshn/gbc/pkg/config"
  6	"github.com/xplshn/gbc/pkg/ir"
  7	"github.com/xplshn/gbc/pkg/token"
  8	"github.com/xplshn/gbc/pkg/util"
  9)
 10
 11func (ctx *Context) codegenIdent(node *ast.Node) (ir.Value, bool) {
 12	name := node.Data.(ast.IdentNode).Name
 13	sym := ctx.findSymbol(name)
 14
 15	if sym == nil {
 16		util.Warn(ctx.cfg, config.WarnImplicitDecl, node.Tok, "Implicit declaration of function '%s'", name)
 17		sym = ctx.addSymbol(name, symFunc, ast.TypeUntyped, false, node)
 18		return sym.IRVal, false
 19	}
 20
 21	switch sym.Type {
 22	case symFunc: return sym.IRVal, false
 23	case symExtrn:
 24		isCall := node.Parent != nil && node.Parent.Type == ast.FuncCall && node.Parent.Data.(ast.FuncCallNode).FuncExpr == node
 25		if isCall {
 26			return sym.IRVal, false
 27		}
 28		ctx.prog.ExtrnVars[name] = true
 29		res := ctx.newTemp()
 30		ctx.addInstr(&ir.Instruction{Op: ir.OpLoad, Typ: ir.TypePtr, Result: res, Args: []ir.Value{sym.IRVal}})
 31		return res, false
 32	}
 33
 34	isArr := sym.IsVector || (sym.BxType != nil && sym.BxType.Kind == ast.TYPE_ARRAY)
 35	if isArr {
 36		isParam := false
 37		if sym.Node != nil && sym.Node.Parent != nil && sym.Node.Parent.Type == ast.FuncDecl {
 38			funcDecl := sym.Node.Parent.Data.(ast.FuncDeclNode)
 39			for _, p := range funcDecl.Params {
 40				if p == sym.Node {
 41					isParam = true
 42					break
 43				}
 44			}
 45		}
 46		_, isLocal := sym.IRVal.(*ir.Temporary)
 47		if isLocal {
 48			isDopeVector := sym.IsVector && (sym.BxType == nil || sym.BxType.Kind == ast.TYPE_UNTYPED)
 49			if isParam || isDopeVector {
 50				return ctx.genLoad(sym.IRVal, sym.BxType), false
 51			}
 52		}
 53		return sym.IRVal, false
 54	}
 55
 56	if sym.BxType != nil && sym.BxType.Kind == ast.TYPE_STRUCT {
 57		return sym.IRVal, false
 58	}
 59
 60	return ctx.genLoad(sym.IRVal, sym.BxType), false
 61}
 62
 63func (ctx *Context) isIntegerType(t *ast.BxType) bool {
 64	return t != nil && (t.Kind == ast.TYPE_PRIMITIVE || t.Kind == ast.TYPE_LITERAL_INT || t.Kind == ast.TYPE_ENUM)
 65}
 66
 67func (ctx *Context) isFloatType(t *ast.BxType) bool {
 68	return t != nil && (t.Kind == ast.TYPE_FLOAT || t.Kind == ast.TYPE_LITERAL_FLOAT)
 69}
 70
 71// getActualOperandType returns the IR type that will be used when loading this operand
 72// This looks at the original declaration type, not the type-checker promoted type
 73func (ctx *Context) getActualOperandType(node *ast.Node) ir.Type {
 74	switch node.Type {
 75	case ast.Ident:
 76		// For identifiers, use the symbol's original declared type
 77		name := node.Data.(ast.IdentNode).Name
 78		if sym := ctx.findSymbol(name); sym != nil && sym.BxType != nil {
 79			return ir.GetType(sym.BxType, ctx.wordSize)
 80		}
 81	case ast.FuncCall:
 82		// For function calls, use the function's return type
 83		d := node.Data.(ast.FuncCallNode)
 84		if d.FuncExpr.Type == ast.Ident {
 85			funcName := d.FuncExpr.Data.(ast.IdentNode).Name
 86			if sym := ctx.findSymbol(funcName); sym != nil && sym.BxType != nil {
 87				return ir.GetType(sym.BxType, ctx.wordSize)
 88			}
 89		}
 90	}
 91	// Fallback to the promoted type
 92	return ir.GetType(node.Typ, ctx.wordSize)
 93}
 94
 95func (ctx *Context) codegenAssign(node *ast.Node) (ir.Value, bool) {
 96	d := node.Data.(ast.AssignNode)
 97
 98	// Resolve named struct types to their actual definitions
 99	lhsType := d.Lhs.Typ
100	if lhsType != nil && lhsType.Kind != ast.TYPE_STRUCT && lhsType.Name != "" {
101		if typeSym := ctx.findTypeSymbol(lhsType.Name); typeSym != nil && typeSym.BxType.Kind == ast.TYPE_STRUCT {
102			lhsType = typeSym.BxType
103		}
104	}
105
106	if lhsType != nil && lhsType.Kind == ast.TYPE_STRUCT {
107		if d.Op != token.Eq {
108			util.Error(node.Tok, "Compound assignment operators are not supported for structs")
109			return nil, false
110		}
111		lvalAddr := ctx.codegenLvalue(d.Lhs)
112		rvalPtr, _ := ctx.codegenExpr(d.Rhs)
113		size := ctx.getSizeof(lhsType)
114		ctx.addInstr(&ir.Instruction{
115			Op:   ir.OpBlit,
116			Args: []ir.Value{rvalPtr, lvalAddr, &ir.Const{Value: size}},
117		})
118		return lvalAddr, false
119	}
120
121	lvalAddr := ctx.codegenLvalue(d.Lhs)
122	var rval ir.Value
123
124	if d.Op == token.Eq {
125		rval, _ = ctx.codegenExpr(d.Rhs)
126		if d.Lhs.Typ != nil && d.Rhs.Typ != nil && d.Lhs.Typ.Kind == ast.TYPE_FLOAT && ctx.isIntegerType(d.Rhs.Typ) {
127			castRval := ctx.newTemp()
128			var convOp ir.Op
129			if ctx.getSizeof(d.Rhs.Typ) == 8 {
130				convOp = ir.OpSLToF
131			} else {
132				convOp = ir.OpSWToF
133			}
134			ctx.addInstr(&ir.Instruction{
135				Op:     convOp,
136				Typ:    ir.GetType(d.Lhs.Typ, ctx.wordSize),
137				Result: castRval,
138				Args:   []ir.Value{rval},
139			})
140			rval = castRval
141		}
142	} else {
143		currentLvalVal := ctx.genLoad(lvalAddr, d.Lhs.Typ)
144		rhsVal, _ := ctx.codegenExpr(d.Rhs)
145		op, typ := getBinaryOpAndType(d.Op, d.Lhs.Typ, ctx.wordSize)
146		rval = ctx.newTemp()
147		ctx.addInstr(&ir.Instruction{Op: op, Typ: typ, Result: rval, Args: []ir.Value{currentLvalVal, rhsVal}})
148	}
149
150	ctx.genStore(lvalAddr, rval, d.Lhs.Typ)
151	return rval, false
152}
153
154func (ctx *Context) codegenMultiAssign(node *ast.Node) (ir.Value, bool) {
155	d := node.Data.(ast.MultiAssignNode)
156	
157	// Only support simple '=' assignment for multi-assignment
158	if d.Op != token.Eq {
159		util.Error(node.Tok, "Compound assignment operators are not supported for multi-assignment")
160		return nil, false
161	}
162	
163	// Evaluate all rhs expressions first to avoid dependencies
164	var rvals []ir.Value
165	for _, rhs := range d.Rhs {
166		rval, _ := ctx.codegenExpr(rhs)
167		rvals = append(rvals, rval)
168	}
169	
170	// Then assign to all lhs expressions
171	for i, lhs := range d.Lhs {
172		lvalAddr := ctx.codegenLvalue(lhs)
173		rval := rvals[i]
174		
175		// Handle type conversions if needed (similar to single assignment)
176		if lhs.Typ != nil && d.Rhs[i].Typ != nil && lhs.Typ.Kind == ast.TYPE_FLOAT && ctx.isIntegerType(d.Rhs[i].Typ) {
177			castRval := ctx.newTemp()
178			var convOp ir.Op
179			if ctx.getSizeof(d.Rhs[i].Typ) == 8 {
180				convOp = ir.OpSLToF
181			} else {
182				convOp = ir.OpSWToF
183			}
184			ctx.addInstr(&ir.Instruction{
185				Op:     convOp,
186				Typ:    ir.GetType(lhs.Typ, ctx.wordSize),
187				Result: castRval,
188				Args:   []ir.Value{rval},
189			})
190			rval = castRval
191		}
192		
193		ctx.genStore(lvalAddr, rval, lhs.Typ)
194	}
195	
196	// Return the last assigned value
197	if len(rvals) > 0 {
198		return rvals[len(rvals)-1], false
199	}
200	return nil, false
201}
202
203func (ctx *Context) codegenBinaryOp(node *ast.Node) (ir.Value, bool) {
204	d := node.Data.(ast.BinaryOpNode)
205	if d.Op == token.OrOr || d.Op == token.AndAnd {
206		res := ctx.newTemp()
207		trueL, falseL, endL := ctx.newLabel(), ctx.newLabel(), ctx.newLabel()
208
209		ctx.codegenLogicalCond(node, trueL, falseL)
210
211		ctx.startBlock(trueL)
212		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
213
214		ctx.startBlock(falseL)
215		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
216
217		ctx.startBlock(endL)
218		wordType := ir.GetType(nil, ctx.wordSize)
219		ctx.addInstr(&ir.Instruction{
220			Op:     ir.OpPhi,
221			Typ:    wordType,
222			Result: res,
223			Args: []ir.Value{
224				trueL, &ir.Const{Value: 1},
225				falseL, &ir.Const{Value: 0},
226			},
227		})
228		return res, false
229	}
230
231	l, _ := ctx.codegenExpr(d.Left)
232	r, _ := ctx.codegenExpr(d.Right)
233	res := ctx.newTemp()
234	op, resultIrType := getBinaryOpAndType(d.Op, node.Typ, ctx.wordSize)
235
236	isComparison := op >= ir.OpCEq && op <= ir.OpCGe
237	isFloatComparison := false
238	if isComparison && (ctx.isFloatType(d.Left.Typ) || ctx.isFloatType(d.Right.Typ)) {
239		isFloatComparison = true
240	}
241
242	if ctx.isFloatType(node.Typ) || isFloatComparison {
243		floatType := resultIrType
244		if isFloatComparison {
245			if ctx.isFloatType(d.Left.Typ) {
246				floatType = ir.GetType(d.Left.Typ, ctx.wordSize)
247			} else {
248				floatType = ir.GetType(d.Right.Typ, ctx.wordSize)
249			}
250		}
251
252		if !ctx.isFloatType(d.Left.Typ) {
253			castL := ctx.newTemp()
254			var convOp ir.Op
255			if ctx.getSizeof(d.Left.Typ) == 8 {
256				convOp = ir.OpSLToF
257			} else {
258				convOp = ir.OpSWToF
259			}
260			ctx.addInstr(&ir.Instruction{Op: convOp, Typ: floatType, Result: castL, Args: []ir.Value{l}})
261			l = castL
262		}
263		if !ctx.isFloatType(d.Right.Typ) {
264			castR := ctx.newTemp()
265			var convOp ir.Op
266			if ctx.getSizeof(d.Right.Typ) == 8 {
267				convOp = ir.OpSLToF
268			} else {
269				convOp = ir.OpSWToF
270			}
271			ctx.addInstr(&ir.Instruction{Op: convOp, Typ: floatType, Result: castR, Args: []ir.Value{r}})
272			r = castR
273		}
274		if l_const, ok := l.(*ir.Const); ok {
275			l = &ir.FloatConst{Value: float64(l_const.Value), Typ: floatType}
276		}
277		if r_const, ok := r.(*ir.Const); ok {
278			r = &ir.FloatConst{Value: float64(r_const.Value), Typ: floatType}
279		}
280	}
281
282	// Handle integer type conversions - ensure both operands have compatible types for QBE
283	if ctx.isIntegerType(node.Typ) && !isFloatComparison {
284		// For QBE compatibility, we need to look at the actual declaration types of the operands
285		// rather than the promoted types from the type checker
286		actualLeftType := ctx.getActualOperandType(d.Left)
287		actualRightType := ctx.getActualOperandType(d.Right)
288
289		// Use actual types for conversion logic
290		if actualLeftType != resultIrType {
291			castL := ctx.newTemp()
292			var convOp ir.Op = ir.OpCast
293			if actualLeftType < resultIrType {
294				// Extending to larger size
295				switch actualLeftType {
296				case ir.TypeB: convOp = ir.OpExtUB
297				case ir.TypeH: convOp = ir.OpExtUH
298				case ir.TypeW: convOp = ir.OpExtSW
299				}
300			}
301			ctx.addInstr(&ir.Instruction{Op: convOp, Typ: resultIrType, OperandType: actualLeftType, Result: castL, Args: []ir.Value{l}})
302			l = castL
303		}
304
305		if actualRightType != resultIrType {
306			castR := ctx.newTemp()
307			var convOp ir.Op = ir.OpCast
308			if actualRightType < resultIrType {
309				// Extending to larger size
310				switch actualRightType {
311				case ir.TypeB: convOp = ir.OpExtUB
312				case ir.TypeH: convOp = ir.OpExtUH
313				case ir.TypeW: convOp = ir.OpExtSW
314				}
315			}
316			ctx.addInstr(&ir.Instruction{Op: convOp, Typ: resultIrType, OperandType: actualRightType, Result: castR, Args: []ir.Value{r}})
317			r = castR
318		}
319	}
320
321	var operandIrType ir.Type
322	if isComparison {
323		if ctx.isFloatType(d.Left.Typ) || ctx.isFloatType(d.Right.Typ) {
324			if ctx.isFloatType(d.Left.Typ) {
325				operandIrType = ir.GetType(d.Left.Typ, ctx.wordSize)
326			} else {
327				operandIrType = ir.GetType(d.Right.Typ, ctx.wordSize)
328			}
329		} else {
330			operandIrType = ir.GetType(d.Left.Typ, ctx.wordSize)
331		}
332	} else {
333		operandIrType = resultIrType
334	}
335
336	ctx.addInstr(&ir.Instruction{
337		Op:          op,
338		Typ:         resultIrType,
339		OperandType: operandIrType,
340		Result:      res,
341		Args:        []ir.Value{l, r},
342	})
343	return res, false
344}
345
346func (ctx *Context) codegenUnaryOp(node *ast.Node) (ir.Value, bool) {
347	d := node.Data.(ast.UnaryOpNode)
348	res := ctx.newTemp()
349	val, _ := ctx.codegenExpr(d.Expr)
350	valType := ir.GetType(d.Expr.Typ, ctx.wordSize)
351	isFloat := ctx.isFloatType(d.Expr.Typ)
352
353	switch d.Op {
354	case token.Minus:
355		if isFloat {
356			ctx.addInstr(&ir.Instruction{Op: ir.OpNegF, Typ: valType, Result: res, Args: []ir.Value{val}})
357		} else {
358			ctx.addInstr(&ir.Instruction{Op: ir.OpSub, Typ: valType, Result: res, Args: []ir.Value{&ir.Const{Value: 0}, val}})
359		}
360	case token.Plus: return val, false
361	case token.Not:
362		wordType := ir.GetType(nil, ctx.wordSize)
363		ctx.addInstr(&ir.Instruction{Op: ir.OpCEq, Typ: wordType, OperandType: valType, Result: res, Args: []ir.Value{val, &ir.Const{Value: 0}}})
364	case token.Complement:
365		wordType := ir.GetType(nil, ctx.wordSize)
366		ctx.addInstr(&ir.Instruction{Op: ir.OpXor, Typ: wordType, Result: res, Args: []ir.Value{val, &ir.Const{Value: -1}}})
367	case token.Inc, token.Dec:
368		lvalAddr := ctx.codegenLvalue(d.Expr)
369		op := map[token.Type]ir.Op{token.Inc: ir.OpAdd, token.Dec: ir.OpSub}[d.Op]
370		if isFloat {
371			op = map[token.Type]ir.Op{token.Inc: ir.OpAddF, token.Dec: ir.OpSubF}[d.Op]
372		}
373		currentVal := ctx.genLoad(lvalAddr, d.Expr.Typ)
374		oneConst := ir.Value(&ir.Const{Value: 1})
375		if isFloat {
376			oneConst = &ir.FloatConst{Value: 1.0, Typ: valType}
377		}
378		ctx.addInstr(&ir.Instruction{Op: op, Typ: valType, Result: res, Args: []ir.Value{currentVal, oneConst}})
379		ctx.genStore(lvalAddr, res, d.Expr.Typ)
380	default:
381		util.Error(node.Tok, "Unsupported unary operator")
382	}
383	return res, false
384}
385
386func (ctx *Context) codegenPostfixOp(node *ast.Node) (ir.Value, bool) {
387	d := node.Data.(ast.PostfixOpNode)
388	lvalAddr := ctx.codegenLvalue(d.Expr)
389	res := ctx.genLoad(lvalAddr, d.Expr.Typ)
390
391	newVal := ctx.newTemp()
392	valType := ir.GetType(d.Expr.Typ, ctx.wordSize)
393	isFloat := ctx.isFloatType(d.Expr.Typ)
394
395	op := map[token.Type]ir.Op{token.Inc: ir.OpAdd, token.Dec: ir.OpSub}[d.Op]
396	if isFloat {
397		op = map[token.Type]ir.Op{token.Inc: ir.OpAddF, token.Dec: ir.OpSubF}[d.Op]
398	}
399
400	oneConst := ir.Value(&ir.Const{Value: 1})
401	if isFloat {
402		oneConst = &ir.FloatConst{Value: 1.0, Typ: valType}
403	}
404
405	ctx.addInstr(&ir.Instruction{Op: op, Typ: valType, Result: newVal, Args: []ir.Value{res, oneConst}})
406	ctx.genStore(lvalAddr, newVal, d.Expr.Typ)
407	return res, false
408}
409
410func (ctx *Context) codegenIndirection(node *ast.Node) (ir.Value, bool) {
411	exprNode := node.Data.(ast.IndirectionNode).Expr
412	addr, _ := ctx.codegenExpr(exprNode)
413
414	// Resolve named struct types to their actual definitions
415	nodeType := node.Typ
416	if nodeType != nil && nodeType.Kind != ast.TYPE_STRUCT && nodeType.Name != "" {
417		if typeSym := ctx.findTypeSymbol(nodeType.Name); typeSym != nil && typeSym.BxType.Kind == ast.TYPE_STRUCT {
418			nodeType = typeSym.BxType
419		}
420	}
421
422	if nodeType != nil && nodeType.Kind == ast.TYPE_STRUCT {
423		return addr, false
424	}
425
426	loadType := node.Typ
427	if !ctx.isTypedPass && exprNode.Type == ast.Ident {
428		if sym := ctx.findSymbol(exprNode.Data.(ast.IdentNode).Name); sym != nil && sym.IsByteArray {
429			loadType = ast.TypeByte
430		}
431	}
432	return ctx.genLoad(addr, loadType), false
433}
434
435func (ctx *Context) codegenSubscriptAddr(node *ast.Node) ir.Value {
436	d := node.Data.(ast.SubscriptNode)
437	arrayPtr, _ := ctx.codegenExpr(d.Array)
438	indexVal, _ := ctx.codegenExpr(d.Index)
439
440	var scale int64 = int64(ctx.wordSize)
441	if d.Array.Typ != nil {
442		if d.Array.Typ.Kind == ast.TYPE_POINTER || d.Array.Typ.Kind == ast.TYPE_ARRAY {
443			if d.Array.Typ.Base != nil {
444				scale = ctx.getSizeof(d.Array.Typ.Base)
445			}
446		}
447	} else if !ctx.isTypedPass && d.Array.Type == ast.Ident {
448		if sym := ctx.findSymbol(d.Array.Data.(ast.IdentNode).Name); sym != nil && sym.IsByteArray {
449			scale = 1
450		}
451	}
452
453	var scaledIndex ir.Value = indexVal
454	if scale > 1 {
455		scaledIndex = ctx.newTemp()
456		ctx.addInstr(&ir.Instruction{
457			Op:     ir.OpMul,
458			Typ:    ir.GetType(nil, ctx.wordSize),
459			Result: scaledIndex,
460			Args:   []ir.Value{indexVal, &ir.Const{Value: scale}},
461		})
462	}
463
464	resultAddr := ctx.newTemp()
465	ctx.addInstr(&ir.Instruction{
466		Op:     ir.OpAdd,
467		Typ:    ir.GetType(nil, ctx.wordSize),
468		Result: resultAddr,
469		Args:   []ir.Value{arrayPtr, scaledIndex},
470	})
471	return resultAddr
472}
473
474func (ctx *Context) codegenAddressOf(node *ast.Node) (ir.Value, bool) {
475	lvalNode := node.Data.(ast.AddressOfNode).LValue
476	if lvalNode.Type == ast.Ident {
477		name := lvalNode.Data.(ast.IdentNode).Name
478		if sym := ctx.findSymbol(name); sym != nil {
479			isTypedArray := sym.BxType != nil && sym.BxType.Kind == ast.TYPE_ARRAY
480			if sym.Type == symFunc || isTypedArray {
481				return sym.IRVal, false
482			}
483			if sym.IsVector {
484				res, _ := ctx.codegenExpr(lvalNode)
485				return res, false
486			}
487		}
488	}
489	return ctx.codegenLvalue(lvalNode), false
490}
491
492func (ctx *Context) codegenFuncCall(node *ast.Node) (ir.Value, bool) {
493	d := node.Data.(ast.FuncCallNode)
494	if d.FuncExpr.Type == ast.Ident {
495		name := d.FuncExpr.Data.(ast.IdentNode).Name
496		if sym := ctx.findSymbol(name); sym != nil && sym.Type == symVar && !sym.IsVector {
497			util.Error(d.FuncExpr.Tok, "'%s' is a variable but is used as a function", name)
498		}
499	}
500
501	funcVal, _ := ctx.codegenExpr(d.FuncExpr)
502
503	// Get function signature for type checking
504	var expectedParamTypes []*ast.BxType
505	isVariadic := false
506
507	if d.FuncExpr.Type == ast.Ident {
508		name := d.FuncExpr.Data.(ast.IdentNode).Name
509		if sym := ctx.findSymbol(name); sym != nil {
510			if sym.Node != nil {
511				if fd, ok := sym.Node.Data.(ast.FuncDeclNode); ok {
512					isVariadic = fd.HasVarargs
513					// Extract parameter types
514					for _, param := range fd.Params {
515						// Handle both typed parameters (VarDeclNode) and untyped parameters (IdentNode)
516						if paramData, ok := param.Data.(ast.VarDeclNode); ok {
517							expectedParamTypes = append(expectedParamTypes, paramData.Type)
518						}
519						// For IdentNode (untyped parameters), we can't extract type info, so skip
520					}
521				}
522			}
523			if !isVariadic && sym.Type == symExtrn {
524				isVariadic = true
525			}
526		}
527	}
528
529	argVals := make([]ir.Value, len(d.Args))
530	argTypes := make([]ir.Type, len(d.Args))
531	for i := len(d.Args) - 1; i >= 0; i-- {
532		argVals[i], _ = ctx.codegenExpr(d.Args[i])
533
534		// For typed functions with known parameter types, use the expected type
535		var expectedArgType *ast.BxType
536		if i < len(expectedParamTypes) {
537			expectedArgType = expectedParamTypes[i]
538		}
539
540		// If we have an expected type and the argument is a literal that can be coerced
541		if expectedArgType != nil && d.Args[i].Typ != nil {
542			argType := d.Args[i].Typ
543
544			// Handle float literal coercion to specific float types
545			if argType.Kind == ast.TYPE_LITERAL_FLOAT && expectedArgType.Kind == ast.TYPE_FLOAT {
546				// Debug warning for type coercion
547				if ctx.cfg.IsWarningEnabled(config.WarnDebugComp) {
548					util.Warn(ctx.cfg, config.WarnDebugComp, d.Args[i].Tok,
549						"Coercing float literal to %s for parameter %d", expectedArgType.Name, i+1)
550				}
551
552				expectedIrType := ir.GetType(expectedArgType, ctx.wordSize)
553				currentIrType := ir.GetType(argType, ctx.wordSize)
554
555				// Convert if types don't match
556				if currentIrType != expectedIrType {
557					convertedVal := ctx.newTemp()
558					ctx.addInstr(&ir.Instruction{
559						Op:     ir.OpFToF,
560						Typ:    expectedIrType,
561						Result: convertedVal,
562						Args:   []ir.Value{argVals[i]},
563					})
564					argVals[i] = convertedVal
565				}
566				argTypes[i] = expectedIrType
567			} else {
568				argTypes[i] = ir.GetType(d.Args[i].Typ, ctx.wordSize)
569			}
570		} else {
571			argTypes[i] = ir.GetType(d.Args[i].Typ, ctx.wordSize)
572		}
573
574		if isVariadic && argTypes[i] == ir.TypeS {
575			promotedVal := ctx.newTemp()
576			ctx.addInstr(&ir.Instruction{
577				Op:     ir.OpFToF,
578				Typ:    ir.TypeD,
579				Result: promotedVal,
580				Args:   []ir.Value{argVals[i]},
581			})
582			argVals[i] = promotedVal
583			argTypes[i] = ir.TypeD
584		}
585	}
586
587	isStmt := node.Parent != nil && node.Parent.Type == ast.Block
588	var res ir.Value
589	returnType := ir.GetType(node.Typ, ctx.wordSize)
590	callArgs := append([]ir.Value{funcVal}, argVals...)
591
592	if !isStmt && returnType != ir.TypeNone {
593		res = ctx.newTemp()
594	}
595
596	ctx.addInstr(&ir.Instruction{
597		Op:       ir.OpCall,
598		Typ:      returnType,
599		Result:   res,
600		Args:     callArgs,
601		ArgTypes: argTypes,
602	})
603
604	return res, false
605}
606
607func (ctx *Context) codegenTypeCast(node *ast.Node) (ir.Value, bool) {
608	d := node.Data.(ast.TypeCastNode)
609	val, _ := ctx.codegenExpr(d.Expr)
610
611	sourceType := d.Expr.Typ
612	targetType := d.TargetType
613
614	if ir.GetType(sourceType, ctx.wordSize) == ir.GetType(targetType, ctx.wordSize) {
615		return val, false
616	}
617
618	res := ctx.newTemp()
619	targetIrType := ir.GetType(targetType, ctx.wordSize)
620
621	sourceIsInt := ctx.isIntegerType(sourceType)
622	sourceIsFloat := ctx.isFloatType(sourceType)
623	targetIsInt := ctx.isIntegerType(targetType)
624	targetIsFloat := ctx.isFloatType(targetType)
625
626	op := ir.OpCast
627	if sourceIsInt && targetIsFloat {
628		op = ir.OpSWToF
629		if ctx.getSizeof(sourceType) == 8 {
630			op = ir.OpSLToF
631		}
632	} else if sourceIsFloat && targetIsFloat {
633		op = ir.OpFToF
634	} else if sourceIsFloat && targetIsInt {
635		op = ir.OpFToSI
636	} else if sourceIsInt && targetIsInt {
637		sourceSize, targetSize := ctx.getSizeof(sourceType), ctx.getSizeof(targetType)
638		if targetSize > sourceSize {
639			switch sourceSize {
640			case 1:
641				op = ir.OpExtSB
642			case 2:
643				op = ir.OpExtSH
644			case 4:
645				op = ir.OpExtSW
646			}
647		}
648	}
649
650	ctx.addInstr(&ir.Instruction{
651		Op:     op,
652		Typ:    targetIrType,
653		Result: res,
654		Args:   []ir.Value{val},
655	})
656
657	return res, false
658}
659
660func (ctx *Context) codegenTernary(node *ast.Node) (ir.Value, bool) {
661	d := node.Data.(ast.TernaryNode)
662	thenL, elseL, endL := ctx.newLabel(), ctx.newLabel(), ctx.newLabel()
663	res := ctx.newTemp()
664	resType := ir.GetType(node.Typ, ctx.wordSize)
665
666	ctx.codegenLogicalCond(d.Cond, thenL, elseL)
667
668	ctx.startBlock(thenL)
669	thenVal, thenTerminates := ctx.codegenExpr(d.ThenExpr)
670	var thenPred *ir.Label
671	if !thenTerminates {
672		if ir.GetType(d.ThenExpr.Typ, ctx.wordSize) != resType {
673			castedVal := ctx.newTemp()
674			ctx.addInstr(&ir.Instruction{Op: ir.OpCast, Typ: resType, Result: castedVal, Args: []ir.Value{thenVal}})
675			thenVal = castedVal
676		}
677		thenPred = ctx.currentBlock.Label
678		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
679	}
680
681	ctx.startBlock(elseL)
682	elseVal, elseTerminates := ctx.codegenExpr(d.ElseExpr)
683	var elsePred *ir.Label
684	if !elseTerminates {
685		if ir.GetType(d.ElseExpr.Typ, ctx.wordSize) != resType {
686			castedVal := ctx.newTemp()
687			ctx.addInstr(&ir.Instruction{Op: ir.OpCast, Typ: resType, Result: castedVal, Args: []ir.Value{elseVal}})
688			elseVal = castedVal
689		}
690		elsePred = ctx.currentBlock.Label
691		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
692	}
693
694	terminates := thenTerminates && elseTerminates
695	if !terminates {
696		ctx.startBlock(endL)
697		phiArgs := []ir.Value{}
698		if !thenTerminates {
699			phiArgs = append(phiArgs, thenPred, thenVal)
700		}
701		if !elseTerminates {
702			phiArgs = append(phiArgs, elsePred, elseVal)
703		}
704		ctx.addInstr(&ir.Instruction{Op: ir.OpPhi, Typ: resType, Result: res, Args: phiArgs})
705	}
706	return res, terminates
707}
708
709func (ctx *Context) codegenAutoAlloc(node *ast.Node) (ir.Value, bool) {
710	d := node.Data.(ast.AutoAllocNode)
711	sizeVal, _ := ctx.codegenExpr(d.Size)
712	res := ctx.newTemp()
713
714	sizeInBytes := ctx.newTemp()
715	wordType := ir.GetType(nil, ctx.wordSize)
716	ctx.addInstr(&ir.Instruction{
717		Op:     ir.OpMul,
718		Typ:    wordType,
719		Result: sizeInBytes,
720		Args:   []ir.Value{sizeVal, &ir.Const{Value: int64(ctx.wordSize)}},
721	})
722
723	ctx.addInstr(&ir.Instruction{
724		Op:     ir.OpAlloc,
725		Typ:    wordType,
726		Result: res,
727		Args:   []ir.Value{sizeInBytes},
728		Align:  ctx.stackAlign,
729	})
730	return res, false
731}
732
733func (ctx *Context) codegenStructLiteral(node *ast.Node) (ir.Value, bool) {
734	d := node.Data.(ast.StructLiteralNode)
735	structType := node.Typ
736	if structType == nil || structType.Kind != ast.TYPE_STRUCT {
737		util.Error(node.Tok, "internal: struct literal has invalid type")
738		return nil, false
739	}
740
741	size := ctx.getSizeof(structType)
742	align := ctx.getAlignof(structType)
743	structPtr := ctx.newTemp()
744	ctx.addInstr(&ir.Instruction{
745		Op:     ir.OpAlloc,
746		Typ:    ir.GetType(nil, ctx.wordSize),
747		Result: structPtr,
748		Args:   []ir.Value{&ir.Const{Value: size}},
749		Align:  int(align),
750	})
751
752	if d.Names == nil {
753		var currentOffset int64
754		for i, valNode := range d.Values {
755			field := structType.Fields[i].Data.(ast.VarDeclNode)
756			fieldAlign := ctx.getAlignof(field.Type)
757			currentOffset = util.AlignUp(currentOffset, fieldAlign)
758			fieldAddr := ctx.newTemp()
759			ctx.addInstr(&ir.Instruction{
760				Op:     ir.OpAdd,
761				Typ:    ir.GetType(nil, ctx.wordSize),
762				Result: fieldAddr,
763				Args:   []ir.Value{structPtr, &ir.Const{Value: currentOffset}},
764			})
765			val, _ := ctx.codegenExpr(valNode)
766			ctx.genStore(fieldAddr, val, field.Type)
767			currentOffset += ctx.getSizeof(field.Type)
768		}
769	} else {
770		fieldOffsets := make(map[string]int64)
771		fieldTypes := make(map[string]*ast.BxType)
772		var currentOffset int64
773		for _, fieldNode := range structType.Fields {
774			fieldData := fieldNode.Data.(ast.VarDeclNode)
775			fieldAlign := ctx.getAlignof(fieldData.Type)
776			currentOffset = util.AlignUp(currentOffset, fieldAlign)
777			fieldOffsets[fieldData.Name] = currentOffset
778			fieldTypes[fieldData.Name] = fieldData.Type
779			currentOffset += ctx.getSizeof(fieldData.Type)
780		}
781
782		for i, nameNode := range d.Names {
783			fieldName := nameNode.Data.(ast.IdentNode).Name
784			offset, ok := fieldOffsets[fieldName]
785			if !ok {
786				util.Error(nameNode.Tok, "internal: struct '%s' has no field '%s'", structType.Name, fieldName)
787				continue
788			}
789			fieldAddr := ctx.newTemp()
790			ctx.addInstr(&ir.Instruction{
791				Op:     ir.OpAdd,
792				Typ:    ir.GetType(nil, ctx.wordSize),
793				Result: fieldAddr,
794				Args:   []ir.Value{structPtr, &ir.Const{Value: offset}},
795			})
796
797			val, _ := ctx.codegenExpr(d.Values[i])
798			ctx.genStore(fieldAddr, val, fieldTypes[fieldName])
799		}
800	}
801
802	return structPtr, false
803}
804
805func (ctx *Context) codegenArrayLiteral(node *ast.Node) (ir.Value, bool) {
806	d := node.Data.(ast.ArrayLiteralNode)
807
808	// For array literals, we need the element type and count from the literal itself
809	elemType := d.ElementType
810	elemSize := ctx.getSizeof(elemType)
811	elemAlign := ctx.getAlignof(elemType)
812	arraySize := int64(len(d.Values)) * elemSize
813
814	// Allocate memory for the array
815	arrayPtr := ctx.newTemp()
816	ctx.addInstr(&ir.Instruction{
817		Op:     ir.OpAlloc,
818		Typ:    ir.GetType(nil, ctx.wordSize),
819		Result: arrayPtr,
820		Args:   []ir.Value{&ir.Const{Value: arraySize}},
821		Align:  int(elemAlign),
822	})
823
824	// Initialize each element
825	for i, valNode := range d.Values {
826		elemOffset := int64(i) * elemSize
827		elemAddr := ctx.newTemp()
828		ctx.addInstr(&ir.Instruction{
829			Op:     ir.OpAdd,
830			Typ:    ir.GetType(nil, ctx.wordSize),
831			Result: elemAddr,
832			Args:   []ir.Value{arrayPtr, &ir.Const{Value: elemOffset}},
833		})
834		val, _ := ctx.codegenExpr(valNode)
835		ctx.genStore(elemAddr, val, elemType)
836	}
837
838	return arrayPtr, false
839}
840
841func (ctx *Context) codegenReturn(node *ast.Node) bool {
842	d := node.Data.(ast.ReturnNode)
843	var retVal ir.Value
844	if d.Expr != nil {
845		retVal, _ = ctx.codegenExpr(d.Expr)
846	} else if ctx.currentFunc != nil && ctx.currentFunc.ReturnType != ir.TypeNone {
847		retVal = &ir.Const{Value: 0}
848	}
849	ctx.addInstr(&ir.Instruction{Op: ir.OpRet, Args: []ir.Value{retVal}})
850	ctx.currentBlock = nil
851	return true
852}
853
854func (ctx *Context) codegenIf(node *ast.Node) bool {
855	d := node.Data.(ast.IfNode)
856	thenL, endL := ctx.newLabel(), ctx.newLabel()
857	elseL := endL
858	if d.ElseBody != nil {
859		elseL = ctx.newLabel()
860	}
861
862	ctx.codegenLogicalCond(d.Cond, thenL, elseL)
863
864	ctx.startBlock(thenL)
865	thenTerminates := ctx.codegenStmt(d.ThenBody)
866	if !thenTerminates {
867		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
868	}
869
870	var elseTerminates bool
871	if d.ElseBody != nil {
872		ctx.startBlock(elseL)
873		elseTerminates = ctx.codegenStmt(d.ElseBody)
874		if !elseTerminates {
875			ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{endL}})
876		}
877	}
878
879	if !thenTerminates || !elseTerminates {
880		ctx.startBlock(endL)
881	}
882	return thenTerminates && (d.ElseBody != nil && elseTerminates)
883}
884
885func (ctx *Context) codegenWhile(node *ast.Node) bool {
886	d := node.Data.(ast.WhileNode)
887	startL, bodyL, endL := ctx.newLabel(), ctx.newLabel(), ctx.newLabel()
888
889	oldBreak, oldContinue := ctx.breakLabel, ctx.continueLabel
890	ctx.breakLabel, ctx.continueLabel = endL, startL
891	defer func() { ctx.breakLabel, ctx.continueLabel = oldBreak, oldContinue }()
892
893	ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{startL}})
894	ctx.startBlock(startL)
895	ctx.codegenLogicalCond(d.Cond, bodyL, endL)
896
897	ctx.startBlock(bodyL)
898	bodyTerminates := ctx.codegenStmt(d.Body)
899	if !bodyTerminates {
900		ctx.addInstr(&ir.Instruction{Op: ir.OpJmp, Args: []ir.Value{startL}})
901	}
902
903	ctx.startBlock(endL)
904	return false
905}
906
907func getBinaryOpAndType(op token.Type, resultAstType *ast.BxType, wordSize int) (ir.Op, ir.Type) {
908	if resultAstType != nil && (resultAstType.Kind == ast.TYPE_FLOAT || resultAstType.Kind == ast.TYPE_LITERAL_FLOAT) {
909		typ := ir.GetType(resultAstType, wordSize)
910		switch op {
911		case token.Plus, token.PlusEq, token.EqPlus: return ir.OpAddF, typ
912		case token.Minus, token.MinusEq, token.EqMinus: return ir.OpSubF, typ
913		case token.Star, token.StarEq, token.EqStar: return ir.OpMulF, typ
914		case token.Slash, token.SlashEq, token.EqSlash: return ir.OpDivF, typ
915		case token.Rem, token.RemEq, token.EqRem: return ir.OpRemF, typ
916		case token.EqEq: return ir.OpCEq, typ
917		case token.Neq: return ir.OpCNeq, typ
918		case token.Lt: return ir.OpCLt, typ
919		case token.Gt: return ir.OpCGt, typ
920		case token.Lte: return ir.OpCLe, typ
921		case token.Gte: return ir.OpCGe, typ
922		}
923	}
924
925	typ := ir.GetType(resultAstType, wordSize)
926	switch op {
927	case token.Plus, token.PlusEq, token.EqPlus: return ir.OpAdd, typ
928	case token.Minus, token.MinusEq, token.EqMinus: return ir.OpSub, typ
929	case token.Star, token.StarEq, token.EqStar: return ir.OpMul, typ
930	case token.Slash, token.SlashEq, token.EqSlash: return ir.OpDiv, typ
931	case token.Rem, token.RemEq, token.EqRem: return ir.OpRem, typ
932	case token.And, token.AndEq, token.EqAnd: return ir.OpAnd, typ
933	case token.Or, token.OrEq, token.EqOr: return ir.OpOr, typ
934	case token.Xor, token.XorEq, token.EqXor: return ir.OpXor, typ
935	case token.Shl, token.ShlEq, token.EqShl: return ir.OpShl, typ
936	case token.Shr, token.ShrEq, token.EqShr: return ir.OpShr, typ
937	case token.EqEq: return ir.OpCEq, typ
938	case token.Neq:
939		return ir.OpCNeq, typ
940	case token.Lt:
941		return ir.OpCLt, typ
942	case token.Gt:
943		return ir.OpCGt, typ
944	case token.Lte:
945		return ir.OpCLe, typ
946	case token.Gte:
947		return ir.OpCGe, typ
948	}
949	return -1, -1
950}
951
952// codegenTypeOf generates code for typeof(expr) which returns a string representation of the type
953func (ctx *Context) codegenTypeOf(node *ast.Node) (ir.Value, bool) {
954	d := node.Data.(ast.TypeOfNode)
955
956	// Type check the expression to determine its type
957	_, _ = ctx.codegenExpr(d.Expr)
958
959	// Get the type of the expression
960	var exprType *ast.BxType
961	if d.Expr.Typ != nil {
962		exprType = d.Expr.Typ
963	} else {
964		exprType = ast.TypeUntyped
965	}
966
967	// Convert the type to its string representation
968	typeStr := ast.TypeToString(exprType)
969
970	// Add the string to the string table and return a reference to it
971	return ctx.addString(typeStr), false
972}