Quick Q: do all FFI (non-primop) calls involve State# and RealWorld?

Hi all, I tried compiling this file: {-# LANGUAGE NoImplicitPrelude #-}-- | Demonstrate various use of the FFI.module Foreign whereimport Foreign.Cforeign import ccall "math.h sin" sin :: CDouble -> CDoubleit :: CDoubleit = sin 1 And I’ve noticed that the annotated type given for this foreign op in Core, is (# State# RealWorld, CDouble #), whereas I would have expected e.g. CDouble. Meanwhile, the foreign op call is passed a RealWorld argument. Additionally, code that consumes the result of this foreign call expects a (# CDouble #) as a return value. So there are some assumptions I put in my interpreter to test this FFI call out: 1. Despite claiming to return the real world in a tuple, it actually should just return an unboxed tuple of the value. 2. It should ignore the RealWorld argument entirely. I assume, if I were to lift this function into returning IO, that I should indeed return the RealWorld argument given. So the lesson is: All FFI functions accept a RealWorld, and may return a 2-tuple of State# RealWorld *if* it’s impure, else it’ll return a 1-tuple of the value. Correct? Can someone confirm that my observations are right? Also, if so, is there somewhere I can read more about this? Cheers Chris

Hi,
I've also observed that in the final lowered STG the State# is always
passed to effectful primops and FFI calls, but the returning new State# is
removed from the result type. The State# has VoidRep representation in in
cmm, so no register gets allocated for it and eventually the State#
function argument is compiled to nothing in the machine code. i.e. the
compilation steps for the code above is:
foreign import ccall "math.h sin" sin :: CDouble -> CDouble
1. Initial STG type is: CDouble -> State# RealWorld -> (# State#
RealWorld, CDouble #)
2. Lowered STG type is: CDouble -> State# RealWorld -> (# CDouble #)
3. FFI C function should be: double sin(double x);
Regards,
Csaba
On Mon, Oct 28, 2019 at 10:59 AM Christopher Done
Hi all,
I tried compiling this file:
{-# LANGUAGE NoImplicitPrelude #-}-- | Demonstrate various use of the FFI.module Foreign whereimport Foreign.Cforeign import ccall "math.h sin" sin :: CDouble -> CDoubleit :: CDoubleit = sin 1
And I’ve noticed that the annotated type given for this foreign op in Core, is (# State# RealWorld, CDouble #), whereas I would have expected e.g. CDouble.
Meanwhile, the foreign op call is passed a RealWorld argument.
Additionally, code that consumes the result of this foreign call expects a (# CDouble #) as a return value.
So there are some assumptions I put in my interpreter to test this FFI call out:
1. Despite claiming to return the real world in a tuple, it actually should just return an unboxed tuple of the value. 2. It should ignore the RealWorld argument entirely.
I assume, if I were to lift this function into returning IO, that I should indeed return the RealWorld argument given. So the lesson is:
All FFI functions accept a RealWorld, and may return a 2-tuple of State# RealWorld *if* it’s impure, else it’ll return a 1-tuple of the value. Correct?
Can someone confirm that my observations are right? Also, if so, is there somewhere I can read more about this?
Cheers
Chris _______________________________________________ ghc-devs mailing list ghc-devs@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs

Aha, thanks Csaba. So I’m not losing my marbles. The AST has a type signature of the “initial” but implements the “lowered”. So with -ddump-stg we can observe it: The version claimed in the type signature (returning a tuple): Foreign.it :: Foreign.C.Types.CDouble [GblId] = [] \u [] case ds_r1HA of { GHC.Types.D# ds2_s1HW [Occ=Once] -> case __pkg_ccall_GC main [ds2_s1HW GHC.Prim.realWorld#] of { (#,#) _ [Occ=Dead] ds4_s1I0 [Occ=Once] -> GHC.Types.D# [ds4_s1I0]; }; }; The final “lowered” version: Foreign.it :: Foreign.C.Types.CDouble [GblId] = [] \u [] case ds_r1HA of { GHC.Types.D# ds2_s1HW [Occ=Once] -> case __pkg_ccall_GC main [ds2_s1HW GHC.Prim.realWorld#] of { Unit# ds4_s1I0 [Occ=Once] -> GHC.Types.D# [ds4_s1I0]; }; }; Cheers! Chris

For anyone interested, here's a complete list of all foreign imports at the
STG level from base and integer-simple:
https://gist.github.com/chrisdone/24b476862b678a3665fbf9b833a9905f
They all have type (# State# RealWorld #) or (# State# RealWorld,
<something> #).
On Tue, 5 Nov 2019 at 15:18, Christopher Done
Aha, thanks Csaba. So I’m not losing my marbles. The AST has a type signature of the “initial” but implements the “lowered”. So with -ddump-stg we can observe it:
The version claimed in the type signature (returning a tuple):
Foreign.it :: Foreign.C.Types.CDouble [GblId] = [] \u [] case ds_r1HA of { GHC.Types.D# ds2_s1HW [Occ=Once] -> case __pkg_ccall_GC main [ds2_s1HW GHC.Prim.realWorld#] of { (#,#) _ [Occ=Dead] ds4_s1I0 [Occ=Once] -> GHC.Types.D# [ds4_s1I0]; }; };
The final “lowered” version:
Foreign.it :: Foreign.C.Types.CDouble [GblId] = [] \u [] case ds_r1HA of { GHC.Types.D# ds2_s1HW [Occ=Once] -> case __pkg_ccall_GC main [ds2_s1HW GHC.Prim.realWorld#] of { Unit# ds4_s1I0 [Occ=Once] -> GHC.Types.D# [ds4_s1I0]; }; };
Cheers!
Chris
participants (2)
-
Christopher Done
-
Csaba Hruska