authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-22 13:36:08-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-22 13:36:08-07:00
log54f3b0a560f17effbb582f86ebf83c53916fc697
tree7f428fe750a9248cad7db55f8d889a02e474869e
parent24efbf5ddfd7037eb5e0ec32fe73e10631add788

stage2: clean up SPU Mk II code

* move SPU code from std to self hosted compiler * change std lib comments to be descriptive rather than prescriptive * avoid usingnamespace * fix case style of error codes * remove duplication of producer_string * generalize handling of less than 64 bit arch pointers * clean up SPU II related test harness code

11 files changed, 467 insertions(+), 439 deletions(-)

lib/std/spu.zig deleted-3
...@@ -1,3 +0,0 @@
1pub usingnamespace @import("spu/defines.zig");
2
3pub const interpreter = @import("spu/interpreter.zig").Interpreter;
lib/std/spu/defines.zig deleted-158
...@@ -1,158 +0,0 @@
1const std = @import("std");
2
3pub const ExecutionCondition = enum(u3) {
4 always = 0,
5 when_zero = 1,
6 not_zero = 2,
7 greater_zero = 3,
8 less_than_zero = 4,
9 greater_or_equal_zero = 5,
10 less_or_equal_zero = 6,
11 overflow = 7,
12};
13
14pub const InputBehaviour = enum(u2) {
15 zero = 0,
16 immediate = 1,
17 peek = 2,
18 pop = 3,
19};
20
21pub const OutputBehaviour = enum(u2) {
22 discard = 0,
23 push = 1,
24 jump = 2,
25 jump_relative = 3,
26};
27
28pub const Command = enum(u5) {
29 copy = 0,
30 ipget = 1,
31 get = 2,
32 set = 3,
33 store8 = 4,
34 store16 = 5,
35 load8 = 6,
36 load16 = 7,
37 undefined0 = 8,
38 undefined1 = 9,
39 frget = 10,
40 frset = 11,
41 bpget = 12,
42 bpset = 13,
43 spget = 14,
44 spset = 15,
45 add = 16,
46 sub = 17,
47 mul = 18,
48 div = 19,
49 mod = 20,
50 @"and" = 21,
51 @"or" = 22,
52 xor = 23,
53 not = 24,
54 signext = 25,
55 rol = 26,
56 ror = 27,
57 bswap = 28,
58 asr = 29,
59 lsl = 30,
60 lsr = 31,
61};
62
63pub const Instruction = packed struct {
64 condition: ExecutionCondition,
65 input0: InputBehaviour,
66 input1: InputBehaviour,
67 modify_flags: bool,
68 output: OutputBehaviour,
69 command: Command,
70 reserved: u1 = 0,
71
72 pub fn format(instr: Instruction, comptime fmt: []const u8, options: std.fmt.FormatOptions, out: anytype) !void {
73 try std.fmt.format(out, "0x{x:0<4} ", .{@bitCast(u16, instr)});
74 try out.writeAll(switch (instr.condition) {
75 .always => " ",
76 .when_zero => "== 0",
77 .not_zero => "!= 0",
78 .greater_zero => " > 0",
79 .less_than_zero => " < 0",
80 .greater_or_equal_zero => ">= 0",
81 .less_or_equal_zero => "<= 0",
82 .overflow => "ovfl",
83 });
84 try out.writeAll(" ");
85 try out.writeAll(switch (instr.input0) {
86 .zero => "zero",
87 .immediate => "imm ",
88 .peek => "peek",
89 .pop => "pop ",
90 });
91 try out.writeAll(" ");
92 try out.writeAll(switch (instr.input1) {
93 .zero => "zero",
94 .immediate => "imm ",
95 .peek => "peek",
96 .pop => "pop ",
97 });
98 try out.writeAll(" ");
99 try out.writeAll(switch (instr.command) {
100 .copy => "copy ",
101 .ipget => "ipget ",
102 .get => "get ",
103 .set => "set ",
104 .store8 => "store8 ",
105 .store16 => "store16 ",
106 .load8 => "load8 ",
107 .load16 => "load16 ",
108 .undefined0 => "undefined",
109 .undefined1 => "undefined",
110 .frget => "frget ",
111 .frset => "frset ",
112 .bpget => "bpget ",
113 .bpset => "bpset ",
114 .spget => "spget ",
115 .spset => "spset ",
116 .add => "add ",
117 .sub => "sub ",
118 .mul => "mul ",
119 .div => "div ",
120 .mod => "mod ",
121 .@"and" => "and ",
122 .@"or" => "or ",
123 .xor => "xor ",
124 .not => "not ",
125 .signext => "signext ",
126 .rol => "rol ",
127 .ror => "ror ",
128 .bswap => "bswap ",
129 .asr => "asr ",
130 .lsl => "lsl ",
131 .lsr => "lsr ",
132 });
133 try out.writeAll(" ");
134 try out.writeAll(switch (instr.output) {
135 .discard => "discard",
136 .push => "push ",
137 .jump => "jmp ",
138 .jump_relative => "rjmp ",
139 });
140 try out.writeAll(" ");
141 try out.writeAll(if (instr.modify_flags)
142 "+ flags"
143 else
144 " ");
145 }
146};
147
148pub const FlagRegister = packed struct {
149 zero: bool,
150 negative: bool,
151 carry: bool,
152 carry_enabled: bool,
153 interrupt0_enabled: bool,
154 interrupt1_enabled: bool,
155 interrupt2_enabled: bool,
156 interrupt3_enabled: bool,
157 reserved: u8 = 0,
158};
lib/std/spu/interpreter.zig deleted-163
...@@ -1,163 +0,0 @@
1const std = @import("std");
2const log = std.log.scoped(.SPU_2_Interpreter);
3usingnamespace @import("defines.zig");
4
5pub fn Interpreter(comptime Bus: type) type {
6 return struct {
7 ip: u16 = 0,
8 sp: u16 = undefined,
9 bp: u16 = undefined,
10 fr: FlagRegister = @bitCast(FlagRegister, @as(u16, 0)),
11 /// This is set to true when we hit an undefined0 instruction, allowing it to
12 /// be used as a trap for testing purposes
13 undefined0: bool = false,
14 /// This is set to true when we hit an undefined1 instruction, allowing it to
15 /// be used as a trap for testing purposes. undefined1 is used as a breakpoint.
16 undefined1: bool = false,
17 bus: Bus,
18
19 pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void {
20 var count: usize = 0;
21 while (size == null or count < size.?) {
22 count += 1;
23 var instruction = @bitCast(Instruction, self.bus.read16(self.ip));
24
25 log.debug("Executing {}\n", .{instruction});
26
27 self.ip +%= 2;
28
29 const execute = switch (instruction.condition) {
30 .always => true,
31 .not_zero => !self.fr.zero,
32 .when_zero => self.fr.zero,
33 .overflow => self.fr.carry,
34 ExecutionCondition.greater_or_equal_zero => !self.fr.negative,
35 else => return error.unimplemented,
36 };
37
38 if (execute) {
39 const val0 = switch (instruction.input0) {
40 .zero => @as(u16, 0),
41 .immediate => i: {
42 const val = self.bus.read16(@intCast(u16, self.ip));
43 self.ip +%= 2;
44 break :i val;
45 },
46 else => |e| e: {
47 // peek or pop; show value at current SP, and if pop, increment sp
48 const val = self.bus.read16(self.sp);
49 if (e == .pop) {
50 self.sp +%= 2;
51 }
52 break :e val;
53 },
54 };
55 const val1 = switch (instruction.input1) {
56 .zero => @as(u16, 0),
57 .immediate => i: {
58 const val = self.bus.read16(@intCast(u16, self.ip));
59 self.ip +%= 2;
60 break :i val;
61 },
62 else => |e| e: {
63 // peek or pop; show value at current SP, and if pop, increment sp
64 const val = self.bus.read16(self.sp);
65 if (e == .pop) {
66 self.sp +%= 2;
67 }
68 break :e val;
69 },
70 };
71
72 const output: u16 = switch (instruction.command) {
73 .get => self.bus.read16(self.bp +% (2 *% val0)),
74 .set => a: {
75 self.bus.write16(self.bp +% 2 *% val0, val1);
76 break :a val1;
77 },
78 .load8 => self.bus.read8(val0),
79 .load16 => self.bus.read16(val0),
80 .store8 => a: {
81 const val = @truncate(u8, val1);
82 self.bus.write8(val0, val);
83 break :a val;
84 },
85 .store16 => a: {
86 self.bus.write16(val0, val1);
87 break :a val1;
88 },
89 .copy => val0,
90 .add => a: {
91 var val: u16 = undefined;
92 self.fr.carry = @addWithOverflow(u16, val0, val1, &val);
93 break :a val;
94 },
95 .sub => a: {
96 var val: u16 = undefined;
97 self.fr.carry = @subWithOverflow(u16, val0, val1, &val);
98 break :a val;
99 },
100 .spset => a: {
101 self.sp = val0;
102 break :a val0;
103 },
104 .bpset => a: {
105 self.bp = val0;
106 break :a val0;
107 },
108 .frset => a: {
109 const val = (@bitCast(u16, self.fr) & val1) | (val0 & ~val1);
110 self.fr = @bitCast(FlagRegister, val);
111 break :a val;
112 },
113 .bswap => (val0 >> 8) | (val0 << 8),
114 .bpget => self.bp,
115 .spget => self.sp,
116 .ipget => self.ip +% (2 *% val0),
117 .lsl => val0 << 1,
118 .lsr => val0 >> 1,
119 .@"and" => val0 & val1,
120 .@"or" => val0 | val1,
121 .xor => val0 ^ val1,
122 .not => ~val0,
123 .undefined0 => {
124 self.undefined0 = true;
125 // Break out of the loop, and let the caller decide what to do
126 return;
127 },
128 .undefined1 => {
129 self.undefined1 = true;
130 // Break out of the loop, and let the caller decide what to do
131 return;
132 },
133 .signext => if ((val0 & 0x80) != 0)
134 (val0 & 0xFF) | 0xFF00
135 else
136 (val0 & 0xFF),
137 else => return error.unimplemented,
138 };
139
140 switch (instruction.output) {
141 .discard => {},
142 .push => {
143 self.sp -%= 2;
144 self.bus.write16(self.sp, output);
145 },
146 .jump => {
147 self.ip = output;
148 },
149 else => return error.unimplemented,
150 }
151 if (instruction.modify_flags) {
152 self.fr.negative = (output & 0x8000) != 0;
153 self.fr.zero = (output == 0x0000);
154 }
155 } else {
156 if (instruction.input0 == .immediate) self.ip +%= 2;
157 if (instruction.input1 == .immediate) self.ip +%= 2;
158 break;
159 }
160 }
161 }
162 };
163}
lib/std/std.zig-3
...@@ -80,9 +80,6 @@ pub const valgrind = @import("valgrind.zig");...@@ -80,9 +80,6 @@ pub const valgrind = @import("valgrind.zig");
80pub const zig = @import("zig.zig");80pub const zig = @import("zig.zig");
81pub const start = @import("start.zig");81pub const start = @import("start.zig");
8282
83// TODO move this
84pub const spu = @import("spu.zig");
85
86// This forces the start.zig file to be imported, and the comptime logic inside that83// This forces the start.zig file to be imported, and the comptime logic inside that
87// file decides whether to export any appropriate start symbols.84// file decides whether to export any appropriate start symbols.
88comptime {85comptime {
lib/std/target.zig+2-1
...@@ -663,7 +663,8 @@ pub const Target = struct {...@@ -663,7 +663,8 @@ pub const Target = struct {
663 renderscript32,663 renderscript32,
664 renderscript64,664 renderscript64,
665 ve,665 ve,
666 // Non-LLVM targets go here666 // Stage1 currently assumes that architectures above this comment
667 // map one-to-one with the ZigLLVM_ArchType enum.
667 spu_2,668 spu_2,
668669
669 pub fn isARM(arch: Arch) bool {670 pub fn isARM(arch: Arch) bool {
src-self-hosted/codegen/spu-mk2.zig+160-1
...@@ -1,4 +1,163 @@...@@ -1,4 +1,163 @@
1pub usingnamespace @import("std").spu;1const std = @import("std");
2
3pub const Interpreter = @import("spu-mk2/interpreter.zig").Interpreter;
4
5pub const ExecutionCondition = enum(u3) {
6 always = 0,
7 when_zero = 1,
8 not_zero = 2,
9 greater_zero = 3,
10 less_than_zero = 4,
11 greater_or_equal_zero = 5,
12 less_or_equal_zero = 6,
13 overflow = 7,
14};
15
16pub const InputBehaviour = enum(u2) {
17 zero = 0,
18 immediate = 1,
19 peek = 2,
20 pop = 3,
21};
22
23pub const OutputBehaviour = enum(u2) {
24 discard = 0,
25 push = 1,
26 jump = 2,
27 jump_relative = 3,
28};
29
30pub const Command = enum(u5) {
31 copy = 0,
32 ipget = 1,
33 get = 2,
34 set = 3,
35 store8 = 4,
36 store16 = 5,
37 load8 = 6,
38 load16 = 7,
39 undefined0 = 8,
40 undefined1 = 9,
41 frget = 10,
42 frset = 11,
43 bpget = 12,
44 bpset = 13,
45 spget = 14,
46 spset = 15,
47 add = 16,
48 sub = 17,
49 mul = 18,
50 div = 19,
51 mod = 20,
52 @"and" = 21,
53 @"or" = 22,
54 xor = 23,
55 not = 24,
56 signext = 25,
57 rol = 26,
58 ror = 27,
59 bswap = 28,
60 asr = 29,
61 lsl = 30,
62 lsr = 31,
63};
64
65pub const Instruction = packed struct {
66 condition: ExecutionCondition,
67 input0: InputBehaviour,
68 input1: InputBehaviour,
69 modify_flags: bool,
70 output: OutputBehaviour,
71 command: Command,
72 reserved: u1 = 0,
73
74 pub fn format(instr: Instruction, comptime fmt: []const u8, options: std.fmt.FormatOptions, out: anytype) !void {
75 try std.fmt.format(out, "0x{x:0<4} ", .{@bitCast(u16, instr)});
76 try out.writeAll(switch (instr.condition) {
77 .always => " ",
78 .when_zero => "== 0",
79 .not_zero => "!= 0",
80 .greater_zero => " > 0",
81 .less_than_zero => " < 0",
82 .greater_or_equal_zero => ">= 0",
83 .less_or_equal_zero => "<= 0",
84 .overflow => "ovfl",
85 });
86 try out.writeAll(" ");
87 try out.writeAll(switch (instr.input0) {
88 .zero => "zero",
89 .immediate => "imm ",
90 .peek => "peek",
91 .pop => "pop ",
92 });
93 try out.writeAll(" ");
94 try out.writeAll(switch (instr.input1) {
95 .zero => "zero",
96 .immediate => "imm ",
97 .peek => "peek",
98 .pop => "pop ",
99 });
100 try out.writeAll(" ");
101 try out.writeAll(switch (instr.command) {
102 .copy => "copy ",
103 .ipget => "ipget ",
104 .get => "get ",
105 .set => "set ",
106 .store8 => "store8 ",
107 .store16 => "store16 ",
108 .load8 => "load8 ",
109 .load16 => "load16 ",
110 .undefined0 => "undefined",
111 .undefined1 => "undefined",
112 .frget => "frget ",
113 .frset => "frset ",
114 .bpget => "bpget ",
115 .bpset => "bpset ",
116 .spget => "spget ",
117 .spset => "spset ",
118 .add => "add ",
119 .sub => "sub ",
120 .mul => "mul ",
121 .div => "div ",
122 .mod => "mod ",
123 .@"and" => "and ",
124 .@"or" => "or ",
125 .xor => "xor ",
126 .not => "not ",
127 .signext => "signext ",
128 .rol => "rol ",
129 .ror => "ror ",
130 .bswap => "bswap ",
131 .asr => "asr ",
132 .lsl => "lsl ",
133 .lsr => "lsr ",
134 });
135 try out.writeAll(" ");
136 try out.writeAll(switch (instr.output) {
137 .discard => "discard",
138 .push => "push ",
139 .jump => "jmp ",
140 .jump_relative => "rjmp ",
141 });
142 try out.writeAll(" ");
143 try out.writeAll(if (instr.modify_flags)
144 "+ flags"
145 else
146 " ");
147 }
148};
149
150pub const FlagRegister = packed struct {
151 zero: bool,
152 negative: bool,
153 carry: bool,
154 carry_enabled: bool,
155 interrupt0_enabled: bool,
156 interrupt1_enabled: bool,
157 interrupt2_enabled: bool,
158 interrupt3_enabled: bool,
159 reserved: u8 = 0,
160};
2161
3pub const Register = enum {162pub const Register = enum {
4 dummy,163 dummy,
src-self-hosted/codegen/spu-mk2/interpreter.zig created+166
...@@ -0,0 +1,166 @@
1const std = @import("std");
2const log = std.log.scoped(.SPU_2_Interpreter);
3const spu = @import("../spu-mk2.zig");
4const FlagRegister = spu.FlagRegister;
5const Instruction = spu.Instruction;
6const ExecutionCondition = spu.ExecutionCondition;
7
8pub fn Interpreter(comptime Bus: type) type {
9 return struct {
10 ip: u16 = 0,
11 sp: u16 = undefined,
12 bp: u16 = undefined,
13 fr: FlagRegister = @bitCast(FlagRegister, @as(u16, 0)),
14 /// This is set to true when we hit an undefined0 instruction, allowing it to
15 /// be used as a trap for testing purposes
16 undefined0: bool = false,
17 /// This is set to true when we hit an undefined1 instruction, allowing it to
18 /// be used as a trap for testing purposes. undefined1 is used as a breakpoint.
19 undefined1: bool = false,
20 bus: Bus,
21
22 pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void {
23 var count: usize = 0;
24 while (size == null or count < size.?) {
25 count += 1;
26 var instruction = @bitCast(Instruction, self.bus.read16(self.ip));
27
28 log.debug("Executing {}\n", .{instruction});
29
30 self.ip +%= 2;
31
32 const execute = switch (instruction.condition) {
33 .always => true,
34 .not_zero => !self.fr.zero,
35 .when_zero => self.fr.zero,
36 .overflow => self.fr.carry,
37 ExecutionCondition.greater_or_equal_zero => !self.fr.negative,
38 else => return error.Unimplemented,
39 };
40
41 if (execute) {
42 const val0 = switch (instruction.input0) {
43 .zero => @as(u16, 0),
44 .immediate => i: {
45 const val = self.bus.read16(@intCast(u16, self.ip));
46 self.ip +%= 2;
47 break :i val;
48 },
49 else => |e| e: {
50 // peek or pop; show value at current SP, and if pop, increment sp
51 const val = self.bus.read16(self.sp);
52 if (e == .pop) {
53 self.sp +%= 2;
54 }
55 break :e val;
56 },
57 };
58 const val1 = switch (instruction.input1) {
59 .zero => @as(u16, 0),
60 .immediate => i: {
61 const val = self.bus.read16(@intCast(u16, self.ip));
62 self.ip +%= 2;
63 break :i val;
64 },
65 else => |e| e: {
66 // peek or pop; show value at current SP, and if pop, increment sp
67 const val = self.bus.read16(self.sp);
68 if (e == .pop) {
69 self.sp +%= 2;
70 }
71 break :e val;
72 },
73 };
74
75 const output: u16 = switch (instruction.command) {
76 .get => self.bus.read16(self.bp +% (2 *% val0)),
77 .set => a: {
78 self.bus.write16(self.bp +% 2 *% val0, val1);
79 break :a val1;
80 },
81 .load8 => self.bus.read8(val0),
82 .load16 => self.bus.read16(val0),
83 .store8 => a: {
84 const val = @truncate(u8, val1);
85 self.bus.write8(val0, val);
86 break :a val;
87 },
88 .store16 => a: {
89 self.bus.write16(val0, val1);
90 break :a val1;
91 },
92 .copy => val0,
93 .add => a: {
94 var val: u16 = undefined;
95 self.fr.carry = @addWithOverflow(u16, val0, val1, &val);
96 break :a val;
97 },
98 .sub => a: {
99 var val: u16 = undefined;
100 self.fr.carry = @subWithOverflow(u16, val0, val1, &val);
101 break :a val;
102 },
103 .spset => a: {
104 self.sp = val0;
105 break :a val0;
106 },
107 .bpset => a: {
108 self.bp = val0;
109 break :a val0;
110 },
111 .frset => a: {
112 const val = (@bitCast(u16, self.fr) & val1) | (val0 & ~val1);
113 self.fr = @bitCast(FlagRegister, val);
114 break :a val;
115 },
116 .bswap => (val0 >> 8) | (val0 << 8),
117 .bpget => self.bp,
118 .spget => self.sp,
119 .ipget => self.ip +% (2 *% val0),
120 .lsl => val0 << 1,
121 .lsr => val0 >> 1,
122 .@"and" => val0 & val1,
123 .@"or" => val0 | val1,
124 .xor => val0 ^ val1,
125 .not => ~val0,
126 .undefined0 => {
127 self.undefined0 = true;
128 // Break out of the loop, and let the caller decide what to do
129 return;
130 },
131 .undefined1 => {
132 self.undefined1 = true;
133 // Break out of the loop, and let the caller decide what to do
134 return;
135 },
136 .signext => if ((val0 & 0x80) != 0)
137 (val0 & 0xFF) | 0xFF00
138 else
139 (val0 & 0xFF),
140 else => return error.Unimplemented,
141 };
142
143 switch (instruction.output) {
144 .discard => {},
145 .push => {
146 self.sp -%= 2;
147 self.bus.write16(self.sp, output);
148 },
149 .jump => {
150 self.ip = output;
151 },
152 else => return error.Unimplemented,
153 }
154 if (instruction.modify_flags) {
155 self.fr.negative = (output & 0x8000) != 0;
156 self.fr.zero = (output == 0x0000);
157 }
158 } else {
159 if (instruction.input0 == .immediate) self.ip +%= 2;
160 if (instruction.input1 == .immediate) self.ip +%= 2;
161 break;
162 }
163 }
164 }
165 };
166}
src-self-hosted/link.zig+1-1
...@@ -7,7 +7,7 @@ const Package = @import("Package.zig");...@@ -7,7 +7,7 @@ const Package = @import("Package.zig");
7const Type = @import("type.zig").Type;7const Type = @import("type.zig").Type;
8const build_options = @import("build_options");8const build_options = @import("build_options");
99
10const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;10pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
1111
12pub const Options = struct {12pub const Options = struct {
13 target: std.Target,13 target: std.Target,
src-self-hosted/link/Elf.zig+21-18
...@@ -14,12 +14,10 @@ const leb128 = std.debug.leb;...@@ -14,12 +14,10 @@ const leb128 = std.debug.leb;
14const Package = @import("../Package.zig");14const Package = @import("../Package.zig");
15const Value = @import("../value.zig").Value;15const Value = @import("../value.zig").Value;
16const Type = @import("../type.zig").Type;16const Type = @import("../type.zig").Type;
17const build_options = @import("build_options");
18const link = @import("../link.zig");17const link = @import("../link.zig");
19const File = link.File;18const File = link.File;
20const Elf = @This();19const Elf = @This();
2120
22const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
23const default_entry_addr = 0x8000000;21const default_entry_addr = 0x8000000;
2422
25// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented.23// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented.
...@@ -249,8 +247,8 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf {...@@ -249,8 +247,8 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf {
249 .allocator = allocator,247 .allocator = allocator,
250 },248 },
251 .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {249 .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
252 16, 32 => .p32,250 0 ... 32 => .p32,
253 64 => .p64,251 33 ... 64 => .p64,
254 else => return error.UnsupportedELFArchitecture,252 else => return error.UnsupportedELFArchitecture,
255 },253 },
256 };254 };
...@@ -278,8 +276,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf...@@ -278,8 +276,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf
278 .file = file,276 .file = file,
279 },277 },
280 .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {278 .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
281 16, 32 => .p32,279 0 ... 32 => .p32,
282 64 => .p64,280 33 ... 64 => .p64,
283 else => return error.UnsupportedELFArchitecture,281 else => return error.UnsupportedELFArchitecture,
284 },282 },
285 .shdr_table_dirty = true,283 .shdr_table_dirty = true,
...@@ -346,7 +344,7 @@ fn getDebugLineProgramEnd(self: Elf) u32 {...@@ -346,7 +344,7 @@ fn getDebugLineProgramEnd(self: Elf) u32 {
346344
347/// Returns end pos of collision, if any.345/// Returns end pos of collision, if any.
348fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {346fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
349 const small_ptr = self.base.options.target.cpu.arch.ptrBitWidth() == 32;347 const small_ptr = self.ptr_width == .p32;
350 const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);348 const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);
351 if (start < ehdr_size)349 if (start < ehdr_size)
352 return ehdr_size;350 return ehdr_size;
...@@ -487,7 +485,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {...@@ -487,7 +485,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
487 // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at.485 // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at.
488 // we'll need to re-use that function anyway, in case the GOT grows and overlaps something486 // we'll need to re-use that function anyway, in case the GOT grows and overlaps something
489 // else in virtual memory.487 // else in virtual memory.
490 const got_addr = if (self.base.options.target.cpu.arch.ptrBitWidth() == 16) @as(u32, 0x8000) else 0x4000000;488 const got_addr: u32 = if (self.base.options.target.cpu.arch.ptrBitWidth() >= 32) 0x4000000 else 0x8000;
491 try self.program_headers.append(self.base.allocator, .{489 try self.program_headers.append(self.base.allocator, .{
492 .p_type = elf.PT_LOAD,490 .p_type = elf.PT_LOAD,
493 .p_offset = off,491 .p_offset = off,
...@@ -864,7 +862,7 @@ pub fn flush(self: *Elf, module: *Module) !void {...@@ -864,7 +862,7 @@ pub fn flush(self: *Elf, module: *Module) !void {
864 // Write the form for the compile unit, which must match the abbrev table above.862 // Write the form for the compile unit, which must match the abbrev table above.
865 const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path);863 const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path);
866 const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path);864 const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path);
867 const producer_strp = try self.makeDebugString(producer_string);865 const producer_strp = try self.makeDebugString(link.producer_string);
868 // Currently only one compilation unit is supported, so the address range is simply866 // Currently only one compilation unit is supported, so the address range is simply
869 // identical to the main program header virtual address and memory size.867 // identical to the main program header virtual address and memory size.
870 const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];868 const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];
...@@ -2152,29 +2150,28 @@ pub fn deleteExport(self: *Elf, exp: Export) void {...@@ -2152,29 +2150,28 @@ pub fn deleteExport(self: *Elf, exp: Export) void {
2152fn writeProgHeader(self: *Elf, index: usize) !void {2150fn writeProgHeader(self: *Elf, index: usize) !void {
2153 const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();2151 const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
2154 const offset = self.program_headers.items[index].p_offset;2152 const offset = self.program_headers.items[index].p_offset;
2155 switch (self.base.options.target.cpu.arch.ptrBitWidth()) {2153 switch (self.ptr_width) {
2156 32 => {2154 .p32 => {
2157 var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};2155 var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};
2158 if (foreign_endian) {2156 if (foreign_endian) {
2159 bswapAllFields(elf.Elf32_Phdr, &phdr[0]);2157 bswapAllFields(elf.Elf32_Phdr, &phdr[0]);
2160 }2158 }
2161 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);2159 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
2162 },2160 },
2163 64 => {2161 .p64 => {
2164 var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]};2162 var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]};
2165 if (foreign_endian) {2163 if (foreign_endian) {
2166 bswapAllFields(elf.Elf64_Phdr, &phdr[0]);2164 bswapAllFields(elf.Elf64_Phdr, &phdr[0]);
2167 }2165 }
2168 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);2166 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
2169 },2167 },
2170 else => return error.UnsupportedArchitecture,
2171 }2168 }
2172}2169}
21732170
2174fn writeSectHeader(self: *Elf, index: usize) !void {2171fn writeSectHeader(self: *Elf, index: usize) !void {
2175 const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();2172 const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
2176 switch (self.base.options.target.cpu.arch.ptrBitWidth()) {2173 switch (self.ptr_width) {
2177 32 => {2174 .p32 => {
2178 var shdr: [1]elf.Elf32_Shdr = undefined;2175 var shdr: [1]elf.Elf32_Shdr = undefined;
2179 shdr[0] = sectHeaderTo32(self.sections.items[index]);2176 shdr[0] = sectHeaderTo32(self.sections.items[index]);
2180 if (foreign_endian) {2177 if (foreign_endian) {
...@@ -2183,7 +2180,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void {...@@ -2183,7 +2180,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
2183 const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr);2180 const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr);
2184 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);2181 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
2185 },2182 },
2186 64 => {2183 .p64 => {
2187 var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]};2184 var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]};
2188 if (foreign_endian) {2185 if (foreign_endian) {
2189 bswapAllFields(elf.Elf64_Shdr, &shdr[0]);2186 bswapAllFields(elf.Elf64_Shdr, &shdr[0]);
...@@ -2191,14 +2188,13 @@ fn writeSectHeader(self: *Elf, index: usize) !void {...@@ -2191,14 +2188,13 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
2191 const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr);2188 const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr);
2192 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);2189 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
2193 },2190 },
2194 else => return error.UnsupportedArchitecture,
2195 }2191 }
2196}2192}
21972193
2198fn writeOffsetTableEntry(self: *Elf, index: usize) !void {2194fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
2199 const shdr = &self.sections.items[self.got_section_index.?];2195 const shdr = &self.sections.items[self.got_section_index.?];
2200 const phdr = &self.program_headers.items[self.phdr_got_index.?];2196 const phdr = &self.program_headers.items[self.phdr_got_index.?];
2201 const entry_size: u16 = self.base.options.target.cpu.arch.ptrBitWidth() / 8;2197 const entry_size: u16 = self.archPtrWidthBytes();
2202 if (self.offset_table_count_dirty) {2198 if (self.offset_table_count_dirty) {
2203 // TODO Also detect virtual address collisions.2199 // TODO Also detect virtual address collisions.
2204 const allocated_size = self.allocatedSize(shdr.sh_offset);2200 const allocated_size = self.allocatedSize(shdr.sh_offset);
...@@ -2351,6 +2347,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void {...@@ -2351,6 +2347,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void {
2351 }2347 }
2352}2348}
23532349
2350/// Always 4 or 8 depending on whether this is 32-bit ELF or 64-bit ELF.
2354fn ptrWidthBytes(self: Elf) u8 {2351fn ptrWidthBytes(self: Elf) u8 {
2355 return switch (self.ptr_width) {2352 return switch (self.ptr_width) {
2356 .p32 => 4,2353 .p32 => 4,
...@@ -2358,6 +2355,12 @@ fn ptrWidthBytes(self: Elf) u8 {...@@ -2358,6 +2355,12 @@ fn ptrWidthBytes(self: Elf) u8 {
2358 };2355 };
2359}2356}
23602357
2358/// Does not necessarily match `ptrWidthBytes` for example can be 2 bytes
2359/// in a 32-bit ELF file.
2360fn archPtrWidthBytes(self: Elf) u8 {
2361 return @intCast(u8, self.base.options.target.cpu.arch.ptrBitWidth() / 8);
2362}
2363
2361/// The reloc offset for the virtual address of a function in its Line Number Program.2364/// The reloc offset for the virtual address of a function in its Line Number Program.
2362/// Size is a virtual address integer.2365/// Size is a virtual address integer.
2363const dbg_line_vaddr_reloc_index = 3;2366const dbg_line_vaddr_reloc_index = 3;
src-self-hosted/test.zig+115-90
...@@ -571,95 +571,6 @@ pub const TestContext = struct {...@@ -571,95 +571,6 @@ pub const TestContext = struct {
571 std.debug.assert(!case.cbe);571 std.debug.assert(!case.cbe);
572572
573 update_node.estimated_total_items = 4;573 update_node.estimated_total_items = 4;
574 if (case.target.cpu_arch) |arch| {
575 if (arch == .spu_2) {
576 if (case.target.os_tag) |os| {
577 if (os != .freestanding) {
578 std.debug.panic("Only freestanding makes sense for SPU-II tests!", .{});
579 }
580 } else {
581 std.debug.panic("SPU_2 has no native OS, check the test!", .{});
582 }
583
584 var interpreter = std.spu.interpreter(struct {
585 RAM: [0x10000]u8 = undefined,
586
587 pub fn read8(bus: @This(), addr: u16) u8 {
588 return bus.RAM[addr];
589 }
590 pub fn read16(bus: @This(), addr: u16) u16 {
591 return std.mem.readIntLittle(u16, bus.RAM[addr..][0..2]);
592 }
593
594 pub fn write8(bus: *@This(), addr: u16, val: u8) void {
595 bus.RAM[addr] = val;
596 }
597
598 pub fn write16(bus: *@This(), addr: u16, val: u16) void {
599 std.mem.writeIntLittle(u16, bus.RAM[addr..][0..2], val);
600 }
601 }){
602 .bus = .{},
603 };
604
605 {
606 var load_node = update_node.start("load", null);
607 load_node.activate();
608 defer load_node.end();
609
610 var file = try tmp.dir.openFile(bin_name, .{ .read = true });
611 defer file.close();
612
613 const header = try std.elf.readHeader(file);
614 var iterator = header.program_header_iterator(file);
615
616 var none_loaded = true;
617
618 while (try iterator.next()) |phdr| {
619 if (phdr.p_type != std.elf.PT_LOAD) {
620 std.debug.print("Encountered unexpected ELF program header: type {}\n", .{phdr.p_type});
621 std.process.exit(1);
622 }
623 if (phdr.p_paddr != phdr.p_vaddr) {
624 std.debug.print("Physical address does not match virtual address in ELF header!\n", .{});
625 std.process.exit(1);
626 }
627 if (phdr.p_filesz != phdr.p_memsz) {
628 std.debug.print("Physical size does not match virtual size in ELF header!\n", .{});
629 std.process.exit(1);
630 }
631 if ((try file.pread(interpreter.bus.RAM[phdr.p_paddr .. phdr.p_paddr + phdr.p_filesz], phdr.p_offset)) != phdr.p_filesz) {
632 std.debug.print("Read less than expected from ELF file!", .{});
633 std.process.exit(1);
634 }
635 std.log.scoped(.spu2_test).debug("Loaded 0x{x} bytes to 0x{x:0<4}\n", .{ phdr.p_filesz, phdr.p_paddr });
636 none_loaded = false;
637 }
638 if (none_loaded) {
639 std.debug.print("No data found in ELF file!\n", .{});
640 std.process.exit(1);
641 }
642 }
643
644 var exec_node = update_node.start("execute", null);
645 exec_node.activate();
646 defer exec_node.end();
647
648 var blocks: u16 = 1000;
649 const block_size = 1000;
650 while (!interpreter.undefined0) {
651 const pre_ip = interpreter.ip;
652 if (blocks > 0) {
653 blocks -= 1;
654 try interpreter.ExecuteBlock(block_size);
655 if (pre_ip == interpreter.ip) {
656 std.debug.print("Infinite loop detected in SPU II test!\n", .{});
657 std.process.exit(1);
658 }
659 }
660 }
661 }
662 }
663 var exec_result = x: {574 var exec_result = x: {
664 var exec_node = update_node.start("execute", null);575 var exec_node = update_node.start("execute", null);
665 exec_node.activate();576 exec_node.activate();
...@@ -672,7 +583,10 @@ pub const TestContext = struct {...@@ -672,7 +583,10 @@ pub const TestContext = struct {
672583
673 switch (case.target.getExternalExecutor()) {584 switch (case.target.getExternalExecutor()) {
674 .native => try argv.append(exe_path),585 .native => try argv.append(exe_path),
675 .unavailable => return, // No executor available; pass test.586 .unavailable => {
587 try self.runInterpreterIfAvailable(allocator, &exec_node, case, tmp.dir, bin_name);
588 return; // Pass test.
589 },
676590
677 .qemu => |qemu_bin_name| if (enable_qemu) {591 .qemu => |qemu_bin_name| if (enable_qemu) {
678 // TODO Ability for test cases to specify whether to link libc.592 // TODO Ability for test cases to specify whether to link libc.
...@@ -745,4 +659,115 @@ pub const TestContext = struct {...@@ -745,4 +659,115 @@ pub const TestContext = struct {
745 }659 }
746 }660 }
747 }661 }
662
663 fn runInterpreterIfAvailable(
664 self: *TestContext,
665 gpa: *Allocator,
666 node: *std.Progress.Node,
667 case: Case,
668 tmp_dir: std.fs.Dir,
669 bin_name: []const u8,
670 ) !void {
671 const arch = case.target.cpu_arch orelse return;
672 switch (arch) {
673 .spu_2 => return self.runSpu2Interpreter(gpa, node, case, tmp_dir, bin_name),
674 else => return,
675 }
676 }
677
678 fn runSpu2Interpreter(
679 self: *TestContext,
680 gpa: *Allocator,
681 update_node: *std.Progress.Node,
682 case: Case,
683 tmp_dir: std.fs.Dir,
684 bin_name: []const u8,
685 ) !void {
686 const spu = @import("codegen/spu-mk2.zig");
687 if (case.target.os_tag) |os| {
688 if (os != .freestanding) {
689 std.debug.panic("Only freestanding makes sense for SPU-II tests!", .{});
690 }
691 } else {
692 std.debug.panic("SPU_2 has no native OS, check the test!", .{});
693 }
694
695 var interpreter = spu.Interpreter(struct {
696 RAM: [0x10000]u8 = undefined,
697
698 pub fn read8(bus: @This(), addr: u16) u8 {
699 return bus.RAM[addr];
700 }
701 pub fn read16(bus: @This(), addr: u16) u16 {
702 return std.mem.readIntLittle(u16, bus.RAM[addr..][0..2]);
703 }
704
705 pub fn write8(bus: *@This(), addr: u16, val: u8) void {
706 bus.RAM[addr] = val;
707 }
708
709 pub fn write16(bus: *@This(), addr: u16, val: u16) void {
710 std.mem.writeIntLittle(u16, bus.RAM[addr..][0..2], val);
711 }
712 }){
713 .bus = .{},
714 };
715
716 {
717 var load_node = update_node.start("load", null);
718 load_node.activate();
719 defer load_node.end();
720
721 var file = try tmp_dir.openFile(bin_name, .{ .read = true });
722 defer file.close();
723
724 const header = try std.elf.readHeader(file);
725 var iterator = header.program_header_iterator(file);
726
727 var none_loaded = true;
728
729 while (try iterator.next()) |phdr| {
730 if (phdr.p_type != std.elf.PT_LOAD) {
731 std.debug.print("Encountered unexpected ELF program header: type {}\n", .{phdr.p_type});
732 std.process.exit(1);
733 }
734 if (phdr.p_paddr != phdr.p_vaddr) {
735 std.debug.print("Physical address does not match virtual address in ELF header!\n", .{});
736 std.process.exit(1);
737 }
738 if (phdr.p_filesz != phdr.p_memsz) {
739 std.debug.print("Physical size does not match virtual size in ELF header!\n", .{});
740 std.process.exit(1);
741 }
742 if ((try file.pread(interpreter.bus.RAM[phdr.p_paddr .. phdr.p_paddr + phdr.p_filesz], phdr.p_offset)) != phdr.p_filesz) {
743 std.debug.print("Read less than expected from ELF file!", .{});
744 std.process.exit(1);
745 }
746 std.log.scoped(.spu2_test).debug("Loaded 0x{x} bytes to 0x{x:0<4}\n", .{ phdr.p_filesz, phdr.p_paddr });
747 none_loaded = false;
748 }
749 if (none_loaded) {
750 std.debug.print("No data found in ELF file!\n", .{});
751 std.process.exit(1);
752 }
753 }
754
755 var exec_node = update_node.start("execute", null);
756 exec_node.activate();
757 defer exec_node.end();
758
759 var blocks: u16 = 1000;
760 const block_size = 1000;
761 while (!interpreter.undefined0) {
762 const pre_ip = interpreter.ip;
763 if (blocks > 0) {
764 blocks -= 1;
765 try interpreter.ExecuteBlock(block_size);
766 if (pre_ip == interpreter.ip) {
767 std.debug.print("Infinite loop detected in SPU II test!\n", .{});
768 std.process.exit(1);
769 }
770 }
771 }
772 }
748};773};
test/stage2/test.zig+2-1
...@@ -26,6 +26,8 @@ const wasi = std.zig.CrossTarget{...@@ -26,6 +26,8 @@ const wasi = std.zig.CrossTarget{
26pub fn addCases(ctx: *TestContext) !void {26pub fn addCases(ctx: *TestContext) !void {
27 try @import("zir.zig").addCases(ctx);27 try @import("zir.zig").addCases(ctx);
28 try @import("cbe.zig").addCases(ctx);28 try @import("cbe.zig").addCases(ctx);
29 try @import("spu-ii.zig").addCases(ctx);
30
29 {31 {
30 var case = ctx.exe("hello world with updates", linux_x64);32 var case = ctx.exe("hello world with updates", linux_x64);
3133
...@@ -785,5 +787,4 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -785,5 +787,4 @@ pub fn addCases(ctx: *TestContext) !void {
785 \\}787 \\}
786 \\extern var foo;788 \\extern var foo;
787 , &[_][]const u8{":4:1: error: unable to infer variable type"});789 , &[_][]const u8{":4:1: error: unable to infer variable type"});
788 try @import("spu-ii.zig").addCases(ctx);
789}790}