-
Notifications
You must be signed in to change notification settings - Fork 517
Expand file tree
/
Copy pathinterpreter.ml
More file actions
835 lines (775 loc) · 27.5 KB
/
interpreter.ml
File metadata and controls
835 lines (775 loc) · 27.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
open Reference_interpreter
open Ds
open Al
open Ast
open Al_util
open Xl
open Construct
open Print
open Util
open Source
open Printf
(* Errors *)
let empty = ""
let error at msg step = raise (Exception.Error (at, msg, step))
let fail_expr expr msg =
failwith ("on expr `" ^ string_of_expr expr ^ "` " ^ msg)
let fail_path path msg =
failwith ("on path `" ^ string_of_path path ^ "` " ^ msg)
let try_with_error fname at stringifier f step =
let prefix = if fname <> empty then "$" ^ fname ^ ": " else fname in
try f step with
|(Exception.WrongConversion _
| Exception.ArgMismatch _
| Exception.UnknownFunc _
| Exception.FreeVar _) as e -> error at (prefix ^ Printexc.to_string e) (stringifier step)
| Failure msg -> error at (prefix ^ msg) (stringifier step)
let warn msg = print_endline ("warning: " ^ msg)
(* Hints *)
(* Try to find a hint `hintid` on a spectec function definition `fname`. *)
let find_hint fname hintid =
let open Il.Ast in
let open Il2al.Il2al_util in
List.find_map (fun hintdef ->
match hintdef.it with
| DecH (id', hints) when fname = id'.it ->
List.find_opt (fun hint -> hint.hintid.it = hintid) hints
| _ -> None
) !hintdefs
(* Matrix operations *)
let is_matrix matrix =
match matrix with
| [] -> true (* Empty matrix is considered a valid matrix *)
| row :: rows -> List.for_all (fun r -> List.length row = List.length r) rows
let transpose matrix =
assert (is_matrix matrix);
let rec transpose' = function
| [] -> []
| [] :: _ -> []
| (x :: xs) :: xss ->
let new_row = (x :: List.map List.hd xss) in
let new_rows = transpose' (xs :: List.map List.tl xss) in
new_row :: new_rows in
transpose' matrix
let is_not_typarg a =
match a.it with
| TypA _ -> false
| _ -> true
let remove_typargs = List.filter is_not_typarg
let dispatch_fname f env =
match lookup_env_opt ("$" ^ f) env with
| Some (FnameV f') -> f'
| _ -> f
let rec create_sub_env (iter, xes) env =
let length_to_list l = List.init l al_of_nat in
let xe_to_values (x, e) =
match iter with
| ListN (e_n, Some x') when x = x' ->
eval_expr env e_n |> al_to_nat |> length_to_list
| Opt ->
eval_expr env e |> unwrap_optv |> Option.to_list
| _ ->
eval_expr env e |> unwrap_listv_to_list
in
let xs = List.map fst xes in
xes
|> List.map xe_to_values
|> transpose
|> List.map (fun vs -> List.fold_right2 Env.add xs vs env)
and access_path env base path =
match path.it with
| IdxP e' ->
let a = base |> unwrap_listv_to_array in
let i = eval_expr env e' |> al_to_nat in
begin try Array.get a i with
| Invalid_argument _ ->
fail_path path
(sprintf "failed Array.get on base %s and index %s"
(string_of_value base) (string_of_int i))
end
| SliceP (e1, e2) ->
let a = base |> unwrap_listv_to_array in
let i1 = eval_expr env e1 |> al_to_nat in
let i2 = eval_expr env e2 |> al_to_nat in
Array.sub a i1 i2 |> listV
| DotP str -> (
let str = Print.string_of_atom str in
match base with
| StrV r -> Record.find str r
| v ->
fail_path path
(sprintf "base %s is not a record" (string_of_value v))
)
and replace_path env base path v_new =
match path.it with
| IdxP e' ->
let a = unwrap_listv_to_array base |> Array.copy in
let i = eval_expr env e' |> al_to_nat in
Array.set a i v_new;
listV a
| SliceP (e1, e2) ->
let a = unwrap_listv_to_array base |> Array.copy in
let i1 = eval_expr env e1 |> al_to_nat in
let i2 = eval_expr env e2 |> al_to_nat in
Array.blit (unwrap_listv_to_array v_new) 0 a i1 i2;
listV a
| DotP str ->
let str = Print.string_of_atom str in
let r =
match base with
| StrV r -> r
| v ->
fail_path path
(sprintf "base %s is not a record" (string_of_value v))
in
let r_new = Record.clone r in
Record.replace str v_new r_new;
strV r_new
and check_type ty v expr =
(* type definition *)
let addr_refs = [
"REF.I31_NUM"; "REF.STRUCT_ADDR"; "REF.ARRAY_ADDR";
"REF.FUNC_ADDR"; "REF.HOST_ADDR"; "REF.EXTERN";
] in
let pnn_types = [ "I8"; "I16" ] in
let inn_types = [ "I32"; "I64" ] in
let fnn_types = [ "F32"; "F64" ] in
let vnn_types = [ "V128"; ] in
let abs_heaptypes = [
"ANY"; "EQ"; "I31"; "STRUCT"; "ARRAY"; "NONE"; "FUNC";
"NOFUNC"; "EXN"; "NOEXN"; "EXTERN"; "NOEXTERN"
] in
match v with
(* addrref *)
| CaseV (ar, _) when List.mem ar addr_refs->
boolV (ty = "addrref" ||ty = "ref" || ty = "val")
(* nul *)
| CaseV ("REF.NULL", _) ->
boolV (ty = "nul" || ty = "ref" || ty = "val")
(* values *)
| CaseV ("CONST", CaseV (nt, []) ::_) when List.mem nt inn_types ->
boolV (ty = "val")
| CaseV ("CONST", CaseV (nt, []) ::_) when List.mem nt fnn_types ->
boolV (ty = "val")
| CaseV ("VCONST", CaseV (vt, [])::_) when List.mem vt vnn_types ->
boolV (ty = "val")
(* numtype *)
| CaseV (nt, []) when List.mem nt inn_types ->
boolV (ty = "Inn" || ty = "Jnn" || ty = "numtype" || ty = "valtype" || ty = "consttype")
| CaseV (nt, []) when List.mem nt fnn_types ->
boolV (ty = "Fnn" || ty = "numtype" || ty = "valtype" || ty = "consttype")
| CaseV (vt, []) when List.mem vt vnn_types ->
boolV (ty = "Vnn" || ty = "vectype" || ty = "valtype" || ty = "consttype")
(* valtype *)
| CaseV ("REF", _) ->
boolV (ty = "reftype" || ty = "valtype" || ty = "val")
(* absheaptype *)
| CaseV (aht, []) when List.mem aht abs_heaptypes ->
boolV (ty = "absheaptype" || ty = "heaptype")
(* deftype *)
| CaseV ("_DEF", [ _; _ ]) ->
boolV (ty = "heaptype" || ty = "typeuse" || ty = "deftype")
(* typevar *)
| CaseV ("_IDX", [ _ ]) ->
boolV (ty = "heaptype" || ty = "typeuse" || ty = "typevar")
(* heaptype *)
| CaseV ("REC", [ _ ]) ->
boolV (ty = "heaptype" || ty = "typeuse" || ty = "typevar")
(* packval *)
| CaseV ("PACK", CaseV (pt, [])::_) when List.mem pt pnn_types ->
boolV (ty = "val")
(* packtype *)
| CaseV (pt, []) when List.mem pt pnn_types ->
boolV (ty = "Pnn" || ty = "Jnn" || ty = "packtype" || ty = "storagetype")
| v -> fail_expr expr
(sprintf "cannot decide if %s has type %s" (structured_string_of_value v) ty)
and eval_expr env expr =
let rec to_bool source = function
| BoolV b -> b
| ListV _ as v -> List.for_all (to_bool source) (unwrap_listv_to_list v)
| _ -> fail_expr source "type mismatch for boolean value"
in
match expr.it with
(* Value *)
| NumE n -> numV n
| BoolE b -> boolV b
(* Numeric Operation *)
| CvtE (e1, _, nt) ->
(match eval_expr env e1 with
| NumV n1 ->
(match Num.cvt nt n1 with
| Some n -> numV n
| None -> fail_expr expr ("conversion not defined for " ^ string_of_value (NumV n1))
)
| v -> fail_expr expr ("type mismatch for conversion operation on " ^ string_of_value v)
)
| UnE (op, e1) ->
(match op, eval_expr env e1 with
| #Bool.unop as op', v -> Bool.un op' (to_bool e1 v) |> boolV
| #Num.unop as op', NumV n1 ->
(match Num.un op' n1 with
| Some n -> numV n
| None -> fail_expr expr ("unary operation `" ^ Num.string_of_unop op' ^ "` not defined for " ^ string_of_value (NumV n1))
)
| _, v -> fail_expr expr ("type mismatch for unary operation on " ^ string_of_value v)
)
| BinE (op, e1, e2) ->
(match op, eval_expr env e1, eval_expr env e2 with
| #Bool.binop as op', v1, v2 -> Bool.bin op' (to_bool e1 v1) (to_bool e2 v2) |> boolV
| #Num.binop as op', NumV n1, NumV n2 ->
(match Num.bin op' n1 n2 with
| Some n -> numV n
| None -> fail_expr expr ("binary operation `" ^ Num.string_of_binop op' ^ "` not defined for " ^ string_of_value (NumV n1) ^ ", " ^ string_of_value (NumV n2))
)
| #Bool.cmpop as op', v1, v2 -> boolV (Bool.cmp op' v1 v2)
| #Num.cmpop as op', NumV n1, NumV n2 ->
(match Num.cmp op' n1 n2 with
| Some b -> boolV b
| None -> fail_expr expr ("comparison operation `" ^ Num.string_of_cmpop op' ^ "` not defined for " ^ string_of_value (NumV n1) ^ ", " ^ string_of_value (NumV n2))
)
| _, v1, v2 -> fail_expr expr ("type mismatch for binary operation on " ^ string_of_value v1 ^ " and " ^ string_of_value v2)
)
(* Set Operation *)
| MemE (e1, e2) ->
let v1 = eval_expr env e1 in
eval_expr env e2 |> unwrap_listv_to_array |> Array.exists ((=) v1) |> boolV
(* Function Call *)
| CallE (fname, al) ->
let fname' = dispatch_fname fname env in
let el = remove_typargs al in
let args = List.map (eval_arg env) el in
(match call_func fname' args with
| Some v -> v
| _ -> raise (Exception.MissingReturnValue fname)
)
| InvCallE (fname, _, al) ->
let fname' = dispatch_fname fname env in
let el = remove_typargs al in
(* TODO: refactor numerics function name *)
let args = List.map (eval_arg env) el in
let inv_fname =
(* If function $f has hint(inverse $invf), but $invf is defined in terms
* of the inversion of $f, then infinite loop! Implement loop checks? *)
match find_hint fname' "inverse" with
| None ->
fail_expr expr (sprintf "no inverse hint is given for definition `%s`" fname')
| Some hint ->
(* We assume that there is only one way to invert the function, on the
* last argument. We could extend the hint syntax to denote an argument. *)
match hint.hintexp.it with
| CallE (fid, []) -> fid.it
| _ -> failwith (sprintf "ill-formed inverse hint for definition `%s`" fname')
in
(match call_func inv_fname args with
| Some v -> v
| _ -> raise (Exception.MissingReturnValue fname)
)
(* Data Structure *)
| ListE el -> List.map (eval_expr env) el |> listV_of_list
| CompE (e1, e2) ->
let s1 = eval_expr env e1 |> unwrap_strv in
let s2 = eval_expr env e2 |> unwrap_strv in
List.map
(fun (id, v) ->
let arr1 = match !v with
| ListV arr_ref -> arr_ref
| _ -> failwith (sprintf "`%s` is not a list" (string_of_value !v))
in
let arr2 = match Record.find id s2 with
| ListV arr_ref -> arr_ref
| v -> failwith (sprintf "`%s` is not a list" (string_of_value v))
in
(id, Array.append !arr1 !arr2 |> listV |> ref)
) s1 |> strV
| LiftE e1 ->
eval_expr env e1 |> unwrap_optv |> Option.to_list |> listV_of_list
| CatE (e1, e2) ->
let a1 = eval_expr env e1 |> unwrap_seq_to_array in
let a2 = eval_expr env e2 |> unwrap_seq_to_array in
Array.append a1 a2 |> listV
| LenE e ->
eval_expr env e |> unwrap_listv_to_array |> Array.length |> Z.of_int |> natV
| StrE r ->
r |> Record.map Print.string_of_atom (eval_expr env) |> strV
| AccE (e, p) ->
let base = eval_expr env e in
access_path env base p
| ExtE (e1, ps, e2, dir) ->
let rec extend ps base =
match ps with
| path :: rest -> access_path env base path |> extend rest |> replace_path env base path
| [] ->
let v = eval_expr env e2 |> unwrap_listv_to_array in
let a_copy = base |> unwrap_listv_to_array |> Array.copy in
let a_new =
match dir with
| Front -> Array.append v a_copy
| Back -> Array.append a_copy v
in
listV a_new
in
eval_expr env e1 |> extend ps
| UpdE (e1, ps, e2) ->
let rec replace ps base =
match ps with
| path :: rest -> access_path env base path |> replace rest |> replace_path env base path
| [] -> eval_expr env e2 in
eval_expr env e1 |> replace ps
| CaseE (op, el) ->
(match Mixop.head op with
| Some a -> caseV (Print.string_of_atom a, List.map (eval_expr env) el)
| None -> caseV ("", List.map (eval_expr env) el)
)
| OptE opt -> Option.map (eval_expr env) opt |> optV
| TupE el -> List.map (eval_expr env) el |> tupV
(* Context *)
| GetCurContextE { it = Atom a; _ } when List.mem a context_names ->
WasmContext.get_current_context a
| ChooseE e ->
let a = eval_expr env e |> unwrap_listv_to_array in
if Array.length a = 0 then
fail_expr expr (sprintf "cannot choose an element from %s because it's empty" (string_of_expr e))
else
Array.get a 0
| VarE "s" -> Store.get ()
| VarE name -> lookup_env name env
(* Optimized getter for simple IterE(VarE, ...) *)
| IterE ({ it = VarE name; _ }, (_, [name', e])) when name = name' ->
eval_expr env e
(* Optimized getter for list init *)
| IterE (e1, (ListN (e2, None), [])) ->
let v = eval_expr env e1 in
let i = eval_expr env e2 |> al_to_nat in
if i > 1024 * 64 * 1024 (* 1024 pages *) then
raise Exception.OutOfMemory
else
Array.make i v |> listV
(* HARDCODE: The case where itered variable does not appear in xes.
--> Insert itered variable. This was instroduced due to the change of IrerE's ListN. *)
| IterE (e1, (ListN (e2, Some x), [])) ->
let dummy_expr = VarE "_" $$ no_region % (Il.Ast.VarT ("_" $ no_region, []) $ no_region) in
let expr' = {expr with it = IterE (e1, (ListN (e2, Some x), [(x, dummy_expr)]))} in
eval_expr env expr'
| IterE (inner_e, (iter, xes)) ->
let vs =
env
|> create_sub_env (iter, xes)
|> List.map (fun env' -> eval_expr env' inner_e)
in
(match vs, iter with
| [], Opt -> optV None
| [v], Opt -> Option.some v |> optV
| l, _ -> listV_of_list l)
(* condition *)
| ContextKindE a ->
let ctx = WasmContext.get_top_context () in
(match a.it, ctx with
| Atom case, CaseV (case', _) -> boolV (case = case')
| _ -> boolV false)
| IsDefinedE e ->
e
|> eval_expr env
|> unwrap_optv
|> Option.is_some
|> boolV
| IsCaseOfE (e, expected_tag) ->
let expected_tag = Print.string_of_atom expected_tag in
(match eval_expr env e with
| CaseV (tag, _) -> boolV (expected_tag = tag)
| _ -> boolV false)
(* TODO : This should be replaced with executing the validation algorithm *)
| IsValidE e ->
let valid_lim k = function
| TupV [ NumV (`Nat n); NumV (`Nat m) ] -> n <= m && m <= k
| _ -> false
in
(match eval_expr env e with
(* valid_tabletype *)
| TupV [ lim; _ ] -> valid_lim (Z.of_int 0xffffffff) lim |> boolV
(* valid_memtype *)
| CaseV ("PAGE", [ lim ]) -> valid_lim (Z.of_int 0x10000) lim |> boolV
(* valid_other *)
| _ ->
fail_expr expr "TODO: deferring validation to reference interpreter"
)
| HasTypeE (e, t) ->
(* TODO: This shouldn't be hardcoded *)
(* check type *)
let v = eval_expr env e in
check_type (string_of_typ t) v expr
| MatchE (e1, e2) ->
(* Deferred to reference interpreter *)
let rt1 = e1 |> eval_expr env |> Construct.al_to_reftype in
let rt2 = e2 |> eval_expr env |> Construct.al_to_reftype in
boolV (Match.match_reftype [] rt1 rt2)
| TopValueE _ ->
(* TODO: type check *)
boolV (List.length (WasmContext.get_value_stack ()) > 0)
| TopValuesE e ->
let i = eval_expr env e |> al_to_nat in
boolV (List.length (WasmContext.get_value_stack ()) >= i)
| _ -> fail_expr expr "cannot evaluate expr"
and eval_arg env a =
match a.it with
| ExpA e -> eval_expr env e
| TypA _ -> assert false
| DefA id -> FnameV (dispatch_fname id env)
(* Assignment *)
and has_same_keys re rv =
let k1 = Record.keys re |> List.map string_of_atom |> List.sort String.compare in
let k2 = Record.keys rv |> List.sort String.compare in
k1 = k2
and assign lhs rhs env =
match lhs.it, rhs with
| VarE name, _ -> Env.add name rhs env
| IterE ({ it = VarE x1; _ }, ((List|List1), [x2, lhs'])), ListV _ when x1 = x2 ->
assign lhs' rhs env
| IterE (e, (iter, xes)), _ ->
let vs = unwrap_seqv_to_list rhs in
let envs = List.map (fun v -> assign e v Env.empty) vs in
(* Assign length variable *)
let env' =
match iter with
| ListN (expr, None) ->
let length = natV_of_int (List.length vs) in
assign expr length env
| ListN _ ->
fail_expr lhs "invalid assignment: iter with index cannot be an assignment target"
| _ -> env
in
List.fold_left (fun env (x, e) ->
let vs = List.map (lookup_env x) envs in
let v =
match iter with
| Opt -> optV (List.nth_opt vs 0)
| _ -> listV_of_list vs
in
assign e v env
) env' xes
| TupE lhs_s, TupV rhs_s
when List.length lhs_s = List.length rhs_s ->
List.fold_right2 assign lhs_s rhs_s env
| ListE lhs_s, ListV rhs_s
when List.length lhs_s = Array.length !rhs_s ->
List.fold_right2 assign lhs_s (Array.to_list !rhs_s) env
| CaseE (op, lhs_s), CaseV (rhs_tag, rhs_s) when List.length lhs_s = List.length rhs_s ->
(match Mixop.head op with
| Some lhs_tag when (Print.string_of_atom lhs_tag) = rhs_tag ->
List.fold_right2 assign lhs_s rhs_s env
| None when "" = rhs_tag ->
List.fold_right2 assign lhs_s rhs_s env
| _ -> fail_expr lhs
(sprintf "invalid assignment: cannot be an assignment target for %s" (string_of_value rhs))
)
| OptE (Some lhs), OptV (Some rhs) -> assign lhs rhs env
| CvtE (e1, nt, _), NumV n ->
(match Num.cvt nt n with
| Some n' -> assign e1 (NumV n') env
| None -> fail_expr lhs ("inverse conversion not defined for " ^ string_of_value (NumV n))
)
(* Assumption: e1 is the assign target *)
| BinE (binop, e1, e2), NumV m ->
let invop =
match binop with
| `AddOp -> `SubOp
| `SubOp -> `AddOp
| `MulOp -> `DivOp
| `DivOp -> `MulOp
| _ -> fail_expr lhs "invalid assignment: logical binop cannot be an assignment target" in
let v =
match Num.bin invop m (eval_expr env e2 |> unwrap_numv) with
| Some n -> numV n
| None -> fail_expr lhs ("invalid assignment: inverted binop not defined for " ^ string_of_value rhs)
in
assign e1 v env
| CatE _, ListV vs -> assign_split lhs !vs env
| StrE r1, StrV r2 when has_same_keys r1 r2 ->
Record.fold (fun k v acc -> (Record.find (Print.string_of_atom k) r2 |> assign v) acc) r1 env
| _, _ ->
fail_expr lhs
(sprintf "invalid assignment: on rhs %s" (string_of_value rhs))
and assign_split lhs vs env =
let ep, es = unwrap_cate lhs in
let len = Array.length vs in
let prefix_len, suffix_len =
let get_fixed_length e =
match e.it with
| ListE es -> Some (List.length es)
| IterE (_, (ListN (e, None), _)) -> Some (al_to_nat (eval_expr env e))
| _ -> None
in
match get_fixed_length ep, get_fixed_length es with
| None, None ->
fail_expr lhs
"invalid assignment: non-deterministic pattern cannot be an assignment target"
| Some l, None -> l, len - l
| None, Some l -> len - l, l
| Some l1, Some l2 -> l1, l2
in
if prefix_len < 0 || suffix_len < 0 then
fail_expr lhs "invalid assignment: negative length cannot be an assignment target"
else if prefix_len + suffix_len <> len then
fail_expr lhs
(sprintf "invalid assignment: %s's length is not equal to lhs"
(string_of_value (listV vs))
)
else (
let prefix = Array.sub vs 0 prefix_len |> listV in
let suffix = Array.sub vs prefix_len suffix_len |> listV in
env |> assign ep prefix |> assign es suffix
)
and assign_param lhs rhs env =
match lhs.it with
| ExpA e -> assign e rhs env
| TypA _ -> assert false
| DefA id -> Env.add ("$" ^ id) rhs env
(* Step *)
and step_instr (fname: string) (ctx: AlContext.t) (env: value Env.t) (instr: instr) : AlContext.t =
(Info.find instr.note).covered <- true;
let rec is_true = function
| BoolV true -> true
| OptV v_opt -> v_opt |> Option.map is_true |> Option.value ~default:true
| ListV a -> Array.for_all is_true !a
| _ -> false
in
match instr.it with
(* Block instruction *)
| IfI (e, il1, il2) ->
if is_true (eval_expr env e) then
AlContext.add_instrs il1 ctx
else
AlContext.add_instrs il2 ctx
| EitherI (il1, il2) ->
(try
ctx |> AlContext.add_instrs il1 |> run
with
| Exception.Fail
| Exception.OutOfMemory ->
AlContext.add_instrs il2 ctx
)
| AssertI e ->
if is_true (eval_expr env e) then
ctx
else
fail_expr e "assertion fail"
| PushI e ->
(match eval_expr env e with
| CaseV ("FRAME_", _) as v ->
WasmContext.push_context (v, [], []);
AlContext.increase_depth ctx
| ListV vs ->
Array.iter WasmContext.push_value !vs;
ctx
| v ->
WasmContext.push_value v;
ctx
)
| PopI e ->
(match e.it with
| CaseE (op, [_; inner_e]) when (Option.get (Mixop.head op)).it = Atom.Atom "FRAME_" ->
(match WasmContext.pop_context () with
| CaseV ("FRAME_", [_; inner_v]), _, _ ->
let new_env = assign inner_e inner_v env in
ctx
|> AlContext.set_env new_env
|> AlContext.decrease_depth
| v, _, _ -> failwith (sprintf "current context `%s` is not a frame" (string_of_value v))
)
| IterE ({ it = VarE name; _ }, (ListN (e_n, None), [name', e'])) when name = name' ->
let i = eval_expr env e_n |> al_to_nat in
let v =
List.init i (fun _ -> WasmContext.pop_value ())
|> List.rev
|> listV_of_list
in
let new_env = assign e' v env in
AlContext.set_env new_env ctx
| _ ->
let new_env = assign e (WasmContext.pop_value ()) env in
AlContext.set_env new_env ctx
)
| PopAllI e ->
let v = WasmContext.pop_value_stack () |> List.rev |> listV_of_list in
let new_env = assign e v env in
AlContext.set_env new_env ctx
| LetI (e1, e2) ->
let new_env = ctx |> AlContext.get_env |> assign e1 (eval_expr env e2) in
AlContext.set_env new_env ctx
| PerformI (f, al) ->
let el = remove_typargs al in
let args = List.map (eval_arg env) el in
call_func f args |> ignore;
ctx
| TrapI -> raise Exception.Trap
| ThrowI _ -> raise Exception.Throw
| FailI -> raise Exception.Fail
| NopI -> ctx
| ReturnI None -> AlContext.tl ctx
| ReturnI (Some e) ->
AlContext.return (eval_expr env e) :: AlContext.tl ctx
| ExecuteI e ->
let v = eval_expr env e in
AlContext.execute v :: ctx
| ExecuteSeqI e ->
let v = eval_expr env e in
(match v with
| ListV _ ->
let ctx' = v |> unwrap_listv_to_list |> List.map AlContext.execute in
ctx' @ ctx
| _ -> failwith (sprintf "%s is not a sequence value" (string_of_value v))
)
| EnterI (e1, e2, il) ->
let v1 = eval_expr env e1 in
let v2 = eval_expr env e2 in
WasmContext.push_context (v1, [], unwrap_listv_to_list v2);
AlContext.enter (fname, il, env) :: ctx
| ExitI _ ->
WasmContext.pop_context () |> ignore;
AlContext.decrease_depth ctx
| ReplaceI (e1, { it = IdxP e2; _ }, e3) ->
let a = eval_expr env e1 |> unwrap_listv_to_array in
let i = eval_expr env e2 |> al_to_nat in
let v = eval_expr env e3 in
Array.set a i v;
ctx
| ReplaceI (e1, { it = SliceP (e2, e3); _ }, e4) ->
let a1 = eval_expr env e1 |> unwrap_listv_to_array in (* dest *)
let i1 = eval_expr env e2 |> al_to_nat in (* start index *)
let i2 = eval_expr env e3 |> al_to_nat in (* length *)
let a2 = eval_expr env e4 |> unwrap_listv_to_array in (* src *)
assert (Array.length a2 = i2);
Array.blit a2 0 a1 i1 i2;
ctx
| ReplaceI (r, { it = DotP s; _ }, e) ->
let s = Print.string_of_atom s in
r
|> eval_expr env
|> unwrap_strv
|> Record.replace s (eval_expr env e);
ctx
| AppendI (e1, e2) ->
let a = eval_expr env e1 |> unwrap_listv in
(match e2.note.it, eval_expr env e2 with
| IterT _, ListV arr_ref -> a := Array.append !a !arr_ref
| IterT (_, Opt), OptV opt ->
a := opt |> Option.to_list |> Array.of_list |> Array.append !a
| IterT _, v ->
v
|> string_of_value
|> sprintf "the expression is evaluated to %s, not a iterable data type"
|> fail_expr e2
| _, v -> a := Array.append !a [|v|]
);
ctx
| _ -> failwith "cannot step instr"
and try_step_instr fname ctx env instr =
try_with_error fname instr.at string_of_instr (step_instr fname ctx env) instr
and step_wasm (ctx: AlContext.t) : value -> AlContext.t = function
| CaseV ("REF.NULL" as name, ([ CaseV ("_IDX", _) ] as args)) ->
create_context name args :: ctx
| CaseV ("REF.NULL", _)
| CaseV ("CONST", _)
| CaseV ("VCONST", _) as v -> WasmContext.push_value v; ctx
| CaseV (name, []) when Host.is_host name -> Host.call name; ctx
| CaseV (name, args) -> create_context name args :: ctx
| v -> fail_value "cannot step a wasm instr" v
and try_step_wasm ctx v =
try_with_error empty no_region structured_string_of_value (step_wasm ctx) v
and step (ctx: AlContext.t) : AlContext.t =
let open AlContext in
Debugger.run ctx;
match ctx with
| Al (name, args, il, env, n) :: ctx ->
(match il with
| [] -> ctx
| [ instr ]
when can_tail_call instr && n = 0 && not !Debugger.debug ->
try_step_instr name ctx env instr
| h :: t ->
let new_ctx = Al (name, args, t, env, n) :: ctx in
try_step_instr name new_ctx env h
)
| Wasm n :: ctx ->
if n = 0 then
ctx
else
try_step_wasm (Wasm n :: ctx) (WasmContext.pop_instr ())
| Enter (name, il, env) :: ctx ->
(match il with
| [] ->
(match ctx with
| Wasm n :: t when not !Debugger.debug -> Wasm (n + 1) :: t
| Enter (_, [], _) :: t -> Wasm 2 :: t
| ctx -> Wasm 1 :: ctx
)
| h :: t ->
let new_ctx = Enter (name, t, env) :: ctx in
try_step_instr name new_ctx env h
)
| Execute v :: ctx -> try_step_wasm ctx v
| _ -> assert false
(* AL interpreter Entry *)
and run (ctx: AlContext.t) : AlContext.t =
if AlContext.is_reducible ctx then run (step ctx) else ctx
and create_context (name: string) (args: value list) : AlContext.mode =
let algo = lookup_algo name in
let params = params_of_algo algo in
let body = body_of_algo algo in
let params = params |> remove_typargs in
if List.length args <> List.length params then (
error
algo.at
(Printf.sprintf "Expected %d arguments for the algorithm `%s` but %d arguments are given"
(List.length params)
name
(List.length args))
(string_of_value (CaseV (name, args)))
);
let env =
Env.empty
|> List.fold_right2 assign_param params args
in
AlContext.al (name, params, body, env, 0)
and call_func (name: string) (args: value list) : value option =
let builtin_name, is_builtin =
match find_hint name "builtin" with
| None -> name, false
| Some hint ->
match hint.hintexp.it with
| SeqE [] -> name, true (* hint(builtin) *)
| TextE fname -> fname, true (* hint(builtin "g") *)
| _ -> failwith (sprintf "ill-formed builtin hint for definition `%s`" name)
in
(* Function *)
if bound_func name && not is_builtin then
[create_context name args]
|> run
|> AlContext.get_return_value
(* Numerics *)
else if Numerics.mem builtin_name then (
if not is_builtin then
warn (sprintf "Numeric function `%s` is not defined in source, consider adding a hint(builtin)" name);
Some (Numerics.call_numerics builtin_name args)
)
(* Relation *)
else if Relation.mem name then (
if bound_rule name then
[create_context name args]
|> run
|> AlContext.get_return_value
else
Some (Relation.call_func name args)
)
else
raise (Exception.UnknownFunc ("There is no function named: " ^ name))
(* Wasm interpreter entry *)
let instantiate (args: value list) : value =
WasmContext.init_context ();
match call_func "instantiate" args with
| Some module_inst -> module_inst
| None -> failwith "Instantiation doesn't return module instance"
let invoke (args: value list) : value =
WasmContext.init_context ();
match call_func "invoke" args with
| Some v -> v
| None -> failwith "Invocation doesn't return any values"