From 527616e950fd8942c182be903d176f4b9890ee5a Mon Sep 17 00:00:00 2001 From: Sylvain Henry <sylvain@haskus.fr> Date: Tue, 26 Mar 2024 14:55:33 +0100 Subject: [PATCH] JS: fix h$appendToHsString implementation (#24495) h$appendToHsString needs to wrap its argument in an updatable thunk to behave like unpackAppendCString#. Otherwise if a SingleEntry thunk is passed, it is stored as-is in a CONS cell, making the resulting list impossible to deepseq (forcing the thunk doesn't update the contents of the CONS cell)! The added test checks that the optimization kicks in and that h$appendToHsString works as intended. Fix #24495 --- compiler/GHC/StgToJS/Linker/Utils.hs | 3 +++ compiler/GHC/StgToJS/Rts/Rts.hs | 13 +++++++++++++ rts/js/string.js | 5 ++++- testsuite/tests/javascript/Makefile | 9 +++++++++ testsuite/tests/javascript/T24495.hs | 22 ++++++++++++++++++++++ testsuite/tests/javascript/T24495.stdout | 2 ++ testsuite/tests/javascript/all.T | 1 + 7 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 testsuite/tests/javascript/Makefile create mode 100644 testsuite/tests/javascript/T24495.hs create mode 100644 testsuite/tests/javascript/T24495.stdout diff --git a/compiler/GHC/StgToJS/Linker/Utils.hs b/compiler/GHC/StgToJS/Linker/Utils.hs index 7acaadd2e395..de66e96be4ce 100644 --- a/compiler/GHC/StgToJS/Linker/Utils.hs +++ b/compiler/GHC/StgToJS/Linker/Utils.hs @@ -191,6 +191,9 @@ genCommonCppDefs profiling = mconcat -- resumable thunks , "#define MAKE_RESUMABLE(closure,stack) { (closure).f = h$resume_e; (closure).d1 = (stack), (closure).d2 = null; }\n" + -- making a thunk + , "#define MK_UPD_THUNK(closure) h$c1(h$upd_thunk_e,(closure))\n" + -- general deconstruction , "#define IS_THUNK(x) ((x).f.t === CLOSURE_TYPE_THUNK)\n" , "#define CONSTR_TAG(x) ((x).f.a)\n" diff --git a/compiler/GHC/StgToJS/Rts/Rts.hs b/compiler/GHC/StgToJS/Rts/Rts.hs index 78d43f196b8d..2297ec60bea7 100644 --- a/compiler/GHC/StgToJS/Rts/Rts.hs +++ b/compiler/GHC/StgToJS/Rts/Rts.hs @@ -448,6 +448,19 @@ rts_gen s = do , r4 |= d4 , returnS (app "h$ap_3_3_fast" []) ]) + , closure (ClosureInfo (TxtI "h$upd_thunk_e") (CIRegs 0 [PtrV]) "updatable thunk" (CILayoutFixed 1 [PtrV]) CIThunk mempty) + (jVar $ \t -> return $ + mconcat [t |= closureField1 r1 + , adjSp' 2 + , stack .! (sp - 1) |= r1 + , stack .! sp |= var "h$upd_frame" + , closureEntry r1 |= var "h$blackhole" + , closureField1 r1 |= var "h$currentThread" + , closureField2 r1 |= null_ + , r1 |= t + , returnS (app "h$ap_0_0_fast" []) + ] + ) -- select first field , closure (ClosureInfo (global "h$select1_e") (CIRegs 0 [PtrV]) "select1" (CILayoutFixed 1 [PtrV]) CIThunk mempty) (jVar \t -> return $ diff --git a/rts/js/string.js b/rts/js/string.js index 302ca55aa5c8..4797cb13b5ea 100644 --- a/rts/js/string.js +++ b/rts/js/string.js @@ -723,7 +723,10 @@ function h$appendToHsStringA(str, appendTo, cc) { function h$appendToHsStringA(str, appendTo) { #endif var i = str.length - 1; - var r = appendTo; + // we need to make an updatable thunk here + // if we embed the given closure in a CONS cell. + // (#24495) + var r = i == 0 ? appendTo : MK_UPD_THUNK(appendTo); while(i>=0) { r = MK_CONS_CC(str.charCodeAt(i), r, cc); --i; diff --git a/testsuite/tests/javascript/Makefile b/testsuite/tests/javascript/Makefile new file mode 100644 index 000000000000..185dc8e117a1 --- /dev/null +++ b/testsuite/tests/javascript/Makefile @@ -0,0 +1,9 @@ +TOP=../.. +include $(TOP)/mk/boilerplate.mk +include $(TOP)/mk/test.mk + +T24495: + '$(TEST_HC)' $(TEST_HC_OPTS) T24495.hs -v0 -O1 -dsuppress-uniques -ddump-js -ddump-to-file + ./T24495 + # check that the optimization occurred + grep -c appendToHsStringA T24495.dump-js diff --git a/testsuite/tests/javascript/T24495.hs b/testsuite/tests/javascript/T24495.hs new file mode 100644 index 000000000000..d2fd49312ae0 --- /dev/null +++ b/testsuite/tests/javascript/T24495.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE MagicHash #-} +{-# OPTIONS_GHC -O1 #-} +-- -O1 required to make "rest" thunk SingleEntry + +module Main where + +import GHC.CString +import GHC.JS.Prim (JSVal, toJSString) + +foo :: Double -> IO () +foo x = debugString (toJSString ("2 " ++ s)) + where + x' = if x == 0 then "b" else "c" + y' = if x == 0 then "b" else "c" + s = "a" ++ x' ++ " " ++ y' ++ "d" + +main :: IO () +main = foo 0 + + +foreign import javascript "((s) => { console.log(s); })" + debugString :: JSVal -> IO () diff --git a/testsuite/tests/javascript/T24495.stdout b/testsuite/tests/javascript/T24495.stdout new file mode 100644 index 000000000000..aa4a4fd55696 --- /dev/null +++ b/testsuite/tests/javascript/T24495.stdout @@ -0,0 +1,2 @@ +2 ab bd +2 diff --git a/testsuite/tests/javascript/all.T b/testsuite/tests/javascript/all.T index d79c01e72ea7..949222d0a296 100644 --- a/testsuite/tests/javascript/all.T +++ b/testsuite/tests/javascript/all.T @@ -21,3 +21,4 @@ test('js-mk_tup', extra_files(['test-mk_tup.js']), compile_and_run, ['test-mk_tu test('T23346', normal, compile_and_run, ['']) test('T22455', normal, compile_and_run, ['-ddisable-js-minifier']) test('T23565', normal, compile_and_run, ['']) +test('T24495', normal, makefile_test, ['T24495']) -- GitLab