Skip to content

Fold PrimLen over a manifest string literal #343

Description

@Unisay

Problem

PrimLen is the IR node for Lua's unary # operator — the element count of a table or the byte count of a string. Constant folding handles it over a manifest array but not over a manifest string. Verbatim from Language.PureScript.Backend.IR.Optimizer:

reduceArrayRead  Applicative m  RewriteRuleM m Ann
reduceArrayRead =
  pure . \case
    PrimLen ann (LiteralArray _ elements) 
      Just $ LiteralInt ann (fromIntegral (length elements))
    ArrayIndex ann (LiteralArray _ elements) index 
      setAnn ann <$> elements !!? fromIntegral index
    _  Nothing

There is no PrimLen ann (LiteralString _ s) case, so a string length over a literal survives to the output. Since #247 lifted Data.String.CodeUnits.length into this node, the gap is now reachable from ordinary source. Golden.LengthLift calls widthOf "hello" where widthOf = CodeUnits.length, and the emitted Lua is:

  local _ = Effect_Console_log(Data_Show_showIntImpl(#("hello")))()

The array case beside it folds all the way, from the same golden's countOf [10, 20, 30]:

  local _ = Effect_Console_log(Data_Show_showIntImpl(3))()

So #("hello") should be 5.

Approach

Add the LiteralString case. The subtlety is which length to use: in pslua a PureScript String is a Lua byte string holding UTF-8 (see docs/QUIRKS.md), so #s at runtime is the byte count of the encoded literal, not the character count of the Haskell Text the IR carries. Folding to Text.length would be wrong for any literal outside ASCII — #("é") is 2, not 1. The fold must therefore measure the literal exactly as the printer encodes it, which is the same encoding decision the string printer already makes, including the lone-surrogate/WTF-8 path the codebase handles deliberately. Reusing that encoder rather than re-deriving a byte count is what keeps the fold and the emitted literal from disagreeing.

Data.String.CodeUnits.length is the operation whose PureScript semantics this matches: it counts code units, and in this backend a code unit is a byte.

Verification / Measurement

A folding unit test per class in the optimizer spec: an ASCII literal (#"hello"5), a multi-byte literal where the byte count and the character count differ (so a Text.length implementation fails the test), and a lone-surrogate literal to pin agreement with the printer's escape path.

Golden.LengthLift's Lua golden then moves from #("hello") to 5, with its eval/golden.txt oracle unchanged at 5 — the oracle is what proves the fold computed the same number the runtime did.

Prerequisites / Relations

Independent. Follows #247, which made the node reachable from string source.

Metadata

Metadata

Assignees

No one assigned

    Labels

    OptimisationA Compiler Optimisationarea: irIR / optimizer / DCE / inlinerenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions