| | |
| | |
| | |
| |
|
| | package main |
| |
|
| | import ( |
| | "bytes" |
| | "flag" |
| | "fmt" |
| | "go/ast" |
| | "go/format" |
| | "go/parser" |
| | "go/token" |
| | "log" |
| | "os" |
| | "strings" |
| |
|
| | "golang.org/x/tools/go/ast/astutil" |
| |
|
| | internalastutil "runtime/_mkmalloc/astutil" |
| | ) |
| |
|
| | var stdout = flag.Bool("stdout", false, "write sizeclasses source to stdout instead of sizeclasses.go") |
| |
|
| | func makeSizeToSizeClass(classes []class) []uint8 { |
| | sc := uint8(0) |
| | ret := make([]uint8, smallScanNoHeaderMax+1) |
| | for i := range ret { |
| | if i > classes[sc].size { |
| | sc++ |
| | } |
| | ret[i] = sc |
| | } |
| | return ret |
| | } |
| |
|
| | func main() { |
| | log.SetFlags(0) |
| | log.SetPrefix("mkmalloc: ") |
| |
|
| | classes := makeClasses() |
| | sizeToSizeClass := makeSizeToSizeClass(classes) |
| |
|
| | if *stdout { |
| | if _, err := os.Stdout.Write(mustFormat(generateSizeClasses(classes))); err != nil { |
| | log.Fatal(err) |
| | } |
| | return |
| | } |
| |
|
| | sizeclasesesfile := "../../internal/runtime/gc/sizeclasses.go" |
| | if err := os.WriteFile(sizeclasesesfile, mustFormat(generateSizeClasses(classes)), 0666); err != nil { |
| | log.Fatal(err) |
| | } |
| |
|
| | outfile := "../malloc_generated.go" |
| | if err := os.WriteFile(outfile, mustFormat(inline(specializedMallocConfig(classes, sizeToSizeClass))), 0666); err != nil { |
| | log.Fatal(err) |
| | } |
| |
|
| | tablefile := "../malloc_tables_generated.go" |
| | if err := os.WriteFile(tablefile, mustFormat(generateTable(sizeToSizeClass)), 0666); err != nil { |
| | log.Fatal(err) |
| | } |
| | } |
| |
|
| | |
| | func withLineNumbers(b []byte) []byte { |
| | var buf bytes.Buffer |
| | i := 1 |
| | for line := range bytes.Lines(b) { |
| | fmt.Fprintf(&buf, "%d: %s", i, line) |
| | i++ |
| | } |
| | return buf.Bytes() |
| | } |
| |
|
| | |
| | func mustFormat(b []byte) []byte { |
| | formatted, err := format.Source(b) |
| | if err != nil { |
| | log.Fatalf("error formatting source: %v\nsource:\n%s\n", err, withLineNumbers(b)) |
| | } |
| | return formatted |
| | } |
| |
|
| | |
| | |
| | type generatorConfig struct { |
| | file string |
| | specs []spec |
| | } |
| |
|
| | |
| | |
| | |
| | type spec struct { |
| | name string |
| | templateFunc string |
| | ops []op |
| | } |
| |
|
| | |
| | type replacementKind int |
| |
|
| | const ( |
| | inlineFunc = replacementKind(iota) |
| | subBasicLit |
| | foldCondition |
| | ) |
| |
|
| | |
| | |
| | |
| | type op struct { |
| | kind replacementKind |
| | from string |
| | to string |
| | } |
| |
|
| | func smallScanNoHeaderSCFuncName(sc, scMax uint8) string { |
| | if sc == 0 || sc > scMax { |
| | return "mallocPanic" |
| | } |
| | return fmt.Sprintf("mallocgcSmallScanNoHeaderSC%d", sc) |
| | } |
| |
|
| | func tinyFuncName(size uintptr) string { |
| | if size == 0 || size > smallScanNoHeaderMax { |
| | return "mallocPanic" |
| | } |
| | return fmt.Sprintf("mallocTiny%d", size) |
| | } |
| |
|
| | func smallNoScanSCFuncName(sc, scMax uint8) string { |
| | if sc < 2 || sc > scMax { |
| | return "mallocPanic" |
| | } |
| | return fmt.Sprintf("mallocgcSmallNoScanSC%d", sc) |
| | } |
| |
|
| | |
| | |
| | func specializedMallocConfig(classes []class, sizeToSizeClass []uint8) generatorConfig { |
| | config := generatorConfig{file: "../malloc_stubs.go"} |
| |
|
| | |
| | |
| | |
| | scMax := sizeToSizeClass[smallScanNoHeaderMax] |
| |
|
| | str := fmt.Sprint |
| |
|
| | |
| | { |
| | const noscan = 0 |
| | for sc := uint8(0); sc <= scMax; sc++ { |
| | if sc == 0 { |
| | continue |
| | } |
| | name := smallScanNoHeaderSCFuncName(sc, scMax) |
| | elemsize := classes[sc].size |
| | config.specs = append(config.specs, spec{ |
| | templateFunc: "mallocStub", |
| | name: name, |
| | ops: []op{ |
| | {inlineFunc, "inlinedMalloc", "smallScanNoHeaderStub"}, |
| | {inlineFunc, "heapSetTypeNoHeaderStub", "heapSetTypeNoHeaderStub"}, |
| | {inlineFunc, "nextFreeFastStub", "nextFreeFastStub"}, |
| | {inlineFunc, "writeHeapBitsSmallStub", "writeHeapBitsSmallStub"}, |
| | {subBasicLit, "elemsize_", str(elemsize)}, |
| | {subBasicLit, "sizeclass_", str(sc)}, |
| | {subBasicLit, "noscanint_", str(noscan)}, |
| | {foldCondition, "isTiny_", str(false)}, |
| | }, |
| | }) |
| | } |
| | } |
| |
|
| | |
| | { |
| | const noscan = 1 |
| |
|
| | |
| | tinySizeClass := sizeToSizeClass[tinySize] |
| | for s := range uintptr(16) { |
| | if s == 0 { |
| | continue |
| | } |
| | name := tinyFuncName(s) |
| | elemsize := classes[tinySizeClass].size |
| | config.specs = append(config.specs, spec{ |
| | templateFunc: "mallocStub", |
| | name: name, |
| | ops: []op{ |
| | {inlineFunc, "inlinedMalloc", "tinyStub"}, |
| | {inlineFunc, "nextFreeFastTiny", "nextFreeFastTiny"}, |
| | {subBasicLit, "elemsize_", str(elemsize)}, |
| | {subBasicLit, "sizeclass_", str(tinySizeClass)}, |
| | {subBasicLit, "size_", str(s)}, |
| | {subBasicLit, "noscanint_", str(noscan)}, |
| | {foldCondition, "isTiny_", str(true)}, |
| | }, |
| | }) |
| | } |
| |
|
| | |
| | for sc := uint8(tinySizeClass); sc <= scMax; sc++ { |
| | name := smallNoScanSCFuncName(sc, scMax) |
| | elemsize := classes[sc].size |
| | config.specs = append(config.specs, spec{ |
| | templateFunc: "mallocStub", |
| | name: name, |
| | ops: []op{ |
| | {inlineFunc, "inlinedMalloc", "smallNoScanStub"}, |
| | {inlineFunc, "nextFreeFastStub", "nextFreeFastStub"}, |
| | {subBasicLit, "elemsize_", str(elemsize)}, |
| | {subBasicLit, "sizeclass_", str(sc)}, |
| | {subBasicLit, "noscanint_", str(noscan)}, |
| | {foldCondition, "isTiny_", str(false)}, |
| | }, |
| | }) |
| | } |
| | } |
| |
|
| | return config |
| | } |
| |
|
| | |
| | func inline(config generatorConfig) []byte { |
| | var out bytes.Buffer |
| |
|
| | |
| | fset := token.NewFileSet() |
| | f, err := parser.ParseFile(fset, config.file, nil, 0) |
| | if err != nil { |
| | log.Fatalf("parsing %s: %v", config.file, err) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | funcDecls := map[string]*ast.FuncDecl{} |
| | importDecls := []*ast.GenDecl{} |
| | for _, decl := range f.Decls { |
| | switch decl := decl.(type) { |
| | case *ast.FuncDecl: |
| | funcDecls[decl.Name.Name] = decl |
| | case *ast.GenDecl: |
| | if decl.Tok.String() == "import" { |
| | importDecls = append(importDecls, decl) |
| | continue |
| | } |
| | } |
| | } |
| |
|
| | |
| | out.WriteString("// Code generated by mkmalloc.go; DO NOT EDIT.\n") |
| | out.WriteString("// See overview in malloc_stubs.go.\n\n") |
| | out.WriteString("package " + f.Name.Name + "\n\n") |
| | for _, importDecl := range importDecls { |
| | out.Write(mustFormatNode(fset, importDecl)) |
| | out.WriteString("\n\n") |
| | } |
| |
|
| | |
| | for _, spec := range config.specs { |
| | |
| | containingFuncCopy := internalastutil.CloneNode(funcDecls[spec.templateFunc]) |
| | if containingFuncCopy == nil { |
| | log.Fatal("did not find", spec.templateFunc) |
| | } |
| | containingFuncCopy.Name.Name = spec.name |
| |
|
| | |
| | stamped := ast.Node(containingFuncCopy) |
| | for _, repl := range spec.ops { |
| | switch repl.kind { |
| | case inlineFunc: |
| | if toDecl, ok := funcDecls[repl.to]; ok { |
| | stamped = inlineFunction(stamped, repl.from, toDecl) |
| | } |
| | case subBasicLit: |
| | stamped = substituteWithBasicLit(stamped, repl.from, repl.to) |
| | case foldCondition: |
| | stamped = foldIfCondition(stamped, repl.from, repl.to) |
| | default: |
| | log.Fatalf("unknown op kind %v", repl.kind) |
| | } |
| | } |
| |
|
| | out.Write(mustFormatNode(fset, stamped)) |
| | out.WriteString("\n\n") |
| | } |
| |
|
| | return out.Bytes() |
| | } |
| |
|
| | |
| | |
| | func substituteWithBasicLit(node ast.Node, from, to string) ast.Node { |
| | |
| | toExpr, err := parser.ParseExpr(to) |
| | if err != nil { |
| | log.Fatalf("parsing expr %q: %v", to, err) |
| | } |
| | if _, ok := toExpr.(*ast.BasicLit); !ok { |
| | log.Fatalf("op 'to' expr %q is not a basic literal", to) |
| | } |
| | return astutil.Apply(node, func(cursor *astutil.Cursor) bool { |
| | if isIdentWithName(cursor.Node(), from) { |
| | cursor.Replace(toExpr) |
| | } |
| | return true |
| | }, nil) |
| | } |
| |
|
| | |
| | |
| | |
| | func foldIfCondition(node ast.Node, from, to string) ast.Node { |
| | var isTrue bool |
| | switch to { |
| | case "true": |
| | isTrue = true |
| | case "false": |
| | isTrue = false |
| | default: |
| | log.Fatalf("op 'to' expr %q is not true or false", to) |
| | } |
| | return astutil.Apply(node, func(cursor *astutil.Cursor) bool { |
| | var foldIfTrue bool |
| | ifexpr, ok := cursor.Node().(*ast.IfStmt) |
| | if !ok { |
| | return true |
| | } |
| | if isIdentWithName(ifexpr.Cond, from) { |
| | foldIfTrue = true |
| | } else if unaryexpr, ok := ifexpr.Cond.(*ast.UnaryExpr); ok && unaryexpr.Op == token.NOT && isIdentWithName(unaryexpr.X, from) { |
| | foldIfTrue = false |
| | } else { |
| | |
| | return true |
| | } |
| | if foldIfTrue == isTrue { |
| | for _, stmt := range ifexpr.Body.List { |
| | cursor.InsertBefore(stmt) |
| | } |
| | } |
| | cursor.Delete() |
| | return true |
| | }, nil) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func inlineFunction(node ast.Node, from string, toDecl *ast.FuncDecl) ast.Node { |
| | return astutil.Apply(node, func(cursor *astutil.Cursor) bool { |
| | switch node := cursor.Node().(type) { |
| | case *ast.AssignStmt: |
| | |
| | |
| | if len(node.Rhs) == 1 && isCallTo(node.Rhs[0], from) { |
| | args := node.Rhs[0].(*ast.CallExpr).Args |
| | if !argsMatchParameters(args, toDecl.Type.Params) { |
| | log.Fatalf("applying op: arguments to %v don't match parameter names of %v: %v", from, toDecl.Name, debugPrint(args...)) |
| | } |
| | replaceAssignment(cursor, node, toDecl) |
| | } |
| | return false |
| | case *ast.CallExpr: |
| | |
| | if isCallTo(node, from) { |
| | if _, ok := cursor.Parent().(*ast.AssignStmt); !ok { |
| | log.Fatalf("applying op: all calls to function %q being replaced must appear in an assignment statement, appears in %T", from, cursor.Parent()) |
| | } |
| | } |
| | } |
| | return true |
| | }, nil) |
| | } |
| |
|
| | |
| | |
| | func argsMatchParameters(args []ast.Expr, params *ast.FieldList) bool { |
| | var paramIdents []*ast.Ident |
| | for _, f := range params.List { |
| | paramIdents = append(paramIdents, f.Names...) |
| | } |
| |
|
| | if len(args) != len(paramIdents) { |
| | return false |
| | } |
| |
|
| | for i := range args { |
| | if !isIdentWithName(args[i], paramIdents[i].Name) { |
| | return false |
| | } |
| | } |
| |
|
| | return true |
| | } |
| |
|
| | |
| | func isIdentWithName(expr ast.Node, name string) bool { |
| | ident, ok := expr.(*ast.Ident) |
| | if !ok { |
| | return false |
| | } |
| | return ident.Name == name |
| | } |
| |
|
| | |
| | func isCallTo(expr ast.Expr, name string) bool { |
| | callexpr, ok := expr.(*ast.CallExpr) |
| | if !ok { |
| | return false |
| | } |
| | return isIdentWithName(callexpr.Fun, name) |
| | } |
| |
|
| | |
| | |
| | |
| | func replaceAssignment(cursor *astutil.Cursor, assign *ast.AssignStmt, funcdecl *ast.FuncDecl) { |
| | if !hasTerminatingReturn(funcdecl.Body) { |
| | log.Fatal("function being inlined must have a return at the end") |
| | } |
| |
|
| | body := internalastutil.CloneNode(funcdecl.Body) |
| | if hasTerminatingAndNonterminatingReturn(funcdecl.Body) { |
| | |
| | |
| | |
| | |
| | body = addContinues(cursor, assign, body, everythingFollowingInParent(cursor)).(*ast.BlockStmt) |
| | } |
| |
|
| | if len(body.List) < 1 { |
| | log.Fatal("replacing with empty bodied function") |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | beforeReturn, ret := body.List[:len(body.List)-1], body.List[len(body.List)-1] |
| | returnStmt, ok := ret.(*ast.ReturnStmt) |
| | if !ok { |
| | log.Fatal("last stmt in function we're replacing with should be a return") |
| | } |
| | results := returnStmt.Results |
| |
|
| | |
| | for _, stmt := range beforeReturn { |
| | cursor.InsertBefore(stmt) |
| | } |
| |
|
| | |
| | replaceWithAssignment(cursor, assign.Lhs, results, assign.Tok) |
| | } |
| |
|
| | |
| | func hasTerminatingReturn(block *ast.BlockStmt) bool { |
| | _, ok := block.List[len(block.List)-1].(*ast.ReturnStmt) |
| | return ok |
| | } |
| |
|
| | |
| | |
| | func hasTerminatingAndNonterminatingReturn(block *ast.BlockStmt) bool { |
| | if !hasTerminatingReturn(block) { |
| | return false |
| | } |
| | var ret bool |
| | for i := range block.List[:len(block.List)-1] { |
| | ast.Inspect(block.List[i], func(node ast.Node) bool { |
| | _, ok := node.(*ast.ReturnStmt) |
| | if ok { |
| | ret = true |
| | return false |
| | } |
| | return true |
| | }) |
| | } |
| | return ret |
| | } |
| |
|
| | |
| | |
| | func everythingFollowingInParent(cursor *astutil.Cursor) *ast.BlockStmt { |
| | parent := cursor.Parent() |
| | block, ok := parent.(*ast.BlockStmt) |
| | if !ok { |
| | log.Fatal("internal error: in everythingFollowingInParent, cursor doesn't point to element in block list") |
| | } |
| |
|
| | blockcopy := internalastutil.CloneNode(block) |
| | blockcopy.List = blockcopy.List[cursor.Index()+1:] |
| |
|
| | if _, ok := blockcopy.List[len(blockcopy.List)-1].(*ast.ReturnStmt); !ok { |
| | log.Printf("%s", mustFormatNode(token.NewFileSet(), blockcopy)) |
| | log.Fatal("internal error: parent doesn't end in a return") |
| | } |
| | return blockcopy |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | func addContinues(cursor *astutil.Cursor, assignNode *ast.AssignStmt, toBlock *ast.BlockStmt, continueBlock *ast.BlockStmt) ast.Node { |
| | if !hasTerminatingReturn(continueBlock) { |
| | log.Fatal("the block being continued to in addContinues must end in a return") |
| | } |
| | applyFunc := func(cursor *astutil.Cursor) bool { |
| | ret, ok := cursor.Node().(*ast.ReturnStmt) |
| | if !ok { |
| | return true |
| | } |
| |
|
| | if cursor.Parent() == toBlock && cursor.Index() == len(toBlock.List)-1 { |
| | return false |
| | } |
| |
|
| | |
| | |
| | |
| | replaceWithAssignment(cursor, assignNode.Lhs, ret.Results, assignNode.Tok) |
| | cursor.InsertAfter(internalastutil.CloneNode(continueBlock)) |
| |
|
| | return false |
| | } |
| | return astutil.Apply(toBlock, applyFunc, nil) |
| | } |
| |
|
| | |
| | func debugPrint(nodes ...ast.Expr) string { |
| | var b strings.Builder |
| | for i, node := range nodes { |
| | b.Write(mustFormatNode(token.NewFileSet(), node)) |
| | if i != len(nodes)-1 { |
| | b.WriteString(", ") |
| | } |
| | } |
| | return b.String() |
| | } |
| |
|
| | |
| | func mustFormatNode(fset *token.FileSet, node any) []byte { |
| | var buf bytes.Buffer |
| | format.Node(&buf, fset, node) |
| | return buf.Bytes() |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | func mustMatchExprs(lhs []ast.Expr, rhs []ast.Expr) ([]ast.Expr, []ast.Expr) { |
| | if len(lhs) != len(rhs) { |
| | log.Fatal("exprs don't match", debugPrint(lhs...), debugPrint(rhs...)) |
| | } |
| |
|
| | var newLhs, newRhs []ast.Expr |
| | for i := range lhs { |
| | lhsIdent, ok1 := lhs[i].(*ast.Ident) |
| | rhsIdent, ok2 := rhs[i].(*ast.Ident) |
| | if ok1 && ok2 && lhsIdent.Name == rhsIdent.Name { |
| | continue |
| | } |
| | newLhs = append(newLhs, lhs[i]) |
| | newRhs = append(newRhs, rhs[i]) |
| | } |
| |
|
| | return newLhs, newRhs |
| | } |
| |
|
| | |
| | |
| | |
| | func replaceWithAssignment(cursor *astutil.Cursor, lhs, rhs []ast.Expr, tok token.Token) { |
| | newLhs, newRhs := mustMatchExprs(lhs, rhs) |
| | if len(newLhs) == 0 { |
| | cursor.Delete() |
| | return |
| | } |
| | if len(newRhs) == 1 { |
| | if lit, ok := newRhs[0].(*ast.BasicLit); ok { |
| | constDecl := &ast.DeclStmt{ |
| | Decl: &ast.GenDecl{ |
| | Tok: token.CONST, |
| | Specs: []ast.Spec{ |
| | &ast.ValueSpec{ |
| | Names: []*ast.Ident{newLhs[0].(*ast.Ident)}, |
| | Values: []ast.Expr{lit}, |
| | }, |
| | }, |
| | }, |
| | } |
| | cursor.Replace(constDecl) |
| | return |
| | } |
| | } |
| | newAssignment := &ast.AssignStmt{ |
| | Lhs: newLhs, |
| | Rhs: newRhs, |
| | Tok: tok, |
| | } |
| | cursor.Replace(newAssignment) |
| | } |
| |
|
| | |
| | func generateTable(sizeToSizeClass []uint8) []byte { |
| | scMax := sizeToSizeClass[smallScanNoHeaderMax] |
| |
|
| | var b bytes.Buffer |
| | fmt.Fprintln(&b, `// Code generated by mkmalloc.go; DO NOT EDIT. |
| | //go:build !plan9 |
| | |
| | package runtime |
| | |
| | import "unsafe" |
| | |
| | var mallocScanTable = [513]func(size uintptr, typ *_type, needzero bool) unsafe.Pointer{`) |
| |
|
| | for i := range uintptr(smallScanNoHeaderMax + 1) { |
| | fmt.Fprintf(&b, "%s,\n", smallScanNoHeaderSCFuncName(sizeToSizeClass[i], scMax)) |
| | } |
| |
|
| | fmt.Fprintln(&b, ` |
| | } |
| | |
| | var mallocNoScanTable = [513]func(size uintptr, typ *_type, needzero bool) unsafe.Pointer{`) |
| | for i := range uintptr(smallScanNoHeaderMax + 1) { |
| | if i < 16 { |
| | fmt.Fprintf(&b, "%s,\n", tinyFuncName(i)) |
| | } else { |
| | fmt.Fprintf(&b, "%s,\n", smallNoScanSCFuncName(sizeToSizeClass[i], scMax)) |
| | } |
| | } |
| |
|
| | fmt.Fprintln(&b, ` |
| | }`) |
| |
|
| | return b.Bytes() |
| | } |
| |
|