Skip to content

Conversation

@nwolverson
Copy link
Contributor

Reusing the same inlining code from runFnN/mkFnN

isNFn prefix n (Var _ name) = name == (prefix <> T.pack (show n))
isNFn prefix n (Indexer _ (StringLiteral _ name) (Var _ dataFunctionUncurried)) | dataFunctionUncurried == C.dataFunctionUncurried =
isNFn :: Text -> Text -> Int -> AST -> Bool
isNFn _ prefix n (Var _ name) = name == (prefix <> T.pack (show n))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this line (preserved from the existing code) means that all runFnX defined in some arbitrary local module are inlined by this same scheme (verified this with an example) - shouldn't it only apply to the target module as per the next clause?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It certainly seems like it. Is it possible to break the current code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I defined in my Main module

runFn3 f a b c = f a b c

and application of this was inlined, leading to runtime failure. I think this line can just be removed, the optimisation still seems to apply otherwise - I just don't understand what case this was meant to address.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok great, could you please add a test for that then, to make sure it doesn't break any more? Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the line sounds fine to me if it works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I didn't realise the compiled test output actually gets run, for some reason I thought there were just compiles/warns/errors tests.

@natefaubion
Copy link
Contributor

Hah! I worked this up last night, and my version was exactly the same, modulo function names. One thing that I think really needs to be solved though, is that the compiler will insert something like this in magic do blocks (if you use case/if):

main = function __do () {
  foo = bar();
  (function () {
    var $1 = ...;
    if ($1 ...) {
      return someEff;
    }
    return otherEff;
  })()();
  return unit;
}

This is a problem because with EffFn inlining, you lose a significant optimization opportunity:

(function () {
  if (...) {
    return function () {
      return someEffFn(1, 2, 3);
    }
  }
  return function () {
    return someEffFn(4, 5, 6);
  }
})()()

If we floated that call into the leaves (I think applyReturns in MagicDo.hs), then the IIFE code should take care of it.

@natefaubion
Copy link
Contributor

FWIW, I'm working converting halogen-vdom to EffFn, so we can compare output and performance.

@nwolverson
Copy link
Contributor Author

Oops.

So I verified that the simple binding in magic-do just becomes a straight call:

function __do() {
...
  $foreign.log2("Hello", "World");
...
}

But yeah this is just the case for the straightforward discard or binding cases of the magic-do optimisation.

@natefaubion
Copy link
Contributor

I added this case to undo in MagicDo.purs, which eliminates a lot of redundant applications. Otherwise, if you have any cases that use EffFn, you'll probably undo the optimization due to redundant applications.

  undo (App _ (App s1 (Function s2 Nothing [] (Block ss body)) []) []) =
    App s1 (Function s2 Nothing [] (Block ss (applyReturns `fmap` body))) []

@nwolverson
Copy link
Contributor Author

Yup, I can see that working in my example. Feel free to PR any other changes to this branch if that would be easiest?

@natefaubion
Copy link
Contributor

Another change I made was to do untilFixedPoint (return . tidyUp . tco . magicDo) js', which removes a lot of leftover things like pure applications. I don't know what the implications overall for this are.

@natefaubion
Copy link
Contributor

Just using untilFixedPoint (return . tidyUp) . tco =<< untilFixedPoint (return . magicDo) js' works well. Applying tco to a fixed point seems a bit dodgy.

-- Remove __do function applications which remain after desugaring
undo :: AST -> AST
undo (Return _ (App _ (Function _ (Just ident) [] body) [])) | ident == fnName = body
undo (App _ (App s1 (Function s2 Nothing [] (Block ss body)) []) []) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why this case is needed, but it makes the code harder to read in my opinion. applyReturns is only used in convert right now, and undo runs after the full traversal.

Can you give an example of why this is necessary? Is it possible to accomplish the same thing by modifying the convert function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defer to @natefaubion here. But the example code is in #3026 (comment) - with this change an extra thunk is removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a PR to this branch that rearranges the optimizations, and removes undo in favor of running convert to a fixed point. The problem was that some cases that covert inlines only arise after undo is run.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I didn't get a notification, sorry

Apply magicDo until fixed point
@paf31
Copy link
Contributor

paf31 commented Aug 6, 2017

Given the new untilFixedPoint call, we should try to measure any performance impact here before this gets merged.

Also, since this code is quite fragile, we should try to build the package set, and actually run some package test suites to make sure nothing gets broken.

@natefaubion
Copy link
Contributor

Maybe the fixed point isn't necessary? It should stabilize after another run, So maybe you'd just need to split up convert into two functions, and inline the repetition.

@nwolverson
Copy link
Contributor Author

Removed the case that was firing on local definitions rather than the particular module name, now there is a test failure for AppendInReverse.purs which I hope is unrelated

@paf31
Copy link
Contributor

paf31 commented Aug 6, 2017

If you merge master, the failure should go away.

@paf31
Copy link
Contributor

paf31 commented Sep 14, 2017

We can try this out extensively once the release candidate is done, just to make sure there isn't a significant performance regression.

Thanks!

Copy link
Contributor

@paf31 paf31 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Thanks!

@paf31 paf31 merged commit 0d1568f into purescript:master Sep 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants