package emit import ( "git.smesh.lol/moxie/pkg/syntax" "git.smesh.lol/moxie/pkg/ssa" "git.smesh.lol/moxie/pkg/types" ) func (e *irEmitter) emitFunction(f *ssa.SSAFunction) { e.w("; [emit] " | f.SSAName() | "\n") if len(f.Blocks) == 0 { linkName := f.SSAName() if f.Pkg != nil && f.Pkg.Pkg != nil { linkName = f.Pkg.Pkg.Path() | "." | f.SSAName() } alias := e.asmAlias(linkName) if alias != "" { e.emitAsmAlias(f, alias) return } e.emitFuncDecl(f) return } e.curFunc = f e.nextReg = 0 e.deferList = nil e.deferID = 0 e.deallocActive = false e.deallocAllocs = nil e.deallocCount = 0 e.scopeActive = false e.scopeAllocs = map[int32][]string{} e.instrScope = map[ssa.SSAInstruction]int32{} e.valName = map[ssa.SSAValue]string{} e.nameUsed = map[string]bool{} e.allocTypes = map[ssa.SSAValue]string{} e.regTypes = map[string]string{} e.blockExitLabel = map[int32]string{} usedNames := map[string]int32{} for i, p := range f.Params { pname := p.SSAName() if pname == "" { pname = "p" | irItoa(i) } if cnt, ok := usedNames[pname]; ok { pname = pname | "." | irItoa(cnt) } usedNames[p.SSAName()]++ e.valName[p] = "%" | pname e.nameUsed["%" | pname] = true } e.w("\ndefine hidden ") e.w(e.funcRetType(f)) e.w(" ") e.w(e.funcSymbol(f)) e.w("(") for i, p := range f.Params { if i > 0 { e.w(", ") } pt := e.llvmType(p.SSAType()) if pt == "void" { pt = "ptr" } e.w(pt) e.w(" ") e.w(e.regName(p)) } if !f.IsExternC() { if len(f.Params) > 0 { e.w(", ") } ctxName := "context" for _, p := range f.Params { if p.SSAName() == "context" { ctxName = "context.1" break } } e.w("ptr %") e.w(ctxName) } e.w(") {\n") // Pre-scan: set allocTypes, detect cross-block alloca references e.allocBlock = map[ssa.SSAValue]int32{} for _, b := range f.Blocks { for _, instr := range b.Instrs { if n, ok := instr.(*ssa.SSANext); ok { if ri, ok2 := n.Iter.(*ssa.SSARange); ok2 { rangeSSAType := ri.X.SSAType() rangeUnderlying := types.SafeUnderlying(rangeSSAType) if mt, ok3 := rangeUnderlying.(*types.TCMap); ok3 { e.allocTypes[n] = "{i1, " | e.llvmType(mt.Key()) | ", " | e.llvmType(mt.Elem()) | "}" } else if arr, ok3 := rangeUnderlying.(*types.Array); ok3 { elemType := e.llvmType(arr.Elem()) e.allocTypes[n] = "{i1, i32, " | elemType | "}" } else if sl, ok3 := rangeUnderlying.(*types.Slice); ok3 { elemType := e.llvmType(sl.Elem()) e.allocTypes[n] = "{i1, i32, " | elemType | "}" } else if p, ok3 := rangeUnderlying.(*types.Pointer); ok3 && p.Elem() != nil { if arr2, ok4 := types.SafeUnderlying(p.Elem()).(*types.Array); ok4 { elemType := e.llvmType(arr2.Elem()) e.allocTypes[n] = "{i1, i32, " | elemType | "}" } } else if n.IsString { e.allocTypes[n] = "{i1, i32, i8}" } else { et := "i32" if tup, ok3 := n.SSAType().(*types.Tuple); ok3 && tup.Len() >= 3 { vt := tup.At(2).Type() if vt != nil { vlt := e.llvmType(vt) if vlt != "void" && vlt != "i32" { et = vlt } } } if et != "i32" { e.allocTypes[n] = "{i1, i32, " | et | "}" } } } } if c, ok := instr.(*ssa.SSACall); ok { if b2, ok2 := c.Call.Value.(*ssa.SSABuiltin); ok2 && b2.SSAName() == "recover" { e.allocTypes[c] = e.ifaceType() } } if a, ok := instr.(*ssa.SSAAlloc); ok { e.allocBlock[a] = b.Index } } } hoistAllocs := map[ssa.SSAValue]bool{} for _, b := range f.Blocks { for _, instr := range b.Instrs { refs := e.instrOperands(instr) for _, ref := range refs { if ab, ok := e.allocBlock[ref]; ok && ab != 0 && ab != b.Index { hoistAllocs[ref] = true } } } } e.hoisted = hoistAllocs e.missingStores = nil e.storedTo = map[string]bool{} for _, b := range f.Blocks { for _, instr := range b.Instrs { if s, ok := instr.(*ssa.SSAStore); ok && s.Addr != nil { e.storedTo[s.Addr.SSAName()] = true } } } e.usedAs = map[string]bool{} for _, b := range f.Blocks { for _, instr := range b.Instrs { refs := e.instrOperands(instr) for _, ref := range refs { if ref != nil { e.usedAs[ref.SSAName()] = true } } } } for _, b := range f.Blocks { for i := 0; i+1 < len(b.Instrs); i++ { load, isLoad := b.Instrs[i].(*ssa.SSAUnOp) if !isLoad || load.Op != ssa.OpMul { continue } alloc, isAlloc := b.Instrs[i+1].(*ssa.SSAAlloc) if !isAlloc { continue } if !e.usedAs[load.SSAName()] && !e.storedTo[alloc.SSAName()] && hoistAllocs[alloc] { srcAlloc, isSrcAlloc := load.X.(*ssa.SSAAlloc) if !isSrcAlloc { continue } srcType := e.llvmType(srcAlloc.SSAType()) if p, ok := types.SafeUnderlying(srcAlloc.SSAType()).(*types.Pointer); ok && p.Elem() != nil { srcType = e.llvmType(p.Elem()) } if len(srcType) > 0 && srcType[0] == '[' { if e.missingStores == nil { e.missingStores = map[ssa.SSAValue]ssa.SSAValue{} } e.missingStores[load] = alloc e.allocTypes[alloc] = srcType } } } } for _, b := range f.Blocks { for _, instr := range b.Instrs { e.instrScope[instr] = b.ScopeID } } var hoistList []*ssa.SSAAlloc for v := range hoistAllocs { if a, ok := v.(*ssa.SSAAlloc); ok { hoistList = append(hoistList, a) } } for i := 1; i < len(hoistList); i++ { for j := i; j > 0 && hoistList[j].SSAName() < hoistList[j-1].SSAName(); j-- { hoistList[j], hoistList[j-1] = hoistList[j-1], hoistList[j] } } for _, b := range f.Blocks { for _, instr := range b.Instrs { if d, ok := instr.(*ssa.SSADefer); ok { e.deferList = append(e.deferList, d) } } } scopeIDs := map[int32]bool{} hasHeapAllocs := false for _, b := range f.Blocks { if b.ScopeID > 0 { scopeIDs[b.ScopeID] = true } for _, instr := range b.Instrs { switch instr.(type) { case *ssa.SSAAlloc: if instr.(*ssa.SSAAlloc).Heap { hasHeapAllocs = true } case *ssa.SSAMakeSlice, *ssa.SSAMakeMap, *ssa.SSAMakeChan, *ssa.SSAMakeClosure, *ssa.SSASlice: hasHeapAllocs = true } } } for _, b := range f.Blocks { if b.Index == 0 { e.w("bb.entry:\n") e.blockExitLabel[0] = "%bb.entry" for _, a := range hoistList { e.emitAlloc(a) } if len(e.deferList) > 0 { e.w(" %deferPtr = alloca ptr\n") e.w(" store ptr null, ptr %deferPtr\n") } if len(scopeIDs) > 0 || hasHeapAllocs { e.scopeActive = true e.w(" %scope.head = alloca ptr\n") e.w(" store ptr null, ptr %scope.head\n") for sid := range scopeIDs { e.w(" %scope.save.") e.w(irItoa(sid)) e.w(" = alloca ptr\n") e.w(" store ptr null, ptr %scope.save.") e.w(irItoa(sid)) e.w("\n") } e.declareRuntime("runtime.scopePush", "void", "ptr, ptr") e.declareRuntime("runtime.scopeFreeTo", "ptr", "ptr, ptr") e.declareRuntime("runtime.scopeRelocate", "void", "ptr, ptr, ptr") } e.deallocSetup(f) if len(e.curFunc.FreeVars) > 0 { e.emitFreeVarUnpack(e.curFunc) } for _, instr := range b.Instrs { e.emitInstr(instr) } } else { e.emitBlock(b) } } e.hoisted = nil e.w("}\n") } func (e *irEmitter) emitAsmAlias(f *ssa.SSAFunction, targetName string) { sym := e.funcSymbol(f) retType := e.funcRetType(f) e.w("\ndefine hidden ") e.w(retType) e.w(" ") e.w(sym) e.w("(") var paramTypes []string var paramNames []string n := 0 if f.Signature != nil && f.Signature.Params() != nil { for i := 0; i < f.Signature.Params().Len(); i++ { if n > 0 { e.w(", ") } pt := e.llvmType(f.Signature.Params().At(i).Type()) pn := "%p" | irItoa(i) e.w(pt) e.w(" ") e.w(pn) paramTypes = append(paramTypes, pt) paramNames = append(paramNames, pn) n++ } } if n > 0 { e.w(", ") } e.w("ptr %context) {\nentry:\n") target := "@\"" | targetName | "\"" if retType == "void" { e.w(" call void ") e.w(target) e.w("(") for i, pt := range paramTypes { if i > 0 { e.w(", ") } e.w(pt) e.w(" ") e.w(paramNames[i]) } e.w(", ptr null)\n ret void\n}\n") } else { e.w(" %r = call ") e.w(retType) e.w(" ") e.w(target) e.w("(") for i, pt := range paramTypes { if i > 0 { e.w(", ") } e.w(pt) e.w(" ") e.w(paramNames[i]) } e.w(", ptr null)\n ret ") e.w(retType) e.w(" %r\n}\n") } } func (e *irEmitter) emitFuncDecl(f *ssa.SSAFunction) { sym := e.funcSymbol(f) if _, ok := e.extDecls[sym]; ok { return } e.w("\ndeclare ") e.w(e.funcRetType(f)) e.w(" ") e.w(sym) e.w("(") n := 0 hasRecv := f.Signature != nil && f.Signature.Recv() != nil if hasRecv { e.w("ptr") n++ } if f.Signature != nil && f.Signature.Params() != nil { for i := 0; i < f.Signature.Params().Len(); i++ { if n > 0 { e.w(", ") } e.w(e.llvmType(f.Signature.Params().At(i).Type())) n++ } } if !f.IsExternC() { if n > 0 { e.w(", ") } e.w("ptr") } e.w(")\n") e.extDecls[sym] = "" } func (e *irEmitter) resolveResultType(t syntax.Type) string { rt := e.llvmType(t) if rt != "void" { return rt } if t == nil { return "ptr" } u := types.SafeUnderlying(t) if u == nil { return "ptr" } switch u.(type) { case *types.TCInterface: return e.ifaceType() case *types.Signature: return "{ptr, ptr}" case *types.TCStruct: return e.llvmStructType(u.(*types.TCStruct)) case *types.Slice: return e.sliceType() } return "ptr" } func (e *irEmitter) funcRetType(f *ssa.SSAFunction) string { if f.Signature == nil || f.Signature.Results() == nil || f.Signature.Results().Len() == 0 { return "void" } if f.Signature.Results().Len() == 1 { return e.resolveResultType(f.Signature.Results().At(0).Type()) } s := "{" for i := 0; i < f.Signature.Results().Len(); i++ { if i > 0 { s = s | ", " } s = s | e.resolveResultType(f.Signature.Results().At(i).Type()) } return s | "}" } func (e *irEmitter) emitBlock(b *ssa.SSABasicBlock) { label := "b" | irItoa(b.Index) if b.Index == 0 { label = "bb.entry" } e.w(label) e.w(":\n") e.blockExitLabel[b.Index] = "%" | label if b.Index == 0 && len(e.curFunc.FreeVars) > 0 { e.emitFreeVarUnpack(e.curFunc) } scopeSaveEmitted := false for _, instr := range b.Instrs { if !scopeSaveEmitted { if _, isPhi := instr.(*ssa.SSAPhi); !isPhi { scopeSaveEmitted = true e.emitScopeSave(b) } } e.emitInstr(instr) if e.missingStores != nil { if v, ok2 := instr.(ssa.SSAValue); ok2 { if dst, ok3 := e.missingStores[v]; ok3 { loadReg := e.regName(v) dstReg := e.regName(dst) arrType := e.allocTypes[dst] e.w(" store ") ; e.w(arrType) ; e.w(" ") ; e.w(loadReg) ; e.w(", ptr ") ; e.w(dstReg) ; e.w("\n") } } } } hasTerminator := false if n := len(b.Instrs); n > 0 { switch b.Instrs[n-1].(type) { case *ssa.SSAJump, *ssa.SSAIf, *ssa.SSAReturn: hasTerminator = true } } if !hasTerminator { e.w(" unreachable\n") } } func (e *irEmitter) blockLabel(b *ssa.SSABasicBlock) string { if b.Index == 0 { return "%entry" } return "%b" | irItoa(b.Index) }