package main import ( "math" "os" "runtime" "git.smesh.lol/moxie/pkg/mxutil" . "git.smesh.lol/moxie/pkg/types" ) // kv is a key-value pair for accumulating cross-function map products. type emitKV struct{ k, v string } // emitKVI is a key-int32 pair for accumulating type ID products. type emitKVI struct{ k string; v int32 } type irEmitter struct { segs []string // accumulated segments; joined in flush() buf []byte // assembled output (only during flush/return) outFile *os.File // streaming output; nil = accumulate in buf operandBuf []SSAValue // scratch buffer for instrOperands triple string ptrBits int32 pkg *SSAPackage valName map[SSAValue]string nextReg int32 extDecls map[string]string extGlobals map[string]string strConst []string strMap map[string]int32 curFunc *SSAFunction typeIDs map[string]int32 typeIDNext int32 extTypeIDs map[string]bool localTypeIDs map[string]bool allocTypes map[SSAValue]string regTypes map[string]string hoisted map[SSAValue]bool wasmAttrID int32 wasmAttrs [][3]string blockExitLabel map[int32]string nameUsed map[string]bool missingStores map[SSAValue]SSAValue globalTypes map[string]string globalDeclTypes map[string]string loadToGlobal map[string]*SSAGlobal allocBlock map[SSAValue]int32 storedTo map[string]bool usedAs map[string]bool deferList []*SSADefer deferID int32 blockDead bool currentEmitBlock *SSABasicBlock sretType string pendingPhiTruncs []phiTrunc // Per-function accumulators for cross-function state. // Written during function emission, merged by caller after flush. // All accumulator contents die with the function arena. pendExtDecls []emitKV pendExtGlobals []emitKV pendStrConsts []string pendTypeIDs []emitKVI pendLocalTypeIDs []string pendGlobalDeclTypes []emitKV pendWasmAttrs [][3]string pendDeepCopyEntries []deepCopyEntry definedSyms []string // symbols that got `define` in this package // Arena deep-copy state (compileArena lifetime) arenaActive bool deepCopyFns map[string]string deepCopyQueue []deepCopyEntry relocMarkVisited map[string]bool relocFixupVisited map[string]bool pendRelocFixupDefs []string typeCache map[Type]string } type phiTrunc struct { rawReg string dstReg string from string to string } func newIREmitter(pkg *SSAPackage, triple string, srcLen int32) (p *irEmitter) { ptrBits := 64 if len(triple) >= 4 && triple[:4] == "wasm" { ptrBits = 32 } return &irEmitter{ segs: []string{:0:1024}, triple: triple, ptrBits: ptrBits, pkg: pkg, valName: map[SSAValue]string{}, extDecls: map[string]string{}, extGlobals: map[string]string{}, strMap: map[string]int32{}, typeIDs: map[string]int32{}, localTypeIDs: map[string]bool{}, globalDeclTypes: map[string]string{}, allocTypes: map[SSAValue]string{}, regTypes: map[string]string{}, blockExitLabel: map[int32]string{}, nameUsed: map[string]bool{}, wasmAttrID: 99, } } func (e *irEmitter) w(s string) { push(e.segs, s) } // assembleSegs joins all segments into e.buf in one allocation. // After this, e.segs is reset and e.buf holds the assembled output. func (e *irEmitter) assembleSegs() { n := int32(0) for _, s := range e.segs { n += len(s) } e.buf = []byte{:n} off := int32(0) for _, s := range e.segs { for j := int32(0); j < len(s); j++ { e.buf[off+j] = s[j] } off += len(s) } e.segs = e.segs[:0] } // flush assembles segments, writes to output file, resets. func (e *irEmitter) flush() { e.assembleSegs() if e.outFile != nil && len(e.buf) > 0 { e.outFile.Write(e.buf) } e.buf = nil } // flushDedup assembles segments, deduplicates declare lines, writes to file. func (e *irEmitter) flushDedup() { e.assembleSegs() if e.outFile == nil || len(e.buf) == 0 { e.buf = nil return } // Scan for declare lines and remove duplicates. var seen []string out := []byte{:0:len(e.buf)} ls := int32(0) n := int32(len(e.buf)) for ls < n { le := ls for le < n && e.buf[le] != '\n' { le++ } if le < n { le++ } line := string(e.buf[ls:le]) // Dedup declare lines. isDeclare := len(line) > 8 && line[0] == 'd' && line[1] == 'e' && line[2] == 'c' && line[3] == 'l' && line[4] == 'a' && line[5] == 'r' && line[6] == 'e' && line[7] == ' ' // Dedup define lines (for per-type fixup functions). isDefine := len(line) > 7 && line[0] == 'd' && line[1] == 'e' && line[2] == 'f' && line[3] == 'i' && line[4] == 'n' && line[5] == 'e' && line[6] == ' ' if isDeclare || isDefine { dup := false for si := int32(0); si < len(seen); si++ { s := seen[si] if len(s) == len(line) { match := true for bi := int32(0); bi < len(s); bi++ { if s[bi] != line[bi] { match = false break } } if match { dup = true break } } } if dup { if isDefine { // Skip entire function body until closing }. for ls < n { if e.buf[ls] == '}' { ls++ if ls < n && e.buf[ls] == '\n' { ls++ } break } ls++ } } else { ls = le } continue } push(seen, line) } for j := ls; j < le; j++ { push(out, e.buf[j]) } ls = le } e.outFile.Write(out) e.buf = nil } // hoistAllocaText assembles segments from start onward, moves alloca lines // to _entry top, stores result as single segment. Segments before start // are left intact (parent function already processed). func (e *irEmitter) hoistAllocaText(start int32) { // Assemble only segments from start to end. n := int32(0) for i := start; i < int32(len(e.segs)); i++ { n += len(e.segs[i]) } body := []byte{:n} off := int32(0) for i := start; i < int32(len(e.segs)); i++ { s := e.segs[i] for j := int32(0); j < len(s); j++ { body[off+j] = s[j] } off += len(s) } e.segs = e.segs[:start] marker := "_entry:\n" mlen := int32(len(marker)) insert := int32(-1) for i := int32(0); i+mlen <= int32(len(body)); i++ { match := true for k := int32(0); k < mlen; k++ { if body[i+k] != marker[k] { match = false break } } if match && (i == 0 || body[i-1] == '\n') { insert = i + mlen break } } if insert < 0 { push(e.segs, string(body)) return } // Pass 1: count alloca bytes vs rest bytes. blen := int32(len(body)) allocaBytes := int32(0) moved := false ls := insert for ls < blen { le := ls for le < blen && body[le] != '\n' { le++ } if le < blen { le++ } lineLen := le - ls if lineLen > 13 && body[ls] == ' ' && body[ls+1] == ' ' && body[ls+2] == '%' { for k := ls + 3; k+10 <= le; k++ { if body[k] == ' ' && body[k+1] == '=' && body[k+2] == ' ' && body[k+3] == 'a' && body[k+4] == 'l' && body[k+5] == 'l' && body[k+6] == 'o' && body[k+7] == 'c' && body[k+8] == 'a' && body[k+9] == ' ' { allocaBytes += lineLen moved = true break } } } ls = le } if !moved { push(e.segs, string(body)) return } // Pass 2: build output - prefix, then allocas, then rest. out := []byte{:int32(len(body))} for i := int32(0); i < insert; i++ { out[i] = body[i] } wp := insert // write pointer for allocas wr := insert + allocaBytes // write pointer for rest ls = insert for ls < blen { le := ls for le < blen && body[le] != '\n' { le++ } if le < blen { le++ } lineLen := le - ls isAlloca := false if lineLen > 13 && body[ls] == ' ' && body[ls+1] == ' ' && body[ls+2] == '%' { for k := ls + 3; k+10 <= le; k++ { if body[k] == ' ' && body[k+1] == '=' && body[k+2] == ' ' && body[k+3] == 'a' && body[k+4] == 'l' && body[k+5] == 'l' && body[k+6] == 'o' && body[k+7] == 'c' && body[k+8] == 'a' && body[k+9] == ' ' { isAlloca = true break } } } if isAlloca { for j := int32(0); j < lineLen; j++ { out[wp+j] = body[ls+j] } wp += lineLen } else { for j := int32(0); j < lineLen; j++ { out[wr+j] = body[ls+j] } wr += lineLen } ls = le } push(e.segs, string(out)) } func (e *irEmitter) regName(v SSAValue) (s string) { if v == nil { e.nextReg++ return "%r" | irItoa(e.nextReg) } if nm, ok := e.valName[v]; ok { return nm } name := safeSSAName(v) if name == "" { e.nextReg++ name = "r" | irItoa(e.nextReg) } n := "%" | name for e.nameUsed[n] { e.nextReg++ n = "%r" | irItoa(e.nextReg) } e.valName[v] = n e.nameUsed[n] = true return n } func (e *irEmitter) setRegType(v SSAValue, reg string, typ string) { e.allocTypes[v] = typ if len(reg) > 0 && reg[0] == '%' { e.regTypes[reg] = typ } } func (e *irEmitter) nextReg2(prefix string) (s string) { e.nextReg++ return "%" | prefix | irItoa(e.nextReg) } func (e *irEmitter) declareRuntime(name, retType, params string) { if params != "" { params = params | ", ptr" } else { params = "ptr" } push(e.pendExtDecls, emitKV{name, retType | " @" | name | "(" | params | ")"}) } func (e *irEmitter) declareRuntimeNoCtx(name, retType, params string) { push(e.pendExtDecls, emitKV{name, retType | " @" | name | "(" | params | ")"}) } func (e *irEmitter) declareExternalGlobal(g *SSAGlobal) { if g.pkg == nil || g.pkg == e.pkg { return } name := e.globalName(g) if _, ok := e.extGlobals[name]; ok { return } for _, p := range e.pendExtGlobals { if p.k == name { return } } typ := e.llvmType(g.typ) if p, ok := SafeUnderlying(g.typ).(*Pointer); ok { if p.Base != nil { typ = e.llvmType(p.Base) } else { typ = "ptr" } } if typ == "void" { typ = "i1" } push(e.pendExtGlobals, emitKV{name, typ}) } func (e *irEmitter) declareExternalFunc(fn *SSAFunction) { if fn == nil || len(fn.Blocks) > 0 { return } sym := e.funcSymbol(fn) if _, ok := e.extDecls[sym]; ok { return } for _, p := range e.pendExtDecls { if p.k == sym { return } } retType := e.funcRetType(fn) useSret := needsSret(retType) isIntrinsic := mxutil.HasPrefix(sym, "@llvm.") || mxutil.HasPrefix(sym, "@\"llvm.") isExport := fn.externalSymbol != "" skipCtx := isIntrinsic || isExport params := "" if useSret { params = "ptr" } var psegs []string if params != "" { push(psegs, params) } hasRecv := fn.Signature != nil && fn.Signature.Recv != nil if hasRecv { push(psegs, "ptr") } if fn.Signature != nil && fn.Signature.Params != nil { for i := 0; i < fn.Signature.Params.Len(); i++ { pt := e.llvmType(fn.Signature.Params.At(i).Typ) if pt == "" || pt == "void" { mxutil.WriteStr(2, "BUG-DECL: empty param type in " | sym | " param " | irItoa(int32(i)) | "\n") pt = "ptr" } push(psegs, pt) } } if !skipCtx { push(psegs, "ptr") } params = joinStrs(commaSep(psegs)) if useSret { push(e.pendExtDecls, emitKV{sym, "void " | sym | "(" | params | ")"}) } else { push(e.pendExtDecls, emitKV{sym, retType | " " | sym | "(" | params | ")"}) } } func (e *irEmitter) addStringConst(s string) (n int32) { if existing, ok := e.strMap[s]; ok { return existing } // Check pending list (not yet merged). base := len(e.strConst) for i, ps := range e.pendStrConsts { if ps == s { return int32(base + i) } } idx := int32(base + len(e.pendStrConsts)) push(e.pendStrConsts, s) return idx } func (e *irEmitter) strConstGlobal(idx int32) (s string) { return "@.str." | irItoa(idx) } func (e *irEmitter) resolveAllNamedTypes() { if e.pkg == nil || e.pkg.Pkg == nil || e.pkg.Pkg.Scope == nil { return } scope := e.pkg.Pkg.Scope for _, name := range scope.Names() { obj := scope.Lookup(name) if obj == nil { continue } tn, ok := obj.(*TypeName) if !ok || tn == nil { continue } named, ok2 := tn.Typ.(*Named) if !ok2 || named == nil { continue } if named.Under != nil { continue } u := SafeUnderlying(named) if u != nil && u != Type(named) { named.Under = u } } } func (e *irEmitter) emit() (s string) { dl := e.dataLayout() if dl != "" { e.w("target datalayout = \"") e.w(dl) e.w("\"\n") } e.w("target triple = \"") e.w(e.triple) e.w("\"\n\n") e.resolveAllNamedTypes() e.globalTypes = map[string]string{} e.globalDeclTypes = map[string]string{} sortedM := e.pkgMembers() for _, member := range sortedM { fn, ok := member.(*SSAFunction) if !ok { continue } for _, b := range fn.Blocks { for _, instr := range b.Instrs { if st, okSt := instr.(*SSAStore); okSt && st.Addr != nil && st.Val != nil { if g, ok3 := st.Addr.(*SSAGlobal); ok3 { vt := e.llvmType(st.Val.SSAType()) if vt != "ptr" && vt != "void" && vt != "i1" && vt != "" { name := e.globalName(g) gt := "" if p, ok4 := SafeUnderlying(g.typ).(*Pointer); ok4 { gt = e.llvmType(p.Base) } if gt != "" && gt != "ptr" && gt != "i8" && gt[0] == '{' && vt[0] != '{' { vt = gt } e.globalTypes[name] = vt } } } } } e.loadToGlobal = map[string]*SSAGlobal{} for _, b := range fn.Blocks { for _, instr := range b.Instrs { load, okUn := instr.(*SSAUnOp) if !okUn || load.Op != OpMul { continue } g, ok3 := load.X.(*SSAGlobal) if !ok3 { continue } e.loadToGlobal[load.SSAName()] = g } } for _, b := range fn.Blocks { for _, instr := range b.Instrs { if ret, okRet := instr.(*SSAReturn); okRet { if fn.Signature == nil { continue } rets := fn.Signature.Results if rets == nil || rets.Len() == 0 { continue } for i, res := range ret.Results { if i >= rets.Len() { break } if g, ok3 := e.loadToGlobal[res.SSAName()]; ok3 { rt := rets.At(i) if rt == nil { continue } expectType := e.llvmType(rt.Typ) if expectType != "ptr" && expectType != "void" && expectType != "i1" && expectType != "" { name := e.globalName(g) if _, exists := e.globalTypes[name]; !exists { e.globalTypes[name] = expectType } } } } } call, okCall := instr.(*SSACall) if !okCall { continue } callee := call.Call.Value if callee == nil { continue } var sig *Signature if cfn, ok3 := callee.(*SSAFunction); ok3 && cfn.Signature != nil { sig = cfn.Signature } else { ct := callee.SSAType() if ct != nil { sig, _ = SafeUnderlying(ct).(*Signature) } } if sig == nil { continue } params := sig.Params if params == nil || params.Len() == 0 { continue } recvOff := 0 if sig.Recv != nil { recvOff = 1 } for i, arg := range call.Call.Args { if arg == nil { continue } sigIdx := i - recvOff if sigIdx < 0 || sigIdx >= params.Len() { continue } pt := params.At(sigIdx) if pt == nil { continue } g, found := e.loadToGlobal[arg.SSAName()] if !found { continue } expectType := e.llvmType(pt.Typ) name := e.globalName(g) if expectType != "void" && expectType != "i1" && expectType != "" { if _, exists := e.globalTypes[name]; !exists { e.globalTypes[name] = expectType } } } } } for _, b := range fn.Blocks { for _, instr := range b.Instrs { if rng, okRng := instr.(*SSARange); okRng && rng.X != nil { if g, ok3 := e.loadToGlobal[rng.X.SSAName()]; ok3 { name := e.globalName(g) if _, exists := e.globalTypes[name]; !exists { e.globalTypes[name] = "ptr" } } } if mu, okMu := instr.(*SSAMapUpdate); okMu && mu.Map != nil { if g, ok3 := e.loadToGlobal[mu.Map.SSAName()]; ok3 { name := e.globalName(g) if _, exists := e.globalTypes[name]; !exists { e.globalTypes[name] = "ptr" } } } if lu, okLu := instr.(*SSALookup); okLu && lu.X != nil { if g, ok3 := e.loadToGlobal[lu.X.SSAName()]; ok3 { name := e.globalName(g) if _, exists := e.globalTypes[name]; !exists { e.globalTypes[name] = "ptr" } } } bop, okBop := instr.(*SSABinOp) if !okBop { continue } if bop.X == nil || bop.Y == nil { continue } gx, xIsGlobal := e.loadToGlobal[bop.X.SSAName()] gy, yIsGlobal := e.loadToGlobal[bop.Y.SSAName()] if xIsGlobal { yt := e.llvmType(bop.Y.SSAType()) if yt != "ptr" && yt != "void" && yt != "i1" && yt != "" { name := e.globalName(gx) if _, exists := e.globalTypes[name]; !exists { e.globalTypes[name] = yt } } } if yIsGlobal { xt := e.llvmType(bop.X.SSAType()) if xt != "ptr" && xt != "void" && xt != "i1" && xt != "" { name := e.globalName(gy) if _, exists := e.globalTypes[name]; !exists { e.globalTypes[name] = xt } } } } } } e.flush() for _, member := range e.pkgMembers() { switch m := member.(type) { case *SSAGlobal: if m.name != "_" { e.emitGlobal(m) } } } e.flush() compileArena := runtime.CurrentArena() // Deep copy state lives in compileArena, survives per-function resets. e.deepCopyFns = map[string]string{} e.deepCopyQueue = nil // Pre-collect symbols that will get `define` (functions with bodies). // Done in compile arena so the slice survives fn arena resets. for _, member := range e.pkgMembers() { if f, ok := member.(*SSAFunction); ok && f.Blocks != nil { defSym := e.funcSymbol(f) if len(defSym) > 0 && defSym[0] == '@' { defSym = defSym[1:] } push(e.definedSyms, defSym) } } fnArena := runtime.ArenaNew(64 * 1024) for _, member := range e.pkgMembers() { if m, ok := member.(*SSAFunction); ok { checkUseAfterFree(m) checkParamMutation(m) runtime.SetCurrentArena(fnArena) e.emitFunction(m) e.emitAnonFuncs(m) e.flush() runtime.SetCurrentArena(compileArena) e.mergeAccumulators() e.segs = nil e.buf = nil e.operandBuf = nil e.valName = nil e.nameUsed = map[string]bool{} e.allocTypes = nil e.regTypes = nil e.blockExitLabel = nil e.typeCache = nil e.hoisted = nil e.pendingPhiTruncs = nil e.deferList = nil e.allocBlock = nil e.storedTo = nil e.usedAs = nil e.missingStores = nil m.Blocks = nil m.Locals = nil m.Params = nil m.FreeVars = nil m.NamedResults = nil m.vars = nil runtime.ArenaReset(fnArena) } } runtime.ArenaFree(fnArena) // Fresh segs for trailing declarations. e.segs = []string{:0:256} // Emit per-type deep copy helper functions for arena return copy. e.emitDeepCopyFunctions() // Merge any declares/globals generated by deep copy functions. e.mergeAccumulators() e.emitLibMain() for i, sc := range e.strConst { e.w(e.strConstGlobal(i)) e.w(" = private constant [") e.w(irItoa(len(sc))) e.w(" x i8] c\"") e.w(irEscapeString(sc)) e.w("\"\n") } if len(e.extDecls) > 0 { e.w("\n") var edKeys []string for k := range e.extDecls { push(edKeys, k) } for i := 1; i < len(edKeys); i++ { for j := i; j > 0 && edKeys[j] < edKeys[j-1]; j-- { edKeys[j], edKeys[j-1] = edKeys[j-1], edKeys[j] } } for _, k := range edKeys { decl := e.extDecls[k] if decl == "" { continue } if e.isPkgFuncDefined(k) { continue } e.w("declare ") e.w(decl) // Check for wasm import attributes if len(cctx.wasmImportMap) > 0 { sym := k if len(sym) > 0 && sym[0] == '@' { sym = sym[1:] } if len(sym) >= 2 && sym[0] == '"' && sym[len(sym)-1] == '"' { sym = sym[1 : len(sym)-1] } if wi, ok := cctx.wasmImportMap[sym]; ok { e.wasmAttrID++ aid := e.wasmAttrID e.w(" #" | simpleItoa(aid)) push(e.wasmAttrs, [3]string{simpleItoa(aid), wi[0], wi[1]}) } } e.w("\n") } } if len(e.extGlobals) > 0 { e.w("\n") var egKeys []string for name := range e.extGlobals { push(egKeys, name) } for i := 1; i < len(egKeys); i++ { for j := i; j > 0 && egKeys[j] < egKeys[j-1]; j-- { egKeys[j], egKeys[j-1] = egKeys[j-1], egKeys[j] } } for _, name := range egKeys { e.w(name) e.w(" = ") e.w(e.tlsExternalPrefix()) e.w(e.extGlobals[name]) e.w("\n") } } // Emit wasm import attribute groups for _, wa := range e.wasmAttrs { e.w("attributes #" | wa[0] | " = { \"wasm-import-module\"=\"" | wa[1] | "\" \"wasm-import-name\"=\"" | wa[2] | "\" }\n") } if e.outFile != nil { e.flushDedup() return "" } e.assembleSegs() result := string(e.buf) e.buf = nil return result } func (e *irEmitter) releaseAfterEmit() {} // copyStr deep-copies a string's backing bytes into the current arena. // Without this, string headers in compile-arena maps would point to // fn-arena bytes that die at ArenaReset. func copyStr(s string) (r string) { if len(s) == 0 { return "" } buf := []byte{:len(s)} copy(buf, s) return string(buf) } // mergeAccumulators deep-copies per-function products into compile-arena maps. // Called on the compile arena after function emit + flush. // All accumulator string bytes are in the fn arena and must be copied. func (e *irEmitter) mergeAccumulators() { for _, p := range e.pendExtDecls { e.extDecls[copyStr(p.k)] = copyStr(p.v) } e.pendExtDecls = nil for _, p := range e.pendExtGlobals { e.extGlobals[copyStr(p.k)] = copyStr(p.v) } e.pendExtGlobals = nil for _, s := range e.pendStrConsts { cs := copyStr(s) if _, ok := e.strMap[cs]; !ok { e.strMap[cs] = len(e.strConst) push(e.strConst, cs) } } e.pendStrConsts = nil for _, p := range e.pendTypeIDs { ck := copyStr(p.k) if _, ok := e.typeIDs[ck]; !ok { e.typeIDs[ck] = p.v } } e.pendTypeIDs = nil for _, k := range e.pendLocalTypeIDs { e.localTypeIDs[copyStr(k)] = true } e.pendLocalTypeIDs = nil for _, p := range e.pendGlobalDeclTypes { e.globalDeclTypes[copyStr(p.k)] = copyStr(p.v) } e.pendGlobalDeclTypes = nil for _, wa := range e.pendWasmAttrs { push(e.wasmAttrs, [3]string{copyStr(wa[0]), copyStr(wa[1]), copyStr(wa[2])}) } e.pendWasmAttrs = nil for _, dc := range e.pendDeepCopyEntries { key := copyStr(typeKeyForDeepCopy(dc.typ)) sym := copyStr(dc.sym) e.deepCopyFns[key] = sym push(e.deepCopyQueue, deepCopyEntry{sym, dc.typ}) } e.pendDeepCopyEntries = nil for _, sym := range e.pendRelocFixupDefs { push(e.definedSyms, copyStr(sym)) } e.pendRelocFixupDefs = nil } func (e *irEmitter) pkgMembers() (ss []SSAMember) { var keys []string for k := range e.pkg.Members { push(keys, k) } for i := 1; i < len(keys); i++ { for j := i; j > 0 && keys[j] < keys[j-1]; j-- { keys[j], keys[j-1] = keys[j-1], keys[j] } } var members []SSAMember for _, k := range keys { m := e.pkg.Members[k] if m != nil { push(members, m) } } return members } func (e *irEmitter) emitGlobal(g *SSAGlobal) { name := e.globalName(g) if ei, ok := cctx.embedVars[g.name]; ok { e.emitEmbedGlobal(g, name, ei) return } typ := e.resolveGlobalDeclType(g) e.w(name) e.w(" = ") e.w(e.tlsPrefix()) e.w(typ) e.w(" zeroinitializer\n") } func (e *irEmitter) emitEmbedGlobal(g *SSAGlobal, name string, ei *embedInfo) { data, ok := mxutil.ReadFile(ei.path) if !ok { // Fail loud: a zeroinitializer embed global is a nil slice at runtime // and SIGSEGVs far from the cause. push(cctx.compileErrors, "embed: file not found: "|ei.path) e.w(name | " = " | e.tlsPrefix() | e.sliceType() | " zeroinitializer\n") return } elemSize := e.embedElemSize(ei.elemType) if elemSize <= 0 { push(cctx.compileErrors, "embed: unknown element type: "|ei.elemType|" for "|ei.path) e.w(name | " = " | e.tlsPrefix() | e.sliceType() | " zeroinitializer\n") return } count := int32(len(data)) / elemSize // name may be quoted (@"pkg/path.var"); the .data suffix must land // inside the quotes or the symbol is malformed IR. constName := name | ".data" if len(name) > 0 && name[len(name)-1] == '"' { constName = name[:len(name)-1] | ".data\"" } e.w(constName | " = private constant [" | irItoa(int32(len(data))) | " x i8] c\"") hexDigit := "0123456789ABCDEF" hexBuf := []byte{:int32(len(data)) * 3} for i := int32(0); i < int32(len(data)); i++ { b := data[i] hexBuf[i*3] = '\\' hexBuf[i*3+1] = hexDigit[b>>4] hexBuf[i*3+2] = hexDigit[b&0x0f] } e.w(string(hexBuf)) e.w("\"\n") ipt := e.intptrType() e.w(name | " = " | e.tlsPrefix() | "{ptr, " | ipt | ", " | ipt | "} {ptr " | constName | ", " | ipt | " " | irItoa(count) | ", " | ipt | " " | irItoa(count) | "}\n") } func (e *irEmitter) embedElemSize(elemType string) (sz int32) { if elemType == "[]Range16" { return 6 } if elemType == "[]Range32" { return 12 } if elemType == "[]CaseRange" { return 20 } if elemType == "[]foldPair" { return 8 } if elemType == "[]uint8" || elemType == "[]byte" { return 1 } if elemType == "[]uint16" { return 2 } if elemType == "[]uint32" || elemType == "[]int32" { return 4 } if elemType == "[]uint64" || elemType == "[]int64" { return 8 } return 0 } func (e *irEmitter) globalName(g *SSAGlobal) (s string) { pkg := e.pkg.Pkg.Path if g.pkg != nil { pkg = g.pkg.Pkg.Path } return irGlobalSymbol(pkg, g.name) } func (e *irEmitter) isPkgFuncDefined(name string) (ok bool) { // Check if we emitted a define for this symbol during this package's emit. for di := int32(0); di < len(e.definedSyms); di++ { ds := e.definedSyms[di] if len(ds) == len(name) { match := true for bi := int32(0); bi < len(ds); bi++ { if ds[bi] != name[bi] { match = false break } } if match { return true } } } return false } func (e *irEmitter) isPkgFunc2(name string) (ok bool) { for _, m := range e.pkg.Members { if f, ok2 := m.(*SSAFunction); ok2 { sym := e.funcSymbol(f) if len(sym) > 2 && sym[0] == '@' && sym[1] == '"' { sym = sym[2 : len(sym)-1] } else if len(sym) > 1 && sym[0] == '@' { sym = sym[1:] } if sym == name { return true } } } return false } func (e *irEmitter) funcSymbol(f *SSAFunction) (s string) { if f == nil { return "@__mxc_nil_func" } if f.externalSymbol != "" { sym := f.externalSymbol if irNeedsQuote(sym) { return "@\"" | sym | "\"" } return "@" | sym } if cctx.linknameMap != nil { if target, ok := cctx.linknameMap[f.name]; ok { if irNeedsQuote(target) { return "@\"" | target | "\"" } return "@" | target } } pkg := e.pkg.Pkg.Path if f.Pkg != nil { pkg = f.Pkg.Pkg.Path } return irGlobalSymbol(pkg, f.name) } func (e *irEmitter) isPkgFunc(f *SSAFunction) (ok bool) { if f == nil { return false } if f.Pkg == e.pkg { return true } if f.parent != nil { return e.isPkgFunc(f.parent) } return false } func (e *irEmitter) emitAnonFuncs(f *SSAFunction) { for _, af := range f.AnonFuncs { e.emitFunction(af) e.emitAnonFuncs(af) af.Blocks = nil af.Locals = nil af.Params = nil af.FreeVars = nil af.NamedResults = nil af.vars = nil } f.AnonFuncs = nil } func (e *irEmitter) emitLibMain() { pkgPath := e.pkg.Pkg.Path if pkgPath == "main" || pkgPath == "command-line-arguments" { return } hasMain := false if m := e.pkg.Members["main"]; m != nil { if fn, ok := m.(*SSAFunction); ok && fn.externalSymbol == "" { hasMain = true } } if hasMain { return } sym := irGlobalSymbol(pkgPath, "main") e.w("\ndefine void ") e.w(sym) e.w("(ptr %_ctx) {\n_entry:\n ret void\n}\n") } func (e *irEmitter) emitFunction(f *SSAFunction) { e.w("; [emit] " | f.name | "\n") sym := e.funcSymbol(f) if len(sym) > 5 && (mxutil.HasPrefix(sym, "@llvm.") || mxutil.HasPrefix(sym, "@\"llvm.")) { e.emitFuncDecl(f) return } if len(f.Blocks) == 0 { // If function has a linkname, the declare uses the target symbol // but consuming packages reference the original fully-qualified name. // Emit a forwarding wrapper so the linker can resolve both. if cctx.linknameMap != nil { if target, ok := cctx.linknameMap[f.name]; ok { _ = target pkg := e.pkg.Pkg.Path if f.Pkg != nil { pkg = f.Pkg.Pkg.Path } origSym := irGlobalSymbol(pkg, f.name) tgtSym := sym // already the linkname target from funcSymbol // Check if the target is defined in the same package // (has a body). If so, skip the declare to avoid // conflicting with the existing define. // If the linkname target is in the same package, the // target function is already defined - skip the declare // to avoid conflicting with the existing define. pkgDot := pkg | "." samePackage := mxutil.HasPrefix(target, pkgDot) if !samePackage { e.emitFuncDecl(f) } else if origSym == tgtSym { // Self-referential linkname: the function's linkname // target is its own fully-qualified name. Body is // provided by another package. Emit a declare. e.emitFuncDecl(f) } if origSym != tgtSym { e.emitLinknameForwarder(f, origSym, tgtSym) } return } } e.emitFuncDecl(f) return } e.curFunc = f e.arenaActive = false markReturnEscapes(f) e.nextReg = 0 e.deferList = nil e.deferID = 0 e.valName = map[SSAValue]string{} e.nameUsed = map[string]bool{} e.allocTypes = map[SSAValue]string{} e.regTypes = map[string]string{} e.blockExitLabel = map[int32]string{} e.typeCache = map[Type]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 ") if f.parent != nil { e.w("hidden ") } retType := e.funcRetType(f) useSret := needsSret(retType) e.sretType = "" if useSret { e.w("void ") e.sretType = retType } else { e.w(retType) e.w(" ") } e.w(e.funcSymbol(f)) e.w("(") nParam := 0 if useSret { e.w("ptr sret(") ; e.w(retType) ; e.w(") %retval") nParam++ } isExport := f.externalSymbol != "" for _, p := range f.Params { if nParam > 0 { e.w(", ") } pt := e.llvmType(p.SSAType()) if pt == "void" { pt = "ptr" } e.w(pt) e.w(" ") e.w(e.regName(p)) nParam++ } if !isExport { if nParam > 0 { e.w(", ") } e.w("ptr %_ctx") } e.w(") {\n") fnBodyStart := int32(len(e.segs)) // segment index where this function's body starts // Pre-scan: set allocTypes, detect cross-block alloca references e.allocBlock = map[SSAValue]int32{} for _, b := range f.Blocks { for _, instr := range b.Instrs { if n, ok := instr.(*SSANext); ok { if ri, ok2 := n.Iter.(*SSARange); ok2 { if arr, ok3 := SafeUnderlying(ri.X.SSAType()).(*Array); ok3 { elemType := e.llvmType(arr.Elem) e.allocTypes[n] = "{i1, i32, " | elemType | "}" } else if sl, ok3b := SafeUnderlying(ri.X.SSAType()).(*Slice); ok3b { elemType := e.llvmType(sl.Elem) e.allocTypes[n] = "{i1, i32, " | elemType | "}" } } } if c, ok := instr.(*SSACall); ok { if b2, ok2 := c.Call.Value.(*SSABuiltin); ok2 && b2.SSAName() == "recover" { e.allocTypes[c] = e.ifaceType() } } if a, ok := instr.(*SSAAlloc); ok { e.allocBlock[a] = b.Index } } } hoistAllocs := map[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.(*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].(*SSAUnOp) if !isLoad || load.Op != OpMul { continue } alloc, isAlloc := b.Instrs[i+1].(*SSAAlloc) if !isAlloc { continue } if !e.usedAs[load.SSAName()] && !e.storedTo[alloc.SSAName()] && hoistAllocs[alloc] { srcAlloc, isSrcAlloc := load.X.(*SSAAlloc) if !isSrcAlloc { continue } srcType := e.llvmType(srcAlloc.SSAType()) if p, ok := SafeUnderlying(srcAlloc.SSAType()).(*Pointer); ok && p.Base != nil { srcType = e.llvmType(p.Base) } if len(srcType) > 0 && srcType[0] == '[' { if e.missingStores == nil { e.missingStores = map[SSAValue]SSAValue{} } e.missingStores[load] = alloc e.allocTypes[alloc] = srcType } } } } var hoistList []*SSAAlloc // Collect allocas deterministically: iterate blocks/instructions in order // (both are slices). Using hoistAllocs map only as a set membership check. for _, hb := range f.Blocks { for _, hinstr := range hb.Instrs { if a, ok := hinstr.(*SSAAlloc); ok && hoistAllocs[a] { push(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.(*SSADefer); ok { push(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 *SSAAlloc: if instr.(*SSAAlloc).Heap { hasHeapAllocs = true } case *SSAMakeSlice, *SSAMakeMap, *SSAMakeChan, *SSAMakeClosure, *SSASlice: hasHeapAllocs = true } } } for _, b := range f.Blocks { if b.Index == 0 { e.w("_entry:\n") e.currentEmitBlock = b 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(e.curFunc.FreeVars) > 0 { e.emitFreeVarUnpack(e.curFunc) } e.emitArenaPrologue() for _, instr := range b.Instrs { e.emitInstr(instr) } e.flushPhiTruncs() } else { e.emitBlock(b) } } e.hoisted = nil e.hoistAllocaText(fnBodyStart) e.w("}\n") } func (e *irEmitter) emitFuncDecl(f *SSAFunction) { sym := e.funcSymbol(f) bareKey := sym if len(bareKey) > 0 && bareKey[0] == '@' { bareKey = bareKey[1:] } if _, ok := e.extDecls[bareKey]; ok { return } for _, p := range e.pendExtDecls { if p.k == bareKey { return } } retType := e.funcRetType(f) useSret := needsSret(retType) decl := "" if useSret { decl = "void " } else { decl = retType | " " } decl = decl | sym | "(" n := 0 if useSret { decl = decl | "ptr" n++ } isIntrinsic := mxutil.HasPrefix(sym, "@llvm.") || mxutil.HasPrefix(sym, "@\"llvm.") hasRecv := f.Signature != nil && f.Signature.Recv != nil if hasRecv { if n > 0 { decl = decl | ", " } decl = decl | "ptr" n++ } if f.Signature != nil && f.Signature.Params != nil { for i := 0; i < f.Signature.Params.Len(); i++ { if n > 0 { decl = decl | ", " } pt := e.llvmType(f.Signature.Params.At(i).Typ) if pt == "" || pt == "void" { pt = "ptr" } decl = decl | pt n++ } } if !isIntrinsic { if n > 0 { decl = decl | ", " } decl = decl | "ptr" } decl = decl | ")" bareKey := sym if len(bareKey) > 0 && bareKey[0] == '@' { bareKey = bareKey[1:] } push(e.pendExtDecls, emitKV{bareKey, decl}) } // emitLinknameForwarder emits a thin wrapper definition with origSym that // calls tgtSym. This allows consuming packages (which reference origSym) // to resolve to the linkname target (tgtSym) at link time. func (e *irEmitter) emitLinknameForwarder(f *SSAFunction, origSym, tgtSym string) { retType := e.funcRetType(f) useSret := needsSret(retType) // Build parameter list with names var paramTypes []string var paramArgs []string idx := int32(0) if useSret { pn := "%sret" push(paramTypes, "ptr " | pn) push(paramArgs, "ptr " | pn) idx++ } hasRecv := f.Signature != nil && f.Signature.Recv != nil if hasRecv { pn := "%p" | irItoa(idx) push(paramTypes, "ptr " | pn) push(paramArgs, "ptr " | pn) idx++ } if f.Signature != nil && f.Signature.Params != nil { for i := 0; i < f.Signature.Params.Len(); i++ { pt := e.llvmType(f.Signature.Params.At(i).Typ) if pt == "" || pt == "void" { pt = "ptr" } pn := "%p" | irItoa(idx) push(paramTypes, pt | " " | pn) push(paramArgs, pt | " " | pn) idx++ } } // context pointer ctxPN := "%p" | irItoa(idx) push(paramTypes, "ptr " | ctxPN) push(paramArgs, "ptr " | ctxPN) // Emit define e.w("\ndefine ") if useSret { e.w("void ") } else { e.w(retType) ; e.w(" ") } e.w(origSym) ; e.w("(") for i, pt := range paramTypes { if i > 0 { e.w(", ") } e.w(pt) } e.w(") {\n") if useSret || retType == "void" { e.w(" call void ") ; e.w(tgtSym) ; e.w("(") for i, pa := range paramArgs { if i > 0 { e.w(", ") } e.w(pa) } e.w(")\n ret void\n") } else { e.w(" %fwd = call ") ; e.w(retType) ; e.w(" ") ; e.w(tgtSym) ; e.w("(") for i, pa := range paramArgs { if i > 0 { e.w(", ") } e.w(pa) } e.w(")\n ret ") ; e.w(retType) ; e.w(" %fwd\n") } e.w("}\n") } func (e *irEmitter) emitBlock(b *SSABasicBlock) { label := "b" | irItoa(b.Index) if b.Index == 0 { label = "_entry" } e.w(label) e.w(":\n") e.blockExitLabel[b.Index] = "%" | label e.currentEmitBlock = b if b.Index == 0 && len(e.curFunc.FreeVars) > 0 { e.emitFreeVarUnpack(e.curFunc) } e.blockDead = false phiFlushed := false for _, instr := range b.Instrs { if e.blockDead { break } if !phiFlushed { if _, isPhi := instr.(*SSAPhi); !isPhi { phiFlushed = true e.flushPhiTruncs() } } e.emitInstr(instr) if e.missingStores != nil { if v, ok2 := instr.(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") } } } } e.flushPhiTruncs() hasTerminator := e.blockDead if !hasTerminator { if n := len(b.Instrs); n > 0 { switch b.Instrs[n-1].(type) { case *SSAJump, *SSAIf, *SSAReturn: hasTerminator = true } } } if !hasTerminator { e.w(" unreachable\n") } } func (e *irEmitter) blockLabel(b *SSABasicBlock) (s string) { if b.Index == 0 { return "%_entry" } return "%b" | irItoa(b.Index) } func (e *irEmitter) emitInstr(instr SSAInstruction) { switch i := instr.(type) { case *SSAAlloc: if e.hoisted != nil && e.hoisted[i] { break } e.emitAlloc(i) case *SSAStore: e.emitStore(i) case *SSABinOp: e.emitBinOp(i) case *SSAUnOp: e.emitUnOp(i) case *SSACall: e.emitCall(i) case *SSAPhi: e.emitPhi(i) case *SSAReturn: e.emitReturn(i) case *SSAJump: e.emitJump(i) case *SSAIf: e.emitIf(i) case *SSAConvert: e.emitConvert(i) case *SSAChangeType: e.emitChangeType(i) case *SSAFieldAddr: e.emitFieldAddr(i) case *SSAIndexAddr: e.emitIndexAddr(i) case *SSAExtract: e.emitExtract(i) case *SSAMakeSlice: e.emitMakeSlice(i) case *SSASlice: e.emitSliceOp(i) case *SSAMakeInterface: e.emitMakeInterface(i) case *SSAInvoke: e.emitInvoke(i) case *SSATypeAssert: e.emitTypeAssert(i) case *SSAMakeMap: e.emitMakeMap(i) case *SSAMapUpdate: e.emitMapUpdate(i) case *SSALookup: e.emitLookup(i) case *SSAMakeClosure: e.emitMakeClosure(i) case *SSAPanic: e.emitPanic(i) case *SSARunDefers: e.emitRunDefers() case *SSADefer: e.emitDefer(i) case *SSASend: e.emitChanSend(i) case *SSAGo: e.w(" ; go\n") case *SSASelect: e.emitSelect(i) case *SSARange: e.emitRange(i) case *SSANext: e.emitNext(i) case *SSAMakeChan: e.emitMakeChan(i) } } func (e *irEmitter) emitStore(s *SSAStore) { if s.Val == nil || s.Addr == nil { e.w(" ; store with nil val/addr\n") return } valType := e.llvmType(s.Val.SSAType()) val := e.operand(s.Val) if load, ok := s.Val.(*SSAUnOp); ok && load.Op == OpMul { if g, ok2 := load.X.(*SSAGlobal); ok2 { valType = e.resolveGlobalDeclType(g) } } _, isStoreAlloc := s.Val.(*SSAAlloc) _, isStoreIA := s.Val.(*SSAIndexAddr) if !isStoreAlloc && !isStoreIA { if at, ok := e.allocTypes[s.Val]; ok && at != valType { bothScalar := len(valType) > 0 && valType[0] == 'i' && len(at) > 0 && at[0] == 'i' if !bothScalar { valType = at if val == "null" && valType != "ptr" { val = "zeroinitializer" } } else if irParseIntWidth(at) > irParseIntWidth(valType) { valType = at } } } if len(valType) > 0 && (valType[0] == '[' || valType[0] == '{') { if addrAt, ok := e.allocTypes[s.Addr]; ok && addrAt != valType { if len(valType) >= len(addrAt) || (valType[0] == '[' && addrAt[0] == '{') { e.allocTypes[s.Addr] = valType } } } if valType == "void" { if at, ok := e.allocTypes[s.Addr]; ok && at != "ptr" && at != "void" { valType = at if val == "null" && valType != "ptr" { val = "zeroinitializer" } } } else if valType == "ptr" { if uop, ok := s.Val.(*SSAUnOp); ok && uop.Op == OpMul { if at, ok2 := e.allocTypes[s.Addr]; ok2 && at != "ptr" && at != "void" { valType = at if val == "null" && valType != "ptr" { val = "zeroinitializer" } } } } if valType == "void" { if _, isFV := s.Addr.(*SSAFreeVar); isFV { valType = e.llvmType(s.Addr.SSAType()) } else if p, ok := SafeUnderlying(s.Addr.SSAType()).(*Pointer); ok { valType = e.llvmType(p.Base) } if valType == "void" { valType = "ptr" } if val == "null" && valType != "ptr" { val = "zeroinitializer" } } addr := e.operand(s.Addr) if at, ok := e.allocTypes[s.Addr]; ok && (at == "double" || at == "float") && len(valType) > 0 && valType[0] == 'i' { if isConstOperand(val) { val = ensureFloatLit(val) } else { e.nextReg++ conv := "%si2f" | irItoa(e.nextReg) e.w(" ") ; e.w(conv) ; e.w(" = sitofp ") ; e.w(valType) ; e.w(" ") ; e.w(val) ; e.w(" to ") ; e.w(at) ; e.w("\n") val = conv } valType = at } if at, ok2 := e.allocTypes[s.Addr]; ok2 && len(at) > 0 && at[0] == '{' && len(valType) > 0 && valType[0] == 'i' { if val == "0" || val == "zeroinitializer" { val = "zeroinitializer" valType = at } } if at, ok2 := e.allocTypes[s.Addr]; ok2 && len(at) > 0 && at[0] == 'i' && len(valType) > 0 && valType[0] == '{' { valType = at val = "zeroinitializer" } if p, ok := SafeUnderlying(s.Addr.SSAType()).(*Pointer); ok { elemT := e.llvmType(p.Base) if len(elemT) > 1 && elemT[0] == 'i' && len(valType) > 1 && valType[0] == 'i' && elemT != valType { vw := irParseIntWidth(valType) ew := irParseIntWidth(elemT) if ew > 0 && vw > ew { e.nextReg++ trunc := "%tr" | irItoa(e.nextReg) e.w(" ") e.w(trunc) e.w(" = trunc ") e.w(valType) e.w(" ") e.w(val) e.w(" to ") e.w(elemT) e.w("\n") val = trunc valType = elemT } else if ew > 0 && vw > 0 && vw < ew { if c, ok2 := s.Val.(*SSAConst); ok2 { if ci, ok3 := c.val.(*ConstInt); ok3 { val = irItoa64(ci.V) valType = elemT } else { valType = elemT } } else { e.nextReg++ ext := "%se" | irItoa(e.nextReg) // Source signedness decides the extension (Go semantics). extOp := "sext" if b, ok4 := SafeUnderlying(s.Val.SSAType()).(*Basic); ok4 && b.Info&IsUnsigned != 0 { extOp = "zext" } e.w(" ") e.w(ext) e.w(" = ") ; e.w(extOp) ; e.w(" ") e.w(valType) e.w(" ") e.w(val) e.w(" to ") e.w(elemT) e.w("\n") val = ext valType = elemT } } } } e.emitScopeRelocateOnStore(s, val, valType) e.w(" store ") e.w(valType) e.w(" ") e.w(val) e.w(", ptr ") e.w(addr) e.w("\n") } func (e *irEmitter) emitPhi(p *SSAPhi) { reg := e.regName(p) typ := e.llvmType(p.SSAType()) blk := p.InstrBlock() if blk == nil { return } // Collect operands and check for i1/i32 mismatch. ops := []string{:len(p.Edges):len(p.Edges)} widen := false if typ == "i1" { for i, edge := range p.Edges { ops[i] = e.operand(edge) if !widen && len(ops[i]) > 0 && ops[i][0] == '%' { if rt, ok := e.regTypes[ops[i]]; ok && rt == "i32" { widen = true } } } } if !widen { // Normal path: emit phi as-is. for i, edge := range p.Edges { if ops[i] == "" { ops[i] = e.operand(edge) } } e.w(" ") ; e.w(reg) ; e.w(" = phi ") ; e.w(typ) ; e.w(" ") for i, op := range ops { if i > 0 { e.w(", ") } e.w("[") ; e.w(op) ; e.w(", ") e.emitPhiPred(blk, i) e.w("]") } e.w("\n") return } // Widen i1 phi to i32 to match edge types, trunc after all phis. rawReg := e.nextReg2("phw") e.w(" ") ; e.w(rawReg) ; e.w(" = phi i32 ") for i, op := range ops { if i > 0 { e.w(", ") } e.w("[") if op == "false" { op = "0" } if op == "true" { op = "1" } e.w(op) ; e.w(", ") e.emitPhiPred(blk, i) e.w("]") } e.w("\n") push(e.pendingPhiTruncs, phiTrunc{rawReg, reg, "i32", "i1"}) } func (e *irEmitter) emitPhiPred(blk *SSABasicBlock, i int32) { if i < len(blk.Preds) { pred := blk.Preds[i] if pred != nil { if exitLbl, ok := e.blockExitLabel[pred.Index]; ok { e.w(exitLbl) } else { e.w(e.blockLabel(pred)) } return } } e.w("%unknown") } func (e *irEmitter) flushPhiTruncs() { for _, pt := range e.pendingPhiTruncs { e.w(" ") ; e.w(pt.dstReg) ; e.w(" = trunc ") ; e.w(pt.from) ; e.w(" ") ; e.w(pt.rawReg) ; e.w(" to ") ; e.w(pt.to) ; e.w("\n") } e.pendingPhiTruncs = e.pendingPhiTruncs[:0] } func (e *irEmitter) emitReturn(r *SSAReturn) { if len(e.deferList) > 0 { e.emitRunDefers() } e.scopeBeforeReturn(r) frt := e.funcRetType(e.curFunc) if len(r.Results) == 0 { rt := e.funcRetType(e.curFunc) if rt == "void" { e.w(" ret void\n") } else if len(e.curFunc.NamedResults) > 0 { if len(e.curFunc.NamedResults) == 1 { nr := e.curFunc.NamedResults[0] nrt := e.namedResultType(nr) e.nextReg++ tmp := "%nr" | irItoa(e.nextReg) e.w(" ") ; e.w(tmp) ; e.w(" = load ") ; e.w(nrt) ; e.w(", ptr ") ; e.w(e.regName(nr)) ; e.w("\n") if e.sretType != "" { e.w(" store ") ; e.w(nrt) ; e.w(" ") ; e.w(tmp) ; e.w(", ptr %retval\n") e.w(" ret void\n") } else { e.emitRetWithArenaCopy(nrt, tmp) } } else { aggRT := rt e.nextReg++ agg := "%nr" | irItoa(e.nextReg) e.w(" ") ; e.w(agg) ; e.w(" = alloca ") ; e.w(aggRT) ; e.w("\n") e.w(" store ") ; e.w(aggRT) ; e.w(" zeroinitializer, ptr ") ; e.w(agg) ; e.w("\n") for i, nr := range e.curFunc.NamedResults { nrt := e.namedResultType(nr) e.nextReg++ tmp := "%nr" | irItoa(e.nextReg) e.w(" ") ; e.w(tmp) ; e.w(" = load ") ; e.w(nrt) ; e.w(", ptr ") ; e.w(e.regName(nr)) ; e.w("\n") e.nextReg++ gep := "%nr" | irItoa(e.nextReg) e.w(" ") ; e.w(gep) ; e.w(" = getelementptr ") ; e.w(aggRT) ; e.w(", ptr ") ; e.w(agg) ; e.w(", i32 0, i32 ") ; e.w(irItoa(i)) ; e.w("\n") e.w(" store ") ; e.w(nrt) ; e.w(" ") ; e.w(tmp) ; e.w(", ptr ") ; e.w(gep) ; e.w("\n") } e.nextReg++ rv := "%nr" | irItoa(e.nextReg) e.w(" ") ; e.w(rv) ; e.w(" = load ") ; e.w(aggRT) ; e.w(", ptr ") ; e.w(agg) ; e.w("\n") if e.sretType != "" { e.w(" store ") ; e.w(aggRT) ; e.w(" ") ; e.w(rv) ; e.w(", ptr %retval\n") e.w(" ret void\n") } else { e.emitRetWithArenaCopy(aggRT, rv) } } } else { if e.sretType != "" { e.w(" store ") ; e.w(rt) ; e.w(" zeroinitializer, ptr %retval\n") e.w(" ret void\n") } else { e.w(" ret ") ; e.w(rt) ; e.w(" zeroinitializer\n") } } return } sig := e.curFunc.Signature if len(r.Results) == 1 { typ := e.llvmType(r.Results[0].SSAType()) val := e.operand(r.Results[0]) expectType := typ if sig != nil && sig.Results != nil && sig.Results.Len() == 1 { expectType = e.llvmType(sig.Results.At(0).Typ) } if typ == "void" { typ = frt } if expectType == "void" { expectType = frt } if val == "null" && expectType != "ptr" { val = "zeroinitializer" } else { val = e.coerceInt(val, typ, expectType) } if typ != expectType && val != "zeroinitializer" { if expectType == "ptr" && e.intBits(typ) > 0 { e.nextReg++ rc := "%rc" | irItoa(e.nextReg) e.w(" ") ; e.w(rc) ; e.w(" = inttoptr ") ; e.w(typ) ; e.w(" ") ; e.w(val) ; e.w(" to ptr\n") val = rc typ = "ptr" } else if typ == "ptr" && e.intBits(expectType) > 0 { e.nextReg++ rc := "%rc" | irItoa(e.nextReg) e.w(" ") ; e.w(rc) ; e.w(" = ptrtoint ptr ") ; e.w(val) ; e.w(" to ") ; e.w(expectType) ; e.w("\n") val = rc typ = expectType } if typ != expectType { val = "zeroinitializer" } } if e.sretType != "" { e.w(" store ") ; e.w(expectType) ; e.w(" ") ; e.w(val) ; e.w(", ptr %retval\n") e.w(" ret void\n") } else { e.emitRetWithArenaCopy(expectType, val) } return } var expectTypes []string if sig != nil && sig.Results != nil { for i := 0; i < sig.Results.Len(); i++ { push(expectTypes, e.resolveResultType(sig.Results.At(i).Typ)) } } rtSegs := []string{"{"} for i := 0; i < len(r.Results); i++ { res := r.Results[i] if i > 0 { push(rtSegs, ", ") } if i < len(expectTypes) { push(rtSegs, expectTypes[i]) } else { push(rtSegs, e.llvmType(res.SSAType())) } } push(rtSegs, "}") retType := joinStrs(rtSegs) prev := "undef" for i := 0; i < len(r.Results); i++ { res := r.Results[i] valType := e.llvmType(res.SSAType()) valOp := e.operand(res) elemType := valType if i < len(expectTypes) { elemType = expectTypes[i] if valType != elemType && len(valType) > 0 && valType[0] == '{' { e.nextReg++ ex := "%rv" | irItoa(e.nextReg) e.w(" ") ; e.w(ex) ; e.w(" = extractvalue ") ; e.w(valType) ; e.w(" ") ; e.w(valOp) ; e.w(", 0\n") valOp = ex valType = elemType } if valType == "ptr" && len(elemType) > 0 && elemType[0] == '{' { e.nextReg++ ld := "%rv" | irItoa(e.nextReg) e.w(" ") ; e.w(ld) ; e.w(" = load ") ; e.w(elemType) ; e.w(", ptr ") ; e.w(valOp) ; e.w("\n") valOp = ld valType = elemType } if valOp == "null" && elemType != "ptr" { valOp = "zeroinitializer" } else if (elemType == "double" || elemType == "float") && isConstOperand(valOp) { valOp = ensureFloatLit(valOp) } else if (elemType == "double" || elemType == "float") && e.intBits(valType) > 0 { valOp = e.intToFloat(valOp, valType, elemType) } else { valOp = e.coerceInt(valOp, valType, elemType) } if valType == "double" && elemType == "float" { e.nextReg++ rc := "%rc" | irItoa(e.nextReg) e.w(" ") ; e.w(rc) ; e.w(" = fptrunc double ") ; e.w(valOp) ; e.w(" to float\n") valOp = rc } else if valType == "float" && elemType == "double" { e.nextReg++ rc := "%rc" | irItoa(e.nextReg) e.w(" ") ; e.w(rc) ; e.w(" = fpext float ") ; e.w(valOp) ; e.w(" to double\n") valOp = rc } } e.nextReg++ cur := "%rv" | irItoa(e.nextReg) e.w(" ") e.w(cur) e.w(" = insertvalue ") e.w(retType) e.w(" ") e.w(prev) e.w(", ") e.w(elemType) e.w(" ") e.w(valOp) e.w(", ") e.w(irItoa(i)) e.w("\n") prev = cur } if e.sretType != "" { e.w(" store ") ; e.w(retType) ; e.w(" ") ; e.w(prev) ; e.w(", ptr %retval\n") e.w(" ret void\n") } else { e.emitRetWithArenaCopy(retType, prev) } } func (e *irEmitter) emitJump(j *SSAJump) { blk := j.InstrBlock() if blk == nil { return } if len(blk.Succs) > 0 { target := blk.Succs[0] if blk.ScopeID > 0 && target.ScopeID != blk.ScopeID && !blockHasReturn(target) { e.emitScopeFreesAt(blk.ScopeID) } e.w(" br label ") e.w(e.blockLabel(target)) e.w("\n") } } func (e *irEmitter) emitIf(i *SSAIf) { blk := i.InstrBlock() if i.Cond == nil { if len(blk.Succs) >= 2 { e.w(" br label ") e.w(e.blockLabel(blk.Succs[1])) e.w("\n") } else { e.w(" unreachable\n") } return } cond := e.operand(i.Cond) condType := e.llvmType(i.Cond.SSAType()) if at, ok := e.allocTypes[i.Cond]; ok { condType = at } if bop, ok := i.Cond.(*SSABinOp); ok && isComparisonOp(bop.Op) { condType = "i1" } if condType != "i1" && condType != "" && condType != "void" { e.nextReg++ truncReg := "%ift" | irItoa(e.nextReg) if condType == "ptr" { e.w(" ") ; e.w(truncReg) ; e.w(" = icmp ne ptr ") ; e.w(cond) ; e.w(", null\n") } else if len(condType) > 0 && condType[0] == '{' { curType := condType curVal := cond for len(curType) > 0 && curType[0] == '{' { e.nextReg++ extReg := "%ife" | irItoa(e.nextReg) e.w(" ") ; e.w(extReg) ; e.w(" = extractvalue ") ; e.w(curType) ; e.w(" ") ; e.w(curVal) ; e.w(", 0\n") curType = e.structField0Type(curType) curVal = extReg } e.w(" ") ; e.w(truncReg) ; e.w(" = icmp ne ptr ") ; e.w(curVal) ; e.w(", null\n") } else { e.w(" ") ; e.w(truncReg) ; e.w(" = trunc ") ; e.w(condType) ; e.w(" ") ; e.w(cond) ; e.w(" to i1\n") } cond = truncReg } if len(blk.Succs) >= 2 { e.w(" br i1 ") e.w(cond) e.w(", label ") e.w(e.blockLabel(blk.Succs[0])) e.w(", label ") e.w(e.blockLabel(blk.Succs[1])) e.w("\n") } } func (e *irEmitter) emitConvert(c *SSAConvert) { reg := e.regName(c) srcType := e.llvmType(c.X.SSAType()) dstType := e.llvmType(c.SSAType()) val := e.operand(c.X) if srcType != "ptr" { resolved := e.resolvedType(c.X, srcType) if resolved != srcType { srcType = resolved } } if srcType == "void" || c.X.SSAType() == nil { if dstType == "ptr" { e.valName[c] = "null" } else { e.valName[c] = "zeroinitializer" } return } if srcType == dstType { e.valName[c] = val e.allocTypes[c] = srcType return } // Constant int->int conversions fold at emission. Materializing an // untyped constant at its default width and extending loses high bits // (e.g. uint64(0xFFFFFFFFFFFFFFFF) emitted as zext i32 -1 -> 0xFFFFFFFF). if k, ok := c.X.(*SSAConst); ok { if ci, ok2 := k.val.(*ConstInt); ok2 { sb := e.intBits(srcType) db := e.intBits(dstType) if sb > 0 && db > 0 { v := ci.V if db < 64 { v = v & ((int64(1) << uint32(db)) - 1) } e.valName[c] = irItoa64(v) return } } } if dstType == e.ifaceType() { _, dstIsIface := SafeUnderlying(c.SSAType()).(*TCInterface) if dstIsIface { var valPtr string if srcType == "ptr" { valPtr = val } else if e.isScalarType(srcType) { ipt := e.intptrType() srcBits := e.intBits(srcType) dstBits := e.intBits(ipt) if srcBits > dstBits { // Value doesn't fit in intptr - heap allocate sz := e.nextReg2("cv") e.w(" ") ; e.w(sz) ; e.w(" = ptrtoint ptr getelementptr (") ; e.w(srcType) ; e.w(", ptr null, i32 1) to ") ; e.w(ipt) ; e.w("\n") valPtr = e.nextReg2("cv") e.w(" ") ; e.w(valPtr) ; e.w(" = call ptr @runtime.alloc(") ; e.w(ipt) ; e.w(" ") ; e.w(sz) ; e.w(", ptr null, ptr null)\n") e.w(" store ") ; e.w(srcType) ; e.w(" ") ; e.w(val) ; e.w(", ptr ") ; e.w(valPtr) ; e.w("\n") e.declareRuntime("runtime.alloc", "ptr", ipt | ", ptr") } else { ext := e.nextReg2("cv") if srcBits == dstBits { ext = val } else { e.w(" ") ; e.w(ext) ; e.w(" = zext ") ; e.w(srcType) ; e.w(" ") ; e.w(val) ; e.w(" to ") ; e.w(ipt) ; e.w("\n") } valPtr = e.nextReg2("cv") e.w(" ") ; e.w(valPtr) ; e.w(" = inttoptr ") ; e.w(ipt) ; e.w(" ") ; e.w(ext) ; e.w(" to ptr\n") } } else { ipt := e.intptrType() sz := e.nextReg2("cv") e.w(" ") ; e.w(sz) ; e.w(" = ptrtoint ptr getelementptr (") ; e.w(srcType) ; e.w(", ptr null, i32 1) to ") ; e.w(ipt) ; e.w("\n") valPtr = e.nextReg2("cv") e.w(" ") ; e.w(valPtr) ; e.w(" = call ptr @runtime.alloc(") ; e.w(ipt) ; e.w(" ") ; e.w(sz) ; e.w(", ptr null, ptr null)\n") e.w(" store ") ; e.w(srcType) ; e.w(" ") ; e.w(val) ; e.w(", ptr ") ; e.w(valPtr) ; e.w("\n") e.declareRuntime("runtime.alloc", "ptr", ipt | ", ptr") } typeid := e.typeIDHash(c.X.SSAType()) t1 := e.nextReg2("cv") e.w(" ") ; e.w(t1) ; e.w(" = insertvalue " | e.ifaceType() | " undef, " | e.intptrType() | " ") ; e.w(typeid) ; e.w(", 0\n") e.w(" ") ; e.w(reg) ; e.w(" = insertvalue " | e.ifaceType() | " ") ; e.w(t1) ; e.w(", ptr ") ; e.w(valPtr) ; e.w(", 1\n") return } } srcIsInt := false if b, ok := SafeUnderlying(c.X.SSAType()).(*Basic); ok { srcIsInt = b.Info&IsInteger != 0 } if !srcIsInt && len(srcType) > 0 && srcType[0] == 'i' { srcIsInt = true } if e.isStringLike(c.SSAType()) && srcIsInt { if k, ok := c.X.(*SSAConst); ok { rv := int64(0) if ci, ok2 := k.val.(*ConstInt); ok2 { rv = ci.V } s := runeToUTF8(rune(rv)) idx := e.addStringConst(s) ipt := e.intptrType() slen := irItoa64(int64(len(s))) e.valName[c] = "{ ptr " | e.strConstGlobal(idx) | ", " | ipt | " " | slen | ", " | ipt | " " | slen | " }" return } e.declareRuntime("runtime.stringFromUnicode", e.sliceType(), "i32") srcVal := val if srcType != "i32" { e.nextReg++ srcVal = "%cv" | irItoa(e.nextReg) if e.typeBits(c.X.SSAType()) < 32 { e.w(" ") ; e.w(srcVal) ; e.w(" = sext ") ; e.w(srcType) ; e.w(" ") ; e.w(val) ; e.w(" to i32\n") } else if e.typeBits(c.X.SSAType()) > 32 { e.w(" ") ; e.w(srcVal) ; e.w(" = trunc ") ; e.w(srcType) ; e.w(" ") ; e.w(val) ; e.w(" to i32\n") } } e.w(" ") ; e.w(reg) ; e.w(" = call ") ; e.w(e.sliceType()) ; e.w(" @runtime.stringFromUnicode(i32 ") ; e.w(srcVal) ; e.w(", ptr null)\n") return } srcIsSlice := false if _, slOK := SafeUnderlying(c.X.SSAType()).(*Slice); slOK { srcIsSlice = true } else if e.isStringLike(c.X.SSAType()) { srcIsSlice = true } else if srcType == e.sliceType() { srcIsSlice = true } if srcIsSlice { if _, arOK := SafeUnderlying(c.SSAType()).(*Array); arOK { dp := e.nextReg2("cv") e.w(" ") ; e.w(dp) ; e.w(" = extractvalue ") ; e.w(srcType) ; e.w(" ") ; e.w(val) ; e.w(", 0\n") e.w(" ") ; e.w(reg) ; e.w(" = load ") ; e.w(dstType) ; e.w(", ptr ") ; e.w(dp) ; e.w("\n") return } } op := e.conversionOp(c.X.SSAType(), c.SSAType()) srcBitsLLVM := e.intBits(srcType) dstBitsLLVM := e.intBits(dstType) if (op == "sext" || op == "zext") && srcBitsLLVM > 0 && dstBitsLLVM > 0 && srcBitsLLVM > dstBitsLLVM { op = "trunc" } else if op == "trunc" && srcBitsLLVM > 0 && dstBitsLLVM > 0 && srcBitsLLVM < dstBitsLLVM { op = "sext" } srcIsFloat := srcType == "double" || srcType == "float" dstIsFloat := dstType == "double" || dstType == "float" if op == "trunc" && srcIsFloat && !dstIsFloat { op = "fptosi" } else if op == "trunc" && !srcIsFloat && dstIsFloat { op = "sitofp" } else if (op == "sext" || op == "zext") && !srcIsFloat && dstIsFloat { op = "sitofp" } else if (op == "sext" || op == "zext") && srcIsFloat && !dstIsFloat { op = "fptosi" } else if op == "bitcast" && srcIsFloat != dstIsFloat { if srcIsFloat { op = "fptosi" } else { op = "sitofp" } } else if (op == "sext" || op == "zext" || op == "trunc") && srcIsFloat && dstIsFloat { if e.intBits(srcType) < e.intBits(dstType) { op = "fpext" } else { op = "fptrunc" } } if op == "ptrtoint" && e.intBits(dstType) == 0 { if dstType == e.ifaceType() { typeid := e.typeIDHash(c.X.SSAType()) t1 := e.nextReg2("cv") e.w(" ") ; e.w(t1) ; e.w(" = insertvalue " | e.ifaceType() | " undef, " | e.intptrType() | " ") ; e.w(typeid) ; e.w(", 0\n") e.w(" ") ; e.w(reg) ; e.w(" = insertvalue " | e.ifaceType() | " ") ; e.w(t1) ; e.w(", ptr ") ; e.w(val) ; e.w(", 1\n") } else { e.valName[c] = "zeroinitializer" } return } if op == "inttoptr" && e.intBits(srcType) == 0 { if srcType == e.ifaceType() { e.nextReg++ r := "%cv" | irItoa(e.nextReg) e.w(" ") ; e.w(r) ; e.w(" = extractvalue ") ; e.w(srcType) ; e.w(" ") ; e.w(val) ; e.w(", 1\n") e.valName[c] = r } else { e.valName[c] = "null" } return } e.w(" ") e.w(reg) e.w(" = ") e.w(op) e.w(" ") e.w(srcType) e.w(" ") e.w(val) e.w(" to ") e.w(dstType) e.w("\n") if e.intBits(dstType) > 0 || dstType == "ptr" { e.setRegType(c, reg, dstType) } } func (e *irEmitter) emitChangeType(c *SSAChangeType) { srcType := e.llvmType(c.X.SSAType()) dstType := e.llvmType(c.SSAType()) if at, ok := e.allocTypes[c.X]; ok && at != "ptr" && at != "void" { srcType = at } if srcType == dstType || (srcType == "ptr" && dstType == "ptr") { e.valName[c] = e.operand(c.X) return } reg := e.regName(c) val := e.operand(c.X) e.nextReg++ tmp := "%ct" | irItoa(e.nextReg) e.w(" ") ; e.w(tmp) ; e.w(" = alloca ") ; e.w(dstType) ; e.w("\n") e.w(" store ") ; e.w(srcType) ; e.w(" ") ; e.w(val) ; e.w(", ptr ") ; e.w(tmp) ; e.w("\n") e.w(" ") ; e.w(reg) ; e.w(" = load ") ; e.w(dstType) ; e.w(", ptr ") ; e.w(tmp) ; e.w("\n") } func (e *irEmitter) resolveFieldAddrBase(f *SSAFieldAddr) (s string) { baseType := e.llvmType(f.X.SSAType()) if p, ok := SafeUnderlying(f.X.SSAType()).(*Pointer); ok && p.Base != nil { elem := p.Base if p2, ok2 := SafeUnderlying(elem).(*Pointer); ok2 && p2.Base != nil { baseType = e.llvmType(p2.Base) } else { baseType = e.llvmType(elem) } } if at, ok := e.allocTypes[f.X]; ok && at != "ptr" && at != "void" { baseType = at } return baseType } func (e *irEmitter) emitFieldAddr(f *SSAFieldAddr) { reg := e.regName(f) baseType := e.resolveFieldAddrBase(f) base := e.operand(f.X) if uop, ok := f.X.(*SSAUnOp); ok { _, isFreeVar := uop.X.(*SSAFreeVar) addrType := e.llvmType(uop.X.SSAType()) useSource := false if p, ok2 := SafeUnderlying(uop.X.SSAType()).(*Pointer); ok2 && p.Base != nil { elem := p.Base if _, ok3 := SafeUnderlying(elem).(*Pointer); ok3 { // double-pointer: alloca holds **T, keep the loaded *T as base } else { baseType = e.llvmType(elem) useSource = true } } if useSource && !isFreeVar && addrType == "ptr" && baseType != "ptr" && baseType != "void" { base = e.operand(uop.X) } } if baseType == "ptr" || baseType == "void" { e.w(" ") ; e.w(reg) ; e.w(" = getelementptr inbounds i8, ptr ") ; e.w(base) e.w(", i32 0\n") return } e.w(" ") e.w(reg) e.w(" = getelementptr inbounds ") e.w(baseType) e.w(", ptr ") e.w(base) e.w(", i32 0, i32 ") e.w(irItoa(f.Field)) e.w("\n") } func (e *irEmitter) emitIndexAddr(idx *SSAIndexAddr) { reg := e.regName(idx) elemType := e.llvmType(idx.SSAType()) if p, ok := SafeUnderlying(idx.SSAType()).(*Pointer); ok { elemType = e.llvmType(p.Base) } base := e.operand(idx.X) index := e.operand(idx.Index) baseType := e.llvmType(idx.X.SSAType()) resolvedBase := e.resolvedType(idx.X, baseType) _, isSlice := SafeUnderlying(idx.X.SSAType()).(*Slice) if !isSlice { if b, ok := SafeUnderlying(idx.X.SSAType()).(*Basic); ok && b.Info&IsString != 0 { isSlice = true } } if at, ok4 := e.allocTypes[idx.X]; ok4 && len(at) > 0 && at[0] == '[' { isSlice = false } else if !isSlice && (baseType == e.sliceType() || resolvedBase == e.sliceType()) { isSlice = true } if isSlice && (elemType == "void" || elemType == "i8" || elemType == "ptr") { baseU := SafeUnderlying(idx.X.SSAType()) if sl, ok2 := baseU.(*Slice); ok2 && sl.Elem != nil { elemType = e.llvmType(sl.Elem) } else if b, ok3 := baseU.(*Basic); ok3 && b.Info&IsString != 0 { elemType = "i8" } if elemType == "void" { elemType = "i8" } } idxType := e.resolvedType(idx.Index, e.llvmType(idx.Index.SSAType())) // GEP indices are interpreted as signed: a raw i8/i16 index from a // uint8/uint16 value >= half-range goes negative and reads garbage. // Normalize narrow indices to i32 with the extension matching the // source signedness. if idxType == "i8" || idxType == "i16" { ext := "sext" if b, okx := SafeUnderlying(idx.Index.SSAType()).(*Basic); okx && b.Info&IsUnsigned != 0 { ext = "zext" } e.nextReg++ widened := "%ixw" | irItoa(e.nextReg) e.w(" ") ; e.w(widened) ; e.w(" = ") ; e.w(ext) ; e.w(" ") ; e.w(idxType) ; e.w(" ") ; e.w(index) ; e.w(" to i32\n") index = widened idxType = "i32" } if isSlice { e.nextReg++ dataPtr := "%sp" | irItoa(e.nextReg) e.w(" ") e.w(dataPtr) e.w(" = extractvalue ") e.w(e.sliceType()) e.w(" ") e.w(base) e.w(", 0\n") e.w(" ") e.w(reg) e.w(" = getelementptr inbounds ") e.w(elemType) e.w(", ptr ") e.w(dataPtr) e.w(", ") e.w(idxType) e.w(" ") e.w(index) e.w("\n") if elemType != "i8" { e.allocTypes[idx] = elemType } return } arr, isArray := SafeUnderlying(idx.X.SSAType()).(*Array) if !isArray { if at, ok4 := e.allocTypes[idx.X]; ok4 && len(at) > 0 && at[0] == '[' { isArray = true } } if !isArray { if alloc, ok4 := idx.X.(*SSAAlloc); ok4 { if p, ok5 := SafeUnderlying(alloc.SSAType()).(*Pointer); ok5 && p.Base != nil { if ar, ok6 := SafeUnderlying(p.Base).(*Array); ok6 && ar.Len > 0 { isArray = true arrT := e.llvmType(p.Base) if len(arrT) > 0 && arrT[0] == '[' { e.allocTypes[alloc] = arrT } } } } } if !isArray { if load, ok4 := idx.X.(*SSAUnOp); ok4 && load.Op == OpMul { if at, ok5 := e.allocTypes[load.X]; ok5 && len(at) > 0 && at[0] == '[' { isArray = true e.allocTypes[idx.X] = at allocBase := e.operand(load.X) e.w(" ") ; e.w(reg) ; e.w(" = getelementptr inbounds ") e.w(at) ; e.w(", ptr ") ; e.w(allocBase) ; e.w(", i32 0, ") e.w(e.llvmType(idx.Index.SSAType())) ; e.w(" ") ; e.w(index) ; e.w("\n") aet := e.arrayElemType(at) if aet != "" { e.setRegType(idx, reg, aet) } return } } } if isArray { arrType := e.llvmType(idx.X.SSAType()) if at, ok4 := e.allocTypes[idx.X]; ok4 && len(at) > 0 && at[0] == '[' { arrType = at } if arrType == "ptr" || arrType == "void" { if p, ok4 := SafeUnderlying(idx.X.SSAType()).(*Pointer); ok4 && p.Base != nil { arrType = e.llvmType(p.Base) } } _, isGlobal := idx.X.(*SSAGlobal) _, isAlloc := idx.X.(*SSAAlloc) _, isFieldAddr := idx.X.(*SSAFieldAddr) if isGlobal || isAlloc || isFieldAddr { _ = arr e.w(" ") ; e.w(reg) ; e.w(" = getelementptr inbounds ") e.w(arrType) ; e.w(", ptr ") ; e.w(base) ; e.w(", i32 0, ") e.w(idxType) ; e.w(" ") ; e.w(index) ; e.w("\n") return } if load, ok4 := idx.X.(*SSAUnOp); ok4 && load.Op == OpMul { _, srcAlloc := load.X.(*SSAAlloc) _, srcGlobal := load.X.(*SSAGlobal) _, srcField := load.X.(*SSAFieldAddr) // A load of an index/field-addr result is a load from an // addressable chain (e.g. global[i][j]): forwarding to the // source GEP keeps the element addressable so mutations // through method receivers hit the original, not a spill copy. _, srcIndex := load.X.(*SSAIndexAddr) if srcAlloc || srcGlobal || srcField || srcIndex { // Index into a load-of-array: GEP the source address // instead of spilling a copy. The spill alloca lands in // the current block - inside a loop that grows the frame // per iteration until the stack is gone. allocBase := e.operand(load.X) e.w(" ") ; e.w(reg) ; e.w(" = getelementptr inbounds ") e.w(arrType) ; e.w(", ptr ") ; e.w(allocBase) ; e.w(", i32 0, ") e.w(idxType) ; e.w(" ") ; e.w(index) ; e.w("\n") aet0 := e.arrayElemType(arrType) if aet0 != "" { e.setRegType(idx, reg, aet0) } return } } e.nextReg++ arrPtr := "%ai" | irItoa(e.nextReg) e.w(" ") ; e.w(arrPtr) ; e.w(" = alloca ") ; e.w(arrType) ; e.w("\n") e.w(" store ") ; e.w(arrType) ; e.w(" ") ; e.w(base) ; e.w(", ptr ") ; e.w(arrPtr) ; e.w("\n") e.w(" ") ; e.w(reg) ; e.w(" = getelementptr inbounds ") e.w(arrType) ; e.w(", ptr ") ; e.w(arrPtr) ; e.w(", i32 0, ") e.w(idxType) ; e.w(" ") ; e.w(index) ; e.w("\n") aet := e.arrayElemType(arrType) if aet != "" { e.setRegType(idx, reg, aet) } return } if len(elemType) > 0 && elemType[0] == '[' { aet := e.arrayElemType(elemType) e.w(" ") ; e.w(reg) ; e.w(" = getelementptr inbounds ") e.w(elemType) ; e.w(", ptr ") ; e.w(base) ; e.w(", i32 0, ") e.w(idxType) ; e.w(" ") ; e.w(index) ; e.w("\n") e.setRegType(idx, reg, aet) return } e.w(" ") e.w(reg) e.w(" = getelementptr inbounds ") e.w(elemType) e.w(", ptr ") e.w(base) e.w(", ") e.w(idxType) e.w(" ") e.w(index) e.w("\n") } func (e *irEmitter) emitExtract(ex *SSAExtract) { reg := e.regName(ex) tupType := e.llvmType(ex.Tuple.SSAType()) if at, ok := e.allocTypes[ex.Tuple]; ok { tupType = at } if n, ok := ex.Tuple.(*SSANext); ok { rangeInstr := n.Iter.(*SSARange) if mt, ok2 := SafeUnderlying(rangeInstr.X.SSAType()).(*TCMap); ok2 { tupType = "{i1, " | e.llvmType(mt.Key) | ", " | e.llvmType(mt.Elem) | "}" } else if arr, ok3 := SafeUnderlying(rangeInstr.X.SSAType()).(*Array); ok3 { tupType = "{i1, i32, " | e.llvmType(arr.Elem) | "}" } else if p, ok4 := SafeUnderlying(rangeInstr.X.SSAType()).(*Pointer); ok4 { if ar, ok5 := SafeUnderlying(p.Base).(*Array); ok5 { tupType = "{i1, i32, " | e.llvmType(ar.Elem) | "}" } } else { et := "i32" if sl, ok6 := SafeUnderlying(rangeInstr.X.SSAType()).(*Slice); ok6 { et = e.llvmType(sl.Elem) } tupType = "{i1, i32, " | et | "}" } } val := e.operand(ex.Tuple) // Track extracted element type for downstream alloc/store consistency extractedType := extractTupleField(tupType, ex.Index) if extractedType != "" { ssaType := e.llvmType(ex.SSAType()) if extractedType != ssaType { e.allocTypes[ex] = extractedType } } if tupType == "ptr" || tupType == "void" { elemType := e.llvmType(ex.SSAType()) if elemType == "void" { elemType = "ptr" } e.nextReg++ castReg := "%ev" | irItoa(e.nextReg) e.w(" ") ; e.w(castReg) ; e.w(" = getelementptr inbounds i8, ptr ") ; e.w(val) ; e.w(", i32 0\n") e.w(" ") ; e.w(reg) ; e.w(" = load ") ; e.w(elemType) ; e.w(", ptr ") ; e.w(castReg) ; e.w("\n") e.allocTypes[ex] = elemType return } e.w(" ") e.w(reg) e.w(" = extractvalue ") e.w(tupType) e.w(" ") e.w(val) e.w(", ") e.w(irItoa(ex.Index)) e.w("\n") } func (e *irEmitter) sextToIpt(val SSAValue, op string) (s string) { ipt := e.intptrType() if val == nil { return op } valType := e.llvmType(val.SSAType()) if valType == ipt { return op } e.nextReg++ ext := "%sx" | irItoa(e.nextReg) srcBits := e.intBits(valType) dstBits := e.intBits(ipt) if srcBits > dstBits { e.w(" ") ; e.w(ext) ; e.w(" = trunc ") ; e.w(valType) ; e.w(" ") ; e.w(op) ; e.w(" to ") ; e.w(ipt) ; e.w("\n") return ext } extOp := "sext" if b, ok := SafeUnderlying(val.SSAType()).(*Basic); ok && b.Info&IsUnsigned != 0 { extOp = "zext" } e.w(" ") ; e.w(ext) ; e.w(" = ") ; e.w(extOp) ; e.w(" ") ; e.w(valType) ; e.w(" ") ; e.w(op) ; e.w(" to ") ; e.w(ipt) ; e.w("\n") return ext } func (e *irEmitter) emitMakeSlice(m *SSAMakeSlice) { reg := e.regName(m) ipt := e.intptrType() sty := e.sliceType() lenOp := e.sextToIpt(m.Len, e.operand(m.Len)) capOp := lenOp if m.Cap != nil { capOp = e.sextToIpt(m.Cap, e.operand(m.Cap)) } var dataPtr string if m.Data != nil { dataPtr = e.operand(m.Data) } else { elemType := "i8" if sl, ok := SafeUnderlying(m.SSAType()).(*Slice); ok { elemType = e.llvmType(sl.Elem) } else if b, ok2 := SafeUnderlying(m.SSAType()).(*Basic); ok2 && b.Info&IsString != 0 { elemType = "i8" } e.nextReg++ elemSz := "%ms" | irItoa(e.nextReg) e.w(" ") e.w(elemSz) e.w(" = ptrtoint ptr getelementptr (") e.w(elemType) e.w(", ptr null, i32 1) to ") e.w(ipt) e.w("\n") e.nextReg++ allocSz := "%ms" | irItoa(e.nextReg) e.w(" ") e.w(allocSz) e.w(" = mul ") e.w(ipt) e.w(" ") e.w(elemSz) e.w(", ") e.w(capOp) e.w("\n") e.nextReg++ dataPtr = "%ms" | irItoa(e.nextReg) e.w(" ") e.w(dataPtr) e.w(" = call ptr @runtime.alloc(") e.w(ipt) e.w(" ") e.w(allocSz) e.w(", ptr null, ptr null)\n") e.declareRuntime("runtime.alloc", "ptr", ipt | ", ptr") e.scopeTrackAlloc(dataPtr) } e.nextReg++ s1 := "%ms" | irItoa(e.nextReg) e.w(" ") e.w(s1) e.w(" = insertvalue ") e.w(sty) e.w(" undef, ptr ") e.w(dataPtr) e.w(", 0\n") e.nextReg++ s2 := "%ms" | irItoa(e.nextReg) e.w(" ") e.w(s2) e.w(" = insertvalue ") e.w(sty) e.w(" ") e.w(s1) e.w(", ") e.w(ipt) e.w(" ") e.w(lenOp) e.w(", 1\n") e.w(" ") e.w(reg) e.w(" = insertvalue ") e.w(sty) e.w(" ") e.w(s2) e.w(", ") e.w(ipt) e.w(" ") e.w(capOp) e.w(", 2\n") } func (e *irEmitter) emitSliceOp(s *SSASlice) { reg := e.regName(s) ipt := e.intptrType() sty := e.sliceType() src := e.operand(s.X) var oldPtr, oldLen, oldCap string srcType := SafeUnderlying(s.X.SSAType()) ptrToArr := false if p, ok := srcType.(*Pointer); ok && p.Base != nil { if arr, ok2 := SafeUnderlying(p.Base).(*Array); ok2 { oldPtr = src oldLen = irItoa64(arr.Len) oldCap = oldLen ptrToArr = true } } if !ptrToArr { if arr, ok := srcType.(*Array); ok { // Heap-allocate the array copy: the resulting slice can escape // (e.g. returned), so a stack alloca would dangle. arrType := e.llvmType(s.X.SSAType()) e.nextReg++ sz := "%sl" | irItoa(e.nextReg) e.w(" ") ; e.w(sz) ; e.w(" = ptrtoint ptr getelementptr (") ; e.w(arrType) ; e.w(", ptr null, i32 1) to ") ; e.w(ipt) ; e.w("\n") e.nextReg++ tmp := "%sl" | irItoa(e.nextReg) e.w(" ") ; e.w(tmp) ; e.w(" = call ptr @runtime.alloc(") ; e.w(ipt) ; e.w(" ") ; e.w(sz) ; e.w(", ptr null, ptr null)\n") e.declareRuntime("runtime.alloc", "ptr", ipt | ", ptr") e.w(" store ") ; e.w(arrType) ; e.w(" ") ; e.w(src) ; e.w(", ptr ") ; e.w(tmp) ; e.w("\n") oldPtr = tmp oldLen = irItoa64(arr.Len) oldCap = oldLen } else { e.nextReg++ oldPtr = "%sl" | irItoa(e.nextReg) e.w(" ") e.w(oldPtr) e.w(" = extractvalue ") e.w(sty) e.w(" ") e.w(src) e.w(", 0\n") e.nextReg++ oldLen = "%sl" | irItoa(e.nextReg) e.w(" ") e.w(oldLen) e.w(" = extractvalue ") e.w(sty) e.w(" ") e.w(src) e.w(", 1\n") e.nextReg++ oldCap = "%sl" | irItoa(e.nextReg) e.w(" ") e.w(oldCap) e.w(" = extractvalue ") e.w(sty) e.w(" ") e.w(src) e.w(", 2\n") } } low := "0" if s.Low != nil { low = e.sliceIdxToIpt(s.Low, ipt) } high := oldLen if s.High != nil { high = e.sliceIdxToIpt(s.High, ipt) } _ = oldCap if s.Max != nil { _ = e.sliceIdxToIpt(s.Max, ipt) } elemType := "i8" if sl, ok := SafeUnderlying(s.X.SSAType()).(*Slice); ok { elemType = e.llvmType(sl.Elem) } else if ar, ok2 := SafeUnderlying(s.X.SSAType()).(*Array); ok2 { elemType = e.llvmType(ar.Elem) } else if p, ok3 := SafeUnderlying(s.X.SSAType()).(*Pointer); ok3 && p.Base != nil { if ar2, ok4 := SafeUnderlying(p.Base).(*Array); ok4 { elemType = e.llvmType(ar2.Elem) } } newPtr := e.nextReg2("sl") e.w(" ") ; e.w(newPtr) ; e.w(" = getelementptr inbounds ") ; e.w(elemType) e.w(", ptr ") ; e.w(oldPtr) ; e.w(", ") ; e.w(ipt) ; e.w(" ") ; e.w(low) ; e.w("\n") newLen := e.nextReg2("sl") e.w(" ") ; e.w(newLen) ; e.w(" = sub ") ; e.w(ipt) ; e.w(" ") ; e.w(high) ; e.w(", ") ; e.w(low) ; e.w("\n") newCap := e.nextReg2("sl") e.w(" ") ; e.w(newCap) ; e.w(" = sub ") ; e.w(ipt) ; e.w(" ") ; e.w(oldCap) ; e.w(", ") ; e.w(low) ; e.w("\n") s1 := e.nextReg2("sl") e.w(" ") ; e.w(s1) ; e.w(" = insertvalue ") ; e.w(sty) ; e.w(" undef, ptr ") ; e.w(newPtr) ; e.w(", 0\n") s2 := e.nextReg2("sl") e.w(" ") ; e.w(s2) ; e.w(" = insertvalue ") ; e.w(sty) ; e.w(" ") ; e.w(s1) ; e.w(", ") ; e.w(ipt) ; e.w(" ") ; e.w(newLen) ; e.w(", 1\n") e.w(" ") ; e.w(reg) ; e.w(" = insertvalue ") ; e.w(sty) ; e.w(" ") ; e.w(s2) ; e.w(", ") ; e.w(ipt) ; e.w(" ") ; e.w(newCap) ; e.w(", 2\n") } func (e *irEmitter) sliceIdxToIpt(val SSAValue, ipt string) (s string) { operandStr := e.operand(val) valType := e.llvmType(val.SSAType()) if valType == ipt { return operandStr } e.nextReg++ ext := "%sl" | irItoa(e.nextReg) srcBits := e.intBits(valType) dstBits := e.intBits(ipt) if srcBits > dstBits { e.w(" ") ; e.w(ext) ; e.w(" = trunc ") ; e.w(valType) ; e.w(" ") ; e.w(operandStr) ; e.w(" to ") ; e.w(ipt) ; e.w("\n") return ext } op := "sext" if b, ok2 := SafeUnderlying(val.SSAType()).(*Basic); ok2 && b.Info&IsUnsigned != 0 { op = "zext" } e.w(" ") ; e.w(ext) ; e.w(" = ") ; e.w(op) ; e.w(" ") ; e.w(valType) ; e.w(" ") ; e.w(operandStr) ; e.w(" to ") ; e.w(ipt) ; e.w("\n") return ext } func (e *irEmitter) emitPanic(p *SSAPanic) { if c, ok := p.X.(*SSAConst); ok && e.isStringLike(c.SSAType()) { arg := e.operand(c) sty := e.sliceType() e.w(" call void @runtime._panicstr(") ; e.w(sty) ; e.w(" ") ; e.w(arg) ; e.w(", ptr null)\n") e.declareRuntime("runtime._panicstr", "void", sty) e.w(" unreachable\n") return } ity := e.ifaceType() e.w(" call void @runtime._panic(" | ity | " zeroinitializer, ptr null)\n") e.declareRuntime("runtime._panic", "void", ity) e.w(" unreachable\n") } func (e *irEmitter) operandNoSideEffect(v SSAValue) (s string) { if v == nil { return "zeroinitializer" } if c, ok := v.(*SSAConst); ok { return e.constOperand(c) } if n, ok := e.valName[v]; ok { return n } return "" } func (e *irEmitter) operand(v SSAValue) (s string) { if v == nil { return "zeroinitializer" } if c, ok := v.(*SSAConst); ok { return e.constOperand(c) } if b, ok := v.(*SSABuiltin); ok { return "@runtime." | b.SSAName() } if f, ok := v.(*SSAFunction); ok { if !e.isPkgFunc(f) { // Func used as a value: ensure the symbol is declared, the // call-emission path is never reached for it. e.declareExternalFunc(f) } return "{ ptr null, ptr " | e.funcSymbol(f) | " }" } if g, ok := v.(*SSAGlobal); ok { e.declareExternalGlobal(g) return e.globalName(g) } return e.regName(v) } func (e *irEmitter) constOperand(c *SSAConst) (s string) { if c.val == nil { if c.typ == nil { return "null" } typ := e.llvmType(c.typ) if typ == "ptr" { return "null" } if typ == "i1" { return "false" } return "zeroinitializer" } b := underlyingBasic(c.typ) if b != nil { switch b.Kind { case Bool, UntypedBool: if cb, ok := c.val.(*ConstBool); ok { if cb.V { return "true" } return "false" } sv := c.val.String() if sv == "true" { return "true" } return "false" case Int8, Int16, Int32, Int64, Uint8, Uint16, Uint32, Uint64, UntypedInt, UntypedRune: if ci, ok := c.val.(*ConstInt); ok { v := ci.V switch b.Kind { case Int8: v = int64(int8(v)) case Uint8: v = int64(uint8(v)) case Int16: v = int64(int16(v)) case Uint16: v = int64(uint16(v)) case Int32: v = int64(int32(v)) case Uint32: v = int64(uint32(v)) case UntypedInt, UntypedRune: if v < -2147483648 || v > 4294967295 { e.allocTypes[c] = "i64" } } return irItoa64(v) } if cf, ok := c.val.(*ConstFloat); ok { return irItoa64(int64(cf.V)) } if cs, ok := c.val.(*ConstStr); ok { if len(cs.S) == 0 { return "zeroinitializer" } idx := e.addStringConst(cs.S) ipt := e.intptrType() slen := irItoa64(int64(len(cs.S))) e.allocTypes[c] = e.sliceType() return "{ ptr " | e.strConstGlobal(idx) | ", " | ipt | " " | slen | ", " | ipt | " " | slen | " }" } return c.val.String() case Float32: if cf, ok := c.val.(*ConstFloat); ok { if cf.Lit != "" { return NormalizeLLVMFloat(cf.Lit) } bits := math.Float64bits(float64(float32(cf.V))) return "0x" | irHex64(bits) } return "0.0" case Float64, UntypedFloat: if cf, ok := c.val.(*ConstFloat); ok { if cf.Lit != "" { return NormalizeLLVMFloat(cf.Lit) } bits := math.Float64bits(cf.V) return "0x" | irHex64(bits) } return "0.0" case TCString, UntypedString: if cs, ok := c.val.(*ConstStr); ok { if len(cs.S) == 0 { return "zeroinitializer" } idx := e.addStringConst(cs.S) ipt := e.intptrType() slen := irItoa64(int64(len(cs.S))) return "{ ptr " | e.strConstGlobal(idx) | ", " | ipt | " " | slen | ", " | ipt | " " | slen | " }" } return "zeroinitializer" } } if c.typ == nil { return c.val.String() } return "zeroinitializer" } func (e *irEmitter) instrOperands(instr SSAInstruction) (ss []SSAValue) { // Reuse scratch slice to avoid per-instruction allocation. e.operandBuf = e.operandBuf[:0] switch i := instr.(type) { case *SSAStore: push(e.operandBuf, i.Addr, i.Val) case *SSAUnOp: push(e.operandBuf, i.X) case *SSABinOp: push(e.operandBuf, i.X, i.Y) case *SSACall: push(e.operandBuf, i.Call.Value) for _, a := range i.Call.Args { push(e.operandBuf, a) } case *SSAFieldAddr: push(e.operandBuf, i.X) case *SSAIndexAddr: push(e.operandBuf, i.X, i.Index) case *SSAExtract: push(e.operandBuf, i.Tuple) case *SSAPhi: return i.Edges case *SSAReturn: for _, r := range i.Results { push(e.operandBuf, r) } case *SSAIf: push(e.operandBuf, i.Cond) case *SSAConvert: push(e.operandBuf, i.X) case *SSAChangeType: push(e.operandBuf, i.X) case *SSAMakeInterface: push(e.operandBuf, i.X) case *SSATypeAssert: push(e.operandBuf, i.X) case *SSASlice: push(e.operandBuf, i.X) if i.Low != nil { push(e.operandBuf, i.Low) } if i.High != nil { push(e.operandBuf, i.High) } if i.Max != nil { push(e.operandBuf, i.Max) } case *SSAMapUpdate: push(e.operandBuf, i.Map, i.Key, i.Value) case *SSALookup: push(e.operandBuf, i.X, i.Index) case *SSARange: push(e.operandBuf, i.X) case *SSANext: push(e.operandBuf, i.Iter) case *SSASend: push(e.operandBuf, i.Chan, i.X) case *SSAMakeSlice: push(e.operandBuf, i.Len) if i.Cap != nil { push(e.operandBuf, i.Cap) } if i.Data != nil { push(e.operandBuf, i.Data) } } return e.operandBuf return nil }