| ... | @@ -29,6 +29,32 @@ pub const FnData = struct { | ... | @@ -29,6 +29,32 @@ pub const FnData = struct { |
| 29 | idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{}, | 29 | idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{}, |
| 30 | }; | 30 | }; |
| 31 | | 31 | |
| | 32 | /// Data section of the wasm binary |
| | 33 | /// Each declaration will have its own 'data_segment' within the section |
| | 34 | /// where the offset is calculated using the previous segments and the content length |
| | 35 | /// of the data |
| | 36 | pub const DataSection = struct { |
| | 37 | segments: std.AutoArrayHashMapUnmanaged(*const Module.Decl, []const u8) = .{}, |
| | 38 | |
| | 39 | /// Returns the offset into the data segment based on a given `Decl` |
| | 40 | pub fn offset(self: DataSection, decl: *const Module.Decl) u32 { |
| | 41 | var cur_offset: u32 = 0; |
| | 42 | return for (self.segments.items()) |entry| { |
| | 43 | if (entry.key == decl) break cur_offset; |
| | 44 | cur_offset += @intCast(u32, entry.value.len); |
| | 45 | } else cur_offset; |
| | 46 | } |
| | 47 | |
| | 48 | /// Returns the total payload size of the data section |
| | 49 | pub fn size(self: DataSection) u32 { |
| | 50 | var total: u32 = 0; |
| | 51 | for (self.segments.items()) |entry| { |
| | 52 | total += @intCast(u32, entry.value.len); |
| | 53 | } |
| | 54 | return total; |
| | 55 | } |
| | 56 | }; |
| | 57 | |
| 32 | base: link.File, | 58 | base: link.File, |
| 33 | | 59 | |
| 34 | /// List of all function Decls to be written to the output file. The index of | 60 | /// List of all function Decls to be written to the output file. The index of |
| ... | @@ -45,6 +71,10 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{}, | ... | @@ -45,6 +71,10 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{}, |
| 45 | /// to support existing code. | 71 | /// to support existing code. |
| 46 | /// TODO: Allow setting this through a flag? | 72 | /// TODO: Allow setting this through a flag? |
| 47 | host_name: []const u8 = "env", | 73 | host_name: []const u8 = "env", |
| | 74 | /// Map of declarations with its bytes payload, used to keep track of all data segments |
| | 75 | /// that needs to be emit when creating the wasm binary. |
| | 76 | /// The `DataSection`'s lifetime must be kept alive until the linking stage. |
| | 77 | data: DataSection = .{}, |
| 48 | | 78 | |
| 49 | pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm { | 79 | pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm { |
| 50 | assert(options.object_format == .wasm); | 80 | assert(options.object_format == .wasm); |
| ... | @@ -52,7 +82,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio | ... | @@ -52,7 +82,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio |
| 52 | if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO | 82 | if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO |
| 53 | if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO | 83 | if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO |
| 54 | | 84 | |
| 55 | // TODO: read the file and keep vaild parts instead of truncating | 85 | // TODO: read the file and keep valid parts instead of truncating |
| 56 | const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true }); | 86 | const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true }); |
| 57 | errdefer file.close(); | 87 | errdefer file.close(); |
| 58 | | 88 | |
| ... | @@ -92,14 +122,13 @@ pub fn deinit(self: *Wasm) void { | ... | @@ -92,14 +122,13 @@ pub fn deinit(self: *Wasm) void { |
| 92 | } | 122 | } |
| 93 | self.funcs.deinit(self.base.allocator); | 123 | self.funcs.deinit(self.base.allocator); |
| 94 | self.ext_funcs.deinit(self.base.allocator); | 124 | self.ext_funcs.deinit(self.base.allocator); |
| | 125 | self.data.segments.deinit(self.base.allocator); |
| 95 | } | 126 | } |
| 96 | | 127 | |
| 97 | // Generate code for the Decl, storing it in memory to be later written to | 128 | // Generate code for the Decl, storing it in memory to be later written to |
| 98 | // the file on flush(). | 129 | // the file on flush(). |
| 99 | pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { | 130 | pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { |
| 100 | const typed_value = decl.typed_value.most_recent.typed_value; | 131 | const typed_value = decl.typed_value.most_recent.typed_value; |
| 101 | if (typed_value.ty.zigTypeTag() != .Fn) | | |
| 102 | return error.TODOImplementNonFnDeclsForWasm; | | |
| 103 | | 132 | |
| 104 | if (decl.fn_link.wasm) |*fn_data| { | 133 | if (decl.fn_link.wasm) |*fn_data| { |
| 105 | fn_data.functype.items.len = 0; | 134 | fn_data.functype.items.len = 0; |
| ... | @@ -111,6 +140,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { | ... | @@ -111,6 +140,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { |
| 111 | switch (decl.typed_value.most_recent.typed_value.val.tag()) { | 140 | switch (decl.typed_value.most_recent.typed_value.val.tag()) { |
| 112 | .function => try self.funcs.append(self.base.allocator, decl), | 141 | .function => try self.funcs.append(self.base.allocator, decl), |
| 113 | .extern_fn => try self.ext_funcs.append(self.base.allocator, decl), | 142 | .extern_fn => try self.ext_funcs.append(self.base.allocator, decl), |
| | 143 | .bytes => {}, |
| 114 | else => return error.TODOImplementNonFnDeclsForWasm, | 144 | else => return error.TODOImplementNonFnDeclsForWasm, |
| 115 | } | 145 | } |
| 116 | } | 146 | } |
| ... | @@ -132,7 +162,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { | ... | @@ -132,7 +162,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { |
| 132 | defer context.deinit(); | 162 | defer context.deinit(); |
| 133 | | 163 | |
| 134 | // generate the 'code' section for the function declaration | 164 | // generate the 'code' section for the function declaration |
| 135 | context.gen() catch |err| switch (err) { | 165 | const result = context.gen() catch |err| switch (err) { |
| 136 | error.CodegenFail => { | 166 | error.CodegenFail => { |
| 137 | decl.analysis = .codegen_failure; | 167 | decl.analysis = .codegen_failure; |
| 138 | try module.failed_decls.put(module.gpa, decl, context.err_msg); | 168 | try module.failed_decls.put(module.gpa, decl, context.err_msg); |
| ... | @@ -141,15 +171,24 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { | ... | @@ -141,15 +171,24 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { |
| 141 | else => |e| return err, | 171 | else => |e| return err, |
| 142 | }; | 172 | }; |
| 143 | | 173 | |
| 144 | // as locals are patched afterwards, the offsets of funcidx's are off, | 174 | switch (typed_value.ty.zigTypeTag()) { |
| 145 | // here we update them to correct them | 175 | .Fn => { |
| 146 | for (decl.fn_link.wasm.?.idx_refs.items) |*func| { | 176 | // as locals are patched afterwards, the offsets of funcidx's are off, |
| 147 | // For each local, add 6 bytes (count + type) | 177 | // here we update them to correct them |
| 148 | func.offset += @intCast(u32, context.locals.items.len * 6); | 178 | for (decl.fn_link.wasm.?.idx_refs.items) |*func| { |
| 149 | } | 179 | // For each local, add 6 bytes (count + type) |
| | 180 | func.offset += @intCast(u32, context.locals.items.len * 6); |
| | 181 | } |
| 150 | | 182 | |
| 151 | fn_data.functype = context.func_type_data.toUnmanaged(); | 183 | fn_data.functype = context.func_type_data.toUnmanaged(); |
| 152 | fn_data.code = context.code.toUnmanaged(); | 184 | fn_data.code = context.code.toUnmanaged(); |
| | 185 | }, |
| | 186 | .Array => switch (result) { |
| | 187 | .appended => unreachable, |
| | 188 | .externally_managed => |payload| try self.data.segments.put(self.base.allocator, decl, payload), |
| | 189 | }, |
| | 190 | else => return error.TODO, |
| | 191 | } |
| 153 | } | 192 | } |
| 154 | | 193 | |
| 155 | pub fn updateDeclExports( | 194 | pub fn updateDeclExports( |
| ... | @@ -257,6 +296,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { | ... | @@ -257,6 +296,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { |
| 257 | ); | 296 | ); |
| 258 | } | 297 | } |
| 259 | | 298 | |
| | 299 | // Memory section |
| | 300 | if (self.data.size() != 0) { |
| | 301 | const header_offset = try reserveVecSectionHeader(file); |
| | 302 | const writer = file.writer(); |
| | 303 | |
| | 304 | try leb.writeULEB128(writer, @as(u32, 0)); |
| | 305 | try leb.writeULEB128(writer, @as(u32, 1)); |
| | 306 | try writeVecSectionHeader( |
| | 307 | file, |
| | 308 | header_offset, |
| | 309 | .memory, |
| | 310 | @intCast(u32, (try file.getPos()) - header_offset - header_size), |
| | 311 | @as(u32, 1), |
| | 312 | ); |
| | 313 | } |
| | 314 | |
| 260 | // Export section | 315 | // Export section |
| 261 | if (self.base.options.module) |module| { | 316 | if (self.base.options.module) |module| { |
| 262 | const header_offset = try reserveVecSectionHeader(file); | 317 | const header_offset = try reserveVecSectionHeader(file); |
| ... | @@ -281,6 +336,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { | ... | @@ -281,6 +336,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { |
| 281 | count += 1; | 336 | count += 1; |
| 282 | } | 337 | } |
| 283 | } | 338 | } |
| | 339 | |
| | 340 | // export memory if size is not 0 |
| | 341 | if (self.data.size() != 0) { |
| | 342 | try leb.writeULEB128(writer, @intCast(u32, "memory".len)); |
| | 343 | try writer.writeAll("memory"); |
| | 344 | try writer.writeByte(wasm.externalKind(.memory)); |
| | 345 | try leb.writeULEB128(writer, @as(u32, 0)); // only 1 memory 'object' can exist |
| | 346 | count += 1; |
| | 347 | } |
| | 348 | |
| 284 | try writeVecSectionHeader( | 349 | try writeVecSectionHeader( |
| 285 | file, | 350 | file, |
| 286 | header_offset, | 351 | header_offset, |
| ... | @@ -320,6 +385,38 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { | ... | @@ -320,6 +385,38 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { |
| 320 | @intCast(u32, self.funcs.items.len), | 385 | @intCast(u32, self.funcs.items.len), |
| 321 | ); | 386 | ); |
| 322 | } | 387 | } |
| | 388 | |
| | 389 | // Data section |
| | 390 | { |
| | 391 | const header_offset = try reserveVecSectionHeader(file); |
| | 392 | const writer = file.writer(); |
| | 393 | var offset: i32 = 0; |
| | 394 | for (self.data.segments.items()) |entry| { |
| | 395 | // index to memory section (always 0 in current wasm version) |
| | 396 | try leb.writeULEB128(writer, @as(u32, 0)); |
| | 397 | |
| | 398 | // offset into data section |
| | 399 | try writer.writeByte(wasm.opcode(.i32_const)); |
| | 400 | try leb.writeILEB128(writer, offset); |
| | 401 | try writer.writeByte(wasm.opcode(.end)); |
| | 402 | |
| | 403 | // payload size |
| | 404 | const len = @intCast(u32, entry.value.len); |
| | 405 | try leb.writeULEB128(writer, len); |
| | 406 | |
| | 407 | // write payload |
| | 408 | try writer.writeAll(entry.value); |
| | 409 | offset += @bitCast(i32, len); |
| | 410 | } |
| | 411 | |
| | 412 | try writeVecSectionHeader( |
| | 413 | file, |
| | 414 | header_offset, |
| | 415 | .data, |
| | 416 | @intCast(u32, (try file.getPos()) - header_offset - header_size), |
| | 417 | @intCast(u32, self.data.segments.items().len), |
| | 418 | ); |
| | 419 | } |
| 323 | } | 420 | } |
| 324 | | 421 | |
| 325 | fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { | 422 | fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { |