@@ -302,11 +302,74 @@ function baseGoFields(key: string): GoField[] {
302302 for ( const ext of baseGoEmbeds ( base ) ) fields . push ( ...baseGoFields ( ext ) ) ;
303303 for ( const field of base . fields ) {
304304 if ( field . noGo ) continue ;
305- fields . push ( { name : field . name , goType : field . goOnly ? field . rawType as string : field . type . formatGoReference ( ) , isNode : isNodeField ( field ) } ) ;
305+ fields . push ( { name : field . name , goType : field . goOnly ? field . rawType as string : field . type . formatGoReference ( ) , isNode : isNodeField ( field ) , owner : key } ) ;
306306 }
307307 return fields ;
308308}
309309
310+ // A field as laid out in a record.
311+ interface RecordField {
312+ storage : string ;
313+ goType : string ;
314+ comment : string ;
315+ }
316+
317+ function baseRecordFields ( key : string ) : RecordField [ ] {
318+ const base = api . getBase ( key ) ;
319+ if ( ! base || HAND_WRITTEN_BASES . has ( key ) ) return [ ] ;
320+ const fields : RecordField [ ] = [ ] ;
321+ for ( const ext of baseGoEmbeds ( base ) ) fields . push ( ...baseRecordFields ( ext ) ) ;
322+ for ( const field of base . fields ) {
323+ if ( field . noGo ) continue ;
324+ const valueType = field . goOnly ? field . rawType as string : field . type . formatGoReference ( ) ;
325+ const goType = recordGoType ( isNodeField ( field ) , valueType ) ;
326+ const parts = [ ] ;
327+ if ( goType !== valueType ) parts . push ( valueType ) ;
328+ if ( field . optional ) parts . push ( "Optional" ) ;
329+ fields . push ( { storage : goStorageName ( field . name ) , goType, comment : parts . length > 0 ? `// ${ parts . join ( ". " ) } ` : "" } ) ;
330+ }
331+ return fields ;
332+ }
333+
334+ // Alignments of the Go types that record fields may have.
335+ const recordFieldAlignments : Record < string , number > = {
336+ "nodeRef" : 8 ,
337+ "textRef" : 4 ,
338+ "sideRef" : 4 ,
339+ "atomic.Uint32" : 4 ,
340+ "TokenFlags" : 4 ,
341+ "Kind" : 2 ,
342+ "ImportPhaseModifierSyntaxKind" : 2 ,
343+ "bool" : 1 ,
344+ } ;
345+
346+ function recordFieldAlignment ( goType : string ) : number {
347+ if ( ! Object . hasOwn ( recordFieldAlignments , goType ) ) {
348+ throw new Error ( `unknown alignment for record field type ${ goType } ` ) ;
349+ }
350+ return recordFieldAlignments [ goType ] ;
351+ }
352+
353+ // A node's record holds its own and its bases' fields after the header,
354+ // ordered by decreasing alignment so that no padding falls between them.
355+ function nodeRecordFields ( node : NodeType ) : RecordField [ ] {
356+ const fields : RecordField [ ] = [ ] ;
357+ for ( const ext of goEmbeds ( node . extendsKeys ) ) fields . push ( ...baseRecordFields ( ext ) ) ;
358+ for ( const m of nodeOwnFields ( node ) ) {
359+ const goType = recordGoType ( isNodeField ( m ) , memberGoType ( m ) ) ;
360+ fields . push ( { storage : goStorageName ( m . name ) , goType, comment : buildFieldComment ( m , goType !== memberGoType ( m ) ? memberGoType ( m ) : undefined ) } ) ;
361+ }
362+ const seen = new Set < string > ( ) ;
363+ for ( const field of fields ) {
364+ if ( seen . has ( field . storage ) ) throw new Error ( `${ node . name } has more than one field named ${ field . storage } ` ) ;
365+ seen . add ( field . storage ) ;
366+ }
367+ return fields
368+ . map ( ( field , index ) => ( { field, index } ) )
369+ . sort ( ( a , b ) => recordFieldAlignment ( b . field . goType ) - recordFieldAlignment ( a . field . goType ) || a . index - b . index )
370+ . map ( ( { field } ) => field ) ;
371+ }
372+
310373function nodeGoFields ( node : NodeType ) : GoField [ ] {
311374 const fields : GoField [ ] = [ ] ;
312375 for ( const ext of goEmbeds ( node . extendsKeys ) ) fields . push ( ...baseGoFields ( ext ) ) ;
@@ -318,33 +381,18 @@ function generateStructDef(w: CodeWriter, node: NodeType) {
318381 const structName = node . name ;
319382 w . write ( `type ${ dataName ( node ) } struct {` ) ;
320383 w . push ( ) ;
321-
322- // Embeddings from extends (each maps to a Go struct via convention)
323- for ( const ext of goEmbeds ( node . extendsKeys ) ) {
324- w . write ( baseDataName ( ext ) ) ;
325- }
326-
327- // Fields from members (skip inherited, Kind params, and noGo)
328- for ( const m of nodeOwnFields ( node ) ) {
329- const fieldName = goStorageName ( m . name ) ;
330- const goType = recordGoType ( isNodeField ( m ) , memberGoType ( m ) ) ;
331- const comment = buildFieldComment ( m , goType !== memberGoType ( m ) ? memberGoType ( m ) : undefined ) ;
332- if ( comment ) {
333- w . write ( `${ fieldName } ${ goType } ${ comment } ` ) ;
334- }
335- else {
336- w . write ( `${ fieldName } ${ goType } ` ) ;
337- }
384+ w . write ( "nodeBaseData" ) ;
385+ for ( const { storage, goType, comment } of nodeRecordFields ( node ) ) {
386+ w . write ( comment ? `${ storage } ${ goType } ${ comment } ` : `${ storage } ${ goType } ` ) ;
338387 }
339-
340388 w . pop ( ) ;
341389 w . write ( "}" ) ;
342390 w . write ( "" ) ;
343391 w . write ( `type ${ structName } struct{ Node }` ) ;
344392 w . write ( "" ) ;
345393 w . write ( `func (node ${ structName } ) data() *${ dataName ( node ) } { return ${ dataOf ( node , "node" ) } }` ) ;
346394 w . write ( "" ) ;
347- generateFieldAccessors ( w , structName , nodeGoFields ( node ) , " node.data()" ) ;
395+ generateFieldAccessors ( w , structName , nodeGoFields ( node ) , f => `& node.data(). ${ goStorageName ( f . name ) } ` , f => `node.data(). ${ goStorageName ( f . name ) } ` ) ;
348396}
349397
350398function buildFieldComment ( m : MemberInfo , refType ?: string ) : string {
@@ -401,79 +449,56 @@ const HAND_WRITTEN_BASES = new Set(["NodeBase"]);
401449
402450function generateBaseStructDefs ( w : CodeWriter ) {
403451 w . write ( "// ──────────────────────────────────────────────────────────────────────" ) ;
404- w . write ( "// Base struct definitions " ) ;
452+ w . write ( "// Base views " ) ;
405453 w . write ( "// ──────────────────────────────────────────────────────────────────────" ) ;
406454 w . write ( "" ) ;
407455
408456 for ( const base of api . bases ( ) ) {
409457 if ( HAND_WRITTEN_BASES . has ( base . key ) ) continue ;
410458
411459 const structName = base . key ;
412-
413- const goExts = baseGoEmbeds ( base ) ;
414-
415- w . write ( `type ${ baseDataName ( structName ) } struct {` ) ;
416- w . push ( ) ;
417-
418- // Embeddings from extends
419- for ( const ext of goExts ) {
420- w . write ( baseDataName ( ext ) ) ;
421- }
422-
423- // Schema fields
424460 for ( const field of base . fields ) {
425- if ( field . noGo ) continue ;
426- const valueType = field . goOnly ? field . rawType as string : field . type . formatGoReference ( ) ;
427- const goType = recordGoType ( isNodeField ( field ) , valueType ) ;
428- const parts = [ ] ;
429- if ( goType !== valueType ) parts . push ( valueType ) ;
430- if ( field . optional ) parts . push ( "Optional" ) ;
431- const comment = parts . length > 0 ? ` // ${ parts . join ( ". " ) } ` : "" ;
432- w . write ( `${ goStorageName ( field . name ) } ${ goType } ${ comment } ` ) ;
461+ if ( ! field . noGo ) generateBaseFieldOffsets ( w , base , field . name , field . goOnly ? field . rawType as string : field . type . formatGoReference ( ) , isNodeField ( field ) ) ;
433462 }
434463
435- w . pop ( ) ;
436- w . write ( "}" ) ;
437- w . write ( "" ) ;
438-
439464 // Base views see the fields of whichever concrete node they wrap.
440465 w . write ( `type ${ structName } struct{ Node }` ) ;
441466 w . write ( "" ) ;
442467 const fields = baseGoFields ( structName ) ;
443468 if ( fields . length > 0 ) {
444- generateBaseDataDispatch ( w , base ) ;
445- generateFieldAccessors ( w , structName , fields , `node.${ baseDataName ( structName ) } ()` ) ;
469+ generateFieldAccessors ( w , structName , fields , f => `node.${ baseFieldName ( f . owner ! , f . name ) } ()` , f => `*node.${ baseFieldName ( f . owner ! , f . name ) } ()` ) ;
446470 }
447471 }
448472}
449473
450- function generateBaseDataDispatch ( w : CodeWriter , base : NodeType ) {
451- const cases : { node : NodeType ; kinds : string [ ] ; } [ ] = [ ] ;
452- for ( const node of api . nodes ( ) ) {
453- if ( ! transitiveBaseKeys ( node ) . has ( base . key ) ) continue ;
454- const kinds = node . allKinds ( ) . map ( kind => kind . formatGoConstant ( ) ) ;
455- if ( kinds . length !== 0 ) cases . push ( { node, kinds } ) ;
456- }
457- const table = `${ baseDataName ( base . key ) } Dispatch` ;
474+ // The method locating a base's field in a record.
475+ function baseFieldName ( key : string , name : string ) : string {
476+ return `${ goStorageName ( key ) } ${ api . capitalize ( goStorageName ( name ) ) } ` ;
477+ }
478+
479+ // A base's fields lie at different offsets in each kind's record, so each
480+ // has a table of its offsets by kind, zero for kinds without the base.
481+ function generateBaseFieldOffsets ( w : CodeWriter , base : NodeType , name : string , valueType : string , isNode : boolean ) {
482+ const method = baseFieldName ( base . key , name ) ;
483+ const table = `${ method } Offsets` ;
484+ const goType = recordGoType ( isNode , valueType ) ;
458485 w . write ( `var ${ table } = [kindFlowReduceLabelData + 1]uint8{` ) ;
459486 w . push ( ) ;
460- for ( const [ index , { kinds } ] of cases . entries ( ) ) {
461- for ( const kind of kinds ) {
462- w . write ( `${ kind } : ${ index + 1 } ,` ) ;
487+ for ( const node of api . nodes ( ) ) {
488+ if ( ! transitiveBaseKeys ( node ) . has ( base . key ) ) continue ;
489+ for ( const kind of node . allKinds ( ) ) {
490+ w . write ( `${ kind . formatGoConstant ( ) } : uint8(unsafe.Offsetof(${ dataName ( node ) } {}.${ goStorageName ( name ) } )),` ) ;
463491 }
464492 }
465493 w . pop ( ) ;
466494 w . write ( "}" ) ;
467495 w . write ( "" ) ;
468- w . write ( `func (n Node) ${ baseDataName ( base . key ) } () *${ baseDataName ( base . key ) } {` ) ;
496+ w . write ( `func (n Node) ${ method } () *${ goType } {` ) ;
469497 w . push ( ) ;
470- w . write ( `switch ${ table } [n.Kind()] {` ) ;
471- for ( const [ index , { node } ] of cases . entries ( ) ) {
472- w . write ( `case ${ index + 1 } :` ) ;
473- w . push ( ) ;
474- w . write ( `return &${ dataOf ( node , "n" ) } .${ baseDataName ( base . key ) } ` ) ;
475- w . pop ( ) ;
476- }
498+ w . write ( `if off := ${ table } [n.Kind()]; off != 0 {` ) ;
499+ w . push ( ) ;
500+ w . write ( `return (*${ goType } )(unsafe.Add(n.p, off))` ) ;
501+ w . pop ( ) ;
477502 w . write ( "}" ) ;
478503 w . write ( "return nil" ) ;
479504 w . pop ( ) ;
@@ -626,16 +651,14 @@ interface GoField {
626651 name : string ;
627652 goType : string ;
628653 isNode : boolean ;
654+ // The base declaring the field, or undefined for a node's own field.
655+ owner ?: string ;
629656}
630657
631658function dataName ( node : NodeType ) : string {
632659 return node . handWritten ? node . name : `${ goStorageName ( node . name ) } Data` ;
633660}
634661
635- function baseDataName ( key : string ) : string {
636- return key === "NodeBase" ? "nodeBaseData" : `${ goStorageName ( key ) } Data` ;
637- }
638-
639662// Private fields get exported getters only where they agree with the shared
640663// Node method of the same name; others would shadow it with a different type.
641664function hasPublicGetter ( name : string , goType : string ) : boolean {
@@ -660,23 +683,24 @@ function viewOf(node: NodeType, recv: string): string {
660683 return node . handWritten ? dataOf ( node , recv ) : `${ node . name } {${ recv } }` ;
661684}
662685
663- function generateFieldAccessors ( w : CodeWriter , structName : string , fields : GoField [ ] , data : string ) {
686+ // addr and value give a field's address and value within a record.
687+ function generateFieldAccessors ( w : CodeWriter , structName : string , fields : GoField [ ] , addr : ( field : GoField ) => string , value : ( field : GoField ) => string ) {
664688 let wrote = false ;
665- for ( const { name, goType, isNode } of fields ) {
689+ for ( const field of fields ) {
690+ const { name, goType, isNode } = field ;
666691 if ( goType . startsWith ( "atomic." ) ) continue ;
667- const storage = goStorageName ( name ) ;
668692 const side = sideTableOf ( goType ) ;
669693 if ( isNode ) {
670- w . write ( `func (node ${ structName } ) ${ getterName ( name , goType ) } () ${ goType } { return node.deref(& ${ data } . ${ storage } ) }` ) ;
671- w . write ( `func (node ${ structName } ) ${ setterName ( name ) } (value ${ goType } ) { node.setRef(& ${ data } . ${ storage } , value) }` ) ;
694+ w . write ( `func (node ${ structName } ) ${ getterName ( name , goType ) } () ${ goType } { return node.deref(${ addr ( field ) } ) }` ) ;
695+ w . write ( `func (node ${ structName } ) ${ setterName ( name ) } (value ${ goType } ) { node.setRef(${ addr ( field ) } , value) }` ) ;
672696 }
673697 else if ( side ) {
674- w . write ( `func (node ${ structName } ) ${ getterName ( name , goType ) } () ${ goType } { return node.store().${ side } .get(${ data } . ${ storage } ) }` ) ;
675- w . write ( `func (node ${ structName } ) ${ setterName ( name ) } (value ${ goType } ) { node.store().${ side } .set(& ${ data } . ${ storage } , value, ${ zeroCheck ( goType , "value" ) } ) }` ) ;
698+ w . write ( `func (node ${ structName } ) ${ getterName ( name , goType ) } () ${ goType } { return node.store().${ side } .get(${ value ( field ) } ) }` ) ;
699+ w . write ( `func (node ${ structName } ) ${ setterName ( name ) } (value ${ goType } ) { node.store().${ side } .set(${ addr ( field ) } , value, ${ zeroCheck ( goType , "value" ) } ) }` ) ;
676700 }
677701 else {
678- w . write ( `func (node ${ structName } ) ${ getterName ( name , goType ) } () ${ goType } { return ${ data } . ${ storage } }` ) ;
679- w . write ( `func (node ${ structName } ) ${ setterName ( name ) } (value ${ goType } ) { ${ data } . ${ storage } = value }` ) ;
702+ w . write ( `func (node ${ structName } ) ${ getterName ( name , goType ) } () ${ goType } { return ${ value ( field ) } }` ) ;
703+ w . write ( `func (node ${ structName } ) ${ setterName ( name ) } (value ${ goType } ) { ${ value ( field ) } = value }` ) ;
680704 }
681705 wrote = true ;
682706 }
@@ -1106,7 +1130,7 @@ function generateSubtreeFactsDispatch(w: CodeWriter) {
11061130 w . write ( `case ${ kinds . join ( ", " ) } :` ) ;
11071131 w . push ( ) ;
11081132 if ( transitiveBaseKeys ( node ) . has ( "CompositeBase" ) ) {
1109- w . write ( `return ${ dataOf ( node , "n" ) } .subtreeFactsWorker( n)` ) ;
1133+ w . write ( `return subtreeFactsWorker(& ${ dataOf ( node , "n" ) } .facts, n)` ) ;
11101134 }
11111135 else {
11121136 w . write ( "return n.computeSubtreeFacts()" ) ;
0 commit comments