@@ -18,6 +18,78 @@ through further refactoring before we are happy with it. Things to do:
18
18
of functions for each syntactic category.
19
19
*)
20
20
21
+ (*
22
+ Resolving GOT.func and GOT.mem imports
23
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24
+
25
+ GOT.func and GOT.mem imports arise from function and data pointers,
26
+ respectively, in languages with pointers (e.g. C and Rust). The idea is that if
27
+ a shared library exposes a function/data pointer the entire process should use
28
+ the same pointer for the function/data so that the pointer arithmetic and
29
+ comparisons will work. For example, this C code:
30
+
31
+ __attribute__ ((visibility("default")))
32
+ int f0(int x, int y)
33
+ {
34
+ return x + y;
35
+ }
36
+
37
+ __attribute__ ((visibility("default")))
38
+ int (\*f1(void)) (int x, int y)
39
+ {
40
+ return &f0;
41
+ }
42
+
43
+ generates this GOT.func import:
44
+
45
+ (import "GOT.func" "f0" (global (;N;) (mut i32)))
46
+
47
+ The host is responsible of allocating a table index for this function and
48
+ resolving the import to the table index for `f0` so that this code in the
49
+ importing module would work:
50
+
51
+ assert(f1() == f0);
52
+
53
+ Note that the definition of `f1` is in the *imported* module and this assertion
54
+ is in the *importing* module.
55
+
56
+ Similarly exposing a data pointer generates a GOT.mem import. All GOT.mem
57
+ imports to a symbol should resolve to the same constant to support equality as
58
+ above, and additionally pointer arithmetic.
59
+
60
+ (Pointer arithmetic on function pointers are undefined behavior is C and is not
61
+ supported by clang's wasm backend)
62
+
63
+ Normally this stuff is for dynamic linking, but we want to link the RTS
64
+ statically, so we resolve these imports during linking. Currently we only
65
+ support GOT.func imports, but implementing GOT.mem imports would be similar.
66
+ Secondly, we only support GOT.func imports in the module that defines the
67
+ function that we take the address of. This currently works as moc-generated code
68
+ doesn't import function addresses from the RTS.
69
+
70
+ We resolve GOT.func imports in two steps:
71
+
72
+ - After loading the RTS module we generate a list of (global index, function
73
+ index) pairs of GOT.func imports. In the example above, global index is N and
74
+ function index is the index of f0 in the defining module (the RTS).
75
+
76
+ This is implemented in `collect_got_func_imports`.
77
+
78
+ - After merging the sections we add the functions to the table and replace
79
+ `GOT.func` imports with globals to the functions' table indices.
80
+
81
+ Note that we don't reuse table entries when a function is already in the
82
+ table, to avoid breakage when [ref-types] proposal is implemented, which will
83
+ allow mutating table entries.
84
+
85
+ [ref-types]: https://github.com/WebAssembly/reference-types
86
+
87
+ This is implemented in `replace_got_func_imports`.
88
+
89
+ See also the test `test/ld/fun-ptr` for a concrete exaple of GOT.func generation
90
+ and resolving.
91
+ *)
92
+
21
93
(* Linking *)
22
94
23
95
type imports = (int32 * name ) list
@@ -64,7 +136,8 @@ let remove_imports is_thing resolved : module_' -> module_' = fun m ->
64
136
if List. mem_assoc i resolved
65
137
then go (Int32. add i 1l ) is
66
138
else imp :: go (Int32. add i 1l ) is
67
- else imp :: go i is in
139
+ else imp :: go i is
140
+ in
68
141
{ m with imports = go 0l m.imports }
69
142
70
143
let count_imports is_thing m =
@@ -191,7 +264,7 @@ let remove_non_ic_exports (em : extended_module) : extended_module =
191
264
let keep_export exp =
192
265
is_ic_export exp ||
193
266
match exp.it.edesc.it with
194
- | FuncExport var -> false
267
+ | FuncExport _ -> false
195
268
| GlobalExport _ -> false
196
269
| MemoryExport _ -> true
197
270
| TableExport _ -> true in
@@ -211,7 +284,7 @@ let resolve imports exports : (int32 * int32) list =
211
284
| None -> []
212
285
) imports)
213
286
214
- let calculate_renaming n_imports1 n_things1 n_imports2 n_things2 resolved12 resolved21 : (renumbering * renumbering) =
287
+ let calculate_renaming n_imports1 n_things1 n_imports2 resolved12 resolved21 : (renumbering * renumbering) =
215
288
let open Int32 in
216
289
217
290
let n_imports1' = sub n_imports1 (Lib.List32. length resolved12) in
@@ -228,6 +301,7 @@ let calculate_renaming n_imports1 n_things1 n_imports2 n_things2 resolved12 reso
228
301
then sub i skipped
229
302
else sub (add i n_imports2') skipped
230
303
in go 0l resolved12
304
+
231
305
and fun2 i =
232
306
let rec go skipped = function
233
307
| (imp , exp )::is ->
@@ -240,6 +314,7 @@ let calculate_renaming n_imports1 n_things1 n_imports2 n_things2 resolved12 reso
240
314
else sub (add (add i n_imports1') n_things1) skipped
241
315
in go 0l resolved21
242
316
in
317
+
243
318
(fun1, fun2)
244
319
245
320
@@ -575,10 +650,117 @@ let align p n =
575
650
let p = to_int p in
576
651
shift_left (shift_right_logical (add n (sub (shift_left 1l p) 1l )) p) p
577
652
653
+ let find_fun_export (name : name ) (exports : export list ) : var option =
654
+ Lib.List. first_opt (fun (export : export ) ->
655
+ if export.it.name = name then
656
+ match export.it.edesc.it with
657
+ | FuncExport var -> Some var
658
+ | _ -> raise (LinkError (Format. sprintf " Export %s is not a function" (Wasm.Utf8. encode name)))
659
+ else
660
+ None
661
+ ) exports
662
+
663
+ let remove_got_func_imports (imports : import list ) : import list =
664
+ let got_func_str = Wasm.Utf8. decode " GOT.func" in
665
+ List. filter (fun import -> import.it.module_name <> got_func_str) imports
666
+
667
+ (* Merge global list of a module with a sorted (on global index) list of (global
668
+ index, global) pairs, overriding globals at those indices, and appending
669
+ left-overs at the end. *)
670
+ let add_globals (globals0 : global list ) (insert0 : (int32 * global') list ) : global list =
671
+ let rec go (current_idx : int32 ) globals insert =
672
+ match insert with
673
+ | [] -> globals
674
+ | (insert_idx , global ) :: rest ->
675
+ if current_idx = insert_idx then
676
+ (global @@ no_region) :: go (Int32. add current_idx 1l ) globals rest
677
+ else
678
+ match globals with
679
+ | [] -> List. map (fun (_ , global ) -> global @@ no_region) insert
680
+ | global :: globals -> global :: go (Int32. add current_idx 1l ) globals rest
681
+ in
682
+ go 0l globals0 insert0
683
+
684
+ let mk_i32_const (i : int32 ) =
685
+ Const (Wasm.Values. I32 i @@ no_region) @@ no_region
686
+
687
+ let mk_i32_global (i : int32 ) =
688
+ { gtype = Wasm.Types. GlobalType (Wasm.Types. I32Type , Wasm.Types. Immutable );
689
+ value = [mk_i32_const i] @@ no_region }
690
+
691
+ (* Generate (global index, function index) pairs for GOT.func imports of a
692
+ module. Uses import and export lists of the module so those should be valid. *)
693
+ let collect_got_func_imports (m : module_' ) : (int32 * int32) list =
694
+ let got_func_name = Wasm.Utf8. decode " GOT.func" in
695
+
696
+ let get_got_func_import (global_idx , imports ) import : (int32 * (int32 * int32) list) =
697
+ if import.it.module_name = got_func_name then
698
+ (* Found a GOT.func import, find the exported function for it *)
699
+ let name = import.it.item_name in
700
+ let fun_idx =
701
+ match find_fun_export name m.exports with
702
+ | None -> raise (LinkError (Format. sprintf " Can't find export for GOT.func import %s" (Wasm.Utf8. encode name)))
703
+ | Some export_idx -> export_idx.it
704
+ in
705
+ let global_idx =
706
+ if is_global_import import.it.idesc.it then
707
+ global_idx
708
+ else
709
+ raise (LinkError " GOT.func import is not global" )
710
+ in
711
+ ( Int32. add global_idx (Int32. of_int 1 ), (global_idx, fun_idx) :: imports )
712
+ else
713
+ let global_idx =
714
+ if is_global_import import.it.idesc.it then
715
+ Int32. add global_idx (Int32. of_int 1 )
716
+ else
717
+ global_idx
718
+ in
719
+ ( global_idx, imports )
720
+ in
721
+
722
+ (* (global index, function index) list *)
723
+ let (_, got_func_imports) =
724
+ List. fold_left get_got_func_import (0l , [] ) m.imports
725
+ in
726
+
727
+ got_func_imports
728
+
729
+ (* Add functions imported from GOT.func to the table, replace GOT.func imports
730
+ with globals to the table indices.
731
+
732
+ `tbe_size` is the size of the table in the merged module before adding
733
+ GOT.func functions. *)
734
+ let replace_got_func_imports (tbl_size : int32 ) (imports : (int32 * int32) list ) (m : module_' ) : module_' =
735
+ (* null check to avoid adding empty elem section *)
736
+ if Lib.List. null imports then
737
+ m
738
+ else
739
+ let imports =
740
+ List. sort (fun (gbl_idx_1 , _ ) (gbl_idx_2 , _ ) -> compare gbl_idx_1 gbl_idx_2) imports
741
+ in
742
+
743
+ let elems : var list =
744
+ List. map (fun (_ , fun_idx ) -> fun_idx @@ no_region) imports
745
+ in
746
+
747
+ let elem_section =
748
+ { index = 0l @@ no_region; offset = [ mk_i32_const tbl_size ] @@ no_region; init = elems }
749
+ in
750
+
751
+ let globals =
752
+ List. mapi (fun idx (global_idx , _ ) -> (global_idx, mk_i32_global (Int32. add tbl_size (Int32. of_int idx)))) imports
753
+ in
754
+
755
+ { m with
756
+ elems = List. append m.elems [elem_section @@ no_region];
757
+ imports = remove_got_func_imports m.imports;
758
+ globals = add_globals m.globals globals
759
+ }
760
+
578
761
(* The first argument specifies the global of the first module indicating the
579
762
start of free memory *)
580
763
let link (em1 : extended_module ) libname (em2 : extended_module ) =
581
-
582
764
let global_exports1 = find_exports is_global_export em1.module_ in
583
765
584
766
let heap_global =
@@ -595,14 +777,15 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
595
777
let lib_heap_start = align dylink.memory_alignment old_heap_start in
596
778
let new_heap_start = align 4l (Int32. add lib_heap_start dylink.memory_size) in
597
779
598
- let old_elem_size = read_table_size em1.module_ in
599
- let lib_elem_start = align dylink.table_alignment old_elem_size in
600
- let new_elem_size = Int32. add lib_elem_start dylink.table_size in
780
+ let old_table_size = read_table_size em1.module_ in
781
+ let lib_table_start = align dylink.table_alignment old_table_size in
601
782
602
- (* Fill in memory base pointer *)
783
+ (* Fill in memory and table base pointers *)
603
784
let dm2 = em2.module_
604
785
|> fill_memory_base_import lib_heap_start
605
- |> fill_table_base_import lib_elem_start in
786
+ |> fill_table_base_import lib_table_start in
787
+
788
+ let got_func_imports = collect_got_func_imports dm2 in
606
789
607
790
(* Link functions *)
608
791
let fun_required1 = find_imports is_fun_import libname em1.module_ in
@@ -617,7 +800,6 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
617
800
(count_imports is_fun_import em1.module_)
618
801
(Lib.List32. length em1.module_.funcs)
619
802
(count_imports is_fun_import dm2)
620
- (Lib.List32. length dm2.funcs)
621
803
fun_resolved12
622
804
fun_resolved21 in
623
805
@@ -626,7 +808,7 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
626
808
627
809
(* Link globals *)
628
810
let global_required1 = find_imports is_global_import libname em1.module_ in
629
- let global_required2 = find_imports is_global_import " env" dm2 in
811
+ let global_required2 = find_imports is_global_import " env" dm2 in
630
812
let global_exports2 = find_exports is_global_export dm2 in
631
813
(* Resolve imports, to produce a renumbering globalction: *)
632
814
let global_resolved12 = resolve global_required1 global_exports2 in
@@ -636,7 +818,6 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
636
818
(count_imports is_global_import em1.module_)
637
819
(Lib.List32. length em1.module_.globals)
638
820
(count_imports is_global_import dm2)
639
- (Lib.List32. length dm2.globals)
640
821
global_resolved12
641
822
global_resolved21 in
642
823
assert (global_required1 = [] ); (* so far, we do not import globals *)
@@ -673,7 +854,7 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
673
854
in
674
855
675
856
(* Rename types in second module *)
676
- let dm2_tys =
857
+ let dm2 =
677
858
{ (rename_types (ty_renamer dm2.types) dm2) with types = [] }
678
859
in
679
860
@@ -692,9 +873,13 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
692
873
| Some fi -> prepend_to_start (funs2 fi) (add_or_get_ty (Wasm.Types. FuncType ([] , [] )))
693
874
in
694
875
695
- assert (dm2_tys.globals = [] );
876
+ assert (dm2.globals = [] );
877
+
878
+ let new_table_size =
879
+ Int32. add (Int32. add lib_table_start dylink.table_size) (Int32. of_int (List. length got_func_imports))
880
+ in
696
881
697
- join_modules
882
+ let merged = join_modules
698
883
( em1_tys
699
884
|> map_module (fun m -> { m with types = type_indices_sorted })
700
885
|> map_module (remove_imports is_fun_import fun_resolved12)
@@ -704,9 +889,9 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
704
889
|> rename_globals_extended globals1
705
890
|> map_module (set_global heap_global new_heap_start)
706
891
|> map_module (set_memory_size new_heap_start)
707
- |> map_module (set_table_size new_elem_size )
892
+ |> map_module (set_table_size new_table_size )
708
893
)
709
- ( dm2_tys
894
+ ( dm2
710
895
|> remove_imports is_fun_import fun_resolved21
711
896
|> remove_imports is_global_import global_resolved21
712
897
|> remove_imports is_memory_import [0l , 0l ]
@@ -721,3 +906,16 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
721
906
)
722
907
|> add_call_ctors
723
908
|> remove_non_ic_exports (* only sane if no additional files get linked in *)
909
+ in
910
+
911
+ (* Rename global and function indices in GOT.func stuff *)
912
+ let got_func_imports =
913
+ List. map (fun (global_idx , func_idx ) -> (globals2 global_idx, funs2 func_idx)) got_func_imports
914
+ in
915
+
916
+ (* Replace GOT.func imports with globals to function table indices *)
917
+ let final =
918
+ replace_got_func_imports (Int32. add lib_table_start dylink.table_size) got_func_imports merged.module_
919
+ in
920
+
921
+ { merged with module_ = final }
0 commit comments