4221 on new codegen
Test 4221 (under the ffi folder) segfaults with the new codegen turned on. Before: 0xb7a68c78: 0x822fadc <stg_ap_v_info> 0xb7a68c74: 0xb7ab8f94 0xb7a68c70: 0xb7ab8fa4 0xb7a68c6c: 0x8134dc0 <stT_info> 0xb7a68c68: 0xb7ab8f94 0xb7a68c64: 0x822f9f4 <stg_marked_upd_frame_info> 0xb7a68c60: 0xb7ab8f48 0xb7a68c5c: 0xb7ab8f88 0xb7a68c58: 0x4063a000 # this is the counted float; this 0xb7a68c54: 0x0 # val is 157.0 0xb7a68c50: 0x804bbc0 <c1jG_info> After: 0xb7a68c78: 0x822fadc <stg_ap_v_info> 0xb7a68c74: 0xb7ab8f94 0xb7a68c70: 0xb7ab8fa4 0xb7a68c6c: 0x8134dc0 <stT_info> 0xb7a68c68: 0xb7ab8f94 0xb7a68c64: 0x822f9f4 <stg_marked_upd_frame_info> 0xb7a68c60: 0xb7ab8f48 0xb7a68c5c: 0xb7ab8f88 0xb7a68c58: 0x4063a000 0xb7a68c54: 0x0 It then jumps c1jL_entry to which jumps to stg_gc_d1_info which segfaults on return because 0xb7a68c54 is not a valid address. The offending assembly: (gdb) disas Dump of assembler code for function c1jG_info: 0x0804bbc0 <+0>: fldl 0x4(%ebp) 0x0804bbc3 <+3>: fstp %st(1) 0x0804bbc5 <+5>: fstl 0x4(%ebp) 0x0804bbc8 <+8>: add $0x4,%ebp => 0x0804bbcb <+11>: ffree %st(0) 0x0804bbcd <+13>: ffree %st(1) 0x0804bbcf <+15>: ffree %st(2) 0x0804bbd1 <+17>: ffree %st(3) 0x0804bbd3 <+19>: ffree %st(4) 0x0804bbd5 <+21>: ffree %st(5) 0x0804bbd7 <+23>: jmp 0x804bbdc <c1jL_entry> 0x0804bbd9 <+25>: lea 0x0(%esi),%esi Optimized C--: block_c1jG_entry() { [const 484;, const 32;] } c1jG: // emitReturn: Sequel: Assign _s1ev::F64 = F64[Sp + 4]; ; F64[Sp + 4] = _s1ev::F64; Sp = Sp + 4; jump block_c1jL_entry (); } Regardless of whether or not it's right, this is pretty dumb code, so we should fix the codegen not to do that. I'm not sure what this code is /supposed/ to be doing, or what generated it. This bit might be correct and the FFI call leading up to it may be wrong. I have a prodigous amout of difficulty trying to figure out if what's on the stack is right or not: maybe we could have a chat about this. Cheers, Edward Some possible useful raw data: Raw C--: block_c1jh_entry() { [const 484;, const 32;] } c1jh: // emitReturn: Sequel: Assign _s1ev::F64 = F64[Sp + 4]; _s1ev::F64 = _s1ev::F64; F64[Sp + 4] = _s1ev::F64; Sp = Sp + 4; jump block_c1jm_entry (); } after manifest SP {offset c1j0: _s1el::I32 = I32[Sp + 4]; // CmmAssign I32[Sp - 4] = _s1el::I32; // CmmStore _s1ei::I32 = I32[Sp + 0]; // CmmAssign if (Sp - 12 < SpLim) goto u1Am; else goto u1Ao; // CmmCondBranch u1Am: Sp = Sp + 12; // CmmAssign goto c1j7; // CmmBranch c1j7: // outOfLine here R1 = call_fn_blob_rk2_closure; // CmmAssign Sp = Sp - 12; // CmmAssign call (stg_gc_fun)(...) returns to Nothing (12) (0) with update frame 12; // CmmCall u1Ao: Sp = Sp + 12; // CmmAssign goto c1j3; // CmmBranch c1j3: // outOfLine should follow: if (_s1ei::I32 & 3 != 0) goto c1j9; else goto c1j8; // CmmCondBranch c1j9: // emitReturn: Sequel: Assign _s1fO::I32 = _s1ei::I32; // CmmAssign I32[Sp - 20] = _s1fO::I32; // CmmStore goto u1Ai; // CmmBranch u1Ai: Sp = Sp - 20; // CmmAssign goto c1jc; // CmmBranch c1j8: // emitCall: Sequel: Assign R1 = _s1ei::I32; // CmmAssign I32[Sp - 20] = c1iw; // CmmStore Sp = Sp - 20; // CmmAssign call (I32[_s1ei::I32])(...) returns to Just c1iw (4) (4) with update frame 4; // CmmCall c1iw: _s1fO::I32 = R1; // CmmAssign I32[Sp + 0] = _s1fO::I32; // CmmStore Sp = Sp + 4; // CmmAssign goto u1Af; // CmmBranch u1Af: Sp = Sp - 4; // CmmAssign goto c1jc; // CmmBranch c1jc: // outOfLine should follow: _s1fO::I32 = I32[Sp + 0]; // CmmAssign _s1ep::I32 = I32[_s1fO::I32 + 3]; // CmmAssign I32[Sp + 0] = _s1ep::I32; // CmmStore _s1el::I32 = I32[Sp + 4]; // CmmAssign if (_s1el::I32 & 3 != 0) goto c1jf; else goto c1je; // CmmCondBranch c1jf: // emitReturn: Sequel: Assign _s1el::I32 = I32[Sp + 4]; // CmmAssign _s1fP::I32 = _s1el::I32; // CmmAssign I32[Sp - 4] = _s1fP::I32; // CmmStore goto u1Aj; // CmmBranch u1Aj: Sp = Sp - 4; // CmmAssign goto c1jj; // CmmBranch c1je: // emitCall: Sequel: Assign _s1el::I32 = I32[Sp + 4]; // CmmAssign R1 = _s1el::I32; // CmmAssign I32[Sp - 4] = c1iG; // CmmStore _s1el::I32 = I32[Sp + 4]; // CmmAssign Sp = Sp - 4; // CmmAssign call (I32[_s1el::I32])(...) returns to Just c1iG (4) (4) with update frame 4; // CmmCall c1iG: _s1fP::I32 = R1; // CmmAssign I32[Sp + 0] = _s1fP::I32; // CmmStore Sp = Sp + 4; // CmmAssign goto u1Ag; // CmmBranch u1Ag: Sp = Sp - 4; // CmmAssign goto c1jj; // CmmBranch c1jj: // outOfLine should follow: _s1fP::I32 = I32[Sp + 0]; // CmmAssign _s1eq::F64 = F64[_s1fP::I32 + 3]; // CmmAssign I32[Sp + 0] = c1jh; // CmmStore _s1ep::I32 = I32[Sp + 4]; // CmmAssign foreign call "ccall" arg hints: [PtrHint,] result hints: [] call_fn_blob(...) returns to c1jh args: ([_s1ep::I32, _s1eq::F64]) ress: ([_s1ev::F64]) with update frame 4; // CmmForeignCall c1jh: // emitReturn: Sequel: Assign _s1ev::F64 = F64[Sp + 4]; // CmmAssign _s1ev::F64 = _s1ev::F64; // CmmAssign F64[Sp + 4] = _s1ev::F64; // CmmStore Sp = Sp + 4; // CmmAssign goto u1Ak; // CmmBranch u1Ak: goto c1jm; // CmmBranch c1jm: Hp = Hp + 12; // CmmAssign if (Hp > HpLim) goto c1jn; else goto c1jl; // CmmCondBranch c1jn: HpAlloc = 12; // CmmAssign // outOfLine here _s1ev::F64 = F64[Sp + 0]; // CmmAssign F64[Sp - 4] = _s1ev::F64; // CmmStore I32[Sp + 4] = c1iT; // CmmStore Sp = Sp - 4; // CmmAssign call stg_gc_d1(...) returns to Just c1iT (12) (12) with update frame 4; // CmmCall c1iT: _s1ev::F64 = F64[Sp + 0]; // CmmAssign F64[Sp + 4] = _s1ev::F64; // CmmStore Sp = Sp + 12; // CmmAssign goto u1Ah; // CmmBranch u1Ah: Sp = Sp - 8; // CmmAssign goto c1jm; // CmmBranch c1jl: // outOfLine should follow: // allocDynClosure I32[Hp - 8] = GHC.Types.D#_con_info; // CmmStore _s1ev::F64 = F64[Sp + 0]; // CmmAssign F64[Hp - 4] = _s1ev::F64; // CmmStore _c1iZ::I32 = Hp - 8; // CmmAssign // emitReturn: Sequel: Return R1 = _c1iZ::I32 + 1; // CmmAssign Sp = Sp + 16; // CmmAssign call (I32[Sp + 0])(...) returns to Nothing (4) (0) with update frame 4; // CmmCall } post dead assignment elimination {offset c1j0: _s1el::I32 = I32[(old + 8)]; // CmmAssign I32[(slot<_s1el::I32> + 4)] = _s1el::I32; // CmmStore _s1ei::I32 = I32[(old + 12)]; // CmmAssign if (Sp - <highSp> < SpLim) goto c1j7; else goto c1j3; // CmmCondBranch c1j7: // outOfLine here R1 = call_fn_blob_rk2_closure; // CmmAssign call (stg_gc_fun)(...) returns to Nothing (12) (0) with update frame 12; // CmmCall c1j3: // outOfLine should follow: if (_s1ei::I32 & 3 != 0) goto c1j9; else goto c1j8; // CmmCondBranch c1j9: // emitReturn: Sequel: Assign _s1fO::I32 = _s1ei::I32; // CmmAssign I32[(slot<_s1fO::I32> + 4)] = _s1fO::I32; // CmmStore goto u1Ai; // CmmBranch u1Ai: goto c1jc; // CmmBranch c1j8: // emitCall: Sequel: Assign R1 = _s1ei::I32; // CmmAssign I32[(young<c1iw> + 4)] = c1iw; // CmmStore call (I32[_s1ei::I32])(...) returns to Just c1iw (4) (4) with update frame 4; // CmmCall c1iw: _s1fO::I32 = R1; // CmmAssign I32[(slot<_s1fO::I32> + 4)] = _s1fO::I32; // CmmStore goto u1Af; // CmmBranch u1Af: goto c1jc; // CmmBranch c1jc: // outOfLine should follow: _s1fO::I32 = I32[(slot<_s1fO::I32> + 4)]; // CmmAssign _s1ep::I32 = I32[_s1fO::I32 + 3]; // CmmAssign I32[(slot<_s1ep::I32> + 4)] = _s1ep::I32; // CmmStore _s1el::I32 = I32[(slot<_s1el::I32> + 4)]; // CmmAssign if (_s1el::I32 & 3 != 0) goto c1jf; else goto c1je; // CmmCondBranch c1jf: // emitReturn: Sequel: Assign _s1el::I32 = I32[(slot<_s1el::I32> + 4)]; // CmmAssign _s1fP::I32 = _s1el::I32; // CmmAssign I32[(slot<_s1fP::I32> + 4)] = _s1fP::I32; // CmmStore goto u1Aj; // CmmBranch u1Aj: goto c1jj; // CmmBranch c1je: // emitCall: Sequel: Assign _s1el::I32 = I32[(slot<_s1el::I32> + 4)]; // CmmAssign R1 = _s1el::I32; // CmmAssign I32[(young<c1iG> + 4)] = c1iG; // CmmStore _s1el::I32 = I32[(slot<_s1el::I32> + 4)]; // CmmAssign call (I32[_s1el::I32])(...) returns to Just c1iG (4) (4) with update frame 4; // CmmCall c1iG: _s1fP::I32 = R1; // CmmAssign I32[(slot<_s1fP::I32> + 4)] = _s1fP::I32; // CmmStore goto u1Ag; // CmmBranch u1Ag: goto c1jj; // CmmBranch c1jj: // outOfLine should follow: _s1fP::I32 = I32[(slot<_s1fP::I32> + 4)]; // CmmAssign _s1eq::F64 = F64[_s1fP::I32 + 3]; // CmmAssign I32[(young<c1jh> + 4)] = c1jh; // CmmStore _s1ep::I32 = I32[(slot<_s1ep::I32> + 4)]; // CmmAssign foreign call "ccall" arg hints: [PtrHint,] result hints: [] call_fn_blob(...) returns to c1jh args: ([_s1ep::I32, _s1eq::F64]) ress: ([_s1ev::F64]) with update frame 4; // CmmForeignCall c1jh: // emitReturn: Sequel: Assign _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign _s1ev::F64 = _s1ev::F64; // CmmAssign F64[(slot<_s1ev::F64> + 8)] = _s1ev::F64; // CmmStore goto u1Ak; // CmmBranch u1Ak: goto c1jm; // CmmBranch c1jm: Hp = Hp + 12; // CmmAssign if (Hp > HpLim) goto c1jn; else goto c1jl; // CmmCondBranch c1jn: HpAlloc = 12; // CmmAssign // outOfLine here _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign F64[(young<c1iT> + 12)] = _s1ev::F64; // CmmStore I32[(young<c1iT> + 4)] = c1iT; // CmmStore call stg_gc_d1(...) returns to Just c1iT (12) (12) with update frame 4; // CmmCall c1iT: _s1ev::F64 = F64[(young<c1iT> + 12)]; // CmmAssign F64[(slot<_s1ev::F64> + 8)] = _s1ev::F64; // CmmStore goto u1Ah; // CmmBranch u1Ah: goto c1jm; // CmmBranch c1jl: // outOfLine should follow: // allocDynClosure I32[Hp - 8] = GHC.Types.D#_con_info; // CmmStore _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign F64[Hp - 4] = _s1ev::F64; // CmmStore _c1iZ::I32 = Hp - 8; // CmmAssign // emitReturn: Sequel: Return R1 = _c1iZ::I32 + 1; // CmmAssign call (I32[(old + 4)])(...) returns to Nothing (4) (0) with update frame 4; // CmmCall }
Current theory: c1jj: _s1ep::I32 = I32[(slot<_s1ep::I32> + 4)]; // CmmAssign _s1fP::I32 = I32[(slot<_s1fP::I32> + 4)]; // CmmAssign // outOfLine should follow: _s1eq::F64 = F64[_s1fP::I32 + 3]; // CmmAssign I32[(young<c1jh> + 4)] = c1jh; // CmmStore foreign call "ccall" arg hints: [PtrHint,] result hints: [] call_fn_blob(...) returns to c1jh args: ([_s1ep::I32, _s1eq::F64]) ress: ([_s1ev::F64]) with update frame 4; // CmmForeignCall c1jh: _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign // emitReturn: Sequel: Assign _s1ev::F64 = _s1ev::F64; // CmmAssign F64[(slot<_s1ev::F64> + 8)] = _s1ev::F64; // CmmStore goto u1Ak; // CmmBranch Note the line immediately after c1jh, where we reload the ostensibly spilled _s1ev back into a register. Except that it was never spilled there in the first place, and we just clobbered the real value. Oops. Is this interpretation correct? Edward
On 01/02/2011 00:01, Edward Z. Yang wrote:
Current theory:
c1jj: _s1ep::I32 = I32[(slot<_s1ep::I32> + 4)]; // CmmAssign _s1fP::I32 = I32[(slot<_s1fP::I32> + 4)]; // CmmAssign // outOfLine should follow: _s1eq::F64 = F64[_s1fP::I32 + 3]; // CmmAssign I32[(young<c1jh> + 4)] = c1jh; // CmmStore foreign call "ccall" arg hints: [PtrHint,] result hints: [] call_fn_blob(...) returns to c1jh args: ([_s1ep::I32, _s1eq::F64]) ress: ([_s1ev::F64]) with update frame 4; // CmmForeignCall c1jh: _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign // emitReturn: Sequel: Assign _s1ev::F64 = _s1ev::F64; // CmmAssign F64[(slot<_s1ev::F64> + 8)] = _s1ev::F64; // CmmStore goto u1Ak; // CmmBranch
Note the line immediately after c1jh, where we reload the ostensibly spilled _s1ev back into a register. Except that it was never spilled there in the first place, and we just clobbered the real value. Oops.
Is this interpretation correct?
It sounds plausible, but I really have no idea. The code generator does not have to generate spill/reloads around foreign calls, the register allocator will do that. Cheers, Simon
More Hoopling later, I see this segment in the rewrite function: middle m@(CmmUnsafeForeignCall _ fs _) live = return $ case map spill (filter (flip elemRegSet (on_stack live)) fs) ++ map reload (uniqSetToList (kill fs (in_regs live))) of [] -> Nothing reloads -> Just $ mkMiddles (m : reloads) So, if I understand this code correctly, it unilaterally reloads /anything/ in the registers according to the analysis at that point. Well, I could see that resulting in the behavior below. It's not so clear to me what the correct rewrite is; according to Marlow's comment on IRC, we ought not to be spilling/reloading foreign calls yet, so maybe the whole bit should get excised? Otherwise, it seems to me that transfer function needs to accomodate unsafe foreign functions. Cheers, Edward Excerpts from Simon Marlow's message of Tue Feb 01 03:44:41 -0500 2011:
On 01/02/2011 00:01, Edward Z. Yang wrote:
Current theory:
c1jj: _s1ep::I32 = I32[(slot<_s1ep::I32> + 4)]; // CmmAssign _s1fP::I32 = I32[(slot<_s1fP::I32> + 4)]; // CmmAssign // outOfLine should follow: _s1eq::F64 = F64[_s1fP::I32 + 3]; // CmmAssign I32[(young<c1jh> + 4)] = c1jh; // CmmStore foreign call "ccall" arg hints: [PtrHint,] result hints: [] call_fn_blob(...) returns to c1jh args: ([_s1ep::I32, _s1eq::F64]) ress: ([_s1ev::F64]) with update frame 4; // CmmForeignCall c1jh: _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign // emitReturn: Sequel: Assign _s1ev::F64 = _s1ev::F64; // CmmAssign F64[(slot<_s1ev::F64> + 8)] = _s1ev::F64; // CmmStore goto u1Ak; // CmmBranch
Note the line immediately after c1jh, where we reload the ostensibly spilled _s1ev back into a register. Except that it was never spilled there in the first place, and we just clobbered the real value. Oops.
Is this interpretation correct?
It sounds plausible, but I really have no idea. The code generator does not have to generate spill/reloads around foreign calls, the register allocator will do that.
Cheers, Simon
On 02/02/2011 00:29, Edward Z. Yang wrote:
More Hoopling later, I see this segment in the rewrite function:
middle m@(CmmUnsafeForeignCall _ fs _) live = return $ case map spill (filter (flip elemRegSet (on_stack live)) fs) ++ map reload (uniqSetToList (kill fs (in_regs live))) of [] -> Nothing reloads -> Just $ mkMiddles (m : reloads)
So, if I understand this code correctly, it unilaterally reloads /anything/ in the registers according to the analysis at that point.
Well, I could see that resulting in the behavior below.
It's not so clear to me what the correct rewrite is; according to Marlow's comment on IRC, we ought not to be spilling/reloading foreign calls yet, so maybe the whole bit should get excised? Otherwise, it seems to me that transfer function needs to accomodate unsafe foreign functions.
Right, there's no need to spill/reload anything around an *unsafe* foreign call in the Cmm code generator. The NCG's register allocator will do any necessary spilling/reloading around foreign calls. Cheers, Simon
Cheers, Edward
Excerpts from Simon Marlow's message of Tue Feb 01 03:44:41 -0500 2011:
On 01/02/2011 00:01, Edward Z. Yang wrote:
Current theory:
c1jj: _s1ep::I32 = I32[(slot<_s1ep::I32> + 4)]; // CmmAssign _s1fP::I32 = I32[(slot<_s1fP::I32> + 4)]; // CmmAssign // outOfLine should follow: _s1eq::F64 = F64[_s1fP::I32 + 3]; // CmmAssign I32[(young<c1jh> + 4)] = c1jh; // CmmStore foreign call "ccall" arg hints: [PtrHint,] result hints: [] call_fn_blob(...) returns to c1jh args: ([_s1ep::I32, _s1eq::F64]) ress: ([_s1ev::F64]) with update frame 4; // CmmForeignCall c1jh: _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign // emitReturn: Sequel: Assign _s1ev::F64 = _s1ev::F64; // CmmAssign F64[(slot<_s1ev::F64> + 8)] = _s1ev::F64; // CmmStore goto u1Ak; // CmmBranch
Note the line immediately after c1jh, where we reload the ostensibly spilled _s1ev back into a register. Except that it was never spilled there in the first place, and we just clobbered the real value. Oops.
Is this interpretation correct?
It sounds plausible, but I really have no idea. The code generator does not have to generate spill/reloads around foreign calls, the register allocator will do that.
Cheers, Simon
Simon Peyton Jones, I have a question about optimization fuel and GHC panics. When I vary the fuel using -dopt-fuel, I get the following varying behavior: ... -dopt-fuel=144 = normal segfault (late in the program) -dopt-fuel=143 = segfaults ~immediately -dopt-fuel=142 = normal segfault -dopt-fuel=141 = fails an assert in file compiler/cmm/CmmBuildInfoTables.hs, line 128 -dopt-fuel=140 = ditto -dopt-fuel=139 = resulting executable prints 'start' and then doesn't do anything ... My impression was that these optimizations should not affect program behavior, in which case the first thing I should figure out is why -dopt-fuel results in the programming terminating after it prints 'start'. However, I'm not sure if this is a red herring. Am I on the right track? Cheers, Edward Quoting Simon Marlow <marlowsd@gmail.com>:
On 02/02/2011 00:29, Edward Z. Yang wrote:
More Hoopling later, I see this segment in the rewrite function:
middle m@(CmmUnsafeForeignCall _ fs _) live = return $ case map spill (filter (flip elemRegSet (on_stack live)) fs) ++ map reload (uniqSetToList (kill fs (in_regs live))) of [] -> Nothing reloads -> Just $ mkMiddles (m : reloads)
So, if I understand this code correctly, it unilaterally reloads /anything/ in the registers according to the analysis at that point.
Well, I could see that resulting in the behavior below.
It's not so clear to me what the correct rewrite is; according to Marlow's comment on IRC, we ought not to be spilling/reloading foreign calls yet, so maybe the whole bit should get excised? Otherwise, it seems to me that transfer function needs to accomodate unsafe foreign functions.
Right, there's no need to spill/reload anything around an *unsafe* foreign call in the Cmm code generator. The NCG's register allocator will do any necessary spilling/reloading around foreign calls.
Cheers, Simon
Cheers, Edward
Excerpts from Simon Marlow's message of Tue Feb 01 03:44:41 -0500 2011:
On 01/02/2011 00:01, Edward Z. Yang wrote:
Current theory:
c1jj: _s1ep::I32 = I32[(slot<_s1ep::I32> + 4)]; // CmmAssign _s1fP::I32 = I32[(slot<_s1fP::I32> + 4)]; // CmmAssign // outOfLine should follow: _s1eq::F64 = F64[_s1fP::I32 + 3]; // CmmAssign I32[(young<c1jh> + 4)] = c1jh; // CmmStore foreign call "ccall" arg hints: [PtrHint,] result hints: [] call_fn_blob(...) returns to c1jh args: ([_s1ep::I32,
_s1eq::F64]) ress: ([_s1ev::F64]) with update frame 4; // CmmForeignCall c1jh: _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign // emitReturn: Sequel: Assign _s1ev::F64 = _s1ev::F64; // CmmAssign F64[(slot<_s1ev::F64> + 8)] = _s1ev::F64; // CmmStore goto u1Ak; // CmmBranch
Note the line immediately after c1jh, where we reload the ostensibly spilled _s1ev back into a register. Except that it was never spilled there in the first place, and we just clobbered the real value. Oops.
Is this interpretation correct?
It sounds plausible, but I really have no idea. The code generator does not have to generate spill/reloads around foreign calls, the register allocator will do that.
Cheers, Simon
Here is an example of a very simple program getting strange results on low fuel: main = do putStrLn "a" f putStrLn "b" f = putStrLn "c" ezyang@javelin:~/Dev/ghc-build-nco-default/testsuite/tests/ghc-regress/simple$ '/home/ezyang/Dev/ghc-build-nco-default/inplace/bin/ghc-stage2' -fforce-recomp -dno-debug-output -dcore-lint -dcmm-lint -no-user-package-conf -rtsopts -dstub-dead-values -dopt-fuel=0 -o test test.hs && ./test [1 of 1] Compiling Main ( test.hs, test.o ) Linking test ... a ezyang@javelin:~/Dev/ghc-build-nco-default/testsuite/tests/ghc-regress/simple$ '/home/ezyang/Dev/ghc-build-nco-default/inplace/bin/ghc-stage2' -fforce-recomp -dno-debug-output -dcore-lint -dcmm-lint -no-user-package-conf -rtsopts -dstub-dead-values -dopt-fuel=1 -o test test.hs && ./test [1 of 1] Compiling Main ( test.hs, test.o ) ghc-stage2: panic! (the 'impossible' happened) (GHC version 7.1.20110128 for i386-unknown-linux): ASSERT failed! file compiler/cmm/CmmBuildInfoTables.hs, line 128 Please report this as a GHC bug: http://www.haskell.org/ghc/reportabug ezyang@javelin:~/Dev/ghc-build-nco-default/testsuite/tests/ghc-regress/simple$ '/home/ezyang/Dev/ghc-build-nco-default/inplace/bin/ghc-stage2' -fforce-recomp -dno-debug-output -dcore-lint -dcmm-lint -no-user-package-conf -rtsopts -dstub-dead-values -dopt-fuel=2 -o test test.hs && ./test [1 of 1] Compiling Main ( test.hs, test.o ) ghc-stage2: panic! (the 'impossible' happened) (GHC version 7.1.20110128 for i386-unknown-linux): ASSERT failed! file compiler/cmm/CmmBuildInfoTables.hs, line 128 Please report this as a GHC bug: http://www.haskell.org/ghc/reportabug ezyang@javelin:~/Dev/ghc-build-nco-default/testsuite/tests/ghc-regress/simple$ '/home/ezyang/Dev/ghc-build-nco-default/inplace/bin/ghc-stage2' -fforce-recomp -dno-debug-output -dcore-lint -dcmm-lint -no-user-package-conf -rtsopts -dstub-dead-values -dopt-fuel=3 -o test test.hs && ./test [1 of 1] Compiling Main ( test.hs, test.o ) ghc-stage2: panic! (the 'impossible' happened) (GHC version 7.1.20110128 for i386-unknown-linux): ASSERT failed! file compiler/cmm/CmmBuildInfoTables.hs, line 128 Please report this as a GHC bug: http://www.haskell.org/ghc/reportabug ezyang@javelin:~/Dev/ghc-build-nco-default/testsuite/tests/ghc-regress/simple$ '/home/ezyang/Dev/ghc-build-nco-default/inplace/bin/ghc-stage2' -fforce-recomp -dno-debug-output -dcore-lint -dcmm-lint -no-user-package-conf -rtsopts -dstub-dead-values -dopt-fuel=4 -o test test.hs && ./test [1 of 1] Compiling Main ( test.hs, test.o ) Linking test ... a c Segmentation fault (core dumped) ezyang@javelin:~/Dev/ghc-build-nco-default/testsuite/tests/ghc-regress/simple$ '/home/ezyang/Dev/ghc-build-nco-default/inplace/bin/ghc-stage2' -fforce-recomp -dno-debug-output -dcore-lint -dcmm-lint -no-user-package-conf -rtsopts -dstub-dead-values -dopt-fuel=5 -o test test.hs && ./test [1 of 1] Compiling Main ( test.hs, test.o ) Linking test ... a c b ezyang@javelin:~/Dev/ghc-build-nco-default/testsuite/tests/ghc-regress/simple$ '/home/ezyang/Dev/ghc-build-nco-default/inplace/bin/ghc-stage2' -fforce-recomp -dno-debug-output -dcore-lint -dcmm-lint -no-user-package-conf -rtsopts -dstub-dead-values -dopt-fuel=6 -o test test.hs && ./test [1 of 1] Compiling Main ( test.hs, test.o ) Linking test ... a Segmentation fault (core dumped) ezyang@javelin:~/Dev/ghc-build-nco-default/testsuite/tests/ghc-regress/simple$ '/home/ezyang/Dev/ghc-build-nco-default/inplace/bin/ghc-stage2' -fforce-recomp -dno-debug-output -dcore-lint -dcmm-lint -no-user-package-conf -rtsopts -dstub-dead-values -dopt-fuel=7 -o test test.hs && ./test [1 of 1] Compiling Main ( test.hs, test.o ) Linking test ... a Segmentation fault (core dumped) ezyang@javelin:~/Dev/ghc-build-nco-default/testsuite/tests/ghc-regress/simple$ '/home/ezyang/Dev/ghc-build-nco-default/inplace/bin/ghc-stage2' -fforce-recomp -dno-debug-output -dcore-lint -dcmm-lint -no-user-package-conf -rtsopts -dstub-dead-values -dopt-fuel=8 -o test test.hs && ./test [1 of 1] Compiling Main ( test.hs, test.o ) Linking test ... a c b Quoting ezyang <ezyang@MIT.EDU>:
Simon Peyton Jones, I have a question about optimization fuel and GHC panics. When I vary the fuel using -dopt-fuel, I get the following varying behavior:
... -dopt-fuel=144 = normal segfault (late in the program) -dopt-fuel=143 = segfaults ~immediately -dopt-fuel=142 = normal segfault -dopt-fuel=141 = fails an assert in file compiler/cmm/CmmBuildInfoTables.hs, line 128 -dopt-fuel=140 = ditto -dopt-fuel=139 = resulting executable prints 'start' and then doesn't do anything ...
My impression was that these optimizations should not affect program behavior, in which case the first thing I should figure out is why -dopt-fuel results in the programming terminating after it prints 'start'. However, I'm not sure if this is a red herring. Am I on the right track?
Cheers, Edward
Quoting Simon Marlow <marlowsd@gmail.com>:
On 02/02/2011 00:29, Edward Z. Yang wrote:
More Hoopling later, I see this segment in the rewrite function:
middle m@(CmmUnsafeForeignCall _ fs _) live = return $ case map spill (filter (flip elemRegSet (on_stack live)) fs) ++ map reload (uniqSetToList (kill fs (in_regs live))) of [] -> Nothing reloads -> Just $ mkMiddles (m : reloads)
So, if I understand this code correctly, it unilaterally reloads /anything/ in the registers according to the analysis at that point.
Well, I could see that resulting in the behavior below.
It's not so clear to me what the correct rewrite is; according to Marlow's comment on IRC, we ought not to be spilling/reloading foreign calls yet, so maybe the whole bit should get excised? Otherwise, it seems to me that transfer function needs to accomodate unsafe foreign functions.
Right, there's no need to spill/reload anything around an *unsafe* foreign call in the Cmm code generator. The NCG's register allocator will do any necessary spilling/reloading around foreign calls.
Cheers, Simon
Cheers, Edward
Excerpts from Simon Marlow's message of Tue Feb 01 03:44:41 -0500 2011:
On 01/02/2011 00:01, Edward Z. Yang wrote:
Current theory:
c1jj: _s1ep::I32 = I32[(slot<_s1ep::I32> + 4)]; // CmmAssign _s1fP::I32 = I32[(slot<_s1fP::I32> + 4)]; // CmmAssign // outOfLine should follow: _s1eq::F64 = F64[_s1fP::I32 + 3]; // CmmAssign I32[(young<c1jh> + 4)] = c1jh; // CmmStore foreign call "ccall" arg hints: [PtrHint,] result hints: [] call_fn_blob(...) returns to c1jh args: ([_s1ep::I32,
_s1eq::F64]) ress: ([_s1ev::F64]) with update frame 4; // CmmForeignCall c1jh: _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign // emitReturn: Sequel: Assign _s1ev::F64 = _s1ev::F64; // CmmAssign F64[(slot<_s1ev::F64> + 8)] = _s1ev::F64; // CmmStore goto u1Ak; // CmmBranch
Note the line immediately after c1jh, where we reload the ostensibly spilled _s1ev back into a register. Except that it was never spilled there in the first place, and we just clobbered the real value. Oops.
Is this interpretation correct?
It sounds plausible, but I really have no idea. The code generator does not have to generate spill/reloads around foreign calls, the register allocator will do that.
Cheers, Simon
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
Correct. The Cmm optimiser is supposed to make correctness preserving transformations. The idea of the "fuel" is that you can binary chop your way to a situation where Fuel = 0-143 Program works Fuel = 144 Program crashes Then look at the single transformation that introduces the crash. Well that's the intent anyway! Simon | -----Original Message----- | From: ezyang [mailto:ezyang@MIT.EDU] | Sent: 02 February 2011 23:12 | To: Simon Marlow; Simon Peyton-Jones | Cc: glasgow-haskell-users | Subject: Re: 4221 on new codegen | | Simon Peyton Jones, I have a question about optimization fuel and GHC panics. | When I vary the fuel using -dopt-fuel, I get the following varying behavior: | | ... | -dopt-fuel=144 = normal segfault (late in the program) | -dopt-fuel=143 = segfaults ~immediately | -dopt-fuel=142 = normal segfault | -dopt-fuel=141 = fails an assert in file compiler/cmm/CmmBuildInfoTables.hs, | line 128 | -dopt-fuel=140 = ditto | -dopt-fuel=139 = resulting executable prints 'start' and then doesn't do | anything | ... | | My impression was that these optimizations should not affect program | behavior, | in which case the first thing I should figure out is why -dopt-fuel results | in | the programming terminating after it prints 'start'. However, I'm not sure if | this is a red herring. Am I on the right track? | | Cheers, | Edward | | Quoting Simon Marlow <marlowsd@gmail.com>: | | > On 02/02/2011 00:29, Edward Z. Yang wrote: | >> More Hoopling later, I see this segment in the rewrite function: | >> | >> middle m@(CmmUnsafeForeignCall _ fs _) live = return $ | >> case map spill (filter (flip elemRegSet (on_stack live)) fs) ++ | >> map reload (uniqSetToList (kill fs (in_regs live))) of | >> [] -> Nothing | >> reloads -> Just $ mkMiddles (m : reloads) | >> | >> So, if I understand this code correctly, it unilaterally reloads | >> /anything/ in the registers according to the analysis at that point. | >> | >> Well, I could see that resulting in the behavior below. | >> | >> It's not so clear to me what the correct rewrite is; according to | >> Marlow's comment on IRC, we ought not to be spilling/reloading foreign | >> calls yet, so maybe the whole bit should get excised? Otherwise, it seems | >> to me that transfer function needs to accomodate unsafe foreign | >> functions. | > | > Right, there's no need to spill/reload anything around an *unsafe* | > foreign call in the Cmm code generator. The NCG's register allocator | > will do any necessary spilling/reloading around foreign calls. | > | > Cheers, | > Simon | > | > | > | >> Cheers, | >> Edward | >> | >> Excerpts from Simon Marlow's message of Tue Feb 01 03:44:41 -0500 2011: | >>> On 01/02/2011 00:01, Edward Z. Yang wrote: | >>>> Current theory: | >>>> | >>>> c1jj: | >>>> _s1ep::I32 = I32[(slot<_s1ep::I32> + 4)]; // CmmAssign | >>>> _s1fP::I32 = I32[(slot<_s1fP::I32> + 4)]; // CmmAssign | >>>> // outOfLine should follow: | >>>> _s1eq::F64 = F64[_s1fP::I32 + 3]; // CmmAssign | >>>> I32[(young<c1jh> + 4)] = c1jh; // CmmStore | >>>> foreign call "ccall" arg hints: [PtrHint,] result hints: | >>>> [] call_fn_blob(...) returns to c1jh args: ([_s1ep::I32, | >>>> | >>>> _s1eq::F64]) ress: | >>>> ([_s1ev::F64]) with update frame 4; // CmmForeignCall | >>>> c1jh: | >>>> _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign | >>>> // emitReturn: Sequel: Assign | >>>> _s1ev::F64 = _s1ev::F64; // CmmAssign | >>>> F64[(slot<_s1ev::F64> + 8)] = _s1ev::F64; // CmmStore | >>>> goto u1Ak; // CmmBranch | >>>> | >>>> Note the line immediately after c1jh, where we reload the ostensibly | >>>> spilled _s1ev back into a register. Except that it was never spilled | >>>> there in the first place, and we just clobbered the real value. Oops. | >>>> | >>>> Is this interpretation correct? | >>> | >>> It sounds plausible, but I really have no idea. The code generator does | >>> not have to generate spill/reloads around foreign calls, the register | >>> allocator will do that. | >>> | >>> Cheers, | >>> Simon | > | > | |
I wonder if the fuel is also being used by "essential" transformations, like the CPS pass? Cheers, Simon On 03/02/2011 09:01, Simon Peyton-Jones wrote:
Correct. The Cmm optimiser is supposed to make correctness preserving transformations. The idea of the "fuel" is that you can binary chop your way to a situation where
Fuel = 0-143 Program works Fuel = 144 Program crashes
Then look at the single transformation that introduces the crash.
Well that's the intent anyway!
Simon
| -----Original Message----- | From: ezyang [mailto:ezyang@MIT.EDU] | Sent: 02 February 2011 23:12 | To: Simon Marlow; Simon Peyton-Jones | Cc: glasgow-haskell-users | Subject: Re: 4221 on new codegen | | Simon Peyton Jones, I have a question about optimization fuel and GHC panics. | When I vary the fuel using -dopt-fuel, I get the following varying behavior: | | ... | -dopt-fuel=144 = normal segfault (late in the program) | -dopt-fuel=143 = segfaults ~immediately | -dopt-fuel=142 = normal segfault | -dopt-fuel=141 = fails an assert in file compiler/cmm/CmmBuildInfoTables.hs, | line 128 | -dopt-fuel=140 = ditto | -dopt-fuel=139 = resulting executable prints 'start' and then doesn't do | anything | ... | | My impression was that these optimizations should not affect program | behavior, | in which case the first thing I should figure out is why -dopt-fuel results | in | the programming terminating after it prints 'start'. However, I'm not sure if | this is a red herring. Am I on the right track? | | Cheers, | Edward | | Quoting Simon Marlow<marlowsd@gmail.com>: | |> On 02/02/2011 00:29, Edward Z. Yang wrote: |>> More Hoopling later, I see this segment in the rewrite function: |>> |>> middle m@(CmmUnsafeForeignCall _ fs _) live = return $ |>> case map spill (filter (flip elemRegSet (on_stack live)) fs) ++ |>> map reload (uniqSetToList (kill fs (in_regs live))) of |>> [] -> Nothing |>> reloads -> Just $ mkMiddles (m : reloads) |>> |>> So, if I understand this code correctly, it unilaterally reloads |>> /anything/ in the registers according to the analysis at that point. |>> |>> Well, I could see that resulting in the behavior below. |>> |>> It's not so clear to me what the correct rewrite is; according to |>> Marlow's comment on IRC, we ought not to be spilling/reloading foreign |>> calls yet, so maybe the whole bit should get excised? Otherwise, it seems |>> to me that transfer function needs to accomodate unsafe foreign |>> functions. |> |> Right, there's no need to spill/reload anything around an *unsafe* |> foreign call in the Cmm code generator. The NCG's register allocator |> will do any necessary spilling/reloading around foreign calls. |> |> Cheers, |> Simon |> |> |> |>> Cheers, |>> Edward |>> |>> Excerpts from Simon Marlow's message of Tue Feb 01 03:44:41 -0500 2011: |>>> On 01/02/2011 00:01, Edward Z. Yang wrote: |>>>> Current theory: |>>>> |>>>> c1jj: |>>>> _s1ep::I32 = I32[(slot<_s1ep::I32> + 4)]; // CmmAssign |>>>> _s1fP::I32 = I32[(slot<_s1fP::I32> + 4)]; // CmmAssign |>>>> // outOfLine should follow: |>>>> _s1eq::F64 = F64[_s1fP::I32 + 3]; // CmmAssign |>>>> I32[(young<c1jh> + 4)] = c1jh; // CmmStore |>>>> foreign call "ccall" arg hints: [PtrHint,] result hints: |>>>> [] call_fn_blob(...) returns to c1jh args: ([_s1ep::I32, |>>>> |>>>> _s1eq::F64]) ress: |>>>> ([_s1ev::F64]) with update frame 4; // CmmForeignCall |>>>> c1jh: |>>>> _s1ev::F64 = F64[(slot<_s1ev::F64> + 8)]; // CmmAssign |>>>> // emitReturn: Sequel: Assign |>>>> _s1ev::F64 = _s1ev::F64; // CmmAssign |>>>> F64[(slot<_s1ev::F64> + 8)] = _s1ev::F64; // CmmStore |>>>> goto u1Ak; // CmmBranch |>>>> |>>>> Note the line immediately after c1jh, where we reload the ostensibly |>>>> spilled _s1ev back into a register. Except that it was never spilled |>>>> there in the first place, and we just clobbered the real value. Oops. |>>>> |>>>> Is this interpretation correct? |>>> |>>> It sounds plausible, but I really have no idea. The code generator does |>>> not have to generate spill/reloads around foreign calls, the register |>>> allocator will do that. |>>> |>>> Cheers, |>>> Simon |> |> | |
Excerpts from Simon Marlow's message of Thu Feb 03 04:05:04 -0500 2011:
I wonder if the fuel is also being used by "essential" transformations, like the CPS pass?
That seems likely. Shall I try to figure out what the essential transformations are and give them infinite fuel? Edward
On 03/02/2011 10:07, ezyang wrote:
Excerpts from Simon Marlow's message of Thu Feb 03 04:05:04 -0500 2011:
I wonder if the fuel is also being used by "essential" transformations, like the CPS pass?
That seems likely. Shall I try to figure out what the essential transformations are and give them infinite fuel?
Sounds reasonable, yes. Cheers, Simon
Assuming that runFuelIO is the only mechanism by which fueled execution is performed, the only file using fuel is CmmCPS.hs. This file performs: 1. Proc point analysis 2. Proc point transformation 3. Spills and reloads 4. Late reloads 5. Dead assignment elimination 6. Stub dead slots (optional) 7. Stack slot analysis 8. Manifest the SP 9. Proc point analysis 10. Splitting proc points 11. CAF analysis 12. Safe call lowering Out of these, it only seems like (5) and (11) are optional. Any that I missed? It's not very promising (and I think I will go for another angle of attack soon); I guess fuel will be more useful when we start implementing optimizations on top of the new codegen. Amusingly enough the simple control flow optimisation is not subject to fuel constraints: tis all pure code. :-) Cheers, Edward P.S. I guess if I get stuck, I'll go and remove the dead code we talked about. That shouldn't be too hard, right? ;-)
On 03/02/2011 22:41, ezyang wrote:
Assuming that runFuelIO is the only mechanism by which fueled execution is performed, the only file using fuel is CmmCPS.hs. This file performs:
1. Proc point analysis 2. Proc point transformation 3. Spills and reloads 4. Late reloads 5. Dead assignment elimination 6. Stub dead slots (optional) 7. Stack slot analysis 8. Manifest the SP 9. Proc point analysis 10. Splitting proc points 11. CAF analysis 12. Safe call lowering
Out of these, it only seems like (5) and (11) are optional. Any that I missed? It's not very promising (and I think I will go for another angle of attack soon); I guess fuel will be more useful when we start implementing optimizations on top of the new codegen.
Amusingly enough the simple control flow optimisation is not subject to fuel constraints: tis all pure code. :-)
Cheers, Edward
P.S. I guess if I get stuck, I'll go and remove the dead code we talked about. That shouldn't be too hard, right? ;-)
Yes, I think starting work on the cleanup would be worthwhile at this stage. It'll be much easier to dig around in the code to find the bugs once all the cruft has gone. Cheers, Simon
Excerpts from Simon Marlow's message of Fri Feb 04 03:33:11 -0500 2011:
Yes, I think starting work on the cleanup would be worthwhile at this stage. It'll be much easier to dig around in the code to find the bugs once all the cruft has gone.
Well, I rolled up my sleeves intending to do the cleanup, and found most of it had already been done by the "merge in new codegen patch"! So I guess I don't have to go do that. Edward
participants (4)
-
Edward Z. Yang -
ezyang -
Simon Marlow -
Simon Peyton-Jones