-
Notifications
You must be signed in to change notification settings - Fork 563
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New command: purs codegen #4092
base: master
Are you sure you want to change the base?
Conversation
app/Command/Codegen.hs
Outdated
<> Opts.showDefault | ||
<> Opts.help "The output directory" | ||
|
||
globWarningOnMisses :: (String -> IO ()) -> [FilePath] -> IO [FilePath] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Command.Graph
, Command.Compile
, and Command.Codegen
all contain the same definition here, but I wasn't sure the most appropriate place to pull it out to for sharing.
If there is an appropriate place to move this then I will update all 3 of those modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a good idea. Create a Command.Common
module?
@@ -97,7 +97,7 @@ data MakeActions m = MakeActions | |||
, readExterns :: ModuleName -> m (FilePath, Maybe ExternsFile) | |||
-- ^ Read the externs file for a module as a string and also return the actual | |||
-- path for the file. | |||
, codegen :: CF.Module CF.Ann -> Docs.Module -> ExternsFile -> SupplyT m () | |||
, codegen :: CF.Module CF.Ann -> Docs.Module -> Maybe ExternsFile -> SupplyT m () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When performing codegen
via purs codegen
- we can create a stub ExternsFile
via the CoreFn.Module
- but it isn't actually the ExternsFile
we want to write. I modified this so that we don't write a bogus ExternsFile
during purs codegen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it would be better to factor codegen
the function into a function for each output (including the externs file), and only use the JS-outputting function for purs codegen
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea - I can go ahead and do that now
@@ -97,7 +97,7 @@ data MakeActions m = MakeActions | |||
, readExterns :: ModuleName -> m (FilePath, Maybe ExternsFile) | |||
-- ^ Read the externs file for a module as a string and also return the actual | |||
-- path for the file. | |||
, codegen :: CF.Module CF.Ann -> Docs.Module -> ExternsFile -> SupplyT m () | |||
, codegen :: CF.Module CF.Ann -> Docs.Module -> Maybe ExternsFile -> SupplyT m () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it would be better to factor codegen
the function into a function for each output (including the externs file), and only use the JS-outputting function for purs codegen
.
app/Command/Codegen.hs
Outdated
<> Opts.showDefault | ||
<> Opts.help "The output directory" | ||
|
||
globWarningOnMisses :: (String -> IO ()) -> [FilePath] -> IO [FilePath] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a good idea. Create a Command.Common
module?
app/Command/Codegen.hs
Outdated
|
||
foreigns <- P.inferForeignModules filePathMap | ||
(makeResult, makeWarnings) <- | ||
liftIO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is liftIO
needed here?
app/Command/Codegen.hs
Outdated
return paths | ||
|
||
concatMapM :: (a -> IO [b]) -> [a] -> IO [b] | ||
concatMapM f = fmap concat . mapM f |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While you're messing around with this, you can import concatMapM
from Protolude
. Don't know why we aren't already doing that.
app/Command/Codegen.hs
Outdated
concatMapM f = fmap concat . mapM f | ||
|
||
-- | Arguments: use JSON, warnings, errors | ||
printWarningsAndErrors :: Bool -> P.MultipleErrors -> Either P.MultipleErrors a -> IO () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just Command.Compile.printWarningsAndErrors True
, right? Looks like another candidate for Command.Common
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, it is, great call
Sorry, things got busy around here, I'm going to pick this back up soon to address the feedback. |
@rhendric I've addressed your initial feedback. While doing the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! I have one outstanding question about the runSupplyT 0
here, and I suggest rebasing on master to clean up some conflicts and HLint nits, but I think this is in great shape already.
app/Command/Codegen.hs
Outdated
foreigns <- P.inferForeignModules filePathMap | ||
(makeResult, makeWarnings) <- | ||
P.runMake purescriptOptions | ||
$ runSupplyT 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the normal compilation path, the codegen supply monad is initialized with the next unused number from previous parts of the compilation. Using 0 here raises the question of whether this reuse is necessary. If so, using 0 here might cause problems. If not (I suspect not), we should probably be consistent so that the produced code isn't different when generated just by purs compile
versus purs compile; purs codegen
.
So assuming it's safe to do so, I think we should remove the SupplyT
from the signatures in MakeActions
and push that detail into their implementations. But now would be a really good time for someone else to share why that wouldn't be safe!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great point. I tried reading through the usages of the supply monad in the codegen code and it looks like it's just for generating fresh variable names - it doesn't seem to me that it'd require we start off from where we left off in say typechecking - but I don't have enough experience to say for sure.
At work we've been using zephyr
for quite a while, which also starts from 0 for codegen, so I'd be really surprised if it causes errors.
If it is the case that it doesn't matter, then I'll remove the SupplyT
requirement and start it from zero within codegenJS
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spent some time going through the codegenJS
implementation and I don't think that it is dangerous to always initial that supply with 0. The fact that zephyr was doing that for so long also makes me pretty confident based on my personal experience.
I went ahead and made the change you suggested, and all tests are passing.
If anyone knows more than I do and thinks that we should undo the change, I can do that too!
app/Command/Codegen.hs
Outdated
M.fromList $ map ((\m -> (CoreFn.moduleName m, Right $ CoreFn.modulePath m)) . snd) $ rights mods | ||
|
||
unless (null (lefts mods)) $ do | ||
_ <- traverse (hPutStr stderr . formatParseError) $ lefts mods |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hlint
will yell at you for this when you rebase on master. Use traverse_
.
app/Command/Codegen.hs
Outdated
runCodegen foreigns filePathMap m = | ||
P.codegenJS (makeActions foreigns filePathMap) False m |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hlint
will yell at you for this too, but actually I think you should probably just inline this whole definition.
I'll spend some time thinking about how we could add a meaningful test for this soon. Other than that, there is the outstanding question of initializing the codegen supply with 0, which I've gone ahead and done. Then this should be ready for a final review! |
app/Command/Codegen.hs
Outdated
M.fromList $ map ((\m -> (CoreFn.moduleName m, Right $ CoreFn.modulePath m)) . snd) $ rights mods | ||
|
||
unless (null (lefts mods)) $ do | ||
traverse_ (hPutStr stderr . formatParseError) $ lefts mods |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since left mods
is used twice, perhaps this should be turned into a let
above?
let errList = lefts mods
Also, since filePathMap
isn't used until after the unless
block, perhaps it should go below this block but above the foreigns <- P.inferForeignMoudles filePathMap
line?
app/Command/Codegen.hs
Outdated
(makeResult, makeWarnings) <- | ||
P.runMake purescriptOptions | ||
$ traverse (P.codegenJS (makeActions foreigns filePathMap) codegenSourceMaps . snd) | ||
$ rights mods |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a second rights mods
. Perhaps that should also be moved to a let
binding so more things can reuse it?
@colinwahl do you have bandwidth to finish this off? Can I help? |
I've rebased on current master here. |
@MaybeJustJames would you like to take this over from me? My bandwidth for compiler work is pretty low these days. The big open question is that I'm still not sure if #4092 (comment) could lead to any problems. |
As the question is over a year old, I'm inclined to say let's ship it and find out. |
Happy to take over. How would you like to do it? |
However you'd like to do it is fine with me - you could continue off this PR, or make a new branch and cherry-pick my changes, or just close it and start again from scratch. Let me know what you decide and if I should close this PR! |
IMHO it would be a shame to lose the context here. Could I get write permission to your branch so I can pick up from here? |
I was wondering - is this work necessary at all now that we have the backend optimizer? |
Supporting an optimizer was certainly my main goal for this - now that we've got |
It does strike me as a loss if the best general-purpose JavaScript backend for PureScript remains in a third-party project in the long term. I'm not sure how exactly this happened—I suspect the friction to contributing to PureScript is just too high for this level of innovation—but with enough time I would hope it can be mostly unforked. At that point, exposing the backend used by |
@rhendric I agree with you - my point here is that the new backend has shuffled the landscape quite a bit: it shows not only that it's possible to aggressively optimise the CoreFn, but also that it's possible to emit more performant JS outside of the compiler, and all of this while the implementation is in PureScript. |
Okay yeah, I agree with looking at Is In the meantime, as long as there's some value in having a built-in JS backend (regardless of the language in which the backend is written or the repo in which it lives), I think there's still a case for exposing it, so users can benefit from custom optimizations and rewrites without needing to also use a third-party backend. |
purs-backend-es does not subsume compiler functionality.
So, I do not see any near term future where the current JS backend is rendered obsolete, though I would like a near term future where something like purs-backend-es can be used in a first-class way. That being said, if there are currently no pending users of this feature, I'm not sure what the point is. I think the fresh name issue seems like it can clearly cause a problem, however unlikely, and I'm not sure how you'd fix it. I don't know how I feel about merging a feature with a uncertain prospects and potentially buggy behavior. I'm happy to talk about |
I agree. I wanted to get this through for zephyr specifically. There is potential for other optimization tools to make use of this interface even if an improved backend is eventually merged. |
Also expose the new codegenJS action for use in purs codegen
Always initialize supply with 0 as codegenJS implementation detail in order to get deterministic variable naming while doing a normal purs compile vs purs compile; purs codegen
Co-authored-by: Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>
cb5920a
to
231ed9e
Compare
I think this PR can be closed, right? |
I would still vote to merge for the |
Is the vision for purescript to have multiple codegen backends? If non-javascript backends are always going to be separate projects then maybe to makes sense for JavaScript codegen to be separate too? In which case this PR should be closed. If the vision for the compiler is to include multiple backends then I think a |
Description of the change
Implements a new command:
purs codegen
purs codegen
takes globs to filepaths containing the JSON representation of a CoreFn Module (this can be generated bypurs compile
). It parses the core functional representation out of these files, and passes them in to the standardcodegen
function.This command allows for CoreFn transformations to be written outside of the compiler (even in PureScript!) without having to worry about using PureScript as a library.
Example usage of this would be:
This intends to close #3339
Checklist: