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

DSL for module semantics #32

Merged
merged 50 commits into from
Jul 26, 2023
Merged

DSL for module semantics #32

merged 50 commits into from
Jul 26, 2023

Conversation

ShinWonho
Copy link

DSL for module semantics.
This PR depends on #30

Copy link
Collaborator

@rossberg rossberg left a comment

Choose a reason for hiding this comment

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

Cool, thanks! I have some comments, see below.

@@ -57,7 +57,7 @@ syntax resulttype hint(desc "result type") =
valtype*

syntax limits hint(desc "limits") =
`[u32 .. u32]
`[u32 .. u32?]
Copy link
Collaborator

Choose a reason for hiding this comment

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

FWIW, I dropped the option on purpose, since I figured it's simpler to just desugar an empty upper bound into 2^32-1 in the AST.

Comment on lines 206 to 207
-- if $table(z, x) = ti
-- if $grow_table(ti, n, ref) = ti'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
-- if $table(z, x) = ti
-- if $grow_table(ti, n, ref) = ti'
-- if $grow_table($table(z, x), n, ref) = ti'

z; ref (CONST I32 n) (TABLE.GROW x) ~> $with_tableinst(z, x, ti'); (CONST I32 $(|$table(z, x).ELEM|))
-- if $table(z, x) = ti
-- if $grow_table(ti, n, ref) = ti'
-- Tabletype_ok: |- ti'.TYPE : OK
Copy link
Collaborator

Choose a reason for hiding this comment

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

This condition should probably be moved to $grow_table, such that that becomes a self-contained abstraction.

Comment on lines 314 to 315
-- if $mem(z, 0) = mi
-- if $grow_memory(mi, n) = mi'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
-- if $mem(z, 0) = mi
-- if $grow_memory(mi, n) = mi'
-- if $grow_memory($mem(z, 0), n) = mi'

z; (CONST I32 n) (MEMORY.GROW) ~> $with_meminst(z, 0, mi'); (CONST I32 $(|$mem(z, 0).DATA| / (64 * $Ki)))
-- if $mem(z, 0) = mi
-- if $grow_memory(mi, n) = mi'
-- Memtype_ok: |- mi'.TYPE : OK
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment as above.

-- if elem = ELEM reftype expr* elemmode
-- if n = |expr*|
-- if elemmode = TABLE x instr*
-- Step : s; f; instr* (CONST I32 0) (CONST I32 n) (TABLE.INIT x i) (ELEM.DROP i) ~> s_new; f_new; epsilon
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh. The run functions are supposed to be auxiliary functions that produce a program to run (i.e., desugar active segments), not actually execute the instructions.

-- if $run_data((s_elem; f_elem), data*, 0) = s_res; f_res

def $instantiation(s, module, externval*) = (s_res, m)
-- if module = MODULE import* func* global* table* mem* elem* data* start export*
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we avoid duplicating the definition? Why would the approach from the spec not work?


;; Instantiation

def $instantiation(store, module, externval*) : (store, moduleinst)
Copy link
Collaborator

Choose a reason for hiding this comment

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

So, the instantiate (and similarly invoke) functions in the spec are meant to macro-expand into an actual instruction sequence, which then can be reduced separately by the usual means. This definition seems to be interleave expansion and execution and couple them together, which I'd rather like to avoid.

-- if m_res = $alloc_import(m_new, import'*, externval'*)


def $alloc_func(state, func*) : (store, funcaddr*)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here and below we are conflating the allocx functions with the allocx* liftings. Could we keep them separate? In particular, for the Latex spec, we will only want to include the allocx ones, and not write out each of the others.

-- if m_ex = m_im ++ { FUNC fa*, GLOBAL ga*, TABLE ta*, MEM ma*, ELEM ea*, DATA da*, EXPORT epsilon }
-- if xi* = $alloc_export(m_ex, export)*
-- if m_res = m_ex[.EXPORT = xi*]
-- if s_res = $replace_moduleinst(s_data, fa*, m_res)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see why you did it this way, but now the alloc_module functions is not actually allocating one module instance, but an arbitrary number of them. Can we change this so that there is only one module instance being created in the end?

Also, Conrad suggested we can avoid resorting to mutating immutable parts of the store and instead break the cycle by precomputing the function addresses and the module instance before creating the function instances.

Copy link
Author

Choose a reason for hiding this comment

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

I refined the module semantics. To reflect the aforementioned comments, I extended the DSL (you can see the detailed explanation below). This change requires updating the EL parser, IL validation and other components, and we're planning to update them. Before that, I'd like to know whether the updated DSL looks good enough.

Copy link
Author

Choose a reason for hiding this comment

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

I'm implementing this in dsl branch. It might be helpful to see the implementation to understand the extended syntax.

def $funcs(externval*) = [fa|FUNC fa <- externval]
def $globals(externval*) = [ga|GLOBAL ga <- externval]
def $tables(externval*) = [ta|TABLE ta <- externval]
def $mems(externval*) = [ma|MEM ma <- externval]
Copy link
Author

@ShinWonho ShinWonho Jun 30, 2023

Choose a reason for hiding this comment

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

image

I suggest new syntax for order preserving filter in the spec.

Or, we can use recursion

def $funcs(externval*) : funcaddr*
def $funcs(epsilon) = epsilon
def $funcs((FUNC fa) externval'*) = fa $funcs(externval'*)
def $funcs(externval externval'*) = $funcs(externval'*)
  -- otherwise

Copy link
Author

Choose a reason for hiding this comment

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

I updated the syntax in 236de04.

def $funcs(externval*) = `[fa|FUNC fa <- externval]
def $globals(externval*) = `[ga|GLOBAL ga <- externval]
def $tables(externval*) = `[ta|TABLE ta <- externval]
def $mems(externval*) = `[ma|MEM ma <- externval]

Copy link
Collaborator

Choose a reason for hiding this comment

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

Makes sense, though extending the DSL may be quite a cost for a one-off auxiliary definition like this. Let's perhaps discuss this on Thursday.

-- if ta* = |s.TABLE| ... $(|s.TABLE| + |table*| - 1)
-- if ma* = |s.MEM| ... $(|s.MEM| + |mem*| - 1)
-- if ea* = |s.ELEM| ... $(|s.ELEM| + |elem*| - 1)
-- if da* = |s.DATA| ... $(|s.DATA| + |data*| - 1)
Copy link
Author

@ShinWonho ShinWonho Jun 30, 2023

Choose a reason for hiding this comment

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

I suggest new syntax for range to precomputing the addresses.
For example, 0 ... 5 means 0 1 2 3 4 5.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this perhaps be expressed by an auxiliary function?

def $range(nat_1, nat_2) : nat*  hint(show %...%)
def $range(n_1, n_2) = epsilon  -- if n_1 > n_2
def $range(n_1, n_2) = n_1 $range(n_1+1, n_2)  -- otherwise

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the idea. I'll reflect it.

-- if s_3 = s_2[.TABLE =.. $alloctable(tabletype)*]
-- if s_4 = s_3[.MEM =.. $allocmem(memtype)*]
-- if s_5 = s_4[.ELEM =.. $allocelem(rt, ref*)*]
-- if s_6 = s_5[.DATA =.. $allocdata(byte*)*]
Copy link
Author

@ShinWonho ShinWonho Jun 30, 2023

Choose a reason for hiding this comment

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

Now, we don't need allocx* liftings.
But we can use allocx* liftings, if you want.

def $allocfunc(store, moduleinst, func) : store
def $allocfunc(s, m, func) = s[.FUNC =.. fi]
  -- if fi = { MODULE m, CODE func }

def $allocfunc*(store, moduleinst, func*) : store
def $allocfunc*(s, m, epsilon) = s
def $allocfunc*(s, m, func func'*) = $allocfunc*($allocfunc(s, m, func), m, func'*)

...

def $allocmodule(store, module, externval*, val*, (ref*)*) : (store, moduleinst)
def $allocmodule(s, module, externval*, val*, (ref*)*) = (s_6, m)
  ...
  -- if s_1 = $allocfunc*(s, m, func*)
  ...

Copy link
Collaborator

Choose a reason for hiding this comment

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

The alloc functions are meant as an abstraction that encapsulates allocation (and they are used in other places, specifically, the embedding interface in the appendix). The formulation you suggest, while cute, would be problematic in that sense, because then these functions no longer actually allocate.

So I'd go with the liftings for now. (You probably need to name them without the star and make that a show-hint.)

Mid-term I'd like to revamp the module representation into a linear list of declaration that is processed sequentially. Then this mess should go away.

Copy link
Author

@ShinWonho ShinWonho Jul 5, 2023

Choose a reason for hiding this comment

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

I have a few questions related to the comments you made:

  1. To avoid mutation, we decided to precompute the addresses before allocation. As a result, the alloc functions no longer return address. Is it acceptable to return store only?
  2. In the spec, the allocx lifting is defined for arbitrary object kinds, but DSL doesn't allow it. So I define a lifting function for each of the alloc functions. Is this approach acceptable?
  3. You mentioned that it is necessary to name the lifting functions without a star. If we remove the star, the name of lifting function would be the same as the name of allocation function. To ensure distinct names, would it be appropriate to use the name allocfuncs for the lifting function?
  4. I didn't fully understand the sentence: "Mid-term I'd like to revamp the module representation into a linear list of declaration that is processed sequentially." Could you provide further clarification on its meaning?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Re 1: Well, if you rephrase "precompute" as "predict", then no change to the allocation functions is necessary, I believe.

Re 2: Yes, defining a function for each type is fine for the DSL. We can probably still tweak it to produce a single template in the Latex output.

Re 3: allocfuncs sounds good to me.

Re 4: With some of the future extensions, module checking and instantiation becomes more sequential, with ordered dependencies between declarations. Also, there are ideas for allowing multiple appearances of section types, so that the order of different sorts of declarations can be mixed. So I would like to refactor modules to be more like a sequence of different declarations that is processed in order, which would also eliminate the specific iterations. I still have to work out the details, though, in particular wrt instantiation.

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for your kind reply!


def $instantiation(store, module, externval*) : config
def $instantiation(s, module, externval*) =
s'; f; ...$runelem(elem*[i], i)* ...$rundata(data*[j], j)* (CALL x)?
Copy link
Author

@ShinWonho ShinWonho Jun 30, 2023

Choose a reason for hiding this comment

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

We also need range for runelem and rundata.
Here, i* is a range 0 ... $(n-1), so $runelem(elem*[i], i)* means $runelem(elem*[0], 0) ... $runelem(elem*[$(n-1)]), $(n-1)) where n = |elem*|.
However, type of $runelem(..) is instr*, so type of $runelem(..)* is instr**.
Therefore, it requires new syntax for flattening.

...$runelem(..)* notation stands for kind of flattening which comes from JavaScript syntax spread.
Alternatively, we can use notations from Scala or Python
Scala repeated parameters: $runelem(..):_*
Python unpacking: *($runelem(..)*)


image

Rather than using *-notation, we can also use syntax similar to the spec
$runelem(elem*[0], 0) ... $runelem(elem*[$(n-1)], $(n-1))

Copy link
Collaborator

@rossberg rossberg Jul 4, 2023

Choose a reason for hiding this comment

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

Yeah, at this point I wonder if we shouldn't have a version of iteration with an index variable, e.g., exp^(i<n). Then this could be expressed as

(runelem_(i, elem))^(i<n)

when given elem^n. But maybe that's going too far?

Note that the spec also defines an auxiliary concat operator for flattening (see here).
A monomorphic version of that can already be defined in the DSL. For example, this works:

def $concat(nat**) : nat*
def $concat(epsilon) = epsilon
def $concat(n* n'**) = n* $concat(n'**)

def $f(nat) : nat*
def $f(n) = n n n
def $g(nat*) : nat*
def $g(n*) = $concat($f(n)*) $concat($f(n)*)

Copy link
Author

Choose a reason for hiding this comment

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

Oh, Thanks for the ideas! I'll reflect them.

Copy link
Author

@ShinWonho ShinWonho Jul 5, 2023

Choose a reason for hiding this comment

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

We can also use iteration with an index to express the range
For example, fa* = $range(|s.FUNC|, $(|s.FUNC| + |func*| - 1)) can be replaced by fa* = $(|s.FUNC| + i)^(i<n) where n is |func*|.
I think it is more compact.

@ShinWonho
Copy link
Author

I updated module-semantics, filter with recursive version.

Copy link
Collaborator

@rossberg rossberg left a comment

Choose a reason for hiding this comment

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

Looks mostly good, module nits. However, I would suggest minor refactorings of the new AST node:

  • Instead of a separate IndexedListN, have single ListN of exp * id option. That avoids repeating the logic for the exponent.

  • Introduce the same in the EL and disambiguate the syntax in the parser.

@@ -762,7 +766,7 @@ and elab_exp_notation' env e t : Il.exp list =
| (EpsE | SeqE _), IterT (t1, iter) ->
[elab_exp_notation_iter env (unseq_exp e) (t1, iter) t e.at]
| IterE (e1, iter1), IterT (t1, iter) ->
if (iter = Opt) <> (iter1 = Opt) then
if (iter = Opt) && (iter1 <> Opt) then
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this change needed?

Copy link
Author

@ShinWonho ShinWonho Jul 19, 2023

Choose a reason for hiding this comment

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

def $instantiation(s, module, externval*) = s'; f; instr_elem* instr_data* (CALL x)?
  ...

This is instantiation function in DSL.
In this context, (CALL x)? should be interpreted as admininstr*, which can either be epsilon (empty) or have a length of one.
So, it is intended to allow the optional iterative expression to have a list iteration type.

Comment on lines 339 to 343
| ListN e ->
match e.it with
| ParenE ({ it = CmpE ({ it = VarE id; _ }, LtOp, e); _}, _) ->
Il.IndexedListN (id, elab_exp env e (NatT $ e.at))
| _ -> Il.ListN (elab_exp env e (NatT $ e.at))
Copy link
Collaborator

Choose a reason for hiding this comment

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

I suggest we introduce an IndexedListN constructor in the EL as well and disambiguate in the parser already.

Copy link
Author

@ShinWonho ShinWonho Jul 19, 2023

Choose a reason for hiding this comment

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

Thanks for the comments. I have a few questions.

  1. I want to check whether I understand correctly. As I understand, EL should include both ListN of id and IndexedListN of exp * id, while IL should only have ListN of exp * id option. Is it correct?
  2. To have both ListN and IndexedListN in EL,
iter :
  | QUEST { Opt }
  | PLUS { List1 }
  | STAR { List }
  | UP arith_prim
    { match $2.it with
      | ParenE ({ it = CmpE({ it = VarE id; _ }, LtOp, e); _}, false) ->
          IndexedListN (e, id)
      | _ -> ListN $2
    }

is it enough for parser code, or do I have to refactor parsing the iter?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, sorry for the confusion, I wrote this comment about IndexedListN in EL before I arrived at the other. I'd prefer both to use ListN of exp * id option.

Copy link
Author

Choose a reason for hiding this comment

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

Oh, I see.
However, I believe pattern matching is still necessary to obtain id information if we do not intend to refactor the parser.

iter :
  | QUEST { Opt }
  | PLUS { List1 }
  | STAR { List }
  | UP arith_prim
    { match $2.it with
      | ParenE ({ it = CmpE({ it = VarE id; _ }, LtOp, e); _}, false) ->
          ListN (e, Some id)
      | _ -> ListN ($2, None)
    }

Is this pattern matching seems okay?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, that's fine with me.

Comment on lines 321 to 322
| IndexedListN _
| ListN _ -> List
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
| IndexedListN _
| ListN _ -> List
| ListN _ | IndexedListN _ -> List

@@ -694,6 +694,16 @@ let render_conditions env = function
" &\\quad\n " ^ word "if" ^ "~" ^
concat_map_nl " \\\\\n &&&&\\quad {\\land}~" "" (render_prem env) prems

let render_func_conditions env = function
Copy link
Collaborator

Choose a reason for hiding this comment

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

The only difference between this function and render_red_conditions seems to be the number of tabs to insert. Instead of duplicating the entire definition, can we perhaps make tab string ("&&&" vs "&&&&") a parameter?

@ShinWonho
Copy link
Author

I reflected your comments!
Thanks for the comments :)

