Skip to content

Commit de83479

Browse files
committed
mo-ld: Implement GOT.func imports
Fixes #1810
1 parent 7d9b099 commit de83479

File tree

9 files changed

+312
-21
lines changed

9 files changed

+312
-21
lines changed

rts/char.c

+12
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ export int32_t char_to_lower(int32_t c) {
2020
return musl_fn((unsigned)c) != 0; \
2121
}
2222

23+
__attribute__ ((visibility("default")))
24+
int c_fn_2(int x, int y)
25+
{
26+
return x + y;
27+
}
28+
29+
__attribute__ ((visibility("default")))
30+
int (*c_fn(void)) (int x, int y)
31+
{
32+
return &c_fn_2;
33+
}
34+
2335
CHAR_PRED(char_is_whitespace, iswspace)
2436
CHAR_PRED(char_is_uppercase, iswupper)
2537
CHAR_PRED(char_is_lowercase, iswlower)

src/lib/lib.ml

+15
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,21 @@ struct
264264
then (take n xs, drop n xs)
265265
else (xs, [])
266266

267+
let rec init_ xs =
268+
match xs with
269+
| [] -> []
270+
| [_] -> []
271+
| x :: xs -> x :: init_ xs
272+
273+
let init xs =
274+
match xs with
275+
| [] -> failwith "init -- empty list"
276+
| _ -> init_ xs
277+
278+
let null = function
279+
| [] -> true
280+
| _ :: _ -> false
281+
267282
let hd_opt = function
268283
| x :: _ -> Some x
269284
| _ -> None

src/lib/lib.mli

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ sig
1818
val take : int -> 'a list -> 'a list (* raises Failure *)
1919
val drop : int -> 'a list -> 'a list (* raises Failure *)
2020
val split_at : int -> 'a list -> ('a list * 'a list)
21+
val init : 'a list -> 'a list (* raises Failure when the list is empty *)
22+
val init_ : 'a list -> 'a list (* like init, but returns empty when the list is empty *)
23+
val null : 'a list -> bool
2124

2225
val hd_opt : 'a list -> 'a option
2326
val last : 'a list -> 'a (* raises Failure *)

src/linking/linkModule.ml

+215-17
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,78 @@ through further refactoring before we are happy with it. Things to do:
1818
of functions for each syntactic category.
1919
*)
2020

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+
2193
(* Linking *)
2294

2395
type imports = (int32 * name) list
@@ -64,7 +136,8 @@ let remove_imports is_thing resolved : module_' -> module_' = fun m ->
64136
if List.mem_assoc i resolved
65137
then go (Int32.add i 1l) is
66138
else imp :: go (Int32.add i 1l) is
67-
else imp :: go i is in
139+
else imp :: go i is
140+
in
68141
{ m with imports = go 0l m.imports }
69142

70143
let count_imports is_thing m =
@@ -191,7 +264,7 @@ let remove_non_ic_exports (em : extended_module) : extended_module =
191264
let keep_export exp =
192265
is_ic_export exp ||
193266
match exp.it.edesc.it with
194-
| FuncExport var -> false
267+
| FuncExport _ -> false
195268
| GlobalExport _ -> false
196269
| MemoryExport _ -> true
197270
| TableExport _ -> true in
@@ -211,7 +284,7 @@ let resolve imports exports : (int32 * int32) list =
211284
| None -> []
212285
) imports)
213286

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) =
215288
let open Int32 in
216289

217290
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
228301
then sub i skipped
229302
else sub (add i n_imports2') skipped
230303
in go 0l resolved12
304+
231305
and fun2 i =
232306
let rec go skipped = function
233307
| (imp, exp)::is ->
@@ -240,6 +314,7 @@ let calculate_renaming n_imports1 n_things1 n_imports2 n_things2 resolved12 reso
240314
else sub (add (add i n_imports1') n_things1) skipped
241315
in go 0l resolved21
242316
in
317+
243318
(fun1, fun2)
244319

245320

@@ -575,10 +650,117 @@ let align p n =
575650
let p = to_int p in
576651
shift_left (shift_right_logical (add n (sub (shift_left 1l p) 1l)) p) p
577652

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+
578761
(* The first argument specifies the global of the first module indicating the
579762
start of free memory *)
580763
let link (em1 : extended_module) libname (em2 : extended_module) =
581-
582764
let global_exports1 = find_exports is_global_export em1.module_ in
583765

584766
let heap_global =
@@ -595,14 +777,15 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
595777
let lib_heap_start = align dylink.memory_alignment old_heap_start in
596778
let new_heap_start = align 4l (Int32.add lib_heap_start dylink.memory_size) in
597779

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
601782

602-
(* Fill in memory base pointer *)
783+
(* Fill in memory and table base pointers *)
603784
let dm2 = em2.module_
604785
|> 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
606789

607790
(* Link functions *)
608791
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) =
617800
(count_imports is_fun_import em1.module_)
618801
(Lib.List32.length em1.module_.funcs)
619802
(count_imports is_fun_import dm2)
620-
(Lib.List32.length dm2.funcs)
621803
fun_resolved12
622804
fun_resolved21 in
623805

@@ -626,7 +808,7 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
626808

627809
(* Link globals *)
628810
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
630812
let global_exports2 = find_exports is_global_export dm2 in
631813
(* Resolve imports, to produce a renumbering globalction: *)
632814
let global_resolved12 = resolve global_required1 global_exports2 in
@@ -636,7 +818,6 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
636818
(count_imports is_global_import em1.module_)
637819
(Lib.List32.length em1.module_.globals)
638820
(count_imports is_global_import dm2)
639-
(Lib.List32.length dm2.globals)
640821
global_resolved12
641822
global_resolved21 in
642823
assert (global_required1 = []); (* so far, we do not import globals *)
@@ -673,7 +854,7 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
673854
in
674855

675856
(* Rename types in second module *)
676-
let dm2_tys =
857+
let dm2 =
677858
{ (rename_types (ty_renamer dm2.types) dm2) with types = [] }
678859
in
679860

@@ -692,9 +873,13 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
692873
| Some fi -> prepend_to_start (funs2 fi) (add_or_get_ty (Wasm.Types.FuncType ([], [])))
693874
in
694875

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
696881

697-
join_modules
882+
let merged = join_modules
698883
( em1_tys
699884
|> map_module (fun m -> { m with types = type_indices_sorted })
700885
|> map_module (remove_imports is_fun_import fun_resolved12)
@@ -704,9 +889,9 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
704889
|> rename_globals_extended globals1
705890
|> map_module (set_global heap_global new_heap_start)
706891
|> 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)
708893
)
709-
( dm2_tys
894+
( dm2
710895
|> remove_imports is_fun_import fun_resolved21
711896
|> remove_imports is_global_import global_resolved21
712897
|> remove_imports is_memory_import [0l, 0l]
@@ -721,3 +906,16 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
721906
)
722907
|> add_call_ctors
723908
|> 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

Comments
 (0)