| ... | @@ -16,6 +16,7 @@ const link = @import("../link.zig"); | ... | @@ -16,6 +16,7 @@ const link = @import("../link.zig"); |
| 16 | const trace = @import("../tracy.zig").trace; | 16 | const trace = @import("../tracy.zig").trace; |
| 17 | const build_options = @import("build_options"); | 17 | const build_options = @import("build_options"); |
| 18 | const Cache = @import("../Cache.zig"); | 18 | const Cache = @import("../Cache.zig"); |
| | 19 | const TypedValue = @import("../TypedValue.zig"); |
| 19 | | 20 | |
| 20 | pub const base_tag = link.File.Tag.wasm; | 21 | pub const base_tag = link.File.Tag.wasm; |
| 21 | | 22 | |
| ... | @@ -34,25 +35,33 @@ pub const FnData = struct { | ... | @@ -34,25 +35,33 @@ pub const FnData = struct { |
| 34 | /// where the offset is calculated using the previous segments and the content length | 35 | /// where the offset is calculated using the previous segments and the content length |
| 35 | /// of the data | 36 | /// of the data |
| 36 | pub const DataSection = struct { | 37 | pub const DataSection = struct { |
| 37 | segments: std.AutoArrayHashMapUnmanaged(*const Module.Decl, []const u8) = .{}, | 38 | segments: std.AutoArrayHashMapUnmanaged(*Module.Decl, struct { data: [*]const u8, len: u32 }) = .{}, |
| 38 | | 39 | |
| 39 | /// Returns the offset into the data segment based on a given `Decl` | 40 | /// Returns the offset into the data segment based on a given `Decl` |
| 40 | pub fn offset(self: DataSection, decl: *const Module.Decl) u32 { | 41 | pub fn offset(self: DataSection, decl: *const Module.Decl) u32 { |
| 41 | var cur_offset: u32 = 0; | 42 | var cur_offset: u32 = 0; |
| 42 | return for (self.segments.items()) |entry| { | 43 | return for (self.segments.items()) |entry| { |
| 43 | if (entry.key == decl) break cur_offset; | 44 | if (entry.key == decl) break cur_offset; |
| 44 | cur_offset += @intCast(u32, entry.value.len); | 45 | cur_offset += entry.value.len; |
| 45 | } else cur_offset; | 46 | } else unreachable; // offset() called on declaration that does not live inside 'data' section |
| 46 | } | 47 | } |
| 47 | | 48 | |
| 48 | /// Returns the total payload size of the data section | 49 | /// Returns the total payload size of the data section |
| 49 | pub fn size(self: DataSection) u32 { | 50 | pub fn size(self: DataSection) u32 { |
| 50 | var total: u32 = 0; | 51 | var total: u32 = 0; |
| 51 | for (self.segments.items()) |entry| { | 52 | for (self.segments.items()) |entry| { |
| 52 | total += @intCast(u32, entry.value.len); | 53 | total += entry.value.len; |
| 53 | } | 54 | } |
| 54 | return total; | 55 | return total; |
| 55 | } | 56 | } |
| | 57 | |
| | 58 | /// Updates the data in the data segment belonging to the given decl. |
| | 59 | /// It's illegal behaviour to call this before allocateDeclIndexes was called |
| | 60 | /// `data` must be managed externally with a lifetime that last as long as codegen does. |
| | 61 | pub fn updateData(self: DataSection, decl: *Module.Decl, data: []const u8) void { |
| | 62 | const entry = self.segments.getEntry(decl).?; // called updateData before the declaration was added to data segments |
| | 63 | entry.value.data = data.ptr; |
| | 64 | } |
| 56 | }; | 65 | }; |
| 57 | | 66 | |
| 58 | base: link.File, | 67 | base: link.File, |
| ... | @@ -111,49 +120,92 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm { | ... | @@ -111,49 +120,92 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm { |
| 111 | | 120 | |
| 112 | pub fn deinit(self: *Wasm) void { | 121 | pub fn deinit(self: *Wasm) void { |
| 113 | for (self.funcs.items) |decl| { | 122 | for (self.funcs.items) |decl| { |
| 114 | decl.fn_link.wasm.?.functype.deinit(self.base.allocator); | 123 | decl.fn_link.wasm.functype.deinit(self.base.allocator); |
| 115 | decl.fn_link.wasm.?.code.deinit(self.base.allocator); | 124 | decl.fn_link.wasm.code.deinit(self.base.allocator); |
| 116 | decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); | 125 | decl.fn_link.wasm.idx_refs.deinit(self.base.allocator); |
| 117 | } | 126 | } |
| 118 | for (self.ext_funcs.items) |decl| { | 127 | for (self.ext_funcs.items) |decl| { |
| 119 | decl.fn_link.wasm.?.functype.deinit(self.base.allocator); | 128 | decl.fn_link.wasm.functype.deinit(self.base.allocator); |
| 120 | decl.fn_link.wasm.?.code.deinit(self.base.allocator); | 129 | decl.fn_link.wasm.code.deinit(self.base.allocator); |
| 121 | decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); | 130 | decl.fn_link.wasm.idx_refs.deinit(self.base.allocator); |
| | 131 | } |
| | 132 | for (self.data.segments.items()) |entry| { |
| | 133 | // data segments only use the code section |
| | 134 | entry.key.fn_link.wasm.code.deinit(self.base.allocator); |
| 122 | } | 135 | } |
| 123 | self.funcs.deinit(self.base.allocator); | 136 | self.funcs.deinit(self.base.allocator); |
| 124 | self.ext_funcs.deinit(self.base.allocator); | 137 | self.ext_funcs.deinit(self.base.allocator); |
| 125 | self.data.segments.deinit(self.base.allocator); | 138 | self.data.segments.deinit(self.base.allocator); |
| 126 | } | 139 | } |
| 127 | | 140 | |
| | 141 | pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void { |
| | 142 | std.debug.print("INIT: '{s}'\n", .{decl.name}); |
| | 143 | const tv = decl.typed_value.most_recent.typed_value; |
| | 144 | decl.fn_link.wasm = .{}; |
| | 145 | |
| | 146 | switch (tv.ty.zigTypeTag()) { |
| | 147 | .Array => { |
| | 148 | // if the codegen of the given decl contributes to the data segment |
| | 149 | // we must calculate its data length now so that the data offsets are available |
| | 150 | // to other decls when called |
| | 151 | const data_len = calcDataLen(self, tv) catch return error.AnalysisFail; |
| | 152 | try self.data.segments.putNoClobber(self.base.allocator, decl, .{ .data = undefined, .len = data_len }); |
| | 153 | }, |
| | 154 | .Fn => if (self.getFuncidx(decl) == null) switch (tv.val.tag()) { |
| | 155 | // dependent on function type, appends it to the correct list |
| | 156 | .function => try self.funcs.append(self.base.allocator, decl), |
| | 157 | .extern_fn => try self.ext_funcs.append(self.base.allocator, decl), |
| | 158 | else => unreachable, |
| | 159 | }, |
| | 160 | else => {}, |
| | 161 | } |
| | 162 | } |
| | 163 | |
| | 164 | // TODO, remove this and use the existing error mechanism |
| | 165 | const DataLenError = error{ |
| | 166 | TODO_WASM_CalcDataLenArray, |
| | 167 | TODO_WASM_CalcDataLen, |
| | 168 | }; |
| | 169 | /// Calculates the length of the data segment that will be occupied by the given `TypedValue` |
| | 170 | fn calcDataLen(bin_file: *Wasm, typed_value: TypedValue) DataLenError!u32 { |
| | 171 | switch (typed_value.ty.zigTypeTag()) { |
| | 172 | .Array => { |
| | 173 | if (typed_value.val.castTag(.bytes)) |payload| { |
| | 174 | if (typed_value.ty.sentinel()) |sentinel| { |
| | 175 | return @intCast(u32, payload.data.len) + try calcDataLen(bin_file, .{ |
| | 176 | .ty = typed_value.ty.elemType(), |
| | 177 | .val = sentinel, |
| | 178 | }); |
| | 179 | } |
| | 180 | return @intCast(u32, payload.data.len); |
| | 181 | } |
| | 182 | return error.TODO_WASM_CalcDataLenArray; |
| | 183 | }, |
| | 184 | .Int => { |
| | 185 | const info = typed_value.ty.intInfo(bin_file.base.options.target); |
| | 186 | return info.bits / 8; |
| | 187 | }, |
| | 188 | .Pointer => return 4, |
| | 189 | else => return error.TODO_WASM_CalcDataLen, |
| | 190 | } |
| | 191 | } |
| | 192 | |
| 128 | // Generate code for the Decl, storing it in memory to be later written to | 193 | // Generate code for the Decl, storing it in memory to be later written to |
| 129 | // the file on flush(). | 194 | // the file on flush(). |
| 130 | pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { | 195 | pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { |
| | 196 | std.debug.print("Updating '{s}'\n", .{decl.name}); |
| 131 | const typed_value = decl.typed_value.most_recent.typed_value; | 197 | const typed_value = decl.typed_value.most_recent.typed_value; |
| 132 | | 198 | |
| 133 | if (decl.fn_link.wasm) |*fn_data| { | 199 | const fn_data = &decl.fn_link.wasm; |
| 134 | fn_data.functype.items.len = 0; | 200 | fn_data.functype.items.len = 0; |
| 135 | fn_data.code.items.len = 0; | 201 | fn_data.code.items.len = 0; |
| 136 | fn_data.idx_refs.items.len = 0; | 202 | fn_data.idx_refs.items.len = 0; |
| 137 | } else { | | |
| 138 | decl.fn_link.wasm = .{}; | | |
| 139 | // dependent on function type, appends it to the correct list | | |
| 140 | switch (decl.typed_value.most_recent.typed_value.val.tag()) { | | |
| 141 | .function => try self.funcs.append(self.base.allocator, decl), | | |
| 142 | .extern_fn => try self.ext_funcs.append(self.base.allocator, decl), | | |
| 143 | .bytes => {}, | | |
| 144 | else => return error.TODOImplementNonFnDeclsForWasm, | | |
| 145 | } | | |
| 146 | } | | |
| 147 | const fn_data = &decl.fn_link.wasm.?; | | |
| 148 | | | |
| 149 | var managed_functype = fn_data.functype.toManaged(self.base.allocator); | | |
| 150 | var managed_code = fn_data.code.toManaged(self.base.allocator); | | |
| 151 | | 203 | |
| 152 | var context = codegen.Context{ | 204 | var context = codegen.Context{ |
| 153 | .gpa = self.base.allocator, | 205 | .gpa = self.base.allocator, |
| 154 | .values = .{}, | 206 | .values = .{}, |
| 155 | .code = managed_code, | 207 | .code = fn_data.code.toManaged(self.base.allocator), |
| 156 | .func_type_data = managed_functype, | 208 | .func_type_data = fn_data.functype.toManaged(self.base.allocator), |
| 157 | .decl = decl, | 209 | .decl = decl, |
| 158 | .err_msg = undefined, | 210 | .err_msg = undefined, |
| 159 | .locals = .{}, | 211 | .locals = .{}, |
| ... | @@ -162,7 +214,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { | ... | @@ -162,7 +214,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { |
| 162 | defer context.deinit(); | 214 | defer context.deinit(); |
| 163 | | 215 | |
| 164 | // generate the 'code' section for the function declaration | 216 | // generate the 'code' section for the function declaration |
| 165 | const result = context.gen() catch |err| switch (err) { | 217 | const result = context.gen(typed_value) catch |err| switch (err) { |
| 166 | error.CodegenFail => { | 218 | error.CodegenFail => { |
| 167 | decl.analysis = .codegen_failure; | 219 | decl.analysis = .codegen_failure; |
| 168 | try module.failed_decls.put(module.gpa, decl, context.err_msg); | 220 | try module.failed_decls.put(module.gpa, decl, context.err_msg); |
| ... | @@ -175,7 +227,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { | ... | @@ -175,7 +227,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { |
| 175 | .Fn => { | 227 | .Fn => { |
| 176 | // as locals are patched afterwards, the offsets of funcidx's are off, | 228 | // as locals are patched afterwards, the offsets of funcidx's are off, |
| 177 | // here we update them to correct them | 229 | // here we update them to correct them |
| 178 | for (decl.fn_link.wasm.?.idx_refs.items) |*func| { | 230 | for (decl.fn_link.wasm.idx_refs.items) |*func| { |
| 179 | // For each local, add 6 bytes (count + type) | 231 | // For each local, add 6 bytes (count + type) |
| 180 | func.offset += @intCast(u32, context.locals.items.len * 6); | 232 | func.offset += @intCast(u32, context.locals.items.len * 6); |
| 181 | } | 233 | } |
| ... | @@ -184,8 +236,12 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { | ... | @@ -184,8 +236,12 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { |
| 184 | fn_data.code = context.code.toUnmanaged(); | 236 | fn_data.code = context.code.toUnmanaged(); |
| 185 | }, | 237 | }, |
| 186 | .Array => switch (result) { | 238 | .Array => switch (result) { |
| 187 | .appended => unreachable, | 239 | .appended => { |
| 188 | .externally_managed => |payload| try self.data.segments.put(self.base.allocator, decl, payload), | 240 | fn_data.functype = context.func_type_data.toUnmanaged(); |
| | 241 | fn_data.code = context.code.toUnmanaged(); |
| | 242 | self.data.updateData(decl, fn_data.code.items); |
| | 243 | }, |
| | 244 | .externally_managed => |payload| self.data.updateData(decl, payload), |
| 189 | }, | 245 | }, |
| 190 | else => return error.TODO, | 246 | else => return error.TODO, |
| 191 | } | 247 | } |
| ... | @@ -199,18 +255,18 @@ pub fn updateDeclExports( | ... | @@ -199,18 +255,18 @@ pub fn updateDeclExports( |
| 199 | ) !void {} | 255 | ) !void {} |
| 200 | | 256 | |
| 201 | pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { | 257 | pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { |
| 202 | // TODO: remove this assert when non-function Decls are implemented | 258 | if (self.getFuncidx(decl)) |func_idx| { |
| 203 | assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn); | 259 | switch (decl.typed_value.most_recent.typed_value.val.tag()) { |
| 204 | const func_idx = self.getFuncidx(decl).?; | 260 | .function => _ = self.funcs.swapRemove(func_idx), |
| 205 | switch (decl.typed_value.most_recent.typed_value.val.tag()) { | 261 | .extern_fn => _ = self.ext_funcs.swapRemove(func_idx), |
| 206 | .function => _ = self.funcs.swapRemove(func_idx), | 262 | else => unreachable, |
| 207 | .extern_fn => _ = self.ext_funcs.swapRemove(func_idx), | 263 | } |
| 208 | else => unreachable, | | |
| 209 | } | 264 | } |
| 210 | decl.fn_link.wasm.?.functype.deinit(self.base.allocator); | 265 | decl.fn_link.wasm.functype.deinit(self.base.allocator); |
| 211 | decl.fn_link.wasm.?.code.deinit(self.base.allocator); | 266 | decl.fn_link.wasm.code.deinit(self.base.allocator); |
| 212 | decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); | 267 | decl.fn_link.wasm.idx_refs.deinit(self.base.allocator); |
| 213 | decl.fn_link.wasm = null; | 268 | _ = self.data.segments.orderedRemove(decl); |
| | 269 | decl.fn_link.wasm = undefined; |
| 214 | } | 270 | } |
| 215 | | 271 | |
| 216 | pub fn flush(self: *Wasm, comp: *Compilation) !void { | 272 | pub fn flush(self: *Wasm, comp: *Compilation) !void { |
| ... | @@ -238,8 +294,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { | ... | @@ -238,8 +294,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { |
| 238 | | 294 | |
| 239 | // extern functions are defined in the wasm binary first through the `import` | 295 | // extern functions are defined in the wasm binary first through the `import` |
| 240 | // section, so define their func types first | 296 | // section, so define their func types first |
| 241 | for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items); | 297 | for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.functype.items); |
| 242 | for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items); | 298 | for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.functype.items); |
| 243 | | 299 | |
| 244 | try writeVecSectionHeader( | 300 | try writeVecSectionHeader( |
| 245 | file, | 301 | file, |
| ... | @@ -302,13 +358,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { | ... | @@ -302,13 +358,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { |
| 302 | const writer = file.writer(); | 358 | const writer = file.writer(); |
| 303 | | 359 | |
| 304 | try leb.writeULEB128(writer, @as(u32, 0)); | 360 | try leb.writeULEB128(writer, @as(u32, 0)); |
| 305 | try leb.writeULEB128(writer, @as(u32, 1)); | 361 | // Calculate the amount of memory pages are required and write them. |
| | 362 | // Wasm uses 64kB page sizes. Round up to ensure the data segments fit into the memory |
| | 363 | try leb.writeULEB128( |
| | 364 | writer, |
| | 365 | try std.math.divCeil( |
| | 366 | u32, |
| | 367 | self.data.size(), |
| | 368 | std.mem.page_size, |
| | 369 | ), |
| | 370 | ); |
| 306 | try writeVecSectionHeader( | 371 | try writeVecSectionHeader( |
| 307 | file, | 372 | file, |
| 308 | header_offset, | 373 | header_offset, |
| 309 | .memory, | 374 | .memory, |
| 310 | @intCast(u32, (try file.getPos()) - header_offset - header_size), | 375 | @intCast(u32, (try file.getPos()) - header_offset - header_size), |
| 311 | @as(u32, 1), | 376 | @as(u32, 1), // wasm currently only supports 1 linear memory segment |
| 312 | ); | 377 | ); |
| 313 | } | 378 | } |
| 314 | | 379 | |
| ... | @@ -360,7 +425,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { | ... | @@ -360,7 +425,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { |
| 360 | const header_offset = try reserveVecSectionHeader(file); | 425 | const header_offset = try reserveVecSectionHeader(file); |
| 361 | const writer = file.writer(); | 426 | const writer = file.writer(); |
| 362 | for (self.funcs.items) |decl| { | 427 | for (self.funcs.items) |decl| { |
| 363 | const fn_data = &decl.fn_link.wasm.?; | 428 | const fn_data = &decl.fn_link.wasm; |
| 364 | | 429 | |
| 365 | // Write the already generated code to the file, inserting | 430 | // Write the already generated code to the file, inserting |
| 366 | // function indexes where required. | 431 | // function indexes where required. |
| ... | @@ -387,34 +452,30 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { | ... | @@ -387,34 +452,30 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { |
| 387 | } | 452 | } |
| 388 | | 453 | |
| 389 | // Data section | 454 | // Data section |
| 390 | { | 455 | if (self.data.size() != 0) { |
| 391 | const header_offset = try reserveVecSectionHeader(file); | 456 | const header_offset = try reserveVecSectionHeader(file); |
| 392 | const writer = file.writer(); | 457 | const writer = file.writer(); |
| 393 | var offset: i32 = 0; | 458 | var len: u32 = 0; |
| 394 | for (self.data.segments.items()) |entry| { | 459 | // index to memory section (currently, there can only be 1 memory section in wasm) |
| 395 | // index to memory section (always 0 in current wasm version) | 460 | try leb.writeULEB128(writer, @as(u32, 0)); |
| 396 | try leb.writeULEB128(writer, @as(u32, 0)); | 461 | |
| 397 | | 462 | // offset into data section |
| 398 | // offset into data section | 463 | try writer.writeByte(wasm.opcode(.i32_const)); |
| 399 | try writer.writeByte(wasm.opcode(.i32_const)); | 464 | try leb.writeILEB128(writer, @as(i32, 0)); |
| 400 | try leb.writeILEB128(writer, offset); | 465 | try writer.writeByte(wasm.opcode(.end)); |
| 401 | try writer.writeByte(wasm.opcode(.end)); | 466 | |
| 402 | | 467 | // payload size |
| 403 | // payload size | 468 | try leb.writeULEB128(writer, self.data.size()); |
| 404 | const len = @intCast(u32, entry.value.len); | 469 | |
| 405 | try leb.writeULEB128(writer, len); | 470 | // write payload |
| 406 | | 471 | for (self.data.segments.items()) |entry| try writer.writeAll(entry.value.data[0..entry.value.len]); |
| 407 | // write payload | | |
| 408 | try writer.writeAll(entry.value); | | |
| 409 | offset += @bitCast(i32, len); | | |
| 410 | } | | |
| 411 | | 472 | |
| 412 | try writeVecSectionHeader( | 473 | try writeVecSectionHeader( |
| 413 | file, | 474 | file, |
| 414 | header_offset, | 475 | header_offset, |
| 415 | .data, | 476 | .data, |
| 416 | @intCast(u32, (try file.getPos()) - header_offset - header_size), | 477 | @intCast(u32, (try file.getPos()) - header_offset - header_size), |
| 417 | @intCast(u32, self.data.segments.items().len), | 478 | @intCast(u32, 1), |
| 418 | ); | 479 | ); |
| 419 | } | 480 | } |
| 420 | } | 481 | } |
| ... | @@ -681,7 +742,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { | ... | @@ -681,7 +742,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { |
| 681 | /// Get the current index of a given Decl in the function list | 742 | /// Get the current index of a given Decl in the function list |
| 682 | /// This will correctly provide the index, regardless whether the function is extern or not | 743 | /// This will correctly provide the index, regardless whether the function is extern or not |
| 683 | /// TODO: we could maintain a hash map to potentially make this simpler | 744 | /// TODO: we could maintain a hash map to potentially make this simpler |
| 684 | fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 { | 745 | fn getFuncidx(self: Wasm, decl: *const Module.Decl) ?u32 { |
| 685 | var offset: u32 = 0; | 746 | var offset: u32 = 0; |
| 686 | const slice = switch (decl.typed_value.most_recent.typed_value.val.tag()) { | 747 | const slice = switch (decl.typed_value.most_recent.typed_value.val.tag()) { |
| 687 | .function => blk: { | 748 | .function => blk: { |