Copy link
Collaborator

@rossberg rossberg left a comment

Choose a reason for hiding this comment

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

Thanks! I have mostly nits, but I think there are few things missing around checking the iteration variable.

| ListN (e, None) -> free_exp e
| ListN (e, Some id) ->
union (free_exp e) (free_varid id)
| ListN (e, _) -> free_exp e
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't his be

Suggested change
| ListN (e, _) -> free_exp e
| ListN (e, id_opt) → union (free_exp e) (free_opt free_varid id_opt)


(match iter' with
| ListN (_, Some id) ->
assert (List.length ids' = 1 && id.it = (List.hd ids').it)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure I understand this assertion. Why can't there be any other ids in that case?

Copy link
Author

Choose a reason for hiding this comment

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

It is because I would like to block the use of "iter with index" like this:

def $foo() : nat
def $foo() = 0
  -- if x* = 0 1 2
  -- if (x*[i] = y)^(i<3)

If you think we should allow semantics like this, I will remove the assertion.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think I follow, what problem do you see with that example? In Shouldn't using them for indexing be one of the primary use cases for iter vars?

Copy link
Author

Choose a reason for hiding this comment

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

I apologize for not providing sufficient explanation earlier.
My intention was to use 'iter with index' in a way that restricts iteration on variables other than the index variable.
However, upon further consideration, I realized that 'iter with index' merely introduces an index for iteration, so there shouldn't be such a restriction. I'll erase the assertion.

@@ -163,13 +162,9 @@ let union = Env.union (fun _ ctx1 ctx2 -> assert (ctx1 = ctx2); Some ctx1)
let rec annot_iter env iter : Il.Ast.iter * occur =
match iter with
| Opt | List | List1 -> iter, Env.empty
| ListN (e, None) ->
| ListN (e, opt) ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit:

Suggested change
| ListN (e, opt) ->
| ListN (e, id_opt) ->

Also, I believe this needs to check that the variable has the right type.

let e', occur1 = annot_exp env e in
let occur2 = Env.singleton id.it (Env.find id.it env) in
ListN (e', Some id), union occur1 occur2
ListN (e', opt), occur
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
ListN (e', opt), occur
ListN (e', id_opt), occur

@@ -68,8 +68,7 @@ let iter_nl_list f xs = List.iter (function Nl -> () | Elem x -> f x) xs
let rec check_iter env ctx iter =
match iter with
| Opt | List | List1 -> ()
| ListN (e, opt) ->
Option.iter (fun id -> check_id env ctx id) opt;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't we still need this check?

Comment on lines 317 to 321
let iter' =
match fst iter with
| ListN _ -> List
| iter' -> iter'
in
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: I think you can undo the reformatting now.

@@ -119,7 +119,7 @@ and t_exp' env = function
| SubE (e, t1, t2) -> SubE (e, t1, t2)

and t_iter env = function
| ListN e -> ListN (t_exp env e)
| ListN (e, id) -> ListN (t_exp env e, id)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
| ListN (e, id) -> ListN (t_exp env e, id)
| ListN (e, id_opt) -> ListN (t_exp env e, id_opt)

@@ -78,7 +78,7 @@ and t_exp' env = function
| SubE (e, t1, t2) -> SubE (e, t1, t2)

and t_iter env = function
| ListN e -> ListN (t_exp env e)
| ListN (e, id) -> ListN (t_exp env e, id)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
| ListN (e, id) -> ListN (t_exp env e, id)
| ListN (e, id_opt) -> ListN (t_exp env e, id_opt)

@@ -156,7 +156,7 @@ and t_iterexp n (iter, vs) =
unary t_iter n iter (fun iter' -> (iter', vs))

and t_iter n iter = match iter with
| ListN e -> unary t_exp n e (fun e' -> ListN e')
| ListN (e, id) -> unary t_exp n e (fun e' -> ListN (e', id))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
| ListN (e, id) -> unary t_exp n e (fun e' -> ListN (e', id))
| ListN (e, id_opt) -> unary t_exp n e (fun e' -> ListN (e', id_opt))

@@ -180,7 +180,7 @@ and t_iterexp env (iter, vs) =
unary t_iter env iter (fun iter' -> (iter', vs))

and t_iter env iter = match iter with
| ListN e -> unary t_exp env e (fun e' -> ListN e')
| ListN (e, id) -> unary t_exp env e (fun e' -> ListN (e', id))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
| ListN (e, id) -> unary t_exp env e (fun e' -> ListN (e', id))
| ListN (e, id_opt) -> unary t_exp env e (fun e' -> ListN (e', id_opt))

@ShinWonho
Copy link
Author

I introduced a check for the 'iter with index.'
If we check IterE where iter is of the form ListN (e, Some id), I treat the dimension of id as [ListN (e, Some id)], which is recursive.
Should we allow this recursion? If you prefer to avoid recursion, I can modify the dimension as [ListN (e)] or [List]

Copy link
Collaborator

@rossberg rossberg left a comment

Choose a reason for hiding this comment

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

Good point. Now that you mention it, I think no iteration variables should occur in any variable dimension. They are not part of the "dimension type". I believe this means that in multiplicity.ml:

  • check_iter needs to strip the variable, like you suggest
  • both check_exp and check_prem also need to strip any variable from the iter that they add to the ctx list in the cases of IterE and IterPr,
  • the equality assertion in annot_iterexp must strip iter before comparing it.

