GHC
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Incorporating Kinds Are Calling Conventions ## Overall goal Extend Core so that * The kind of a type expresses its arity (as well as levity and representation, which it does now). * Two lambdas: one "old" lamda, and a new arity-aware one; similarly two arrow type constructors `(->)` and `(~>)`. ## Status **Shant's [google doc](https://docs.google.com/document/d/1TJjZL3qjq3fa5IPP2Am5Gf82vd7yoaQivUGnvdW-N1o/edit)** * Question: long term, do we want the RuntimeInfo structure to be a pair? Maybe yes... convenient when you want to be polymorphic in both. * Question: do we want to expose `RuntimeInfo` to users, or make it abstract, with accessor functions? ## Notes * `data RuntimeRepInfo` avoids the need for a big linear case. But for now we put up with the big linear case. ## Overview Example for the arity WW transformation: ```hs map :: (a->b) -> [a] -> [b] map1 :: (a~>b) ~> [a] ~> [b] map1 = \~f \~xs. case xs of (x:xs) -> f x : map1 f xs [] -> [] map f xs = map1 (\~x. f x) xs ``` NB: Not only the outer (first-order) level of lambdas of the worker is squiggled, but also the inner (second-order) level. How to encode the type of ~> within GHC? With -XLinearTypes, we currently have ```hs Int %m -> Int type Multiplicity = Type data Type = TyVarTy TyVar | FunTy Multiplicity Squiggle Type Type | TyConApp TyCon [Type] | ForAllTy TyVar Type data Expr = Var Var | Lam Var Expr | App Expr Expr | Case ... | Let ... ``` every Id has an IdInfo, one field of which is Multiplicity Not a nice trick, but at least the same trick. *Digression: Shant and Paul's idea involved extending `TYPE` with an additional argument. They were annoyed by the fact that the compiler/Haskell's type system couldn't help with the refactoring by pointing out potential (runtime) errors in advance, and would probably long for something like classic GADT à la (STITCH, viz.)* ```hs data Type k where AppTy::: Type (k1->k2) -> Type k1 -> Type k2 ``` ## What we have Shant then gave a rough account of what he did. Basically, he replaced `RuntimeRep` with `RuntimeInfo` below: ```hs -- in GHC.Types.RepType: data RuntimeRep = VecRep VecCount VecElem -- ^ a SIMD vector type | TupleRep [RuntimeRep] -- ^ An unboxed tuple of the given reps | SumRep [RuntimeRep] -- ^ An unboxed sum of the given reps | BoxedRep Levity -- ^ boxed; represented by a pointer | IntRep -- ^ signed, word-sized value ...etc... type Levity = Type data Levity = Lifted | Unlifted data RuntimeInfo = RInfo {rep :: RuntimeRep, conv :: CallingConv} data CallingConv = ConvEval | ConvCall [RuntimeRep] data RuntimeRep = .... | BoxedRep Levity | ... ``` #### `RuntimeInfo` permits illegal states As Sebastian already implied in a mail he sent, he was a bit confused why the proposed `RuntimeInfo` allows illegal states such as `RInfo IntRep (ConvCall [AddrRep])`. Paul brought up the need to want to define types that are rep-poly but not calling-conv-poly and vice versa. It would be good to have examples here for that. Paul also said there are some in the paper, which we can probably just copy here. ##### Encoding 1: `RuntimeRep`-inside-of-`ConvEval` Suppose we have instead ```hs TYPE :: CallingConv -> Type data CallingConv = ConvEval RuntimeRep | ConvCall [CallingConv] data RuntimeRep = ... as today ... ``` Counter-example `Array#` from section 4.4: ```hs Array# :: forall lev. TYPE (ConvEval (BoxedRep lev)) -> TYPE (ConvEval UnliftedRep) ``` This wouldn't allow us to store `Int ~> Int :: TYPE (ConvCall [ConvEval LiftedRep])` in the array. ##### Encoding 2: `CallingConv`-instead-of-`BoxedRep` Suppose we have instead ```hs data RuntimeRep = ... | BoxedRep CallingConv ... ``` Counter-example `revapp` from section 4.5: ```hs revapp :: forall conv (t1 :: TYPE (BoxedRep conv)) rep (t2 :: TYPE rep). t1 ~> (t1 ~> t2) ~> t2 revapp x f = f x ``` Suppose this type-checks. Then, `revapp 23 (g :: Int ~> Int ~> Int) 42` type-checks. But so should `App (revapp 23 (h :: Int ~> {Int ~> Int}) 42`! The same generated code for `revapp` has to eval its body expr `f x` in the latter case and apply it in the former. SG: Feel free to improve explanation Previously, we could assume that all GC'd pointers can be entered/eval'd (even if that is a no-op). With ~>, not so much, it seems. Paul remarks that we then can't represent error: ``` error :: forall (r::RuntimeRep). String -> TYPE r ``` But now we can't tell the calling convention of the result type. (Simon asks: do we care?) ##### Encoding 2+: But what if we *could* eval ~> types? Suppose we have Encoding 2. Our issue with `revapp` was that we couldn't generate code for its body, `f x`. But all the information we seek for dynamic dispatch is right there! E.g., knowing that the arg `f` has type `t1 ~> t2`, we see that its arity is `BoxedRep conv,arity(rep)`. Since we know that `revapp` will be passed `arity(rep)` many args, this is enough to do a saturated call to `f`! Like this (pretty much a [generic apply](https://gitlab.haskell.org/ghc/ghc/-/wikis/commentary/rts/haskell-execution/function-calls#generic-apply)): 1. Look at the info pointer of `f` ("eval", as in eval/apply), find out how many args it takes (this will usually be encoded in the tagged pointer of `f`). Let's say the answer is 10. Then `revapp` will have 11 args. 2. Put `x` in arg position 1 (it's already there). 3. Shift arguments 3 to 11 down by one (so that they are in arg positions 2 to 10). So R3 -> R2, R4 -> R3, ..., adjust stack. In general (for other functions than `revapp`), we might also have to be able to "shift up". 4. Jump to the code for `f` ("apply") Note how arity polymorphism ended up as a generic apply function. It's a bit curious how `arity(rep)` can't be erased and ends up in the info table pointer/pointer tag, but isn't that exactly the mechanism we already have in GHC today? Arguably, this would probably need changing the semantics of $\mathcal{ML}$ to account for generic application/runtime information... *Paul:* Instead, you can always get the functionality proposed in encoding 2+ by using a `->` arrow instead of `~>`. The `->` arrow: 1. Can be used with *any* argument and return types: ```hs (->) :: forall (r1 :: RuntimeInfo) (r2 :: RuntimeInfo). TYPE r1 -> TYPE r2 -> Type ``` In other words, there is no need to know the calling convention of `b` in `a -> b`. 2. Falls back to a dynamic calling convention when one cannot be inferred from its context. For example, we can lookup the definition of `(+)` to find that it's arity is 2, and statically compile `1 + 2` as a binary function call. In contrast, the call `f x` inside `revapp x f` is to a lambda-bound `f`; we cannot look up the definition of `f` and so the calling convention of `f x` is checked dynamically. This lets us write a definition of `revapp` that does not statically track the calling convention of `b` in its type & kind: ```hs revapp :: forall (cc :: CallingConv) (ri :: RuntimeInfo) (a :: TYPE ('RInfo 'LiftedRep cc)) (b :: TYPE ri). a ~> (a -> b) ~> (# #) -> b :: TYPE ('RInfo 'UnliftedRep ('CallConv ['LiftedRep, 'LiftedRep])) revapp x f _ = f x ``` The kind of `revapp` says its calling convention takes exactly two (lifted) pointer arguments, so all calls `revapp x f` will be statically compiled to a single binary call without dynamic arity checks. However, the higher-order argument is `f :: a -> b :: Type`, so the call `f x` cannot be statically compiled to a unary call (or any other calling convention). Instead, the calling convention of `f` is checked at runtime to see if it is under-/over-/perfectly-saturated. This dynamic call to `f` is triggered by the final application of the empty tuple argument in `revapp x f (# #)`. Without the final empty argument, we have the closure `revapp x f :: (# #) -> b :: TYPE ('RInfo ('LiftedRep 'EvalConv))` whose kind explicitly says that it can be evaluated. Note that `f :: a -> b` has a completely unknown arity at compile time; it might even have arity 0 if it is a thunk that needs to be evaluated. But what if we want to say in the types that `f` takes *at least* one argument, but maybe more? We can use the same encoding as in the definition of `revapp` itself, to accept the first argument of type `a` statically and trigger the dynamic check for more arguments in a separate step using a `->` arrow with an empty argument. This is done like so: ```hs revapp :: forall (cc :: CallingConv) (ri :: RuntimeInfo) (a :: TYPE ('RInfo 'LiftedRep cc)) (b :: TYPE ri). a ~> (a ~> (# #) -> b) ~> (# #) -> b :: TYPE ('RInfo 'UnliftedRep ('CallConv ['LiftedRep, 'LiftedRep])) revapp x f = f x ``` Now the call to `f x` is done statically as one step, which returns a closure that may ask for more arguments. *Note:* The type `(# #) -> b` is interchangable with the closure type `{b}` from the ICFP'20 paper. The two type constructors can be defined in terms of one another (with the help of empty tuples and the primitive function arrow `~>`). Taking `a -> b` as primitive we have: ```hs type {b} = (# #) -> b ``` Taking `{b}` as primitive we have: ```hs type a -> b = {a ~> b} ``` --- Then Shant showcased how he extended the `typePrimRep` function to turn the `RuntimeInfo` into the new `PrimInfo`. Context (for the status quo in GHC): ```hs typePrimRep :: HasDebugCallStack => Type -> [PrimRep] typePrimRep ty = kindPrimRep (typeKind ty) kindPrimRep :: Kind {- = TyConApp TYPE [rr] -} -> [PrimRep] kindPrimRep (TyConApp tc [rr]) = runtimeRepPrimRep rr kindPrimrep _ = panic where arg to be of form (TYPE rr) runtimeRepPrimRep :: Type {- :: RuntimeRep -} -> [PrimRep] runtimeRepPrimRep (TyConApp tc tys) | tc == intRepTyCon = [IntRep] | tc == wordRepTyCon = [WordRep] | tc == vecRepTyCon = map runtimeRepPrimRep tys ... | otherwise = panic runtimeRepPrimRep :: HasDebugCallStack => SDoc -> Type -> [PrimRep] runtimeRepPrimRep doc rr_ty | Just rr_ty' <- coreView rr_ty = runtimeRepPrimRep doc rr_ty' | TyConApp rr_dc args <- rr_ty , RuntimeRep fun <- tyConRuntimeRepInfo rr_dc = fun args | otherwise = pprPanic "runtimeRepPrimRep" (doc $$ ppr rr_ty) ``` Simon argued that the impl could simply discard the calling conv ```hs kindPrimRep (TyConApp tc [_, rr]) = runtimeRepPrimRep rr ``` for the MVP, where code generation is not affected yet at all. Afterwards, we hunted down the reason for a bug in Shant's impl relating to `pcPrimRep` and concluded the call. # Call on 2021-05-04 We gave Richard an introduction and then focused on the kind errors relating to unboxed tuples. ## Specification for unboxed tuples types and kinds Richard nudged us into writing out the kind of the unboxed pair type constructor and the type of the unboxed pair data constructor, as follows: ```hs -- Type constructor type (# , #) :: forall (r1 :: RuntimeRep) (r2 :: RuntimeRep) (v1 :: CallingConv) (v2 :: CallingConv). TYPE (RInfo r1 v1) -> TYPE (RInfo r2 v2) -> TYPE (RInfo (TupleRep [r1, r2]) ConvEval) -- Data constructor (# , #) :: forall (r1 :: RuntimeRep) (r2 :: RuntimeRep) (v1 :: CallingConv) (v2 :: CallingConv). (a :: TYPE (RInfo r1 v1)) (b :: TYPE (RInfo r2 v2)). a -> b -> (# , #) r1 r2 v1 v2 a b ``` NB: `TupleRep` takes a list of `RuntimeRep`, not `RuntimeInfo`. Richard was sure that the kind errors that Shant was experiencing would go away if the type and data constructors were kinded/typed according to this spec. ## Discussion: Kinds are (could be) STG's types Meta-point by Sebastian: think of the passage from om Core to STG, as moving `x :: ty :: ki` to `x :: ki`. That is, we forget all the *type* distinctions, retaining only *kind* distinctions. Kinds classify types, which classify values; so kinds also classify values, but with a coarser mesh. Sebastian pointed out that by only storing `RuntimeRep` in the field of `TupleRep`, we thus lose STG type/kind safety for examples like the following (assuming the `x :: ty :: ki` to `x :: ki` move for STG "types"): ```hs f -- :: Int# ~> Int# in Core, but the following kind in STG :: TYPE (RInfo (BoxedRep Unlifted) (ConvCall [IntRep])) f x = ... y -- :: Int# in Core, but the following kind in STG :: TYPE (RInfo IntRep ConvEval) y = ... ... case (#,#) f y :: TYPE (RInfo (TupleRep [BoxedRep Unlifted,IntRep]) ConvEval) of (#,#) (g :: TYPE (RInfo (BoxedRep Unlifted) (ConvCall [IntRep,IntRep]))) -- this is the bad boy ^^^^^^ (z :: TYPE (RInfo IntRep ConvEval)) -> g z 42# ``` Here, we call the arity 1 function `f` with arity 2. There's no way to catch this in the type system of STG, as the `CallingConv` the TYPE of `g` can be chosen freely. So well-typedness doesn't guarantee progress; the above expression is well-typed but stuck at runtime. Sebastian can see how the kind system is not expressive enough in more cases than just `TupleRep`: 1. Why doesn't `ConvCall` specify a returned `RuntimeInfo`? 2. Why does the arity only take `RuntimeRep` and not full `RuntimeInfo`? It's questionable if it's worth fixing `ConvCall`. So if we don't fix it, why fix `TupleRep`? SG argues that fixing `ConvCall` will make more valid programs we output typeable, but it will not impact progress, while fixing `TupleRep` does. ## A small problem with having levity in the rep field. (Our stop-gap.) **Example** (moving levity over to `ConvEval`, from `BoxedRep`): ``` hs revapp :: forall (r :: RuntimeRep) (v :: CallingConv) (l :: Levity) (a :: TYPE (RInfo BoxedRep v)) (b :: TYPE (RInfo r (ConvEval l))). a ~> (a ~> b) ~> b :: TYPE (RInfo BoxedRep (ConvCall [BoxedRep, BoxedRep])) revapp x f = f x ``` (NB: a *caller* of revapp needs to know levity of x, to know whether to use call-by-value or call-by-need; but revapp *itself* doesn't need to know.) But if we move levity into the representation field (our stop-gap), we can't get the above levity polymorphism. Let's try! ``` revapp :: forall (r :: RuntimeRep (v :: CallingConv) (l :: Levity) (a :: TYPE (RInfo (BoxedRep l) v)) (b :: TYPE (RInfo r ConvEval)). a ~> (a ~> b) ~> b :: TYPE (RInfo (BoxedRep Unlifted) (ConvCall [BoxedRep l, BoxedRep Unlifted])) -- ^^^^ no no no revapp x f = f x ``` See also https://gitlab.haskell.org/ghc/ghc/-/issues/19487 for why SG thinks the `l` in the kind could simply be replaced by `UnknownLevity`. SG thinks that by having `TupleRep` list `RuntimeInfo` instead of `RuntimeRep` we might get similar problems (example needed). ## Reviewing Shant's code that builds the types and kinds of unboxed tuple constructors ```hs mk_tuple Unboxed arity = (tycon, tuple_con) where tycon = mkTupleTyCon tc_name tc_binders tc_res_kind tc_arity tuple_con UnboxedTuple flavour -- See Note [Unboxed tuple RuntimeRep vars] in GHC.Core.TyCon -- Kind: forall (k1:RuntimeRep) (k2:RuntimeRep). TYPE k1 -> TYPE k2 -> # tc_binders = mkTemplateTyConBinders (replicate arity runtimeInfoTy) (\ks -> map tYPE ks) tc_res_kind = rr_tys tc_arity = arity * 2 flavour = UnboxedAlgTyCon $ Just (mkPrelTyConRepName tc_name) dc_tvs = binderVars tc_binders (rr_tys, dc_arg_tys) = splitAt arity (mkTyVarTys dc_tvs) tuple_con = pcDataCon dc_name dc_tvs dc_arg_tys tycon boxity = Unboxed modu = gHC_PRIM tc_name = mkWiredInName modu (mkTupleOcc tcName boxity arity) tc_uniq (ATyCon tycon) BuiltInSyntax dc_name = mkWiredInName modu (mkTupleOcc dataName boxity arity) dc_uniq (AConLike (RealDataCon tuple_con)) BuiltInSyntax tc_uniq = mkTupleTyConUnique boxity arity dc_uniq = mkTupleDataConUnique boxity arity unboxedTupleSumKind tc rr_tys = tYPE $ mkTyConApp runtimeInfoDataConTyCon [(mkTyConApp tc [mkPromotedListTy runtimeRepTy rr_tys]), convEvalDataConTy] ``` Problem: `unboxedTupleSumKind` expects `rr_tys` to have kind `RuntimeRep`. The `rr_tys` declared in `mk_tuple` probably has kind `RuntimeInfo`. There are a few other call sites of `unboxedTupleSumKind` in the type checker that should be checked. ## Matching Unboxed Tuple implemementation with paper spec ``` hs -- mk_tuple creates the type AND data constructors mk_tuple Unboxed arity = (tycon, tuple_con) where tycon = mkTupleTyCon tc_name tc_binders tc_res_kind tc_arity tuple_con UnboxedTuple flavour tc_binders = mkTemplateTyConBinders (take (arity * 2) $ cycle [runtimeRepTy, callingConvTy]) (\ris -> map (\(r,c) -> tYPE $ rInfo r c) (take2 ris)) tc_res_kind = unboxedTupleKind ri_tys tc_arity = arity * 3 flavour = UnboxedAlgTyCon $ Just (mkPrelTyConRepName tc_name) dc_tvs = binderVars tc_binders (ri_tys, dc_arg_tys) = splitAt (arity * 2) (mkTyVarTys dc_tvs) tuple_con = pcDataCon dc_name dc_tvs dc_arg_tys tycon ... ``` ``` hs -- typechecking unboxed tuples finish_tuple :: HsType GhcRn -> TupleSort -> [TcType] -- ^ argument types -> [TcKind] -- ^ of these kinds -> TcKind -- ^ expected kind of the whole tuple -> TcM TcType finish_tuple rn_ty tup_sort tau_tys tau_kinds exp_kind = do traceTc "finish_tuple" (ppr tup_sort $$ ppr tau_kinds $$ ppr exp_kind) case tup_sort of ... UnboxedTuple -> do let tycon = tupleTyCon Unboxed arity tau_reps = map kindRep tau_kinds tau_ccs = map kindConv tau_kinds reps_ccs = interleave tau_reps tau_ccs -- See also Note [Unboxed tuple RuntimeRep vars] in GHC.Core.TyCon arg_tys = reps_ccs ++ tau_tys res_kind = unboxedTupleKind reps_ccs checkTupSize arity check_expected_kind (mkTyConApp tycon arg_tys) res_kind where arity = length tau_tys check_expected_kind ty act_kind = checkExpectedKind rn_ty ty act_kind exp_kind ``` ``` hs type Type = TYPE (RInfo LiftedRep ConvEval) TYPE :: RuntimeInfo -> Type data RuntimeInfo = RInfo RuntimeRep CallingConv ``` ``` hs -- Type constructor for (#,#) (#,#) :: forall (k0 :: RuntimeRep) (k1 :: CallingConv) (k2 :: RuntimeRep) (k3 :: CallingConv). TYPE (RInfo k0 k1) -> TYPE (RInfo k2 k3) -> TYPE ('RInfo ('TupleRep '[k0, k2]) 'ConvEval) -- data constructor for (#,#) (#,#) :: forall (k0 :: RuntimeRep) (k1 :: CallingConv) (k2 :: RuntimeRep) (k3 :: CallingConv) (a :: TYPE ('RInfo k0 k1)) (b :: TYPE ('RInfo k2 k3)). a -> b -> (#,#) k0 k1 k2 k3 a b ``` OR! Alternative kind for (#,#): ``` -- Type constructor for (#,#) (#,#) :: forall (r1 :: RuntimeInfo) (r2 :: RuntimeInfo). TYPE r1 -> TYPE r2 -> TYPE ('RInfo ('TupleRep '[GetRep r1, GetRep r2]) 'ConvEval) -- data constructor for (#,#) (#,#) :: forall (r1 :: RuntimeInfo) (r2 :: RuntimeInfo) (a :: TYPE r1) (b :: TYPE r2). a -> b -> (#,#) r1 r2 a b ``` The kind for `error` in terms of `'RInfo` constructor: ``` error :: forall (r::RuntimeRep) (a :: TYPE ('RInfo r 'ConvEval)). String -> a ``` ``` f :: forall (rr::RuntimeInfo) (a::TYPE rr). (GetConv rr ~ ConEval) => f s = errror (s++ s) ``` ``` [W] RInfo a b ~ c -- c::RuntimeInfo is a skolem ``` Then you might hope solve this by using the eta rule: `c ~ RInfo (GetRep c) (GetConv c)` ``` -- this error was caused by tyVars of RuntimeRep -- being incorrectly defaulted getRuntimeInfo (#,#) :: TYPE ('RInfo ('RInfo ('TupleRep '[]) 'ConvEval) ('RInfo 'LiftedRep 'ConvEval)) -> TYPE ('RInfo (State# RealWorld) a_ae5) -> TYPE ('RInfo ('TupleRep '[ 'RInfo ('TupleRep '[]) 'ConvEval, State# RealWorld]) 'ConvEval) Call stack: CallStack (from HasCallStack): callStackDoc, called at compiler/GHC/Utils/Panic.hs:181:37 in ghc:GHC.Utils.Panic pprPanic, called at compiler/GHC/Core/Type.hs:2123:18 in ghc:GHC.Core.Type ``` ``` hs defaultTyVar :: Bool -- True <=> please default this kind variable to * -> TcTyVar -- If it's a MetaTyVar then it is unbound -> TcM Bool -- True <=> defaulted away altogether defaultTyVar default_kind tv ... | isRuntimeRepVar tv -- Do not quantify over a RuntimeRep var -- unless it is a TyVarTv, handled earlier = do { traceTc "Defaulting a RuntimeRep var to LiftedRep" (ppr tv) -- was defaulting to (RInfo LiftedRep ConvEval) rather than -- just LiftedRep ; writeMetaTyVar tv liftedRepTy ; return True } ``` ``` (r :: RuntimeInfo) (TYPE r) (TYPE ('RInfo rr cc)) ``` ``` hs newOpenTypeKind :: TcM TcKind newOpenTypeKind = do { rr <- newFlexiTyVarTy runtimeRepTy ; cc <- newFlexiTyVarTy callingConvTy ; return (tYPE $ rInfo rr cc) } ``` These changes fixed the issues we were having in typechecking unboxed tuples, and now we have new errors! ``` libraries/ghc-prim/GHC/Magic.hs:101:24: error: • Couldn't match kind ‘q’ with ‘'GHC.Types.RInfo t2 t3’ Expected a type, but ‘a’ has kind ‘TYPE q’ ‘q’ is a rigid type variable bound by an explicit forall (q :: RuntimeInfo) (r :: RuntimeInfo) (a :: TYPE q) (b :: TYPE r) at libraries/ghc-prim/GHC/Magic.hs:99:20 • In the type signature: oneShot :: forall (q :: RuntimeInfo) (r :: RuntimeInfo) (a :: TYPE q) (b :: TYPE r). (a -> b) -> a -> b | 101 | (a -> b) -> a -> b | ^ libraries/ghc-prim/GHC/Magic.hs:101:29: error: • Couldn't match kind ‘r’ with ‘'GHC.Types.RInfo t4 t5’ Expected a type, but ‘b’ has kind ‘TYPE r’ ‘r’ is a rigid type variable bound by an explicit forall (q :: RuntimeInfo) (r :: RuntimeInfo) (a :: TYPE q) (b :: TYPE r) at libraries/ghc-prim/GHC/Magic.hs:99:39 • In the type signature: oneShot :: forall (q :: RuntimeInfo) (r :: RuntimeInfo) (a :: TYPE q) (b :: TYPE r). (a -> b) -> a -> b | 101 | (a -> b) -> a -> b | ^ libraries/ghc-prim/GHC/Magic.hs:115:38: error: • Couldn't match kind ‘r’ with ‘'GHC.Types.RInfo t0 t1’ Expected a type, but ‘o’ has kind ‘TYPE r’ ‘r’ is a rigid type variable bound by an explicit forall (r :: RuntimeInfo) (o :: TYPE r) at libraries/ghc-prim/GHC/Magic.hs:114:19 • In the type signature: runRW# :: forall (r :: RuntimeInfo) (o :: TYPE r). (State# RealWorld -> o) -> o | 115 | (State# RealWorld -> o) -> o | ``` ``` hs oneShot :: forall (q :: RuntimeInfo) (r :: RuntimeInfo) (a :: TYPE q) (b :: TYPE r). (a -> b) -> a -> b oneShot f = f oneShot2 :: forall (rep1 :: RuntimeRep) (cc1 :: CallingConv) (rep2 :: RuntimeRep) (cc2 :: CallingConv) (a :: TYPE (RInfo rep cc)) (b :: TYPE (RInfo rep2 cc2)). (a -> b) -> a -> b ``` Cannot write `oneShot = oneShot2` ```hs funTyCon :: TyCon funTyCon = mkFunTyCon funTyConName tc_bndrs tc_rep_nm where -- See also unrestrictedFunTyCon tc_bndrs = [ mkNamedTyConBinder Required multiplicityTyVar , mkNamedTyConBinder Inferred runtimeInfo1TyVar , mkNamedTyConBinder Inferred runtimeInfo2TyVar ] ++ mkTemplateAnonTyConBinders [ tYPE runtimeInfo1Ty , tYPE runtimeInfo2Ty ] tc_rep_nm = mkPrelTyConRepName funTyConName (->) :: forall (m:Multiplicty) (r1 :: RuntimeInfo) (r2::RuntimeInfo). TYPE r1 -> TYPE r2 -> Type ``` ``` hs kindRep_maybe :: HasDebugCallStack => Kind -> Maybe Type kindRep_maybe kind | TyConApp tc [arg] <- coreFullView kind , tc `hasKey` tYPETyConKey , TyConApp rinfo [rep, conv] <- coreFullView arg , rinfo `hasKey` runtimeInfoDataConKey = Just rep | otherwise = Nothing ``` ``` hs tc_fun_type :: TcTyMode -> HsArrow GhcRn -> LHsType GhcRn -> LHsType GhcRn -> TcKind -> TcM TcType tc_fun_type mode mult ty1 ty2 exp_kind = case mode_tyki mode of TypeLevel -> do { arg_k <- newOpenTypeKind ; res_k <- newOpenTypeKind ; ty1' <- tc_lhs_type mode ty1 arg_k ; ty2' <- tc_lhs_type mode ty2 res_k ; mult' <- tc_mult mode mult ; checkExpectedKind (HsFunTy noExtField mult ty1 ty2) (mkVisFunTy mult' ty1' ty2') liftedTypeKind exp_kind } KindLevel -> -- no representation polymorphism in kinds. yet. do { ty1' <- tc_lhs_type mode ty1 liftedTypeKind ; ty2' <- tc_lhs_type mode ty2 liftedTypeKind ; mult' <- tc_mult mode mult ; checkExpectedKind (HsFunTy noExtField mult ty1 ty2) (mkVisFunTy mult' ty1' ty2') liftedTypeKind exp_kind } ``` ## GetRep and GetConv in ghc-prim/Types.hs ```hs type family GetRep (ri :: RuntimeInfo) :: RuntimeRep where GetRep ('RInfo rep _) = rep type family GetConv (ri :: RuntimeInfo) :: CallingConv where GetConv ('RInfo _ conv) = conv ``` In GHC/Core/Type.hs ```hs kindRep_maybe :: HasDebugCallStack => Kind -> Maybe Type kindRep_maybe kind | TyConApp tc [arg] <- coreFullView kind , tc `hasKey` tYPETyConKey = Just $ getRep arg | otherwise = Nothing -- | Given a type (RInfo rr cc) extracts its RuntimeRep -- classifier rr. getRep :: Type -> Type getRep rinfo | TyConApp rinfo [rep, _] <- coreFullView rinfo , rinfo `hasKey` runtimeInfoDataConKey = rep | otherwise = mkTyConApp getRepTyCon [rinfo] kindConv_maybe :: HasDebugCallStack => Kind -> Maybe Type kindConv_maybe kind | TyConApp tc [arg] <- coreFullView kind , tc `hasKey` tYPETyConKey = Just $ getConv arg | otherwise = Nothing -- | Given a type (RInfo rr cc) extracts its CallingConv -- classifier cc. getConv :: Type -> Type getConv rinfo | TyConApp rinfo [_, conv] <- coreFullView rinfo , rinfo `hasKey` runtimeInfoDataConKey = conv | otherwise = mkTyConApp getConvTyCon [rinfo] ``` ```hs coreView ty@(TyConApp tc tys) | Just (tenv, rhs, tys') <- expandSynTyCon_maybe tc tys = Just (mkAppTys (substTy (mkTvSubstPrs tenv) rhs) tys') -- This equation is exactly like tcView -- At the Core level, Constraint = Type -- See Note [coreView vs tcView] | isConstraintKindCon tc = ASSERT2( null tys, ppr ty ) Just liftedTypeKind | tc `hasKey` getRepTyConKey = Just $ getRep ty | tc `hasKey` getConvTyConKey = Just $ getConv ty where getRep ty | TyConApp getRepTyCon [rinfo] <- ty , TyConApp rinfo [rep, conv] <- rinfo = rep | otherwise = ty getConv ty | TyConApp getConvTyCon [rinfo] <- ty , TyConApp rinfo [rep, conv] <- rinfo = conv | otherwise = ty coreView _ = Nothing {-# INLINE coreFullView #-} coreFullView :: Type -> Type -- ^ Iterates 'coreView' until there is no more to synonym to expand. -- See Note [Inlining coreView]. coreFullView ty@(TyConApp tc _) | isTypeSynonymTyCon tc || isConstraintKindCon tc || tc `hasKey` getRepTyConKey || tc `hasKey` getConvTyConKey = go ty where go ty | Just ty' <- coreView ty = go ty' | otherwise = ty coreFullView ty = ty ``` ``` (GHC version 9.1.0.20210528: mkTyConKindRepBinds(TyConApp) GetRep GetRep ('RInfo ('TupleRep '[]) 'ConvEval) Call stack: CallStack (from HasCallStack): callStackDoc, called at compiler/GHC/Utils/Panic.hs:181:37 in ghc:GHC.Utils.Panic pprPanic, called at compiler/GHC/Tc/Instance/Typeable.hs:593:9 in ghc:GHC.Tc.Instance.Typeable ``` ```hs {-# INLINE tcView #-} tcView :: Type -> Maybe Type tcView ty@(TyConApp tc tys) | Just (tenv, rhs, tys') <- expandSynTyCon_maybe tc tys = Just (mkAppTys (substTy (mkTvSubstPrs tenv) rhs) tys') -- The free vars of 'rhs' should all be bound by 'tenv', so it's -- ok to use 'substTy' here. -- See also Note [The substitution invariant] in GHC.Core.TyCo.Subst. -- Its important to use mkAppTys, rather than (foldl AppTy), -- because the function part might well return a -- partially-applied type constructor; indeed, usually will! | tc `hasKey` getRepTyConKey , [TyConApp rinfo [rep, _conv]] <- tys , rinfo `hasKey` runtimeInfoDataConKey = Just rep | tc `hasKey` getConvTyConKey , [TyConApp rinfo [_rep, conv]] <- tys , rinfo `hasKey` runtimeInfoDataConKey = Just conv tcView _ = Nothing ``` ``` type T a = a->a ``` The type `T Int` is *equal (eqType) to* `Int -> Int` But for ``` type family F [a] = a -> a ``` the type `F [Int]` is *not* equal to `Int -> Int`. ``` f ty | Just ty' <- tcView ty = f ty' f (TyConApp tc tys) = ... ... ``` After inlining ``` f (TyConApp tc tys) | expandsyn = f ty' -- result of inlining tcview f (TyConApp tc tys) = ... -- the second line of the original f ``` # Alternative designs From the paper: ``` f :: forall c::CallingConv. (a :: TYPE (RR PtrRep c). blah ``` With type functions, keeping `RuntimeInfo` ``` f :: forall r::RuntimeInfo. (a :: TYPE r). (Rep r ~ PtrRep) => a -> blah f x = blah ``` Richard's version: ``` f :: forall r::RuntimeInfo. (a :: TYPE r). (FixedRep r PtrRep) => a -> blah f x = blah ``` ``` f1 :: forall a. a -> forall b. b -> blah f1 = blah f1_worker :: forall (# 0 a :: Type, _ :: a, 0 b :: Type, _ :: b #) -> blah{a,b} f1_worker (# _ , x , _ , y #) = blah f2 :: forall a b. a -> b -> blah f2 x y = blah f2_worker :: forall a b. (# a, b #) -> blah f2_worker (# x, y #) = blah ``` ## Uncurrying [Types are Calling Conventions](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/08/tacc-hs09.pdf) used unboxed tuples to encode "multiple args" https://www.microsoft.com/en-us/research/publication/making-a-faster-curry-with-extensional-types/, Section 8.1 Essentially we'd need a new uncurried arrow type ``` ty :: qauntifiers -> ty | ... quantifiers ::= /* empty */ | ty, quantifiers | (var::ty), quantifiers ``` and a similar term former ``` data Expr = ... | Lam [Var] Expr | App Expr [Expr] ``` ``` data T = MkT (Int -> Bool -> Int -> Int) f :: <Int Bool> -> Int -> Int f = \x y.\z. e -- SG: this takes two args (in R1 R2) and allocates -- and returns a closrue g = MkT f -- Ill typed g = MkF f_imp -- Well typed f_imp = \p. \q. \r. f p q r -- SG: This takes one argument (n R1) and allocates -- and returns a closure, of one argumetn... h x = case x of MkT t -> t 3 True p x = f x True $wzipWith :: (<a b> -> c) -> [a] -> [b] -> [c] $wzipWith f (x:xs) (y:ys) = f x y : zipwith f xs ys -- zipWith f xs ys -- here f is compiled to take 3 args! wurble :: (Int -> Int -> Int) -> Int -> Int wurble f = f 1 blah :: <Int Int> -> Int blah x y = x + y -- SG: This takes two arguments (in R1 and RT) and -- returns R1+R2 blah2 :: Int -> Int -> Int blah2 x y = x + y -- SG: This takes one arguments (in R1 and -- returns a closure blah3 :: <Int Int Int> -> Int blah3 x y z = x + y + z blah4 :: <Int Int> -> Int -> Int blah4 x y = \z. x + y + z garble :: (Int -> Int -> Int -> Int) -> Int -> Int garble f = f 1 2 garble' :: (<Int Int> -> Int -> Int) -> Int -> Int garble' f = f 1 2 -- How do we express that `f` only takes one argument? -- Like this? What is the difference between <Int> and Int? -- Surely it's rather a property of the arrow that follows <Int>. garble1 :: (<Int> -> Int -> Int) -> Int -> Int garble1 f = f 1 -- (wurble blah) and (wurble blah2) (\x. blah 1 x) :: Int -> Int $wzipWith :: (<a b> -> c) -> [a] -> [b] -> [c] $wzipWith f (x:xs) (y:ys) = f x y : zipwith f xs ys .... wurble blah ... case x of MkT ff -> let gg = ff 3 in map gg boozel ``` f's closure has an info table with (a) code for invocation with three args, (b) arity info saying "I need two groups of args" f_imp's closure has an info-table with (a) code for invocation (with three arguments) (b) arity info saying "I need three args" At the unknown call in `h`, we do a dynamic check, just as today. **Paul's claim (new to Simon)** Mixing static and dynamic arity checks (Section 7) is * compatible with the story in the paper, * but *not* compatible with uncurrying. We must keep the dynamic arity checks, else MkT becomes terribly inefficient, forcing the one-arg-at-a-time mechanism, even for arity-3 functions! Possible way to approach combined static/dynamic arity checks with currying/uncurrying: Use *two* arrow types: 1. The curried arrow type `a -> b`, which is dynamically checked for arity when called at runtime 2. The uncurried arrow type `<a1..an> -> b` which is not dynamically checked for arity at runtime when called. Note that there is a difference between the type `Int -> Int` versus the singleton uncurried arrow `<Int> -> Int`. ## GetRep and GetConv Simplification As per what was discussed in the previous meeting, we removed GetRep and GetConv simplification from ```coreView``` and put it off until code gen. As such we had to modify ```isKindLevPoly``` to see through the applications of ```GetRep``` and ```GetConv```. ```haskell= isKindLevPoly :: Kind -> Bool isKindLevPoly k = ASSERT2( isLiftedTypeKind k || _is_type, ppr k ) go k where ... go ty@(TyConApp tc tys) | Just rep <- simplifyRep ty = go rep | Just conv <- simplifyConv ty = go conv | otherwise = isFamilyTyCon tc || any go tys ... ``` Once that change was made, errors popped up in ```runtimeRepPrimRep``` due to unsimplified applications of GetRep ``` ghc-stage1: panic! (the 'impossible' happened) (GHC version 9.1.0.20210528: runtimeRepPrimRep tupleRepDataCon [GetRep ('RInfo 'WordRep 'ConvEval), GetRep ('RInfo 'WordRep 'ConvEval)] GetRep ('RInfo 'WordRep 'ConvEval) ``` In order to simplify these terms, we added the following clause to ```runtimeRepPrimRep``` ```haskell= runtimeRepPrimRep :: HasDebugCallStack => SDoc -> Type -> [PrimRep] runtimeRepPrimRep doc rr_ty | Just rr_ty' <- coreView rr_ty = runtimeRepPrimRep doc rr_ty' -- Clause for simplifying GetRep applications | Just rr_ty' <- simplifyRep rr_ty = runtimeRepPrimRep doc rr_ty' | TyConApp rr_dc args <- rr_ty , RuntimeRep fun <- tyConRuntimeRepInfo rr_dc = fun args | otherwise = pprPanic "runtimeRepPrimRep" (doc $$ ppr rr_ty) ``` ## runtimeRepPrimRep_maybe ```=haskell runtimeRepPrimRep :: HasDebugCallStack => SDoc -> Type -> [PrimRep] runtimeRepPrimRep doc rr_ty | Just p_reps <- runtimeRepPrimRep_maybe doc rr_ty = p_reps | otherwise = pprPanic "runtimeRepPrimRep" (doc $$ ppr rr_ty) runtimeRepPrimRep_maybe :: HasDebugCallStack => SDoc -> Type -> Maybe [PrimRep] runtimeRepPrimRep_maybe doc rr_ty | Just rr_ty' <- coreView rr_ty = runtimeRepPrimRep_maybe doc rr_ty' | Just rr_ty' <- simplifyRep rr_ty = runtimeRepPrimRep_maybe doc rr_ty' | TyConApp rr_dc args <- rr_ty , RuntimeRep fun <- tyConRuntimeRepInfo rr_dc = Just $ fun args | otherwise = Nothing ``` Our hope was also to be able to use ```runtimeRepPrimRep_maybe``` within ```isLiftedRuntimeRep``` and ```isUnliftedRuntimeRep```. The issue with this approach is in the handling of singleton Tuples. ```=Haskell runtimeRepPrimRep (TupleRep [LiftedRep]) = [LiftedRep] runtimeRepPrimRep LiftedRep = [LiftedRep] -- should be isLiftedRuntimeRep (TupleRep [LiftedRep]) = False -- with runtimeRepPrimRep_maybe approach isLiftedRuntimeRep (TupleRep [LiftedRep]) = True ``` ### The Solution An additional function which uses simplifyRep and returns a ```Maybe Levity```: ```Just lev``` in the case where the levity is concrete, and ```Nothing``` when it is polymorphic. ```=haskell data Levity = Lifted | Unlifted -- LIbrary: -- data RuntimeRep = LiftedRep | UnliftedRep | IntRep ... runtimeInfoLevity_maybe :: Type -> Maybe Levity -- (runtimeRepLevity_maybe (ty :: RuntimeInfo)) -- takes a type of kind RuntimeInfo, and returns -- (Just lev) if we can figure out its levity for certain, -- and Nothing if we can't (eg levity polymorphic) runtimeInfoLevity_maybe rinfo | Just (_, [rep, _conv]) <- splitTyConApp_maybe rinfo , Just (tc, _) <- splitTyConApp_maybe (simplifyRep rep `orElse` rep) = Just (if tc `hasKey` liftedRepDataConKey then Lifted else Unlifted) | otherwise = Nothing ``` We have quarantined ```simplifyRep``` inside ```RepType``` where ```runtimeRepPrimRep``` and ```runtimeInfoLevity_maybe``` are defined. The latter two are then exported and used within the lev-poly and isLifted checks. ```=haskell isRuntimeInfoLevPoly :: Type -> Bool isRuntimeInfoLevPoly ty | Nothing <- runtimeInfoLevity_maybe ty = True | otherwise = False isKindLevPoly :: Kind -> Bool isKindLevPoly k = ASSERT2( isLiftedTypeKind k || _is_type, ppr k ) go k where ... go ty@(TyConApp tc tys) | tc `hasKey` runtimeInfoDataConKey = isRuntimeInfoLevPoly ty | otherwise = isFamilyTyCon tc || any go tys ... ``` ``` isLiftedTypeKind :: Kind -> Bool isLiftedTypeKind kind = case kindInfo_maybe kind of Just rinfo -> isLiftedRuntimeInfo rinfo Nothing -> False isLiftedRuntimeInfo :: Type -> Bool isLiftedRuntimeInfo ty | Just Lifted <- runtimeInfoLevity_maybe ty = True | otherwise = False ``` ## Other Issue ``` TYPE ('RInfo ('SumRep '[GetRep ('RInfo 'LiftedRep 'ConvEval), GetRep ('RInfo 'UnliftedRep 'ConvEval)]) 'ConvEval) TYPE ('RInfo ('SumRep '[GetRep ('RInfo 'LiftedRep 'ConvEval), GetRep ('RInfo 'UnliftedRep 'ConvEval)]) 'ConvEval) LT ``` ``` (first kind) (GHC.Types.GetRep{(w) tc 3cU} (second kind) (GHC.Types.GetRep{tc r7T} ``` ```haskell simplifyRep :: Type -> Maybe Type simplifyRep rep | Just rinfo <- getRepArg_maybe rep , (TyConApp rinfo [rep', _]) <- rinfo = Just rep' | otherwise = Nothing ``` GHC.Types.Name ``` pprExternal debug sty uniq mod occ is_wired is_builtin | codeStyle sty = ppr mod <> char '_' <> ppr_z_occ_name occ -- In code style, always qualify -- ToDo: maybe we could print all wired-in things unqualified -- in code style, to reduce symbol table bloat? | debug = pp_mod <> ppr_occ_name occ <> braces (hsep [if is_wired then text "(w)" else empty, pprNameSpaceBrief (occNameSpace occ), pprUnique uniq]) ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully