#11792 closed bug (fixed)

Optimised unsafe FFI call can get wrong argument

Reported by: Szunti Owned by:
Priority: high Milestone: 8.0.2
Component: Compiler Version: 7.10.3
Keywords: Cc: hsyl20
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: Incorrect result at runtime Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s): Phab:D2263
Wiki Page:

Description

Attached a simple test case. It should print 7457, but the C function is called with 0 as the third argument.

If I compile with -O0 or omit the unsafe keyword in the FFI import it works as it should.

In gdb disassembly looks to me as edx (the place for third argument on 64-bit) is set to 7457, then the opaquify is inlined, but it doesn't preserve edx and then third_arg is called with the zeroed edx.


Specs


64-bit Archlinux with arch-haskell repo

gcc -v:

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-unknown-linux-gnu/5.3.0/lto-wrapper
Target: x86_64-unknown-linux-gnu
Configured with: /build/gcc-multilib/src/gcc-5-20160209/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --enable-libmpx --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --enable-multilib --disable-werror --enable-checking=release
Thread model: posix
gcc version 5.3.0 (GCC)

ghc compile output:

Glasgow Haskell Compiler, Version 7.10.3, stage 2 booted by GHC version 7.10.3
Using binary package database: /usr/lib/ghc-7.10.3/package.conf.d/package.cache
wired-in package ghc-prim mapped to ghc-prim-0.4.0.0-6cdc86811872333585fa98756aa7c51e
wired-in package integer-gmp mapped to integer-gmp-1.0.0.0-3c8c40657a9870f5c33be17496806d8d
wired-in package base mapped to base-4.8.2.0-0d6d1084fbc041e1cded9228e80e264d
wired-in package rts mapped to builtin_rts
wired-in package template-haskell mapped to template-haskell-2.10.0.0-3c4cb52230f347282af9b2817f013181
wired-in package ghc mapped to ghc-7.10.3-3a39f8f970ff545623196002970730d1
wired-in package dph-seq not found.
wired-in package dph-par not found.
Hsc static flags: 
wired-in package ghc-prim mapped to ghc-prim-0.4.0.0-6cdc86811872333585fa98756aa7c51e
wired-in package integer-gmp mapped to integer-gmp-1.0.0.0-3c8c40657a9870f5c33be17496806d8d
wired-in package base mapped to base-4.8.2.0-0d6d1084fbc041e1cded9228e80e264d
wired-in package rts mapped to builtin_rts
wired-in package template-haskell mapped to template-haskell-2.10.0.0-3c4cb52230f347282af9b2817f013181
wired-in package ghc mapped to ghc-7.10.3-3a39f8f970ff545623196002970730d1
wired-in package dph-seq not found.
wired-in package dph-par not found.
*** Chasing dependencies:
Chasing modules from: *Main.hs
Stable obj: []
Stable BCO: []
Ready for upsweep
  [NONREC
      ModSummary {
         ms_hs_date = 2016-04-05 14:24:20.801997492 UTC
         ms_mod = Main,
         ms_textual_imps = [import (implicit) Prelude, import Data.Word]
         ms_srcimps = []
      }]
*** Deleting temp files:
Deleting: 
compile: input file Main.hs
Created temporary directory: /tmp/ghc1541_0
*** Checking old interface for Main:
[1 of 1] Compiling Main             ( Main.hs, Main.o )
*** Parser:
*** Renamer/typechecker:
*** Desugar:
Result size of Desugar (after optimization)
  = {terms: 317, types: 387, coercions: 3}
*** Core Linted result of Desugar (after optimization):
*** Simplifier:
Result size of Simplifier iteration=1
  = {terms: 261, types: 290, coercions: 14}
*** Core Linted result of Simplifier:
Result size of Simplifier iteration=2
  = {terms: 216, types: 262, coercions: 18}
*** Core Linted result of Simplifier:
Result size of Simplifier = {terms: 216, types: 262, coercions: 18}
*** Core Linted result of Simplifier:
*** Specialise:
Result size of Specialise = {terms: 216, types: 262, coercions: 18}
*** Core Linted result of Specialise:
*** Float out(FOS {Lam = Just 0, Consts = True, OverSatApps = False}):
Result size of Float out(FOS {Lam = Just 0,
                              Consts = True,
                              OverSatApps = False})
  = {terms: 274, types: 305, coercions: 18}
*** Core Linted result of Float out(FOS {Lam = Just 0, Consts = True, OverSatApps = False}):
*** Simplifier:
Result size of Simplifier iteration=1
  = {terms: 407, types: 388, coercions: 70}
*** Core Linted result of Simplifier:
Result size of Simplifier iteration=2
  = {terms: 463, types: 375, coercions: 25}
*** Core Linted result of Simplifier:
Result size of Simplifier = {terms: 430, types: 362, coercions: 25}
*** Core Linted result of Simplifier:
*** Simplifier:
Result size of Simplifier iteration=1
  = {terms: 426, types: 363, coercions: 25}
*** Core Linted result of Simplifier:
Result size of Simplifier = {terms: 426, types: 363, coercions: 25}
*** Core Linted result of Simplifier:
*** Simplifier:
Result size of Simplifier iteration=1
  = {terms: 310, types: 291, coercions: 25}
*** Core Linted result of Simplifier:
Result size of Simplifier iteration=2
  = {terms: 248, types: 217, coercions: 25}
*** Core Linted result of Simplifier:
Result size of Simplifier iteration=3
  = {terms: 336, types: 242, coercions: 25}
*** Core Linted result of Simplifier:
Result size of Simplifier = {terms: 336, types: 242, coercions: 25}
*** Core Linted result of Simplifier:
*** Float inwards:
Result size of Float inwards
  = {terms: 336, types: 242, coercions: 25}
*** Core Linted result of Float inwards:
*** Called arity analysis:
Result size of Called arity analysis
  = {terms: 336, types: 242, coercions: 25}
*** Core Linted result of Called arity analysis:
*** Simplifier:
Result size of Simplifier = {terms: 336, types: 242, coercions: 25}
*** Core Linted result of Simplifier:
*** Demand analysis:
Result size of Demand analysis
  = {terms: 336, types: 242, coercions: 25}
*** Core Linted result of Demand analysis:
*** Worker Wrapper binds:
Result size of Worker Wrapper binds
  = {terms: 369, types: 283, coercions: 25}
*** Core Linted result of Worker Wrapper binds:
*** Simplifier:
Result size of Simplifier iteration=1
  = {terms: 354, types: 266, coercions: 25}
*** Core Linted result of Simplifier:
Result size of Simplifier = {terms: 354, types: 266, coercions: 25}
*** Core Linted result of Simplifier:
*** Float out(FOS {Lam = Just 0, Consts = True, OverSatApps = True}):
Result size of Float out(FOS {Lam = Just 0,
                              Consts = True,
                              OverSatApps = True})
  = {terms: 356, types: 267, coercions: 25}
*** Core Linted result of Float out(FOS {Lam = Just 0, Consts = True, OverSatApps = True}):
*** Common sub-expression:
Result size of Common sub-expression
  = {terms: 356, types: 267, coercions: 25}
*** Core Linted result of Common sub-expression:
*** Float inwards:
Result size of Float inwards
  = {terms: 356, types: 267, coercions: 25}
*** Core Linted result of Float inwards:
*** Simplifier:
Result size of Simplifier = {terms: 356, types: 267, coercions: 25}
*** Core Linted result of Simplifier:
*** Tidy Core:
Result size of Tidy Core = {terms: 356, types: 267, coercions: 25}
*** Core Linted result of Tidy Core:
writeBinIface: 18 Names
writeBinIface: 81 dict entries
*** CorePrep:
Result size of CorePrep = {terms: 654, types: 379, coercions: 25}
*** Core Linted result of CorePrep:
*** Stg2Stg:
*** CodeGen:
*** Assembler:
/usr/bin/gcc -fno-stack-protector -DTABLES_NEXT_TO_CODE -I. -x assembler -c /tmp/ghc1541_0/ghc_2.s -o Main.o
Upsweep completely successful.
*** Deleting temp files:
Deleting: /tmp/ghc1541_0/ghc_3.c /tmp/ghc1541_0/ghc_2.s /tmp/ghc1541_0/ghc_1.s
Warning: deleting non-existent /tmp/ghc1541_0/ghc_3.c
Warning: deleting non-existent /tmp/ghc1541_0/ghc_1.s
link: linkables are ...
LinkableM (2016-04-05 15:42:11.288210053 UTC) Main
   [DotO Main.o]
Linking Main ...
*** C Compiler:
/usr/bin/gcc -fno-stack-protector -DTABLES_NEXT_TO_CODE -c /tmp/ghc1541_0/ghc_4.c -o /tmp/ghc1541_0/ghc_5.o -I/usr/lib/ghc-7.10.3/include
*** C Compiler:
/usr/bin/gcc -fno-stack-protector -DTABLES_NEXT_TO_CODE -c /tmp/ghc1541_0/ghc_7.s -o /tmp/ghc1541_0/ghc_8.o -I/usr/lib/ghc-7.10.3/include
*** Linker:
/usr/bin/gcc -fno-stack-protector -DTABLES_NEXT_TO_CODE '-Wl,--hash-size=31' -Wl,--reduce-memory-overheads -Wl,--no-as-needed -o Main Main.o Test.o -L/usr/lib/ghc-7.10.3/base_HQfYBxpPvuw8OunzQu6JGM -L/usr/lib/ghc-7.10.3/integ_2aU3IZNMF9a7mQ0OzsZ0dS -L/usr/lib/ghc-7.10.3/ghcpr_8TmvWUcS1U1IKHT0levwg3 -L/usr/lib/ghc-7.10.3/rts /tmp/ghc1541_0/ghc_5.o /tmp/ghc1541_0/ghc_8.o -Wl,-u,ghczmprim_GHCziTypes_Izh_static_info -Wl,-u,ghczmprim_GHCziTypes_Czh_static_info -Wl,-u,ghczmprim_GHCziTypes_Fzh_static_info -Wl,-u,ghczmprim_GHCziTypes_Dzh_static_info -Wl,-u,base_GHCziPtr_Ptr_static_info -Wl,-u,ghczmprim_GHCziTypes_Wzh_static_info -Wl,-u,base_GHCziInt_I8zh_static_info -Wl,-u,base_GHCziInt_I16zh_static_info -Wl,-u,base_GHCziInt_I32zh_static_info -Wl,-u,base_GHCziInt_I64zh_static_info -Wl,-u,base_GHCziWord_W8zh_static_info -Wl,-u,base_GHCziWord_W16zh_static_info -Wl,-u,base_GHCziWord_W32zh_static_info -Wl,-u,base_GHCziWord_W64zh_static_info -Wl,-u,base_GHCziStable_StablePtr_static_info -Wl,-u,ghczmprim_GHCziTypes_Izh_con_info -Wl,-u,ghczmprim_GHCziTypes_Czh_con_info -Wl,-u,ghczmprim_GHCziTypes_Fzh_con_info -Wl,-u,ghczmprim_GHCziTypes_Dzh_con_info -Wl,-u,base_GHCziPtr_Ptr_con_info -Wl,-u,base_GHCziPtr_FunPtr_con_info -Wl,-u,base_GHCziStable_StablePtr_con_info -Wl,-u,ghczmprim_GHCziTypes_False_closure -Wl,-u,ghczmprim_GHCziTypes_True_closure -Wl,-u,base_GHCziPack_unpackCString_closure -Wl,-u,base_GHCziIOziException_stackOverflow_closure -Wl,-u,base_GHCziIOziException_heapOverflow_closure -Wl,-u,base_ControlziExceptionziBase_nonTermination_closure -Wl,-u,base_GHCziIOziException_blockedIndefinitelyOnMVar_closure -Wl,-u,base_GHCziIOziException_blockedIndefinitelyOnSTM_closure -Wl,-u,base_GHCziIOziException_allocationLimitExceeded_closure -Wl,-u,base_ControlziExceptionziBase_nestedAtomically_closure -Wl,-u,base_GHCziEventziThread_blockedOnBadFD_closure -Wl,-u,base_GHCziWeak_runFinalizzerBatch_closure -Wl,-u,base_GHCziTopHandler_flushStdHandles_closure -Wl,-u,base_GHCziTopHandler_runIO_closure -Wl,-u,base_GHCziTopHandler_runNonIO_closure -Wl,-u,base_GHCziConcziIO_ensureIOManagerIsRunning_closure -Wl,-u,base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure -Wl,-u,base_GHCziConcziSync_runSparks_closure -Wl,-u,base_GHCziConcziSignal_runHandlersPtr_closure -lHSbase-4.8.2.0-HQfYBxpPvuw8OunzQu6JGM -lHSinteger-gmp-1.0.0.0-2aU3IZNMF9a7mQ0OzsZ0dS -lHSghc-prim-0.4.0.0-8TmvWUcS1U1IKHT0levwg3 -lHSrts -lCffi -lgmp -lm -lrt -ldl
link: done
*** Deleting temp files:
Deleting: /tmp/ghc1541_0/ghc_10.rsp /tmp/ghc1541_0/ghc_9.rsp /tmp/ghc1541_0/ghc_8.o /tmp/ghc1541_0/ghc_7.s /tmp/ghc1541_0/ghc_6.rsp /tmp/ghc1541_0/ghc_5.o /tmp/ghc1541_0/ghc_4.c
*** Deleting temp dirs:
Deleting: /tmp/ghc1541_0

Attachments (1)

evil-bug.tar.gz (724 bytes) - added by Szunti 20 months ago.
Simple test case

Download all attachments as: .zip

Change History (17)

Changed 20 months ago by Szunti

Attachment: evil-bug.tar.gz added

Simple test case

comment:1 Changed 18 months ago by Szunti

Can I help more to get this fixed?

comment:2 Changed 18 months ago by hsyl20

For the record, the unsafe foreign call in Cmm after sink assignments:

(_s3N3::I64) = call "ccall" arg hints:  [, , ,]  result hints:  []
third_arg(0, 0, 7457, %MO_UU_Conv_W32_W64(%MO_UU_Conv_W64_W32(%MO_UU_Conv_W32_W64(%MO_UU_Conv_W64_W32(%MO_UU_Conv_W32_W64(%MO_UU_Conv_W64_W32(%MO_UU_Conv_W32_W64(%MO_UU_Conv_W64_W32(I64[Sp + 16] * _s3MA::I64)) / 255 + %MO_UU_Conv_W32_W64(%MO_UU_Conv_W64_W32(%MO_UU_Conv_W32_W64(%MO_UU_Conv_W64_W32(%MO_UU_Conv_W32_W64(%MO_UU_Conv_W64_W32(I64[Sp + 24] * _s3MA::I64)) / 255 + %MO_UU_Conv_W32_W64(%MO_UU_Conv_W64_W32(%MO_UU_Conv_W32_W64(%MO_UU_Conv_W64_W32(I64[R1 + 7] * _s3MA::I64)) / 255 << 8)))) << 8)))) << 8)) + 255)));

Indeed edx doesn't seem to be in the clobber list when the code for the fourth argument is generated and we obtain:

  405dc9:       ba 21 1d 00 00          mov    $0x1d21,%edx    ; 0x1d21 is the third arg
  405dce:       b9 ff 00 00 00          mov    $0xff,%ecx
  405dd3:       48 8b 5b 07             mov    0x7(%rbx),%rbx
  405dd7:       48 0f af d8             imul   %rax,%rbx
  405ddb:       48 89 c2                mov    %rax,%rdx       ; problem
  ...
  405e43:       e8 82 03 00 00          callq  4061ca <third_arg>

The following note in compiler/cmm/CmmNode.hs seems to be relevant:

{- Note [Register parameter passing]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On certain architectures, some registers are utilized for parameter
passing in the C calling convention.  For example, in x86-64 Linux
convention, rdi, rsi, rdx and rcx (as well as r8 and r9) may be used for
argument passing.  These are registers R3-R6, which our generated
code may also be using; as a result, it's necessary to save these
values before doing a foreign call.  This is done during initial
code generation in callerSaveVolatileRegs in StgCmmUtils.hs.  However,
one result of doing this is that the contents of these registers
may mysteriously change if referenced inside the arguments.  This
is dangerous, so you'll need to disable inlining much in the same
way is done in cmm/CmmOpt.hs currently.  We should fix this!
-}

comment:3 Changed 18 months ago by hsyl20

Cc: hsyl20 added
Priority: normalhigh

comment:4 Changed 18 months ago by osa1

Interestingly, these seem to fix this code:

  • Marking the FFI function "safe".
  • -XStrict
  • -fno-cmm-sink

May be helpful when debugging...

Last edited 18 months ago by osa1 (previous) (diff)

comment:5 Changed 18 months ago by osa1

I spent some time debugging this. Here are codes for FFI function arguments (before register allocation):

first arg:  [   xorl %edi,%edi]
second arg: [   xorl %esi,%esi]
third arg:  [   movl $7457,%edx]
fourth arg: [   movl $255,%vI_n4I8,     movq 7(%rbx),%vI_n4Ia,
                imulq %vI_s4pW,%vI_n4Ia,        movl %vI_n4Ia,%eax,     xorq %rdx,%rdx,
                divq %vI_n4I8,  movq %rax,%vI_n4Ib,     shlq $8,%vI_n4Ib,
                movl %vI_n4Ib,%vI_n4Ic,         movl $255,%vI_n4Id,
                movq 24(%rbp),%vI_n4If,         imulq %vI_s4pW,%vI_n4If,
                movl %vI_n4If,%eax,     xorq %rdx,%rdx,         divq %vI_n4Id,
                movq %rax,%vI_n4Ih,     addq %vI_n4Ic,%vI_n4Ih,
                movl %vI_n4Ih,%vI_n4Ii,         shlq $8,%vI_n4Ii,
                movl %vI_n4Ii,%vI_n4Ij,         movl $255,%vI_n4Ik,
                movq 16(%rbp),%vI_n4Im,         imulq %vI_s4pW,%vI_n4Im,
                movl %vI_n4Im,%eax,     xorq %rdx,%rdx,         divq %vI_n4Ik,
                movq %rax,%vI_n4Io,     addq %vI_n4Ij,%vI_n4Io,
                movl %vI_n4Io,%vI_n4Ip,         shlq $8,%vI_n4Ip,
                movl %vI_n4Ip,%vI_n4Iq,         leaq 255(%vI_n4Iq),%vI_n4Ir,
                movl %vI_n4Ir,%ecx]

These look correct to me. Since the code mostly uses virtual registers %edx isn't overridden at this point. So I think the problem may be in register allocation. I don't know how register allocation in GHC is working, but I looked at liveness information output, and it shows this for the `call third_arg` instruction:

call third_arg
    # born:    %r8 %r9 %r10 %r11 %r16 %r17 %r18 %r19 %r20 %r21 %r24 %r25 %r26 %r27 %r28 %r29 %r30 %r31 %r32 %r33 %r34 %r35 %r36 %r37 %r38 %r39
    # w_dying: %r2 %r3 %r4 %r5 %r8 %r9 %r10 %r11 %r16 %r17 %r18 %r19 %r20 %r21 %r24 %r25 %r26 %r27 %r28 %r29 %r30 %r31 %r32 %r33 %r34 %r35 %r36 %r37 %r38 %r39

I don't quite understand what's happening here (what are all those registers??), but I'd expect this instruction to "read" (or "use") %edx. So %edx would have to stay live between third arg and this call instruction, and so register allocation would have to generate some spill instructions when allocating registers for fourth argument if available registers are not enough. At least this is how my compiler is doing it.

I may have another look tomorrow.

comment:6 in reply to:  5 Changed 18 months ago by hsyl20

Replying to osa1:

third arg:  [   movl $7457,%edx]
fourth arg: [   movl $255,%vI_n4I8,     movq 7(%rbx),%vI_n4Ia,
                imulq %vI_s4pW,%vI_n4Ia,        movl %vI_n4Ia,%eax,     xorq %rdx,%rdx,

These look correct to me. Since the code mostly uses virtual registers %edx isn't overridden at this point.

It is overriden: %rdx is xored before each divq. With -ddump-asm-liveness, we can see that %edx (%r3) born in the movl is overwritten.

            	movl $7457,%edx
                    # born:    %r3
                    # w_dying: %r3
                     
            	movl $255,%vI_n413
                    # born:    %vI_n413
                     
            	movq 7(%rbx),%vI_n415
                    # born:    %vI_n415
                    # r_dying: %r1
                     
            	imulq %vI_s3Mw,%vI_n415
                     
            	movl %vI_n415,%eax
                    # born:    %r0
                    # r_dying: %vI_n415
                     
            	xorq %rdx,%rdx
                    # born:    %r3
                     
            	divq %vI_n413
                    # r_dying: %vI_n413
                    # w_dying: %r3

comment:7 Changed 18 months ago by hsyl20

I have made a first patch: https://phabricator.haskell.org/D2263

The result looks better (even if a little bit suboptimal):

        movq 8(%rbp),%rax
        xorl %ecx,%ecx
        xorl %edx,%edx
        movl $7457,%esi
        movl $255,%edi
        ; inlined code not using %esi/%rsi
        ; ...
        movq %rcx,%rdi ; suboptimal (could be xorq %rdi, %rdi)
        movq %rsi,%rcx ; suboptimal (could be movq %rsi, %rdx directly)
        movq %rbx,%rsi ; suboptimal (could be xorq %rsi, %rsi)
        movq %rcx,%rdx
        movq %rax,%rcx
        subq $8,%rsp
        xorl %eax,%eax
        call third_arg

The patch doesn't handle pushed args yet.

comment:8 Changed 18 months ago by hsyl20

Differential Rev(s): Phab:D2263

comment:9 Changed 18 months ago by simonmar

Hmm, I'd like to find a way to fix this without making codegen worse for every unsafe call. The problem appears to be that the code for the fourth argument uses divq which clobbers %rdx. I need to refresh my memory about how this is supposed to work...

comment:10 Changed 18 months ago by osa1

I added a comment to the Phab ticket. I think we should fix the code gen so that if a register is written at some point A and read at point B, it should be kept alive no matter what.

It'd be great if we could write some unit tests about that, but that needs a lot of infrastructure work...

comment:11 Changed 18 months ago by osa1

I was doing some reading on Cmm and found something that may be relevant in [this wiki page](https://ghc.haskell.org/trac/ghc/wiki/Commentary/Compiler/CmmType):

-

Global Registers in Cmm currently have a problem with inlining: because neither compiler/cmm/PprC.hs nor the NCG are able to keep Global Registers from clashing with C argument passing registers, Cmm expressions that contain Global Registers cannot be inlined into an argument position of a foreign call. For more thorough notes on inlining, see the comments in compiler/cmm/CmmOpt.hs.

-

The page is a bit out of date but this part sounded like the problem we're having here.

Last edited 18 months ago by osa1 (previous) (diff)

comment:12 Changed 18 months ago by osa1

I still don't know how the register allocator works, but I realized something interesting. CmmCall has a field cml_args_regs :: [GlobalReg] for the arguments that are passed to the function, but CmmUnsafeForeignCall doesn't have anything like that. Shouldn't it have the same thing to be used in register allocation?

comment:13 Changed 18 months ago by simonmar

This is unrelated to global registers, in fact. There is a related issue with global registers which is described here: https://phabricator.haskell.org/diffusion/GHC/browse/master/compiler/cmm/CmmNode.hs;e9e61f18a548b70693f4ccd245bc56335c94b498$205-218 But the issue here is different: see my comment on the diff.

comment:14 Changed 14 months ago by Ben Gamari <ben@…>

In b61b7c24/ghc:

CodeGen X86: fix unsafe foreign calls wrt inlining

Foreign calls (unsafe and safe) interact badly with inlining and
register passing ABIs (see #11792 and #12614):
the inlined code to compute a parameter of the call may overwrite a
register already set to pass a preceding parameter.

With this patch, we compute all parameters which are not simple
expressions before assigning them to fixed registers required by the
ABI.

Test Plan:
   - Add test (test both reg and stack parameters)
   - Validate

Reviewers: osa1, bgamari, austin, simonmar

Reviewed By: simonmar

Subscribers: thomie

Differential Revision: https://phabricator.haskell.org/D2263

GHC Trac Issues: #11792, #12614

comment:15 Changed 14 months ago by bgamari

Milestone: 8.0.2
Status: newmerge

comment:16 Changed 14 months ago by bgamari

Resolution: fixed
Status: mergeclosed
Note: See TracTickets for help on using tickets.