| author | |
| committer | |
| log | b3b00ec62f518875a486b4da532f74e304c3aba2 |
| tree | 5ae6f6dd8afba7cacd3d5b1532d4c25b3661189d |
| parent | 4fdfaf69c8c55ebac4c5b3c00025c0ac51281b5b |
| parent | 8899e6e334758f2e101399075d0456195035c372 |
12 files changed, 916 insertions(+), 238 deletions(-)
lib/std/fmt.zig+2| ... | ... | @@ -88,6 +88,8 @@ pub fn format( |
| 88 | 88 | if (args.len > ArgSetType.bit_count) { |
| 89 | 89 | @compileError("32 arguments max are supported per format call"); |
| 90 | 90 | } |
| 91 | if (args.len == 0) | |
| 92 | return writer.writeAll(fmt); | |
| 91 | 93 | |
| 92 | 94 | const State = enum { |
| 93 | 95 | Start, |
src-self-hosted/Module.zig+66-19| ... | ... | @@ -47,7 +47,6 @@ export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, |
| 47 | 47 | /// Maps fully qualified namespaced names to the Decl struct for them. |
| 48 | 48 | decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{}, |
| 49 | 49 | |
| 50 | optimize_mode: std.builtin.Mode, | |
| 51 | 50 | link_error_flags: link.File.ErrorFlags = .{}, |
| 52 | 51 | |
| 53 | 52 | work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), |
| ... | ... | @@ -385,18 +384,6 @@ pub const Scope = struct { |
| 385 | 384 | }; |
| 386 | 385 | } |
| 387 | 386 | |
| 388 | pub fn dumpInst(self: *Scope, inst: *Inst) void { | |
| 389 | const zir_module = self.namespace(); | |
| 390 | const loc = std.zig.findLineColumn(zir_module.source.bytes, inst.src); | |
| 391 | std.debug.warn("{}:{}:{}: {}: ty={}\n", .{ | |
| 392 | zir_module.sub_file_path, | |
| 393 | loc.line + 1, | |
| 394 | loc.column + 1, | |
| 395 | @tagName(inst.tag), | |
| 396 | inst.ty, | |
| 397 | }); | |
| 398 | } | |
| 399 | ||
| 400 | 387 | /// Asserts the scope has a parent which is a ZIRModule or File and |
| 401 | 388 | /// returns the sub_file_path field. |
| 402 | 389 | pub fn subFilePath(base: *Scope) []const u8 { |
| ... | ... | @@ -802,6 +789,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 802 | 789 | .output_mode = options.output_mode, |
| 803 | 790 | .link_mode = options.link_mode orelse .Static, |
| 804 | 791 | .object_format = options.object_format orelse options.target.getObjectFormat(), |
| 792 | .optimize_mode = options.optimize_mode, | |
| 805 | 793 | }); |
| 806 | 794 | errdefer bin_file.destroy(); |
| 807 | 795 | |
| ... | ... | @@ -838,7 +826,6 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 838 | 826 | .bin_file_dir = bin_file_dir, |
| 839 | 827 | .bin_file_path = options.bin_file_path, |
| 840 | 828 | .bin_file = bin_file, |
| 841 | .optimize_mode = options.optimize_mode, | |
| 842 | 829 | .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa), |
| 843 | 830 | .keep_source_files_loaded = options.keep_source_files_loaded, |
| 844 | 831 | }; |
| ... | ... | @@ -894,7 +881,11 @@ fn freeExportList(gpa: *Allocator, export_list: []*Export) void { |
| 894 | 881 | } |
| 895 | 882 | |
| 896 | 883 | pub fn target(self: Module) std.Target { |
| 897 | return self.bin_file.options().target; | |
| 884 | return self.bin_file.options.target; | |
| 885 | } | |
| 886 | ||
| 887 | pub fn optimizeMode(self: Module) std.builtin.Mode { | |
| 888 | return self.bin_file.options.optimize_mode; | |
| 898 | 889 | } |
| 899 | 890 | |
| 900 | 891 | /// Detect changes to source files, perform semantic analysis, and update the output files. |
| ... | ... | @@ -1991,14 +1982,14 @@ pub fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { |
| 1991 | 1982 | pub fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst { |
| 1992 | 1983 | return self.constInst(scope, src, .{ |
| 1993 | 1984 | .ty = Type.initTag(.void), |
| 1994 | .val = Value.initTag(.the_one_possible_value), | |
| 1985 | .val = Value.initTag(.void_value), | |
| 1995 | 1986 | }); |
| 1996 | 1987 | } |
| 1997 | 1988 | |
| 1998 | 1989 | pub fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst { |
| 1999 | 1990 | return self.constInst(scope, src, .{ |
| 2000 | 1991 | .ty = Type.initTag(.noreturn), |
| 2001 | .val = Value.initTag(.the_one_possible_value), | |
| 1992 | .val = Value.initTag(.unreachable_value), | |
| 2002 | 1993 | }); |
| 2003 | 1994 | } |
| 2004 | 1995 | |
| ... | ... | @@ -2151,7 +2142,8 @@ pub fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_sr |
| 2151 | 2142 | }); |
| 2152 | 2143 | } |
| 2153 | 2144 | |
| 2154 | return self.fail(scope, src, "TODO implement runtime deref", .{}); | |
| 2145 | const b = try self.requireRuntimeBlock(scope, src); | |
| 2146 | return self.addUnOp(b, src, elem_ty, .load, ptr); | |
| 2155 | 2147 | } |
| 2156 | 2148 | |
| 2157 | 2149 | pub fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst { |
| ... | ... | @@ -2161,7 +2153,8 @@ pub fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: |
| 2161 | 2153 | } |
| 2162 | 2154 | |
| 2163 | 2155 | pub fn wantSafety(self: *Module, scope: *Scope) bool { |
| 2164 | return switch (self.optimize_mode) { | |
| 2156 | // TODO take into account scope's safety overrides | |
| 2157 | return switch (self.optimizeMode()) { | |
| 2165 | 2158 | .Debug => true, |
| 2166 | 2159 | .ReleaseSafe => true, |
| 2167 | 2160 | .ReleaseFast => false, |
| ... | ... | @@ -2504,6 +2497,22 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst |
| 2504 | 2497 | return self.fail(scope, inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type }); |
| 2505 | 2498 | } |
| 2506 | 2499 | |
| 2500 | pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_value: *Inst) !*Inst { | |
| 2501 | if (ptr.ty.isConstPtr()) | |
| 2502 | return self.fail(scope, src, "cannot assign to constant", .{}); | |
| 2503 | ||
| 2504 | const elem_ty = ptr.ty.elemType(); | |
| 2505 | const value = try self.coerce(scope, elem_ty, uncasted_value); | |
| 2506 | if (elem_ty.onePossibleValue() != null) | |
| 2507 | return self.constVoid(scope, src); | |
| 2508 | ||
| 2509 | // TODO handle comptime pointer writes | |
| 2510 | // TODO handle if the element type requires comptime | |
| 2511 | ||
| 2512 | const b = try self.requireRuntimeBlock(scope, src); | |
| 2513 | return self.addBinOp(b, src, Type.initTag(.void), .store, ptr, value); | |
| 2514 | } | |
| 2515 | ||
| 2507 | 2516 | pub fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { |
| 2508 | 2517 | if (inst.value()) |val| { |
| 2509 | 2518 | // Keep the comptime Value representation; take the new type. |
| ... | ... | @@ -2780,3 +2789,41 @@ pub fn singleMutPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) |
| 2780 | 2789 | type_payload.* = .{ .pointee_type = elem_ty }; |
| 2781 | 2790 | return Type.initPayload(&type_payload.base); |
| 2782 | 2791 | } |
| 2792 | ||
| 2793 | pub fn singleConstPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) error{OutOfMemory}!Type { | |
| 2794 | const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer); | |
| 2795 | type_payload.* = .{ .pointee_type = elem_ty }; | |
| 2796 | return Type.initPayload(&type_payload.base); | |
| 2797 | } | |
| 2798 | ||
| 2799 | pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void { | |
| 2800 | const zir_module = scope.namespace(); | |
| 2801 | const source = zir_module.getSource(self) catch @panic("dumpInst failed to get source"); | |
| 2802 | const loc = std.zig.findLineColumn(source, inst.src); | |
| 2803 | if (inst.tag == .constant) { | |
| 2804 | std.debug.warn("constant ty={} val={} src={}:{}:{}\n", .{ | |
| 2805 | inst.ty, | |
| 2806 | inst.castTag(.constant).?.val, | |
| 2807 | zir_module.subFilePath(), | |
| 2808 | loc.line + 1, | |
| 2809 | loc.column + 1, | |
| 2810 | }); | |
| 2811 | } else if (inst.deaths == 0) { | |
| 2812 | std.debug.warn("{} ty={} src={}:{}:{}\n", .{ | |
| 2813 | @tagName(inst.tag), | |
| 2814 | inst.ty, | |
| 2815 | zir_module.subFilePath(), | |
| 2816 | loc.line + 1, | |
| 2817 | loc.column + 1, | |
| 2818 | }); | |
| 2819 | } else { | |
| 2820 | std.debug.warn("{} ty={} deaths={b} src={}:{}:{}\n", .{ | |
| 2821 | @tagName(inst.tag), | |
| 2822 | inst.ty, | |
| 2823 | inst.deaths, | |
| 2824 | zir_module.subFilePath(), | |
| 2825 | loc.line + 1, | |
| 2826 | loc.column + 1, | |
| 2827 | }); | |
| 2828 | } | |
| 2829 | } |
src-self-hosted/astgen.zig+23-14| ... | ... | @@ -87,7 +87,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr |
| 87 | 87 | .ArrayCat => return simpleBinOp(mod, scope, rl, node.castTag(.ArrayCat).?, .array_cat), |
| 88 | 88 | .ArrayMult => return simpleBinOp(mod, scope, rl, node.castTag(.ArrayMult).?, .array_mul), |
| 89 | 89 | |
| 90 | .Identifier => return rlWrap(mod, scope, rl, try identifier(mod, scope, node.castTag(.Identifier).?)), | |
| 90 | .Identifier => return try identifier(mod, scope, rl, node.castTag(.Identifier).?), | |
| 91 | 91 | .Asm => return rlWrap(mod, scope, rl, try assembly(mod, scope, node.castTag(.Asm).?)), |
| 92 | 92 | .StringLiteral => return rlWrap(mod, scope, rl, try stringLiteral(mod, scope, node.castTag(.StringLiteral).?)), |
| 93 | 93 | .IntegerLiteral => return rlWrap(mod, scope, rl, try integerLiteral(mod, scope, node.castTag(.IntegerLiteral).?)), |
| ... | ... | @@ -145,8 +145,10 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block |
| 145 | 145 | |
| 146 | 146 | else => { |
| 147 | 147 | const possibly_unused_result = try expr(mod, scope, .none, statement); |
| 148 | const src = scope.tree().token_locs[statement.firstToken()].start; | |
| 149 | _ = try addZIRUnOp(mod, scope, src, .ensure_result_used, possibly_unused_result); | |
| 148 | if (!possibly_unused_result.tag.isNoReturn()) { | |
| 149 | const src = scope.tree().token_locs[statement.firstToken()].start; | |
| 150 | _ = try addZIRUnOp(mod, scope, src, .ensure_result_used, possibly_unused_result); | |
| 151 | } | |
| 150 | 152 | }, |
| 151 | 153 | } |
| 152 | 154 | } |
| ... | ... | @@ -469,7 +471,7 @@ fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerE |
| 469 | 471 | } |
| 470 | 472 | } |
| 471 | 473 | |
| 472 | fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError!*zir.Inst { | |
| 474 | fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneToken) InnerError!*zir.Inst { | |
| 473 | 475 | const tracy = trace(@src()); |
| 474 | 476 | defer tracy.end(); |
| 475 | 477 | |
| ... | ... | @@ -481,7 +483,8 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError |
| 481 | 483 | } |
| 482 | 484 | |
| 483 | 485 | if (getSimplePrimitiveValue(ident_name)) |typed_value| { |
| 484 | return addZIRInstConst(mod, scope, src, typed_value); | |
| 486 | const result = try addZIRInstConst(mod, scope, src, typed_value); | |
| 487 | return rlWrap(mod, scope, rl, result); | |
| 485 | 488 | } |
| 486 | 489 | |
| 487 | 490 | if (ident_name.len >= 2) integer: { |
| ... | ... | @@ -505,16 +508,18 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError |
| 505 | 508 | else => { |
| 506 | 509 | const int_type_payload = try scope.arena().create(Value.Payload.IntType); |
| 507 | 510 | int_type_payload.* = .{ .signed = is_signed, .bits = bit_count }; |
| 508 | return addZIRInstConst(mod, scope, src, .{ | |
| 511 | const result = try addZIRInstConst(mod, scope, src, .{ | |
| 509 | 512 | .ty = Type.initTag(.comptime_int), |
| 510 | 513 | .val = Value.initPayload(&int_type_payload.base), |
| 511 | 514 | }); |
| 515 | return rlWrap(mod, scope, rl, result); | |
| 512 | 516 | }, |
| 513 | 517 | }; |
| 514 | return addZIRInstConst(mod, scope, src, .{ | |
| 518 | const result = try addZIRInstConst(mod, scope, src, .{ | |
| 515 | 519 | .ty = Type.initTag(.type), |
| 516 | 520 | .val = val, |
| 517 | 521 | }); |
| 522 | return rlWrap(mod, scope, rl, result); | |
| 518 | 523 | } |
| 519 | 524 | } |
| 520 | 525 | |
| ... | ... | @@ -525,14 +530,19 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError |
| 525 | 530 | .local_val => { |
| 526 | 531 | const local_val = s.cast(Scope.LocalVal).?; |
| 527 | 532 | if (mem.eql(u8, local_val.name, ident_name)) { |
| 528 | return local_val.inst; | |
| 533 | return rlWrap(mod, scope, rl, local_val.inst); | |
| 529 | 534 | } |
| 530 | 535 | s = local_val.parent; |
| 531 | 536 | }, |
| 532 | 537 | .local_ptr => { |
| 533 | 538 | const local_ptr = s.cast(Scope.LocalPtr).?; |
| 534 | 539 | if (mem.eql(u8, local_ptr.name, ident_name)) { |
| 535 | return try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr); | |
| 540 | if (rl == .lvalue) { | |
| 541 | return local_ptr.ptr; | |
| 542 | } else { | |
| 543 | const result = try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr); | |
| 544 | return rlWrap(mod, scope, rl, result); | |
| 545 | } | |
| 536 | 546 | } |
| 537 | 547 | s = local_ptr.parent; |
| 538 | 548 | }, |
| ... | ... | @@ -542,7 +552,9 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError |
| 542 | 552 | } |
| 543 | 553 | |
| 544 | 554 | if (mod.lookupDeclName(scope, ident_name)) |decl| { |
| 545 | return try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); | |
| 555 | // TODO handle lvalues | |
| 556 | const result = try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); | |
| 557 | return rlWrap(mod, scope, rl, result); | |
| 546 | 558 | } |
| 547 | 559 | |
| 548 | 560 | return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name}); |
| ... | ... | @@ -1066,10 +1078,7 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr |
| 1066 | 1078 | .ptr = ptr_inst, |
| 1067 | 1079 | .value = result, |
| 1068 | 1080 | }, .{}); |
| 1069 | _ = try addZIRInst(mod, scope, result.src, zir.Inst.Store, .{ | |
| 1070 | .ptr = ptr_inst, | |
| 1071 | .value = casted_result, | |
| 1072 | }, .{}); | |
| 1081 | _ = try addZIRBinOp(mod, scope, result.src, .store, ptr_inst, casted_result); | |
| 1073 | 1082 | return casted_result; |
| 1074 | 1083 | }, |
| 1075 | 1084 | .bitcasted_ptr => |bitcasted_ptr| { |
src-self-hosted/codegen.zig+499-94| ... | ... | @@ -50,7 +50,7 @@ pub fn generateSymbol( |
| 50 | 50 | |
| 51 | 51 | switch (typed_value.ty.zigTypeTag()) { |
| 52 | 52 | .Fn => { |
| 53 | switch (bin_file.options.target.cpu.arch) { | |
| 53 | switch (bin_file.base.options.target.cpu.arch) { | |
| 54 | 54 | //.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code), |
| 55 | 55 | //.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code), |
| 56 | 56 | //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code), |
| ... | ... | @@ -143,7 +143,7 @@ pub fn generateSymbol( |
| 143 | 143 | // TODO handle the dependency of this symbol on the decl's vaddr. |
| 144 | 144 | // If the decl changes vaddr, then this symbol needs to get regenerated. |
| 145 | 145 | const vaddr = bin_file.local_symbols.items[decl.link.local_sym_index].st_value; |
| 146 | const endian = bin_file.options.target.cpu.arch.endian(); | |
| 146 | const endian = bin_file.base.options.target.cpu.arch.endian(); | |
| 147 | 147 | switch (bin_file.ptr_width) { |
| 148 | 148 | .p32 => { |
| 149 | 149 | try code.resize(4); |
| ... | ... | @@ -166,7 +166,7 @@ pub fn generateSymbol( |
| 166 | 166 | }; |
| 167 | 167 | }, |
| 168 | 168 | .Int => { |
| 169 | const info = typed_value.ty.intInfo(bin_file.options.target); | |
| 169 | const info = typed_value.ty.intInfo(bin_file.base.options.target); | |
| 170 | 170 | if (info.bits == 8 and !info.signed) { |
| 171 | 171 | const x = typed_value.val.toUnsignedInt(); |
| 172 | 172 | try code.append(@intCast(u8, x)); |
| ... | ... | @@ -209,10 +209,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 209 | 209 | err_msg: ?*ErrorMsg, |
| 210 | 210 | args: []MCValue, |
| 211 | 211 | ret_mcv: MCValue, |
| 212 | fn_type: Type, | |
| 212 | 213 | arg_index: usize, |
| 213 | 214 | src: usize, |
| 214 | 215 | stack_align: u32, |
| 215 | 216 | |
| 217 | /// The value is an offset into the `Function` `code` from the beginning. | |
| 218 | /// To perform the reloc, write 32-bit signed little-endian integer | |
| 219 | /// which is a relative jump, based on the address following the reloc. | |
| 220 | exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{}, | |
| 221 | ||
| 216 | 222 | /// Whenever there is a runtime branch, we push a Branch onto this stack, |
| 217 | 223 | /// and pop it off when the runtime branch joins. This provides an "overlay" |
| 218 | 224 | /// of the table of mappings from instructions to `MCValue` from within the branch. |
| ... | ... | @@ -229,16 +235,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 229 | 235 | unreach, |
| 230 | 236 | /// No more references to this value remain. |
| 231 | 237 | dead, |
| 238 | /// The value is undefined. | |
| 239 | undef, | |
| 232 | 240 | /// A pointer-sized integer that fits in a register. |
| 241 | /// If the type is a pointer, this is the pointer address in virtual address space. | |
| 233 | 242 | immediate: u64, |
| 234 | 243 | /// The constant was emitted into the code, at this offset. |
| 244 | /// If the type is a pointer, it means the pointer address is embedded in the code. | |
| 235 | 245 | embedded_in_code: usize, |
| 246 | /// The value is a pointer to a constant which was emitted into the code, at this offset. | |
| 247 | ptr_embedded_in_code: usize, | |
| 236 | 248 | /// The value is in a target-specific register. |
| 237 | 249 | register: Register, |
| 238 | 250 | /// The value is in memory at a hard-coded address. |
| 251 | /// If the type is a pointer, it means the pointer address is at this memory location. | |
| 239 | 252 | memory: u64, |
| 240 | 253 | /// The value is one of the stack variables. |
| 241 | stack_offset: u64, | |
| 254 | /// If the type is a pointer, it means the pointer address is in the stack at this offset. | |
| 255 | stack_offset: u32, | |
| 256 | /// The value is a pointer to one of the stack variables (payload is stack offset). | |
| 257 | ptr_stack_offset: u32, | |
| 242 | 258 | /// The value is in the compare flags assuming an unsigned operation, |
| 243 | 259 | /// with this operator applied on top of it. |
| 244 | 260 | compare_flags_unsigned: math.CompareOperator, |
| ... | ... | @@ -271,6 +287,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 271 | 287 | .memory, |
| 272 | 288 | .compare_flags_unsigned, |
| 273 | 289 | .compare_flags_signed, |
| 290 | .ptr_stack_offset, | |
| 291 | .ptr_embedded_in_code, | |
| 292 | .undef, | |
| 274 | 293 | => false, |
| 275 | 294 | |
| 276 | 295 | .register, |
| ... | ... | @@ -309,6 +328,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 309 | 328 | self.free_registers |= @as(FreeRegInt, 1) << shift; |
| 310 | 329 | } |
| 311 | 330 | |
| 331 | /// Before calling, must ensureCapacity + 1 on branch.registers. | |
| 332 | /// Returns `null` if all registers are allocated. | |
| 333 | fn allocReg(self: *Branch, inst: *ir.Inst) ?Register { | |
| 334 | const free_index = @ctz(FreeRegInt, self.free_registers); | |
| 335 | if (free_index >= callee_preserved_regs.len) { | |
| 336 | return null; | |
| 337 | } | |
| 338 | self.free_registers &= ~(@as(FreeRegInt, 1) << free_index); | |
| 339 | const reg = callee_preserved_regs[free_index]; | |
| 340 | self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); | |
| 341 | return reg; | |
| 342 | } | |
| 343 | ||
| 312 | 344 | fn deinit(self: *Branch, gpa: *Allocator) void { |
| 313 | 345 | self.inst_table.deinit(gpa); |
| 314 | 346 | self.registers.deinit(gpa); |
| ... | ... | @@ -349,18 +381,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 349 | 381 | |
| 350 | 382 | var function = Self{ |
| 351 | 383 | .gpa = bin_file.allocator, |
| 352 | .target = &bin_file.options.target, | |
| 384 | .target = &bin_file.base.options.target, | |
| 353 | 385 | .bin_file = bin_file, |
| 354 | 386 | .mod_fn = module_fn, |
| 355 | 387 | .code = code, |
| 356 | 388 | .err_msg = null, |
| 357 | 389 | .args = undefined, // populated after `resolveCallingConventionValues` |
| 358 | 390 | .ret_mcv = undefined, // populated after `resolveCallingConventionValues` |
| 391 | .fn_type = fn_type, | |
| 359 | 392 | .arg_index = 0, |
| 360 | 393 | .branch_stack = &branch_stack, |
| 361 | 394 | .src = src, |
| 362 | 395 | .stack_align = undefined, |
| 363 | 396 | }; |
| 397 | defer function.exitlude_jump_relocs.deinit(bin_file.allocator); | |
| 364 | 398 | |
| 365 | 399 | var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) { |
| 366 | 400 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, |
| ... | ... | @@ -386,29 +420,78 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 386 | 420 | } |
| 387 | 421 | |
| 388 | 422 | fn gen(self: *Self) !void { |
| 389 | try self.code.ensureCapacity(self.code.items.len + 11); | |
| 390 | ||
| 391 | // TODO omit this for naked functions | |
| 392 | // push rbp | |
| 393 | // mov rbp, rsp | |
| 394 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x55, 0x48, 0x89, 0xe5 }); | |
| 395 | ||
| 396 | // sub rsp, x | |
| 397 | const stack_end = self.branch_stack.items[0].max_end_stack; | |
| 398 | if (stack_end > math.maxInt(i32)) { | |
| 399 | return self.fail(self.src, "too much stack used in call parameters", .{}); | |
| 400 | } else if (stack_end > math.maxInt(i8)) { | |
| 401 | // 48 83 ec xx sub rsp,0x10 | |
| 402 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xec }); | |
| 403 | const x = @intCast(u32, stack_end); | |
| 404 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); | |
| 405 | } else if (stack_end != 0) { | |
| 406 | // 48 81 ec xx xx xx xx sub rsp,0x80 | |
| 407 | const x = @intCast(u8, stack_end); | |
| 408 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xec, x }); | |
| 409 | } | |
| 423 | switch (arch) { | |
| 424 | .x86_64 => { | |
| 425 | try self.code.ensureCapacity(self.code.items.len + 11); | |
| 426 | ||
| 427 | const cc = self.fn_type.fnCallingConvention(); | |
| 428 | if (cc != .Naked) { | |
| 429 | // We want to subtract the aligned stack frame size from rsp here, but we don't | |
| 430 | // yet know how big it will be, so we leave room for a 4-byte stack size. | |
| 431 | // TODO During semantic analysis, check if there are no function calls. If there | |
| 432 | // are none, here we can omit the part where we subtract and then add rsp. | |
| 433 | self.code.appendSliceAssumeCapacity(&[_]u8{ | |
| 434 | // push rbp | |
| 435 | 0x55, | |
| 436 | // mov rbp, rsp | |
| 437 | 0x48, | |
| 438 | 0x89, | |
| 439 | 0xe5, | |
| 440 | // sub rsp, imm32 (with reloc) | |
| 441 | 0x48, | |
| 442 | 0x81, | |
| 443 | 0xec, | |
| 444 | }); | |
| 445 | const reloc_index = self.code.items.len; | |
| 446 | self.code.items.len += 4; | |
| 447 | ||
| 448 | try self.genBody(self.mod_fn.analysis.success); | |
| 449 | ||
| 450 | const stack_end = self.branch_stack.items[0].max_end_stack; | |
| 451 | if (stack_end > math.maxInt(i32)) | |
| 452 | return self.fail(self.src, "too much stack used in call parameters", .{}); | |
| 453 | const aligned_stack_end = mem.alignForward(stack_end, self.stack_align); | |
| 454 | mem.writeIntLittle(u32, self.code.items[reloc_index..][0..4], @intCast(u32, aligned_stack_end)); | |
| 455 | ||
| 456 | if (self.code.items.len >= math.maxInt(i32)) { | |
| 457 | return self.fail(self.src, "unable to perform relocation: jump too far", .{}); | |
| 458 | } | |
| 459 | for (self.exitlude_jump_relocs.items) |jmp_reloc| { | |
| 460 | const amt = self.code.items.len - (jmp_reloc + 4); | |
| 461 | // If it wouldn't jump at all, elide it. | |
| 462 | if (amt == 0) { | |
| 463 | self.code.items.len -= 5; | |
| 464 | continue; | |
| 465 | } | |
| 466 | const s32_amt = @intCast(i32, amt); | |
| 467 | mem.writeIntLittle(i32, self.code.items[jmp_reloc..][0..4], s32_amt); | |
| 468 | } | |
| 469 | ||
| 470 | try self.code.ensureCapacity(self.code.items.len + 9); | |
| 471 | // add rsp, x | |
| 472 | if (aligned_stack_end > math.maxInt(i8)) { | |
| 473 | // example: 48 81 c4 ff ff ff 7f add rsp,0x7fffffff | |
| 474 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xc4 }); | |
| 475 | const x = @intCast(u32, aligned_stack_end); | |
| 476 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); | |
| 477 | } else if (aligned_stack_end != 0) { | |
| 478 | // example: 48 83 c4 7f add rsp,0x7f | |
| 479 | const x = @intCast(u8, aligned_stack_end); | |
| 480 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xc4, x }); | |
| 481 | } | |
| 410 | 482 | |
| 411 | try self.genBody(self.mod_fn.analysis.success); | |
| 483 | self.code.appendSliceAssumeCapacity(&[_]u8{ | |
| 484 | 0x5d, // pop rbp | |
| 485 | 0xc3, // ret | |
| 486 | }); | |
| 487 | } else { | |
| 488 | try self.genBody(self.mod_fn.analysis.success); | |
| 489 | } | |
| 490 | }, | |
| 491 | else => { | |
| 492 | try self.genBody(self.mod_fn.analysis.success); | |
| 493 | }, | |
| 494 | } | |
| 412 | 495 | } |
| 413 | 496 | |
| 414 | 497 | fn genBody(self: *Self, body: ir.Body) InnerError!void { |
| ... | ... | @@ -432,8 +515,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 432 | 515 | entry.value = .dead; |
| 433 | 516 | switch (prev_value) { |
| 434 | 517 | .register => |reg| { |
| 435 | _ = branch.registers.remove(reg); | |
| 436 | branch.markRegFree(reg); | |
| 518 | const reg64 = reg.to64(); | |
| 519 | _ = branch.registers.remove(reg64); | |
| 520 | branch.markRegFree(reg64); | |
| 437 | 521 | }, |
| 438 | 522 | else => {}, // TODO process stack allocation death |
| 439 | 523 | } |
| ... | ... | @@ -459,26 +543,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 459 | 543 | .cmp_neq => return self.genCmp(inst.castTag(.cmp_neq).?, .neq), |
| 460 | 544 | .condbr => return self.genCondBr(inst.castTag(.condbr).?), |
| 461 | 545 | .constant => unreachable, // excluded from function bodies |
| 546 | .floatcast => return self.genFloatCast(inst.castTag(.floatcast).?), | |
| 547 | .intcast => return self.genIntCast(inst.castTag(.intcast).?), | |
| 462 | 548 | .isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?), |
| 463 | 549 | .isnull => return self.genIsNull(inst.castTag(.isnull).?), |
| 550 | .load => return self.genLoad(inst.castTag(.load).?), | |
| 551 | .not => return self.genNot(inst.castTag(.not).?), | |
| 464 | 552 | .ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?), |
| 553 | .ref => return self.genRef(inst.castTag(.ref).?), | |
| 465 | 554 | .ret => return self.genRet(inst.castTag(.ret).?), |
| 466 | 555 | .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?), |
| 556 | .store => return self.genStore(inst.castTag(.store).?), | |
| 467 | 557 | .sub => return self.genSub(inst.castTag(.sub).?), |
| 468 | 558 | .unreach => return MCValue{ .unreach = {} }, |
| 469 | .not => return self.genNot(inst.castTag(.not).?), | |
| 470 | .floatcast => return self.genFloatCast(inst.castTag(.floatcast).?), | |
| 471 | .intcast => return self.genIntCast(inst.castTag(.intcast).?), | |
| 472 | 559 | } |
| 473 | 560 | } |
| 474 | 561 | |
| 475 | fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue { | |
| 476 | const elem_ty = inst.base.ty.elemType(); | |
| 477 | const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { | |
| 478 | return self.fail(inst.base.src, "type '{}' too big to fit into stack frame", .{elem_ty}); | |
| 479 | }; | |
| 480 | // TODO swap this for inst.base.ty.ptrAlign | |
| 481 | const abi_align = elem_ty.abiAlignment(self.target.*); | |
| 562 | fn allocMem(self: *Self, inst: *ir.Inst, abi_size: u32, abi_align: u32) !u32 { | |
| 482 | 563 | if (abi_align > self.stack_align) |
| 483 | 564 | self.stack_align = abi_align; |
| 484 | 565 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| ... | ... | @@ -488,10 +569,62 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 488 | 569 | if (branch.next_stack_offset > branch.max_end_stack) |
| 489 | 570 | branch.max_end_stack = branch.next_stack_offset; |
| 490 | 571 | try branch.stack.putNoClobber(self.gpa, offset, .{ |
| 491 | .inst = &inst.base, | |
| 572 | .inst = inst, | |
| 492 | 573 | .size = abi_size, |
| 493 | 574 | }); |
| 494 | return MCValue{ .stack_offset = offset }; | |
| 575 | return offset; | |
| 576 | } | |
| 577 | ||
| 578 | /// Use a pointer instruction as the basis for allocating stack memory. | |
| 579 | fn allocMemPtr(self: *Self, inst: *ir.Inst) !u32 { | |
| 580 | const elem_ty = inst.ty.elemType(); | |
| 581 | const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { | |
| 582 | return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty}); | |
| 583 | }; | |
| 584 | // TODO swap this for inst.ty.ptrAlign | |
| 585 | const abi_align = elem_ty.abiAlignment(self.target.*); | |
| 586 | return self.allocMem(inst, abi_size, abi_align); | |
| 587 | } | |
| 588 | ||
| 589 | fn allocRegOrMem(self: *Self, inst: *ir.Inst) !MCValue { | |
| 590 | const elem_ty = inst.ty; | |
| 591 | const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { | |
| 592 | return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty}); | |
| 593 | }; | |
| 594 | const abi_align = elem_ty.abiAlignment(self.target.*); | |
| 595 | if (abi_align > self.stack_align) | |
| 596 | self.stack_align = abi_align; | |
| 597 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | |
| 598 | ||
| 599 | // Make sure the type can fit in a register before we try to allocate one. | |
| 600 | const ptr_bits = arch.ptrBitWidth(); | |
| 601 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); | |
| 602 | if (abi_size <= ptr_bytes) { | |
| 603 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); | |
| 604 | if (branch.allocReg(inst)) |reg| { | |
| 605 | return MCValue{ .register = registerAlias(reg, abi_size) }; | |
| 606 | } | |
| 607 | } | |
| 608 | const stack_offset = try self.allocMem(inst, abi_size, abi_align); | |
| 609 | return MCValue{ .stack_offset = stack_offset }; | |
| 610 | } | |
| 611 | ||
| 612 | /// Does not "move" the instruction. | |
| 613 | fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue { | |
| 614 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | |
| 615 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); | |
| 616 | ||
| 617 | const reg = branch.allocReg(inst) orelse | |
| 618 | return self.fail(inst.src, "TODO implement spilling register to stack", .{}); | |
| 619 | const old_mcv = branch.inst_table.get(inst).?; | |
| 620 | const new_mcv: MCValue = .{ .register = reg }; | |
| 621 | try self.genSetReg(inst.src, reg, old_mcv); | |
| 622 | return new_mcv; | |
| 623 | } | |
| 624 | ||
| 625 | fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue { | |
| 626 | const stack_offset = try self.allocMemPtr(&inst.base); | |
| 627 | return MCValue{ .ptr_stack_offset = stack_offset }; | |
| 495 | 628 | } |
| 496 | 629 | |
| 497 | 630 | fn genFloatCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue { |
| ... | ... | @@ -572,6 +705,87 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 572 | 705 | } |
| 573 | 706 | } |
| 574 | 707 | |
| 708 | fn genLoad(self: *Self, inst: *ir.Inst.UnOp) !MCValue { | |
| 709 | const elem_ty = inst.base.ty; | |
| 710 | if (!elem_ty.hasCodeGenBits()) | |
| 711 | return MCValue.none; | |
| 712 | const ptr = try self.resolveInst(inst.operand); | |
| 713 | const is_volatile = inst.operand.ty.isVolatilePtr(); | |
| 714 | if (inst.base.isUnused() and !is_volatile) | |
| 715 | return MCValue.dead; | |
| 716 | const dst_mcv: MCValue = blk: { | |
| 717 | if (inst.base.operandDies(0) and ptr.isMutable()) { | |
| 718 | // The MCValue that holds the pointer can be re-used as the value. | |
| 719 | // TODO track this in the register/stack allocation metadata. | |
| 720 | break :blk ptr; | |
| 721 | } else { | |
| 722 | break :blk try self.allocRegOrMem(&inst.base); | |
| 723 | } | |
| 724 | }; | |
| 725 | switch (ptr) { | |
| 726 | .none => unreachable, | |
| 727 | .undef => unreachable, | |
| 728 | .unreach => unreachable, | |
| 729 | .dead => unreachable, | |
| 730 | .compare_flags_unsigned => unreachable, | |
| 731 | .compare_flags_signed => unreachable, | |
| 732 | .immediate => |imm| try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .memory = imm }), | |
| 733 | .ptr_stack_offset => |off| try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .stack_offset = off }), | |
| 734 | .ptr_embedded_in_code => |off| { | |
| 735 | try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .embedded_in_code = off }); | |
| 736 | }, | |
| 737 | .embedded_in_code => { | |
| 738 | return self.fail(inst.base.src, "TODO implement loading from MCValue.embedded_in_code", .{}); | |
| 739 | }, | |
| 740 | .register => { | |
| 741 | return self.fail(inst.base.src, "TODO implement loading from MCValue.register", .{}); | |
| 742 | }, | |
| 743 | .memory => { | |
| 744 | return self.fail(inst.base.src, "TODO implement loading from MCValue.memory", .{}); | |
| 745 | }, | |
| 746 | .stack_offset => { | |
| 747 | return self.fail(inst.base.src, "TODO implement loading from MCValue.stack_offset", .{}); | |
| 748 | }, | |
| 749 | } | |
| 750 | return dst_mcv; | |
| 751 | } | |
| 752 | ||
| 753 | fn genStore(self: *Self, inst: *ir.Inst.BinOp) !MCValue { | |
| 754 | const ptr = try self.resolveInst(inst.lhs); | |
| 755 | const value = try self.resolveInst(inst.rhs); | |
| 756 | const elem_ty = inst.rhs.ty; | |
| 757 | switch (ptr) { | |
| 758 | .none => unreachable, | |
| 759 | .undef => unreachable, | |
| 760 | .unreach => unreachable, | |
| 761 | .dead => unreachable, | |
| 762 | .compare_flags_unsigned => unreachable, | |
| 763 | .compare_flags_signed => unreachable, | |
| 764 | .immediate => |imm| { | |
| 765 | try self.setRegOrMem(inst.base.src, elem_ty, .{ .memory = imm }, value); | |
| 766 | }, | |
| 767 | .ptr_stack_offset => |off| { | |
| 768 | try self.genSetStack(inst.base.src, elem_ty, off, value); | |
| 769 | }, | |
| 770 | .ptr_embedded_in_code => |off| { | |
| 771 | try self.setRegOrMem(inst.base.src, elem_ty, .{ .embedded_in_code = off }, value); | |
| 772 | }, | |
| 773 | .embedded_in_code => { | |
| 774 | return self.fail(inst.base.src, "TODO implement storing to MCValue.embedded_in_code", .{}); | |
| 775 | }, | |
| 776 | .register => { | |
| 777 | return self.fail(inst.base.src, "TODO implement storing to MCValue.register", .{}); | |
| 778 | }, | |
| 779 | .memory => { | |
| 780 | return self.fail(inst.base.src, "TODO implement storing to MCValue.memory", .{}); | |
| 781 | }, | |
| 782 | .stack_offset => { | |
| 783 | return self.fail(inst.base.src, "TODO implement storing to MCValue.stack_offset", .{}); | |
| 784 | }, | |
| 785 | } | |
| 786 | return .none; | |
| 787 | } | |
| 788 | ||
| 575 | 789 | fn genSub(self: *Self, inst: *ir.Inst.BinOp) !MCValue { |
| 576 | 790 | // No side effects, so if it's unreferenced, do nothing. |
| 577 | 791 | if (inst.base.isUnused()) |
| ... | ... | @@ -654,13 +868,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 654 | 868 | fn genX8664BinMathCode(self: *Self, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void { |
| 655 | 869 | switch (dst_mcv) { |
| 656 | 870 | .none => unreachable, |
| 871 | .undef => unreachable, | |
| 657 | 872 | .dead, .unreach, .immediate => unreachable, |
| 658 | 873 | .compare_flags_unsigned => unreachable, |
| 659 | 874 | .compare_flags_signed => unreachable, |
| 875 | .ptr_stack_offset => unreachable, | |
| 876 | .ptr_embedded_in_code => unreachable, | |
| 660 | 877 | .register => |dst_reg| { |
| 661 | 878 | switch (src_mcv) { |
| 662 | 879 | .none => unreachable, |
| 880 | .undef => try self.genSetReg(src, dst_reg, .undef), | |
| 663 | 881 | .dead, .unreach => unreachable, |
| 882 | .ptr_stack_offset => unreachable, | |
| 883 | .ptr_embedded_in_code => unreachable, | |
| 664 | 884 | .register => |src_reg| { |
| 665 | 885 | self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); |
| 666 | 886 | self.code.appendSliceAssumeCapacity(&[_]u8{ mr + 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) }); |
| ... | ... | @@ -743,6 +963,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 743 | 963 | for (info.args) |mc_arg, arg_i| { |
| 744 | 964 | const arg = inst.args[arg_i]; |
| 745 | 965 | const arg_mcv = try self.resolveInst(inst.args[arg_i]); |
| 966 | // Here we do not use setRegOrMem even though the logic is similar, because | |
| 967 | // the function call will move the stack pointer, so the offsets are different. | |
| 746 | 968 | switch (mc_arg) { |
| 747 | 969 | .none => continue, |
| 748 | 970 | .register => |reg| { |
| ... | ... | @@ -754,6 +976,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 754 | 976 | // mov qword ptr [rsp + stack_offset], x |
| 755 | 977 | return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); |
| 756 | 978 | }, |
| 979 | .ptr_stack_offset => { | |
| 980 | return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{}); | |
| 981 | }, | |
| 982 | .ptr_embedded_in_code => { | |
| 983 | return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); | |
| 984 | }, | |
| 985 | .undef => unreachable, | |
| 757 | 986 | .immediate => unreachable, |
| 758 | 987 | .unreach => unreachable, |
| 759 | 988 | .dead => unreachable, |
| ... | ... | @@ -788,17 +1017,47 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 788 | 1017 | return info.return_value; |
| 789 | 1018 | } |
| 790 | 1019 | |
| 1020 | fn genRef(self: *Self, inst: *ir.Inst.UnOp) !MCValue { | |
| 1021 | const operand = try self.resolveInst(inst.operand); | |
| 1022 | switch (operand) { | |
| 1023 | .unreach => unreachable, | |
| 1024 | .dead => unreachable, | |
| 1025 | .none => return .none, | |
| 1026 | ||
| 1027 | .immediate, | |
| 1028 | .register, | |
| 1029 | .ptr_stack_offset, | |
| 1030 | .ptr_embedded_in_code, | |
| 1031 | .compare_flags_unsigned, | |
| 1032 | .compare_flags_signed, | |
| 1033 | => { | |
| 1034 | const stack_offset = try self.allocMemPtr(&inst.base); | |
| 1035 | try self.genSetStack(inst.base.src, inst.operand.ty, stack_offset, operand); | |
| 1036 | return MCValue{ .ptr_stack_offset = stack_offset }; | |
| 1037 | }, | |
| 1038 | ||
| 1039 | .stack_offset => |offset| return MCValue{ .ptr_stack_offset = offset }, | |
| 1040 | .embedded_in_code => |offset| return MCValue{ .ptr_embedded_in_code = offset }, | |
| 1041 | .memory => |vaddr| return MCValue{ .immediate = vaddr }, | |
| 1042 | ||
| 1043 | .undef => return self.fail(inst.base.src, "TODO implement ref on an undefined value", .{}), | |
| 1044 | } | |
| 1045 | } | |
| 1046 | ||
| 791 | 1047 | fn ret(self: *Self, src: usize, mcv: MCValue) !MCValue { |
| 792 | try self.setRegOrStack(src, self.ret_mcv, mcv); | |
| 1048 | const ret_ty = self.fn_type.fnReturnType(); | |
| 1049 | try self.setRegOrMem(src, ret_ty, self.ret_mcv, mcv); | |
| 793 | 1050 | switch (arch) { |
| 794 | 1051 | .i386 => { |
| 795 | 1052 | try self.code.append(0xc3); // ret |
| 796 | 1053 | }, |
| 797 | 1054 | .x86_64 => { |
| 798 | try self.code.appendSlice(&[_]u8{ | |
| 799 | 0x5d, // pop rbp | |
| 800 | 0xc3, // ret | |
| 801 | }); | |
| 1055 | // TODO when implementing defer, this will need to jump to the appropriate defer expression. | |
| 1056 | // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction | |
| 1057 | // which is available if the jump is 127 bytes or less forward. | |
| 1058 | try self.code.resize(self.code.items.len + 5); | |
| 1059 | self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 | |
| 1060 | try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4); | |
| 802 | 1061 | }, |
| 803 | 1062 | else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), |
| 804 | 1063 | } |
| ... | ... | @@ -882,7 +1141,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 882 | 1141 | // test reg, 1 |
| 883 | 1142 | // TODO detect al, ax, eax |
| 884 | 1143 | try self.code.ensureCapacity(self.code.items.len + 4); |
| 885 | self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 }); | |
| 1144 | // TODO audit this codegen: we force w = true here to make | |
| 1145 | // the value affect the big register | |
| 1146 | self.rex(.{ .b = reg.isExtended(), .w = true }); | |
| 886 | 1147 | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 887 | 1148 | 0xf6, |
| 888 | 1149 | @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()), |
| ... | ... | @@ -938,6 +1199,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 938 | 1199 | switch (reloc) { |
| 939 | 1200 | .rel32 => |pos| { |
| 940 | 1201 | const amt = self.code.items.len - (pos + 4); |
| 1202 | // If it wouldn't jump at all, elide it. | |
| 1203 | if (amt == 0) { | |
| 1204 | self.code.items.len -= 5; | |
| 1205 | return; | |
| 1206 | } | |
| 941 | 1207 | const s32_amt = math.cast(i32, amt) catch |
| 942 | 1208 | return self.fail(src, "unable to perform relocation: jump too far", .{}); |
| 943 | 1209 | mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt); |
| ... | ... | @@ -1042,25 +1308,141 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1042 | 1308 | } |
| 1043 | 1309 | |
| 1044 | 1310 | /// Sets the value without any modifications to register allocation metadata or stack allocation metadata. |
| 1045 | fn setRegOrStack(self: *Self, src: usize, loc: MCValue, val: MCValue) !void { | |
| 1311 | fn setRegOrMem(self: *Self, src: usize, ty: Type, loc: MCValue, val: MCValue) !void { | |
| 1046 | 1312 | switch (loc) { |
| 1047 | 1313 | .none => return, |
| 1048 | 1314 | .register => |reg| return self.genSetReg(src, reg, val), |
| 1049 | .stack_offset => { | |
| 1050 | return self.fail(src, "TODO implement setRegOrStack for stack offset", .{}); | |
| 1315 | .stack_offset => |off| return self.genSetStack(src, ty, off, val), | |
| 1316 | .memory => { | |
| 1317 | return self.fail(src, "TODO implement setRegOrMem for memory", .{}); | |
| 1051 | 1318 | }, |
| 1052 | 1319 | else => unreachable, |
| 1053 | 1320 | } |
| 1054 | 1321 | } |
| 1055 | 1322 | |
| 1056 | fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) error{ CodegenFail, OutOfMemory }!void { | |
| 1323 | fn genSetStack(self: *Self, src: usize, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void { | |
| 1057 | 1324 | switch (arch) { |
| 1058 | 1325 | .x86_64 => switch (mcv) { |
| 1059 | 1326 | .dead => unreachable, |
| 1327 | .ptr_stack_offset => unreachable, | |
| 1328 | .ptr_embedded_in_code => unreachable, | |
| 1060 | 1329 | .unreach, .none => return, // Nothing to do. |
| 1330 | .undef => { | |
| 1331 | if (!self.wantSafety()) | |
| 1332 | return; // The already existing value will do just fine. | |
| 1333 | // TODO Upgrade this to a memset call when we have that available. | |
| 1334 | switch (ty.abiSize(self.target.*)) { | |
| 1335 | 1 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaa }), | |
| 1336 | 2 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaa }), | |
| 1337 | 4 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa }), | |
| 1338 | 8 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), | |
| 1339 | else => return self.fail(src, "TODO implement memset", .{}), | |
| 1340 | } | |
| 1341 | }, | |
| 1342 | .compare_flags_unsigned => |op| { | |
| 1343 | return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{}); | |
| 1344 | }, | |
| 1345 | .compare_flags_signed => |op| { | |
| 1346 | return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{}); | |
| 1347 | }, | |
| 1348 | .immediate => |x_big| { | |
| 1349 | const abi_size = ty.abiSize(self.target.*); | |
| 1350 | const adj_off = stack_offset + abi_size; | |
| 1351 | if (adj_off > 128) { | |
| 1352 | return self.fail(src, "TODO implement set stack variable with large stack offset", .{}); | |
| 1353 | } | |
| 1354 | try self.code.ensureCapacity(self.code.items.len + 8); | |
| 1355 | switch (abi_size) { | |
| 1356 | 1 => { | |
| 1357 | return self.fail(src, "TODO implement set abi_size=1 stack variable with immediate", .{}); | |
| 1358 | }, | |
| 1359 | 2 => { | |
| 1360 | return self.fail(src, "TODO implement set abi_size=2 stack variable with immediate", .{}); | |
| 1361 | }, | |
| 1362 | 4 => { | |
| 1363 | const x = @intCast(u32, x_big); | |
| 1364 | // We have a positive stack offset value but we want a twos complement negative | |
| 1365 | // offset from rbp, which is at the top of the stack frame. | |
| 1366 | const negative_offset = @intCast(i8, -@intCast(i32, adj_off)); | |
| 1367 | const twos_comp = @bitCast(u8, negative_offset); | |
| 1368 | // mov DWORD PTR [rbp+offset], immediate | |
| 1369 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp }); | |
| 1370 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); | |
| 1371 | }, | |
| 1372 | 8 => { | |
| 1373 | return self.fail(src, "TODO implement set abi_size=8 stack variable with immediate", .{}); | |
| 1374 | }, | |
| 1375 | else => { | |
| 1376 | return self.fail(src, "TODO implement set abi_size=large stack variable with immediate", .{}); | |
| 1377 | }, | |
| 1378 | } | |
| 1379 | if (x_big <= math.maxInt(u32)) {} else { | |
| 1380 | return self.fail(src, "TODO implement set stack variable with large immediate", .{}); | |
| 1381 | } | |
| 1382 | }, | |
| 1383 | .embedded_in_code => |code_offset| { | |
| 1384 | return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{}); | |
| 1385 | }, | |
| 1386 | .register => |reg| { | |
| 1387 | const abi_size = ty.abiSize(self.target.*); | |
| 1388 | const adj_off = stack_offset + abi_size; | |
| 1389 | try self.code.ensureCapacity(self.code.items.len + 7); | |
| 1390 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); | |
| 1391 | const reg_id: u8 = @truncate(u3, reg.id()); | |
| 1392 | if (adj_off <= 128) { | |
| 1393 | // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx | |
| 1394 | const RM = @as(u8, 0b01_000_101) | (reg_id << 3); | |
| 1395 | const negative_offset = @intCast(i8, -@intCast(i32, adj_off)); | |
| 1396 | const twos_comp = @bitCast(u8, negative_offset); | |
| 1397 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM, twos_comp }); | |
| 1398 | } else if (adj_off <= 2147483648) { | |
| 1399 | // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx | |
| 1400 | const RM = @as(u8, 0b10_000_101) | (reg_id << 3); | |
| 1401 | const negative_offset = @intCast(i32, -@intCast(i33, adj_off)); | |
| 1402 | const twos_comp = @bitCast(u32, negative_offset); | |
| 1403 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM }); | |
| 1404 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp); | |
| 1405 | } else { | |
| 1406 | return self.fail(src, "stack offset too large", .{}); | |
| 1407 | } | |
| 1408 | }, | |
| 1409 | .memory => |vaddr| { | |
| 1410 | return self.fail(src, "TODO implement set stack variable from memory vaddr", .{}); | |
| 1411 | }, | |
| 1412 | .stack_offset => |off| { | |
| 1413 | if (stack_offset == off) | |
| 1414 | return; // Copy stack variable to itself; nothing to do. | |
| 1415 | return self.fail(src, "TODO implement copy stack variable to stack variable", .{}); | |
| 1416 | }, | |
| 1417 | }, | |
| 1418 | else => return self.fail(src, "TODO implement getSetStack for {}", .{self.target.cpu.arch}), | |
| 1419 | } | |
| 1420 | } | |
| 1421 | ||
| 1422 | fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) InnerError!void { | |
| 1423 | switch (arch) { | |
| 1424 | .x86_64 => switch (mcv) { | |
| 1425 | .dead => unreachable, | |
| 1426 | .ptr_stack_offset => unreachable, | |
| 1427 | .ptr_embedded_in_code => unreachable, | |
| 1428 | .unreach, .none => return, // Nothing to do. | |
| 1429 | .undef => { | |
| 1430 | if (!self.wantSafety()) | |
| 1431 | return; // The already existing value will do just fine. | |
| 1432 | // Write the debug undefined value. | |
| 1433 | switch (reg.size()) { | |
| 1434 | 8 => return self.genSetReg(src, reg, .{ .immediate = 0xaa }), | |
| 1435 | 16 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaa }), | |
| 1436 | 32 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }), | |
| 1437 | 64 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), | |
| 1438 | else => unreachable, | |
| 1439 | } | |
| 1440 | }, | |
| 1061 | 1441 | .compare_flags_unsigned => |op| { |
| 1062 | 1442 | try self.code.ensureCapacity(self.code.items.len + 3); |
| 1063 | self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 }); | |
| 1443 | // TODO audit this codegen: we force w = true here to make | |
| 1444 | // the value affect the big register | |
| 1445 | self.rex(.{ .b = reg.isExtended(), .w = true }); | |
| 1064 | 1446 | const opcode: u8 = switch (op) { |
| 1065 | 1447 | .gte => 0x93, |
| 1066 | 1448 | .gt => 0x97, |
| ... | ... | @@ -1076,9 +1458,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1076 | 1458 | return self.fail(src, "TODO set register with compare flags value (signed)", .{}); |
| 1077 | 1459 | }, |
| 1078 | 1460 | .immediate => |x| { |
| 1079 | if (reg.size() != 64) { | |
| 1080 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | |
| 1081 | } | |
| 1082 | 1461 | // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit |
| 1083 | 1462 | // register is the fastest way to zero a register. |
| 1084 | 1463 | if (x == 0) { |
| ... | ... | @@ -1131,16 +1510,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1131 | 1510 | // |
| 1132 | 1511 | // In this case, the encoding of the REX byte is 0b0100100B |
| 1133 | 1512 | try self.code.ensureCapacity(self.code.items.len + 10); |
| 1134 | self.rex(.{ .w = true, .b = reg.isExtended() }); | |
| 1513 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); | |
| 1135 | 1514 | self.code.items.len += 9; |
| 1136 | 1515 | self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111); |
| 1137 | 1516 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; |
| 1138 | 1517 | mem.writeIntLittle(u64, imm_ptr, x); |
| 1139 | 1518 | }, |
| 1140 | 1519 | .embedded_in_code => |code_offset| { |
| 1141 | if (reg.size() != 64) { | |
| 1142 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | |
| 1143 | } | |
| 1144 | 1520 | // We need the offset from RIP in a signed i32 twos complement. |
| 1145 | 1521 | // The instruction is 7 bytes long and RIP points to the next instruction. |
| 1146 | 1522 | try self.code.ensureCapacity(self.code.items.len + 7); |
| ... | ... | @@ -1148,7 +1524,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1148 | 1524 | // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three |
| 1149 | 1525 | // bits as five. |
| 1150 | 1526 | // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id. |
| 1151 | self.rex(.{ .w = true, .b = reg.isExtended() }); | |
| 1527 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); | |
| 1152 | 1528 | self.code.items.len += 6; |
| 1153 | 1529 | const rip = self.code.items.len; |
| 1154 | 1530 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); |
| ... | ... | @@ -1160,12 +1536,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1160 | 1536 | }, |
| 1161 | 1537 | .register => |src_reg| { |
| 1162 | 1538 | // If the registers are the same, nothing to do. |
| 1163 | if (src_reg == reg) | |
| 1539 | if (src_reg.id() == reg.id()) | |
| 1164 | 1540 | return; |
| 1165 | 1541 | |
| 1166 | if (reg.size() != 64) { | |
| 1167 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | |
| 1168 | } | |
| 1169 | 1542 | // This is a variant of 8B /r. Since we're using 64-bit moves, we require a REX. |
| 1170 | 1543 | // This is thus three bytes: REX 0x8B R/M. |
| 1171 | 1544 | // If the destination is extended, the R field must be 1. |
| ... | ... | @@ -1173,14 +1546,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1173 | 1546 | // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle |
| 1174 | 1547 | // three bits) contain the destination, and the R/M field (the lower three bits) contain the source. |
| 1175 | 1548 | try self.code.ensureCapacity(self.code.items.len + 3); |
| 1176 | self.rex(.{ .w = true, .r = reg.isExtended(), .b = src_reg.isExtended() }); | |
| 1549 | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended(), .b = src_reg.isExtended() }); | |
| 1177 | 1550 | const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111); |
| 1178 | 1551 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R }); |
| 1179 | 1552 | }, |
| 1180 | 1553 | .memory => |x| { |
| 1181 | if (reg.size() != 64) { | |
| 1182 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | |
| 1183 | } | |
| 1184 | 1554 | if (x <= math.maxInt(u32)) { |
| 1185 | 1555 | // Moving from memory to a register is a variant of `8B /r`. |
| 1186 | 1556 | // Since we're using 64-bit moves, we require a REX. |
| ... | ... | @@ -1190,7 +1560,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1190 | 1560 | // 0b00RRR100, where RRR is the lower three bits of the register ID. |
| 1191 | 1561 | // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32. |
| 1192 | 1562 | try self.code.ensureCapacity(self.code.items.len + 8); |
| 1193 | self.rex(.{ .w = true, .b = reg.isExtended() }); | |
| 1563 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); | |
| 1194 | 1564 | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 1195 | 1565 | 0x8B, |
| 1196 | 1566 | 0x04 | (@as(u8, reg.id() & 0b111) << 3), // R |
| ... | ... | @@ -1218,7 +1588,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1218 | 1588 | // is no way to possibly encode it. This means that RSP, RBP, R12, and R13 cannot be used with |
| 1219 | 1589 | // this instruction. |
| 1220 | 1590 | const id3 = @truncate(u3, reg.id()); |
| 1221 | std.debug.assert(id3 != 4 and id3 != 5); | |
| 1591 | assert(id3 != 4 and id3 != 5); | |
| 1222 | 1592 | |
| 1223 | 1593 | // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue. |
| 1224 | 1594 | try self.genSetReg(src, reg, MCValue{ .immediate = x }); |
| ... | ... | @@ -1233,14 +1603,34 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1233 | 1603 | // |
| 1234 | 1604 | // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both* |
| 1235 | 1605 | // register operands need to be marked as extended. |
| 1236 | self.rex(.{ .w = true, .b = reg.isExtended(), .r = reg.isExtended() }); | |
| 1606 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), .r = reg.isExtended() }); | |
| 1237 | 1607 | const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); |
| 1238 | 1608 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM }); |
| 1239 | 1609 | } |
| 1240 | 1610 | } |
| 1241 | 1611 | }, |
| 1242 | .stack_offset => |off| { | |
| 1243 | return self.fail(src, "TODO implement genSetReg for stack variables", .{}); | |
| 1612 | .stack_offset => |unadjusted_off| { | |
| 1613 | try self.code.ensureCapacity(self.code.items.len + 7); | |
| 1614 | const size_bytes = @divExact(reg.size(), 8); | |
| 1615 | const off = unadjusted_off + size_bytes; | |
| 1616 | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() }); | |
| 1617 | const reg_id: u8 = @truncate(u3, reg.id()); | |
| 1618 | if (off <= 128) { | |
| 1619 | // Example: 48 8b 4d 7f mov rcx,QWORD PTR [rbp+0x7f] | |
| 1620 | const RM = @as(u8, 0b01_000_101) | (reg_id << 3); | |
| 1621 | const negative_offset = @intCast(i8, -@intCast(i32, off)); | |
| 1622 | const twos_comp = @bitCast(u8, negative_offset); | |
| 1623 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8b, RM, twos_comp }); | |
| 1624 | } else if (off <= 2147483648) { | |
| 1625 | // Example: 48 8b 8d 80 00 00 00 mov rcx,QWORD PTR [rbp+0x80] | |
| 1626 | const RM = @as(u8, 0b10_000_101) | (reg_id << 3); | |
| 1627 | const negative_offset = @intCast(i32, -@intCast(i33, off)); | |
| 1628 | const twos_comp = @bitCast(u32, negative_offset); | |
| 1629 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8b, RM }); | |
| 1630 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp); | |
| 1631 | } else { | |
| 1632 | return self.fail(src, "stack offset too large", .{}); | |
| 1633 | } | |
| 1244 | 1634 | }, |
| 1245 | 1635 | }, |
| 1246 | 1636 | else => return self.fail(src, "TODO implement getSetReg for {}", .{self.target.cpu.arch}), |
| ... | ... | @@ -1279,24 +1669,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1279 | 1669 | } |
| 1280 | 1670 | } |
| 1281 | 1671 | |
| 1282 | /// Does not "move" the instruction. | |
| 1283 | fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue { | |
| 1284 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | |
| 1285 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); | |
| 1286 | try branch.inst_table.ensureCapacity(self.gpa, branch.inst_table.items().len + 1); | |
| 1287 | ||
| 1288 | const free_index = @ctz(FreeRegInt, branch.free_registers); | |
| 1289 | if (free_index >= callee_preserved_regs.len) | |
| 1290 | return self.fail(inst.src, "TODO implement spilling register to stack", .{}); | |
| 1291 | branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index); | |
| 1292 | const reg = callee_preserved_regs[free_index]; | |
| 1293 | branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); | |
| 1294 | const old_mcv = branch.inst_table.get(inst).?; | |
| 1295 | const new_mcv: MCValue = .{ .register = reg }; | |
| 1296 | try self.genSetReg(inst.src, reg, old_mcv); | |
| 1297 | return new_mcv; | |
| 1298 | } | |
| 1299 | ||
| 1300 | 1672 | /// If the MCValue is an immediate, and it does not fit within this type, |
| 1301 | 1673 | /// we put it in a register. |
| 1302 | 1674 | /// A potential opportunity for future optimization here would be keeping track |
| ... | ... | @@ -1324,6 +1696,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1324 | 1696 | } |
| 1325 | 1697 | |
| 1326 | 1698 | fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) !MCValue { |
| 1699 | if (typed_value.val.isUndef()) | |
| 1700 | return MCValue.undef; | |
| 1327 | 1701 | const ptr_bits = self.target.cpu.arch.ptrBitWidth(); |
| 1328 | 1702 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 1329 | 1703 | switch (typed_value.ty.zigTypeTag()) { |
| ... | ... | @@ -1398,11 +1772,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1398 | 1772 | for (param_types) |ty, i| { |
| 1399 | 1773 | switch (ty.zigTypeTag()) { |
| 1400 | 1774 | .Bool, .Int => { |
| 1775 | const param_size = @intCast(u32, ty.abiSize(self.target.*)); | |
| 1401 | 1776 | if (next_int_reg >= c_abi_int_param_regs.len) { |
| 1402 | 1777 | result.args[i] = .{ .stack_offset = next_stack_offset }; |
| 1403 | next_stack_offset += @intCast(u32, ty.abiSize(self.target.*)); | |
| 1778 | next_stack_offset += param_size; | |
| 1404 | 1779 | } else { |
| 1405 | result.args[i] = .{ .register = c_abi_int_param_regs[next_int_reg] }; | |
| 1780 | const aliased_reg = registerAlias( | |
| 1781 | c_abi_int_param_regs[next_int_reg], | |
| 1782 | param_size, | |
| 1783 | ); | |
| 1784 | result.args[i] = .{ .register = aliased_reg }; | |
| 1406 | 1785 | next_int_reg += 1; |
| 1407 | 1786 | } |
| 1408 | 1787 | }, |
| ... | ... | @@ -1426,7 +1805,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1426 | 1805 | .x86_64 => switch (cc) { |
| 1427 | 1806 | .Naked => unreachable, |
| 1428 | 1807 | .Unspecified, .C => { |
| 1429 | result.return_value = .{ .register = c_abi_int_return_regs[0] }; | |
| 1808 | const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); | |
| 1809 | const aliased_reg = registerAlias(c_abi_int_return_regs[0], ret_ty_size); | |
| 1810 | result.return_value = .{ .register = aliased_reg }; | |
| 1430 | 1811 | }, |
| 1431 | 1812 | else => return self.fail(src, "TODO implement function return values for {}", .{cc}), |
| 1432 | 1813 | }, |
| ... | ... | @@ -1435,6 +1816,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1435 | 1816 | return result; |
| 1436 | 1817 | } |
| 1437 | 1818 | |
| 1819 | /// TODO support scope overrides. Also note this logic is duplicated with `Module.wantSafety`. | |
| 1820 | fn wantSafety(self: *Self) bool { | |
| 1821 | return switch (self.bin_file.base.options.optimize_mode) { | |
| 1822 | .Debug => true, | |
| 1823 | .ReleaseSafe => true, | |
| 1824 | .ReleaseFast => false, | |
| 1825 | .ReleaseSmall => false, | |
| 1826 | }; | |
| 1827 | } | |
| 1828 | ||
| 1438 | 1829 | fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } { |
| 1439 | 1830 | @setCold(true); |
| 1440 | 1831 | assert(self.err_msg == null); |
| ... | ... | @@ -1463,5 +1854,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1463 | 1854 | fn parseRegName(name: []const u8) ?Register { |
| 1464 | 1855 | return std.meta.stringToEnum(Register, name); |
| 1465 | 1856 | } |
| 1857 | ||
| 1858 | fn registerAlias(reg: Register, size_bytes: u32) Register { | |
| 1859 | switch (arch) { | |
| 1860 | // For x86_64 we have to pick a smaller register alias depending on abi size. | |
| 1861 | .x86_64 => switch (size_bytes) { | |
| 1862 | 1 => return reg.to8(), | |
| 1863 | 2 => return reg.to16(), | |
| 1864 | 4 => return reg.to32(), | |
| 1865 | 8 => return reg.to64(), | |
| 1866 | else => unreachable, | |
| 1867 | }, | |
| 1868 | else => return reg, | |
| 1869 | } | |
| 1870 | } | |
| 1466 | 1871 | }; |
| 1467 | 1872 | } |
src-self-hosted/codegen/x86_64.zig+20| ... | ... | @@ -81,6 +81,26 @@ pub const Register = enum(u8) { |
| 81 | 81 | else => null, |
| 82 | 82 | }; |
| 83 | 83 | } |
| 84 | ||
| 85 | /// Convert from any register to its 64 bit alias. | |
| 86 | pub fn to64(self: Register) Register { | |
| 87 | return @intToEnum(Register, self.id()); | |
| 88 | } | |
| 89 | ||
| 90 | /// Convert from any register to its 32 bit alias. | |
| 91 | pub fn to32(self: Register) Register { | |
| 92 | return @intToEnum(Register, @as(u8, self.id()) + 16); | |
| 93 | } | |
| 94 | ||
| 95 | /// Convert from any register to its 16 bit alias. | |
| 96 | pub fn to16(self: Register) Register { | |
| 97 | return @intToEnum(Register, @as(u8, self.id()) + 32); | |
| 98 | } | |
| 99 | ||
| 100 | /// Convert from any register to its 8 bit alias. | |
| 101 | pub fn to8(self: Register) Register { | |
| 102 | return @intToEnum(Register, @as(u8, self.id()) + 48); | |
| 103 | } | |
| 84 | 104 | }; |
| 85 | 105 | |
| 86 | 106 | // zig fmt: on |
src-self-hosted/ir.zig+9-2| ... | ... | @@ -67,9 +67,14 @@ pub const Inst = struct { |
| 67 | 67 | constant, |
| 68 | 68 | isnonnull, |
| 69 | 69 | isnull, |
| 70 | /// Read a value from a pointer. | |
| 71 | load, | |
| 70 | 72 | ptrtoint, |
| 73 | ref, | |
| 71 | 74 | ret, |
| 72 | 75 | retvoid, |
| 76 | /// Write a value to a pointer. LHS is pointer, RHS is value. | |
| 77 | store, | |
| 73 | 78 | sub, |
| 74 | 79 | unreach, |
| 75 | 80 | not, |
| ... | ... | @@ -85,6 +90,7 @@ pub const Inst = struct { |
| 85 | 90 | .breakpoint, |
| 86 | 91 | => NoOp, |
| 87 | 92 | |
| 93 | .ref, | |
| 88 | 94 | .ret, |
| 89 | 95 | .bitcast, |
| 90 | 96 | .not, |
| ... | ... | @@ -93,6 +99,7 @@ pub const Inst = struct { |
| 93 | 99 | .ptrtoint, |
| 94 | 100 | .floatcast, |
| 95 | 101 | .intcast, |
| 102 | .load, | |
| 96 | 103 | => UnOp, |
| 97 | 104 | |
| 98 | 105 | .add, |
| ... | ... | @@ -103,6 +110,7 @@ pub const Inst = struct { |
| 103 | 110 | .cmp_gte, |
| 104 | 111 | .cmp_gt, |
| 105 | 112 | .cmp_neq, |
| 113 | .store, | |
| 106 | 114 | => BinOp, |
| 107 | 115 | |
| 108 | 116 | .assembly => Assembly, |
| ... | ... | @@ -157,8 +165,7 @@ pub const Inst = struct { |
| 157 | 165 | |
| 158 | 166 | /// Returns `null` if runtime-known. |
| 159 | 167 | pub fn value(base: *Inst) ?Value { |
| 160 | if (base.ty.onePossibleValue()) | |
| 161 | return Value.initTag(.the_one_possible_value); | |
| 168 | if (base.ty.onePossibleValue()) |opv| return opv; | |
| 162 | 169 | |
| 163 | 170 | const inst = base.cast(Constant) orelse return null; |
| 164 | 171 | return inst.val; |
src-self-hosted/link.zig+63-57| ... | ... | @@ -16,6 +16,7 @@ pub const Options = struct { |
| 16 | 16 | output_mode: std.builtin.OutputMode, |
| 17 | 17 | link_mode: std.builtin.LinkMode, |
| 18 | 18 | object_format: std.builtin.ObjectFormat, |
| 19 | optimize_mode: std.builtin.Mode, | |
| 19 | 20 | /// Used for calculating how much space to reserve for symbols in case the binary file |
| 20 | 21 | /// does not already have a symbol table. |
| 21 | 22 | symbol_count_hint: u64 = 32, |
| ... | ... | @@ -66,6 +67,7 @@ pub fn writeFilePath( |
| 66 | 67 | .link_mode = module.link_mode, |
| 67 | 68 | .object_format = module.object_format, |
| 68 | 69 | .symbol_count_hint = module.decls.items.len, |
| 70 | .optimize_mode = module.optimize_mode, | |
| 69 | 71 | }; |
| 70 | 72 | const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(options) }); |
| 71 | 73 | defer af.deinit(); |
| ... | ... | @@ -88,9 +90,12 @@ pub fn writeFilePath( |
| 88 | 90 | |
| 89 | 91 | fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C { |
| 90 | 92 | return File.C{ |
| 93 | .base = .{ | |
| 94 | .tag = .c, | |
| 95 | .options = options, | |
| 96 | }, | |
| 91 | 97 | .allocator = allocator, |
| 92 | 98 | .file = file, |
| 93 | .options = options, | |
| 94 | 99 | .main = std.ArrayList(u8).init(allocator), |
| 95 | 100 | .header = std.ArrayList(u8).init(allocator), |
| 96 | 101 | .constants = std.ArrayList(u8).init(allocator), |
| ... | ... | @@ -114,6 +119,8 @@ pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !File |
| 114 | 119 | |
| 115 | 120 | pub const File = struct { |
| 116 | 121 | tag: Tag, |
| 122 | options: Options, | |
| 123 | ||
| 117 | 124 | pub fn cast(base: *File, comptime T: type) ?*T { |
| 118 | 125 | if (base.tag != T.base_tag) |
| 119 | 126 | return null; |
| ... | ... | @@ -123,47 +130,47 @@ pub const File = struct { |
| 123 | 130 | |
| 124 | 131 | pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void { |
| 125 | 132 | switch (base.tag) { |
| 126 | .Elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path), | |
| 127 | .C => {}, | |
| 133 | .elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path), | |
| 134 | .c => {}, | |
| 128 | 135 | } |
| 129 | 136 | } |
| 130 | 137 | |
| 131 | 138 | pub fn makeExecutable(base: *File) !void { |
| 132 | 139 | switch (base.tag) { |
| 133 | .Elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(), | |
| 134 | .C => unreachable, | |
| 140 | .elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(), | |
| 141 | .c => unreachable, | |
| 135 | 142 | } |
| 136 | 143 | } |
| 137 | 144 | |
| 138 | 145 | pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void { |
| 139 | 146 | switch (base.tag) { |
| 140 | .Elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), | |
| 141 | .C => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), | |
| 147 | .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), | |
| 148 | .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), | |
| 142 | 149 | } |
| 143 | 150 | } |
| 144 | 151 | |
| 145 | 152 | pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void { |
| 146 | 153 | switch (base.tag) { |
| 147 | .Elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), | |
| 148 | .C => {}, | |
| 154 | .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), | |
| 155 | .c => {}, | |
| 149 | 156 | } |
| 150 | 157 | } |
| 151 | 158 | |
| 152 | 159 | pub fn deinit(base: *File) void { |
| 153 | 160 | switch (base.tag) { |
| 154 | .Elf => @fieldParentPtr(Elf, "base", base).deinit(), | |
| 155 | .C => @fieldParentPtr(C, "base", base).deinit(), | |
| 161 | .elf => @fieldParentPtr(Elf, "base", base).deinit(), | |
| 162 | .c => @fieldParentPtr(C, "base", base).deinit(), | |
| 156 | 163 | } |
| 157 | 164 | } |
| 158 | 165 | |
| 159 | 166 | pub fn destroy(base: *File) void { |
| 160 | 167 | switch (base.tag) { |
| 161 | .Elf => { | |
| 168 | .elf => { | |
| 162 | 169 | const parent = @fieldParentPtr(Elf, "base", base); |
| 163 | 170 | parent.deinit(); |
| 164 | 171 | parent.allocator.destroy(parent); |
| 165 | 172 | }, |
| 166 | .C => { | |
| 173 | .c => { | |
| 167 | 174 | const parent = @fieldParentPtr(C, "base", base); |
| 168 | 175 | parent.deinit(); |
| 169 | 176 | parent.allocator.destroy(parent); |
| ... | ... | @@ -173,29 +180,22 @@ pub const File = struct { |
| 173 | 180 | |
| 174 | 181 | pub fn flush(base: *File) !void { |
| 175 | 182 | try switch (base.tag) { |
| 176 | .Elf => @fieldParentPtr(Elf, "base", base).flush(), | |
| 177 | .C => @fieldParentPtr(C, "base", base).flush(), | |
| 183 | .elf => @fieldParentPtr(Elf, "base", base).flush(), | |
| 184 | .c => @fieldParentPtr(C, "base", base).flush(), | |
| 178 | 185 | }; |
| 179 | 186 | } |
| 180 | 187 | |
| 181 | 188 | pub fn freeDecl(base: *File, decl: *Module.Decl) void { |
| 182 | 189 | switch (base.tag) { |
| 183 | .Elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), | |
| 184 | .C => unreachable, | |
| 190 | .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), | |
| 191 | .c => unreachable, | |
| 185 | 192 | } |
| 186 | 193 | } |
| 187 | 194 | |
| 188 | 195 | pub fn errorFlags(base: *File) ErrorFlags { |
| 189 | 196 | return switch (base.tag) { |
| 190 | .Elf => @fieldParentPtr(Elf, "base", base).error_flags, | |
| 191 | .C => return .{ .no_entry_point_found = false }, | |
| 192 | }; | |
| 193 | } | |
| 194 | ||
| 195 | pub fn options(base: *File) Options { | |
| 196 | return switch (base.tag) { | |
| 197 | .Elf => @fieldParentPtr(Elf, "base", base).options, | |
| 198 | .C => @fieldParentPtr(C, "base", base).options, | |
| 197 | .elf => @fieldParentPtr(Elf, "base", base).error_flags, | |
| 198 | .c => return .{ .no_entry_point_found = false }, | |
| 199 | 199 | }; |
| 200 | 200 | } |
| 201 | 201 | |
| ... | ... | @@ -207,14 +207,14 @@ pub const File = struct { |
| 207 | 207 | exports: []const *Module.Export, |
| 208 | 208 | ) !void { |
| 209 | 209 | switch (base.tag) { |
| 210 | .Elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), | |
| 211 | .C => return {}, | |
| 210 | .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), | |
| 211 | .c => return {}, | |
| 212 | 212 | } |
| 213 | 213 | } |
| 214 | 214 | |
| 215 | 215 | pub const Tag = enum { |
| 216 | Elf, | |
| 217 | C, | |
| 216 | elf, | |
| 217 | c, | |
| 218 | 218 | }; |
| 219 | 219 | |
| 220 | 220 | pub const ErrorFlags = struct { |
| ... | ... | @@ -222,15 +222,15 @@ pub const File = struct { |
| 222 | 222 | }; |
| 223 | 223 | |
| 224 | 224 | pub const C = struct { |
| 225 | pub const base_tag: Tag = .C; | |
| 226 | base: File = File{ .tag = base_tag }, | |
| 225 | pub const base_tag: Tag = .c; | |
| 226 | ||
| 227 | base: File, | |
| 227 | 228 | |
| 228 | 229 | allocator: *Allocator, |
| 229 | 230 | header: std.ArrayList(u8), |
| 230 | 231 | constants: std.ArrayList(u8), |
| 231 | 232 | main: std.ArrayList(u8), |
| 232 | 233 | file: ?fs.File, |
| 233 | options: Options, | |
| 234 | 234 | called: std.StringHashMap(void), |
| 235 | 235 | need_stddef: bool = false, |
| 236 | 236 | need_stdint: bool = false, |
| ... | ... | @@ -294,13 +294,13 @@ pub const File = struct { |
| 294 | 294 | }; |
| 295 | 295 | |
| 296 | 296 | pub const Elf = struct { |
| 297 | pub const base_tag: Tag = .Elf; | |
| 298 | base: File = File{ .tag = base_tag }, | |
| 297 | pub const base_tag: Tag = .elf; | |
| 298 | ||
| 299 | base: File, | |
| 299 | 300 | |
| 300 | 301 | allocator: *Allocator, |
| 301 | 302 | file: ?fs.File, |
| 302 | 303 | owns_file_handle: bool, |
| 303 | options: Options, | |
| 304 | 304 | ptr_width: enum { p32, p64 }, |
| 305 | 305 | |
| 306 | 306 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. |
| ... | ... | @@ -460,13 +460,13 @@ pub const File = struct { |
| 460 | 460 | self.file = try dir.createFile(sub_path, .{ |
| 461 | 461 | .truncate = false, |
| 462 | 462 | .read = true, |
| 463 | .mode = determineMode(self.options), | |
| 463 | .mode = determineMode(self.base.options), | |
| 464 | 464 | }); |
| 465 | 465 | } |
| 466 | 466 | |
| 467 | 467 | /// Returns end pos of collision, if any. |
| 468 | 468 | fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { |
| 469 | const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32; | |
| 469 | const small_ptr = self.base.options.target.cpu.arch.ptrBitWidth() == 32; | |
| 470 | 470 | const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); |
| 471 | 471 | if (start < ehdr_size) |
| 472 | 472 | return ehdr_size; |
| ... | ... | @@ -569,7 +569,7 @@ pub const File = struct { |
| 569 | 569 | }; |
| 570 | 570 | if (self.phdr_load_re_index == null) { |
| 571 | 571 | self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len); |
| 572 | const file_size = self.options.program_code_size_hint; | |
| 572 | const file_size = self.base.options.program_code_size_hint; | |
| 573 | 573 | const p_align = 0x1000; |
| 574 | 574 | const off = self.findFreeSpace(file_size, p_align); |
| 575 | 575 | std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); |
| ... | ... | @@ -588,7 +588,7 @@ pub const File = struct { |
| 588 | 588 | } |
| 589 | 589 | if (self.phdr_got_index == null) { |
| 590 | 590 | self.phdr_got_index = @intCast(u16, self.program_headers.items.len); |
| 591 | const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint; | |
| 591 | const file_size = @as(u64, ptr_size) * self.base.options.symbol_count_hint; | |
| 592 | 592 | // We really only need ptr alignment but since we are using PROGBITS, linux requires |
| 593 | 593 | // page align. |
| 594 | 594 | const p_align = 0x1000; |
| ... | ... | @@ -671,7 +671,7 @@ pub const File = struct { |
| 671 | 671 | self.symtab_section_index = @intCast(u16, self.sections.items.len); |
| 672 | 672 | const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); |
| 673 | 673 | const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); |
| 674 | const file_size = self.options.symbol_count_hint * each_size; | |
| 674 | const file_size = self.base.options.symbol_count_hint * each_size; | |
| 675 | 675 | const off = self.findFreeSpace(file_size, min_align); |
| 676 | 676 | std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); |
| 677 | 677 | |
| ... | ... | @@ -726,7 +726,7 @@ pub const File = struct { |
| 726 | 726 | |
| 727 | 727 | /// Commit pending changes and write headers. |
| 728 | 728 | pub fn flush(self: *Elf) !void { |
| 729 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 729 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 730 | 730 | |
| 731 | 731 | // Unfortunately these have to be buffered and done at the end because ELF does not allow |
| 732 | 732 | // mixing local and global symbols within a symbol table. |
| ... | ... | @@ -845,7 +845,7 @@ pub const File = struct { |
| 845 | 845 | } |
| 846 | 846 | self.shdr_table_dirty = false; |
| 847 | 847 | } |
| 848 | if (self.entry_addr == null and self.options.output_mode == .Exe) { | |
| 848 | if (self.entry_addr == null and self.base.options.output_mode == .Exe) { | |
| 849 | 849 | std.log.debug(.link, "no_entry_point_found = true\n", .{}); |
| 850 | 850 | self.error_flags.no_entry_point_found = true; |
| 851 | 851 | } else { |
| ... | ... | @@ -875,7 +875,7 @@ pub const File = struct { |
| 875 | 875 | }; |
| 876 | 876 | index += 1; |
| 877 | 877 | |
| 878 | const endian = self.options.target.cpu.arch.endian(); | |
| 878 | const endian = self.base.options.target.cpu.arch.endian(); | |
| 879 | 879 | hdr_buf[index] = switch (endian) { |
| 880 | 880 | .Little => elf.ELFDATA2LSB, |
| 881 | 881 | .Big => elf.ELFDATA2MSB, |
| ... | ... | @@ -893,10 +893,10 @@ pub const File = struct { |
| 893 | 893 | |
| 894 | 894 | assert(index == 16); |
| 895 | 895 | |
| 896 | const elf_type = switch (self.options.output_mode) { | |
| 896 | const elf_type = switch (self.base.options.output_mode) { | |
| 897 | 897 | .Exe => elf.ET.EXEC, |
| 898 | 898 | .Obj => elf.ET.REL, |
| 899 | .Lib => switch (self.options.link_mode) { | |
| 899 | .Lib => switch (self.base.options.link_mode) { | |
| 900 | 900 | .Static => elf.ET.REL, |
| 901 | 901 | .Dynamic => elf.ET.DYN, |
| 902 | 902 | }, |
| ... | ... | @@ -904,7 +904,7 @@ pub const File = struct { |
| 904 | 904 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian); |
| 905 | 905 | index += 2; |
| 906 | 906 | |
| 907 | const machine = self.options.target.cpu.arch.toElfMachine(); | |
| 907 | const machine = self.base.options.target.cpu.arch.toElfMachine(); | |
| 908 | 908 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian); |
| 909 | 909 | index += 2; |
| 910 | 910 | |
| ... | ... | @@ -1216,7 +1216,7 @@ pub const File = struct { |
| 1216 | 1216 | }, |
| 1217 | 1217 | }; |
| 1218 | 1218 | |
| 1219 | const required_alignment = typed_value.ty.abiAlignment(self.options.target); | |
| 1219 | const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); | |
| 1220 | 1220 | |
| 1221 | 1221 | const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) { |
| 1222 | 1222 | .Fn => elf.STT_FUNC, |
| ... | ... | @@ -1361,9 +1361,9 @@ pub const File = struct { |
| 1361 | 1361 | } |
| 1362 | 1362 | |
| 1363 | 1363 | fn writeProgHeader(self: *Elf, index: usize) !void { |
| 1364 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 1364 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 1365 | 1365 | const offset = self.program_headers.items[index].p_offset; |
| 1366 | switch (self.options.target.cpu.arch.ptrBitWidth()) { | |
| 1366 | switch (self.base.options.target.cpu.arch.ptrBitWidth()) { | |
| 1367 | 1367 | 32 => { |
| 1368 | 1368 | var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; |
| 1369 | 1369 | if (foreign_endian) { |
| ... | ... | @@ -1383,9 +1383,9 @@ pub const File = struct { |
| 1383 | 1383 | } |
| 1384 | 1384 | |
| 1385 | 1385 | fn writeSectHeader(self: *Elf, index: usize) !void { |
| 1386 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 1386 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 1387 | 1387 | const offset = self.sections.items[index].sh_offset; |
| 1388 | switch (self.options.target.cpu.arch.ptrBitWidth()) { | |
| 1388 | switch (self.base.options.target.cpu.arch.ptrBitWidth()) { | |
| 1389 | 1389 | 32 => { |
| 1390 | 1390 | var shdr: [1]elf.Elf32_Shdr = undefined; |
| 1391 | 1391 | shdr[0] = sectHeaderTo32(self.sections.items[index]); |
| ... | ... | @@ -1433,7 +1433,7 @@ pub const File = struct { |
| 1433 | 1433 | |
| 1434 | 1434 | self.offset_table_count_dirty = false; |
| 1435 | 1435 | } |
| 1436 | const endian = self.options.target.cpu.arch.endian(); | |
| 1436 | const endian = self.base.options.target.cpu.arch.endian(); | |
| 1437 | 1437 | const off = shdr.sh_offset + @as(u64, entry_size) * index; |
| 1438 | 1438 | switch (self.ptr_width) { |
| 1439 | 1439 | .p32 => { |
| ... | ... | @@ -1475,7 +1475,7 @@ pub const File = struct { |
| 1475 | 1475 | syms_sect.sh_size = needed_size; // anticipating adding the global symbols later |
| 1476 | 1476 | self.shdr_table_dirty = true; // TODO look into only writing one section |
| 1477 | 1477 | } |
| 1478 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 1478 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 1479 | 1479 | switch (self.ptr_width) { |
| 1480 | 1480 | .p32 => { |
| 1481 | 1481 | var sym = [1]elf.Elf32_Sym{ |
| ... | ... | @@ -1511,7 +1511,7 @@ pub const File = struct { |
| 1511 | 1511 | .p32 => @sizeOf(elf.Elf32_Sym), |
| 1512 | 1512 | .p64 => @sizeOf(elf.Elf64_Sym), |
| 1513 | 1513 | }; |
| 1514 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 1514 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 1515 | 1515 | const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size; |
| 1516 | 1516 | switch (self.ptr_width) { |
| 1517 | 1517 | .p32 => { |
| ... | ... | @@ -1577,9 +1577,12 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !Fi |
| 1577 | 1577 | } |
| 1578 | 1578 | |
| 1579 | 1579 | var self: File.Elf = .{ |
| 1580 | .base = .{ | |
| 1581 | .tag = .elf, | |
| 1582 | .options = options, | |
| 1583 | }, | |
| 1580 | 1584 | .allocator = allocator, |
| 1581 | 1585 | .file = file, |
| 1582 | .options = options, | |
| 1583 | 1586 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { |
| 1584 | 1587 | 32 => .p32, |
| 1585 | 1588 | 64 => .p64, |
| ... | ... | @@ -1637,10 +1640,13 @@ fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !Fil |
| 1637 | 1640 | .raw => return error.IncrFailed, |
| 1638 | 1641 | } |
| 1639 | 1642 | var self: File.Elf = .{ |
| 1643 | .base = .{ | |
| 1644 | .tag = .elf, | |
| 1645 | .options = options, | |
| 1646 | }, | |
| 1640 | 1647 | .allocator = allocator, |
| 1641 | 1648 | .file = file, |
| 1642 | 1649 | .owns_file_handle = false, |
| 1643 | .options = options, | |
| 1644 | 1650 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { |
| 1645 | 1651 | 32 => .p32, |
| 1646 | 1652 | 64 => .p64, |
src-self-hosted/type.zig+75-12| ... | ... | @@ -380,7 +380,7 @@ pub const Type = extern union { |
| 380 | 380 | }, |
| 381 | 381 | .single_mut_pointer => { |
| 382 | 382 | const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", ty.ptr_otherwise); |
| 383 | try out_stream.writeAll("* "); | |
| 383 | try out_stream.writeAll("*"); | |
| 384 | 384 | ty = payload.pointee_type; |
| 385 | 385 | continue; |
| 386 | 386 | }, |
| ... | ... | @@ -803,6 +803,58 @@ pub const Type = extern union { |
| 803 | 803 | }; |
| 804 | 804 | } |
| 805 | 805 | |
| 806 | pub fn isVolatilePtr(self: Type) bool { | |
| 807 | return switch (self.tag()) { | |
| 808 | .u8, | |
| 809 | .i8, | |
| 810 | .u16, | |
| 811 | .i16, | |
| 812 | .u32, | |
| 813 | .i32, | |
| 814 | .u64, | |
| 815 | .i64, | |
| 816 | .usize, | |
| 817 | .isize, | |
| 818 | .c_short, | |
| 819 | .c_ushort, | |
| 820 | .c_int, | |
| 821 | .c_uint, | |
| 822 | .c_long, | |
| 823 | .c_ulong, | |
| 824 | .c_longlong, | |
| 825 | .c_ulonglong, | |
| 826 | .c_longdouble, | |
| 827 | .f16, | |
| 828 | .f32, | |
| 829 | .f64, | |
| 830 | .f128, | |
| 831 | .c_void, | |
| 832 | .bool, | |
| 833 | .void, | |
| 834 | .type, | |
| 835 | .anyerror, | |
| 836 | .comptime_int, | |
| 837 | .comptime_float, | |
| 838 | .noreturn, | |
| 839 | .@"null", | |
| 840 | .@"undefined", | |
| 841 | .array, | |
| 842 | .array_u8_sentinel_0, | |
| 843 | .fn_noreturn_no_args, | |
| 844 | .fn_void_no_args, | |
| 845 | .fn_naked_noreturn_no_args, | |
| 846 | .fn_ccc_void_no_args, | |
| 847 | .function, | |
| 848 | .int_unsigned, | |
| 849 | .int_signed, | |
| 850 | .single_mut_pointer, | |
| 851 | .single_const_pointer, | |
| 852 | .single_const_pointer_to_comptime_int, | |
| 853 | .const_slice_u8, | |
| 854 | => false, | |
| 855 | }; | |
| 856 | } | |
| 857 | ||
| 806 | 858 | /// Asserts the type is a pointer or array type. |
| 807 | 859 | pub fn elemType(self: Type) Type { |
| 808 | 860 | return switch (self.tag()) { |
| ... | ... | @@ -1601,7 +1653,7 @@ pub const Type = extern union { |
| 1601 | 1653 | }; |
| 1602 | 1654 | } |
| 1603 | 1655 | |
| 1604 | pub fn onePossibleValue(self: Type) bool { | |
| 1656 | pub fn onePossibleValue(self: Type) ?Value { | |
| 1605 | 1657 | var ty = self; |
| 1606 | 1658 | while (true) switch (ty.tag()) { |
| 1607 | 1659 | .f16, |
| ... | ... | @@ -1640,21 +1692,32 @@ pub const Type = extern union { |
| 1640 | 1692 | .single_const_pointer_to_comptime_int, |
| 1641 | 1693 | .array_u8_sentinel_0, |
| 1642 | 1694 | .const_slice_u8, |
| 1643 | => return false, | |
| 1644 | ||
| 1645 | 1695 | .c_void, |
| 1646 | .void, | |
| 1647 | .noreturn, | |
| 1648 | .@"null", | |
| 1649 | .@"undefined", | |
| 1650 | => return true, | |
| 1696 | => return null, | |
| 1697 | ||
| 1698 | .void => return Value.initTag(.void_value), | |
| 1699 | .noreturn => return Value.initTag(.unreachable_value), | |
| 1700 | .@"null" => return Value.initTag(.null_value), | |
| 1701 | .@"undefined" => return Value.initTag(.undef), | |
| 1651 | 1702 | |
| 1652 | .int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0, | |
| 1653 | .int_signed => return ty.cast(Payload.IntSigned).?.bits == 0, | |
| 1703 | .int_unsigned => { | |
| 1704 | if (ty.cast(Payload.IntUnsigned).?.bits == 0) { | |
| 1705 | return Value.initTag(.zero); | |
| 1706 | } else { | |
| 1707 | return null; | |
| 1708 | } | |
| 1709 | }, | |
| 1710 | .int_signed => { | |
| 1711 | if (ty.cast(Payload.IntSigned).?.bits == 0) { | |
| 1712 | return Value.initTag(.zero); | |
| 1713 | } else { | |
| 1714 | return null; | |
| 1715 | } | |
| 1716 | }, | |
| 1654 | 1717 | .array => { |
| 1655 | 1718 | const array = ty.cast(Payload.Array).?; |
| 1656 | 1719 | if (array.len == 0) |
| 1657 | return true; | |
| 1720 | return Value.initTag(.empty_array); | |
| 1658 | 1721 | ty = array.elem_type; |
| 1659 | 1722 | continue; |
| 1660 | 1723 | }, |
src-self-hosted/value.zig+50-22| ... | ... | @@ -63,7 +63,9 @@ pub const Value = extern union { |
| 63 | 63 | |
| 64 | 64 | undef, |
| 65 | 65 | zero, |
| 66 | the_one_possible_value, // when the type only has one possible value | |
| 66 | void_value, | |
| 67 | unreachable_value, | |
| 68 | empty_array, | |
| 67 | 69 | null_value, |
| 68 | 70 | bool_true, |
| 69 | 71 | bool_false, // See last_no_payload_tag below. |
| ... | ... | @@ -164,7 +166,9 @@ pub const Value = extern union { |
| 164 | 166 | .const_slice_u8_type, |
| 165 | 167 | .undef, |
| 166 | 168 | .zero, |
| 167 | .the_one_possible_value, | |
| 169 | .void_value, | |
| 170 | .unreachable_value, | |
| 171 | .empty_array, | |
| 168 | 172 | .null_value, |
| 169 | 173 | .bool_true, |
| 170 | 174 | .bool_false, |
| ... | ... | @@ -285,7 +289,8 @@ pub const Value = extern union { |
| 285 | 289 | .null_value => return out_stream.writeAll("null"), |
| 286 | 290 | .undef => return out_stream.writeAll("undefined"), |
| 287 | 291 | .zero => return out_stream.writeAll("0"), |
| 288 | .the_one_possible_value => return out_stream.writeAll("(one possible value)"), | |
| 292 | .void_value => return out_stream.writeAll("{}"), | |
| 293 | .unreachable_value => return out_stream.writeAll("unreachable"), | |
| 289 | 294 | .bool_true => return out_stream.writeAll("true"), |
| 290 | 295 | .bool_false => return out_stream.writeAll("false"), |
| 291 | 296 | .ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream), |
| ... | ... | @@ -312,6 +317,7 @@ pub const Value = extern union { |
| 312 | 317 | try out_stream.print("&[{}] ", .{elem_ptr.index}); |
| 313 | 318 | val = elem_ptr.array_ptr; |
| 314 | 319 | }, |
| 320 | .empty_array => return out_stream.writeAll(".{}"), | |
| 315 | 321 | .bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream), |
| 316 | 322 | .repeated => { |
| 317 | 323 | try out_stream.writeAll("(repeated) "); |
| ... | ... | @@ -388,7 +394,9 @@ pub const Value = extern union { |
| 388 | 394 | |
| 389 | 395 | .undef, |
| 390 | 396 | .zero, |
| 391 | .the_one_possible_value, | |
| 397 | .void_value, | |
| 398 | .unreachable_value, | |
| 399 | .empty_array, | |
| 392 | 400 | .bool_true, |
| 393 | 401 | .bool_false, |
| 394 | 402 | .null_value, |
| ... | ... | @@ -460,15 +468,18 @@ pub const Value = extern union { |
| 460 | 468 | .decl_ref, |
| 461 | 469 | .elem_ptr, |
| 462 | 470 | .bytes, |
| 463 | .undef, | |
| 464 | 471 | .repeated, |
| 465 | 472 | .float_16, |
| 466 | 473 | .float_32, |
| 467 | 474 | .float_64, |
| 468 | 475 | .float_128, |
| 476 | .void_value, | |
| 477 | .unreachable_value, | |
| 478 | .empty_array, | |
| 469 | 479 | => unreachable, |
| 470 | 480 | |
| 471 | .the_one_possible_value, // An integer with one possible value is always zero. | |
| 481 | .undef => unreachable, | |
| 482 | ||
| 472 | 483 | .zero, |
| 473 | 484 | .bool_false, |
| 474 | 485 | => return BigIntMutable.init(&space.limbs, 0).toConst(), |
| ... | ... | @@ -532,16 +543,19 @@ pub const Value = extern union { |
| 532 | 543 | .decl_ref, |
| 533 | 544 | .elem_ptr, |
| 534 | 545 | .bytes, |
| 535 | .undef, | |
| 536 | 546 | .repeated, |
| 537 | 547 | .float_16, |
| 538 | 548 | .float_32, |
| 539 | 549 | .float_64, |
| 540 | 550 | .float_128, |
| 551 | .void_value, | |
| 552 | .unreachable_value, | |
| 553 | .empty_array, | |
| 541 | 554 | => unreachable, |
| 542 | 555 | |
| 556 | .undef => unreachable, | |
| 557 | ||
| 543 | 558 | .zero, |
| 544 | .the_one_possible_value, // an integer with one possible value is always zero | |
| 545 | 559 | .bool_false, |
| 546 | 560 | => return 0, |
| 547 | 561 | |
| ... | ... | @@ -570,7 +584,7 @@ pub const Value = extern union { |
| 570 | 584 | .float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val), |
| 571 | 585 | .float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val), |
| 572 | 586 | |
| 573 | .zero, .the_one_possible_value => 0, | |
| 587 | .zero => 0, | |
| 574 | 588 | .int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int), |
| 575 | 589 | // .int_i64 => @intToFloat(f128, self.cast(Payload.Int_i64).?.int), |
| 576 | 590 | .int_i64 => @panic("TODO lld: error: undefined symbol: __floatditf"), |
| ... | ... | @@ -637,9 +651,11 @@ pub const Value = extern union { |
| 637 | 651 | .float_32, |
| 638 | 652 | .float_64, |
| 639 | 653 | .float_128, |
| 654 | .void_value, | |
| 655 | .unreachable_value, | |
| 656 | .empty_array, | |
| 640 | 657 | => unreachable, |
| 641 | 658 | |
| 642 | .the_one_possible_value, // an integer with one possible value is always zero | |
| 643 | 659 | .zero, |
| 644 | 660 | .bool_false, |
| 645 | 661 | => return 0, |
| ... | ... | @@ -714,11 +730,13 @@ pub const Value = extern union { |
| 714 | 730 | .float_32, |
| 715 | 731 | .float_64, |
| 716 | 732 | .float_128, |
| 733 | .void_value, | |
| 734 | .unreachable_value, | |
| 735 | .empty_array, | |
| 717 | 736 | => unreachable, |
| 718 | 737 | |
| 719 | 738 | .zero, |
| 720 | 739 | .undef, |
| 721 | .the_one_possible_value, // an integer with one possible value is always zero | |
| 722 | 740 | .bool_false, |
| 723 | 741 | => return true, |
| 724 | 742 | |
| ... | ... | @@ -797,13 +815,13 @@ pub const Value = extern union { |
| 797 | 815 | // return Value.initPayload(&res_payload.base).copy(allocator); |
| 798 | 816 | }, |
| 799 | 817 | 32 => { |
| 800 | var res_payload = Value.Payload.Float_32{.val = self.toFloat(f32)}; | |
| 818 | var res_payload = Value.Payload.Float_32{ .val = self.toFloat(f32) }; | |
| 801 | 819 | if (!self.eql(Value.initPayload(&res_payload.base))) |
| 802 | 820 | return error.Overflow; |
| 803 | 821 | return Value.initPayload(&res_payload.base).copy(allocator); |
| 804 | 822 | }, |
| 805 | 823 | 64 => { |
| 806 | var res_payload = Value.Payload.Float_64{.val = self.toFloat(f64)}; | |
| 824 | var res_payload = Value.Payload.Float_64{ .val = self.toFloat(f64) }; | |
| 807 | 825 | if (!self.eql(Value.initPayload(&res_payload.base))) |
| 808 | 826 | return error.Overflow; |
| 809 | 827 | return Value.initPayload(&res_payload.base).copy(allocator); |
| ... | ... | @@ -875,7 +893,9 @@ pub const Value = extern union { |
| 875 | 893 | .int_i64, |
| 876 | 894 | .int_big_positive, |
| 877 | 895 | .int_big_negative, |
| 878 | .the_one_possible_value, | |
| 896 | .empty_array, | |
| 897 | .void_value, | |
| 898 | .unreachable_value, | |
| 879 | 899 | => unreachable, |
| 880 | 900 | |
| 881 | 901 | .zero => false, |
| ... | ... | @@ -939,10 +959,12 @@ pub const Value = extern union { |
| 939 | 959 | .bytes, |
| 940 | 960 | .repeated, |
| 941 | 961 | .undef, |
| 962 | .void_value, | |
| 963 | .unreachable_value, | |
| 964 | .empty_array, | |
| 942 | 965 | => unreachable, |
| 943 | 966 | |
| 944 | 967 | .zero, |
| 945 | .the_one_possible_value, // an integer with one possible value is always zero | |
| 946 | 968 | .bool_false, |
| 947 | 969 | => .eq, |
| 948 | 970 | |
| ... | ... | @@ -964,8 +986,8 @@ pub const Value = extern union { |
| 964 | 986 | pub fn order(lhs: Value, rhs: Value) std.math.Order { |
| 965 | 987 | const lhs_tag = lhs.tag(); |
| 966 | 988 | const rhs_tag = rhs.tag(); |
| 967 | const lhs_is_zero = lhs_tag == .zero or lhs_tag == .the_one_possible_value; | |
| 968 | const rhs_is_zero = rhs_tag == .zero or rhs_tag == .the_one_possible_value; | |
| 989 | const lhs_is_zero = lhs_tag == .zero; | |
| 990 | const rhs_is_zero = rhs_tag == .zero; | |
| 969 | 991 | if (lhs_is_zero) return rhs.orderAgainstZero().invert(); |
| 970 | 992 | if (rhs_is_zero) return lhs.orderAgainstZero(); |
| 971 | 993 | |
| ... | ... | @@ -1071,9 +1093,11 @@ pub const Value = extern union { |
| 1071 | 1093 | .float_32, |
| 1072 | 1094 | .float_64, |
| 1073 | 1095 | .float_128, |
| 1096 | .void_value, | |
| 1097 | .unreachable_value, | |
| 1098 | .empty_array, | |
| 1074 | 1099 | => unreachable, |
| 1075 | 1100 | |
| 1076 | .the_one_possible_value => Value.initTag(.the_one_possible_value), | |
| 1077 | 1101 | .ref_val => self.cast(Payload.RefVal).?.val, |
| 1078 | 1102 | .decl_ref => self.cast(Payload.DeclRef).?.decl.value(), |
| 1079 | 1103 | .elem_ptr => { |
| ... | ... | @@ -1130,7 +1154,6 @@ pub const Value = extern union { |
| 1130 | 1154 | .single_const_pointer_to_comptime_int_type, |
| 1131 | 1155 | .const_slice_u8_type, |
| 1132 | 1156 | .zero, |
| 1133 | .the_one_possible_value, | |
| 1134 | 1157 | .bool_true, |
| 1135 | 1158 | .bool_false, |
| 1136 | 1159 | .null_value, |
| ... | ... | @@ -1147,8 +1170,12 @@ pub const Value = extern union { |
| 1147 | 1170 | .float_32, |
| 1148 | 1171 | .float_64, |
| 1149 | 1172 | .float_128, |
| 1173 | .void_value, | |
| 1174 | .unreachable_value, | |
| 1150 | 1175 | => unreachable, |
| 1151 | 1176 | |
| 1177 | .empty_array => unreachable, // out of bounds array index | |
| 1178 | ||
| 1152 | 1179 | .bytes => { |
| 1153 | 1180 | const int_payload = try allocator.create(Payload.Int_u64); |
| 1154 | 1181 | int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] }; |
| ... | ... | @@ -1175,8 +1202,7 @@ pub const Value = extern union { |
| 1175 | 1202 | return self.tag() == .undef; |
| 1176 | 1203 | } |
| 1177 | 1204 | |
| 1178 | /// Valid for all types. Asserts the value is not undefined. | |
| 1179 | /// `.the_one_possible_value` is reported as not null. | |
| 1205 | /// Valid for all types. Asserts the value is not undefined and not unreachable. | |
| 1180 | 1206 | pub fn isNull(self: Value) bool { |
| 1181 | 1207 | return switch (self.tag()) { |
| 1182 | 1208 | .ty, |
| ... | ... | @@ -1221,7 +1247,7 @@ pub const Value = extern union { |
| 1221 | 1247 | .single_const_pointer_to_comptime_int_type, |
| 1222 | 1248 | .const_slice_u8_type, |
| 1223 | 1249 | .zero, |
| 1224 | .the_one_possible_value, | |
| 1250 | .empty_array, | |
| 1225 | 1251 | .bool_true, |
| 1226 | 1252 | .bool_false, |
| 1227 | 1253 | .function, |
| ... | ... | @@ -1238,9 +1264,11 @@ pub const Value = extern union { |
| 1238 | 1264 | .float_32, |
| 1239 | 1265 | .float_64, |
| 1240 | 1266 | .float_128, |
| 1267 | .void_value, | |
| 1241 | 1268 | => false, |
| 1242 | 1269 | |
| 1243 | 1270 | .undef => unreachable, |
| 1271 | .unreachable_value => unreachable, | |
| 1244 | 1272 | .null_value => true, |
| 1245 | 1273 | }; |
| 1246 | 1274 | } |
src-self-hosted/zir.zig+47-13| ... | ... | @@ -183,6 +183,10 @@ pub const Inst = struct { |
| 183 | 183 | shl, |
| 184 | 184 | /// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type. |
| 185 | 185 | shr, |
| 186 | /// Create a const pointer type based on the element type. `*const T` | |
| 187 | single_const_ptr_type, | |
| 188 | /// Create a mutable pointer type based on the element type. `*T` | |
| 189 | single_mut_ptr_type, | |
| 186 | 190 | /// Write a value to a pointer. For loading, see `deref`. |
| 187 | 191 | store, |
| 188 | 192 | /// String Literal. Makes an anonymous Decl and then takes a pointer to it. |
| ... | ... | @@ -228,6 +232,8 @@ pub const Inst = struct { |
| 228 | 232 | .ref, |
| 229 | 233 | .bitcast_lvalue, |
| 230 | 234 | .typeof, |
| 235 | .single_const_ptr_type, | |
| 236 | .single_mut_ptr_type, | |
| 231 | 237 | => UnOp, |
| 232 | 238 | |
| 233 | 239 | .add, |
| ... | ... | @@ -242,6 +248,7 @@ pub const Inst = struct { |
| 242 | 248 | .mulwrap, |
| 243 | 249 | .shl, |
| 244 | 250 | .shr, |
| 251 | .store, | |
| 245 | 252 | .sub, |
| 246 | 253 | .subwrap, |
| 247 | 254 | .cmp_lt, |
| ... | ... | @@ -270,7 +277,6 @@ pub const Inst = struct { |
| 270 | 277 | .coerce_result_block_ptr => CoerceResultBlockPtr, |
| 271 | 278 | .compileerror => CompileError, |
| 272 | 279 | .@"const" => Const, |
| 273 | .store => Store, | |
| 274 | 280 | .str => Str, |
| 275 | 281 | .int => Int, |
| 276 | 282 | .inttype => IntType, |
| ... | ... | @@ -348,6 +354,8 @@ pub const Inst = struct { |
| 348 | 354 | .ret_type, |
| 349 | 355 | .shl, |
| 350 | 356 | .shr, |
| 357 | .single_const_ptr_type, | |
| 358 | .single_mut_ptr_type, | |
| 351 | 359 | .store, |
| 352 | 360 | .str, |
| 353 | 361 | .sub, |
| ... | ... | @@ -545,17 +553,6 @@ pub const Inst = struct { |
| 545 | 553 | kw_args: struct {}, |
| 546 | 554 | }; |
| 547 | 555 | |
| 548 | pub const Store = struct { | |
| 549 | pub const base_tag = Tag.store; | |
| 550 | base: Inst, | |
| 551 | ||
| 552 | positionals: struct { | |
| 553 | ptr: *Inst, | |
| 554 | value: *Inst, | |
| 555 | }, | |
| 556 | kw_args: struct {}, | |
| 557 | }; | |
| 558 | ||
| 559 | 556 | pub const Str = struct { |
| 560 | 557 | pub const base_tag = Tag.str; |
| 561 | 558 | base: Inst, |
| ... | ... | @@ -745,7 +742,7 @@ pub const Inst = struct { |
| 745 | 742 | .@"false" => .{ .ty = Type.initTag(.bool), .val = Value.initTag(.bool_false) }, |
| 746 | 743 | .@"null" => .{ .ty = Type.initTag(.@"null"), .val = Value.initTag(.null_value) }, |
| 747 | 744 | .@"undefined" => .{ .ty = Type.initTag(.@"undefined"), .val = Value.initTag(.undef) }, |
| 748 | .void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.the_one_possible_value) }, | |
| 745 | .void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.void_value) }, | |
| 749 | 746 | }; |
| 750 | 747 | } |
| 751 | 748 | }; |
| ... | ... | @@ -1601,6 +1598,21 @@ const EmitZIR = struct { |
| 1601 | 1598 | const decl = decl_ref.decl; |
| 1602 | 1599 | return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl)); |
| 1603 | 1600 | } |
| 1601 | if (typed_value.val.isUndef()) { | |
| 1602 | const as_inst = try self.arena.allocator.create(Inst.BinOp); | |
| 1603 | as_inst.* = .{ | |
| 1604 | .base = .{ | |
| 1605 | .tag = .as, | |
| 1606 | .src = src, | |
| 1607 | }, | |
| 1608 | .positionals = .{ | |
| 1609 | .lhs = (try self.emitType(src, typed_value.ty)).inst, | |
| 1610 | .rhs = (try self.emitPrimitive(src, .@"undefined")).inst, | |
| 1611 | }, | |
| 1612 | .kw_args = .{}, | |
| 1613 | }; | |
| 1614 | return self.emitUnnamedDecl(&as_inst.base); | |
| 1615 | } | |
| 1604 | 1616 | switch (typed_value.ty.zigTypeTag()) { |
| 1605 | 1617 | .Pointer => { |
| 1606 | 1618 | const ptr_elem_type = typed_value.ty.elemType(); |
| ... | ... | @@ -1837,9 +1849,12 @@ const EmitZIR = struct { |
| 1837 | 1849 | .ptrtoint => try self.emitUnOp(inst.src, new_body, inst.castTag(.ptrtoint).?, .ptrtoint), |
| 1838 | 1850 | .isnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnull).?, .isnull), |
| 1839 | 1851 | .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull), |
| 1852 | .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref), | |
| 1853 | .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref), | |
| 1840 | 1854 | |
| 1841 | 1855 | .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add), |
| 1842 | 1856 | .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub), |
| 1857 | .store => try self.emitBinOp(inst.src, new_body, inst.castTag(.store).?, .store), | |
| 1843 | 1858 | .cmp_lt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lt).?, .cmp_lt), |
| 1844 | 1859 | .cmp_lte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lte).?, .cmp_lte), |
| 1845 | 1860 | .cmp_eq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_eq).?, .cmp_eq), |
| ... | ... | @@ -2103,6 +2118,25 @@ const EmitZIR = struct { |
| 2103 | 2118 | }; |
| 2104 | 2119 | return self.emitUnnamedDecl(&inttype_inst.base); |
| 2105 | 2120 | }, |
| 2121 | .Pointer => { | |
| 2122 | if (ty.isSinglePointer()) { | |
| 2123 | const inst = try self.arena.allocator.create(Inst.UnOp); | |
| 2124 | const tag: Inst.Tag = if (ty.isConstPtr()) .single_const_ptr_type else .single_mut_ptr_type; | |
| 2125 | inst.* = .{ | |
| 2126 | .base = .{ | |
| 2127 | .src = src, | |
| 2128 | .tag = tag, | |
| 2129 | }, | |
| 2130 | .positionals = .{ | |
| 2131 | .operand = (try self.emitType(src, ty.elemType())).inst, | |
| 2132 | }, | |
| 2133 | .kw_args = .{}, | |
| 2134 | }; | |
| 2135 | return self.emitUnnamedDecl(&inst.base); | |
| 2136 | } else { | |
| 2137 | std.debug.panic("TODO implement emitType for {}", .{ty}); | |
| 2138 | } | |
| 2139 | }, | |
| 2106 | 2140 | else => std.debug.panic("TODO implement emitType for {}", .{ty}), |
| 2107 | 2141 | }, |
| 2108 | 2142 | } |
src-self-hosted/zir_sema.zig+28-5| ... | ... | @@ -50,6 +50,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! |
| 50 | 50 | .ref => return analyzeInstRef(mod, scope, old_inst.castTag(.ref).?), |
| 51 | 51 | .ret_ptr => return analyzeInstRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?), |
| 52 | 52 | .ret_type => return analyzeInstRetType(mod, scope, old_inst.castTag(.ret_type).?), |
| 53 | .single_const_ptr_type => return analyzeInstSingleConstPtrType(mod, scope, old_inst.castTag(.single_const_ptr_type).?), | |
| 54 | .single_mut_ptr_type => return analyzeInstSingleMutPtrType(mod, scope, old_inst.castTag(.single_mut_ptr_type).?), | |
| 53 | 55 | .store => return analyzeInstStore(mod, scope, old_inst.castTag(.store).?), |
| 54 | 56 | .str => return analyzeInstStr(mod, scope, old_inst.castTag(.str).?), |
| 55 | 57 | .int => { |
| ... | ... | @@ -287,8 +289,11 @@ fn analyzeInstCoerceResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp |
| 287 | 289 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultPtr", .{}); |
| 288 | 290 | } |
| 289 | 291 | |
| 292 | /// Equivalent to `as(ptr_child_type(typeof(ptr)), value)`. | |
| 290 | 293 | fn analyzeInstCoerceToPtrElem(mod: *Module, scope: *Scope, inst: *zir.Inst.CoerceToPtrElem) InnerError!*Inst { |
| 291 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceToPtrElem", .{}); | |
| 294 | const ptr = try resolveInst(mod, scope, inst.positionals.ptr); | |
| 295 | const operand = try resolveInst(mod, scope, inst.positionals.value); | |
| 296 | return mod.coerce(scope, ptr.ty.elemType(), operand); | |
| 292 | 297 | } |
| 293 | 298 | |
| 294 | 299 | fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { |
| ... | ... | @@ -296,7 +301,10 @@ fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerErr |
| 296 | 301 | } |
| 297 | 302 | |
| 298 | 303 | fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { |
| 299 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstRef", .{}); | |
| 304 | const operand = try resolveInst(mod, scope, inst.positionals.operand); | |
| 305 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | |
| 306 | const ptr_type = try mod.singleConstPtrType(scope, inst.base.src, operand.ty); | |
| 307 | return mod.addUnOp(b, inst.base.src, ptr_type, .ref, operand); | |
| 300 | 308 | } |
| 301 | 309 | |
| 302 | 310 | fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { |
| ... | ... | @@ -333,8 +341,10 @@ fn analyzeInstAllocInferred(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) I |
| 333 | 341 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{}); |
| 334 | 342 | } |
| 335 | 343 | |
| 336 | fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.Store) InnerError!*Inst { | |
| 337 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstStore", .{}); | |
| 344 | fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | |
| 345 | const ptr = try resolveInst(mod, scope, inst.positionals.lhs); | |
| 346 | const value = try resolveInst(mod, scope, inst.positionals.rhs); | |
| 347 | return mod.storePtr(scope, inst.base.src, ptr, value); | |
| 338 | 348 | } |
| 339 | 349 | |
| 340 | 350 | fn analyzeInstParamType(mod: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerError!*Inst { |
| ... | ... | @@ -872,7 +882,7 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn |
| 872 | 882 | fn analyzeInstComptimeOp(mod: *Module, scope: *Scope, res_type: Type, inst: *zir.Inst.BinOp, lhs_val: Value, rhs_val: Value) InnerError!*Inst { |
| 873 | 883 | // incase rhs is 0, simply return lhs without doing any calculations |
| 874 | 884 | // TODO Once division is implemented we should throw an error when dividing by 0. |
| 875 | if (rhs_val.tag() == .zero or rhs_val.tag() == .the_one_possible_value) { | |
| 885 | if (rhs_val.compareWithZero(.eq)) { | |
| 876 | 886 | return mod.constInst(scope, inst.base.src, .{ |
| 877 | 887 | .ty = res_type, |
| 878 | 888 | .val = lhs_val, |
| ... | ... | @@ -1073,6 +1083,7 @@ fn analyzeInstUnreachNoChk(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) |
| 1073 | 1083 | |
| 1074 | 1084 | fn analyzeInstUnreachable(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { |
| 1075 | 1085 | const b = try mod.requireRuntimeBlock(scope, unreach.base.src); |
| 1086 | // TODO Add compile error for @optimizeFor occurring too late in a scope. | |
| 1076 | 1087 | if (mod.wantSafety(scope)) { |
| 1077 | 1088 | // TODO Once we have a panic function to call, call it here instead of this. |
| 1078 | 1089 | _ = try mod.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint); |
| ... | ... | @@ -1129,3 +1140,15 @@ fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerErr |
| 1129 | 1140 | |
| 1130 | 1141 | return decl; |
| 1131 | 1142 | } |
| 1143 | ||
| 1144 | fn analyzeInstSingleConstPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | |
| 1145 | const elem_type = try resolveType(mod, scope, inst.positionals.operand); | |
| 1146 | const ty = try mod.singleConstPtrType(scope, inst.base.src, elem_type); | |
| 1147 | return mod.constType(scope, inst.base.src, ty); | |
| 1148 | } | |
| 1149 | ||
| 1150 | fn analyzeInstSingleMutPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | |
| 1151 | const elem_type = try resolveType(mod, scope, inst.positionals.operand); | |
| 1152 | const ty = try mod.singleMutPtrType(scope, inst.base.src, elem_type); | |
| 1153 | return mod.constType(scope, inst.base.src, ty); | |
| 1154 | } |
test/stage2/compare_output.zig+34| ... | ... | @@ -363,5 +363,39 @@ pub fn addCases(ctx: *TestContext) !void { |
| 363 | 363 | , |
| 364 | 364 | "", |
| 365 | 365 | ); |
| 366 | ||
| 367 | // Local mutable variables. | |
| 368 | case.addCompareOutput( | |
| 369 | \\export fn _start() noreturn { | |
| 370 | \\ assert(add(3, 4) == 7); | |
| 371 | \\ assert(add(20, 10) == 30); | |
| 372 | \\ | |
| 373 | \\ exit(); | |
| 374 | \\} | |
| 375 | \\ | |
| 376 | \\fn add(a: u32, b: u32) u32 { | |
| 377 | \\ var x: u32 = undefined; | |
| 378 | \\ x = 0; | |
| 379 | \\ x += a; | |
| 380 | \\ x += b; | |
| 381 | \\ return x; | |
| 382 | \\} | |
| 383 | \\ | |
| 384 | \\pub fn assert(ok: bool) void { | |
| 385 | \\ if (!ok) unreachable; // assertion failure | |
| 386 | \\} | |
| 387 | \\ | |
| 388 | \\fn exit() noreturn { | |
| 389 | \\ asm volatile ("syscall" | |
| 390 | \\ : | |
| 391 | \\ : [number] "{rax}" (231), | |
| 392 | \\ [arg1] "{rdi}" (0) | |
| 393 | \\ : "rcx", "r11", "memory" | |
| 394 | \\ ); | |
| 395 | \\ unreachable; | |
| 396 | \\} | |
| 397 | , | |
| 398 | "", | |
| 399 | ); | |
| 366 | 400 | } |
| 367 | 401 | } |