Skip to content
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

WIP: Implement Java Interfaces with Frege Records #363

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
3 changes: 0 additions & 3 deletions frege/compiler/common/Errors.fr
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import frege.compiler.enums.Flags
import frege.compiler.types.Positions
import Compiler.types.Global as G

derive Eq Severity
derive Show Severity

{--
Format an error message, remember in the state and update error counter in the state.
-}
Expand Down
8 changes: 7 additions & 1 deletion frege/compiler/enums/CaseKind.fr
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
--- "Kind" of a case
module frege.compiler.enums.CaseKind where

import frege.data.JSON (ToJSON, toJSON)

{--
case kind
-}
Expand All @@ -10,4 +12,8 @@ data CKind =
| CNoWarn --- compiler generated, do not emit warnings

derive Eq CKind
derive Enum CKind
derive Enum CKind
derive Show CKind

instance ToJSON CKind where
toJSON = toJSON . show
5 changes: 4 additions & 1 deletion frege/compiler/enums/Flags.fr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
module frege.compiler.enums.Flags where

import Data.Bits (BitSet())
import Data.JSON (ToJSON, toJSON)

--- the compiler flags
data Flag =
Expand Down Expand Up @@ -37,10 +38,12 @@ data Flag =
derive Show Flag
derive Enum Flag

instance ToJSON Flag where
toJSON = toJSON . show

--- 'BitSet' for 'Flag's
type Flags = BitSet Flag


--- get flag bit
flag :: Flag -> Flags
flag = Flags.singleton
Expand Down
5 changes: 5 additions & 0 deletions frege/compiler/enums/Literals.fr
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
--- Classification of literals.
module frege.compiler.enums.Literals where

import frege.data.JSON (ToJSON, toJSON)

{--
type tag for Literals
-}
Expand All @@ -24,3 +26,6 @@ derive Enum Literalkind


derive Show Literalkind

instance ToJSON Literalkind where
toJSON = toJSON . show
5 changes: 5 additions & 0 deletions frege/compiler/enums/RFlag.fr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
module frege.compiler.enums.RFlag where
-- generated by Splitter
import frege.data.Bits(BitSet)
import frege.data.JSON (ToJSON, toJSON)