And in il/validation.ml:

  • in valid_iter the iter must be stripped before comparing to dim

Perhaps some other places I am missing?

@@ -71,8 +71,7 @@ let rec string_of_iter iter =
| List -> "*"
| List1 -> "+"
| ListN (e, None) -> "^" ^ string_of_exp e
| ListN (e, Some (id)) ->
"^(" ^ id.it ^ "<" ^ string_of_exp e ^ ")"
| ListN (e, Some (id)) -> "^(" ^ id.it ^ "<" ^ string_of_exp e ^ ")"
Copy link
Collaborator

Choose a reason for hiding this comment

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

I missed this nit before:

Suggested change
| ListN (e, Some (id)) -> "^(" ^ id.it ^ "<" ^ string_of_exp e ^ ")"
| ListN (e, Some id) -> "^(" ^ id.it ^ "<" ^ string_of_exp e ^ ")"

Comment on lines 71 to 73
| ListN (e, opt) ->
Option.iter (fun id -> check_id env (iter::ctx) id) opt;
check_exp env ctx e
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
| ListN (e, opt) ->
Option.iter (fun id -> check_id env (iter::ctx) id) opt;
check_exp env ctx e
| ListN (e, id_opt) ->
Option.iter (fun id -> check_id env (ListN (e, None) :: ctx) id) id_opt;
check_exp env ctx e

Comment on lines 166 to 172
| ListN (e, None) ->
let e', occur = annot_exp env e in
ListN (e', opt), occur
ListN (e', None), occur
| ListN (e, Some id) ->
let e', occur1 = annot_exp env e in
let occur2 = Env.singleton id.it [] in
ListN (e', Some id), union occur1 occur2
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
| ListN (e, None) ->
let e', occur = annot_exp env e in
ListN (e', opt), occur
ListN (e', None), occur
| ListN (e, Some id) ->
let e', occur1 = annot_exp env e in
let occur2 = Env.singleton id.it [] in
ListN (e', Some id), union occur1 occur2
| ListN (e, id_opt) ->
let e', occur1 = annot_exp env e in
let occur2 =
match id_opt with None -> Env.empty | Some id -> Env.singleton id.it [] in
ListN (e', id_opt), union occur1 occur2


(match iter' with
| ListN (_, Some id) ->
assert (List.length ids' = 1 && id.it = (List.hd ids').it)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think I follow, what problem do you see with that example? In Shouldn't using them for indexing be one of the primary use cases for iter vars?

@@ -336,7 +336,7 @@ let rec elab_iter env iter : Il.iter =
| Opt -> Il.Opt
| List -> Il.List
| List1 -> Il.List1
| ListN (e, opt) -> Il.ListN (elab_exp env e (NatT $ e.at), opt)
| ListN (e, id_opt) -> Il.ListN (elab_exp env e (NatT $ e.at), id_opt)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This also needs to check the type of the id.

| ListN (e, id_opt) ->
Option.iter (fun id ->
let t = find "variable" env.vars (prefix_id id) in
if (t.it <> NatT) then error_typ e.at "iteration index" (NatT $ e.at)
Copy link
Author

Choose a reason for hiding this comment

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

I add type check for index variable.
However, there is no region information of index, the error prints the wrong region.

@@ -160,16 +164,18 @@ type occur = Il.Ast.iter list Env.t
let union = Env.union (fun _ ctx1 ctx2 -> assert (ctx1 = ctx2); Some ctx1)


let strip_index = function
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems to be defined twice in this module.

Copy link
Author

Choose a reason for hiding this comment

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

I defined strip_index for both El and Il. Would it better to use distinct names?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, sorry, missed that. It's fine then.

spectec/src/frontend/elab.ml Outdated Show resolved Hide resolved
@@ -286,16 +292,12 @@ and annot_iterexp env occur1 (iter, ids) at : Il.Ast.iterexp * occur =
Env.filter_map (fun _ iters ->
match iters with
| [] -> None
| iter1::iters' -> assert (Il.Eq.eq_iter iter iter1); Some iters'
| iter1::iters' ->
assert (Il.Eq.eq_iter (strip_index iter) iter1); Some iters'
) occur1
in
let ids' = List.map (fun (x, _) -> x $ at) (Env.bindings occur1') in

Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change

@@ -241,15 +241,7 @@ let valid_list valid_x_y env xs ys at =
let rec valid_iter env iter =
match iter with
| Opt | List | List1 -> ()
| ListN (e, None) -> valid_exp env e (NatT $ e.at)
| ListN (e, Some id) ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you still need to validate the id here, see comment below.

@@ -478,6 +470,11 @@ and valid_path env p t : typ =
t'

and valid_iterexp env (iter, ids) : env =
let iter =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, sorry for not being clearer. We still need to validate the variable. We only need to strip it for the purpose of the eq_iter check below. Because this combination should be legal:

(...x...)^n          (...x...)^(i<n)

Copy link
Author

@ShinWonho ShinWonho Jul 26, 2023

Choose a reason for hiding this comment

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

Oh, I see. I've fixed it.
Thanks for the comments.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: can we name this iter' to avoid confusion?

Copy link
Collaborator

@rossberg rossberg left a comment

Choose a reason for hiding this comment

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

Looks good, modulo the two comments below. Thanks!

@@ -478,6 +470,11 @@ and valid_path env p t : typ =
t'

and valid_iterexp env (iter, ids) : env =
let iter =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: can we name this iter' to avoid confusion?

spectec/src/il/validation.ml Outdated Show resolved Hide resolved
@ShinWonho ShinWonho merged commit 056aebe into main Jul 26, 2023
1 check passed
@rossberg rossberg deleted the module-semantics branch July 27, 2023 06:35
@rossberg
Copy link
Collaborator

@ShinWonho, I fixed a couple of bugs in the multiplicity annotation and the renderer backend that I had overlooked during the code review. However, the bigger issue I really only noticed now is that the index variable is completely ignored in all the middlend phases. Who will implement this? Or is the plan to have a pass that eliminates the variable first?

@ShinWonho
Copy link
Author

My naive thought was that introducing 'iter with index' wouldn't affect the middle-end phases.
I'm willing to implement that, but I don't have a deep understanding of middle-end phases, so there could be someone more suited for this task.
Therefore, I suggest discussing it in today's meeting.

Alan-Liang pushed a commit to Alan-Liang/spectec that referenced this pull request Sep 1, 2024
Merge with upstream/`wasm-3.0+exn`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants