ghcjs / ghcjs-base

base library for GHCJS for JavaScript interaction and marshalling, used by higher level libraries like JSC
MIT License
45 stars 67 forks source link

js_toJSArrayPure could be used with unforced JSVal (unless FFI is lazy?) #98

Open louispan opened 7 years ago

louispan commented 7 years ago

There are parts of ghc-base that forces to whnf using seq for some javascript functions that expect forced JSVals. Eg.

Data/JSString/Text.hs 46:lazyTextToJSString t = rnf t seq js_lazyTextToString (unsafeCoerce t)

Data/JSString.hs 228:pack x = rnf x seq js_pack (unsafeCoerce x) 518:intercalate i xs = rnf xs seq js_intercalate i (unsafeCoerce xs) 788:concat xs = rnf xs seq js_concat (unsafeCoerce xs) 1558:unlines xs = rnf xs seq js_unlines (unsafeCoerce xs) 1563:unwords xs = rnf xs seq js_unwords (unsafeCoerce xs)

JavaScript/Array/Internal.hs 52:fromList xs = rnf xs seq js_toJSArrayPure (unsafeCoerce xs) 56:fromListIO xs = IO (\s -> rnf xs seq js_toJSArray (unsafeCoerce xs) s)

JavaScript/Array/ST.hs 59:fromList xs = ST (\s -> rnf xs seq I.js_toJSArray (unsafeCoerce xs) s)

Eg. The comment on shim/strings.js h$fromHsListJSVal(xs) says

list of JSVal to array, list must have been completely forced first

However, the documentation for seq says

A note on evaluation order: the expression seq a b does not guarantee that a will be evaluated before b. The only guarantee given by seq is that the both a and b will be evaluated before seq returns a value. In particular, this means that b may be evaluated before a. If you need to guarantee a specific order of evaluation, you must use the function pseq from the "parallel" package.

This means that is is possible to break the precondition for h$fromHsListJSVal and pass it an unforced JSVal.

Note, pseq is defined as

-- The reason for the strange "lazy" call is that -- it fools the compiler into thinking that pseq and par are non-strict in -- their second argument (even if it inlines pseq at the call site). -- If it thinks pseq is strict in "y", then it often evaluates -- "y" before "x", which is totally wrong. pseq :: a -> b -> b pseq x y = x seq lazy y

And GHC.Exts.lazy is documented:

The lazy function restrains strictness analysis a little. The call lazy e means the same as e, but lazy has a magical property so far as strictness analysis is concerned: it is lazy in its first argument, even though its semantics is strict. After strictness analysis has run, calls to lazy are inlined to be the identity function.

Are javascript FFI's implicitly lazy? If javascript FFI have the same effect as lazy, then this is a non issue. Otherwise, there may be cases where the javascript functions does not get a forced JSVal as expected.

Even so, it might still be best to replace seq with pseq in case in the future the FFI behaviour changes.