{--
Attributes that describe the run time behaviour of a function/value
Expand All @@ -18,6 +19,10 @@ data RFlag =
derive Show RFlag


instance ToJSON RFlag where
toJSON = toJSON . show


derive Enum RFlag

--- The 'RState' is maintained and persisted in class files for each variable and function
Expand Down
4 changes: 4 additions & 0 deletions frege/compiler/enums/SymState.fr
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
--- Symbol state
module frege.compiler.enums.SymState where

import frege.data.JSON (ToJSON, toJSON)

{--
symbol state
-}
Expand All @@ -19,3 +21,5 @@ derive Show SymState
derive Enum SymState


instance ToJSON SymState where
toJSON = toJSON . show
5 changes: 4 additions & 1 deletion frege/compiler/enums/TokenID.fr
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
--- 'TokenID' and associated instances
module frege.compiler.enums.TokenID where

import frege.data.JSON (ToJSON, toJSON)

{--
The tokens for the frege language.
-}
Expand Down Expand Up @@ -55,4 +57,5 @@ derive Ord TokenID

derive Enum TokenID


instance ToJSON TokenID where
toJSON = toJSON . show
6 changes: 6 additions & 0 deletions frege/compiler/enums/Visibility.fr
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
--- cross module visibility of items
module frege.compiler.enums.Visibility where

import frege.data.JSON (ToJSON, toJSON)

--- cross module visibility of items
data Visibility =
Private --- item is not available in other packages, except constructors for inlined code
Expand All @@ -15,6 +17,10 @@ instance Show Visibility where
show Abstract = "abstract"


instance ToJSON Visibility where
toJSON = toJSON . show


derive Eq Visibility


Expand Down
8 changes: 8 additions & 0 deletions frege/compiler/gen/java/Bindings.fr
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import frege.Prelude hiding(<+>)
import Data.TreeMap (TreeMap Map)
-- import Data.List as DL(zip4)
import Data.Bits(BitSet.member)
import Data.JSON(ToJSON)


-- import Compiler.common.Errors as E()
Expand Down Expand Up @@ -59,6 +60,13 @@ instance Show Binding where
++ stype ++ ", "
++ show jtype ++ ", "
++ showJex jex ++ "}"
instance ToJSON Binding where
toJSON x = JSON.struct "Bind" $ JSON.Struct
[ JSON.assoc "stype" x.stype
, JSON.assoc "ftype" x.ftype
, JSON.assoc "jtype" x.jtype
, JSON.assoc "jex" x.jex
]

newBind g sigma jex = Bind{stype=nicer sigma g,
ftype=sigma, jtype=sigmaJT g sigma, jex}
Expand Down
131 changes: 92 additions & 39 deletions frege/compiler/gen/java/MethodCall.fr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import Compiler.types.Types(unST, Sigma, Tau, TauT, ForAll, RhoTau, RhoFun)
import Compiler.types.Symbols(SymbolT)
import Compiler.types.Global
import Compiler.types.JNames(JName, memberOf)
import Compiler.types.Strictness()

import Compiler.common.Types as CT
import Compiler.common.JavaName
Expand Down Expand Up @@ -66,16 +65,23 @@ nativeCall ∷ Global → Symbol → TreeMap String Tau → [JExpr] → Binding
nativeCall g (sym@SymV {nativ = Just item, gargs}) subst aexs = newBind g bsig (call jrty args)
where
(rty, sigmas) = U.returnType sym.typ.rho
-- The types of the arguments
taus = [ tau | Just tau <- map U.sigmaAsTau sigmas ]
-- The base return type of the method
-- e.g. @b@ in @ST s (a|Maybe b)@
brty = substTau subst (baserty rty)
bsig = U.tauAsSigma brty
-- The java type arguments (generics arguments)
targs = map (boxed . tauJT g . substTau subst) gargs
args | [tau] <- taus, Just _ <- U.isUnit tau = [] -- no arguments
| otherwise = zipWith (argEx g) aexs taus
bjt = tauJT g brty
-- The java type of the result
jrty = strict bjt
-- retmode = maxStrict jrty

-- a Java expression for an argument e.g. putting @.call()@, marshalling @Maybe@, etc.
argEx :: Global -> JExpr -> Tau -> JExpr
argEx g arg tau
| Just x <- U.isMaybe tau = JQC checknothing (JAtom "null") evex
-- Just x <- arrayTau g tau = JCast x bind.jex
Expand Down Expand Up @@ -103,19 +109,20 @@ nativeCall g (sym@SymV {nativ = Just item, gargs}) subst aexs = newBind g bsig (
NIExtends ->
let evalStG :: Global -> StG a -> a
evalStG g st = fst $ st.run g
x = do g <- getST
si <- symInfo sym
let name = (head si.argSigs).rho.tau.name
irsym = unJust $ g.findit name
nms = mapMaybe (_.name) [ fld | x@SymD{} <- values irsym.env, fld <- x.flds ]
return $ flip mapMaybe nms $ \fldnm -> do
nativrsym <- g.findit $ si.retSig.rho.tau.name
nativsym <- TreeMap.lookup fldnm nativrsym.env
nativnm <- nativsym.nativ
let nativsi = evalStG g $ symInfo nativsym
fldsym <- TreeMap.lookup fldnm irsym.env
pure $ wrapIRMethod g (head args) (head si.argJTs) nativsi nativnm fldnm fldsym
in JNewClass jrty [] (evalStG g x)
x = -- IR stands for an Implementation Record
let irsym = unJust $ g.findit irname where
irname = (head irtau.flat).name
irtau = (head sigmas).rho.tau
nms = mapMaybe (_.name) [ fld | x@SymD{} <- values irsym.env, fld <- x.flds ]
in
flip mapMaybe nms $ \fldnm -> do
nativrsym <- g.findit $ (head rty.flat).name
nativsym <- TreeMap.lookup fldnm nativrsym.env
nativnm <- nativsym.nativ
let nativsi = evalStG g $ symInfo nativsym
fldsym <- TreeMap.lookup fldnm irsym.env
pure $ wrapIRMethod g (head args) (tauJT g $ head taus) fldsym fldnm nativsi nativnm
in JNewClass jrty (tail args) x
Copy link
Member Author

Choose a reason for hiding this comment

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

head args here is the IR itself, so it's omitted. args is ensured to be nonempty in the sanity function.

NICast -> case args of
[a] -> JInvoke (JAtom item) args -- was: JCast (Ref (JName "" item) []) a
_ -> JAtom "null"
Expand Down Expand Up @@ -300,39 +307,85 @@ wildReturn g (symv@SymV{}) = [ v | v@TVar{} ← values (U.freeTauTVars [] TreeM
wildReturn _ _ = []


wrapIRMethod :: Global -> JExpr -> JType -> SymInfo8 -> String -> String -> Symbol -> JDecl
wrapIRMethod g this irjt nativsi nativnm fldnm fldsym =
let nativargs = argDefs attrFinal (nativsi.{ argSigs <- tail, argJTs <- tail }) (getArgs g)
fldstri = case fldsym.strsig of
Strictness.S xs -> tail xs
_ -> []
-- how to detect strictness of result value?
retstrict = all (_.isStrict) fldstri
-- retlazy = if not (null fldstri) then (last fldstri).isStrict else False
callit jex = JInvoke { args = [], jex = JExMem { jex, name = "call", targs = [] } }
call = if retstrict then id else callit
--- Generates a method that overrides an interface method and wraps a call to an IR method.
---
--- Parameters:
--- - global
--- - The expression of the instance of the IR (i.e. the receiver).
--- - The Java type of the IR.
--- - The symbol of the invoked IR method.
--- - The name of the invoked IR method.
--- - The symbol information on the native member function.
--- - The "java item" of the native member function.
-- TODO make sure that the every field of an IR is 1-or-more arity functions beforehand
wrapIRMethod :: Global -> JExpr -> JType -> Symbol -> String -> SymInfo8 -> String -> JDecl
wrapIRMethod g this irjt fldsym fldnm nativsi nativnm =
let -- collect the FormalArgs of the native member function, excluding the receiver
nativargs = argDefs attrFinal (nativsi.{ argSigs <- tail, argJTs <- tail }) (getArgs g)
-- the tau of the result type of the IR field
fldrettau = fst $ U.returnType fldsym.typ.rho
-- the return type of the overridden method
methretjt = case unST fldrettau of
Just (_, a)
| isJust $ U.isUnit a -> nativ "void" []
| otherwise -> tauJT g a
Nothing -> nativsi.returnJT
-- force an expression. if it is an ST, run it after that
call = (if isST then tstRun (boxedv methretjt) else id) . callit where
isST = isJust $ unST fldrettau
-- add ".call()" after jex
callit jex = JInvoke { args = [], jex = JExMem { jex, name = "call", targs = [] } }
-- box JType, but box void into Short
boxedv (v@Nativ{typ="void"}) = boxed (v.{typ = "short"})
boxedv jt = boxed jt
-- "return" if the return type is not the primitive void
ret = if isVoid methretjt then JEx else JReturn where
isVoid Nativ{typ="void"} = true
isVoid _ = false
in
JMethod
{ attr = attrs [JPublic]
, gvars = []
, jtype = nativsi.returnJT
, jtype = methretjt
, name = nativnm
, args = nativargs
, body = JBlock $ pure $ JReturn $ call $ invokeIR irjt fldnm this $
zipWith (\(_, _, _, x) s -> StriArg x s.isStrict) nativargs fldstri
, body = JBlock $ pure $ ret $ call $ invokeIR irjt fldnm this $
map (\(_, _, _, x) -> x) nativargs
}

data StriArg = StriArg { name :: String, strict :: Bool }

striArgExpr :: StriArg -> JExpr
striArgExpr (StriArg{name, strict}) =
let thunkjt = Ref { jname = JName { qual = "", base = "Thunk" }, gargs = [] } -- TODO use the proper JType
lazy s = JInvoke { args = [JAtom s], jex = JStMem { jt = thunkjt, name = "lazy", targs = [] } }
--- Wraps an expression into Thunk.lazy, without generics arguments:
--- > Thunk.lazy(expr)
asLazy :: JExpr -> JExpr
asLazy expr =
Copy link
Member Author

Choose a reason for hiding this comment

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

This is slightly different from frege.compiler.gen.java.Common.lazyJX. This function doesn't take a JType. A java compiler can infer the generics of Thunk.lazy from its argument, so there isn't a need to emit one, or is it?

Copy link
Member

Choose a reason for hiding this comment

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

That's probably so, however, I adopted the policy to not rely on Java type inference if at all possible, as the frege compiler should know the desired types anyway.
Or, to put it differently, if we don't know the types, then we might need to re-consider our code generation model.

Therefore, I prefer having everything explicit in general.

That being said, it is possible that we can avoid some clutter here and there in code generation primitives like "lazyJX". But then we should adopt this in general, not just for some code.

let thunkjt = Ref { jname = JName { qual = "", base = "Thunk" }, gargs = [] }
lazy = JInvoke { args = [expr], jex = JStMem { jt = thunkjt, name = "lazy", targs = [] } }
in
if strict then JAtom name else lazy name
lazy

invokeIR :: JType -> String -> JExpr -> [StriArg] -> JExpr
invokeIR jt name this args = JInvoke
{ args = this : map striArgExpr args
, jex = JStMem { jt, name, targs = [] }
--- Invokes a method of an implementation record.
---
--- Parameters:
--- - The Java type of the IR.
--- - The name of the invoked IR method.
--- - The expression of the instance of the IR.
--- - The arguments of the invoked IR method, excluding the receiver of the implemented Java class.
invokeIR :: JType -> String -> JExpr -> [String] -> JExpr
invokeIR jt name irthis args = JInvoke
{ args = irthis : asLazy (JAtom "this") : map (asLazy . JAtom) args
, jex = JStMem { jt = rawType jt, name, targs = [] }
}

--- Runs an expression of type ST/IO.
---
--- Generated code should look like:
--- > PreludeBase.TST.<retjt>performUnsafe(jex).call()
tstRun :: JType -> JExpr -> JExpr
tstRun retjt jex =
let call jex = JInvoke { args = [], jex = JExMem { jex, name = "call", targs = [] } }
run retjt = JStMem
{ jt = Ref { jname = JName { qual = "PreludeBase", base = "TST" }, gargs = [] }
, name = "performUnsafe"
, targs = [retjt]
}
in
call $ JInvoke { jex = run retjt, args = [jex] }
Loading