Skip to content

Commit 9f317d0

Browse files
authored
Add test on CI and infrastructure for wasm globals (#1)
* Updates * Add a verification test to CI * Produce a global-referencing object on stable * Restrict CI again
1 parent c3c806d commit 9f317d0

File tree

2 files changed

+302
-45
lines changed

2 files changed

+302
-45
lines changed

build.rs

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
use std::env;
2+
use std::path::PathBuf;
3+
4+
const SYMBOL: &str = "replace_realloc_global";
5+
6+
fn main() {
7+
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
8+
let wasm = build_raw_intrinsics();
9+
let archive = build_archive(&wasm);
10+
11+
std::fs::write(out_dir.join("libwasm-raw-intrinsics.a"), &archive).unwrap();
12+
println!("cargo:rustc-link-lib=static=wasm-raw-intrinsics");
13+
println!(
14+
"cargo:rustc-link-search=native={}",
15+
out_dir.to_str().unwrap()
16+
);
17+
}
18+
19+
/// This function will produce a wasm module which is itself an object file
20+
/// that is the basic equivalent of:
21+
///
22+
/// ```rust
23+
/// std::arch::global_asm!(
24+
/// "
25+
/// .globaltype internal_realloc_global, i32
26+
/// internal_realloc_global:
27+
/// "
28+
/// );
29+
///
30+
/// #[no_mangle]
31+
/// fn replace_realloc_global(val: usize) -> usize {
32+
/// unsafe {
33+
/// let ret: usize;
34+
/// std::arch::asm!(
35+
/// "
36+
/// global.get internal_realloc_global
37+
/// local.set {}
38+
/// local.get {}
39+
/// global.set internal_realloc_global
40+
/// ",
41+
/// out(local) ret,
42+
/// in(local) val,
43+
/// options(nostack)
44+
/// );
45+
/// ret
46+
/// }
47+
/// }
48+
/// ```
49+
///
50+
/// The main trickiness here is getting the `reloc.CODE` and `linking` sections
51+
/// right.
52+
fn build_raw_intrinsics() -> Vec<u8> {
53+
use wasm_encoder::Instruction::*;
54+
use wasm_encoder::*;
55+
56+
let mut module = Module::new();
57+
58+
// One function type, i32 -> i32
59+
let mut types = TypeSection::new();
60+
types.function([ValType::I32], [ValType::I32]);
61+
module.section(&types);
62+
63+
// One function, using the type we just added
64+
let mut funcs = FunctionSection::new();
65+
funcs.function(0);
66+
module.section(&funcs);
67+
68+
// This is the `internal_realloc_global` definition
69+
let mut globals = GlobalSection::new();
70+
globals.global(
71+
GlobalType {
72+
val_type: ValType::I32,
73+
mutable: true,
74+
},
75+
&ConstExpr::i32_const(0),
76+
);
77+
module.section(&globals);
78+
79+
// Here the `code` section is defined. This is tricky because an offset is
80+
// needed within the code section itself for the `reloc.CODE` section
81+
// later. At this time `wasm-encoder` doesn't give enough functionality to
82+
// use the high-level APIs. so everything is done manually here.
83+
//
84+
// First the function body is created and then it's appended into a code
85+
// section.
86+
let mut body = Vec::new();
87+
0u32.encode(&mut body); // no locals
88+
let global_offset1 = body.len() + 1;
89+
// global.get 0 ;; but with maximal encoding of the 0
90+
body.extend_from_slice(&[0x23, 0x80, 0x80, 0x80, 0x80, 0x00]);
91+
LocalGet(0).encode(&mut body);
92+
let global_offset2 = body.len() + 1;
93+
// global.set 0 ;; but with maximal encoding of the 0
94+
body.extend_from_slice(&[0x24, 0x81, 0x80, 0x80, 0x80, 0x00]);
95+
End.encode(&mut body);
96+
97+
let mut code = Vec::new();
98+
1u32.encode(&mut code); // one function
99+
body.len().encode(&mut code); // length of the function
100+
let body_offset = code.len();
101+
code.extend_from_slice(&body); // the function itself
102+
module.section(&RawSection {
103+
id: SectionId::Code as u8,
104+
data: &code,
105+
});
106+
107+
// Calculate the relocation offsets within the `code` section itself by
108+
// adding the start of where the body was placed to the offset within the
109+
// body.
110+
let global_offset1 = global_offset1 + body_offset;
111+
let global_offset2 = global_offset2 + body_offset;
112+
113+
// Here the linking section is constructed. There are two symbols described
114+
// here, one for the function that we injected and one for the global
115+
// that was injected. The injected global here is referenced in the
116+
// relocations below.
117+
//
118+
// More information about this format is at
119+
// https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md
120+
{
121+
let mut linking = Vec::new();
122+
linking.push(0x02); // version
123+
124+
linking.push(0x08); // `WASM_SYMBOL_TABLE`
125+
let mut subsection = Vec::new();
126+
2u32.encode(&mut subsection); // 2 symbols
127+
128+
subsection.push(0x00); // SYMTAB_FUNCTION
129+
0x00.encode(&mut subsection); // flags
130+
0x00u32.encode(&mut subsection); // function index
131+
SYMBOL.encode(&mut subsection); // symbol name
132+
133+
subsection.push(0x02); // SYMTAB_GLOBAL
134+
0x02.encode(&mut subsection); // flags
135+
0x00u32.encode(&mut subsection); // global index
136+
"internal_realloc_global".encode(&mut subsection); // symbol name
137+
138+
subsection.encode(&mut linking);
139+
module.section(&CustomSection {
140+
name: "linking",
141+
data: &linking,
142+
});
143+
}
144+
145+
// A `reloc.CODE` section is appended here with two relocations for the
146+
// two `global`-referencing instructions that were added.
147+
{
148+
let mut reloc = Vec::new();
149+
3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed)
150+
2u32.encode(&mut reloc); // 2 relocations
151+
reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB
152+
global_offset1.encode(&mut reloc); // offset
153+
0x01u32.encode(&mut reloc); // symbol index
154+
reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB
155+
global_offset2.encode(&mut reloc); // offset
156+
0x01u32.encode(&mut reloc); // symbol index
157+
158+
module.section(&CustomSection {
159+
name: "reloc.CODE",
160+
data: &reloc,
161+
});
162+
}
163+
164+
module.finish()
165+
}
166+
167+
/// This function produces the output of `llvm-ar crus libfoo.a foo.o` given
168+
/// the object file above as input. The archive is what's eventually fed to
169+
/// LLD.
170+
///
171+
/// Like above this is still tricky, mainly around the production of the symbol
172+
/// table.
173+
fn build_archive(wasm: &[u8]) -> Vec<u8> {
174+
use object::{bytes_of, endian::BigEndian, U32Bytes};
175+
176+
let mut archive = Vec::new();
177+
archive.extend_from_slice(&object::archive::MAGIC);
178+
179+
// The symbol table is in the "GNU" format which means it has a structure
180+
// that looks like:
181+
//
182+
// * a big-endian 32-bit integer for the number of symbols
183+
// * N big-endian 32-bit integers for the offset to the object file, within
184+
// the entire archive, for which object has the symbol
185+
// * N nul-delimited strings for each symbol
186+
//
187+
// Here we're building an archive with just one symbol so it's a bit
188+
// easier. Note though we don't know the offset of our `intrinsics.o` up
189+
// front so it's left as 0 for now and filled in later.
190+
let mut symbol_table = Vec::new();
191+
symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 1)));
192+
symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0)));
193+
symbol_table.extend_from_slice(SYMBOL.as_bytes());
194+
symbol_table.push(0x00);
195+
196+
archive.extend_from_slice(bytes_of(&object::archive::Header {
197+
name: *b"/ ",
198+
date: *b"0 ",
199+
uid: *b"0 ",
200+
gid: *b"0 ",
201+
mode: *b"0 ",
202+
size: format!("{:<10}", symbol_table.len())
203+
.as_bytes()
204+
.try_into()
205+
.unwrap(),
206+
terminator: object::archive::TERMINATOR,
207+
}));
208+
let symtab_offset = archive.len();
209+
archive.extend_from_slice(&symbol_table);
210+
211+
// All archive memberes must start on even offsets
212+
if archive.len() & 1 == 1 {
213+
archive.push(0x00);
214+
}
215+
216+
// Now that we have the starting offset of the `intrinsics.o` file go back
217+
// and fill in the offset within the symbol table generated earlier.
218+
let member_offset = archive.len();
219+
archive[symtab_offset + 4..][..4].copy_from_slice(bytes_of(&U32Bytes::new(
220+
BigEndian,
221+
member_offset.try_into().unwrap(),
222+
)));
223+
224+
archive.extend_from_slice(object::bytes_of(&object::archive::Header {
225+
name: *b"intrinsics.o ",
226+
date: *b"0 ",
227+
uid: *b"0 ",
228+
gid: *b"0 ",
229+
mode: *b"644 ",
230+
size: format!("{:<10}", wasm.len()).as_bytes().try_into().unwrap(),
231+
terminator: object::archive::TERMINATOR,
232+
}));
233+
archive.extend_from_slice(&wasm);
234+
archive
235+
}

0 commit comments

Comments
 (0)