github.com/ipld/go-ipld-prime@v0.21.0/schema/gen/go/adjunctCfg.go (about) 1 package gengo 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 "github.com/ipld/go-ipld-prime/datamodel" 9 "github.com/ipld/go-ipld-prime/node/basicnode" 10 "github.com/ipld/go-ipld-prime/schema" 11 ) 12 13 // This entire file is placeholder-quality implementations. 14 // 15 // The AdjunctCfg struct should be replaced with an IPLD Schema-specified thing! 16 // The values in the unionMemlayout field should be an enum; 17 // etcetera! 18 19 type FieldTuple struct { 20 TypeName schema.TypeName 21 FieldName string 22 } 23 24 type AdjunctCfg struct { 25 typeSymbolOverrides map[schema.TypeName]string 26 FieldSymbolLowerOverrides map[FieldTuple]string 27 fieldSymbolUpperOverrides map[FieldTuple]string 28 maybeUsesPtr map[schema.TypeName]bool // absent uses a heuristic 29 CfgUnionMemlayout map[schema.TypeName]string // "embedAll"|"interface"; maybe more options later, unclear for now. 30 31 // ... some of these fields have sprouted messy name prefixes so they don't collide with their matching method names. 32 // this structure has reached the critical threshhold where it due to be cleaned up and taken seriously. 33 34 // note: PkgName doesn't appear in here, because it's... 35 // not adjunct data. it's a generation invocation parameter. 36 // ... this might not hold up in the future though. 37 // There are unanswered questions about how (also, tbf, *if*) we'll handle generation of multiple packages which use each other's types. 38 } 39 40 // TypeSymbol returns the symbol for a type; 41 // by default, it's the same string as its name in the schema, 42 // but it can be overriden. 43 // 44 // This is the base, unembellished symbol. 45 // It's frequently augmented: 46 // prefixing an underscore to make it unexported; 47 // suffixing "__Something" to make the name of a supporting type; 48 // etc. 49 // (Most such augmentations are not configurable.) 50 func (cfg *AdjunctCfg) TypeSymbol(t schema.Type) string { 51 if x, ok := cfg.typeSymbolOverrides[t.Name()]; ok { 52 return x 53 } 54 return string(t.Name()) // presumed already upper 55 } 56 57 func (cfg *AdjunctCfg) FieldSymbolLower(f schema.StructField) string { 58 if x, ok := cfg.FieldSymbolLowerOverrides[FieldTuple{f.Parent().Name(), f.Name()}]; ok { 59 return x 60 } 61 return f.Name() // presumed already lower 62 } 63 64 func (cfg *AdjunctCfg) FieldSymbolUpper(f schema.StructField) string { 65 if x, ok := cfg.fieldSymbolUpperOverrides[FieldTuple{f.Type().Name(), f.Name()}]; ok { 66 return x 67 } 68 return strings.Title(f.Name()) //lint:ignore SA1019 cases.Title doesn't work for this 69 } 70 71 // Comments returns a bool for whether comments should be included in gen output or not. 72 func (cfg *AdjunctCfg) Comments() bool { 73 return true // FUTURE: okay, maybe this should be configurable :) 74 } 75 76 func (cfg *AdjunctCfg) MaybeUsesPtr(t schema.Type) bool { 77 if x, ok := cfg.maybeUsesPtr[t.Name()]; ok { 78 return x 79 } 80 81 // As a simple heuristic, 82 // check how large the Go representation of this type will be. 83 // If it weighs little, we estimate that a pointer is not worthwhile, 84 // as storing the data directly will barely take more memory. 85 // Plus, the resulting code will be shorter and have fewer branches. 86 return sizeOfSchemaType(t) > sizeSmallEnoughForInlining 87 } 88 89 var ( 90 // The cutoff for "weighs little" is any size up to this number. 91 // It's hasn't been measured with any benchmarks or stats just yet. 92 // It's possible that, with those, it might increase in the future. 93 // Intuitively, any type 4x the size of a pointer is fine to inline. 94 // Adding a pointer will already add 1x overhead, anyway. 95 sizeSmallEnoughForInlining = 4 * reflect.TypeOf(new(int)).Size() 96 97 sizeOfTypeKind [128]uintptr 98 ) 99 100 func init() { 101 // Uncomment for debugging. 102 // fmt.Fprintf(os.Stderr, "sizeOf(small): %d (4x pointer size)\n", sizeSmallEnoughForInlining) 103 104 // Get the basic node sizes via basicnode. 105 for _, tk := range []struct { 106 typeKind schema.TypeKind 107 prototype datamodel.NodePrototype 108 }{ 109 {schema.TypeKind_Bool, basicnode.Prototype.Bool}, 110 {schema.TypeKind_Int, basicnode.Prototype.Int}, 111 {schema.TypeKind_Float, basicnode.Prototype.Float}, 112 {schema.TypeKind_String, basicnode.Prototype.String}, 113 {schema.TypeKind_Bytes, basicnode.Prototype.Bytes}, 114 {schema.TypeKind_List, basicnode.Prototype.List}, 115 {schema.TypeKind_Map, basicnode.Prototype.Map}, 116 {schema.TypeKind_Link, basicnode.Prototype.Link}, 117 } { 118 nb := tk.prototype.NewBuilder() 119 switch tk.typeKind { 120 case schema.TypeKind_List: 121 am, err := nb.BeginList(0) 122 if err != nil { 123 panic(err) 124 } 125 if err := am.Finish(); err != nil { 126 panic(err) 127 } 128 case schema.TypeKind_Map: 129 am, err := nb.BeginMap(0) 130 if err != nil { 131 panic(err) 132 } 133 if err := am.Finish(); err != nil { 134 panic(err) 135 } 136 } 137 // Note that the Node interface has a pointer underneath, 138 // so we use Elem to reach the underlying type. 139 size := reflect.TypeOf(nb.Build()).Elem().Size() 140 sizeOfTypeKind[tk.typeKind] = size 141 142 // Uncomment for debugging. 143 // fmt.Fprintf(os.Stderr, "sizeOf(%s): %d\n", tk.typeKind, size) 144 } 145 } 146 147 // sizeOfSchemaType returns the size of a schema type, 148 // relative to the size of a pointer in native Go. 149 // 150 // For example, TypeInt and TypeMap returns 1, but TypeList returns 3, as a 151 // slice in Go has a pointer and two integers for length and capacity. 152 // Any basic type smaller than a pointer, such as TypeBool, returns 1. 153 func sizeOfSchemaType(t schema.Type) uintptr { 154 kind := t.TypeKind() 155 156 // If this TypeKind is represented by the basicnode package, 157 // we statically know its size and we can return here. 158 if size := sizeOfTypeKind[kind]; size > 0 { 159 return size 160 } 161 162 // TODO: handle typekinds like structs, unions, etc. 163 // For now, return a large size to fall back to using a pointer. 164 return 100 * sizeSmallEnoughForInlining 165 } 166 167 // UnionMemlayout returns a plain string at present; 168 // there's a case-switch in the templates that processes it. 169 // We validate that it's a known string when this method is called. 170 // This should probably be improved in type-safety, 171 // and validated more aggressively up front when adjcfg is loaded. 172 func (cfg *AdjunctCfg) UnionMemlayout(t schema.Type) string { 173 if t.TypeKind() != schema.TypeKind_Union { 174 panic(fmt.Errorf("%s is not a union", t.Name())) 175 } 176 v, ok := cfg.CfgUnionMemlayout[t.Name()] 177 if !ok { 178 return "embedAll" 179 } 180 switch v { 181 case "embedAll", "interface": 182 return v 183 default: 184 panic(fmt.Errorf("invalid config: unionMemlayout values must be either \"embedAll\" or \"interface\", not %q", v)) 185 } 186 }