Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions changelog.d/20260723_100000_unisay_fold_ascii_char_ordering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### Changed

- The IR constant folder now evaluates ordering comparisons (`<`, `<=`,
`>`, `>=`) on two ASCII `Char` literals (#222). Equality on `Char`
literals already folded; ordering was held back because Lua orders
strings by bytes, which can disagree with codepoint order — but for
two codepoints below U+0080 the single-byte representation orders
identically, so the fold is gated on the ASCII range. Non-ASCII chars
and `String` literals remain unfolded. In the `CharLiterals` golden,
`show ('\t' < '\n')` collapses from a runtime branch on a constant
condition to the literal `"true"`.
23 changes: 14 additions & 9 deletions lib/Language/PureScript/Backend/IR/Optimizer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Language.PureScript.Backend.IR.Optimizer where

import Control.Lens (over, toListOf, transformOf, universeOf)
import Control.Monad.Writer.CPS (WriterT, runWriterT, tell)
import Data.Char (isAscii)
import Data.Foldable (foldrM)
import Data.List qualified as List
import Data.List.NonEmpty qualified as NE
Expand Down Expand Up @@ -1036,9 +1037,11 @@ primops]). The per-operator caveats:
* @..@ folds string with string only. Lua coerces a number operand on
concat with a version- and build-dependent format, so a number is
never reproduced at compile time.
* Comparisons fold on two numbers (int or finite float); strings and
chars are left alone, because Lua orders strings by bytes while the
IR literal carries semantic 'Text'.
* Comparisons fold on two numbers (int or finite float), and on two
ASCII chars, whose single-byte Lua representation orders exactly
like the codepoint. Strings and non-ASCII chars are left alone,
because Lua orders strings by bytes while the IR literal carries
semantic 'Text'.
* @and@/@or@ fold when both operands are boolean, and additionally
collapse a known-boolean first operand (@true and b == b@,
@false or b == b@, and the two annihilators): sound because Lua
Expand Down Expand Up @@ -1071,10 +1074,10 @@ foldPrimBinOp op l r = case (op, l, r) of
(PrimDiv, LiteralFloat _ a, LiteralFloat _ b) → floatFold (a / b)
(PrimConcat, LiteralString _ a, LiteralString _ b) →
Just (literalString (a <> b))
(PrimLt, _, _) → literalBool . (== LT) <$> numericCompare l r
(PrimLe, _, _) → literalBool . (/= GT) <$> numericCompare l r
(PrimGt, _, _) → literalBool . (== GT) <$> numericCompare l r
(PrimGe, _, _) → literalBool . (/= LT) <$> numericCompare l r
(PrimLt, _, _) → literalBool . (== LT) <$> compareLiterals l r
(PrimLe, _, _) → literalBool . (/= GT) <$> compareLiterals l r
(PrimGt, _, _) → literalBool . (== GT) <$> compareLiterals l r
(PrimGe, _, _) → literalBool . (/= LT) <$> compareLiterals l r
(PrimAnd, LiteralBool _ a, LiteralBool _ b) → Just (literalBool (a && b))
(PrimAnd, LiteralBool _ True, b) → Just b
(PrimAnd, LiteralBool _ False, _) → Just (literalBool False)
Expand All @@ -1095,11 +1098,13 @@ foldPrimBinOp op l r = case (op, l, r) of
| isFinite result = Just (literalFloat result)
| otherwise = Nothing

numericCompare ∷ Exp → Exp → Maybe Ordering
numericCompare a b = case (a, b) of
compareLiterals ∷ Exp → Exp → Maybe Ordering
compareLiterals a b = case (a, b) of
(LiteralInt _ x, LiteralInt _ y) → Just (compare x y)
(LiteralFloat _ x, LiteralFloat _ y)
| isFinite x, isFinite y → Just (compare x y)
(LiteralChar _ x, LiteralChar _ y)
| isAscii x, isAscii y → Just (compare x y)
_ → Nothing

isFinite ∷ Double → Bool
Expand Down
17 changes: 17 additions & 0 deletions test/Language/PureScript/Backend/IR/Optimizer/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import Language.PureScript.Backend.IR.Types
, lets
, listGrouping
, literalBool
, literalChar
, literalFloat
, literalInt
, literalObject
Expand Down Expand Up @@ -927,6 +928,22 @@ spec = describe "IR Optimizer" do
let original = primBinOp PrimLt (literalString "a") (literalString "b")
optimizedExpression original `shouldBe` original

it "folds ordering on two ASCII char literals (#222)" do
optimizedExpression (primBinOp PrimLt (literalChar '\t') (literalChar '\n'))
`shouldBe` literalBool True
optimizedExpression (primBinOp PrimLe (literalChar 'a') (literalChar 'a'))
`shouldBe` literalBool True
optimizedExpression (primBinOp PrimGt (literalChar 'a') (literalChar 'b'))
`shouldBe` literalBool False
optimizedExpression (primBinOp PrimGe (literalChar 'z') (literalChar 'a'))
`shouldBe` literalBool True

it "does not fold ordering on non-ASCII char literals (bytes vs Text)" do
let nonAscii = primBinOp PrimLt (literalChar 'é') (literalChar 'ê')
mixed = primBinOp PrimLt (literalChar 'a') (literalChar 'é')
optimizedExpression nonAscii `shouldBe` nonAscii
optimizedExpression mixed `shouldBe` mixed

it "folds boolean and/or and not" do
optimizedExpression (primBinOp PrimAnd (literalBool True) (literalBool False))
`shouldBe` literalBool False
Expand Down
20 changes: 1 addition & 19 deletions test/ps/output/Golden.CharLiterals.Test/golden.ir
Original file line number Diff line number Diff line change
Expand Up @@ -116,25 +116,7 @@ UberModule
( AppN Nothing
( AppN Nothing
( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) )
( Let Nothing
( Standalone
( Nothing, Name "v$221", PrimBinOp Nothing PrimLt
( LiteralChar Nothing '\x9' )
( LiteralChar Nothing ' ' )
) :| []
)
( IfThenElse Nothing
( Ref Nothing ( Local ( Name "v$221" ) ) )
( LiteralString Nothing "true" )
( IfThenElse Nothing
( Eq Nothing ( LiteralBool Nothing False )
( Ref Nothing ( Local ( Name "v$221" ) ) )
)
( LiteralString Nothing "false" )
( Exception Nothing "No patterns matched" )
)
) :| []
)
( LiteralString Nothing "true" :| [] )
)
( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] )
)
Expand Down
11 changes: 1 addition & 10 deletions test/ps/output/Golden.CharLiterals.Test/golden.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,5 @@ return (function()
local _ = Effect_Console_log(Golden_CharLiterals_Test_show("\\"))()
local _ = Effect_Console_log(Golden_CharLiterals_Test_show("a"))()
local _ = Effect_Console_log("true")()
return Effect_Console_log((function()
local v_S_221 = "\t" < "\n"
if v_S_221 then
return "true"
elseif false == v_S_221 then
return "false"
else
return error("No patterns matched")
end
end)())()
return Effect_Console_log("true")()
end)()