diff --git a/src/main/java/dk/camelot64/kickc/model/types/SymbolTypeStruct.java b/src/main/java/dk/camelot64/kickc/model/types/SymbolTypeStruct.java index 947465752..21867a4fc 100644 --- a/src/main/java/dk/camelot64/kickc/model/types/SymbolTypeStruct.java +++ b/src/main/java/dk/camelot64/kickc/model/types/SymbolTypeStruct.java @@ -66,7 +66,7 @@ public class SymbolTypeStruct implements SymbolType { if(programScope != null) { ConstantLiteral sizeLiteral = arraySize.calculateLiteral(programScope); if(sizeLiteral instanceof ConstantInteger) { - return ((ConstantInteger) sizeLiteral).getInteger().intValue(); + return memberType.getSizeBytes() * ((ConstantInteger) sizeLiteral).getInteger().intValue(); } } else { return 5; // Add a token size diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 91de20222..8e35b5d81 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -1726,6 +1726,11 @@ public class TestPrograms { compileAndCompare("struct-directives.c"); } + @Test + public void testStruct43() throws IOException, URISyntaxException { + compileAndCompare("struct-43.c"); + } + @Test public void testStruct42() throws IOException, URISyntaxException { compileAndCompare("struct-42.c"); diff --git a/src/test/kc/struct-43.c b/src/test/kc/struct-43.c new file mode 100644 index 000000000..a3cb7a3eb --- /dev/null +++ b/src/test/kc/struct-43.c @@ -0,0 +1,17 @@ +// Minimal struct with C-Standard behavior - struct with internal int array + +struct Point { + char x; + unsigned int pos[2]; + unsigned int id; +}; + +__mem __ma struct Point point1 = { 4, {1, 2}, 3 }; + +unsigned int* const SCREEN = 0x0400; + +void main() { + SCREEN[0] = point1.id; + SCREEN[1] = point1.pos[0]; + SCREEN[2] = point1.pos[1]; +} \ No newline at end of file diff --git a/src/test/ref/struct-43.asm b/src/test/ref/struct-43.asm new file mode 100644 index 000000000..14406332b --- /dev/null +++ b/src/test/ref/struct-43.asm @@ -0,0 +1,29 @@ +// Minimal struct with C-Standard behavior - struct with internal int array +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + .const SIZEOF_WORD = 2 + .const OFFSET_STRUCT_POINT_ID = 5 + .const OFFSET_STRUCT_POINT_POS = 1 + .label SCREEN = $400 +main: { + // SCREEN[0] = point1.id + lda point1+OFFSET_STRUCT_POINT_ID + sta SCREEN + lda point1+OFFSET_STRUCT_POINT_ID+1 + sta SCREEN+1 + // SCREEN[1] = point1.pos[0] + lda point1+OFFSET_STRUCT_POINT_POS + sta SCREEN+1*SIZEOF_WORD + lda point1+OFFSET_STRUCT_POINT_POS+1 + sta SCREEN+1*SIZEOF_WORD+1 + // SCREEN[2] = point1.pos[1] + lda point1+OFFSET_STRUCT_POINT_POS+1*SIZEOF_WORD + sta SCREEN+2*SIZEOF_WORD + lda point1+OFFSET_STRUCT_POINT_POS+1*SIZEOF_WORD+1 + sta SCREEN+2*SIZEOF_WORD+1 + // } + rts +} + point1: .byte 4 + .word 1, 2, 3 diff --git a/src/test/ref/struct-43.cfg b/src/test/ref/struct-43.cfg new file mode 100644 index 000000000..18f49fd8e --- /dev/null +++ b/src/test/ref/struct-43.cfg @@ -0,0 +1,19 @@ +@begin: scope:[] from + [0] phi() + to:@1 +@1: scope:[] from @begin + [1] phi() + [2] call main + to:@end +@end: scope:[] from @1 + [3] phi() + +(void()) main() +main: scope:[main] from @1 + [4] *((const nomodify word*) SCREEN) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_ID) + [5] *((const nomodify word*) SCREEN+(byte) 1*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS) + [6] *((const nomodify word*) SCREEN+(byte) 2*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS+(byte) 1*(const byte) SIZEOF_WORD) + to:main::@return +main::@return: scope:[main] from main + [7] return + to:@return diff --git a/src/test/ref/struct-43.log b/src/test/ref/struct-43.log new file mode 100644 index 000000000..25b5b59a4 --- /dev/null +++ b/src/test/ref/struct-43.log @@ -0,0 +1,361 @@ +Fixing struct type size struct Point to 7 +Fixing struct type SIZE_OF struct Point to 7 +Fixing struct type SIZE_OF struct Point to 7 + +CONTROL FLOW GRAPH SSA +@begin: scope:[] from + to:@1 + +(void()) main() +main: scope:[main] from @1 + (number~) main::$0 ← (number) 0 * (const byte) SIZEOF_WORD + *((const nomodify word*) SCREEN + (number~) main::$0) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_ID) + (number~) main::$1 ← (number) 0 * (const byte) SIZEOF_WORD + (number~) main::$2 ← (number) 1 * (const byte) SIZEOF_WORD + *((const nomodify word*) SCREEN + (number~) main::$2) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS + (number~) main::$1) + (number~) main::$3 ← (number) 1 * (const byte) SIZEOF_WORD + (number~) main::$4 ← (number) 2 * (const byte) SIZEOF_WORD + *((const nomodify word*) SCREEN + (number~) main::$4) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS + (number~) main::$3) + to:main::@return +main::@return: scope:[main] from main + return + to:@return +@1: scope:[] from @begin + call main + to:@2 +@2: scope:[] from @1 + to:@end +@end: scope:[] from @2 + +SYMBOL TABLE SSA +(label) @1 +(label) @2 +(label) @begin +(label) @end +(const byte) OFFSET_STRUCT_POINT_ID = (byte) 5 +(const byte) OFFSET_STRUCT_POINT_POS = (byte) 1 +(word) Point::id +(const word*) Point::pos[(number) 2] = { fill( 2, 0) } +(byte) Point::x +(const nomodify word*) SCREEN = (word*)(number) $400 +(const byte) SIZEOF_WORD = (byte) 2 +(void()) main() +(number~) main::$0 +(number~) main::$1 +(number~) main::$2 +(number~) main::$3 +(number~) main::$4 +(label) main::@return +(struct Point) point1 loadstore = { x: (byte) 4, pos: { (word) 1, (word) 2 }, id: (word) 3 } + +Adding number conversion cast (unumber) 0 in (number~) main::$0 ← (number) 0 * (const byte) SIZEOF_WORD +Adding number conversion cast (unumber) main::$0 in (number~) main::$0 ← (unumber)(number) 0 * (const byte) SIZEOF_WORD +Adding number conversion cast (unumber) 0 in (number~) main::$1 ← (number) 0 * (const byte) SIZEOF_WORD +Adding number conversion cast (unumber) main::$1 in (number~) main::$1 ← (unumber)(number) 0 * (const byte) SIZEOF_WORD +Adding number conversion cast (unumber) 1 in (number~) main::$2 ← (number) 1 * (const byte) SIZEOF_WORD +Adding number conversion cast (unumber) main::$2 in (number~) main::$2 ← (unumber)(number) 1 * (const byte) SIZEOF_WORD +Adding number conversion cast (unumber) 1 in (number~) main::$3 ← (number) 1 * (const byte) SIZEOF_WORD +Adding number conversion cast (unumber) main::$3 in (number~) main::$3 ← (unumber)(number) 1 * (const byte) SIZEOF_WORD +Adding number conversion cast (unumber) 2 in (number~) main::$4 ← (number) 2 * (const byte) SIZEOF_WORD +Adding number conversion cast (unumber) main::$4 in (number~) main::$4 ← (unumber)(number) 2 * (const byte) SIZEOF_WORD +Successful SSA optimization PassNAddNumberTypeConversions +Simplifying constant pointer cast (word*) 1024 +Simplifying constant integer cast 0 +Simplifying constant integer cast 0 +Simplifying constant integer cast 1 +Simplifying constant integer cast 1 +Simplifying constant integer cast 2 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) 0 +Finalized unsigned number type (byte) 0 +Finalized unsigned number type (byte) 1 +Finalized unsigned number type (byte) 1 +Finalized unsigned number type (byte) 2 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Inferred type updated to byte in (unumber~) main::$0 ← (byte) 0 * (const byte) SIZEOF_WORD +Inferred type updated to byte in (unumber~) main::$1 ← (byte) 0 * (const byte) SIZEOF_WORD +Inferred type updated to byte in (unumber~) main::$2 ← (byte) 1 * (const byte) SIZEOF_WORD +Inferred type updated to byte in (unumber~) main::$3 ← (byte) 1 * (const byte) SIZEOF_WORD +Inferred type updated to byte in (unumber~) main::$4 ← (byte) 2 * (const byte) SIZEOF_WORD +Constant right-side identified [0] (byte~) main::$0 ← (byte) 0 * (const byte) SIZEOF_WORD +Constant right-side identified [2] (byte~) main::$1 ← (byte) 0 * (const byte) SIZEOF_WORD +Constant right-side identified [3] (byte~) main::$2 ← (byte) 1 * (const byte) SIZEOF_WORD +Constant right-side identified [5] (byte~) main::$3 ← (byte) 1 * (const byte) SIZEOF_WORD +Constant right-side identified [6] (byte~) main::$4 ← (byte) 2 * (const byte) SIZEOF_WORD +Successful SSA optimization Pass2ConstantRValueConsolidation +Constant (const byte) main::$0 = 0*SIZEOF_WORD +Constant (const byte) main::$1 = 0*SIZEOF_WORD +Constant (const byte) main::$2 = 1*SIZEOF_WORD +Constant (const byte) main::$3 = 1*SIZEOF_WORD +Constant (const byte) main::$4 = 2*SIZEOF_WORD +Successful SSA optimization Pass2ConstantIdentification +Simplifying constant evaluating to zero (byte) 0*(const byte) SIZEOF_WORD in +Simplifying constant evaluating to zero (byte) 0*(const byte) SIZEOF_WORD in +Successful SSA optimization PassNSimplifyConstantZero +Simplifying expression containing zero SCREEN in [1] *((const nomodify word*) SCREEN + (const byte) main::$0) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_ID) +Simplifying expression containing zero (word*)&point1+OFFSET_STRUCT_POINT_POS in [4] *((const nomodify word*) SCREEN + (const byte) main::$2) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS + (const byte) main::$1) +Successful SSA optimization PassNSimplifyExpressionWithZero +Eliminating unused constant (const byte) main::$0 +Eliminating unused constant (const byte) main::$1 +Successful SSA optimization PassNEliminateUnusedVars +Constant inlined main::$3 = (byte) 1*(const byte) SIZEOF_WORD +Constant inlined main::$4 = (byte) 2*(const byte) SIZEOF_WORD +Constant inlined main::$2 = (byte) 1*(const byte) SIZEOF_WORD +Successful SSA optimization Pass2ConstantInlining +Consolidated array index constant in *(SCREEN+1*SIZEOF_WORD) +Consolidated array index constant in *((word*)&point1+OFFSET_STRUCT_POINT_POS+1*SIZEOF_WORD) +Consolidated array index constant in *(SCREEN+2*SIZEOF_WORD) +Successful SSA optimization Pass2ConstantAdditionElimination +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @1 +Adding NOP phi() at start of @2 +Adding NOP phi() at start of @end +CALL GRAPH +Calls in [] to main:2 + +Created 0 initial phi equivalence classes +Coalesced down to 0 phi equivalence classes +Culled Empty Block (label) @2 +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @1 +Adding NOP phi() at start of @end + +FINAL CONTROL FLOW GRAPH +@begin: scope:[] from + [0] phi() + to:@1 +@1: scope:[] from @begin + [1] phi() + [2] call main + to:@end +@end: scope:[] from @1 + [3] phi() + +(void()) main() +main: scope:[main] from @1 + [4] *((const nomodify word*) SCREEN) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_ID) + [5] *((const nomodify word*) SCREEN+(byte) 1*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS) + [6] *((const nomodify word*) SCREEN+(byte) 2*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS+(byte) 1*(const byte) SIZEOF_WORD) + to:main::@return +main::@return: scope:[main] from main + [7] return + to:@return + + +VARIABLE REGISTER WEIGHTS +(word) Point::id +(byte) Point::x +(void()) main() +(struct Point) point1 loadstore = { x: (byte) 4, pos: { (word) 1, (word) 2 }, id: (word) 3 } + +Initial phi equivalence classes +Added variable point1 to live range equivalence class [ point1 ] +Complete equivalence classes +[ point1 ] +Allocated mem[7] [ point1 ] + +INITIAL ASM +Target platform is c64basic / MOS6502X + // File Comments +// Minimal struct with C-Standard behavior - struct with internal int array + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + .const SIZEOF_WORD = 2 + .const OFFSET_STRUCT_POINT_ID = 5 + .const OFFSET_STRUCT_POINT_POS = 1 + .label SCREEN = $400 + // @begin +__bbegin: + // [1] phi from @begin to @1 [phi:@begin->@1] +__b1_from___bbegin: + jmp __b1 + // @1 +__b1: + // [2] call main + jsr main + // [3] phi from @1 to @end [phi:@1->@end] +__bend_from___b1: + jmp __bend + // @end +__bend: + // main +main: { + // [4] *((const nomodify word*) SCREEN) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_ID) -- _deref_pwuc1=_deref_pwuc2 + lda point1+OFFSET_STRUCT_POINT_ID + sta SCREEN + lda point1+OFFSET_STRUCT_POINT_ID+1 + sta SCREEN+1 + // [5] *((const nomodify word*) SCREEN+(byte) 1*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS) -- _deref_pwuc1=_deref_pwuc2 + lda point1+OFFSET_STRUCT_POINT_POS + sta SCREEN+1*SIZEOF_WORD + lda point1+OFFSET_STRUCT_POINT_POS+1 + sta SCREEN+1*SIZEOF_WORD+1 + // [6] *((const nomodify word*) SCREEN+(byte) 2*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS+(byte) 1*(const byte) SIZEOF_WORD) -- _deref_pwuc1=_deref_pwuc2 + lda point1+OFFSET_STRUCT_POINT_POS+1*SIZEOF_WORD + sta SCREEN+2*SIZEOF_WORD + lda point1+OFFSET_STRUCT_POINT_POS+1*SIZEOF_WORD+1 + sta SCREEN+2*SIZEOF_WORD+1 + jmp __breturn + // main::@return + __breturn: + // [7] return + rts +} + // File Data + point1: .byte 4 + .word 1, 2, 3 + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [4] *((const nomodify word*) SCREEN) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_ID) [ point1 ] ( main:2 [ point1 ] { } ) always clobbers reg byte a +Statement [5] *((const nomodify word*) SCREEN+(byte) 1*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS) [ point1 ] ( main:2 [ point1 ] { } ) always clobbers reg byte a +Statement [6] *((const nomodify word*) SCREEN+(byte) 2*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS+(byte) 1*(const byte) SIZEOF_WORD) [ ] ( main:2 [ ] { } ) always clobbers reg byte a +Potential registers mem[7] [ point1 ] : mem[7] , + +REGISTER UPLIFT SCOPES +Uplift Scope [Point] +Uplift Scope [main] +Uplift Scope [] 0: mem[7] [ point1 ] + +Uplifting [Point] best 69 combination +Uplifting [main] best 69 combination +Uplifting [] best 69 combination mem[7] [ point1 ] + +ASSEMBLER BEFORE OPTIMIZATION + // File Comments +// Minimal struct with C-Standard behavior - struct with internal int array + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + .const SIZEOF_WORD = 2 + .const OFFSET_STRUCT_POINT_ID = 5 + .const OFFSET_STRUCT_POINT_POS = 1 + .label SCREEN = $400 + // @begin +__bbegin: + // [1] phi from @begin to @1 [phi:@begin->@1] +__b1_from___bbegin: + jmp __b1 + // @1 +__b1: + // [2] call main + jsr main + // [3] phi from @1 to @end [phi:@1->@end] +__bend_from___b1: + jmp __bend + // @end +__bend: + // main +main: { + // [4] *((const nomodify word*) SCREEN) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_ID) -- _deref_pwuc1=_deref_pwuc2 + lda point1+OFFSET_STRUCT_POINT_ID + sta SCREEN + lda point1+OFFSET_STRUCT_POINT_ID+1 + sta SCREEN+1 + // [5] *((const nomodify word*) SCREEN+(byte) 1*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS) -- _deref_pwuc1=_deref_pwuc2 + lda point1+OFFSET_STRUCT_POINT_POS + sta SCREEN+1*SIZEOF_WORD + lda point1+OFFSET_STRUCT_POINT_POS+1 + sta SCREEN+1*SIZEOF_WORD+1 + // [6] *((const nomodify word*) SCREEN+(byte) 2*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS+(byte) 1*(const byte) SIZEOF_WORD) -- _deref_pwuc1=_deref_pwuc2 + lda point1+OFFSET_STRUCT_POINT_POS+1*SIZEOF_WORD + sta SCREEN+2*SIZEOF_WORD + lda point1+OFFSET_STRUCT_POINT_POS+1*SIZEOF_WORD+1 + sta SCREEN+2*SIZEOF_WORD+1 + jmp __breturn + // main::@return + __breturn: + // [7] return + rts +} + // File Data + point1: .byte 4 + .word 1, 2, 3 + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp __b1 +Removing instruction jmp __bend +Removing instruction jmp __breturn +Succesful ASM optimization Pass5NextJumpElimination +Removing instruction __b1_from___bbegin: +Removing instruction __b1: +Removing instruction __bend_from___b1: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction __bbegin: +Removing instruction __bend: +Removing instruction __breturn: +Succesful ASM optimization Pass5UnusedLabelElimination +Removing instruction jsr main +Succesful ASM optimization Pass5SkipBegin + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(const byte) OFFSET_STRUCT_POINT_ID = (byte) 5 +(const byte) OFFSET_STRUCT_POINT_POS = (byte) 1 +(word) Point::id +(const word*) Point::pos[(number) 2] = { fill( 2, 0) } +(byte) Point::x +(const nomodify word*) SCREEN = (word*) 1024 +(const byte) SIZEOF_WORD = (byte) 2 +(void()) main() +(label) main::@return +(struct Point) point1 loadstore mem[7] = { x: (byte) 4, pos: { (word) 1, (word) 2 }, id: (word) 3 } + +mem[7] [ point1 ] + + +FINAL ASSEMBLER +Score: 54 + + // File Comments +// Minimal struct with C-Standard behavior - struct with internal int array + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + .const SIZEOF_WORD = 2 + .const OFFSET_STRUCT_POINT_ID = 5 + .const OFFSET_STRUCT_POINT_POS = 1 + .label SCREEN = $400 + // @begin + // [1] phi from @begin to @1 [phi:@begin->@1] + // @1 + // [2] call main + // [3] phi from @1 to @end [phi:@1->@end] + // @end + // main +main: { + // SCREEN[0] = point1.id + // [4] *((const nomodify word*) SCREEN) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_ID) -- _deref_pwuc1=_deref_pwuc2 + lda point1+OFFSET_STRUCT_POINT_ID + sta SCREEN + lda point1+OFFSET_STRUCT_POINT_ID+1 + sta SCREEN+1 + // SCREEN[1] = point1.pos[0] + // [5] *((const nomodify word*) SCREEN+(byte) 1*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS) -- _deref_pwuc1=_deref_pwuc2 + lda point1+OFFSET_STRUCT_POINT_POS + sta SCREEN+1*SIZEOF_WORD + lda point1+OFFSET_STRUCT_POINT_POS+1 + sta SCREEN+1*SIZEOF_WORD+1 + // SCREEN[2] = point1.pos[1] + // [6] *((const nomodify word*) SCREEN+(byte) 2*(const byte) SIZEOF_WORD) ← *((word*)&(struct Point) point1+(const byte) OFFSET_STRUCT_POINT_POS+(byte) 1*(const byte) SIZEOF_WORD) -- _deref_pwuc1=_deref_pwuc2 + lda point1+OFFSET_STRUCT_POINT_POS+1*SIZEOF_WORD + sta SCREEN+2*SIZEOF_WORD + lda point1+OFFSET_STRUCT_POINT_POS+1*SIZEOF_WORD+1 + sta SCREEN+2*SIZEOF_WORD+1 + // main::@return + // } + // [7] return + rts +} + // File Data + point1: .byte 4 + .word 1, 2, 3 + diff --git a/src/test/ref/struct-43.sym b/src/test/ref/struct-43.sym new file mode 100644 index 000000000..8a282485b --- /dev/null +++ b/src/test/ref/struct-43.sym @@ -0,0 +1,15 @@ +(label) @1 +(label) @begin +(label) @end +(const byte) OFFSET_STRUCT_POINT_ID = (byte) 5 +(const byte) OFFSET_STRUCT_POINT_POS = (byte) 1 +(word) Point::id +(const word*) Point::pos[(number) 2] = { fill( 2, 0) } +(byte) Point::x +(const nomodify word*) SCREEN = (word*) 1024 +(const byte) SIZEOF_WORD = (byte) 2 +(void()) main() +(label) main::@return +(struct Point) point1 loadstore mem[7] = { x: (byte) 4, pos: { (word) 1, (word) 2 }, id: (word) 3 } + +mem[7] [ point1 ]