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
|
use std::env;
use std::path::PathBuf;
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let wasm = build_raw_intrinsics();
let archive = build_archive(&wasm);
std::fs::write(out_dir.join("libwasm-raw-intrinsics.a"), &archive).unwrap();
println!("cargo:rustc-link-lib=static=wasm-raw-intrinsics");
println!(
"cargo:rustc-link-search=native={}",
out_dir.to_str().unwrap()
);
// Some specific flags to `wasm-ld` to inform the shape of this adapter.
// Notably we're importing memory from the main module and additionally our
// own module has no stack at all since it's specifically allocated at
// startup.
println!("cargo:rustc-link-arg=--import-memory");
println!("cargo:rustc-link-arg=-zstack-size=0");
}
/// This function will produce a wasm module which is itself an object file
/// that is the basic equivalent of:
///
/// ```rust
/// std::arch::global_asm!(
/// "
/// .globaltype internal_state_ptr, i32
/// internal_state_ptr:
/// "
/// );
///
/// #[no_mangle]
/// extern "C" fn get_state_ptr() -> *mut u8 {
/// unsafe {
/// let ret: *mut u8;
/// std::arch::asm!(
/// "
/// global.get internal_state_ptr
/// ",
/// out(local) ret,
/// options(nostack, readonly)
/// );
/// ret
/// }
/// }
///
/// #[no_mangle]
/// extern "C" fn set_state_ptr(val: *mut u8) {
/// unsafe {
/// std::arch::asm!(
/// "
/// local.get {}
/// global.set internal_state_ptr
/// ",
/// in(local) val,
/// options(nostack, readonly)
/// );
/// }
/// }
///
/// // And likewise for `allocation_state`, `get_allocation_state`, and `set_allocation_state`
/// ```
///
/// The main trickiness here is getting the `reloc.CODE` and `linking` sections
/// right.
fn build_raw_intrinsics() -> Vec<u8> {
use wasm_encoder::Instruction::*;
use wasm_encoder::*;
let mut module = Module::new();
let mut types = TypeSection::new();
types.ty().function([], [ValType::I32]);
types.ty().function([ValType::I32], []);
module.section(&types);
// Declare the functions, using the type we just added.
let mut funcs = FunctionSection::new();
funcs.function(0);
funcs.function(1);
funcs.function(0);
funcs.function(1);
module.section(&funcs);
// Declare the globals.
let mut globals = GlobalSection::new();
// internal_state_ptr
globals.global(
GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
&ConstExpr::i32_const(0),
);
// allocation_state
globals.global(
GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
&ConstExpr::i32_const(0),
);
module.section(&globals);
// Here the `code` section is defined. This is tricky because an offset is
// needed within the code section itself for the `reloc.CODE` section
// later. At this time `wasm-encoder` doesn't give enough functionality to
// use the high-level APIs. so everything is done manually here.
//
// First the function bodies are created and then they're appended into a
// code section.
let mut code = Vec::new();
4u32.encode(&mut code); // number of functions
let global_get = 0x23;
let global_set = 0x24;
let encode = |code: &mut _, global, instruction| {
assert!(global < 0x7F);
let mut body = Vec::new();
0u32.encode(&mut body); // no locals
if instruction == global_set {
LocalGet(0).encode(&mut body);
}
let global_offset = body.len() + 1;
// global.get $global ;; but with maximal encoding of $global
body.extend_from_slice(&[instruction, 0x80u8 + global, 0x80, 0x80, 0x80, 0x00]);
End.encode(&mut body);
body.len().encode(code); // length of the function
let offset = code.len() + global_offset;
code.extend_from_slice(&body); // the function itself
offset
};
let internal_state_ptr_ref1 = encode(&mut code, 0, global_get); // get_state_ptr
let internal_state_ptr_ref2 = encode(&mut code, 0, global_set); // set_state_ptr
let allocation_state_ref1 = encode(&mut code, 1, global_get); // get_allocation_state
let allocation_state_ref2 = encode(&mut code, 1, global_set); // set_allocation_state
module.section(&RawSection {
id: SectionId::Code as u8,
data: &code,
});
// Here the linking section is constructed. There is one symbol for each function and global. The injected
// globals here are referenced in the relocations below.
//
// More information about this format is at
// https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md
{
let mut linking = Vec::new();
linking.push(0x02); // version
linking.push(0x08); // `WASM_SYMBOL_TABLE`
let mut subsection = Vec::new();
6u32.encode(&mut subsection); // 6 symbols (4 functions + 2 globals)
subsection.push(0x00); // SYMTAB_FUNCTION
0x00.encode(&mut subsection); // flags
0u32.encode(&mut subsection); // function index
"get_state_ptr".encode(&mut subsection); // symbol name
subsection.push(0x00); // SYMTAB_FUNCTION
0x00.encode(&mut subsection); // flags
1u32.encode(&mut subsection); // function index
"set_state_ptr".encode(&mut subsection); // symbol name
subsection.push(0x00); // SYMTAB_FUNCTION
0x00.encode(&mut subsection); // flags
2u32.encode(&mut subsection); // function index
"get_allocation_state".encode(&mut subsection); // symbol name
subsection.push(0x00); // SYMTAB_FUNCTION
0x00.encode(&mut subsection); // flags
3u32.encode(&mut subsection); // function index
"set_allocation_state".encode(&mut subsection); // symbol name
subsection.push(0x02); // SYMTAB_GLOBAL
0x02.encode(&mut subsection); // flags (WASM_SYM_BINDING_LOCAL)
0u32.encode(&mut subsection); // global index
"internal_state_ptr".encode(&mut subsection); // symbol name
subsection.push(0x02); // SYMTAB_GLOBAL
0x00.encode(&mut subsection); // flags
1u32.encode(&mut subsection); // global index
"allocation_state".encode(&mut subsection); // symbol name
subsection.encode(&mut linking);
module.section(&CustomSection {
name: "linking".into(),
data: linking.into(),
});
}
// A `reloc.CODE` section is appended here with relocations for the
// `global`-referencing instructions that were added.
{
let mut reloc = Vec::new();
3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed)
4u32.encode(&mut reloc); // 4 relocations
reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB
internal_state_ptr_ref1.encode(&mut reloc); // offset
4u32.encode(&mut reloc); // symbol index
reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB
internal_state_ptr_ref2.encode(&mut reloc); // offset
4u32.encode(&mut reloc); // symbol index
reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB
allocation_state_ref1.encode(&mut reloc); // offset
5u32.encode(&mut reloc); // symbol index
reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB
allocation_state_ref2.encode(&mut reloc); // offset
5u32.encode(&mut reloc); // symbol index
module.section(&CustomSection {
name: "reloc.CODE".into(),
data: reloc.into(),
});
}
module.finish()
}
/// This function produces the output of `llvm-ar crus libfoo.a foo.o` given
/// the object file above as input. The archive is what's eventually fed to
/// LLD.
///
/// Like above this is still tricky, mainly around the production of the symbol
/// table.
fn build_archive(wasm: &[u8]) -> Vec<u8> {
use object::{bytes_of, endian::BigEndian, U32Bytes};
let mut archive = Vec::new();
archive.extend_from_slice(&object::archive::MAGIC);
// The symbol table is in the "GNU" format which means it has a structure
// that looks like:
//
// * a big-endian 32-bit integer for the number of symbols
// * N big-endian 32-bit integers for the offset to the object file, within
// the entire archive, for which object has the symbol
// * N nul-delimited strings for each symbol
//
// Here we're building an archive with just a few symbols so it's a bit
// easier. Note though we don't know the offset of our `intrinsics.o` up
// front so it's left as 0 for now and filled in later.
let syms = [
"get_state_ptr",
"set_state_ptr",
"get_allocation_state",
"set_allocation_state",
"allocation_state",
];
let mut symbol_table = Vec::new();
symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, syms.len() as u32)));
for _ in syms.iter() {
symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0)));
}
for s in syms.iter() {
symbol_table.extend_from_slice(&std::ffi::CString::new(*s).unwrap().into_bytes_with_nul());
}
archive.extend_from_slice(bytes_of(&object::archive::Header {
name: *b"/ ",
date: *b"0 ",
uid: *b"0 ",
gid: *b"0 ",
mode: *b"0 ",
size: format!("{:<10}", symbol_table.len())
.as_bytes()
.try_into()
.unwrap(),
terminator: object::archive::TERMINATOR,
}));
let symtab_offset = archive.len();
archive.extend_from_slice(&symbol_table);
// All archive members must start on even offsets
if archive.len() & 1 == 1 {
archive.push(0x00);
}
// Now that we have the starting offset of the `intrinsics.o` file go back
// and fill in the offset within the symbol table generated earlier.
let member_offset = archive.len();
for (index, _) in syms.iter().enumerate() {
let index = index + 1;
archive[symtab_offset + (index * 4)..][..4].copy_from_slice(bytes_of(&U32Bytes::new(
BigEndian,
member_offset.try_into().unwrap(),
)));
}
archive.extend_from_slice(object::bytes_of(&object::archive::Header {
name: *b"intrinsics.o ",
date: *b"0 ",
uid: *b"0 ",
gid: *b"0 ",
mode: *b"644 ",
size: format!("{:<10}", wasm.len()).as_bytes().try_into().unwrap(),
terminator: object::archive::TERMINATOR,
}));
archive.extend_from_slice(&wasm);
archive
}
|