| author | |
| committer | |
| log | 00b2e31589b2f4c3f67ab2bf46e140e00df3f910 |
| tree | 50531677b96e547c4a0b8219b807c0be6eee5748 |
| parent | 9f744f19e7036df9a410f780f3394f6669b8079e |
| signature |
2 files changed, 187 insertions(+), 66 deletions(-)
src/codegen/wasm.zig+78-54| ... | ... | @@ -163,25 +163,23 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 163 | 163 | .global_get => return .global_get, |
| 164 | 164 | .global_set => return .global_set, |
| 165 | 165 | |
| 166 | .load => if (args.width) |width| | |
| 167 | switch (width) { | |
| 168 | 8 => switch (args.valtype1.?) { | |
| 169 | .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u, | |
| 170 | .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u, | |
| 171 | .f32, .f64 => unreachable, | |
| 172 | }, | |
| 173 | 16 => switch (args.valtype1.?) { | |
| 174 | .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u, | |
| 175 | .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u, | |
| 176 | .f32, .f64 => unreachable, | |
| 177 | }, | |
| 178 | 32 => switch (args.valtype1.?) { | |
| 179 | .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u, | |
| 180 | .i32, .f32, .f64 => unreachable, | |
| 181 | }, | |
| 182 | else => unreachable, | |
| 183 | } | |
| 184 | else switch (args.valtype1.?) { | |
| 166 | .load => if (args.width) |width| switch (width) { | |
| 167 | 8 => switch (args.valtype1.?) { | |
| 168 | .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u, | |
| 169 | .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u, | |
| 170 | .f32, .f64 => unreachable, | |
| 171 | }, | |
| 172 | 16 => switch (args.valtype1.?) { | |
| 173 | .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u, | |
| 174 | .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u, | |
| 175 | .f32, .f64 => unreachable, | |
| 176 | }, | |
| 177 | 32 => switch (args.valtype1.?) { | |
| 178 | .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u, | |
| 179 | .i32, .f32, .f64 => unreachable, | |
| 180 | }, | |
| 181 | else => unreachable, | |
| 182 | } else switch (args.valtype1.?) { | |
| 185 | 183 | .i32 => return .i32_load, |
| 186 | 184 | .i64 => return .i64_load, |
| 187 | 185 | .f32 => return .f32_load, |
| ... | ... | @@ -469,6 +467,13 @@ test "Wasm - buildOpcode" { |
| 469 | 467 | testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64); |
| 470 | 468 | } |
| 471 | 469 | |
| 470 | pub const Result = union(enum) { | |
| 471 | /// The codegen bytes have been appended to `Context.code` | |
| 472 | appended: void, | |
| 473 | /// The data is managed externally and are part of the `Result` | |
| 474 | externally_managed: []const u8, | |
| 475 | }; | |
| 476 | ||
| 472 | 477 | /// Hashmap to store generated `WValue` for each `Inst` |
| 473 | 478 | pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue); |
| 474 | 479 | |
| ... | ... | @@ -504,6 +509,8 @@ pub const Context = struct { |
| 504 | 509 | const InnerError = error{ |
| 505 | 510 | OutOfMemory, |
| 506 | 511 | CodegenFail, |
| 512 | /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed | |
| 513 | AnalysisFail, | |
| 507 | 514 | }; |
| 508 | 515 | |
| 509 | 516 | pub fn deinit(self: *Context) void { |
| ... | ... | @@ -604,48 +611,65 @@ pub const Context = struct { |
| 604 | 611 | } |
| 605 | 612 | |
| 606 | 613 | /// Generates the wasm bytecode for the function declaration belonging to `Context` |
| 607 | pub fn gen(self: *Context) InnerError!void { | |
| 614 | pub fn gen(self: *Context) InnerError!Result { | |
| 608 | 615 | assert(self.code.items.len == 0); |
| 609 | try self.genFunctype(); | |
| 610 | 616 | |
| 611 | // Write instructions | |
| 612 | // TODO: check for and handle death of instructions | |
| 613 | 617 | const tv = self.decl.typed_value.most_recent.typed_value; |
| 614 | const mod_fn = blk: { | |
| 615 | if (tv.val.castTag(.function)) |func| break :blk func.data; | |
| 616 | if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions | |
| 617 | return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()}); | |
| 618 | }; | |
| 619 | ||
| 620 | // Reserve space to write the size after generating the code as well as space for locals count | |
| 621 | try self.code.resize(10); | |
| 622 | ||
| 623 | try self.genBody(mod_fn.body); | |
| 618 | switch (tv.ty.zigTypeTag()) { | |
| 619 | .Fn => { | |
| 620 | try self.genFunctype(); | |
| 621 | ||
| 622 | // Write instructions | |
| 623 | // TODO: check for and handle death of instructions | |
| 624 | const mod_fn = blk: { | |
| 625 | if (tv.val.castTag(.function)) |func| break :blk func.data; | |
| 626 | if (tv.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions | |
| 627 | return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()}); | |
| 628 | }; | |
| 629 | ||
| 630 | // Reserve space to write the size after generating the code as well as space for locals count | |
| 631 | try self.code.resize(10); | |
| 632 | ||
| 633 | try self.genBody(mod_fn.body); | |
| 634 | ||
| 635 | // finally, write our local types at the 'offset' position | |
| 636 | { | |
| 637 | leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len)); | |
| 638 | ||
| 639 | // offset into 'code' section where we will put our locals types | |
| 640 | var local_offset: usize = 10; | |
| 641 | ||
| 642 | // emit the actual locals amount | |
| 643 | for (self.locals.items) |local| { | |
| 644 | var buf: [6]u8 = undefined; | |
| 645 | leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1)); | |
| 646 | buf[5] = local; | |
| 647 | try self.code.insertSlice(local_offset, &buf); | |
| 648 | local_offset += 6; | |
| 649 | } | |
| 650 | } | |
| 624 | 651 | |
| 625 | // finally, write our local types at the 'offset' position | |
| 626 | { | |
| 627 | leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len)); | |
| 652 | const writer = self.code.writer(); | |
| 653 | try writer.writeByte(wasm.opcode(.end)); | |
| 628 | 654 | |
| 629 | // offset into 'code' section where we will put our locals types | |
| 630 | var local_offset: usize = 10; | |
| 655 | // Fill in the size of the generated code to the reserved space at the | |
| 656 | // beginning of the buffer. | |
| 657 | const size = self.code.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5; | |
| 658 | leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size)); | |
| 631 | 659 | |
| 632 | // emit the actual locals amount | |
| 633 | for (self.locals.items) |local| { | |
| 634 | var buf: [6]u8 = undefined; | |
| 635 | leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1)); | |
| 636 | buf[5] = local; | |
| 637 | try self.code.insertSlice(local_offset, &buf); | |
| 638 | local_offset += 6; | |
| 639 | } | |
| 660 | // codegen data has been appended to `code` | |
| 661 | return Result.appended; | |
| 662 | }, | |
| 663 | .Array => { | |
| 664 | if (tv.val.castTag(.bytes)) |payload| { | |
| 665 | if (tv.ty.sentinel()) |sentinel| { | |
| 666 | // TODO, handle sentinel correctly | |
| 667 | } | |
| 668 | return Result{ .externally_managed = payload.data }; | |
| 669 | } else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{}); | |
| 670 | }, | |
| 671 | else => |tag| return self.fail(.{ .node_offset = 0 }, "TODO: Implement zig type codegen for type: '{s}'", .{tag}), | |
| 640 | 672 | } |
| 641 | ||
| 642 | const writer = self.code.writer(); | |
| 643 | try writer.writeByte(wasm.opcode(.end)); | |
| 644 | ||
| 645 | // Fill in the size of the generated code to the reserved space at the | |
| 646 | // beginning of the buffer. | |
| 647 | const size = self.code.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5; | |
| 648 | leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size)); | |
| 649 | 673 | } |
| 650 | 674 | |
| 651 | 675 | fn genInst(self: *Context, inst: *Inst) InnerError!WValue { |
src/link/Wasm.zig+109-12| ... | ... | @@ -29,6 +29,32 @@ pub const FnData = struct { |
| 29 | 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 | 58 | base: link.File, |
| 33 | 59 | |
| 34 | 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 | 71 | /// to support existing code. |
| 46 | 72 | /// TODO: Allow setting this through a flag? |
| 47 | 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 | 79 | pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm { |
| 50 | 80 | assert(options.object_format == .wasm); |
| ... | ... | @@ -52,7 +82,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio |
| 52 | 82 | if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO |
| 53 | 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 | 86 | const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true }); |
| 57 | 87 | errdefer file.close(); |
| 58 | 88 | |
| ... | ... | @@ -92,14 +122,13 @@ pub fn deinit(self: *Wasm) void { |
| 92 | 122 | } |
| 93 | 123 | self.funcs.deinit(self.base.allocator); |
| 94 | 124 | self.ext_funcs.deinit(self.base.allocator); |
| 125 | self.data.segments.deinit(self.base.allocator); | |
| 95 | 126 | } |
| 96 | 127 | |
| 97 | 128 | // Generate code for the Decl, storing it in memory to be later written to |
| 98 | 129 | // the file on flush(). |
| 99 | 130 | pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { |
| 100 | 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 | 133 | if (decl.fn_link.wasm) |*fn_data| { |
| 105 | 134 | fn_data.functype.items.len = 0; |
| ... | ... | @@ -111,6 +140,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { |
| 111 | 140 | switch (decl.typed_value.most_recent.typed_value.val.tag()) { |
| 112 | 141 | .function => try self.funcs.append(self.base.allocator, decl), |
| 113 | 142 | .extern_fn => try self.ext_funcs.append(self.base.allocator, decl), |
| 143 | .bytes => {}, | |
| 114 | 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 | 162 | defer context.deinit(); |
| 133 | 163 | |
| 134 | 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 | 166 | error.CodegenFail => { |
| 137 | 167 | decl.analysis = .codegen_failure; |
| 138 | 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 | 171 | else => |e| return err, |
| 142 | 172 | }; |
| 143 | 173 | |
| 144 | // as locals are patched afterwards, the offsets of funcidx's are off, | |
| 145 | // here we update them to correct them | |
| 146 | for (decl.fn_link.wasm.?.idx_refs.items) |*func| { | |
| 147 | // For each local, add 6 bytes (count + type) | |
| 148 | func.offset += @intCast(u32, context.locals.items.len * 6); | |
| 149 | } | |
| 174 | switch (typed_value.ty.zigTypeTag()) { | |
| 175 | .Fn => { | |
| 176 | // as locals are patched afterwards, the offsets of funcidx's are off, | |
| 177 | // here we update them to correct them | |
| 178 | for (decl.fn_link.wasm.?.idx_refs.items) |*func| { | |
| 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(); | |
| 152 | fn_data.code = context.code.toUnmanaged(); | |
| 183 | fn_data.functype = context.func_type_data.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 | 194 | pub fn updateDeclExports( |
| ... | ... | @@ -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 | 315 | // Export section |
| 261 | 316 | if (self.base.options.module) |module| { |
| 262 | 317 | const header_offset = try reserveVecSectionHeader(file); |
| ... | ... | @@ -281,6 +336,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { |
| 281 | 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 | 349 | try writeVecSectionHeader( |
| 285 | 350 | file, |
| 286 | 351 | header_offset, |
| ... | ... | @@ -320,6 +385,38 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { |
| 320 | 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 | 422 | fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { |