dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.21k stars 1.57k forks source link

[VM/AOT] Smaller `throw` expressions #56969

Open rakudrama opened 3 hours ago

rakudrama commented 3 hours ago

I have been investigating the code size of

    for (final item in list) {
      ...
    }

where list is the default system growable list.

The loop is the same as

    for (final iter = list.iterator; iter.moveNext();) {
      final item = iter.current;
      ...
    }

With recent improvements, AOT now generates relatively good code. The compiler fully inlines ListIterator. The local value that represents _index is monotonic and compared against the current list length, allowing the bounds check to be removed.

The part that is still lacking is the code size for throwing a ConcurrentModificationError if the list (length) changes during iteration. I copied ListIterator and tried several variations on moveNext() that differ only at the throw expression:

  bool moveNext() {
    int length = _iterable.length;
    if (_length != length) {
      throw CME(_iterable); // This line
    }
    ...

Variation 1

The default inlined loop has throw-sequence of 7 instructions and 25 bytes:

        ;; B13
        ;; v34 <- AllocateObject:24(cls=CME, <not-aliased>) T{CME}
        ;; Inlined [ListIterator2.moveNext]
0x7faa347b1986    e8fbffffff             call 0x00007faa347b1986
        ;; ParallelMove rcx <- rax, rax <- fp[-3]
        ;; Inlined [ListIterator2.moveNext]
0x7faa347b198b    4889c1                 movq rcx,rax
0x7faa347b198e    488b45e8               movq rax,[rbp-0x18]
        ;; StoreField(v34 T{CME} . modifiedObject = v2, NoStoreBarrier)
        ;; Inlined [ListIterator2.moveNext]
0x7faa347b1992    4889410f               movq [rcx+0xf],rax
        ;; ParallelMove rax <- rcx
        ;; Inlined [ListIterator2.moveNext]
0x7faa347b1996    4889c8                 movq rax,rcx
        ;; Throw:30(v34)
        ;; Inlined [ListIterator2.moveNext]
0x7faa347b1999    e8fbffffff             call 0x00007faa347b1999
0x7faa347b199e    cc                     int3

Variation 2

I tried calling a constructor (CME.new2) that is annotated with vm:never-inline. The result is worse - 8 instruction / 30 bytes.

        ;; B13
        ;; v34 <- AllocateObject:24(cls=CME) T{CME}
        ;; Inlined [ListIterator2.moveNext2]
0x7faa347b1886    e8fbffffff             call 0x00007faa347b1886
        ;; ParallelMove rdi <- rax, rsi <- fp[-3], rax <- rax
        ;; Inlined [ListIterator2.moveNext2]
0x7faa347b188b    4889c7                 movq rdi,rax
0x7faa347b188e    488b75e8               movq rsi,[rbp-0x18]
        ;; ParallelMove fp[-3] <- rax
        ;; Inlined [ListIterator2.moveNext2]
0x7faa347b1892    488945e8               movq [rbp-0x18],rax
        ;; StaticCall:28( CME.new2<0> v34, v2)
        ;; Inlined [ListIterator2.moveNext2]
0x7faa347b1896    e8fbffffff             call 0x00007faa347b1896
        ;; ParallelMove rax <- fp[-3]
        ;; Inlined [ListIterator2.moveNext2]
0x7faa347b189b    488b45e8               movq rax,[rbp-0x18]
        ;; Throw:30(v34)
        ;; Inlined [ListIterator2.moveNext2]
0x7faa347b189f    e8fbffffff             call 0x00007faa347b189f
0x7faa347b18a4    cc                     int3

The inlining boundary is unexpected - naively I would have thought that the pragma would have caused the whole constructor sequence, including the allocation, to be out-of-line.

Variation 3

Calls a static method CME.new2 (never-inline) that calls the constructor. Using a static method as a 'constructor' gets the code size down to 4 instructions / 15 bytes.

        ;; B13
        ;; ParallelMove rdi <- fp[-3]
        ;; Inlined [ListIterator2.moveNext3]
0x7faa347b17a6    488b7de8               movq rdi,[rbp-0x18]
        ;; v35 <- StaticCall:26( new3<0> v2) T{CME}
        ;; Inlined [ListIterator2.moveNext3]
0x7faa347b17aa    e8fbffffff             call 0x00007faa347b17aa
        ;; ParallelMove rax <- rax
        ;; Throw:28(v35)
        ;; Inlined [ListIterator2.moveNext3]
0x7faa347b17af    e8fbffffff             call 0x00007faa347b17af
0x7faa347b17b4    cc                     int3

Variation 4

Call a helper function throwCME that throws. This by far the worst. The call itself is small (2 instructions, 8 bytes). The problem is that the rest of the moveNext method is worse. The loop is optimized as though (1) throwCME modifies the list and (2) then returns. The bounds check can no longer be eliminated. The block with the throwing call is in the middle if the loop instead of outside.

        ;; B13
        ;;   Loop 0
        ;; ParallelMove rdi <- rax
        ;; Inlined [ListIterator2.moveNext4]
0x7faa347b13d8    4889c7                 movq rdi,rax
        ;; StaticCall:26( throwCME<0> v2)
        ;; Inlined [ListIterator2.moveNext4]
0x7faa347b13db    e8fbffffff             call 0x00007faa347b13db
        ;; ParallelMove  goto:32 B15
        ;; B14
        ;; B15

Discussion

Variation 4 should have been the best for code size.

The compiler should treat functions that never return like it does Throw instructions. With sound null-safety, it is no longer possible for a Never function to return null. The call to the throw-helper should be block-scheduled outside the loop.

dart2js automatically achieves something close to Version 3.

In dart2js model, each generative constructor has a generative constructor factory which contains the allocation and everything else. A new expression compiles to a call to the factory, which may or may not be inlined. One of the inlining heuristics dart2js uses is to only allow inlining in the expression of a throw if the inlining is expected to reduce size (i.e. a location dependent reductive heuristic).

If AOT adopted a similar generative constructor factor element in its model, a similar heuristic would work. As we can see from Variation 2, that would un unadvisable without the change in the element model.

Variation 4 has the disadvantage that the throw-helper changes the call stack.

I would like to see AOT automatically compile the original code more like a slow-path stub. This would be a combination of limited out-lining and a tail call.

It might work something like this:

During inlining we see the static call in this pattern:

v1 <- AllocateObject(cls=FooError)
v2 <- StaticCall( constructor, v1, a1, a2, a3)
Throw(v2)

A lot of information is available about the candidate. If the inlining heuristics would want to inline constructor, or otherwise can tell there are no errors, instead, generate a small method constructor_a1_a2_a3 with parameters a1, a2 etc. Replace the three instructions with

StaticCall( constructor_a1_a2_a3, a1, a2, a3)
ThrowTrap() // int3

The body of the synthesized method is constructed from the inlining candidate by inserting an allocation, and ending with a new instruction

ThrowTail() // jump to a throw stub.

There will be a different stub for each combination of optional arguments. In large programs most stubs will be used several times. There will need to be a way to deduplicate stubs.

Appendix

dart compile exe                                         \
   --extra-gen-snapshot-options --disassemble-optimized  \
   --extra-gen-snapshot-options --code-comments          \
   --extra-gen-snapshot-options --print-flow-graph-filter=useL \
   -v bug1a3j.dart --output=z5
code.dart ```dart import 'dart:core'; import 'dart:core' as core; int _line = 0, _limit1 = 1, _limit2 = 10; @pragma('vm:never-inline') void print(Object? item) { _line++; if (_line >= 10) { if (_line != _limit1 * _limit2) return; if (_limit1 == 1) { _limit1 = 2; } else if (_limit1 == 2) { _limit1 = 5; } else if (_limit1 == 5) { _limit1 = 1; _limit2 *= 10; } } core.print('$_line\t$item'); } class ListIterator2 implements Iterator { final Iterable _iterable; final int _length; int _index; E? _current; @pragma("wasm:prefer-inline") ListIterator2(Iterable iterable) : _iterable = iterable, _length = iterable.length, _index = 0; E get current => _current as E; @pragma("vm:prefer-inline") bool moveNext() { int length = _iterable.length; if (_length != length) { throw CME(_iterable); } if (_index >= length) { _current = null; return false; } _current = _iterable.elementAt(_index); _index++; return true; } @pragma("vm:prefer-inline") bool moveNext2() { int length = _iterable.length; if (_length != length) { throw CME.new2(_iterable); } if (_index >= length) { _current = null; return false; } _current = _iterable.elementAt(_index); _index++; return true; } @pragma("vm:prefer-inline") bool moveNext3() { int length = _iterable.length; if (_length != length) { throw CME.new3(_iterable); } if (_index >= length) { _current = null; return false; } _current = _iterable.elementAt(_index); _index++; return true; } @pragma("vm:prefer-inline") bool moveNext4() { int length = _iterable.length; if (_length != length) { CME.throwCME(_iterable); } if (_index >= length) { _current = null; return false; } _current = _iterable.elementAt(_index); _index++; return true; } } class CME extends Error { /// The object that was modified in an incompatible way. final Object? modifiedObject; CME([this.modifiedObject]); @pragma('vm:never-inline') CME.new2([this.modifiedObject]); @pragma('vm:never-inline') static CME new3(Object? o) => CME(o); @pragma('vm:never-inline') static Never throwCME(Object? o) { throw CME(o); } String toString() { if (modifiedObject == null) { return "Concurrent modification during iteration."; } return "Concurrent modification during iteration: " "${Error.safeToString(modifiedObject)}."; } } @pragma('vm:never-inline') @pragma('dart2js:never-inline') void useList1(List list) { for (final iter = ListIterator2(list); iter.moveNext();) print(iter.current); } @pragma('vm:never-inline') @pragma('dart2js:never-inline') void useList2(List list) { for (final iter = ListIterator2(list); iter.moveNext2();) print(iter.current); } @pragma('vm:never-inline') @pragma('dart2js:never-inline') void useList3(List list) { for (final iter = ListIterator2(list); iter.moveNext3();) print(iter.current); } @pragma('vm:never-inline') @pragma('dart2js:never-inline') void useList4(List list) { for (final iter = ListIterator2(list); iter.moveNext4();) print(iter.current); } void main() { for (int i = 0; i < 1000000; i++) { useList1([]); useList1([1]); useList1([2, 3]); useList2([]); useList2([1]); useList2([2, 3]); useList3([]); useList3([1]); useList3([2, 3]); useList4([]); useList4([1]); useList4([2, 3]); } } ```
Output ``` Specializing Platform getters for target OS linux. Generating AOT kernel dill. Compiling /usr/local/google/home/sra/play1/bug1a3j.dart to /usr/local/google/home/sra/Dart2/sdk/z5 using format Kind.exe: Generating AOT snapshot. /usr/local/google/home/sra/Dart2/sdk/out/ReleaseX64/dart-sdk/bin/utils/gen_snapshot [--disassemble-optimized, --code-comments, --print-flow-graph-filter=useL] Code for optimized function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList4' (RegularFunction) { ;; B0 ;; B1 ;; Enter frame ;; PrologueOffset = 0 ;; Inlined [ListIterator2.moveNext4] 0x7faa347b1390 55 push rbp 0x7faa347b1391 4889e5 movq rbp,rsp 0x7faa347b1394 4883ec20 subq rsp,0x20 ;; ParallelMove rax <- rdi, fp[-4] <- rdi ;; Inlined [ListIterator2.moveNext4] 0x7faa347b1398 4889f8 movq rax,rdi 0x7faa347b139b 48897de0 movq [rbp-0x20],rdi ;; CheckStackOverflow:8(stack=0, loop=0) ;; Inlined [ListIterator2.moveNext4] 0x7faa347b139f 493b6638 cmpq rsp,[thr+0x38] 0x7faa347b13a3 0f868c000000 jna 0x00007faa347b1435 ;; v84 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.length) [0, 576460752303423487] T{_Smi} ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13a9 488b480f movq rcx,[rax+0xf] ;; ParallelMove rcx <- rcx ;; v90 <- UnboxInt64([non-speculative], v84 T{_Smi}) [v84, v84] int64 ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13ad 48d1f9 sarq rcx,1 ;; ParallelMove fp[-3] <- rcx ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13b0 48894de8 movq [rbp-0x18],rcx ;; ParallelMove rbx <- C goto:30 B5 ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13b4 33db xorl rbx,rbx ;; B5 ;; Loop 0 ;; Loop Header ;; ParallelMove fp[-2] <- rbx ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13b6 48895df0 movq [rbp-0x10],rbx ;; CheckStackOverflow:34(stack=0, loop=1) ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13ba 493b6638 cmpq rsp,[thr+0x38] 0x7faa347b13be 0f867d000000 jna 0x00007faa347b1441 ;; v79 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.length) [0, 576460752303423487] T{_Smi} ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13c4 488b500f movq rdx,[rax+0xf] ;; ParallelMove rdx <- rdx ;; v92 <- UnboxInt64([non-speculative], v79 T{_Smi}) [v79, v79] int64 ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13c8 48d1fa sarq rdx,1 ;; ParallelMove fp[-1] <- rdx ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13cb 488955f8 movq [rbp-0x8],rdx ;; Branch if EqualityCompare(v90 T{_Smi} != v92 T{_Smi}) T{bool} goto (13, 14) ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13cf 483bca cmpq rcx,rdx 0x7faa347b13d2 0f8408000000 jz 0x00007faa347b13e0 ;; B13 ;; Loop 0 ;; ParallelMove rdi <- rax ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13d8 4889c7 movq rdi,rax ;; StaticCall:26( throwCME<0> v2) ;; Inlined [ListIterator2.moveNext4] 0x7faa347b13db e8fbffffff call 0x00007faa347b13db ;; ParallelMove goto:32 B15 ;; B14 ;; B15 ;; Loop 0 0x7faa347b13e0 488b45f8 movq rax,[rbp-0x8] 0x7faa347b13e4 488b4df0 movq rcx,[rbp-0x10] ;; Branch if RelationalOp(>=, [non-speculative], v97 T{int}, v92 T{_Smi}) T{bool} goto (4, 10) 0x7faa347b13e8 483bc8 cmpq rcx,rax 0x7faa347b13eb 0f8d3b000000 jge 0x00007faa347b142c ;; B10 ;; Loop 0 ;; ParallelMove rdx <- fp[-4] 0x7faa347b13f1 488b55e0 movq rdx,[rbp-0x20] ;; v54 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.length) [0, 576460752303423487] T{_Smi} 0x7faa347b13f5 488b420f movq rax,[rdx+0xf] ;; ParallelMove rax <- rax ;; v93 <- UnboxInt64([non-speculative], v54) [v54, v54] int64 0x7faa347b13f9 48d1f8 sarq rax,1 ;; ParallelMove rax <- rax, rbx <- rcx 0x7faa347b13fc 4889cb movq rbx,rcx ;; GenericCheckBound:10(v93 T{_Smi}, v97 T{int}) [0, v92-1] int64 0x7faa347b13ff 483bd8 cmpq rbx,rax 0x7faa347b1402 0f8345000000 jnc 0x00007faa347b144d ;; v56 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.data) T{_List} 0x7faa347b1408 488b4217 movq rax,[rdx+0x17] ;; v100 <- LoadIndexed:10([_List] v56, v97 T{int}) T{X0?} 0x7faa347b140c 488b7cc817 movq rdi,[rax+rcx*8+0x17] ;; ParallelMove rcx <- rcx ;; v32 <- BinaryInt64Op(+ [tr], v97 T{int}, v101 T{_Smi}) [1, v92] int64 0x7faa347b1411 4883c101 addq rcx,1 ;; ParallelMove rdi <- rdi, fp[-1] <- rcx 0x7faa347b1415 48894df8 movq [rbp-0x8],rcx ;; StaticCall:26( print<0> v100 T{X0?}) 0x7faa347b1419 e8fbffffff call 0x00007faa347b1419 ;; ParallelMove rbx <- fp[-1], rax <- fp[-4], rcx <- fp[-3] goto:32 B5 0x7faa347b141e 488b5df8 movq rbx,[rbp-0x8] 0x7faa347b1422 488b45e0 movq rax,[rbp-0x20] 0x7faa347b1426 488b4de8 movq rcx,[rbp-0x18] 0x7faa347b142a eb8a jmp 0x00007faa347b13b6 ;; B4 ;; ParallelMove rax <- C 0x7faa347b142c 498b4670 movq rax,[thr+0x70] null ;; DartReturn:36(v0) 0x7faa347b1430 4889ec movq rsp,rbp 0x7faa347b1433 5d pop rbp 0x7faa347b1434 c3 ret ;; CheckStackOverflowSlowPath 0x7faa347b1435 41ff9630020000 call [thr+0x230] 0x7faa347b143c e968ffffff jmp 0x00007faa347b13a9 ;; CheckStackOverflowSlowPath 0x7faa347b1441 41ff9630020000 call [thr+0x230] 0x7faa347b1448 e977ffffff jmp 0x00007faa347b13c4 ;; slow path check bound operation 0x7faa347b144d e8fbffffff call 0x00007faa347b144d } Source positions for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList4' { 7faa347b1390-7faa347b13df: Function 'useList4': static.@145Function 'moveNext4':.@85 7faa347b13e0-7faa347b141d: Function 'useList4': static.@145 7faa347b141e-7faa347b143b: Function 'useList4': static.@142 7faa347b143c-7faa347b1447: Function 'useList4': static.@145 7faa347b1448-7faa347b1451: Function 'useList4': static.@NoSource } StackMaps for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList4' { 0x7faa347b13e0: 000 0x7faa347b141e: 000 0x7faa347b143c: 000000000000001 0x7faa347b1448: 000000000000001 0x7faa347b1452: 000000000000100 } Catch entry moves for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList4' { } Entry points for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList4' { [code+0x07] 7faa347b1390 kNormal [code+0x0f] 7faa347b1390 kMonomorphic [code+0x17] 7faa347b1390 kUnchecked [code+0x1f] 7faa347b1390 kMonomorphicUnchecked } Static call target functions { 0x7faa347b13e0: file:///usr/local/google/home/sra/play1/bug1a3j.dart_CME_throwCME, (pc-relative-call) 0x7faa347b141e: file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_print, (pc-relative-call) 0x7faa347b1452: [Stub] _iso_stub_RangeErrorSharedWithoutFPURegsStub, (pc-relative-call) } Code for optimized function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList3' (RegularFunction) { ;; B0 ;; B1 ;; Enter frame ;; PrologueOffset = 0 0x7faa347b1730 55 push rbp 0x7faa347b1731 4889e5 movq rbp,rsp 0x7faa347b1734 4883ec18 subq rsp,0x18 ;; ParallelMove rax <- rdi, fp[-3] <- rdi 0x7faa347b1738 4889f8 movq rax,rdi 0x7faa347b173b 48897de8 movq [rbp-0x18],rdi ;; CheckStackOverflow:8(stack=0, loop=0) 0x7faa347b173f 493b6638 cmpq rsp,[thr+0x38] 0x7faa347b1743 0f866c000000 jna 0x00007faa347b17b5 ;; v85 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.length) [0, 576460752303423487] T{_Smi} 0x7faa347b1749 488b480f movq rcx,[rax+0xf] ;; ParallelMove rcx <- rcx ;; v91 <- UnboxInt64([non-speculative], v85 T{_Smi}) [v85, v85] int64 0x7faa347b174d 48d1f9 sarq rcx,1 ;; ParallelMove fp[-2] <- rcx 0x7faa347b1750 48894df0 movq [rbp-0x10],rcx ;; ParallelMove rdx <- C goto:30 B5 0x7faa347b1754 33d2 xorl rdx,rdx ;; B5 ;; Loop 0 ;; Loop Header ;; CheckStackOverflow:34(stack=0, loop=1) 0x7faa347b1756 493b6638 cmpq rsp,[thr+0x38] 0x7faa347b175a 0f865e000000 jna 0x00007faa347b17be ;; v80 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.length) [0, 576460752303423487] T{_Smi} 0x7faa347b1760 488b580f movq rbx,[rax+0xf] ;; ParallelMove rbx <- rbx ;; v93 <- UnboxInt64([non-speculative], v80 T{_Smi}) [v80, v80] int64 0x7faa347b1764 48d1fb sarq rbx,1 ;; Branch if EqualityCompare(v91 T{_Smi} != v93 T{_Smi}) T{bool} goto (13, 14) 0x7faa347b1767 483bcb cmpq rcx,rbx 0x7faa347b176a 0f8536000000 jnz 0x00007faa347b17a6 ;; B14 ;; Loop 0 ;; Branch if RelationalOp(>=, [non-speculative], v98 T{int}, v93 T{_Smi}) T{bool} goto (4, 10) 0x7faa347b1770 483bd3 cmpq rdx,rbx 0x7faa347b1773 0f8d24000000 jge 0x00007faa347b179d ;; B10 ;; Loop 0 ;; v57 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.data) T{_List} 0x7faa347b1779 488b5817 movq rbx,[rax+0x17] ;; v101 <- LoadIndexed:10([_List] v57, v98 T{int}) T{X0?} 0x7faa347b177d 488b7cd317 movq rdi,[rbx+rdx*8+0x17] ;; ParallelMove rdx <- rdx ;; v32 <- BinaryInt64Op(+ [tr], v98 T{int}, v102 T{_Smi}) [1, v93] int64 0x7faa347b1782 4883c201 addq rdx,1 ;; ParallelMove rdi <- rdi, fp[-1] <- rdx 0x7faa347b1786 488955f8 movq [rbp-0x8],rdx ;; StaticCall:26( print<0> v101 T{X0?}) 0x7faa347b178a e8fbffffff call 0x00007faa347b178a ;; ParallelMove rdx <- fp[-1], rax <- fp[-3], rcx <- fp[-2] goto:32 B5 ;; Inlined [ListIterator2.moveNext3] 0x7faa347b178f 488b55f8 movq rdx,[rbp-0x8] 0x7faa347b1793 488b45e8 movq rax,[rbp-0x18] 0x7faa347b1797 488b4df0 movq rcx,[rbp-0x10] 0x7faa347b179b ebb9 jmp 0x00007faa347b1756 ;; B4 ;; ParallelMove rax <- C ;; Inlined [ListIterator2.moveNext3] 0x7faa347b179d 498b4670 movq rax,[thr+0x70] null ;; DartReturn:36(v0) ;; Inlined [ListIterator2.moveNext3] 0x7faa347b17a1 4889ec movq rsp,rbp 0x7faa347b17a4 5d pop rbp 0x7faa347b17a5 c3 ret ;; B13 ;; ParallelMove rdi <- fp[-3] ;; Inlined [ListIterator2.moveNext3] 0x7faa347b17a6 488b7de8 movq rdi,[rbp-0x18] ;; v35 <- StaticCall:26( new3<0> v2) T{CME} ;; Inlined [ListIterator2.moveNext3] 0x7faa347b17aa e8fbffffff call 0x00007faa347b17aa ;; ParallelMove rax <- rax ;; Throw:28(v35) ;; Inlined [ListIterator2.moveNext3] 0x7faa347b17af e8fbffffff call 0x00007faa347b17af 0x7faa347b17b4 cc int3 ;; CheckStackOverflowSlowPath 0x7faa347b17b5 41ff9630020000 call [thr+0x230] 0x7faa347b17bc eb8b jmp 0x00007faa347b1749 ;; CheckStackOverflowSlowPath 0x7faa347b17be 41ff9630020000 call [thr+0x230] 0x7faa347b17c5 eb99 jmp 0x00007faa347b1760 } Source positions for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList3' { 7faa347b1730-7faa347b178e: Function 'useList3': static.@139 7faa347b178f-7faa347b17ae: Function 'useList3': static.@139Function 'moveNext3':.@70 7faa347b17af-7faa347b17b3: Function 'useList3': static.@139Function 'moveNext3':.@70 7faa347b17b4-7faa347b17bb: Function 'useList3': static.@136 7faa347b17bc-7faa347b17c4: Function 'useList3': static.@139 } StackMaps for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList3' { 0x7faa347b178f: 00 0x7faa347b17af: 000 0x7faa347b17b4: 000 0x7faa347b17bc: 00000000000001 0x7faa347b17c5: 00000000000001 } Catch entry moves for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList3' { } Entry points for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList3' { [code+0x07] 7faa347b1730 kNormal [code+0x0f] 7faa347b1730 kMonomorphic [code+0x17] 7faa347b1730 kUnchecked [code+0x1f] 7faa347b1730 kMonomorphicUnchecked } Static call target functions { 0x7faa347b178f: file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_print, (pc-relative-call) 0x7faa347b17af: file:///usr/local/google/home/sra/play1/bug1a3j.dart_CME_new3, (pc-relative-call) 0x7faa347b17b4: [Stub] _iso_stub_ThrowStub, (pc-relative-call) } Code for optimized function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList2' (RegularFunction) { ;; B0 ;; B1 ;; Enter frame ;; PrologueOffset = 0 0x7faa347b1810 55 push rbp 0x7faa347b1811 4889e5 movq rbp,rsp 0x7faa347b1814 4883ec18 subq rsp,0x18 ;; ParallelMove rsi <- rdi, fp[-3] <- rdi 0x7faa347b1818 4889fe movq rsi,rdi 0x7faa347b181b 48897de8 movq [rbp-0x18],rdi ;; CheckStackOverflow:8(stack=0, loop=0) 0x7faa347b181f 493b6638 cmpq rsp,[thr+0x38] 0x7faa347b1823 0f867c000000 jna 0x00007faa347b18a5 ;; v85 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.length) [0, 576460752303423487] T{_Smi} 0x7faa347b1829 488b460f movq rax,[rsi+0xf] ;; ParallelMove rax <- rax ;; v91 <- UnboxInt64([non-speculative], v85 T{_Smi}) [v85, v85] int64 0x7faa347b182d 48d1f8 sarq rax,1 ;; ParallelMove fp[-2] <- rax 0x7faa347b1830 488945f0 movq [rbp-0x10],rax ;; ParallelMove rcx <- C goto:30 B5 0x7faa347b1834 33c9 xorl rcx,rcx ;; B5 ;; Loop 0 ;; Loop Header ;; CheckStackOverflow:34(stack=0, loop=1) 0x7faa347b1836 493b6638 cmpq rsp,[thr+0x38] 0x7faa347b183a 0f8671000000 jna 0x00007faa347b18b1 ;; v80 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.length) [0, 576460752303423487] T{_Smi} 0x7faa347b1840 488b560f movq rdx,[rsi+0xf] ;; ParallelMove rdx <- rdx ;; v93 <- UnboxInt64([non-speculative], v80 T{_Smi}) [v80, v80] int64 0x7faa347b1844 48d1fa sarq rdx,1 ;; Branch if EqualityCompare(v91 T{_Smi} != v93 T{_Smi}) T{bool} goto (13, 14) 0x7faa347b1847 483bc2 cmpq rax,rdx 0x7faa347b184a 0f8536000000 jnz 0x00007faa347b1886 ;; B14 ;; Loop 0 ;; Branch if RelationalOp(>=, [non-speculative], v98 T{int}, v93 T{_Smi}) T{bool} goto (4, 10) 0x7faa347b1850 483bca cmpq rcx,rdx 0x7faa347b1853 0f8d24000000 jge 0x00007faa347b187d ;; B10 ;; Loop 0 ;; v57 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.data) T{_List} 0x7faa347b1859 488b5617 movq rdx,[rsi+0x17] ;; v101 <- LoadIndexed:10([_List] v57, v98 T{int}) T{X0?} 0x7faa347b185d 488b7cca17 movq rdi,[rdx+rcx*8+0x17] ;; ParallelMove rcx <- rcx ;; v32 <- BinaryInt64Op(+ [tr], v98 T{int}, v102 T{_Smi}) [1, v93] int64 0x7faa347b1862 4883c101 addq rcx,1 ;; ParallelMove rdi <- rdi, fp[-1] <- rcx 0x7faa347b1866 48894df8 movq [rbp-0x8],rcx ;; StaticCall:26( print<0> v101 T{X0?}) 0x7faa347b186a e8fbffffff call 0x00007faa347b186a ;; ParallelMove rcx <- fp[-1], rsi <- fp[-3], rax <- fp[-2] goto:32 B5 ;; Inlined [ListIterator2.moveNext2] 0x7faa347b186f 488b4df8 movq rcx,[rbp-0x8] 0x7faa347b1873 488b75e8 movq rsi,[rbp-0x18] 0x7faa347b1877 488b45f0 movq rax,[rbp-0x10] 0x7faa347b187b ebb9 jmp 0x00007faa347b1836 ;; B4 ;; ParallelMove rax <- C ;; Inlined [ListIterator2.moveNext2] 0x7faa347b187d 498b4670 movq rax,[thr+0x70] null ;; DartReturn:36(v0) ;; Inlined [ListIterator2.moveNext2] 0x7faa347b1881 4889ec movq rsp,rbp 0x7faa347b1884 5d pop rbp 0x7faa347b1885 c3 ret ;; B13 ;; v34 <- AllocateObject:24(cls=CME) T{CME} ;; Inlined [ListIterator2.moveNext2] 0x7faa347b1886 e8fbffffff call 0x00007faa347b1886 ;; ParallelMove rdi <- rax, rsi <- fp[-3], rax <- rax ;; Inlined [ListIterator2.moveNext2] 0x7faa347b188b 4889c7 movq rdi,rax 0x7faa347b188e 488b75e8 movq rsi,[rbp-0x18] ;; ParallelMove fp[-3] <- rax ;; Inlined [ListIterator2.moveNext2] 0x7faa347b1892 488945e8 movq [rbp-0x18],rax ;; StaticCall:28( CME.new2<0> v34, v2) ;; Inlined [ListIterator2.moveNext2] 0x7faa347b1896 e8fbffffff call 0x00007faa347b1896 ;; ParallelMove rax <- fp[-3] ;; Inlined [ListIterator2.moveNext2] 0x7faa347b189b 488b45e8 movq rax,[rbp-0x18] ;; Throw:30(v34) ;; Inlined [ListIterator2.moveNext2] 0x7faa347b189f e8fbffffff call 0x00007faa347b189f 0x7faa347b18a4 cc int3 ;; CheckStackOverflowSlowPath 0x7faa347b18a5 41ff9630020000 call [thr+0x230] 0x7faa347b18ac e978ffffff jmp 0x00007faa347b1829 ;; CheckStackOverflowSlowPath 0x7faa347b18b1 41ff9630020000 call [thr+0x230] 0x7faa347b18b8 eb86 jmp 0x00007faa347b1840 } Source positions for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList2' { 7faa347b1810-7faa347b186e: Function 'useList2': static.@133 7faa347b186f-7faa347b188a: Function 'useList2': static.@133Function 'moveNext2':.@55 7faa347b188b-7faa347b189a: Function 'useList2': static.@133Function 'moveNext2':.@55 7faa347b189b-7faa347b18a3: Function 'useList2': static.@133Function 'moveNext2':.@55 7faa347b18a4-7faa347b18ab: Function 'useList2': static.@130 7faa347b18ac-7faa347b18b7: Function 'useList2': static.@133 } StackMaps for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList2' { 0x7faa347b186f: 00 0x7faa347b188b: 00 0x7faa347b189b: 00 0x7faa347b18a4: 000 0x7faa347b18ac: 00000000010000 0x7faa347b18b8: 00000000010000 } Catch entry moves for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList2' { } Entry points for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList2' { [code+0x07] 7faa347b1810 kNormal [code+0x0f] 7faa347b1810 kMonomorphic [code+0x17] 7faa347b1810 kUnchecked [code+0x1f] 7faa347b1810 kMonomorphicUnchecked } Static call target functions { 0x7faa347b186f: file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_print, (pc-relative-call) 0x7faa347b188b: allocation stub for Library:'file:///usr/local/google/home/sra/play1/bug1a3j.dart' Class: CME, (pc-relative-call) 0x7faa347b189b: file:///usr/local/google/home/sra/play1/bug1a3j.dart_CME_CME.new2, (pc-relative-call) 0x7faa347b18a4: [Stub] _iso_stub_ThrowStub, (pc-relative-call) } Code for optimized function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList1' (RegularFunction) { ;; B0 ;; B1 ;; Enter frame ;; PrologueOffset = 0 0x7faa347b1910 55 push rbp 0x7faa347b1911 4889e5 movq rbp,rsp 0x7faa347b1914 4883ec18 subq rsp,0x18 ;; ParallelMove rax <- rdi, fp[-3] <- rdi 0x7faa347b1918 4889f8 movq rax,rdi 0x7faa347b191b 48897de8 movq [rbp-0x18],rdi ;; CheckStackOverflow:8(stack=0, loop=0) 0x7faa347b191f 493b6638 cmpq rsp,[thr+0x38] 0x7faa347b1923 0f8676000000 jna 0x00007faa347b199f ;; v91 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.length) [0, 576460752303423487] T{_Smi} 0x7faa347b1929 488b480f movq rcx,[rax+0xf] ;; ParallelMove rcx <- rcx ;; v101 <- UnboxInt64([non-speculative], v91 T{_Smi}) [v91, v91] int64 0x7faa347b192d 48d1f9 sarq rcx,1 ;; ParallelMove fp[-2] <- rcx 0x7faa347b1930 48894df0 movq [rbp-0x10],rcx ;; ParallelMove rdx <- C goto:30 B5 0x7faa347b1934 33d2 xorl rdx,rdx ;; B5 ;; Loop 0 ;; Loop Header ;; CheckStackOverflow:34(stack=0, loop=1) 0x7faa347b1936 493b6638 cmpq rsp,[thr+0x38] 0x7faa347b193a 0f8668000000 jna 0x00007faa347b19a8 ;; v86 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.length) [0, 576460752303423487] T{_Smi} 0x7faa347b1940 488b580f movq rbx,[rax+0xf] ;; ParallelMove rbx <- rbx ;; v103 <- UnboxInt64([non-speculative], v86 T{_Smi}) [v86, v86] int64 0x7faa347b1944 48d1fb sarq rbx,1 ;; Branch if EqualityCompare(v101 T{_Smi} != v103 T{_Smi}) T{bool} goto (13, 14) 0x7faa347b1947 483bcb cmpq rcx,rbx 0x7faa347b194a 0f8536000000 jnz 0x00007faa347b1986 ;; B14 ;; Loop 0 ;; Branch if RelationalOp(>=, [non-speculative], v108 T{int}, v103 T{_Smi}) T{bool} goto (4, 10) 0x7faa347b1950 483bd3 cmpq rdx,rbx 0x7faa347b1953 0f8d24000000 jge 0x00007faa347b197d ;; B10 ;; Loop 0 ;; v57 <- LoadField(v2 T{_GrowableList} . GrowableObjectArray.data) T{_List} 0x7faa347b1959 488b5817 movq rbx,[rax+0x17] ;; v111 <- LoadIndexed:10([_List] v57, v108 T{int}) T{X0?} 0x7faa347b195d 488b7cd317 movq rdi,[rbx+rdx*8+0x17] ;; ParallelMove rdx <- rdx ;; v32 <- BinaryInt64Op(+ [tr], v108 T{int}, v112 T{_Smi}) [1, v103] int64 0x7faa347b1962 4883c201 addq rdx,1 ;; ParallelMove rdi <- rdi, fp[-1] <- rdx 0x7faa347b1966 488955f8 movq [rbp-0x8],rdx ;; StaticCall:26( print<0> v111 T{X0?}) 0x7faa347b196a e8fbffffff call 0x00007faa347b196a ;; ParallelMove rdx <- fp[-1], rax <- fp[-3], rcx <- fp[-2] goto:32 B5 ;; Inlined [ListIterator2.moveNext] 0x7faa347b196f 488b55f8 movq rdx,[rbp-0x8] 0x7faa347b1973 488b45e8 movq rax,[rbp-0x18] 0x7faa347b1977 488b4df0 movq rcx,[rbp-0x10] 0x7faa347b197b ebb9 jmp 0x00007faa347b1936 ;; B4 ;; ParallelMove rax <- C ;; Inlined [ListIterator2.moveNext] 0x7faa347b197d 498b4670 movq rax,[thr+0x70] null ;; DartReturn:36(v0) ;; Inlined [ListIterator2.moveNext] 0x7faa347b1981 4889ec movq rsp,rbp 0x7faa347b1984 5d pop rbp 0x7faa347b1985 c3 ret ;; B13 ;; v34 <- AllocateObject:24(cls=CME, ) T{CME} ;; Inlined [ListIterator2.moveNext] 0x7faa347b1986 e8fbffffff call 0x00007faa347b1986 ;; ParallelMove rcx <- rax, rax <- fp[-3] ;; Inlined [ListIterator2.moveNext] 0x7faa347b198b 4889c1 movq rcx,rax 0x7faa347b198e 488b45e8 movq rax,[rbp-0x18] ;; StoreField(v34 T{CME} . modifiedObject = v2, NoStoreBarrier) ;; Inlined [ListIterator2.moveNext] 0x7faa347b1992 4889410f movq [rcx+0xf],rax ;; ParallelMove rax <- rcx ;; Inlined [ListIterator2.moveNext] 0x7faa347b1996 4889c8 movq rax,rcx ;; Throw:30(v34) ;; Inlined [ListIterator2.moveNext] 0x7faa347b1999 e8fbffffff call 0x00007faa347b1999 0x7faa347b199e cc int3 ;; CheckStackOverflowSlowPath 0x7faa347b199f 41ff9630020000 call [thr+0x230] 0x7faa347b19a6 eb81 jmp 0x00007faa347b1929 ;; CheckStackOverflowSlowPath 0x7faa347b19a8 41ff9630020000 call [thr+0x230] 0x7faa347b19af eb8f jmp 0x00007faa347b1940 } Source positions for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList1' { 7faa347b1910-7faa347b196e: Function 'useList1': static.@127 7faa347b196f-7faa347b198a: Function 'useList1': static.@127Function 'moveNext':.@40 7faa347b198b-7faa347b199d: Function 'useList1': static.@127Function 'moveNext':.@40 7faa347b199e-7faa347b19a5: Function 'useList1': static.@124 7faa347b19a6-7faa347b19ae: Function 'useList1': static.@127 } StackMaps for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList1' { 0x7faa347b196f: 00 0x7faa347b198b: 00 0x7faa347b199e: 000 0x7faa347b19a6: 00000000000001 0x7faa347b19af: 00000000000001 } Catch entry moves for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList1' { } Entry points for function 'file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_useList1' { [code+0x07] 7faa347b1910 kNormal [code+0x0f] 7faa347b1910 kMonomorphic [code+0x17] 7faa347b1910 kUnchecked [code+0x1f] 7faa347b1910 kMonomorphicUnchecked } Static call target functions { 0x7faa347b196f: file:///usr/local/google/home/sra/play1/bug1a3j.dart_::_print, (pc-relative-call) 0x7faa347b198b: allocation stub for Library:'file:///usr/local/google/home/sra/play1/bug1a3j.dart' Class: CME, (pc-relative-call) 0x7faa347b199e: [Stub] _iso_stub_ThrowStub, (pc-relative-call) } Generating executable. Marking binary executable. Generated: /usr/local/google/home/sra/Dart2/sdk/z5 ```
dart-github-bot commented 3 hours ago

Summary: This issue reports that the Dart VM's AOT compiler generates inefficient code for throwing ConcurrentModificationError exceptions during list iteration. The user has experimented with different approaches to minimize code size, but the current implementation still results in large code size and unexpected inlining behavior.