package irgen
import (
"fmt"
"hash/fnv"
"math/big"
"slices"
"strconv"
"strings"
"github.com/marzeq/qk/attributes"
"github.com/marzeq/qk/ir"
"github.com/marzeq/qk/parser"
"github.com/marzeq/qk/shared"
"github.com/marzeq/qk/symbols"
"github.com/marzeq/qk/tokeniser"
"github.com/marzeq/qk/types"
)
type Env struct {
Parent *Env
Variables map[*symbols.Symbol]ir.SlotID
}
func NewEnv(parent *Env) *Env {
return &Env{
Parent: parent,
Variables: make(map[*symbols.Symbol]ir.SlotID),
}
}
func (e *Env) Lookup(sym *symbols.Symbol) (ir.SlotID, bool) {
for scope := e; scope != nil; scope = scope.Parent {
if slot, ok := scope.Variables[sym]; ok {
return slot, true
}
}
return 0, false
}
type Generator struct {
Module *ir.Module
ModuleName string
MainModule string
DependencyInitializers []string
DemandedTraitSlots map[string]map[int]bool
currentFunction *ir.Function
currentBlock *ir.Block
currentEnv *Env
globals map[*symbols.Symbol]string
loopTargets []loopTargets
deferScopes [][]parser.Node
dynamicGlobals []dynamicGlobalInitializer
initializingGlobal bool
specializedVariadicElements map[*symbols.Symbol][]ir.Operand
specializedConstants map[*symbols.Symbol]symbols.SpecializationConstant
lambdaFunctions []*parser.FunctionDefNode
queuedLambdas map[*symbols.Symbol]bool
templateMode bool
currentSourceOrigin *ir.SourceOrigin
}
type dynamicGlobalInitializer struct {
name string
expr parser.ExpressionNode
}
type loopTargets struct {
breakTarget ir.BlockID
continueTarget ir.BlockID
cleanupDepth int
}
func (g *Generator) Emit(instruction ir.Instr) {
if g.currentBlock == nil {
panic("cannot emit instruction without a current block")
}
if g.currentBlockHasTerminator() {
panic("cannot emit instruction after block terminator")
}
index := len(g.currentBlock.Instr)
g.currentBlock.Instr = append(g.currentBlock.Instr, instruction)
if g.currentSourceOrigin != nil {
if g.currentFunction.Origins == nil {
g.currentFunction.Origins = make(map[ir.InstructionLocation]ir.SourceOrigin)
}
g.currentFunction.Origins[ir.InstructionLocation{Block: g.currentBlock.ID, Index: index}] = *g.currentSourceOrigin
}
}
func (g *Generator) Generate(root *parser.RootNode) *ir.Module {
return g.GenerateRoots([]*parser.RootNode{root})
}
func (g *Generator) GenerateRoots(roots []*parser.RootNode) *ir.Module {
g.Module = &ir.Module{}
g.globals = make(map[*symbols.Symbol]string)
g.dynamicGlobals = nil
g.lambdaFunctions = nil
g.queuedLambdas = make(map[*symbols.Symbol]bool)
for _, root := range roots {
for _, node := range root.Body {
if node, ok := node.(*parser.DeclarationNode); ok {
if len(node.GenericParameters) != 0 || node.Symbol.InlineComptime {
continue
}
g.generateGlobalDeclaration(node)
}
}
}
for _, root := range roots {
for _, rawNode := range root.Body {
node, ok := rawNode.(*parser.FunctionDefNode)
if !ok {
continue
}
if node.IsGeneric() {
continue
}
if node.Body == nil {
var externFrom string
for _, attr := range node.Attributes {
switch attr := attr.(type) {
case attributes.FunctionAttributeForeign:
externFrom = attr.From
}
}
if externFrom == "" {
panic("function without body must have foreign attribute")
}
sig := g.buildFunctionSignature(node)
name := node.Name
g.Module.AddExtern(ir.ExternDecl{Name: name, Signature: sig, From: externFrom})
g.generateDefaultWrappers(node, name)
continue
}
g.GenerateFunction(node)
}
}
if len(g.dynamicGlobals) > 0 || len(g.DependencyInitializers) > 0 {
g.generateModuleInitializer()
}
for i := 0; i < len(g.lambdaFunctions); i++ {
g.GenerateFunction(g.lambdaFunctions[i])
}
return g.Module
}
func (g *Generator) generateGlobalDeclaration(node *parser.DeclarationNode) {
if node.Symbol == nil {
panic("global declaration symbol is nil")
}
if foreign, ok := node.Symbol.Attributes.Get(attributes.AttributeTypeForeign).(attributes.FunctionAttributeForeign); ok {
name := foreign.From
if name == "" {
name = node.Name
}
g.Module.AddExternGlobal(ir.ExternGlobal{
Name: name,
Type: node.Symbol.Type,
Mutable: node.Mutable,
})
g.globals[node.Symbol] = name
return
}
linkage := ir.LinkageInternal
visibility := ir.VisibilityDefault
if node.Symbol.Public {
linkage = ir.LinkageExternal
visibility = ir.VisibilityHidden
}
name := g.mangleGlobalName(g.ModuleName, node.Name)
var value ir.Operand
var constant bool
if node.Comptime {
value, constant = g.tryGenerateComptimeInitializer(node.Value)
} else {
value, constant = g.tryGenerateGlobalInitializer(node.Value)
}
if !constant {
value = ir.ZeroConstOperand(node.Symbol.Type)
g.dynamicGlobals = append(g.dynamicGlobals, dynamicGlobalInitializer{
name: name,
expr: node.Value,
})
}
g.Module.AddGlobal(ir.Global{
Name: name,
Type: node.Symbol.Type,
Mutable: node.Mutable || !constant,
Linkage: linkage,
Visibility: visibility,
Value: value,
})
g.globals[node.Symbol] = g.Module.Globals[len(g.Module.Globals)-1].Name
}
func (g *Generator) tryGenerateComptimeInitializer(expr parser.ExpressionNode) (ir.Operand, bool) {
if value, ok := g.tryGenerateGlobalInitializer(expr); ok {
return value, true
}
switch node := expr.(type) {
case *parser.SizeOfNode:
return ir.SizeofConstOperand(node.OperandType), true
case *parser.CastNode:
value, ok := g.tryGenerateComptimeInitializer(node.Operand)
if !ok {
return ir.Operand{}, false
}
value.Type = node.GetType()
return value, true
case *parser.UnaryOpNode:
value, ok := g.tryGenerateComptimeInitializer(node.Operand)
if !ok {
return ir.Operand{}, false
}
switch node.Op {
case parser.UnaryOpNegate:
zero := ir.IntConstOperand("0", node.GetType())
return ir.BinaryConstOperand("sub", zero, value, node.GetType()), true
case parser.UnaryOpBitwiseNot:
allBits := ir.IntConstOperand("-1", node.GetType())
return ir.BinaryConstOperand("xor", value, allBits, node.GetType()), true
case parser.UnaryOpLogicalNot:
one := ir.BoolConstOperand(true)
return ir.BinaryConstOperand("xor", value, one, node.GetType()), true
}
return ir.Operand{}, false
case *parser.BinaryOpNode:
left, leftOK := g.tryGenerateComptimeInitializer(node.Operand1)
right, rightOK := g.tryGenerateComptimeInitializer(node.Operand2)
if !leftOK || !rightOK {
return ir.Operand{}, false
}
operator := ""
switch node.Op {
case parser.BinaryOpAdd:
operator = "add"
case parser.BinaryOpSubtract:
operator = "sub"
case parser.BinaryOpMultiply:
operator = "mul"
case parser.BinaryOpDivide:
if types.IsSigned(node.GetType()) {
operator = "sdiv"
} else {
operator = "udiv"
}
case parser.BinaryOpModulo:
if types.IsSigned(node.GetType()) {
operator = "srem"
} else {
operator = "urem"
}
case parser.BinaryOpBitwiseAnd:
operator = "and"
case parser.BinaryOpBitwiseOr:
operator = "or"
case parser.BinaryOpBitwiseXor:
operator = "xor"
case parser.BinaryOpShiftLeft:
operator = "shl"
case parser.BinaryOpShiftRight:
if types.IsSigned(node.GetType()) {
operator = "ashr"
} else {
operator = "lshr"
}
}
if operator == "" {
return ir.Operand{}, false
}
return ir.BinaryConstOperand(operator, left, right, node.GetType()), true
}
return ir.Operand{}, false
}
func (g *Generator) tryGenerateGlobalInitializer(expr parser.ExpressionNode) (ir.Operand, bool) {
switch node := expr.(type) {
case *parser.BlockNode:
result, ok := parser.BlockResult(node)
if !ok || len(node.Body) != 1 {
return ir.Operand{}, false
}
return g.tryGenerateGlobalInitializer(result)
case *parser.NoInitializerNode:
return ir.ZeroConstOperand(node.GetType()), true
case *parser.IntegerLiteralNode:
if types.IsFloat(node.GetType()) {
return ir.FloatConstOperand(node.Value, node.GetType()), true
}
return ir.IntConstOperand(node.Value, node.GetType()), true
case *parser.FloatLiteralNode:
return ir.FloatConstOperand(node.Value, node.GetType()), true
case *parser.BoolLiteralNode:
return ir.BoolConstOperand(node.Value == string(tokeniser.KeywordTrue)), true
case *parser.CharLiteralNode:
return ir.IntConstOperand(fmt.Sprint(int(node.Value)), node.GetType()), true
case *parser.CStringLiteralNode:
return ir.CStringConstOperand(node.Value), true
case *parser.NilLiteralNode:
if _, traitPointer := types.Underlying(node.GetType()).(types.TraitPointerType); traitPointer {
return ir.ZeroConstOperand(node.GetType()), true
}
return ir.NullConstOperand(node.GetType()), true
case *parser.EnumLiteralNode:
return ir.IntConstOperand(node.Value, node.GetType()), true
case *parser.FieldAccessNode:
if node.IsEnumValue {
return ir.IntConstOperand(node.EnumValue, node.GetType()), true
}
if node.IsFlagValue {
return ir.IntConstOperand(node.FlagValue, node.FlagType), true
}
return ir.Operand{}, false
case *parser.StructLiteralNode:
if node.NoInitRemaining && len(node.Fields) == 0 {
return ir.ZeroConstOperand(node.GetType()), true
}
if flagType, ok := types.Underlying(node.GetType()).(types.FlagsType); ok {
value := new(big.Int)
for _, member := range node.FlagMembers {
raw, _ := flagType.VariantValue(member)
part, _ := new(big.Int).SetString(raw, 10)
value.Or(value, part)
}
return ir.IntConstOperand(value.String(), node.GetType()), true
}
structType, ok := types.Underlying(node.GetType()).(types.StructType)
if !ok {
return ir.Operand{}, false
}
fields := make(map[string]parser.ExpressionNode, len(node.Fields))
for _, field := range node.Fields {
fields[field.L] = field.R
}
values := make([]ir.Operand, len(structType.Fields))
for i, field := range structType.Fields {
value, exists := fields[field.L]
if !exists {
if node.NoInitRemaining {
values[i] = ir.ZeroConstOperand(field.R)
continue
}
return ir.Operand{}, false
}
fieldValue, constant := g.tryGenerateGlobalInitializer(value)
if !constant {
return ir.Operand{}, false
}
values[i] = fieldValue
}
return ir.StructConstOperand(structType, values), true
default:
return ir.Operand{}, false
}
}
func (g *Generator) generateModuleInitializer() {
name := g.mangleFunctionName(g.ModuleName, "__module_init")
guardName := g.mangleGlobalName(g.ModuleName, "__module_initialized")
g.Module.AddGlobal(ir.Global{
Name: guardName, Type: types.PrimitiveBool, Mutable: true,
Linkage: ir.LinkageInternal, Value: ir.BoolConstOperand(false),
})
fn := ir.NewFunction(name, ir.LinkageExternal, nil)
fn.Visibility = ir.VisibilityHidden
fn.Signature = ir.FunctionSignature{ReturnType: types.PrimitiveVoid}
g.Module.AddFunction(fn)
g.Module.Initializer = name
g.currentFunction = fn
g.currentEnv = NewEnv(nil)
g.deferScopes = nil
entry := fn.NewBlock("entry")
initialize := fn.NewBlock("initialize")
done := fn.NewBlock("done")
fn.Entry = entry.ID
g.currentBlock = entry
initialized := fn.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.LoadGlobal{Dest: initialized, Name: guardName, Type: types.PrimitiveBool})
g.Emit(ir.Branch{Cond: ir.ValueOperand(initialized, types.PrimitiveBool), Then: done.ID, Else: initialize.ID})
g.currentBlock = initialize
g.Emit(ir.StoreGlobal{Name: guardName, Value: ir.BoolConstOperand(true)})
for _, dependency := range g.DependencyInitializers {
signature := ir.FunctionSignature{ReturnType: types.PrimitiveVoid}
g.Module.AddExtern(ir.ExternDecl{Name: dependency, Signature: signature, Visibility: ir.VisibilityHidden})
g.Emit(ir.Call{Name: dependency, Signature: signature})
}
for _, global := range g.dynamicGlobals {
g.initializingGlobal = true
value := g.GenerateExpr(global.expr)
g.initializingGlobal = false
g.Emit(ir.StoreGlobal{Name: global.name, Value: value})
}
g.Emit(ir.Jump{Target: done.ID})
g.currentBlock = done
g.Emit(ir.Return{})
}
func (g *Generator) GenerateFunction(fn *parser.FunctionDefNode) {
name := fn.Symbol.Name
linkage := ir.LinkageInternal
visibility := ir.VisibilityDefault
if export, ok := fn.Attributes.Get(attributes.AttributeTypeExport).(attributes.FunctionAttributeExport); ok {
name = export.As
linkage = ir.LinkageExternal
} else {
if fn.Symbol.Public || fn.Symbol.Method || g.isProgramEntryFunction(fn.Symbol.Name) {
linkage = ir.LinkageExternal
visibility = ir.VisibilityHidden
}
name = g.mangleFunctionName(g.ModuleName, fn.Symbol.Name)
}
irFn := ir.NewFunction(name, linkage, fn.Attributes)
irFn.Visibility = visibility
irFn.Signature = g.buildFunctionSignature(fn)
g.Module.AddFunction(irFn)
if g.isProgramEntryFunction(fn.Symbol.Name) {
g.Module.Entry = name
}
g.currentFunction = irFn
g.currentBlock = irFn.NewBlock("entry")
irFn.Entry = g.currentBlock.ID
g.currentEnv = NewEnv(nil)
g.deferScopes = nil
g.emitFunctionParams(fn)
g.generateFunctionBody(fn)
g.currentEnv = nil
g.currentBlock = nil
g.currentFunction = nil
g.deferScopes = nil
g.generateDefaultWrappers(fn, name)
if g.templateMode {
return
}
g.generateTypedVariadicWrappers(fn, name)
if !fn.Symbol.Signature.TypedVariadic {
g.generateConstantWrapper(fn, name)
}
}
// GenerateGenericTemplate lowers an attributed generic body into symbolic QK
// IR. Type parameters deliberately remain in the result and are substituted
// before target lowering.
func (g *Generator) GenerateGenericTemplate(fn *parser.FunctionDefNode) []*ir.Function {
if fn == nil || !fn.IsGeneric() {
return nil
}
if g.Module == nil {
g.Module = &ir.Module{}
}
if g.globals == nil {
g.globals = make(map[*symbols.Symbol]string)
}
if g.queuedLambdas == nil {
g.queuedLambdas = make(map[*symbols.Symbol]bool)
}
before := len(g.Module.Functions)
g.templateMode = true
g.GenerateFunction(fn)
g.templateMode = false
return append([]*ir.Function(nil), g.Module.Functions[before:]...)
}
func (g *Generator) generateFunctionBody(fn *parser.FunctionDefNode) {
if fn.ExpressionBody {
ret := g.GenerateExpr(fn.Body.(parser.ExpressionNode))
if !g.currentBlockHasTerminator() {
if g.isVoidFunction(fn) {
g.Emit(ir.Return{})
} else {
g.Emit(ir.Return{HasValue: true, Value: ret})
}
}
} else {
g.GenerateNode(fn.Body)
if !g.currentBlockHasTerminator() {
if g.isVoidFunction(fn) {
g.Emit(ir.Return{})
} else {
panic("non-void function may fall through without return")
}
}
}
}
func (g *Generator) generateDefaultWrappers(fn *parser.FunctionDefNode, targetName string) {
if fn.Symbol.Signature.TypedVariadic {
return
}
required := fn.Symbol.Signature.RequiredParameters
total := len(fn.Args)
linkage := ir.LinkageInternal
visibility := ir.VisibilityDefault
if fn.Attributes.Get(attributes.AttributeTypeExport) != nil {
linkage = ir.LinkageExternal
} else if fn.Symbol.Public {
linkage = ir.LinkageExternal
visibility = ir.VisibilityHidden
}
for arity := required; arity < total; arity++ {
wrapper := ir.NewFunction(g.defaultWrapperName(targetName, arity), linkage, nil)
wrapper.Visibility = visibility
wrapper.Signature = ir.FunctionSignature{
ParamTypes: append([]types.Type(nil), fn.Symbol.Signature.Parameters[:arity]...),
ReturnType: fn.Symbol.Signature.ReturnType,
}
g.Module.AddFunction(wrapper)
g.currentFunction = wrapper
g.currentBlock = wrapper.NewBlock("entry")
wrapper.Entry = g.currentBlock.ID
g.currentEnv = NewEnv(nil)
g.deferScopes = nil
args := make([]ir.Operand, 0, total)
for i := 0; i < arity; i++ {
arg := fn.Args[i]
slot := wrapper.NewSlot(arg.Symbol.Type, fmt.Sprintf("arg%d", i))
wrapper.AddParameter(arg.Name, arg.Symbol.Type, slot)
g.currentEnv.Variables[arg.Symbol] = slot
g.Emit(ir.Alloca{Slot: slot})
incoming := wrapper.NewValueOfType(arg.Symbol.Type)
g.Emit(ir.Store{Slot: slot, Value: ir.ValueOperand(incoming, arg.Symbol.Type)})
}
for i := 0; i < arity; i++ {
arg := fn.Args[i]
args = append(args, g.generateIdentifierExpr(&parser.IdentifierNode{Symbol: arg.Symbol, Type: arg.Symbol.Type}))
}
for i := arity; i < total; i++ {
value := g.GenerateExpr(fn.Args[i].Default)
args = append(args, value)
// Later defaults can refer to the value produced by this default.
slot := wrapper.NewSlot(fn.Args[i].Symbol.Type, fmt.Sprintf("default%d", i))
g.currentEnv.Variables[fn.Args[i].Symbol] = slot
g.Emit(ir.Alloca{Slot: slot})
g.Emit(ir.Store{Slot: slot, Value: value})
}
callSig := g.buildFunctionSignature(fn)
if callSig.ReturnType == nil || callSig.ReturnType.Equals(types.PrimitiveVoid) {
g.Emit(ir.Call{Name: targetName, Args: args, Signature: callSig})
g.Emit(ir.Return{})
} else {
dst := wrapper.NewValueOfType(callSig.ReturnType)
g.Emit(ir.Call{Dest: dst, Name: targetName, Args: args, Signature: callSig})
g.Emit(ir.Return{HasValue: true, Value: ir.ValueOperand(dst, callSig.ReturnType)})
}
g.currentEnv = nil
g.currentBlock = nil
g.currentFunction = nil
g.deferScopes = nil
}
}
func (g *Generator) generateTypedVariadicWrappers(fn *parser.FunctionDefNode, targetName string) {
if !fn.Symbol.Signature.TypedVariadic || len(fn.Symbol.Signature.TypedVariadicArities) == 0 {
return
}
arities := make([]int, 0, len(fn.Symbol.Signature.TypedVariadicArities))
for arity := range fn.Symbol.Signature.TypedVariadicArities {
arities = append(arities, arity)
}
slices.Sort(arities)
fixedCount := len(fn.Symbol.Signature.Parameters) - 1
constants := selectedSpecializationConstants(fn.Symbol.Signature)
sliceType := fn.Symbol.Signature.Parameters[fixedCount]
for _, arity := range arities {
linkage := ir.LinkageInternal
visibility := ir.VisibilityDefault
if fn.Attributes.Get(attributes.AttributeTypeExport) != nil || fn.Symbol.Public || fn.Symbol.Method {
linkage = ir.LinkageExternal
visibility = ir.VisibilityHidden
}
paramTypes := make([]types.Type, 0, fixedCount+arity)
for i, parameter := range fn.Symbol.Signature.Parameters[:fixedCount] {
if _, specialized := constants[i]; !specialized {
paramTypes = append(paramTypes, parameter)
}
}
for range arity {
paramTypes = append(paramTypes, fn.Symbol.Signature.VariadicElement)
}
wrapper := ir.NewFunction(g.specializedFunctionName(g.typedVariadicWrapperName(targetName, arity), constants), linkage, nil)
wrapper.Visibility = visibility
wrapper.Signature = ir.FunctionSignature{
ParamTypes: paramTypes,
ReturnType: fn.Symbol.Signature.ReturnType,
}
g.Module.AddFunction(wrapper)
g.currentFunction = wrapper
g.currentBlock = wrapper.NewBlock("entry")
wrapper.Entry = g.currentBlock.ID
g.currentEnv = NewEnv(nil)
g.deferScopes = nil
variadicElements := make([]ir.Operand, 0, arity)
variadicSlots := make([]ir.SlotID, 0, arity)
for i := 0; i < fixedCount; i++ {
if _, specialized := constants[i]; specialized {
continue
}
paramType := fn.Symbol.Signature.Parameters[i]
slot := wrapper.NewSlot(paramType, fmt.Sprintf("arg%d", i))
g.currentEnv.Variables[fn.Args[i].Symbol] = slot
wrapper.AddParameter(fn.Args[i].Name, paramType, slot)
g.Emit(ir.Alloca{Slot: slot})
value := wrapper.NewValueOfType(paramType)
g.Emit(ir.Store{Slot: slot, Value: ir.ValueOperand(value, paramType)})
}
for i := 0; i < arity; i++ {
paramType := fn.Symbol.Signature.VariadicElement
slot := wrapper.NewSlot(paramType, fmt.Sprintf("variadic%d", i))
wrapper.AddParameter(fmt.Sprintf("variadic%d", i), paramType, slot)
g.Emit(ir.Alloca{Slot: slot})
value := wrapper.NewValueOfType(paramType)
g.Emit(ir.Store{Slot: slot, Value: ir.ValueOperand(value, paramType)})
variadicSlots = append(variadicSlots, slot)
}
for _, slot := range variadicSlots {
loaded := wrapper.NewValueOfType(fn.Symbol.Signature.VariadicElement)
g.Emit(ir.Load{Dest: loaded, Slot: slot})
variadicElements = append(variadicElements, ir.ValueOperand(loaded, fn.Symbol.Signature.VariadicElement))
}
packed := g.generatePackedVariadicSlice(variadicElements, sliceType)
variadicSlot := wrapper.NewSlot(sliceType, fn.Args[fixedCount].Name)
g.currentEnv.Variables[fn.Args[fixedCount].Symbol] = variadicSlot
g.Emit(ir.Alloca{Slot: variadicSlot})
g.Emit(ir.Store{Slot: variadicSlot, Value: packed})
previousSpecialized := g.specializedVariadicElements
previousConstants := g.specializedConstants
g.specializedVariadicElements = map[*symbols.Symbol][]ir.Operand{
fn.Args[fixedCount].Symbol: variadicElements,
}
g.specializedConstants = make(map[*symbols.Symbol]symbols.SpecializationConstant, len(constants))
for index, constant := range constants {
symbol := fn.Args[index].Symbol
g.specializedConstants[symbol] = constant
slot := wrapper.NewSlot(symbol.Type, fmt.Sprintf("constant%d", index))
g.currentEnv.Variables[symbol] = slot
g.Emit(ir.Alloca{Slot: slot})
g.Emit(ir.Store{Slot: slot, Value: g.generateSpecializationConstant(constant)})
}
g.generateFunctionBody(fn)
g.specializedVariadicElements = previousSpecialized
g.specializedConstants = previousConstants
g.currentEnv = nil
g.currentBlock = nil
g.currentFunction = nil
g.deferScopes = nil
}
}
func (g *Generator) generateConstantWrapper(fn *parser.FunctionDefNode, targetName string) {
constants := selectedSpecializationConstants(fn.Symbol.Signature)
if len(constants) == 0 {
return
}
linkage := ir.LinkageInternal
visibility := ir.VisibilityDefault
if fn.Attributes.Get(attributes.AttributeTypeExport) != nil || fn.Symbol.Public || fn.Symbol.Method {
linkage = ir.LinkageExternal
visibility = ir.VisibilityHidden
}
paramTypes := make([]types.Type, 0, len(fn.Symbol.Signature.Parameters)-len(constants))
for i, parameter := range fn.Symbol.Signature.Parameters {
if _, specialized := constants[i]; !specialized {
paramTypes = append(paramTypes, parameter)
}
}
wrapper := ir.NewFunction(g.specializedFunctionName(targetName, constants), linkage, nil)
wrapper.Visibility = visibility
wrapper.Signature = ir.FunctionSignature{ParamTypes: paramTypes, ReturnType: fn.Symbol.Signature.ReturnType}
g.Module.AddFunction(wrapper)
g.currentFunction = wrapper
g.currentBlock = wrapper.NewBlock("entry")
wrapper.Entry = g.currentBlock.ID
g.currentEnv = NewEnv(nil)
g.deferScopes = nil
for i, argument := range fn.Args {
if _, specialized := constants[i]; specialized {
continue
}
slot := wrapper.NewSlot(argument.Symbol.Type, fmt.Sprintf("arg%d", i))
wrapper.AddParameter(argument.Name, argument.Symbol.Type, slot)
g.currentEnv.Variables[argument.Symbol] = slot
g.Emit(ir.Alloca{Slot: slot})
incoming := wrapper.NewValueOfType(argument.Symbol.Type)
g.Emit(ir.Store{Slot: slot, Value: ir.ValueOperand(incoming, argument.Symbol.Type)})
}
previousConstants := g.specializedConstants
g.specializedConstants = make(map[*symbols.Symbol]symbols.SpecializationConstant, len(constants))
for index, constant := range constants {
symbol := fn.Args[index].Symbol
g.specializedConstants[symbol] = constant
slot := wrapper.NewSlot(symbol.Type, fmt.Sprintf("constant%d", index))
g.currentEnv.Variables[symbol] = slot
g.Emit(ir.Alloca{Slot: slot})
g.Emit(ir.Store{Slot: slot, Value: g.generateSpecializationConstant(constant)})
}
g.generateFunctionBody(fn)
g.specializedConstants = previousConstants
g.currentEnv = nil
g.currentBlock = nil
g.currentFunction = nil
g.deferScopes = nil
}
func selectedSpecializationConstants(signature *symbols.FunctionSignature) map[int]symbols.SpecializationConstant {
selected := make(map[int]symbols.SpecializationConstant)
if signature == nil {
return selected
}
type candidate struct {
index int
priority int
constant symbols.SpecializationConstant
}
var candidates []candidate
for i := 0; i < len(signature.Parameters); i++ {
values := signature.ConstantArguments[i]
if len(values) != 1 {
continue
}
for _, constant := range values {
priority := 2
switch constant.Kind {
case "str", "cstr":
priority = 0
case "bool":
priority = 1
case "float":
priority = 3
}
candidates = append(candidates, candidate{index: i, priority: priority, constant: constant})
}
}
slices.SortFunc(candidates, func(left, right candidate) int {
if left.priority != right.priority {
return left.priority - right.priority
}
return left.index - right.index
})
for _, candidate := range candidates[:min(2, len(candidates))] {
selected[candidate.index] = candidate.constant
}
return selected
}
func (g *Generator) specializedFunctionName(targetName string, constants map[int]symbols.SpecializationConstant) string {
if len(constants) == 0 {
return targetName
}
hash := fnv.New64a()
indices := make([]int, 0, len(constants))
for index := range constants {
indices = append(indices, index)
}
slices.Sort(indices)
for _, index := range indices {
fmt.Fprintf(hash, "%d:%s;", index, constants[index].Key())
}
return fmt.Sprintf("%s__const_%016x", targetName, hash.Sum64())
}
func (g *Generator) generatePackedVariadicSlice(elements []ir.Operand, targetType types.Type) ir.Operand {
sliceType := types.Underlying(targetType).(types.SliceType)
tmpSlot := g.currentFunction.NewSlot(targetType, "")
g.Emit(ir.Alloca{Slot: tmpSlot})
slicePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: targetType})
g.Emit(ir.AddressOf{Dest: slicePtrID, Slot: tmpSlot})
slicePtr := ir.ValueOperand(slicePtrID, types.PointerType{Base: targetType})
var elemPtr ir.Operand
if len(elements) == 0 {
elemPtr = ir.NullConstOperand(types.PointerType{Base: sliceType.Base})
} else {
bufferType := types.StructType{Fields: make([]shared.Pair[string, types.Type], len(elements))}
for i := range elements {
bufferType.Fields[i] = shared.Pair[string, types.Type]{L: fmt.Sprintf("%d", i), R: sliceType.Base}
}
bufferSlot := g.currentFunction.NewSlot(bufferType, "")
g.Emit(ir.Alloca{Slot: bufferSlot})
bufferPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: bufferType})
g.Emit(ir.AddressOf{Dest: bufferPtrID, Slot: bufferSlot})
bufferPtr := ir.ValueOperand(bufferPtrID, types.PointerType{Base: bufferType})
for i, element := range elements {
fieldPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: sliceType.Base})
g.Emit(ir.FieldAddress{Dest: fieldPtrID, Base: bufferPtr, Field: fmt.Sprintf("%d", i)})
g.Emit(ir.StorePtr{
Ptr: ir.ValueOperand(fieldPtrID, types.PointerType{Base: sliceType.Base}),
Value: element,
})
}
elemPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: sliceType.Base})
g.Emit(ir.FieldAddress{Dest: elemPtrID, Base: bufferPtr, Field: "0"})
elemPtr = ir.ValueOperand(elemPtrID, types.PointerType{Base: sliceType.Base})
}
basePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: sliceType.Base})
g.Emit(ir.FieldAddress{Dest: basePtrID, Base: slicePtr, Field: "0"})
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(basePtrID, types.PointerType{Base: sliceType.Base}), Value: elemPtr})
lenPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PrimitiveUsz})
g.Emit(ir.FieldAddress{Dest: lenPtrID, Base: slicePtr, Field: "1"})
g.Emit(ir.StorePtr{
Ptr: ir.ValueOperand(lenPtrID, types.PointerType{Base: types.PrimitiveUsz}),
Value: ir.IntConstOperand(strconv.Itoa(len(elements)), types.PrimitiveUsz),
})
loaded := g.currentFunction.NewValueOfType(targetType)
g.Emit(ir.Load{Dest: loaded, Slot: tmpSlot})
return ir.ValueOperand(loaded, targetType)
}
func (g *Generator) defaultWrapperName(targetName string, arity int) string {
return fmt.Sprintf("%s__default_%d", targetName, arity)
}
func (g *Generator) typedVariadicWrapperName(targetName string, arity int) string {
return fmt.Sprintf("%s__variadic_%d", targetName, arity)
}
func hasTypedVariadicSpecialization(signature *symbols.FunctionSignature, arity int) bool {
return signature != nil && signature.TypedVariadicArities[arity]
}
func (g *Generator) emitFunctionParams(fn *parser.FunctionDefNode) {
for i, arg := range fn.Args {
if arg.Symbol == nil {
panic("function arg symbol is nil")
}
slot := g.currentFunction.NewSlot(arg.Symbol.Type, fmt.Sprintf("arg%d", i))
g.currentFunction.AddParameter(arg.Name, arg.Symbol.Type, slot)
g.currentEnv.Variables[arg.Symbol] = slot
g.Emit(ir.Alloca{Slot: slot})
incoming := g.currentFunction.NewValueOfType(arg.Symbol.Type)
g.Emit(ir.Store{Slot: slot, Value: ir.ValueOperand(incoming, arg.Symbol.Type)})
}
}
func (g *Generator) buildFunctionSignature(fn *parser.FunctionDefNode) ir.FunctionSignature {
sig := ir.FunctionSignature{}
if fn != nil && fn.Symbol != nil && fn.Symbol.Signature != nil {
sig.ParamTypes = append(sig.ParamTypes, fn.Symbol.Signature.Parameters...)
sig.ReturnType = fn.Symbol.Signature.ReturnType
sig.Variadic = fn.Symbol.Signature.Variadic
sig.Attributes = fn.Symbol.Attributes
return sig
}
for _, arg := range fn.Args {
if arg.Symbol != nil {
sig.ParamTypes = append(sig.ParamTypes, arg.Symbol.Type)
}
}
if fn == nil || fn.Symbol == nil || fn.Symbol.Signature == nil || fn.Symbol.Signature.ReturnType == nil {
sig.ReturnType = types.PrimitiveVoid
}
return sig
}
func (g *Generator) isVoidFunction(fn *parser.FunctionDefNode) bool {
if fn == nil || fn.Symbol == nil || fn.Symbol.Signature == nil || fn.Symbol.Signature.ReturnType == nil {
return true
}
return fn.Symbol.Signature.ReturnType.Equals(types.PrimitiveVoid)
}
func (g *Generator) currentBlockHasTerminator() bool {
if g.currentBlock == nil || len(g.currentBlock.Instr) == 0 {
return false
}
last := g.currentBlock.Instr[len(g.currentBlock.Instr)-1]
switch last.(type) {
case ir.Return, ir.Jump, ir.Branch, ir.Unreachable:
return true
default:
return false
}
}
func (g *Generator) GenerateNode(node parser.Node) {
switch n := node.(type) {
case *parser.BlockNode:
g.generateBlock(n)
case *parser.DeclarationNode:
g.generateDeclaration(n)
case *parser.MultiDeclarationNode:
g.generateMultiDeclaration(n)
case *parser.AssignmentNode:
g.generateAssignment(n)
case *parser.ControlKeywordNode:
g.generateControlKeyword(n)
case *parser.DeferNode:
g.registerDefer(n)
case *parser.IfNode:
g.generateIf(n)
case *parser.MatchNode:
g.generateMatch(n)
case *parser.ForNode:
g.generateFor(n)
case *parser.RangeForNode:
g.generateRangeFor(n)
case *parser.ForEachNode:
g.generateForEach(n)
case *parser.FunctionCallNode:
g.generateSourceFunctionCall(n)
case parser.ExpressionNode:
g.GenerateExpr(n)
default:
panic(fmt.Sprintf("todo: generate node %T", n))
}
}
func (g *Generator) generateBlock(node *parser.BlockNode) {
prev := g.currentEnv
g.currentEnv = NewEnv(prev)
g.deferScopes = append(g.deferScopes, nil)
defer func() {
g.deferScopes = g.deferScopes[:len(g.deferScopes)-1]
g.currentEnv = prev
}()
for _, child := range node.Body {
if g.currentBlockHasTerminator() {
break
}
g.GenerateNode(child)
}
if !g.currentBlockHasTerminator() {
g.emitCurrentScopeDefers()
}
}
func (g *Generator) registerDefer(node *parser.DeferNode) {
if len(g.deferScopes) == 0 {
panic("defer outside a lexical scope")
}
last := len(g.deferScopes) - 1
g.deferScopes[last] = append(g.deferScopes[last], node.Action)
}
func (g *Generator) emitDeferredAction(action parser.Node) {
if expr, ok := action.(parser.ExpressionNode); ok {
g.GenerateExpr(expr)
return
}
g.GenerateNode(action)
}
func (g *Generator) emitCurrentScopeDefers() {
if len(g.deferScopes) == 0 {
return
}
g.emitScopeDefers(len(g.deferScopes) - 1)
}
func (g *Generator) emitDefersUntil(depth int) {
for i := len(g.deferScopes) - 1; i >= depth; i-- {
g.emitScopeDefers(i)
if g.currentBlockHasTerminator() {
return
}
}
}
func (g *Generator) emitScopeDefers(scope int) {
actions := g.deferScopes[scope]
defer func() { g.deferScopes[scope] = actions }()
for i, action := range slices.Backward(actions) {
// If this action transfers control, only the earlier actions remain pending.
g.deferScopes[scope] = actions[:i]
g.emitDeferredAction(action)
if g.currentBlockHasTerminator() {
return
}
}
}
func (g *Generator) generateDeclaration(node *parser.DeclarationNode) {
if node.Name == "_" {
g.GenerateExpr(node.Value)
return
}
if node.Symbol == nil {
panic("declaration symbol is nil")
}
if node.Symbol.InlineComptime {
return
}
slot := g.currentFunction.NewSlot(node.Symbol.Type, node.Name)
g.currentEnv.Variables[node.Symbol] = slot
g.Emit(ir.Alloca{Slot: slot})
if _, noInit := node.Value.(*parser.NoInitializerNode); noInit {
return
}
if lit, ok := node.Value.(*parser.StructLiteralNode); ok {
g.generateStructLiteralIntoSlot(slot, lit)
return
}
value := g.GenerateExpr(node.Value)
g.Emit(ir.Store{Slot: slot, Value: value})
}
func (g *Generator) generateMultiDeclaration(node *parser.MultiDeclarationNode) {
bundle := g.GenerateExpr(node.Value)
result := bundle.Type.(types.MultipleReturnType)
for i, sym := range node.Symbols {
if sym == nil {
continue
}
valueID := g.currentFunction.NewValueOfType(result.Types[i])
g.Emit(ir.ExtractValue{Dest: valueID, Aggregate: bundle, Index: i})
slot := g.currentFunction.NewSlot(sym.Type, node.Names[i])
g.currentEnv.Variables[sym] = slot
g.Emit(ir.Alloca{Slot: slot})
g.Emit(ir.Store{Slot: slot, Value: ir.ValueOperand(valueID, result.Types[i])})
}
}
func (g *Generator) generateAssignment(node *parser.AssignmentNode) {
if node.Compound {
g.generateCompoundAssignment(node)
return
}
if len(node.Assignees) > 0 {
bundle := g.GenerateExpr(node.Value)
result := bundle.Type.(types.MultipleReturnType)
for i, target := range node.Assignees {
if id, ok := target.(*parser.IdentifierNode); ok && id.Name == "_" {
continue
}
valueID := g.currentFunction.NewValueOfType(result.Types[i])
g.Emit(ir.ExtractValue{Dest: valueID, Aggregate: bundle, Index: i})
value := ir.ValueOperand(valueID, result.Types[i])
if !result.Types[i].Equals(target.GetType()) {
castID := g.currentFunction.NewValueOfType(target.GetType())
g.Emit(ir.Cast{Dest: castID, From: value, To: target.GetType()})
value = ir.ValueOperand(castID, target.GetType())
}
address := g.generateAddressOfExpr(target)
g.Emit(ir.StorePtr{Ptr: address, Value: value})
}
return
}
if id, ok := node.Assignee.(*parser.IdentifierNode); ok && id.Name == "_" {
g.GenerateExpr(node.Value)
return
}
if field, ok := node.Assignee.(*parser.FieldAccessNode); ok && field.IsFlagTest {
g.generateFlagMemberAssignment(field, node.Value)
return
}
switch n := node.Assignee.(type) {
case *parser.IdentifierNode:
slot, ok := g.currentEnv.Lookup(n.GetSymbol())
if !ok {
global := g.globalNameForIdentifier(n)
rhs := g.GenerateExpr(node.Value)
g.Emit(ir.StoreGlobal{Name: global, Value: rhs})
return
}
if lit, ok := node.Value.(*parser.StructLiteralNode); ok {
g.generateStructLiteralIntoSlot(slot, lit)
return
}
rhs := g.GenerateExpr(node.Value)
g.Emit(ir.Store{Slot: slot, Value: rhs})
case *parser.UnaryOpNode:
switch n.Op {
case parser.UnaryOpDereference:
ptr := g.GenerateExpr(n.Operand)
value := g.GenerateExpr(node.Value)
g.Emit(ir.StorePtr{
Ptr: ptr,
Value: value,
})
default:
panic(fmt.Sprintf("todo: assignment unary op %T", n))
}
case *parser.FieldAccessNode:
base := g.generateFieldSubjectAddress(n.Subject)
fieldPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: n.GetType()})
g.Emit(ir.FieldAddress{
Dest: fieldPtrID,
Base: base,
Field: n.Field.Name,
})
value := g.GenerateExpr(node.Value)
g.Emit(ir.StorePtr{
Ptr: ir.ValueOperand(fieldPtrID, types.PointerType{Base: n.GetType()}),
Value: value,
})
case *parser.IndexExprNode:
elementPtr := g.generateIndexAddress(n)
value := g.GenerateExpr(node.Value)
g.Emit(ir.StorePtr{
Ptr: elementPtr,
Value: value,
})
default:
panic(fmt.Sprintf("todo: assignment assignee %T", n))
}
}
func (g *Generator) generateFlagMemberAssignment(field *parser.FieldAccessNode, rhs parser.ExpressionNode) {
address := g.generateAddressOfExpr(field.Subject)
currentID := g.currentFunction.NewValueOfType(field.FlagType)
g.Emit(ir.LoadPtr{Dest: currentID, Ptr: address})
current := ir.ValueOperand(currentID, field.FlagType)
mask := ir.IntConstOperand(field.FlagValue, field.FlagType)
condition := g.GenerateExpr(rhs)
setBlock := g.currentFunction.NewBlock("flags.set")
clearBlock := g.currentFunction.NewBlock("flags.clear")
mergeBlock := g.currentFunction.NewBlock("flags.assign.merge")
g.Emit(ir.Branch{Cond: condition, Then: setBlock.ID, Else: clearBlock.ID})
g.currentBlock = setBlock
set := g.emitBinaryOperation(parser.BinaryOpBitwiseOr, current, mask, field.FlagType)
g.Emit(ir.StorePtr{Ptr: address, Value: set})
g.Emit(ir.Jump{Target: mergeBlock.ID})
g.currentBlock = clearBlock
notID := g.currentFunction.NewValueOfType(field.FlagType)
g.Emit(ir.BitwiseNot{Dest: notID, Operand: mask})
clear := g.emitBinaryOperation(parser.BinaryOpBitwiseAnd, current, ir.ValueOperand(notID, field.FlagType), field.FlagType)
g.Emit(ir.StorePtr{Ptr: address, Value: clear})
g.Emit(ir.Jump{Target: mergeBlock.ID})
g.currentBlock = mergeBlock
}
func (g *Generator) generateCompoundAssignment(node *parser.AssignmentNode) {
binary, ok := node.Value.(*parser.BinaryOpNode)
if !ok {
panic("compound assignment value is not a binary operation")
}
address := g.generateAddressOfExpr(node.Assignee)
currentID := g.currentFunction.NewValueOfType(node.Assignee.GetType())
g.Emit(ir.LoadPtr{Dest: currentID, Ptr: address})
left := ir.ValueOperand(currentID, node.Assignee.GetType())
right := g.GenerateExpr(binary.Operand2)
result := g.emitBinaryOperation(binary.Op, left, right, node.Assignee.GetType())
g.Emit(ir.StorePtr{Ptr: address, Value: result})
}
func (g *Generator) generateControlKeyword(node *parser.ControlKeywordNode) {
switch node.Keyword {
case tokeniser.KeywordReturn:
var value ir.Operand
if len(node.ReturnValues) > 1 {
multi := g.currentFunction.Signature.ReturnType.(types.MultipleReturnType)
value = ir.ZeroConstOperand(multi)
for i, expr := range node.ReturnValues {
item := g.GenerateExpr(expr)
dest := g.currentFunction.NewValueOfType(multi)
g.Emit(ir.InsertValue{Dest: dest, Aggregate: value, Value: item, Index: i})
value = ir.ValueOperand(dest, multi)
}
} else if node.ReturnValue != nil {
value = g.GenerateExpr(node.ReturnValue)
}
g.emitDefersUntil(0)
if g.currentBlockHasTerminator() {
return
}
if node.ReturnValue == nil {
g.Emit(ir.Return{})
return
}
g.Emit(ir.Return{HasValue: true, Value: value})
case tokeniser.KeywordBreak:
targets := g.currentLoopTargets()
g.emitDefersUntil(targets.cleanupDepth)
if g.currentBlockHasTerminator() {
return
}
g.Emit(ir.Jump{Target: targets.breakTarget})
case tokeniser.KeywordContinue:
targets := g.currentLoopTargets()
g.emitDefersUntil(targets.cleanupDepth)
if g.currentBlockHasTerminator() {
return
}
g.Emit(ir.Jump{Target: targets.continueTarget})
default:
panic(fmt.Sprintf("unsupported control keyword %q", node.Keyword))
}
}
func (g *Generator) currentLoopTargets() loopTargets {
if len(g.loopTargets) == 0 {
panic("loop control keyword outside a loop")
}
return g.loopTargets[len(g.loopTargets)-1]
}
func (g *Generator) pushLoopTargets(breakTarget, continueTarget ir.BlockID) func() {
g.loopTargets = append(g.loopTargets, loopTargets{
breakTarget: breakTarget, continueTarget: continueTarget, cleanupDepth: len(g.deferScopes),
})
return func() { g.loopTargets = g.loopTargets[:len(g.loopTargets)-1] }
}
func (g *Generator) generateFor(node *parser.ForNode) {
prevEnv := g.currentEnv
g.currentEnv = NewEnv(prevEnv)
defer func() {
g.currentEnv = prevEnv
}()
var condition parser.ExpressionNode
var post parser.Node
switch len(node.ExprsOrStmts) {
case 0:
// infinite loop
case 1:
var ok bool
condition, ok = node.ExprsOrStmts[0].(parser.ExpressionNode)
if !ok {
panic("for loop condition must be an expression")
}
case 3:
g.GenerateNode(node.ExprsOrStmts[0])
var ok bool
condition, ok = node.ExprsOrStmts[1].(parser.ExpressionNode)
if !ok {
panic("for loop condition must be an expression")
}
post = node.ExprsOrStmts[2]
default:
panic("for loop must have a condition or initializer, condition, and post expression")
}
var conditionBlock *ir.Block
if condition != nil {
conditionBlock = g.currentFunction.NewBlock("for.condition")
}
bodyBlock := g.currentFunction.NewBlock("for.body")
endBlock := g.currentFunction.NewBlock("for.end")
var postBlock *ir.Block
if post != nil {
postBlock = g.currentFunction.NewBlock("for.post")
}
if !g.currentBlockHasTerminator() {
if conditionBlock != nil {
g.Emit(ir.Jump{Target: conditionBlock.ID})
} else {
g.Emit(ir.Jump{Target: bodyBlock.ID})
}
}
if conditionBlock != nil {
g.currentBlock = conditionBlock
conditionOperand := g.GenerateExpr(condition)
conditionValue := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpNe{
Dest: conditionValue,
Left: conditionOperand,
Right: ir.BoolConstOperand(false),
})
g.Emit(ir.Branch{
Cond: ir.ValueOperand(conditionValue, types.PrimitiveBool),
Then: bodyBlock.ID,
Else: endBlock.ID,
})
}
g.currentBlock = bodyBlock
continueTarget := bodyBlock.ID
if postBlock != nil {
continueTarget = postBlock.ID
} else if conditionBlock != nil {
continueTarget = conditionBlock.ID
}
popLoop := g.pushLoopTargets(endBlock.ID, continueTarget)
g.generateBlock(node.Body)
popLoop()
if !g.currentBlockHasTerminator() {
if postBlock != nil {
g.Emit(ir.Jump{Target: postBlock.ID})
} else if conditionBlock != nil {
g.Emit(ir.Jump{Target: conditionBlock.ID})
} else {
g.Emit(ir.Jump{Target: bodyBlock.ID})
}
}
if postBlock != nil {
g.currentBlock = postBlock
g.GenerateNode(post)
if !g.currentBlockHasTerminator() {
if conditionBlock != nil {
g.Emit(ir.Jump{Target: conditionBlock.ID})
} else {
g.Emit(ir.Jump{Target: bodyBlock.ID})
}
}
}
g.currentBlock = endBlock
}
func (g *Generator) generateRangeFor(node *parser.RangeForNode) {
if node.Symbol == nil {
panic("range loop symbol is nil")
}
prevEnv := g.currentEnv
g.currentEnv = NewEnv(prevEnv)
defer func() { g.currentEnv = prevEnv }()
iteratorSlot := g.currentFunction.NewSlot(node.Symbol.Type, node.Name)
g.currentEnv.Variables[node.Symbol] = iteratorSlot
g.Emit(ir.Alloca{Slot: iteratorSlot})
boundSlot := g.currentFunction.NewSlot(node.Symbol.Type, "for.range.bound")
g.Emit(ir.Alloca{Slot: boundSlot})
start := g.GenerateExpr(node.Start)
if node.Reversed {
g.Emit(ir.Store{Slot: boundSlot, Value: start})
g.Emit(ir.Store{Slot: iteratorSlot, Value: g.GenerateExpr(node.End)})
} else {
g.Emit(ir.Store{Slot: iteratorSlot, Value: start})
g.Emit(ir.Store{Slot: boundSlot, Value: g.GenerateExpr(node.End)})
}
conditionBlock := g.currentFunction.NewBlock("for.range.condition")
bodyBlock := g.currentFunction.NewBlock("for.range.body")
postBlock := g.currentFunction.NewBlock("for.range.post")
endBlock := g.currentFunction.NewBlock("for.range.end")
g.Emit(ir.Jump{Target: conditionBlock.ID})
g.currentBlock = conditionBlock
iterator := g.loadSlot(iteratorSlot, node.Symbol.Type)
bound := g.loadSlot(boundSlot, node.Symbol.Type)
condition := g.currentFunction.NewValueOfType(types.PrimitiveBool)
if node.Reversed && node.Inclusive {
g.Emit(ir.CmpGe{Dest: condition, Left: iterator, Right: bound})
} else if node.Reversed {
g.Emit(ir.CmpGt{Dest: condition, Left: iterator, Right: bound})
} else if node.Inclusive {
g.Emit(ir.CmpLe{Dest: condition, Left: iterator, Right: bound})
} else {
g.Emit(ir.CmpLt{Dest: condition, Left: iterator, Right: bound})
}
g.Emit(ir.Branch{Cond: ir.ValueOperand(condition, types.PrimitiveBool), Then: bodyBlock.ID, Else: endBlock.ID})
g.currentBlock = bodyBlock
if node.Reversed && !node.Inclusive {
current := g.loadSlot(iteratorSlot, node.Symbol.Type)
previous := g.currentFunction.NewValueOfType(node.Symbol.Type)
g.Emit(ir.Sub{Dest: previous, Left: current, Right: ir.IntConstOperand("1", node.Symbol.Type)})
g.Emit(ir.Store{Slot: iteratorSlot, Value: ir.ValueOperand(previous, node.Symbol.Type)})
}
popLoop := g.pushLoopTargets(endBlock.ID, postBlock.ID)
g.generateBlock(node.Body)
popLoop()
if !g.currentBlockHasTerminator() {
g.Emit(ir.Jump{Target: postBlock.ID})
}
g.currentBlock = postBlock
if node.Reversed && !node.Inclusive {
g.Emit(ir.Jump{Target: conditionBlock.ID})
g.currentBlock = endBlock
return
}
current := g.loadSlot(iteratorSlot, node.Symbol.Type)
if node.Reversed {
atStart := g.currentFunction.NewValueOfType(types.PrimitiveBool)
start := g.loadSlot(boundSlot, node.Symbol.Type)
g.Emit(ir.CmpEq{Dest: atStart, Left: current, Right: start})
decrementBlock := g.currentFunction.NewBlock("for.range.decrement")
g.Emit(ir.Branch{Cond: ir.ValueOperand(atStart, types.PrimitiveBool), Then: endBlock.ID, Else: decrementBlock.ID})
g.currentBlock = decrementBlock
previous := g.currentFunction.NewValueOfType(node.Symbol.Type)
g.Emit(ir.Sub{Dest: previous, Left: current, Right: ir.IntConstOperand("1", node.Symbol.Type)})
g.Emit(ir.Store{Slot: iteratorSlot, Value: ir.ValueOperand(previous, node.Symbol.Type)})
} else {
next := g.currentFunction.NewValueOfType(node.Symbol.Type)
g.Emit(ir.Add{Dest: next, Left: current, Right: ir.IntConstOperand("1", node.Symbol.Type)})
g.Emit(ir.Store{Slot: iteratorSlot, Value: ir.ValueOperand(next, node.Symbol.Type)})
}
g.Emit(ir.Jump{Target: conditionBlock.ID})
g.currentBlock = endBlock
}
func (g *Generator) generateForEach(node *parser.ForEachNode) {
iterableType := types.Underlying(node.Iterable.GetType())
switch iterableType.(type) {
case types.SliceType, types.ArrayType:
default:
panic("for-each iterable is not an array or slice")
}
prevEnv := g.currentEnv
g.currentEnv = NewEnv(prevEnv)
defer func() { g.currentEnv = prevEnv }()
iterableSlot := g.currentFunction.NewSlot(node.Iterable.GetType(), "for.each.iterable")
g.Emit(ir.Alloca{Slot: iterableSlot})
g.Emit(ir.Store{Slot: iterableSlot, Value: g.GenerateExpr(node.Iterable)})
indexSlot := g.currentFunction.NewSlot(types.PrimitiveUsz, "for.each.index")
g.Emit(ir.Alloca{Slot: indexSlot})
if node.Reversed {
var length ir.Operand
switch t := iterableType.(type) {
case types.SliceType:
length = g.sliceLength(iterableSlot, t)
case types.ArrayType:
length = ir.IntConstOperand(strconv.Itoa(t.Length), types.PrimitiveUsz)
}
g.Emit(ir.Store{Slot: indexSlot, Value: length})
} else {
g.Emit(ir.Store{Slot: indexSlot, Value: ir.IntConstOperand("0", types.PrimitiveUsz)})
}
var elementSlot ir.SlotID
destructureSlots := make([]ir.SlotID, len(node.Destructure))
if len(node.Destructure) != 0 {
for i := range node.Destructure {
binding := &node.Destructure[i]
if binding.Symbol == nil {
continue
}
slot := g.currentFunction.NewSlot(binding.Symbol.Type, binding.Name)
destructureSlots[i] = slot
g.currentEnv.Variables[binding.Symbol] = slot
g.Emit(ir.Alloca{Slot: slot})
}
} else if node.Symbol != nil {
elementSlot = g.currentFunction.NewSlot(node.Symbol.Type, node.Name)
g.currentEnv.Variables[node.Symbol] = elementSlot
g.Emit(ir.Alloca{Slot: elementSlot})
}
var visibleIndexSlot ir.SlotID
if node.IndexSymbol != nil {
visibleIndexSlot = g.currentFunction.NewSlot(types.PrimitiveUsz, node.IndexName)
g.currentEnv.Variables[node.IndexSymbol] = visibleIndexSlot
g.Emit(ir.Alloca{Slot: visibleIndexSlot})
}
conditionBlock := g.currentFunction.NewBlock("for.each.condition")
bodyBlock := g.currentFunction.NewBlock("for.each.body")
postBlock := g.currentFunction.NewBlock("for.each.post")
endBlock := g.currentFunction.NewBlock("for.each.end")
g.Emit(ir.Jump{Target: conditionBlock.ID})
g.currentBlock = conditionBlock
index := g.loadSlot(indexSlot, types.PrimitiveUsz)
condition := g.currentFunction.NewValueOfType(types.PrimitiveBool)
if node.Reversed {
g.Emit(ir.CmpGt{Dest: condition, Left: index, Right: ir.IntConstOperand("0", types.PrimitiveUsz)})
} else {
var length ir.Operand
switch t := iterableType.(type) {
case types.SliceType:
length = g.sliceLength(iterableSlot, t)
case types.ArrayType:
length = ir.IntConstOperand(strconv.Itoa(t.Length), types.PrimitiveUsz)
}
g.Emit(ir.CmpLt{Dest: condition, Left: index, Right: length})
}
g.Emit(ir.Branch{Cond: ir.ValueOperand(condition, types.PrimitiveBool), Then: bodyBlock.ID, Else: endBlock.ID})
g.currentBlock = bodyBlock
elementIndex := index
if node.Reversed {
previousIndex := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.Sub{Dest: previousIndex, Left: index, Right: ir.IntConstOperand("1", types.PrimitiveUsz)})
elementIndex = ir.ValueOperand(previousIndex, types.PrimitiveUsz)
}
if len(node.Destructure) != 0 {
elementPointer := g.forEachElementPointer(iterableSlot, iterableType, elementIndex, false)
g.storeForEachDestructure(elementPointer, node.Destructure, destructureSlots)
} else if node.Symbol != nil {
if node.ElementKind == parser.ForEachElementValue {
g.storeForEachElement(iterableSlot, iterableType, elementIndex, elementSlot)
} else {
elementPointer := g.forEachElementPointer(
iterableSlot,
iterableType,
elementIndex,
node.ElementKind == parser.ForEachElementMutablePointer,
)
g.Emit(ir.Store{Slot: elementSlot, Value: elementPointer})
}
}
if node.IndexSymbol != nil {
g.Emit(ir.Store{Slot: visibleIndexSlot, Value: elementIndex})
}
popLoop := g.pushLoopTargets(endBlock.ID, postBlock.ID)
g.generateBlock(node.Body)
popLoop()
if !g.currentBlockHasTerminator() {
g.Emit(ir.Jump{Target: postBlock.ID})
}
g.currentBlock = postBlock
currentIndex := g.loadSlot(indexSlot, types.PrimitiveUsz)
nextIndex := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
if node.Reversed {
g.Emit(ir.Sub{Dest: nextIndex, Left: currentIndex, Right: ir.IntConstOperand("1", types.PrimitiveUsz)})
} else {
g.Emit(ir.Add{Dest: nextIndex, Left: currentIndex, Right: ir.IntConstOperand("1", types.PrimitiveUsz)})
}
g.Emit(ir.Store{Slot: indexSlot, Value: ir.ValueOperand(nextIndex, types.PrimitiveUsz)})
g.Emit(ir.Jump{Target: conditionBlock.ID})
g.currentBlock = endBlock
}
func (g *Generator) storeForEachDestructure(
elementPointer ir.Operand,
bindings []parser.ForEachBinding,
slots []ir.SlotID,
) {
elementType := types.Underlying(types.Underlying(elementPointer.Type).(types.PointerType).Base)
switch element := elementType.(type) {
case types.ArrayType:
for i := range bindings {
if bindings[i].Symbol == nil {
continue
}
pointerType := types.PointerType{Base: element.Base}
componentPointer := g.currentFunction.NewValueOfType(pointerType)
g.Emit(ir.ElementAddress{
Dest: componentPointer,
Base: elementPointer,
Index: ir.IntConstOperand(strconv.Itoa(i), types.PrimitiveUsz),
Element: element.Base,
ArrayObject: true,
})
value := g.currentFunction.NewValueOfType(element.Base)
g.Emit(ir.LoadPtr{Dest: value, Ptr: ir.ValueOperand(componentPointer, pointerType)})
g.Emit(ir.Store{Slot: slots[i], Value: ir.ValueOperand(value, element.Base)})
}
case types.StructType:
for i, field := range element.Fields {
if bindings[i].Symbol == nil {
continue
}
pointerType := types.PointerType{Base: field.R}
componentPointer := g.currentFunction.NewValueOfType(pointerType)
g.Emit(ir.FieldAddress{Dest: componentPointer, Base: elementPointer, Field: field.L})
value := g.currentFunction.NewValueOfType(field.R)
g.Emit(ir.LoadPtr{Dest: value, Ptr: ir.ValueOperand(componentPointer, pointerType)})
g.Emit(ir.Store{Slot: slots[i], Value: ir.ValueOperand(value, field.R)})
}
default:
panic("validated for-each destructuring element is not an array or struct")
}
}
func (g *Generator) loadSlot(slot ir.SlotID, ty types.Type) ir.Operand {
dst := g.currentFunction.NewValueOfType(ty)
g.Emit(ir.Load{Dest: dst, Slot: slot})
return ir.ValueOperand(dst, ty)
}
func (g *Generator) sliceLength(slot ir.SlotID, sliceType types.SliceType) ir.Operand {
slicePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: sliceType})
g.Emit(ir.AddressOf{Dest: slicePtrID, Slot: slot})
slicePtr := ir.ValueOperand(slicePtrID, types.PointerType{Base: sliceType})
lengthPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PrimitiveUsz})
g.Emit(ir.FieldAddress{Dest: lengthPtrID, Base: slicePtr, Field: "1"})
lengthID := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.LoadPtr{Dest: lengthID, Ptr: ir.ValueOperand(lengthPtrID, types.PointerType{Base: types.PrimitiveUsz})})
return ir.ValueOperand(lengthID, types.PrimitiveUsz)
}
func (g *Generator) storeForEachElement(iterableSlot ir.SlotID, iterableType types.Type, index ir.Operand, elementSlot ir.SlotID) {
elementPointer := g.forEachElementPointer(iterableSlot, iterableType, index, false)
pointerType := types.Underlying(elementPointer.Type).(types.PointerType)
elementID := g.currentFunction.NewValueOfType(pointerType.Base)
g.Emit(ir.LoadPtr{Dest: elementID, Ptr: elementPointer})
g.Emit(ir.Store{Slot: elementSlot, Value: ir.ValueOperand(elementID, pointerType.Base)})
}
func iterableTypeIsArray(iterableType types.Type) bool {
_, ok := iterableType.(types.ArrayType)
return ok
}
func (g *Generator) forEachElementPointer(
iterableSlot ir.SlotID,
iterableType types.Type,
index ir.Operand,
mutable bool,
) ir.Operand {
var base ir.Operand
var elementType types.Type
switch t := iterableType.(type) {
case types.SliceType:
elementType = t.Base
slicePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: t})
g.Emit(ir.AddressOf{Dest: slicePtrID, Slot: iterableSlot})
slicePtr := ir.ValueOperand(slicePtrID, types.PointerType{Base: t})
basePtrPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PointerType{Base: t.Base}})
g.Emit(ir.FieldAddress{Dest: basePtrPtrID, Base: slicePtr, Field: "0"})
basePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: t.Base})
g.Emit(ir.LoadPtr{Dest: basePtrID, Ptr: ir.ValueOperand(basePtrPtrID, types.PointerType{Base: types.PointerType{Base: t.Base}})})
base = ir.ValueOperand(basePtrID, types.PointerType{Base: t.Base})
case types.ArrayType:
elementType = t.Base
arrayPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: t})
g.Emit(ir.AddressOf{Dest: arrayPtrID, Slot: iterableSlot})
base = ir.ValueOperand(arrayPtrID, types.PointerType{Base: t})
}
elementPointerType := types.PointerType{Base: elementType, Mutable: mutable}
elementPtrID := g.currentFunction.NewValueOfType(elementPointerType)
g.Emit(ir.ElementAddress{
Dest: elementPtrID,
Base: base,
Index: index,
Element: elementType,
ArrayObject: iterableTypeIsArray(iterableType),
})
return ir.ValueOperand(elementPtrID, elementPointerType)
}
func (g *Generator) GenerateExpr(expr parser.ExpressionNode) ir.Operand {
switch n := expr.(type) {
case *parser.LambdaNode:
if n.Function == nil || n.Function.Symbol == nil {
panic("unresolved lambda reached IR generation")
}
if !g.queuedLambdas[n.Function.Symbol] {
g.queuedLambdas[n.Function.Symbol] = true
g.lambdaFunctions = append(g.lambdaFunctions, n.Function)
}
name := g.mangleFunctionName(g.ModuleName, n.Function.Symbol.Name)
return ir.FunctionConstOperand(name, n.GetType())
case *parser.IntegerLiteralNode:
if types.IsFloat(n.GetType()) {
return ir.FloatConstOperand(n.Value, n.GetType())
}
return ir.IntConstOperand(n.Value, n.GetType())
case *parser.FloatLiteralNode:
return ir.FloatConstOperand(n.Value, n.GetType())
case *parser.BoolLiteralNode:
return ir.BoolConstOperand(n.Value == string(tokeniser.KeywordTrue))
case *parser.EnumLiteralNode:
return ir.IntConstOperand(n.Value, n.GetType())
case *parser.IdentifierNode:
return g.generateIdentifierExpr(n)
case *parser.BlockNode:
return g.generateBlockExpr(n)
case *parser.IfNode:
return g.generateIfExpr(n)
case *parser.MatchNode:
return g.generateMatch(n)
case *parser.BinaryOpNode:
return g.generateBinaryExpr(n)
case *parser.UnaryOpNode:
return g.generateUnaryExpr(n)
case *parser.StructLiteralNode:
return g.generateStructLiteralExpr(n)
case *parser.SliceLiteralNode:
return g.generateSliceLiteralExpr(n)
case *parser.IndexExprNode:
return g.generateIndexExpr(n)
case *parser.SliceExprNode:
return g.generateSliceExpr(n)
case *parser.FunctionCallNode:
if n.CompileTimeApplication != nil && n.CompileTimeApplication.Symbol.Kind == symbols.SymbolKindVariable {
return g.generateIdentifierExpr(n.CompileTimeApplication)
}
if n.TaggedUnionType != nil {
return g.generateTaggedUnionConstructor(n)
}
return g.generateSourceFunctionCall(n)
case *parser.InlineAsmNode:
return g.generateInlineAsmExpr(n)
case *parser.FieldAccessNode:
if n.TaggedUnionType != nil {
return g.generateTaggedUnionConstructor(&parser.FunctionCallNode{
Loc: n.Loc, TaggedUnionType: n.TaggedUnionType, TaggedUnionVariant: n.TaggedUnionVariant,
})
}
return g.generateFieldAccessExpr(n)
case *parser.CastNode:
return g.generateCastExpr(n)
case *parser.ReprNode:
value := g.GenerateExpr(n.Operand)
value.Type = n.GetType()
return value
case *parser.SizeOfNode:
return g.generateSizeOfExpr(n)
case *parser.SizeOfExprNode:
return g.generateSizeOfExprExpr(n)
case *parser.AlignOfNode:
return g.generateAlignOfExpr(n)
case *parser.OffsetOfNode:
return g.generateOffsetOfExpr(n)
case *parser.StringLiteralNode:
return g.generateStringLiteralExpr(n)
case *parser.EmbedNode:
return g.generateStringLiteralExpr(&parser.StringLiteralNode{Value: n.Contents, Type: n.GetType()})
case *parser.CStringLiteralNode:
return g.generateCStringLiteralExpr(n)
case *parser.CharLiteralNode:
return ir.IntConstOperand(fmt.Sprint(int(n.Value)), n.GetType())
case *parser.NilLiteralNode:
return g.generateNilLiteralExpr(n)
default:
fmt.Printf("todo: generate expr %T\n", n)
panic("todo")
}
}
func (g *Generator) generateInlineAsmExpr(node *parser.InlineAsmNode) ir.Operand {
args := make([]ir.Operand, len(node.Inputs))
constraints := make([]string, 0, len(node.Outputs)+len(node.Inputs))
for _, output := range node.Outputs {
constraints = append(constraints, output.Constraint)
}
for i, input := range node.Inputs {
args[i] = g.GenerateExpr(input.Value)
constraints = append(constraints, input.Constraint)
}
instr := ir.InlineAsm{
Template: node.Template,
Constraints: constraints,
Clobbers: append([]string(nil), node.Clobbers...),
Args: args,
ResultType: node.GetType(),
SideEffect: node.Volatile || len(node.Outputs) == 0 || len(node.Clobbers) != 0,
}
if node.GetType().Equals(types.PrimitiveVoid) {
g.Emit(instr)
return ir.NullConstOperand(types.PrimitiveVoid)
}
instr.Dest = g.currentFunction.NewValueOfType(node.GetType())
g.Emit(instr)
return ir.ValueOperand(instr.Dest, node.GetType())
}
func (g *Generator) generateBlockExpr(node *parser.BlockNode) ir.Operand {
prev := g.currentEnv
g.currentEnv = NewEnv(prev)
g.deferScopes = append(g.deferScopes, nil)
defer func() {
g.deferScopes = g.deferScopes[:len(g.deferScopes)-1]
g.currentEnv = prev
}()
resultExpr, hasResult := parser.BlockResult(node)
childCount := len(node.Body)
if hasResult {
childCount--
}
for _, child := range node.Body[:childCount] {
if g.currentBlockHasTerminator() {
break
}
g.GenerateNode(child)
}
if g.currentBlockHasTerminator() {
return ir.ZeroConstOperand(node.GetType())
}
if !hasResult {
panic("falling-through block expression has no result")
}
result := g.GenerateExpr(resultExpr)
if !g.currentBlockHasTerminator() {
g.emitCurrentScopeDefers()
}
return result
}
func (g *Generator) generateIndexExpr(node *parser.IndexExprNode) ir.Operand {
elementPtr := g.generateIndexAddress(node)
dst := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.LoadPtr{Dest: dst, Ptr: elementPtr})
return ir.ValueOperand(dst, node.GetType())
}
func (g *Generator) generateIndexAddress(node *parser.IndexExprNode) ir.Operand {
index := g.GenerateExpr(node.Index)
var base ir.Operand
arrayObject := false
switch subjectType := types.Underlying(node.Subject.GetType()).(type) {
case types.SliceType:
slicePtr := g.generateAddressOfExpr(node.Subject)
dataPtrPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PointerType{Base: subjectType.Base}})
g.Emit(ir.FieldAddress{
Dest: dataPtrPtrID,
Base: slicePtr,
Field: "0",
})
dataPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: subjectType.Base})
g.Emit(ir.LoadPtr{
Dest: dataPtrID,
Ptr: ir.ValueOperand(dataPtrPtrID, types.PointerType{Base: types.PointerType{Base: subjectType.Base}}),
})
base = ir.ValueOperand(dataPtrID, types.PointerType{Base: subjectType.Base})
case types.ArrayType:
base = g.generateAddressOfExpr(node.Subject)
arrayObject = true
case types.PointerType:
base = g.GenerateExpr(node.Subject)
default:
panic(fmt.Sprintf("cannot generate index expression for %T", subjectType))
}
elementPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: node.GetType()})
g.Emit(ir.ElementAddress{
Dest: elementPtrID,
Base: base,
Index: index,
Element: node.GetType(),
ArrayObject: arrayObject,
})
return ir.ValueOperand(elementPtrID, types.PointerType{Base: node.GetType()})
}
func (g *Generator) generateSliceExpr(node *parser.SliceExprNode) ir.Operand {
subjectType := types.Underlying(node.Subject.GetType())
resultType := types.Underlying(node.GetType()).(types.SliceType)
var data ir.Operand
var length ir.Operand
checkEndAgainstLength := false
switch subjectType := subjectType.(type) {
case types.SliceType:
subject := g.GenerateExpr(node.Subject)
dataID := g.currentFunction.NewValueOfType(types.PointerType{Base: subjectType.Base})
g.Emit(ir.ExtractValue{Dest: dataID, Aggregate: subject, Index: 0})
lengthID := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.ExtractValue{Dest: lengthID, Aggregate: subject, Index: 1})
data = ir.ValueOperand(dataID, types.PointerType{Base: subjectType.Base})
length = ir.ValueOperand(lengthID, types.PrimitiveUsz)
checkEndAgainstLength = true
case types.ArrayType:
arrayPtr := g.generateAddressOfExpr(node.Subject)
dataID := g.currentFunction.NewValueOfType(types.PointerType{Base: subjectType.Base})
g.Emit(ir.ElementAddress{
Dest: dataID, Base: arrayPtr, Index: ir.IntConstOperand("0", types.PrimitiveUsz), Element: subjectType.Base, ArrayObject: true,
})
data = ir.ValueOperand(dataID, types.PointerType{Base: subjectType.Base})
length = ir.IntConstOperand(strconv.Itoa(subjectType.Length), types.PrimitiveUsz)
checkEndAgainstLength = true
case types.PointerType:
data = g.GenerateExpr(node.Subject)
default:
panic(fmt.Sprintf("cannot generate slice expression for %T", subjectType))
}
start := ir.IntConstOperand("0", types.PrimitiveUsz)
if node.Start != nil {
start = g.GenerateExpr(node.Start)
}
var end ir.Operand
if node.End == nil {
end = length
} else {
end = g.GenerateExpr(node.End)
}
startAfterEnd := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpGt{Dest: startAfterEnd, Left: start, Right: end})
var invalid ir.Operand = ir.ValueOperand(startAfterEnd, types.PrimitiveBool)
if checkEndAgainstLength {
endAfterLength := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpGt{Dest: endAfterLength, Left: end, Right: length})
invalidID := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.LogicalOr{
Dest: invalidID,
Left: invalid,
Right: ir.ValueOperand(endAfterLength, types.PrimitiveBool),
})
invalid = ir.ValueOperand(invalidID, types.PrimitiveBool)
}
panicBlock := g.currentFunction.NewBlock("slice.bounds.panic")
validBlock := g.currentFunction.NewBlock("slice.bounds.valid")
g.Emit(ir.Branch{
Cond: invalid,
Then: panicBlock.ID,
Else: validBlock.ID,
})
g.currentBlock = panicBlock
g.emitRuntimePanic("slice bounds out of range")
g.currentBlock = validBlock
slicedDataID := g.currentFunction.NewValueOfType(types.PointerType{Base: resultType.Base})
g.Emit(ir.ElementAddress{
Dest: slicedDataID,
Base: data,
Index: start,
Element: resultType.Base,
})
slicedLengthID := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.Sub{Dest: slicedLengthID, Left: end, Right: start})
first := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.InsertValue{
Dest: first,
Aggregate: ir.ZeroConstOperand(node.GetType()),
Value: ir.ValueOperand(slicedDataID, types.PointerType{Base: resultType.Base}),
Index: 0,
})
second := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.InsertValue{
Dest: second,
Aggregate: ir.ValueOperand(first, node.GetType()),
Value: ir.ValueOperand(slicedLengthID, types.PrimitiveUsz),
Index: 1,
})
return ir.ValueOperand(second, node.GetType())
}
func (g *Generator) emitRuntimePanic(messageText string) {
messageNode := &parser.StringLiteralNode{
Value: messageText,
Type: types.SliceType{Base: types.PrimitiveU8},
}
message := g.generateStringLiteralExpr(messageNode)
runtimeStr := types.SliceType{Base: types.PrimitiveU8}
message.Type = runtimeStr
panicSig := ir.FunctionSignature{ParamTypes: []types.Type{runtimeStr}, ReturnType: types.PrimitiveVoid}
g.addExternForCall("__qk_panic", panicSig, "", true)
g.Emit(ir.Call{Name: "__qk_panic", Args: []ir.Operand{message}, Signature: panicSig})
g.Emit(ir.Unreachable{})
}
func (g *Generator) generateStringLiteralExpr(node *parser.StringLiteralNode) ir.Operand {
sliceType, ok := types.Underlying(node.GetType()).(types.SliceType)
if !ok {
panic("string literal must have slice type")
}
tmpSlot := g.currentFunction.NewSlot(sliceType, "")
g.Emit(ir.Alloca{Slot: tmpSlot})
stringPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PrimitiveU8})
g.Emit(ir.StringConst{Dest: stringPtrID, Value: node.Value})
stringPtr := ir.ValueOperand(stringPtrID, types.PointerType{Base: types.PrimitiveU8})
slicePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: sliceType})
g.Emit(ir.AddressOf{Dest: slicePtrID, Slot: tmpSlot})
slicePtr := ir.ValueOperand(slicePtrID, types.PointerType{Base: sliceType})
basePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PrimitiveU8})
g.Emit(ir.FieldAddress{Dest: basePtrID, Base: slicePtr, Field: "0"})
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(basePtrID, types.PointerType{Base: types.PrimitiveU8}), Value: stringPtr})
lenPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PrimitiveUsz})
g.Emit(ir.FieldAddress{Dest: lenPtrID, Base: slicePtr, Field: "1"})
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(lenPtrID, types.PointerType{Base: types.PrimitiveUsz}), Value: ir.IntConstOperand(fmt.Sprintf("%d", len(node.Value)), types.PrimitiveUsz)})
loaded := g.currentFunction.NewValueOfType(sliceType)
g.Emit(ir.Load{Dest: loaded, Slot: tmpSlot})
return ir.ValueOperand(loaded, sliceType)
}
func (g *Generator) generateCStringLiteralExpr(node *parser.CStringLiteralNode) ir.Operand {
dst := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.StringConst{Dest: dst, Value: node.Value, NullTerminated: true})
return ir.ValueOperand(dst, node.GetType())
}
func (g *Generator) generateNilLiteralExpr(node *parser.NilLiteralNode) ir.Operand {
if _, traitPointer := types.Underlying(node.GetType()).(types.TraitPointerType); traitPointer {
return ir.ZeroConstOperand(node.GetType())
}
return ir.NullConstOperand(node.GetType())
}
func (g *Generator) generateCastExpr(node *parser.CastNode) ir.Operand {
targetType := node.GetType()
if node.Checked {
targetType = node.CheckedType
}
if node.TraitRecast {
return g.generateTraitRecast(node)
}
if node.TraitConversion {
value := g.generateTraitConversion(node)
if node.Checked {
return g.packCheckedCast(value, ir.BoolConstOperand(true), node.GetType().(types.MultipleReturnType))
}
return value
}
if node.TraitUnwrap {
return g.generateTraitUnwrap(node)
}
assertionMatches := node.AssertionMatches
if node.GenericAssertion {
assertionMatches = node.Operand.GetType().Equals(targetType)
}
if (node.GenericAssertion || node.StaticAssertion || node.StaticTraitView != nil) && !assertionMatches {
return g.generateFailedStaticAssertion(node, targetType)
}
if str, ok := node.Operand.(*parser.StringLiteralNode); ok {
if _, ok := types.Underlying(targetType).(types.PointerType); ok {
dst := g.currentFunction.NewValueOfType(targetType)
g.Emit(ir.StringConst{Dest: dst, Value: str.Value})
return g.finishCertainCheckedCast(node, ir.ValueOperand(dst, targetType))
}
}
if sourceArray, ok := types.Underlying(node.Operand.GetType()).(types.ArrayType); ok {
if targetSlice, ok := types.Underlying(targetType).(types.SliceType); ok && sourceArray.Base.Equals(targetSlice.Base) {
return g.finishCertainCheckedCast(node, g.generateArrayBorrow(node.Operand, targetType, sourceArray))
}
}
if sourceSlice, ok := types.Underlying(node.Operand.GetType()).(types.SliceType); ok {
if targetArray, ok := types.Underlying(targetType).(types.ArrayType); ok && sourceSlice.Base.Equals(targetArray.Base) {
return g.generateSliceToArrayCast(node, targetType, sourceSlice, targetArray)
}
}
from := g.GenerateExpr(node.Operand)
if from.Type.Equals(targetType) {
return g.finishCertainCheckedCast(node, from)
}
if types.Underlying(from.Type).Equals(types.Underlying(targetType)) {
from.Type = targetType
return g.finishCertainCheckedCast(node, from)
}
if fromSlice, ok := types.Underlying(from.Type).(types.SliceType); ok {
if toSlice, ok := types.Underlying(targetType).(types.SliceType); ok && fromSlice.Base.Equals(toSlice.Base) {
// Mutable and immutable slices have the same runtime representation.
from.Type = targetType
return g.finishCertainCheckedCast(node, from)
}
}
dst := g.currentFunction.NewValueOfType(targetType)
g.Emit(ir.Cast{Dest: dst, From: from, To: targetType})
value := ir.ValueOperand(dst, targetType)
if node.Checked {
return g.packCheckedCast(value, ir.BoolConstOperand(true), node.GetType().(types.MultipleReturnType))
}
return value
}
func (g *Generator) generateArrayBorrow(operand parser.ExpressionNode, targetType types.Type, arrayType types.ArrayType) ir.Operand {
arrayPtr := g.generateAddressOfExpr(operand)
dataID := g.currentFunction.NewValueOfType(types.PointerType{Base: arrayType.Base})
g.Emit(ir.ElementAddress{
Dest: dataID, Base: arrayPtr, Index: ir.IntConstOperand("0", types.PrimitiveUsz), Element: arrayType.Base, ArrayObject: true,
})
data := ir.ValueOperand(dataID, types.PointerType{Base: arrayType.Base})
resultSlot := g.currentFunction.NewSlot(targetType, "array.view")
g.Emit(ir.Alloca{Slot: resultSlot})
resultPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: targetType, Mutable: true})
g.Emit(ir.AddressOf{Dest: resultPtrID, Slot: resultSlot})
resultPtr := ir.ValueOperand(resultPtrID, types.PointerType{Base: targetType, Mutable: true})
dataFieldID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PointerType{Base: arrayType.Base}, Mutable: true})
g.Emit(ir.FieldAddress{Dest: dataFieldID, Base: resultPtr, Field: "0"})
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(dataFieldID, types.PointerType{Base: types.PointerType{Base: arrayType.Base}, Mutable: true}), Value: data})
lengthFieldID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PrimitiveUsz, Mutable: true})
g.Emit(ir.FieldAddress{Dest: lengthFieldID, Base: resultPtr, Field: "1"})
g.Emit(ir.StorePtr{
Ptr: ir.ValueOperand(lengthFieldID, types.PointerType{Base: types.PrimitiveUsz, Mutable: true}),
Value: ir.IntConstOperand(strconv.Itoa(arrayType.Length), types.PrimitiveUsz),
})
resultID := g.currentFunction.NewValueOfType(targetType)
g.Emit(ir.Load{Dest: resultID, Slot: resultSlot})
return ir.ValueOperand(resultID, targetType)
}
func (g *Generator) generateSliceToArrayCast(node *parser.CastNode, targetType types.Type, sourceSlice types.SliceType, targetArray types.ArrayType) ir.Operand {
source := g.GenerateExpr(node.Operand)
dataID := g.currentFunction.NewValueOfType(types.PointerType{Base: sourceSlice.Base})
g.Emit(ir.ExtractValue{Dest: dataID, Aggregate: source, Index: 0})
data := ir.ValueOperand(dataID, types.PointerType{Base: sourceSlice.Base})
lengthID := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.ExtractValue{Dest: lengthID, Aggregate: source, Index: 1})
length := ir.ValueOperand(lengthID, types.PrimitiveUsz)
matchesID := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpEq{Dest: matchesID, Left: length, Right: ir.IntConstOperand(strconv.Itoa(targetArray.Length), types.PrimitiveUsz)})
matches := ir.ValueOperand(matchesID, types.PrimitiveBool)
arraySlot := g.currentFunction.NewSlot(targetType, "slice.array")
g.Emit(ir.Alloca{Slot: arraySlot})
if node.Checked {
g.Emit(ir.Store{Slot: arraySlot, Value: ir.ZeroConstOperand(targetType)})
}
success := g.currentFunction.NewBlock("slice.array.success")
failure := g.currentFunction.NewBlock("slice.array.failure")
merge := g.currentFunction.NewBlock("slice.array.end")
g.Emit(ir.Branch{Cond: matches, Then: success.ID, Else: failure.ID})
g.currentBlock = failure
if node.Checked {
g.Emit(ir.Jump{Target: merge.ID})
} else {
g.emitRuntimePanic("slice-to-array cast length mismatch")
}
g.currentBlock = success
arrayPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: targetType, Mutable: true})
g.Emit(ir.AddressOf{Dest: arrayPtrID, Slot: arraySlot})
arrayPtr := ir.ValueOperand(arrayPtrID, types.PointerType{Base: targetType, Mutable: true})
for i := 0; i < targetArray.Length; i++ {
index := ir.IntConstOperand(strconv.Itoa(i), types.PrimitiveUsz)
sourcePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: sourceSlice.Base})
g.Emit(ir.ElementAddress{Dest: sourcePtrID, Base: data, Index: index, Element: sourceSlice.Base})
valueID := g.currentFunction.NewValueOfType(sourceSlice.Base)
g.Emit(ir.LoadPtr{Dest: valueID, Ptr: ir.ValueOperand(sourcePtrID, types.PointerType{Base: sourceSlice.Base})})
targetPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: targetArray.Base, Mutable: true})
g.Emit(ir.ElementAddress{Dest: targetPtrID, Base: arrayPtr, Index: index, Element: targetArray.Base, ArrayObject: true})
g.Emit(ir.StorePtr{
Ptr: ir.ValueOperand(targetPtrID, types.PointerType{Base: targetArray.Base, Mutable: true}),
Value: ir.ValueOperand(valueID, sourceSlice.Base),
})
}
g.Emit(ir.Jump{Target: merge.ID})
g.currentBlock = merge
arrayID := g.currentFunction.NewValueOfType(targetType)
g.Emit(ir.Load{Dest: arrayID, Slot: arraySlot})
array := ir.ValueOperand(arrayID, targetType)
if node.Checked {
return g.packCheckedCast(array, matches, node.GetType().(types.MultipleReturnType))
}
return array
}
func (g *Generator) generateFailedStaticAssertion(node *parser.CastNode, targetType types.Type) ir.Operand {
// Assertions evaluate their operand even when specialization makes the
// outcome statically known.
g.GenerateExpr(node.Operand)
zero := ir.ZeroConstOperand(targetType)
if node.Checked {
return g.packCheckedCast(zero, ir.BoolConstOperand(false), node.GetType().(types.MultipleReturnType))
}
expected := traitRuntimeName(targetType)
if node.StaticTraitView != nil {
expected = node.StaticTraitView.String()
}
messageText := "type assertion failed: " + traitRuntimeName(node.Operand.GetType()) + " is not " + expected
messageNode := &parser.StringLiteralNode{Value: messageText, Type: types.SliceType{Base: types.PrimitiveU8}, Loc: node.Loc}
message := g.generateStringLiteralExpr(messageNode)
runtimeStr := types.SliceType{Base: types.PrimitiveU8}
message.Type = runtimeStr
panicSig := ir.FunctionSignature{ParamTypes: []types.Type{runtimeStr}, ReturnType: types.PrimitiveVoid}
g.addExternForCall("__qk_panic", panicSig, "", true)
g.Emit(ir.Call{Name: "__qk_panic", Args: []ir.Operand{message}, Signature: panicSig})
g.Emit(ir.Unreachable{})
g.currentBlock = g.currentFunction.NewBlock("assert.unreachable")
return zero
}
func (g *Generator) finishCertainCheckedCast(node *parser.CastNode, value ir.Operand) ir.Operand {
if !node.Checked {
return value
}
return g.packCheckedCast(value, ir.BoolConstOperand(true), node.GetType().(types.MultipleReturnType))
}
func (g *Generator) packCheckedCast(value, ok ir.Operand, result types.MultipleReturnType) ir.Operand {
first := g.currentFunction.NewValueOfType(result)
g.Emit(ir.InsertValue{Dest: first, Aggregate: ir.ZeroConstOperand(result), Value: value, Index: 0})
second := g.currentFunction.NewValueOfType(result)
g.Emit(ir.InsertValue{Dest: second, Aggregate: ir.ValueOperand(first, result), Value: ok, Index: 1})
return ir.ValueOperand(second, result)
}
func traitRuntimeName(t types.Type) string {
switch t := t.(type) {
case types.DefinedType:
return t.Module + "." + t.Name
case *types.AliasRef:
return t.Module + "." + t.Name
default:
return t.String()
}
}
func runtimeTypeID(t types.Type) uint64 {
h := fnv.New64a()
_, _ = h.Write([]byte(traitRuntimeName(t)))
return h.Sum64()
}
func traitVTableType(trait types.TraitType) types.StructType {
fields := []shared.Pair[string, types.Type]{{L: "type_id", R: types.PrimitiveU64}}
for _, method := range trait.Methods {
params := append([]types.Type{traitErasedReceiverType(method)}, method.Parameters...)
fields = append(fields, shared.Pair[string, types.Type]{L: method.Name, R: types.PointerType{Base: types.FunctionType{Parameters: params, ReturnType: method.ReturnType}}})
}
return types.StructType{Fields: fields}
}
func traitErasedReceiverType(method types.TraitMethod) types.PointerType {
return types.PointerType{Base: types.PrimitiveVoid, Mutable: method.Receiver == types.TraitReceiverMutablePointer}
}
func (g *Generator) ensureTraitVTable(node *parser.CastNode, trait types.TraitType) string {
key := traitRuntimeName(node.ConcreteType) + "__" + trait.String()
name := fmt.Sprintf("__qk_vtable_%s_%x", sanitizeName(key), stringHash(key))
for _, global := range g.Module.Globals {
if global.Name == name {
return name
}
}
vt := traitVTableType(trait)
values := []ir.Operand{ir.IntConstOperand(fmt.Sprintf("%d", runtimeTypeID(node.ConcreteType)), types.PrimitiveU64)}
concreteModule := g.ModuleName
if d, ok := node.ConcreteType.(types.DefinedType); ok {
concreteModule = d.Module
}
for i, method := range node.TraitMethods {
req := trait.Methods[i]
if g.DemandedTraitSlots != nil && !g.DemandedTraitSlots[trait.String()][i] {
params := append([]types.Type{traitErasedReceiverType(req)}, req.Parameters...)
fnType := types.PointerType{Base: types.FunctionType{Parameters: params, ReturnType: req.ReturnType}}
values = append(values, ir.NullConstOperand(fnType))
continue
}
methodModule := concreteModule
if method.DefinitionModule != "" {
methodModule = method.DefinitionModule
}
params := append([]types.Type{traitErasedReceiverType(req)}, req.Parameters...)
fnType := types.PointerType{Base: types.FunctionType{Parameters: params, ReturnType: req.ReturnType}}
fnName := g.mangleFunctionName(methodModule, method.Name)
if req.Receiver == types.TraitReceiverValue {
fnName = g.generateValueReceiverTraitThunk(name, i, node.ConcreteType, fnName, method, req)
}
functionOperand := ir.FunctionConstOperand(fnName, fnType)
if method.TemplateSymbol != nil {
functionOperand.Generic = &ir.GenericReference{
Module: method.TemplateSymbol.DefinitionModule, Name: method.TemplateSymbol.Name,
TypeArguments: append([]types.Type(nil), method.TypeArguments...),
}
}
values = append(values, functionOperand)
if methodModule != g.ModuleName && req.Receiver != types.TraitReceiverValue {
g.addExternForCall(fnName, ir.FunctionSignature{ParamTypes: method.Signature.Parameters, ReturnType: method.Signature.ReturnType}, "", true)
}
}
g.Module.AddGlobal(ir.Global{Name: name, Type: vt, Linkage: ir.LinkageInternal, Value: ir.StructConstOperand(vt, values)})
return name
}
func stringHash(value string) uint64 {
h := fnv.New64a()
_, _ = h.Write([]byte(value))
return h.Sum64()
}
func (g *Generator) generateValueReceiverTraitThunk(vtableName string, slot int, concrete types.Type, targetName string, method *symbols.Symbol, requirement types.TraitMethod) string {
name := fmt.Sprintf("%s__value_%d", vtableName, slot)
for _, fn := range g.Module.Functions {
if fn.Name == name {
return name
}
}
externalTarget := method.DefinitionModule != "" && method.DefinitionModule != g.ModuleName
if d, ok := concrete.(types.DefinedType); ok && method.DefinitionModule == "" {
externalTarget = d.Module != g.ModuleName
}
if externalTarget {
g.addExternForCall(targetName, ir.FunctionSignature{ParamTypes: method.Signature.Parameters, ReturnType: method.Signature.ReturnType}, "", true)
}
fn := ir.NewFunction(name, ir.LinkageInternal, nil)
params := append([]types.Type{traitErasedReceiverType(requirement)}, requirement.Parameters...)
fn.Signature = ir.FunctionSignature{ParamTypes: params, ReturnType: requirement.ReturnType}
g.Module.AddFunction(fn)
previousFunction, previousBlock, previousEnv := g.currentFunction, g.currentBlock, g.currentEnv
previousDefers := g.deferScopes
g.currentFunction = fn
g.currentBlock = fn.NewBlock("entry")
fn.Entry = g.currentBlock.ID
g.currentEnv = NewEnv(nil)
g.deferScopes = nil
incoming := make([]ir.Operand, len(params))
for i, param := range params {
fn.AddParameter(fmt.Sprintf("arg%d", i), param, 0)
id := fn.NewValueOfType(param)
incoming[i] = ir.ValueOperand(id, param)
}
concretePointer := types.PointerType{Base: concrete}
castID := fn.NewValueOfType(concretePointer)
g.Emit(ir.Cast{Dest: castID, From: incoming[0], To: concretePointer})
valueID := fn.NewValueOfType(concrete)
g.Emit(ir.LoadPtr{Dest: valueID, Ptr: ir.ValueOperand(castID, concretePointer)})
args := append([]ir.Operand{ir.ValueOperand(valueID, concrete)}, incoming[1:]...)
callSig := ir.FunctionSignature{ParamTypes: method.Signature.Parameters, ReturnType: method.Signature.ReturnType}
if requirement.ReturnType.Equals(types.PrimitiveVoid) {
g.Emit(ir.Call{Name: targetName, Args: args, Signature: callSig})
g.Emit(ir.Return{})
} else {
result := fn.NewValueOfType(requirement.ReturnType)
g.Emit(ir.Call{Dest: result, Name: targetName, Args: args, Signature: callSig})
g.Emit(ir.Return{HasValue: true, Value: ir.ValueOperand(result, requirement.ReturnType)})
}
g.currentFunction, g.currentBlock, g.currentEnv = previousFunction, previousBlock, previousEnv
g.deferScopes = previousDefers
return name
}
func (g *Generator) generateTraitConversion(node *parser.CastNode) ir.Operand {
targetType := node.GetType()
if node.Checked {
targetType = node.CheckedType
}
target := targetType.(types.TraitPointerType)
data := g.GenerateExpr(node.Operand)
voidPtr := types.PointerType{Base: types.PrimitiveVoid, Mutable: target.Mutable}
if !data.Type.Equals(voidPtr) {
dst := g.currentFunction.NewValueOfType(voidPtr)
g.Emit(ir.Cast{Dest: dst, From: data, To: voidPtr})
data = ir.ValueOperand(dst, voidPtr)
}
vtName := g.ensureTraitVTable(node, target.Trait)
vtType := traitVTableType(target.Trait)
vtPtrType := types.PointerType{Base: vtType}
vtID := g.currentFunction.NewValueOfType(vtPtrType)
g.Emit(ir.AddressOfGlobal{Dest: vtID, Name: vtName, Type: vtType})
agg := ir.ZeroConstOperand(target)
first := g.currentFunction.NewValueOfType(target)
g.Emit(ir.InsertValue{Dest: first, Aggregate: agg, Value: data, Index: 0})
second := g.currentFunction.NewValueOfType(target)
g.Emit(ir.InsertValue{Dest: second, Aggregate: ir.ValueOperand(first, target), Value: ir.ValueOperand(vtID, vtPtrType), Index: 1})
return ir.ValueOperand(second, target)
}
func (g *Generator) generateTraitRecast(node *parser.CastNode) ir.Operand {
sourceValue := g.GenerateExpr(node.Operand)
source := sourceValue.Type.(types.TraitPointerType)
targetType := node.GetType()
if node.Checked {
targetType = node.CheckedType
}
target := targetType.(types.TraitPointerType)
sourceVT := traitVTableType(source.Trait)
sourceVTPtr := types.PointerType{Base: sourceVT}
vtID := g.currentFunction.NewValueOfType(sourceVTPtr)
g.Emit(ir.ExtractValue{Dest: vtID, Aggregate: sourceValue, Index: 1})
typeIDPtr := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PrimitiveU64})
g.Emit(ir.FieldAddress{Dest: typeIDPtr, Base: ir.ValueOperand(vtID, sourceVTPtr), Field: "type_id"})
actualID := g.currentFunction.NewValueOfType(types.PrimitiveU64)
g.Emit(ir.LoadPtr{Dest: actualID, Ptr: ir.ValueOperand(typeIDPtr, types.PointerType{Base: types.PrimitiveU64})})
sourceDataType := types.PointerType{Base: types.PrimitiveVoid, Mutable: source.Mutable}
sourceDataID := g.currentFunction.NewValueOfType(sourceDataType)
g.Emit(ir.ExtractValue{Dest: sourceDataID, Aggregate: sourceValue, Index: 0})
data := ir.ValueOperand(sourceDataID, sourceDataType)
targetDataType := types.PointerType{Base: types.PrimitiveVoid, Mutable: target.Mutable}
if !data.Type.Equals(targetDataType) {
castID := g.currentFunction.NewValueOfType(targetDataType)
g.Emit(ir.Cast{Dest: castID, From: data, To: targetDataType})
data = ir.ValueOperand(castID, targetDataType)
}
resultSlot := g.currentFunction.NewSlot(target, "trait.recast")
g.Emit(ir.Alloca{Slot: resultSlot})
okSlot := g.currentFunction.NewSlot(types.PrimitiveBool, "trait.recast.ok")
if node.Checked {
g.Emit(ir.Alloca{Slot: okSlot})
g.Emit(ir.Store{Slot: okSlot, Value: ir.BoolConstOperand(false)})
}
end := g.currentFunction.NewBlock("trait.recast.end")
for _, candidate := range node.TraitCandidates {
matches := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpEq{Dest: matches, Left: ir.ValueOperand(actualID, types.PrimitiveU64), Right: ir.IntConstOperand(fmt.Sprintf("%d", runtimeTypeID(candidate.ConcreteType)), types.PrimitiveU64)})
success := g.currentFunction.NewBlock("trait.recast.match")
next := g.currentFunction.NewBlock("trait.recast.next")
g.Emit(ir.Branch{Cond: ir.ValueOperand(matches, types.PrimitiveBool), Then: success.ID, Else: next.ID})
g.currentBlock = success
candidateCast := &parser.CastNode{ConcreteType: candidate.ConcreteType, TraitMethods: candidate.Methods}
vtName := g.ensureTraitVTable(candidateCast, target.Trait)
targetVT := traitVTableType(target.Trait)
targetVTPtr := types.PointerType{Base: targetVT}
targetVTID := g.currentFunction.NewValueOfType(targetVTPtr)
g.Emit(ir.AddressOfGlobal{Dest: targetVTID, Name: vtName, Type: targetVT})
first := g.currentFunction.NewValueOfType(target)
g.Emit(ir.InsertValue{Dest: first, Aggregate: ir.ZeroConstOperand(target), Value: data, Index: 0})
second := g.currentFunction.NewValueOfType(target)
g.Emit(ir.InsertValue{Dest: second, Aggregate: ir.ValueOperand(first, target), Value: ir.ValueOperand(targetVTID, targetVTPtr), Index: 1})
g.Emit(ir.Store{Slot: resultSlot, Value: ir.ValueOperand(second, target)})
if node.Checked {
g.Emit(ir.Store{Slot: okSlot, Value: ir.BoolConstOperand(true)})
}
g.Emit(ir.Jump{Target: end.ID})
g.currentBlock = next
}
if node.Checked {
g.Emit(ir.Store{Slot: resultSlot, Value: ir.ZeroConstOperand(target)})
g.Emit(ir.Jump{Target: end.ID})
} else {
messageText := "trait cast failed: value does not implement " + target.Trait.String()
messageNode := &parser.StringLiteralNode{Value: messageText, Type: types.SliceType{Base: types.PrimitiveU8}, Loc: node.Loc}
message := g.generateStringLiteralExpr(messageNode)
runtimeStr := types.SliceType{Base: types.PrimitiveU8}
message.Type = runtimeStr
panicSig := ir.FunctionSignature{ParamTypes: []types.Type{runtimeStr}, ReturnType: types.PrimitiveVoid}
g.addExternForCall("__qk_panic", panicSig, "", true)
g.Emit(ir.Call{Name: "__qk_panic", Args: []ir.Operand{message}, Signature: panicSig})
g.Emit(ir.Unreachable{})
}
g.currentBlock = end
result := g.currentFunction.NewValueOfType(target)
g.Emit(ir.Load{Dest: result, Slot: resultSlot})
value := ir.ValueOperand(result, target)
if node.Checked {
okID := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.Load{Dest: okID, Slot: okSlot})
return g.packCheckedCast(value, ir.ValueOperand(okID, types.PrimitiveBool), node.GetType().(types.MultipleReturnType))
}
return value
}
func (g *Generator) generateTraitUnwrap(node *parser.CastNode) ir.Operand {
traitValue := g.GenerateExpr(node.Operand)
traitPtr := traitValue.Type.(types.TraitPointerType)
vtType := traitVTableType(traitPtr.Trait)
vtPtrType := types.PointerType{Base: vtType}
vtID := g.currentFunction.NewValueOfType(vtPtrType)
g.Emit(ir.ExtractValue{Dest: vtID, Aggregate: traitValue, Index: 1})
typeIDPtr := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PrimitiveU64})
g.Emit(ir.FieldAddress{Dest: typeIDPtr, Base: ir.ValueOperand(vtID, vtPtrType), Field: "type_id"})
actualID := g.currentFunction.NewValueOfType(types.PrimitiveU64)
g.Emit(ir.LoadPtr{Dest: actualID, Ptr: ir.ValueOperand(typeIDPtr, types.PointerType{Base: types.PrimitiveU64})})
matches := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpEq{Dest: matches, Left: ir.ValueOperand(actualID, types.PrimitiveU64), Right: ir.IntConstOperand(fmt.Sprintf("%d", runtimeTypeID(node.ConcreteType)), types.PrimitiveU64)})
success := g.currentFunction.NewBlock("trait.unwrap.success")
failure := g.currentFunction.NewBlock("trait.unwrap.failure")
end := g.currentFunction.NewBlock("trait.unwrap.end")
targetType := node.GetType()
if node.Checked {
targetType = node.CheckedType
}
resultSlot := g.currentFunction.NewSlot(targetType, "trait.unwrap")
okSlot := g.currentFunction.NewSlot(types.PrimitiveBool, "trait.unwrap.ok")
if node.Checked {
g.Emit(ir.Alloca{Slot: resultSlot})
g.Emit(ir.Alloca{Slot: okSlot})
g.Emit(ir.Store{Slot: okSlot, Value: ir.BoolConstOperand(false)})
}
g.Emit(ir.Branch{Cond: ir.ValueOperand(matches, types.PrimitiveBool), Then: success.ID, Else: failure.ID})
g.currentBlock = failure
if node.Checked {
g.Emit(ir.Store{Slot: resultSlot, Value: ir.ZeroConstOperand(targetType)})
g.Emit(ir.Jump{Target: end.ID})
} else {
messageText := "trait unwrap failed: expected " + traitRuntimeName(node.ConcreteType)
messageNode := &parser.StringLiteralNode{Value: messageText, Type: types.SliceType{Base: types.PrimitiveU8}, Loc: node.Loc}
message := g.generateStringLiteralExpr(messageNode)
runtimeStr := types.SliceType{Base: types.PrimitiveU8}
message.Type = runtimeStr
panicSig := ir.FunctionSignature{ParamTypes: []types.Type{runtimeStr}, ReturnType: types.PrimitiveVoid}
g.addExternForCall("__qk_panic", panicSig, "", true)
g.Emit(ir.Call{Name: "__qk_panic", Args: []ir.Operand{message}, Signature: panicSig})
g.Emit(ir.Unreachable{})
}
g.currentBlock = success
dataType := types.PointerType{Base: types.PrimitiveVoid, Mutable: traitPtr.Mutable}
dataID := g.currentFunction.NewValueOfType(dataType)
g.Emit(ir.ExtractValue{Dest: dataID, Aggregate: traitValue, Index: 0})
targetPointer, byPointer := types.Underlying(targetType).(types.PointerType)
if !byPointer {
targetPointer = types.PointerType{Base: targetType}
}
castID := g.currentFunction.NewValueOfType(targetPointer)
g.Emit(ir.Cast{Dest: castID, From: ir.ValueOperand(dataID, dataType), To: targetPointer})
if byPointer {
value := ir.ValueOperand(castID, targetType)
if node.Checked {
g.Emit(ir.Store{Slot: resultSlot, Value: value})
g.Emit(ir.Store{Slot: okSlot, Value: ir.BoolConstOperand(true)})
g.Emit(ir.Jump{Target: end.ID})
} else {
return value
}
} else {
valueID := g.currentFunction.NewValueOfType(targetType)
g.Emit(ir.LoadPtr{Dest: valueID, Ptr: ir.ValueOperand(castID, targetPointer)})
value := ir.ValueOperand(valueID, targetType)
if node.Checked {
g.Emit(ir.Store{Slot: resultSlot, Value: value})
g.Emit(ir.Store{Slot: okSlot, Value: ir.BoolConstOperand(true)})
g.Emit(ir.Jump{Target: end.ID})
} else {
return value
}
}
g.currentBlock = end
valueID := g.currentFunction.NewValueOfType(targetType)
g.Emit(ir.Load{Dest: valueID, Slot: resultSlot})
okID := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.Load{Dest: okID, Slot: okSlot})
return g.packCheckedCast(ir.ValueOperand(valueID, targetType), ir.ValueOperand(okID, types.PrimitiveBool), node.GetType().(types.MultipleReturnType))
}
func (g *Generator) generateSizeOfExpr(node *parser.SizeOfNode) ir.Operand {
dst := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.Sizeof{Dest: dst, Type: node.OperandType})
return ir.ValueOperand(dst, types.PrimitiveUsz)
}
func (g *Generator) generateSizeOfExprExpr(node *parser.SizeOfExprNode) ir.Operand {
dst := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.Sizeof{Dest: dst, Type: node.Operand.GetType()})
return ir.ValueOperand(dst, types.PrimitiveUsz)
}
func (g *Generator) generateAlignOfExpr(node *parser.AlignOfNode) ir.Operand {
dst := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.Alignof{Dest: dst, Type: node.OperandType})
return ir.ValueOperand(dst, types.PrimitiveUsz)
}
func (g *Generator) generateOffsetOfExpr(node *parser.OffsetOfNode) ir.Operand {
dst := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.Offsetof{Dest: dst, Type: node.OperandType, Field: node.Field})
return ir.ValueOperand(dst, types.PrimitiveUsz)
}
func (g *Generator) generateStructLiteralExpr(node *parser.StructLiteralNode) ir.Operand {
if flagType, ok := types.Underlying(node.GetType()).(types.FlagsType); ok {
value := new(big.Int)
for _, member := range node.FlagMembers {
raw, _ := flagType.VariantValue(member)
part, _ := new(big.Int).SetString(raw, 10)
value.Or(value, part)
}
return ir.IntConstOperand(value.String(), node.GetType())
}
tmpSlot := g.currentFunction.NewSlot(node.GetType(), "")
g.Emit(ir.Alloca{Slot: tmpSlot})
g.generateStructLiteralIntoSlot(tmpSlot, node)
dst := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.Load{Dest: dst, Slot: tmpSlot})
return ir.ValueOperand(dst, node.GetType())
}
func (g *Generator) generateTaggedUnionConstructor(node *parser.FunctionCallNode) ir.Operand {
info, ok := types.TaggedUnion(node.TaggedUnionType)
if !ok || node.TaggedUnionVariant < 0 || node.TaggedUnionVariant >= len(info.Variants) {
panic("invalid tagged union constructor")
}
variant := info.Variants[node.TaggedUnionVariant]
slot := g.currentFunction.NewSlot(node.TaggedUnionType, "tagged.union")
g.Emit(ir.Alloca{Slot: slot})
g.Emit(ir.Store{Slot: slot, Value: ir.ZeroConstOperand(node.TaggedUnionType)})
baseID := g.currentFunction.NewValueOfType(types.PointerType{Base: node.TaggedUnionType})
g.Emit(ir.AddressOf{Dest: baseID, Slot: slot})
base := ir.ValueOperand(baseID, types.PointerType{Base: node.TaggedUnionType})
tagPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: info.Tag})
g.Emit(ir.FieldAddress{Dest: tagPtrID, Base: base, Field: "$tag"})
g.Emit(ir.StorePtr{
Ptr: ir.ValueOperand(tagPtrID, types.PointerType{Base: info.Tag}),
Value: ir.IntConstOperand(variant.TagValue, info.Tag),
})
if len(variant.Fields) != 0 {
storage := types.Underlying(node.TaggedUnionType).(types.StructType)
payloadUnion := storage.Fields[1].R
payloadPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: payloadUnion})
g.Emit(ir.FieldAddress{Dest: payloadPtrID, Base: base, Field: "$payload"})
payloadPtr := ir.ValueOperand(payloadPtrID, types.PointerType{Base: payloadUnion})
variantType := types.StructType{Fields: variant.Fields}
variantPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: variantType})
g.Emit(ir.FieldAddress{Dest: variantPtrID, Base: payloadPtr, Field: variant.Name})
variantPtr := ir.ValueOperand(variantPtrID, types.PointerType{Base: variantType})
for i, field := range variant.Fields {
fieldPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: field.R})
g.Emit(ir.FieldAddress{Dest: fieldPtrID, Base: variantPtr, Field: field.L})
g.Emit(ir.StorePtr{
Ptr: ir.ValueOperand(fieldPtrID, types.PointerType{Base: field.R}),
Value: g.GenerateExpr(node.Args[i]),
})
}
}
resultID := g.currentFunction.NewValueOfType(node.TaggedUnionType)
g.Emit(ir.Load{Dest: resultID, Slot: slot})
return ir.ValueOperand(resultID, node.TaggedUnionType)
}
func (g *Generator) generateMatch(node *parser.MatchNode) ir.Operand {
previousEnv := g.currentEnv
g.currentEnv = NewEnv(previousEnv)
defer func() { g.currentEnv = previousEnv }()
subjectSlots := make([]ir.SlotID, len(node.Subjects))
for i, subject := range node.Subjects {
name := "match.subject"
if len(node.Subjects) != 1 {
name = fmt.Sprintf("match.subject.%d", i)
}
subjectSlots[i] = g.currentFunction.NewSlot(subject.GetType(), name)
g.Emit(ir.Alloca{Slot: subjectSlots[i]})
g.Emit(ir.Store{Slot: subjectSlots[i], Value: g.GenerateExpr(subject)})
}
if node.Binding != nil {
g.currentEnv.Variables[node.Binding] = subjectSlots[0]
}
var resultSlot ir.SlotID
fallsThrough := node.Expression && parser.NodeFallsThrough(node)
if fallsThrough {
resultSlot = g.currentFunction.NewSlot(node.GetType(), "match.result")
g.Emit(ir.Alloca{Slot: resultSlot})
}
mergeBlock := g.currentFunction.NewBlock("match.merge")
for index := range node.Arms {
arm := &node.Arms[index]
setupBlock := g.currentFunction.NewBlock(fmt.Sprintf("match.arm.%d.setup", index))
bodyBlock := g.currentFunction.NewBlock(fmt.Sprintf("match.arm.%d.body", index))
nextBlock := g.currentFunction.NewBlock(fmt.Sprintf("match.arm.%d.next", index))
condition := g.generateMatchArmCondition(arm.Patterns, subjectSlots, node.Subjects)
g.Emit(ir.Branch{Cond: condition, Then: setupBlock.ID, Else: nextBlock.ID})
g.currentBlock = setupBlock
armEnv := NewEnv(g.currentEnv)
g.currentEnv = armEnv
g.bindMatchArmPatterns(arm.Patterns, subjectSlots, node.Subjects)
if arm.Guard != nil {
guard := g.GenerateExpr(arm.Guard)
g.Emit(ir.Branch{Cond: guard, Then: bodyBlock.ID, Else: nextBlock.ID})
} else {
g.Emit(ir.Jump{Target: bodyBlock.ID})
}
g.currentBlock = bodyBlock
if node.Expression {
value := g.GenerateExpr(arm.Body)
if !g.currentBlockHasTerminator() {
if fallsThrough {
g.Emit(ir.Store{Slot: resultSlot, Value: value})
}
g.Emit(ir.Jump{Target: mergeBlock.ID})
}
} else {
g.GenerateNode(arm.Body)
if !g.currentBlockHasTerminator() {
g.Emit(ir.Jump{Target: mergeBlock.ID})
}
}
g.currentEnv = armEnv.Parent
g.currentBlock = nextBlock
}
g.Emit(ir.Unreachable{})
g.currentBlock = mergeBlock
if node.Expression {
if !fallsThrough {
g.Emit(ir.Unreachable{})
return ir.ZeroConstOperand(node.GetType())
}
resultID := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.Load{Dest: resultID, Slot: resultSlot})
return ir.ValueOperand(resultID, node.GetType())
}
if !parser.NodeFallsThrough(node) {
g.Emit(ir.Unreachable{})
}
return ir.Operand{}
}
func (g *Generator) generateMatchArmCondition(patterns []*parser.MatchPatternNode, subjectSlots []ir.SlotID, subjects []parser.ExpressionNode) ir.Operand {
if len(patterns) == 1 && patterns[0].Kind == parser.MatchPatternWildcard {
return ir.BoolConstOperand(true)
}
condition := ir.BoolConstOperand(true)
for i, pattern := range patterns {
part := g.generateMatchPatternCondition(pattern, subjectSlots[i], subjects[i].GetType())
condition = g.emitBinaryOperation(parser.BinaryOpBitwiseAnd, condition, part, types.PrimitiveBool)
}
return condition
}
func (g *Generator) bindMatchArmPatterns(patterns []*parser.MatchPatternNode, subjectSlots []ir.SlotID, subjects []parser.ExpressionNode) {
if len(patterns) == 1 && patterns[0].Kind == parser.MatchPatternWildcard {
return
}
for i, pattern := range patterns {
g.bindMatchPattern(pattern, subjectSlots[i], subjects[i].GetType())
}
}
func (g *Generator) generateMatchPatternCondition(pattern *parser.MatchPatternNode, subjectSlot ir.SlotID, subjectType types.Type) ir.Operand {
switch pattern.Kind {
case parser.MatchPatternWildcard:
return ir.BoolConstOperand(true)
case parser.MatchPatternVariant:
if info, tagged := types.TaggedUnion(subjectType); tagged {
tag := g.loadTaggedUnionTag(subjectSlot, subjectType, info.Tag)
return g.emitBinaryOperation(parser.BinaryOpEqual, tag, ir.IntConstOperand(pattern.TagValue, info.Tag), types.PrimitiveBool)
}
subject := g.loadMatchSubject(subjectSlot, subjectType)
return g.emitBinaryOperation(parser.BinaryOpEqual, subject, ir.IntConstOperand(pattern.TagValue, subjectType), types.PrimitiveBool)
case parser.MatchPatternLiteral:
subject := g.loadMatchSubject(subjectSlot, subjectType)
return g.emitBinaryOperation(parser.BinaryOpEqual, subject, g.GenerateExpr(pattern.Literal), types.PrimitiveBool)
case parser.MatchPatternRange:
subject := g.loadMatchSubject(subjectSlot, subjectType)
start := g.GenerateExpr(pattern.Start)
end := g.GenerateExpr(pattern.End)
atLeast := g.emitBinaryOperation(parser.BinaryOpGreaterEqual, subject, start, types.PrimitiveBool)
below := g.emitBinaryOperation(parser.BinaryOpLess, subject, end, types.PrimitiveBool)
return g.emitBinaryOperation(parser.BinaryOpBitwiseAnd, atLeast, below, types.PrimitiveBool)
case parser.MatchPatternAlternative:
result := ir.BoolConstOperand(false)
for _, alternative := range pattern.Alternatives {
condition := g.generateMatchPatternCondition(alternative, subjectSlot, subjectType)
result = g.emitBinaryOperation(parser.BinaryOpBitwiseOr, result, condition, types.PrimitiveBool)
}
return result
default:
panic("unsupported match pattern")
}
}
func (g *Generator) loadMatchSubject(slot ir.SlotID, subjectType types.Type) ir.Operand {
valueID := g.currentFunction.NewValueOfType(subjectType)
g.Emit(ir.Load{Dest: valueID, Slot: slot})
return ir.ValueOperand(valueID, subjectType)
}
func (g *Generator) loadTaggedUnionTag(slot ir.SlotID, subjectType, tagType types.Type) ir.Operand {
baseID := g.currentFunction.NewValueOfType(types.PointerType{Base: subjectType})
g.Emit(ir.AddressOf{Dest: baseID, Slot: slot})
tagPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: tagType})
g.Emit(ir.FieldAddress{
Dest: tagPtrID, Base: ir.ValueOperand(baseID, types.PointerType{Base: subjectType}), Field: "$tag",
})
tagID := g.currentFunction.NewValueOfType(tagType)
g.Emit(ir.LoadPtr{Dest: tagID, Ptr: ir.ValueOperand(tagPtrID, types.PointerType{Base: tagType})})
return ir.ValueOperand(tagID, tagType)
}
func (g *Generator) bindMatchPattern(pattern *parser.MatchPatternNode, subjectSlot ir.SlotID, subjectType types.Type) {
if pattern.Kind != parser.MatchPatternVariant || len(pattern.Bindings) == 0 {
return
}
info, tagged := types.TaggedUnion(subjectType)
if !tagged {
return
}
variant, _, ok := info.Variant(pattern.Variant)
if !ok || len(variant.Fields) == 0 {
return
}
storage := types.Underlying(subjectType).(types.StructType)
payloadUnion := storage.Fields[1].R
baseID := g.currentFunction.NewValueOfType(types.PointerType{Base: subjectType})
g.Emit(ir.AddressOf{Dest: baseID, Slot: subjectSlot})
payloadPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: payloadUnion})
g.Emit(ir.FieldAddress{
Dest: payloadPtrID,
Base: ir.ValueOperand(baseID, types.PointerType{Base: subjectType}), Field: "$payload",
})
variantType := types.StructType{Fields: variant.Fields}
variantPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: variantType})
g.Emit(ir.FieldAddress{
Dest: variantPtrID,
Base: ir.ValueOperand(payloadPtrID, types.PointerType{Base: payloadUnion}), Field: variant.Name,
})
variantPtr := ir.ValueOperand(variantPtrID, types.PointerType{Base: variantType})
for _, binding := range pattern.Bindings {
if binding.Symbol == nil {
continue
}
fieldType := binding.Symbol.Type
fieldPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: fieldType})
g.Emit(ir.FieldAddress{Dest: fieldPtrID, Base: variantPtr, Field: binding.Field})
valueID := g.currentFunction.NewValueOfType(fieldType)
g.Emit(ir.LoadPtr{Dest: valueID, Ptr: ir.ValueOperand(fieldPtrID, types.PointerType{Base: fieldType})})
slot := g.currentFunction.NewSlot(fieldType, binding.Name)
g.currentEnv.Variables[binding.Symbol] = slot
g.Emit(ir.Alloca{Slot: slot})
g.Emit(ir.Store{Slot: slot, Value: ir.ValueOperand(valueID, fieldType)})
}
}
func (g *Generator) generateSliceLiteralExpr(node *parser.SliceLiteralNode) ir.Operand {
switch literalType := types.Underlying(node.GetType()).(type) {
case types.ArrayType:
return g.generateArrayLiteral(node, literalType)
case types.SliceType:
return g.generateViewLiteral(node, literalType)
default:
panic("sequence literal must have array or slice type")
}
}
func (g *Generator) generateViewLiteral(node *parser.SliceLiteralNode, sliceType types.SliceType) ir.Operand {
targetType := node.GetType()
tmpSlot := g.currentFunction.NewSlot(targetType, "")
g.Emit(ir.Alloca{Slot: tmpSlot})
if node.RepeatValue != nil {
return g.generateRepeatedSliceLiteral(node, targetType, sliceType, tmpSlot)
}
slicePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: targetType})
g.Emit(ir.AddressOf{Dest: slicePtrID, Slot: tmpSlot})
slicePtr := ir.ValueOperand(slicePtrID, types.PointerType{Base: targetType})
var elemPtr ir.Operand
if len(node.Elements) == 0 {
elemPtr = ir.NullConstOperand(types.PointerType{Base: sliceType.Base})
} else {
bufferType := types.StructType{
Fields: make([]shared.Pair[string, types.Type], len(node.Elements)),
}
for i := range node.Elements {
bufferType.Fields[i] = shared.Pair[string, types.Type]{
L: fmt.Sprintf("%d", i),
R: sliceType.Base,
}
}
bufferSlot := g.currentFunction.NewSlot(bufferType, "")
g.Emit(ir.Alloca{Slot: bufferSlot})
bufferPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: bufferType})
g.Emit(ir.AddressOf{Dest: bufferPtrID, Slot: bufferSlot})
bufferPtr := ir.ValueOperand(bufferPtrID, types.PointerType{Base: bufferType})
for i, element := range node.Elements {
fieldPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: sliceType.Base})
g.Emit(ir.FieldAddress{Dest: fieldPtrID, Base: bufferPtr, Field: fmt.Sprintf("%d", i)})
value := g.GenerateExpr(element)
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(fieldPtrID, types.PointerType{Base: sliceType.Base}), Value: value})
}
elemPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: sliceType.Base})
g.Emit(ir.FieldAddress{Dest: elemPtrID, Base: bufferPtr, Field: "0"})
elemPtr = ir.ValueOperand(elemPtrID, types.PointerType{Base: sliceType.Base})
}
basePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: sliceType.Base})
g.Emit(ir.FieldAddress{Dest: basePtrID, Base: slicePtr, Field: "0"})
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(basePtrID, types.PointerType{Base: sliceType.Base}), Value: elemPtr})
lenPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PrimitiveUsz})
g.Emit(ir.FieldAddress{Dest: lenPtrID, Base: slicePtr, Field: "1"})
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(lenPtrID, types.PointerType{Base: types.PrimitiveUsz}), Value: ir.IntConstOperand(fmt.Sprintf("%d", len(node.Elements)), types.PrimitiveUsz)})
loaded := g.currentFunction.NewValueOfType(targetType)
g.Emit(ir.Load{Dest: loaded, Slot: tmpSlot})
return ir.ValueOperand(loaded, targetType)
}
func (g *Generator) generateArrayLiteral(node *parser.SliceLiteralNode, arrayType types.ArrayType) ir.Operand {
arraySlot := g.currentFunction.NewSlot(node.GetType(), "")
g.Emit(ir.Alloca{Slot: arraySlot})
arrayPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: node.GetType(), Mutable: true})
g.Emit(ir.AddressOf{Dest: arrayPtrID, Slot: arraySlot})
arrayPtr := ir.ValueOperand(arrayPtrID, types.PointerType{Base: node.GetType(), Mutable: true})
storeElement := func(index ir.Operand, value ir.Operand) {
elementPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: arrayType.Base, Mutable: true})
g.Emit(ir.ElementAddress{Dest: elementPtrID, Base: arrayPtr, Index: index, Element: arrayType.Base, ArrayObject: true})
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(elementPtrID, types.PointerType{Base: arrayType.Base, Mutable: true}), Value: value})
}
if node.RepeatValue == nil {
for i, element := range node.Elements {
storeElement(ir.IntConstOperand(strconv.Itoa(i), types.PrimitiveUsz), g.GenerateExpr(element))
}
} else if _, noInit := node.RepeatValue.(*parser.NoInitializerNode); !noInit {
value := g.GenerateExpr(node.RepeatValue)
for i := 0; i < arrayType.Length; i++ {
storeElement(ir.IntConstOperand(strconv.Itoa(i), types.PrimitiveUsz), value)
}
}
loaded := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.Load{Dest: loaded, Slot: arraySlot})
return ir.ValueOperand(loaded, node.GetType())
}
func (g *Generator) generateRepeatedSliceLiteral(node *parser.SliceLiteralNode, targetType types.Type, sliceType types.SliceType, tmpSlot ir.SlotID) ir.Operand {
count := g.GenerateExpr(node.RepeatAmount)
bufferID := g.currentFunction.NewValueOfType(types.PointerType{Base: sliceType.Base})
g.Emit(ir.AllocaArray{Dest: bufferID, Element: sliceType.Base, Count: count})
buffer := ir.ValueOperand(bufferID, types.PointerType{Base: sliceType.Base})
if _, noInit := node.RepeatValue.(*parser.NoInitializerNode); !noInit {
value := g.GenerateExpr(node.RepeatValue)
indexSlot := g.currentFunction.NewSlot(types.PrimitiveUsz, "repeat.index")
g.Emit(ir.Alloca{Slot: indexSlot})
g.Emit(ir.Store{Slot: indexSlot, Value: ir.IntConstOperand("0", types.PrimitiveUsz)})
conditionBlock := g.currentFunction.NewBlock("repeat.condition")
bodyBlock := g.currentFunction.NewBlock("repeat.body")
endBlock := g.currentFunction.NewBlock("repeat.end")
g.Emit(ir.Jump{Target: conditionBlock.ID})
g.currentBlock = conditionBlock
indexID := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.Load{Dest: indexID, Slot: indexSlot})
index := ir.ValueOperand(indexID, types.PrimitiveUsz)
conditionID := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpLt{Dest: conditionID, Left: index, Right: count})
g.Emit(ir.Branch{Cond: ir.ValueOperand(conditionID, types.PrimitiveBool), Then: bodyBlock.ID, Else: endBlock.ID})
g.currentBlock = bodyBlock
elementPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: sliceType.Base})
g.Emit(ir.ElementAddress{Dest: elementPtrID, Base: buffer, Index: index, Element: sliceType.Base})
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(elementPtrID, types.PointerType{Base: sliceType.Base}), Value: value})
nextID := g.currentFunction.NewValueOfType(types.PrimitiveUsz)
g.Emit(ir.Add{Dest: nextID, Left: index, Right: ir.IntConstOperand("1", types.PrimitiveUsz)})
g.Emit(ir.Store{Slot: indexSlot, Value: ir.ValueOperand(nextID, types.PrimitiveUsz)})
g.Emit(ir.Jump{Target: conditionBlock.ID})
g.currentBlock = endBlock
}
slicePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: targetType})
g.Emit(ir.AddressOf{Dest: slicePtrID, Slot: tmpSlot})
slicePtr := ir.ValueOperand(slicePtrID, types.PointerType{Base: targetType})
basePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PointerType{Base: sliceType.Base}})
g.Emit(ir.FieldAddress{Dest: basePtrID, Base: slicePtr, Field: "0"})
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(basePtrID, types.PointerType{Base: types.PointerType{Base: sliceType.Base}}), Value: buffer})
lenPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: types.PrimitiveUsz})
g.Emit(ir.FieldAddress{Dest: lenPtrID, Base: slicePtr, Field: "1"})
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(lenPtrID, types.PointerType{Base: types.PrimitiveUsz}), Value: count})
loaded := g.currentFunction.NewValueOfType(targetType)
g.Emit(ir.Load{Dest: loaded, Slot: tmpSlot})
return ir.ValueOperand(loaded, targetType)
}
func (g *Generator) generateStructLiteralIntoSlot(slot ir.SlotID, node *parser.StructLiteralNode) {
if _, ok := types.Underlying(node.GetType()).(types.FlagsType); ok {
g.Emit(ir.Store{Slot: slot, Value: g.generateStructLiteralExpr(node)})
return
}
if !node.NoInitRemaining || g.initializingGlobal {
g.Emit(ir.Store{Slot: slot, Value: ir.ZeroConstOperand(node.GetType())})
}
basePtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: node.GetType()})
g.Emit(ir.AddressOf{Dest: basePtrID, Slot: slot})
basePtr := ir.ValueOperand(basePtrID, types.PointerType{Base: node.GetType()})
for _, field := range node.Fields {
if _, noInit := field.R.(*parser.NoInitializerNode); noInit {
continue
}
fieldTy := node.GetType()
switch composite := types.Underlying(node.GetType()).(type) {
case types.StructType:
for _, f := range composite.Fields {
if f.L == field.L {
fieldTy = f.R
break
}
if f.L == "" {
if embedded, ok := f.R.(types.UnionType); ok {
for _, unionField := range embedded.Fields {
if unionField.L == field.L {
fieldTy = unionField.R
break
}
}
}
}
}
case types.UnionType:
for _, f := range composite.Fields {
if f.L == field.L {
fieldTy = f.R
break
}
}
}
fieldPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: fieldTy})
g.Emit(ir.FieldAddress{Dest: fieldPtrID, Base: basePtr, Field: field.L})
value := g.GenerateExpr(field.R)
g.Emit(ir.StorePtr{Ptr: ir.ValueOperand(fieldPtrID, types.PointerType{Base: fieldTy}), Value: value})
}
}
func (g *Generator) generateFieldAccessExpr(node *parser.FieldAccessNode) ir.Operand {
if node.ResolvedIdentifier != nil {
return g.generateIdentifierExpr(node.ResolvedIdentifier)
}
if node.MethodSymbol != nil {
ident := &parser.IdentifierNode{Name: node.MethodSymbol.Name, Symbol: node.MethodSymbol, Type: node.GetType(), Loc: node.Loc}
if node.MethodModule != g.ModuleName {
ident.Module, ident.ResolvedModuleName = node.MethodModule, node.MethodModule
}
return g.generateIdentifierExpr(ident)
}
if node.IsEnumValue {
return ir.IntConstOperand(node.EnumValue, node.GetType())
}
if node.IsFlagValue {
return ir.IntConstOperand(node.FlagValue, node.FlagType)
}
if node.IsFlagTest {
value := g.GenerateExpr(node.Subject)
mask := ir.IntConstOperand(node.FlagValue, node.FlagType)
masked := g.emitBinaryOperation(parser.BinaryOpBitwiseAnd, value, mask, node.FlagType)
dst := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpEq{Dest: dst, Left: masked, Right: mask})
return ir.ValueOperand(dst, types.PrimitiveBool)
}
basePtr := g.generateFieldSubjectAddress(node.Subject)
fieldPtrID := g.currentFunction.NewValueOfType(types.PointerType{Base: node.GetType()})
g.Emit(ir.FieldAddress{Dest: fieldPtrID, Base: basePtr, Field: node.Field.Name})
dst := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.LoadPtr{Dest: dst, Ptr: ir.ValueOperand(fieldPtrID, types.PointerType{Base: node.GetType()})})
return ir.ValueOperand(dst, node.GetType())
}
func (g *Generator) generateFieldSubjectAddress(subject parser.ExpressionNode) ir.Operand {
if _, ok := types.Underlying(subject.GetType()).(types.PointerType); ok {
return g.GenerateExpr(subject)
}
return g.generateAddressOfExpr(subject)
}
func (g *Generator) generateIfExpr(node *parser.IfNode) ir.Operand {
resultType := node.GetType()
fallsThrough := parser.NodeFallsThrough(node)
var tmpSlot ir.SlotID
if fallsThrough {
tmpSlot = g.currentFunction.NewSlot(resultType, "ifexpr.tmp")
g.Emit(ir.Alloca{Slot: tmpSlot})
}
mergeBlock := g.currentFunction.NewBlock("ifexpr.merge")
thenBlock := g.currentFunction.NewBlock("ifexpr.then")
elseTarget := mergeBlock
if len(node.ElseIfBranches) > 0 || node.ElseBranch != nil {
elseTarget = g.currentFunction.NewBlock("ifexpr.else")
}
cond := g.GenerateExpr(node.IfBranch.Condition)
condVal := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpNe{Dest: condVal, Left: cond, Right: ir.BoolConstOperand(false)})
g.Emit(ir.Branch{Cond: ir.ValueOperand(condVal, types.PrimitiveBool), Then: thenBlock.ID, Else: elseTarget.ID})
g.currentBlock = thenBlock
thenVal := g.GenerateExpr(node.IfBranch.Node)
if !g.currentBlockHasTerminator() {
g.Emit(ir.Store{Slot: tmpSlot, Value: thenVal})
g.Emit(ir.Jump{Target: mergeBlock.ID})
}
if elseTarget != mergeBlock {
g.currentBlock = elseTarget
for i, elif := range node.ElseIfBranches {
thenB := g.currentFunction.NewBlock(fmt.Sprintf("ifexpr.elseif.then.%d", i))
next := mergeBlock
if i < len(node.ElseIfBranches)-1 || node.ElseBranch != nil {
next = g.currentFunction.NewBlock(fmt.Sprintf("ifexpr.elseif.next.%d", i))
}
c := g.GenerateExpr(elif.Condition)
cval := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpNe{Dest: cval, Left: c, Right: ir.BoolConstOperand(false)})
g.Emit(ir.Branch{Cond: ir.ValueOperand(cval, types.PrimitiveBool), Then: thenB.ID, Else: next.ID})
g.currentBlock = thenB
v := g.GenerateExpr(elif.Node)
if !g.currentBlockHasTerminator() {
g.Emit(ir.Store{Slot: tmpSlot, Value: v})
g.Emit(ir.Jump{Target: mergeBlock.ID})
}
g.currentBlock = next
}
if node.ElseBranch != nil {
v := g.GenerateExpr(node.ElseBranch)
if !g.currentBlockHasTerminator() {
g.Emit(ir.Store{Slot: tmpSlot, Value: v})
g.Emit(ir.Jump{Target: mergeBlock.ID})
}
}
}
g.currentBlock = mergeBlock
if !fallsThrough {
g.Emit(ir.Unreachable{})
return ir.ZeroConstOperand(resultType)
}
dst := g.currentFunction.NewValueOfType(resultType)
g.Emit(ir.Load{Dest: dst, Slot: tmpSlot})
return ir.ValueOperand(dst, resultType)
}
func (g *Generator) generateAddressOfExpr(expr parser.ExpressionNode) ir.Operand {
switch node := expr.(type) {
case *parser.IdentifierNode:
ident := node
if ident.Symbol == nil {
panic("identifier symbol is nil")
}
if ident.Symbol.InlineComptime {
value := g.generateIdentifierExpr(ident)
slot := g.currentFunction.NewSlot(ident.GetType(), ident.Name+".materialized")
g.Emit(ir.Alloca{Slot: slot})
g.Emit(ir.Store{Slot: slot, Value: value})
addr := g.currentFunction.NewValueOfType(types.PointerType{Base: ident.GetType()})
g.Emit(ir.AddressOf{Dest: addr, Slot: slot})
return ir.ValueOperand(addr, types.PointerType{Base: ident.GetType()})
}
slot, ok := g.currentEnv.Lookup(ident.Symbol)
if !ok {
global := g.globalNameForIdentifier(ident)
addr := g.currentFunction.NewValueOfType(types.PointerType{Base: ident.GetType()})
g.Emit(ir.AddressOfGlobal{Dest: addr, Name: global, Type: ident.GetType()})
return ir.ValueOperand(addr, types.PointerType{Base: ident.GetType()})
}
addr := g.currentFunction.NewValueOfType(types.PointerType{Base: ident.GetType()})
g.Emit(ir.AddressOf{Dest: addr, Slot: slot})
return ir.ValueOperand(addr, types.PointerType{Base: ident.GetType()})
case *parser.FieldAccessNode:
if node.TaggedUnionType != nil {
break
}
if node.ResolvedIdentifier != nil {
return g.generateAddressOfExpr(node.ResolvedIdentifier)
}
if node.IsEnumValue || node.IsFlagValue || node.IsFlagTest || node.MethodSymbol != nil || node.ModulePath != "" {
break
}
base := g.generateFieldSubjectAddress(node.Subject)
dest := g.currentFunction.NewValueOfType(types.PointerType{Base: node.GetType()})
g.Emit(ir.FieldAddress{Dest: dest, Base: base, Field: node.Field.Name})
return ir.ValueOperand(dest, types.PointerType{Base: node.GetType()})
case *parser.IndexExprNode:
return g.generateIndexAddress(node)
case *parser.UnaryOpNode:
if node.Op == parser.UnaryOpDereference {
return g.GenerateExpr(node.Operand)
}
}
value := g.GenerateExpr(expr)
tmpSlot := g.currentFunction.NewSlot(expr.GetType(), "")
g.Emit(ir.Alloca{Slot: tmpSlot})
g.Emit(ir.Store{Slot: tmpSlot, Value: value})
addr := g.currentFunction.NewValueOfType(types.PointerType{Base: expr.GetType()})
g.Emit(ir.AddressOf{Dest: addr, Slot: tmpSlot})
return ir.ValueOperand(addr, types.PointerType{Base: expr.GetType()})
}
func (g *Generator) generateFunctionCallExpr(node *parser.FunctionCallNode) ir.Operand {
if node.TraitCall {
return g.generateTraitCall(node)
}
runtimeBuiltin := node.Symbol != nil && node.Symbol.DefinitionModule == "" &&
(node.Symbol.Name == "panic" || node.Symbol.Name == "assert")
var callee *ir.Operand
if node.Symbol == nil {
value := g.GenerateExpr(node.Callee)
callee = &value
}
args := make([]ir.Operand, 0, len(node.Args))
callTypedVariadicArity := len(node.Args) - node.TypedVariadicStart
typedVariadicWrapper := node.TypedVariadic && !node.VariadicExpansion && node.Symbol != nil &&
hasTypedVariadicSpecialization(node.Symbol.Signature, callTypedVariadicArity)
constants := map[int]symbols.SpecializationConstant(nil)
constantWrapper := false
if node.Symbol != nil && !runtimeBuiltin {
constants = selectedSpecializationConstants(node.Symbol.Signature)
constantWrapper = len(constants) != 0 && g.callMatchesSpecialization(node, constants)
if !node.Symbol.Signature.TypedVariadic && len(node.Args) != len(node.Symbol.Signature.Parameters) {
constantWrapper = false
}
}
typedVariadicWrapper = typedVariadicWrapper && (len(constants) == 0 || constantWrapper)
flattenedTypedVariadic := false
typedVariadicArity := len(node.Args) - node.TypedVariadicStart
if node.TypedVariadic && node.VariadicExpansion && node.Symbol != nil &&
len(node.Args) == node.TypedVariadicStart+1 {
if argument, ok := node.Args[node.TypedVariadicStart].(*parser.IdentifierNode); ok {
if elements, specialized := g.specializedVariadicElements[argument.Symbol]; specialized &&
hasTypedVariadicSpecialization(node.Symbol.Signature, len(elements)) &&
(len(constants) == 0 || constantWrapper) {
typedVariadicWrapper = true
flattenedTypedVariadic = true
typedVariadicArity = len(elements)
for i, arg := range node.Args[:node.TypedVariadicStart] {
if _, specialized := constants[i]; !specialized {
args = append(args, g.GenerateExpr(arg))
}
}
args = append(args, elements...)
}
}
}
if typedVariadicWrapper && !flattenedTypedVariadic {
for i, arg := range node.Args {
if i < node.TypedVariadicStart {
if _, specialized := constants[i]; specialized {
continue
}
}
args = append(args, g.GenerateExpr(arg))
}
} else if !typedVariadicWrapper && node.TypedVariadic {
for _, arg := range node.Args[:node.TypedVariadicStart] {
args = append(args, g.GenerateExpr(arg))
}
if node.VariadicExpansion {
args = append(args, g.GenerateExpr(node.Args[node.TypedVariadicStart]))
} else {
sliceType := node.TypedVariadicSlice
packed := &parser.SliceLiteralNode{Elements: append([]parser.ExpressionNode(nil), node.Args[node.TypedVariadicStart:]...), Type: sliceType, Loc: node.Loc}
args = append(args, g.generateSliceLiteralExpr(packed))
}
} else {
for i, arg := range node.Args {
if constantWrapper {
if _, specialized := constants[i]; specialized {
continue
}
}
args = append(args, g.GenerateExpr(arg))
}
}
callSig := g.buildCallSignature(node)
args = g.promoteVariadicArgs(args, callSig)
name := ""
if node.Name != nil {
name = node.Name.String()
}
if node.Symbol != nil {
name = node.Symbol.Name
if runtimeBuiltin {
name = "__qk_" + node.Symbol.Name
}
foreignAttr := node.Symbol.Attributes.Get(attributes.AttributeTypeForeign)
if foreign, ok := foreignAttr.(attributes.FunctionAttributeForeign); ok &&
node.Name != nil && node.Name.Module != "" {
g.addExternForCall(node.Symbol.Name, callSig, foreign.From, false)
}
if export, ok := node.Symbol.Attributes.Get(attributes.AttributeTypeExport).(attributes.FunctionAttributeExport); ok {
name = export.As
} else if foreignAttr == nil && !runtimeBuiltin {
callModule := g.ModuleName
if node.Symbol.DefinitionModule != "" {
callModule = node.Symbol.DefinitionModule
}
if node.Name != nil && node.Name.Module != "" {
callModule = node.Name.Module
if node.Name.ResolvedModuleName != "" {
callModule = node.Name.ResolvedModuleName
}
}
name = g.mangleFunctionName(callModule, node.Symbol.Name)
if callModule != g.ModuleName {
hidden := node.Symbol.Attributes.Get(attributes.AttributeTypeExport) == nil
g.addExternForCall(name, callSig, "", hidden)
}
}
if !node.Symbol.Signature.TypedVariadic && len(node.Args) < len(node.Symbol.Signature.Parameters) {
name = g.defaultWrapperName(name, len(node.Args))
callSig.ParamTypes = append([]types.Type(nil), node.Symbol.Signature.Parameters[:len(node.Args)]...)
callSig.Variadic = false
callSig.Attributes = nil
if node.Symbol.DefinitionModule != "" && node.Symbol.DefinitionModule != g.ModuleName ||
node.Name != nil && node.Name.Module != "" {
hidden := node.Symbol.Attributes.Get(attributes.AttributeTypeExport) == nil
g.addExternForCall(name, callSig, "", hidden)
}
}
if typedVariadicWrapper {
name = g.typedVariadicWrapperName(name, typedVariadicArity)
fixedTypes := make([]types.Type, 0, node.TypedVariadicStart)
for i, parameter := range callSig.ParamTypes[:node.TypedVariadicStart] {
if _, specialized := constants[i]; !specialized {
fixedTypes = append(fixedTypes, parameter)
}
}
callSig.ParamTypes = fixedTypes
for range typedVariadicArity {
callSig.ParamTypes = append(callSig.ParamTypes, node.Symbol.Signature.VariadicElement)
}
name = g.specializedFunctionName(name, constants)
callSig.Variadic = false
callSig.Attributes = nil
if node.Symbol.DefinitionModule != "" && node.Symbol.DefinitionModule != g.ModuleName ||
node.Name != nil && node.Name.Module != "" {
hidden := node.Symbol.Attributes.Get(attributes.AttributeTypeExport) == nil
g.addExternForCall(name, callSig, "", hidden)
}
} else if constantWrapper {
name = g.specializedFunctionName(name, constants)
paramTypes := make([]types.Type, 0, len(callSig.ParamTypes)-len(constants))
for i, parameter := range callSig.ParamTypes {
if _, specialized := constants[i]; !specialized {
paramTypes = append(paramTypes, parameter)
}
}
callSig.ParamTypes = paramTypes
callSig.Variadic = false
callSig.Attributes = nil
if node.Symbol.DefinitionModule != "" && node.Symbol.DefinitionModule != g.ModuleName ||
node.Name != nil && node.Name.Module != "" {
hidden := node.Symbol.Attributes.Get(attributes.AttributeTypeExport) == nil
g.addExternForCall(name, callSig, "", hidden)
}
}
}
if runtimeBuiltin {
g.addExternForCall("__qk_"+node.Symbol.Name, callSig, "", true)
}
var genericReference *ir.GenericReference
var requirementReference *ir.TraitRequirementReference
genericSymbol := node.Symbol
if genericSymbol == nil {
if member, ok := node.Callee.(*parser.FieldAccessNode); ok {
genericSymbol = member.MethodSymbol
}
}
if genericSymbol != nil && genericSymbol.TemplateSymbol != nil {
template := genericSymbol.TemplateSymbol
genericReference = &ir.GenericReference{
Module: template.DefinitionModule, Name: template.Name,
TypeArguments: append([]types.Type(nil), genericSymbol.TypeArguments...),
}
} else if genericSymbol != nil && genericSymbol.Template {
genericReference = &ir.GenericReference{Module: genericSymbol.DefinitionModule, Name: genericSymbol.Name}
for _, parameter := range genericSymbol.GenericParameters {
genericReference.TypeArguments = append(genericReference.TypeArguments, parameter)
}
}
if genericSymbol != nil && genericSymbol.TraitRequirement && len(genericSymbol.Signature.Parameters) != 0 {
requirementReference = &ir.TraitRequirementReference{
Name: genericSymbol.Name, ReceiverType: genericSymbol.Signature.Parameters[0],
}
}
if node.GetType().Equals(types.PrimitiveVoid) {
g.Emit(ir.Call{
Name: name,
Callee: callee,
Args: args,
Signature: callSig,
Generic: genericReference,
Requirement: requirementReference,
SourceName: sourceCallName(node.Callee),
SourceLoc: sourceCallLoc(node),
})
if node.Symbol != nil && node.Symbol.Name == "panic" {
g.Emit(ir.Unreachable{})
}
return ir.NullConstOperand(types.PrimitiveVoid)
}
dst := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.Call{
Dest: dst, Name: name, Callee: callee, Args: args, Signature: callSig,
Generic: genericReference, Requirement: requirementReference,
SourceName: sourceCallName(node.Callee), SourceLoc: sourceCallLoc(node),
})
return ir.ValueOperand(dst, node.GetType())
}
func (g *Generator) generateSourceFunctionCall(node *parser.FunctionCallNode) ir.Operand {
previous := g.currentSourceOrigin
origin := ir.SourceOrigin{Name: sourceCallName(node.Callee), Loc: sourceCallLoc(node)}
g.currentSourceOrigin = &origin
value := g.generateFunctionCallExpr(node)
g.currentSourceOrigin = previous
return value
}
func sourceCallName(callee parser.ExpressionNode) string {
loc := callee.GetLoc()
source := []rune(loc.SourceText)
if len(source) != 0 && loc.Offset >= 0 && loc.EndOffset >= loc.Offset && loc.EndOffset <= len(source) {
return string(source[loc.Offset:loc.EndOffset])
}
if identifier, ok := callee.(*parser.IdentifierNode); ok {
return identifier.Name
}
return "call"
}
func sourceCallLoc(call *parser.FunctionCallNode) shared.Location {
loc := call.Callee.GetLoc()
if loc.FilePath != "" {
return loc
}
return call.Loc
}
func (g *Generator) callMatchesSpecialization(node *parser.FunctionCallNode, constants map[int]symbols.SpecializationConstant) bool {
for index, expected := range constants {
if index >= len(node.Args) {
return false
}
actual, ok := specializationConstantExpr(node.Args[index])
if !ok {
identifier, identifierOK := node.Args[index].(*parser.IdentifierNode)
if !identifierOK || identifier.Symbol == nil {
return false
}
actual, ok = g.specializedConstants[identifier.Symbol]
}
if !ok || actual.Key() != expected.Key() {
return false
}
}
return true
}
func specializationConstantExpr(expr parser.ExpressionNode) (symbols.SpecializationConstant, bool) {
switch node := expr.(type) {
case *parser.StringLiteralNode:
return symbols.SpecializationConstant{Kind: "str", Value: node.Value, Type: node.GetType()}, true
case *parser.CStringLiteralNode:
return symbols.SpecializationConstant{Kind: "cstr", Value: node.Value, Type: node.GetType()}, true
case *parser.BoolLiteralNode:
return symbols.SpecializationConstant{Kind: "bool", Value: node.Value, Type: node.GetType()}, true
case *parser.IntegerLiteralNode:
return symbols.SpecializationConstant{Kind: "int", Value: node.Value, Type: node.GetType()}, true
case *parser.FloatLiteralNode:
return symbols.SpecializationConstant{Kind: "float", Value: node.Value, Type: node.GetType()}, true
case *parser.CharLiteralNode:
return symbols.SpecializationConstant{Kind: "char", Value: strconv.Itoa(int(node.Value)), Type: node.GetType()}, true
default:
return symbols.SpecializationConstant{}, false
}
}
func (g *Generator) generateTraitCall(node *parser.FunctionCallNode) ir.Operand {
receiver := g.GenerateExpr(node.Args[0])
traitPtr := node.Symbol.Signature.Parameters[0].(types.TraitPointerType)
vtType := traitVTableType(traitPtr.Trait)
vtPtrType := types.PointerType{Base: vtType}
vtID := g.currentFunction.NewValueOfType(vtPtrType)
g.Emit(ir.ExtractValue{Dest: vtID, Aggregate: receiver, Index: 1})
requirement := traitPtr.Trait.Methods[node.TraitSlot]
params := append([]types.Type{traitErasedReceiverType(requirement)}, requirement.Parameters...)
fnType := types.PointerType{Base: types.FunctionType{Parameters: params, ReturnType: requirement.ReturnType}}
fnPtrPtr := g.currentFunction.NewValueOfType(types.PointerType{Base: fnType})
g.Emit(ir.FieldAddress{Dest: fnPtrPtr, Base: ir.ValueOperand(vtID, vtPtrType), Field: requirement.Name})
fnID := g.currentFunction.NewValueOfType(fnType)
g.Emit(ir.LoadPtr{Dest: fnID, Ptr: ir.ValueOperand(fnPtrPtr, types.PointerType{Base: fnType})})
dataType := params[0]
dataID := g.currentFunction.NewValueOfType(dataType)
g.Emit(ir.ExtractValue{Dest: dataID, Aggregate: receiver, Index: 0})
args := []ir.Operand{ir.ValueOperand(dataID, dataType)}
for _, arg := range node.Args[1:] {
args = append(args, g.GenerateExpr(arg))
}
sig := ir.FunctionSignature{ParamTypes: params, ReturnType: requirement.ReturnType}
callee := ir.ValueOperand(fnID, fnType)
if requirement.ReturnType.Equals(types.PrimitiveVoid) {
g.Emit(ir.Call{Callee: &callee, Args: args, Signature: sig})
return ir.NullConstOperand(types.PrimitiveVoid)
}
dst := g.currentFunction.NewValueOfType(requirement.ReturnType)
g.Emit(ir.Call{Dest: dst, Callee: &callee, Args: args, Signature: sig})
return ir.ValueOperand(dst, requirement.ReturnType)
}
func (g *Generator) addExternForCall(name string, signature ir.FunctionSignature, from string, hidden bool) {
for _, extern := range g.Module.Externs {
if extern.Name == name {
return
}
}
visibility := ir.VisibilityDefault
if hidden {
visibility = ir.VisibilityHidden
}
g.Module.AddExtern(ir.ExternDecl{Name: name, Signature: signature, From: from, Visibility: visibility})
}
func (g *Generator) promoteVariadicArgs(args []ir.Operand, sig ir.FunctionSignature) []ir.Operand {
if !sig.Variadic {
return args
}
for i := len(sig.ParamTypes); i < len(args); i++ {
if !args[i].Type.Equals(types.PrimitiveF32) {
continue
}
dst := g.currentFunction.NewValueOfType(types.PrimitiveF64)
g.Emit(ir.Cast{Dest: dst, From: args[i], To: types.PrimitiveF64})
args[i] = ir.ValueOperand(dst, types.PrimitiveF64)
}
return args
}
func (g *Generator) mangleFunctionName(moduleName, fnName string) string {
return MangleFunctionName(moduleName, fnName)
}
func MangleFunctionName(moduleName, fnName string) string {
mod := encodeModuleName(moduleName)
fn := sanitizeName(fnName)
if mod == "" {
return "__qk_" + fn
}
return "__qk_" + mod + "_" + fn
}
func (g *Generator) mangleGlobalName(moduleName, globalName string) string {
mod := encodeModuleName(moduleName)
global := sanitizeName(globalName)
if mod == "" {
return "__qk_global_" + global
}
return "__qk_" + mod + "_global_" + global
}
func encodeModuleName(name string) string {
if name == "" {
return ""
}
if !strings.Contains(name, ".") {
return sanitizeName(name)
}
var b strings.Builder
b.WriteString("0_")
for _, component := range strings.Split(name, ".") {
fmt.Fprintf(&b, "%d_%s", len(component), component)
}
return b.String()
}
func (g *Generator) isProgramEntryFunction(fnName string) bool {
return g.ModuleName == g.MainModule && fnName == "main"
}
func sanitizeName(name string) string {
if name == "" {
return ""
}
var b strings.Builder
b.Grow(len(name))
for i, r := range name {
valid := (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || r == '_' || (r >= '0' && r <= '9')
if !valid {
b.WriteByte('_')
continue
}
if i == 0 && r >= '0' && r <= '9' {
b.WriteByte('_')
}
b.WriteRune(r)
}
return b.String()
}
func (g *Generator) buildCallSignature(node *parser.FunctionCallNode) ir.FunctionSignature {
sig := ir.FunctionSignature{ReturnType: node.GetType()}
for _, arg := range node.Args {
sig.ParamTypes = append(sig.ParamTypes, arg.GetType())
}
if node.Symbol != nil && node.Symbol.Signature != nil {
sig.ParamTypes = append([]types.Type(nil), node.Symbol.Signature.Parameters...)
sig.ReturnType = node.Symbol.Signature.ReturnType
sig.Variadic = node.Symbol.Signature.Variadic
sig.Attributes = node.Symbol.Attributes
}
if node.Symbol == nil {
ptr := types.Underlying(node.Callee.GetType()).(types.PointerType)
fn := types.Underlying(ptr.Base).(types.FunctionType)
sig.ParamTypes = append([]types.Type(nil), fn.Parameters...)
sig.ReturnType = fn.ReturnType
}
return sig
}
func (g *Generator) generateIdentifierExpr(node *parser.IdentifierNode) ir.Operand {
if node.Symbol == nil {
panic("identifier symbol is nil")
}
if node.Symbol.InlineComptime {
if types.HasUntyped(node.GetType()) {
panic("untyped compile-time integer reached IR generation")
}
if types.Underlying(node.GetType()).Equals(types.PrimitiveBool) {
return ir.BoolConstOperand(node.Symbol.ComptimeInteger == "1")
}
if types.IsFloat(node.GetType()) {
return ir.FloatConstOperand(node.Symbol.ComptimeInteger, node.GetType())
}
return ir.IntConstOperand(node.Symbol.ComptimeInteger, node.GetType())
}
if node.Symbol.Kind == symbols.SymbolKindFunction {
name, sig := g.functionValueName(node)
if node.Module != "" {
from := ""
if foreign, ok := node.Symbol.Attributes.Get(attributes.AttributeTypeForeign).(attributes.FunctionAttributeForeign); ok {
from = foreign.From
}
hidden := node.Symbol.Attributes.Get(attributes.AttributeTypeForeign) == nil &&
node.Symbol.Attributes.Get(attributes.AttributeTypeExport) == nil
g.addExternForCall(name, sig, from, hidden)
}
return ir.FunctionConstOperand(name, node.GetType())
}
if node.Module != "" {
return g.generateModuleIdentifierExpr(node)
}
slot, ok := g.currentEnv.Lookup(node.Symbol)
if !ok {
global, exists := g.globals[node.Symbol]
if !exists {
panic("identifier slot not found")
}
dst := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.LoadGlobal{Dest: dst, Name: global, Type: node.GetType()})
return ir.ValueOperand(dst, node.GetType())
}
dst := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.Load{Dest: dst, Slot: slot})
return ir.ValueOperand(dst, node.GetType())
}
func (g *Generator) generateSpecializationConstant(constant symbols.SpecializationConstant) ir.Operand {
switch constant.Kind {
case "str":
return g.generateStringLiteralExpr(&parser.StringLiteralNode{Value: constant.Value, Type: constant.Type})
case "cstr":
return g.generateCStringLiteralExpr(&parser.CStringLiteralNode{Value: constant.Value, Type: constant.Type})
case "bool":
return ir.BoolConstOperand(constant.Value == string(tokeniser.KeywordTrue))
case "float":
return ir.FloatConstOperand(constant.Value, constant.Type)
case "int", "char":
if types.IsFloat(constant.Type) {
return ir.FloatConstOperand(constant.Value, constant.Type)
}
return ir.IntConstOperand(constant.Value, constant.Type)
default:
panic("unsupported specialization constant")
}
}
func (g *Generator) functionValueName(node *parser.IdentifierNode) (string, ir.FunctionSignature) {
sym := node.Symbol
ret := sym.Signature.ReturnType
if ret == nil {
ret = types.PrimitiveVoid
}
sig := ir.FunctionSignature{ParamTypes: append([]types.Type(nil), sym.Signature.Parameters...), ReturnType: ret, Variadic: sym.Signature.Variadic, Attributes: sym.Attributes}
name := sym.Name
if export, ok := sym.Attributes.Get(attributes.AttributeTypeExport).(attributes.FunctionAttributeExport); ok {
name = export.As
} else if foreign := sym.Attributes.Get(attributes.AttributeTypeForeign); foreign == nil {
module := g.ModuleName
if node.Module != "" {
module = node.Module
}
if node.ResolvedModuleName != "" {
module = node.ResolvedModuleName
}
name = g.mangleFunctionName(module, sym.Name)
}
return name, sig
}
func (g *Generator) generateModuleIdentifierExpr(node *parser.IdentifierNode) ir.Operand {
if node.Symbol == nil || node.Symbol.Kind != symbols.SymbolKindVariable {
panic("module access is not a variable")
}
name := g.globalNameForIdentifier(node)
dst := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.LoadGlobal{
Dest: dst,
Name: name,
Type: node.GetType(),
})
return ir.ValueOperand(dst, node.GetType())
}
func (g *Generator) globalNameForIdentifier(node *parser.IdentifierNode) string {
if node.Symbol == nil {
panic("identifier symbol is nil")
}
if node.Module == "" {
if name, ok := g.globals[node.Symbol]; ok {
return name
}
panic("identifier slot not found")
}
moduleName := node.Module
if node.ResolvedModuleName != "" {
moduleName = node.ResolvedModuleName
}
name := g.mangleGlobalName(moduleName, node.Symbol.Name)
if foreign, ok := node.Symbol.Attributes.Get(attributes.AttributeTypeForeign).(attributes.FunctionAttributeForeign); ok {
name = foreign.From
}
visibility := ir.VisibilityHidden
if node.Symbol.Attributes.Get(attributes.AttributeTypeForeign) != nil {
visibility = ir.VisibilityDefault
}
g.Module.AddExternGlobal(ir.ExternGlobal{Name: name, Type: node.GetType(), Mutable: node.Symbol.Mutable, Visibility: visibility})
return name
}
func (g *Generator) generateBinaryExpr(node *parser.BinaryOpNode) ir.Operand {
if node.Op == parser.BinaryOpLogicalAnd || node.Op == parser.BinaryOpLogicalOr {
return g.generateShortCircuitExpr(node)
}
if node.Op == parser.BinaryOpEqual || node.Op == parser.BinaryOpNotEqual {
if isNilTraitComparison(node.Operand1, node.Operand2) {
return g.generateTraitNilComparison(node, node.Operand2)
}
if isNilTraitComparison(node.Operand2, node.Operand1) {
return g.generateTraitNilComparison(node, node.Operand1)
}
}
leftPtr, leftIsPtr := types.Underlying(node.Operand1.GetType()).(types.PointerType)
_, rightIsPtr := types.Underlying(node.Operand2.GetType()).(types.PointerType)
if node.Op == parser.BinaryOpAdd && (leftIsPtr || rightIsPtr) {
return g.generatePointerOffset(node, false)
}
if node.Op == parser.BinaryOpSubtract && leftIsPtr && rightIsPtr {
return g.generatePointerDifference(node, leftPtr.Base)
}
if node.Op == parser.BinaryOpSubtract && leftIsPtr {
return g.generatePointerOffset(node, true)
}
left := g.GenerateExpr(node.Operand1)
right := g.GenerateExpr(node.Operand2)
return g.emitBinaryOperation(node.Op, left, right, node.GetType())
}
func isNilTraitComparison(nilNode, traitNode parser.ExpressionNode) bool {
if _, nilLiteral := nilNode.(*parser.NilLiteralNode); !nilLiteral {
return false
}
_, traitPointer := types.Underlying(traitNode.GetType()).(types.TraitPointerType)
return traitPointer
}
func (g *Generator) generateTraitNilComparison(node *parser.BinaryOpNode, traitNode parser.ExpressionNode) ir.Operand {
traitType := types.Underlying(traitNode.GetType()).(types.TraitPointerType)
traitValue := g.GenerateExpr(traitNode)
dataType := types.PointerType{Base: types.PrimitiveVoid, Mutable: traitType.Mutable}
dataID := g.currentFunction.NewValueOfType(dataType)
g.Emit(ir.ExtractValue{Dest: dataID, Aggregate: traitValue, Index: 0})
return g.emitBinaryOperation(
node.Op,
ir.ValueOperand(dataID, dataType),
ir.NullConstOperand(dataType),
node.GetType(),
)
}
func (g *Generator) generatePointerOffset(node *parser.BinaryOpNode, subtract bool) ir.Operand {
pointerNode, offsetNode := node.Operand1, node.Operand2
if _, leftIsPtr := types.Underlying(pointerNode.GetType()).(types.PointerType); !leftIsPtr {
pointerNode, offsetNode = offsetNode, pointerNode
}
pointer := g.GenerateExpr(pointerNode)
offset := g.GenerateExpr(offsetNode)
if subtract {
negated := g.currentFunction.NewValueOfType(offset.Type)
g.Emit(ir.Negate{Dest: negated, Operand: offset})
offset = ir.ValueOperand(negated, offset.Type)
}
pointerType := types.Underlying(pointer.Type).(types.PointerType)
dest := g.currentFunction.NewValueOfType(node.GetType())
g.Emit(ir.ElementAddress{Dest: dest, Base: pointer, Index: offset, Element: pointerType.Base})
return ir.ValueOperand(dest, node.GetType())
}
func (g *Generator) generatePointerDifference(node *parser.BinaryOpNode, elementType types.Type) ir.Operand {
left := g.GenerateExpr(node.Operand1)
right := g.GenerateExpr(node.Operand2)
leftIntID := g.currentFunction.NewValueOfType(types.PrimitiveIsz)
g.Emit(ir.Cast{Dest: leftIntID, From: left, To: types.PrimitiveIsz})
rightIntID := g.currentFunction.NewValueOfType(types.PrimitiveIsz)
g.Emit(ir.Cast{Dest: rightIntID, From: right, To: types.PrimitiveIsz})
bytesID := g.currentFunction.NewValueOfType(types.PrimitiveIsz)
g.Emit(ir.Sub{
Dest: bytesID,
Left: ir.ValueOperand(leftIntID, types.PrimitiveIsz),
Right: ir.ValueOperand(rightIntID, types.PrimitiveIsz),
})
sizeID := g.currentFunction.NewValueOfType(types.PrimitiveIsz)
g.Emit(ir.Sizeof{Dest: sizeID, Type: elementType})
elementsID := g.currentFunction.NewValueOfType(types.PrimitiveIsz)
g.Emit(ir.Div{
Dest: elementsID,
Left: ir.ValueOperand(bytesID, types.PrimitiveIsz),
Right: ir.ValueOperand(sizeID, types.PrimitiveIsz),
})
return ir.ValueOperand(elementsID, types.PrimitiveIsz)
}
func (g *Generator) emitBinaryOperation(op parser.BinaryOpKind, left, right ir.Operand, resultType types.Type) ir.Operand {
dst := g.currentFunction.NewValueOfType(resultType)
switch op {
case parser.BinaryOpAdd:
g.Emit(ir.Add{Dest: dst, Left: left, Right: right})
case parser.BinaryOpSubtract:
g.Emit(ir.Sub{Dest: dst, Left: left, Right: right})
case parser.BinaryOpMultiply:
g.Emit(ir.Mul{Dest: dst, Left: left, Right: right})
case parser.BinaryOpDivide:
if types.IsInteger(resultType) {
g.emitIntegerZeroCheck(right, "integer division by zero")
}
g.Emit(ir.Div{Dest: dst, Left: left, Right: right})
case parser.BinaryOpEqual:
g.Emit(ir.CmpEq{Dest: dst, Left: left, Right: right})
case parser.BinaryOpNotEqual:
g.Emit(ir.CmpNe{Dest: dst, Left: left, Right: right})
case parser.BinaryOpLess:
g.Emit(ir.CmpLt{Dest: dst, Left: left, Right: right})
case parser.BinaryOpLessEqual:
g.Emit(ir.CmpLe{Dest: dst, Left: left, Right: right})
case parser.BinaryOpGreater:
g.Emit(ir.CmpGt{Dest: dst, Left: left, Right: right})
case parser.BinaryOpGreaterEqual:
g.Emit(ir.CmpGe{Dest: dst, Left: left, Right: right})
case parser.BinaryOpModulo:
if types.IsInteger(resultType) {
g.emitIntegerZeroCheck(right, "integer modulo by zero")
}
g.Emit(ir.Mod{Dest: dst, Left: left, Right: right})
case parser.BinaryOpLogicalAnd:
g.Emit(ir.LogicalAnd{Dest: dst, Left: left, Right: right})
case parser.BinaryOpLogicalOr:
g.Emit(ir.LogicalOr{Dest: dst, Left: left, Right: right})
case parser.BinaryOpBitwiseAnd:
g.Emit(ir.BitwiseAnd{Dest: dst, Left: left, Right: right})
case parser.BinaryOpBitwiseOr:
g.Emit(ir.BitwiseOr{Dest: dst, Left: left, Right: right})
case parser.BinaryOpBitwiseXor:
g.Emit(ir.BitwiseXor{Dest: dst, Left: left, Right: right})
case parser.BinaryOpShiftLeft:
g.Emit(ir.ShiftLeft{Dest: dst, Left: left, Right: right})
case parser.BinaryOpShiftRight:
g.Emit(ir.ShiftRight{Dest: dst, Left: left, Right: right})
default:
panic(fmt.Sprintf("todo: generate binary expr for op %s", op))
}
return ir.ValueOperand(dst, resultType)
}
func (g *Generator) emitIntegerZeroCheck(divisor ir.Operand, message string) {
isZero := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpEq{Dest: isZero, Left: divisor, Right: ir.IntConstOperand("0", divisor.Type)})
panicBlock := g.currentFunction.NewBlock("integer.zero.panic")
validBlock := g.currentFunction.NewBlock("integer.zero.valid")
g.Emit(ir.Branch{Cond: ir.ValueOperand(isZero, types.PrimitiveBool), Then: panicBlock.ID, Else: validBlock.ID})
g.currentBlock = panicBlock
g.emitRuntimePanic(message)
g.currentBlock = validBlock
}
func (g *Generator) generateShortCircuitExpr(node *parser.BinaryOpNode) ir.Operand {
resultSlot := g.currentFunction.NewSlot(types.PrimitiveBool, "short.circuit.result")
g.Emit(ir.Alloca{Slot: resultSlot})
left := g.GenerateExpr(node.Operand1)
rightBlock := g.currentFunction.NewBlock("short.circuit.right")
shortBlock := g.currentFunction.NewBlock("short.circuit.skip")
mergeBlock := g.currentFunction.NewBlock("short.circuit.merge")
if node.Op == parser.BinaryOpLogicalAnd {
g.Emit(ir.Branch{Cond: left, Then: rightBlock.ID, Else: shortBlock.ID})
} else {
g.Emit(ir.Branch{Cond: left, Then: shortBlock.ID, Else: rightBlock.ID})
}
g.currentBlock = shortBlock
shortValue := node.Op == parser.BinaryOpLogicalOr
g.Emit(ir.Store{Slot: resultSlot, Value: ir.BoolConstOperand(shortValue)})
g.Emit(ir.Jump{Target: mergeBlock.ID})
g.currentBlock = rightBlock
right := g.GenerateExpr(node.Operand2)
if !g.currentBlockHasTerminator() {
g.Emit(ir.Store{Slot: resultSlot, Value: right})
g.Emit(ir.Jump{Target: mergeBlock.ID})
}
g.currentBlock = mergeBlock
result := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.Load{Dest: result, Slot: resultSlot})
return ir.ValueOperand(result, types.PrimitiveBool)
}
func (g *Generator) generateUnaryExpr(node *parser.UnaryOpNode) ir.Operand {
dst := g.currentFunction.NewValueOfType(node.GetType())
switch node.Op {
case parser.UnaryOpNegate:
operand := g.GenerateExpr(node.Operand)
g.Emit(ir.Negate{Dest: dst, Operand: operand})
case parser.UnaryOpLogicalNot:
operand := g.GenerateExpr(node.Operand)
g.Emit(ir.LogicalNot{Dest: dst, Operand: operand})
case parser.UnaryOpBitwiseNot:
operand := g.GenerateExpr(node.Operand)
g.Emit(ir.BitwiseNot{Dest: dst, Operand: operand})
case parser.UnaryOpReference:
fallthrough
case parser.UnaryOpMutableReference:
return g.generateAddressOfExpr(node.Operand)
case parser.UnaryOpDereference:
operand := g.GenerateExpr(node.Operand)
g.Emit(ir.LoadPtr{Dest: dst, Ptr: operand})
case parser.UnaryOpSliceLen:
if array, ok := types.Underlying(node.Operand.GetType()).(types.ArrayType); ok {
return ir.IntConstOperand(strconv.Itoa(array.Length), node.GetType())
}
operand := g.generateAddressOfExpr(node.Operand)
lenPtr := g.currentFunction.NewValueOfType(types.PointerType{Base: node.GetType()})
g.Emit(ir.FieldAddress{Dest: lenPtr, Base: operand, Field: "1"})
g.Emit(ir.LoadPtr{Dest: dst, Ptr: ir.ValueOperand(lenPtr, types.PointerType{Base: node.GetType()})})
default:
panic("todo")
}
return ir.ValueOperand(dst, node.GetType())
}
func (g *Generator) generateIf(node *parser.IfNode) {
mergeBlock := g.currentFunction.NewBlock("if.merge")
thenBlock := g.currentFunction.NewBlock("if.then")
elseTarget := mergeBlock
if len(node.ElseIfBranches) > 0 || node.ElseBranch != nil {
elseTarget = g.currentFunction.NewBlock("if.else")
}
cond := g.GenerateExpr(node.IfBranch.Condition)
condVal := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpNe{
Dest: condVal,
Left: cond,
Right: ir.BoolConstOperand(false),
})
g.Emit(ir.Branch{
Cond: ir.ValueOperand(condVal, types.PrimitiveBool),
Then: thenBlock.ID,
Else: elseTarget.ID,
})
g.currentBlock = thenBlock
g.generateBlock(node.IfBranch.Node)
if !g.currentBlockHasTerminator() {
g.Emit(ir.Jump{Target: mergeBlock.ID})
}
g.generateElseChain(node, elseTarget, mergeBlock)
g.currentBlock = mergeBlock
}
func (g *Generator) generateElseChain(node *parser.IfNode, startElse *ir.Block, mergeBlock *ir.Block) {
if startElse == mergeBlock {
return
}
g.currentBlock = startElse
for i, elif := range node.ElseIfBranches {
thenBlock := g.currentFunction.NewBlock(fmt.Sprintf("if.elseif.then.%d", i))
next := mergeBlock
if i < len(node.ElseIfBranches)-1 || node.ElseBranch != nil {
next = g.currentFunction.NewBlock(fmt.Sprintf("if.elseif.next.%d", i))
}
cond := g.GenerateExpr(elif.Condition)
condVal := g.currentFunction.NewValueOfType(types.PrimitiveBool)
g.Emit(ir.CmpNe{
Dest: condVal,
Left: cond,
Right: ir.BoolConstOperand(false),
})
g.Emit(ir.Branch{
Cond: ir.ValueOperand(condVal, types.PrimitiveBool),
Then: thenBlock.ID,
Else: next.ID,
})
g.currentBlock = thenBlock
g.generateBlock(elif.Node)
if !g.currentBlockHasTerminator() {
g.Emit(ir.Jump{Target: mergeBlock.ID})
}
g.currentBlock = next
}
if node.ElseBranch != nil {
g.generateBlock(node.ElseBranch)
if !g.currentBlockHasTerminator() {
g.Emit(ir.Jump{Target: mergeBlock.ID})
}
}
}