|
| 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